120 lines
3.7 KiB
Python
120 lines
3.7 KiB
Python
import numpy as np
|
|
|
|
|
|
def felli(x):
|
|
N = x.shape[0]
|
|
if N < 2:
|
|
raise ValueError("dimension must be greater than one")
|
|
exponents = np.arange(N) / (N - 1)
|
|
return float(np.sum((1e6 ** exponents) * (x ** 2)))
|
|
|
|
|
|
def escma():
|
|
#Initialization
|
|
|
|
# User defined input parameters
|
|
N = 10
|
|
xmean = np.random.rand(N)
|
|
sigma = 0.5
|
|
stopfitness = 1e-10
|
|
stopeval = int(1e3 * N**2)
|
|
|
|
# Strategy parameter setting: Selection
|
|
lambda_ = 4 + int(np.floor(3 * np.log(N)))
|
|
mu = lambda_ / 2.0
|
|
|
|
# muXone recombination weights
|
|
weights = np.log(mu + 0.5) - np.log(np.arange(1, int(mu) + 1))
|
|
mu = int(np.floor(mu))
|
|
weights = weights / np.sum(weights)
|
|
mueff = np.sum(weights)**2 / np.sum(weights**2)
|
|
|
|
# Strategy parameter setting: Adaptation
|
|
cc = (4 + mueff / N) / (N + 4 + 2 * mueff / N)
|
|
cs = (mueff + 2) / (N + mueff + 5)
|
|
c1 = 2 / ((N + 1.3)**2 + mueff)
|
|
cmu = min(1 - c1,
|
|
2 * (mueff - 2 + 1 / mueff) / ((N + 2)**2 + 2 * mueff))
|
|
damps = 1 + 2 * max(0, np.sqrt((mueff - 1) / (N + 1)) - 1) + cs
|
|
|
|
# Initialize dynamic (internal) strategy parameters and constants
|
|
pc = np.zeros(N)
|
|
ps = np.zeros(N)
|
|
B = np.eye(N)
|
|
D = np.eye(N)
|
|
C = B @ D @ (B @ D).T
|
|
eigeneval = 0
|
|
chiN = np.sqrt(N) * (1 - 1/(4*N) + 1/(21 * N**2))
|
|
|
|
#Generation Loop
|
|
counteval = 0
|
|
arx = np.zeros((N, lambda_))
|
|
arz = np.zeros((N, lambda_))
|
|
arfitness = np.zeros(lambda_)
|
|
|
|
while counteval < stopeval:
|
|
|
|
# Generate and evaluate lambda offspring
|
|
for k in range(lambda_):
|
|
arz[:, k] = np.random.randn(N)
|
|
arx[:, k] = xmean + sigma * (B @ D @ arz[:, k])
|
|
arfitness[k] = felli(arx[:, k])
|
|
counteval += 1
|
|
|
|
# Sort by fitness and compute weighted mean into xmean
|
|
idx = np.argsort(arfitness)
|
|
arfitness = arfitness[idx]
|
|
arindex = idx
|
|
|
|
xold = xmean.copy()
|
|
xmean = arx[:, arindex[:mu]] @ weights
|
|
zmean = arz[:, arindex[:mu]] @ weights
|
|
|
|
# Cumulation: Update evolution paths
|
|
ps = (1 - cs) * ps + np.sqrt(cs * (2 - cs) * mueff) * (B @ zmean)
|
|
norm_ps = np.linalg.norm(ps)
|
|
hsig = norm_ps / np.sqrt(1 - (1 - cs)**(2 * counteval / lambda_)) / chiN < \
|
|
(1.4 + 2 / (N + 1))
|
|
hsig = 1.0 if hsig else 0.0
|
|
|
|
pc = (1 - cc) * pc + hsig * np.sqrt(cc * (2 - cc) * mueff) * (B @ D @ zmean)
|
|
|
|
# Adapt covariance matrix C
|
|
BDz = B @ D @ arz[:, arindex[:mu]]
|
|
C = (1 - c1 - cmu) * C \
|
|
+ c1 * (np.outer(pc, pc) + (1 - hsig) * cc * (2 - cc) * C) \
|
|
+ cmu * BDz @ np.diag(weights) @ BDz.T
|
|
|
|
# Adapt step-size sigma
|
|
sigma = sigma * np.exp((cs / damps) * (norm_ps / chiN - 1))
|
|
|
|
# Update B and D from C (Eigenzerlegung, O(N^2))
|
|
if counteval - eigeneval > lambda_ / ((c1 + cmu) * N * 10):
|
|
eigeneval = counteval
|
|
# enforce symmetry
|
|
C = (C + C.T) / 2.0
|
|
eigvals, B = np.linalg.eigh(C)
|
|
D = np.diag(np.sqrt(eigvals))
|
|
|
|
# Break, if fitness is good enough
|
|
if arfitness[0] <= stopfitness:
|
|
break
|
|
|
|
# Escape flat fitness, or better terminate?
|
|
if arfitness[0] == arfitness[int(np.ceil(0.7 * lambda_)) - 1]:
|
|
sigma = sigma * np.exp(0.2 + cs / damps)
|
|
print("warning: flat fitness, consider reformulating the objective")
|
|
|
|
print(f"{counteval}: {arfitness[0]}")
|
|
|
|
#Final Message
|
|
print(f"{counteval}: {arfitness[0]}")
|
|
xmin = arx[:, arindex[0]]
|
|
return xmin
|
|
|
|
|
|
if __name__ == "__main__":
|
|
xmin = escma()
|
|
print("Bestes gefundenes x:", xmin)
|
|
print("f(xmin) =", felli(xmin))
|