Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bugfix cec2022 benchmarks #16

Merged
merged 2 commits into from
Sep 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions opfunu/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,12 @@ def get_functions_based_ndim(ndim=None):
return functions


def get_all_named_functions():
return [cls for classname, cls in FUNC_DATABASE if classname not in EXCLUDES]


def get_all_cec_functions():
return [cls for classname, cls in CEC_DATABASE if classname not in EXCLUDES]
def get_functions(ndim, continuous=None, linear=None, convex=None, unimodal=None, separable=None,
differentiable=None, scalable=None, randomized_term=None, parametric=None, modality=None):
functions = [cls for classname, cls in FUNC_DATABASE if classname not in EXCLUDES]
Expand Down
2 changes: 1 addition & 1 deletion opfunu/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def check_solution(self, x):
The solution
"""
if not self.dim_changeable and (len(x) != self._ndim):
raise ValueError(f"The length of solution should has {self._ndim} variables!")
raise ValueError(f"The length of solution should have {self._ndim} variables!")

def get_paras(self):
"""
Expand Down
76 changes: 31 additions & 45 deletions opfunu/cec_based/cec2022.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def evaluate(self, x, *args):
self.n_fe += 1
self.check_solution(x, self.dim_max, self.dim_supported)
z = np.dot(self.f_matrix, 0.5*(x - self.f_shift)/100)
return operator.expanded_scaffer_f6_func(z) + self.f_bias
return operator.rotated_expanded_schaffer_func(z) + self.f_bias


class F42022(F12022):
Expand All @@ -134,7 +134,7 @@ def evaluate(self, x, *args):
self.n_fe += 1
self.check_solution(x, self.dim_max, self.dim_supported)
z = np.dot(self.f_matrix, 5.12*(x - self.f_shift)/100)
return operator.expanded_scaffer_f6_func(z) + self.f_bias
return operator.non_continuous_rastrigin_func(z) + self.f_bias


class F52022(F12022):
Expand All @@ -160,7 +160,7 @@ def evaluate(self, x, *args):
self.n_fe += 1
self.check_solution(x, self.dim_max, self.dim_supported)
z = np.dot(self.f_matrix, 5.12*(x - self.f_shift)/100)
return operator.levy_func(z) + self.f_bias
return operator.levy_func(z, shift=1.0) + self.f_bias


class F62022(CecBenchmark):
Expand Down Expand Up @@ -213,9 +213,6 @@ def __init__(self, ndim=None, bounds=None, f_shift="shift_data_6", f_matrix="M_6
self.n1 = int(np.ceil(self.p[0] * self.ndim))
self.n2 = int(np.ceil(self.p[1] * self.ndim)) + self.n1
self.idx1, self.idx2, self.idx3 = self.f_shuffle[:self.n1], self.f_shuffle[self.n1:self.n2], self.f_shuffle[self.n2:self.ndim]
self.g1 = operator.bent_cigar_func
self.g2 = operator.hgbat_func
self.g3 = operator.rastrigin_func
self.paras = {"f_shift": self.f_shift, "f_bias": self.f_bias, "f_matrix": self.f_matrix, "f_shuffle": self.f_shuffle}

def evaluate(self, x, *args):
Expand All @@ -224,7 +221,9 @@ def evaluate(self, x, *args):
z = x - self.f_shift
z1 = np.concatenate((z[self.idx1], z[self.idx2], z[self.idx3]))
mz = np.dot(self.f_matrix, z1)
return self.g1(mz[:self.n1]) + self.g2(mz[self.n1:self.n2]) + self.g3(mz[self.n2:]) + self.f_bias
return (operator.bent_cigar_func(mz[:self.n1]) +
operator.hgbat_func(mz[self.n1:self.n2], shift=-1.0) +
operator.rastrigin_func(mz[self.n2:]) + self.f_bias)


class F72022(CecBenchmark):
Expand Down Expand Up @@ -281,12 +280,6 @@ def __init__(self, ndim=None, bounds=None, f_shift="shift_data_7", f_matrix="M_7
self.n5 = int(np.ceil(self.p[4] * self.ndim)) + self.n4
self.idx1, self.idx2, self.idx3 = self.f_shuffle[:self.n1], self.f_shuffle[self.n1:self.n2], self.f_shuffle[self.n2:self.n3]
self.idx4, self.idx5, self.idx6 = self.f_shuffle[self.n3:self.n4], self.f_shuffle[self.n4:self.n5], self.f_shuffle[self.n5:self.ndim]
self.g1 = operator.hgbat_func
self.g2 = operator.katsuura_func
self.g3 = operator.ackley_func
self.g4 = operator.rastrigin_func
self.g5 = operator.modified_schwefel_func
self.g6 = operator.schaffer_f7_func
self.paras = {"f_shift": self.f_shift, "f_bias": self.f_bias, "f_matrix": self.f_matrix, "f_shuffle": self.f_shuffle}

def evaluate(self, x, *args):
Expand All @@ -295,8 +288,12 @@ def evaluate(self, x, *args):
z = x - self.f_shift
z1 = np.concatenate((z[self.idx1], z[self.idx2], z[self.idx3], z[self.idx4], z[self.idx5], z[self.idx6]))
mz = np.dot(self.f_matrix, z1)
return self.g1(mz[:self.n1]) + self.g2(mz[self.n1:self.n2]) + self.g3(mz[self.n2:self.n3]) + \
self.g4(mz[self.n3:self.n4]) + self.g5(mz[self.n4:self.n5]) + self.g6(mz[self.n5:self.ndim]) + self.f_bias
return (operator.hgbat_func(mz[:self.n1], shift=-1.0) +
operator.katsuura_func(mz[self.n1:self.n2]) +
operator.ackley_func(mz[self.n2:self.n3]) +
operator.rastrigin_func(mz[self.n3:self.n4]) +
operator.modified_schwefel_func(mz[self.n4:self.n5]) +
operator.schaffer_f7_func(mz[self.n5:self.ndim]) + self.f_bias)


class F82022(CecBenchmark):
Expand Down Expand Up @@ -352,11 +349,6 @@ def __init__(self, ndim=None, bounds=None, f_shift="shift_data_8", f_matrix="M_8
self.n4 = int(np.ceil(self.p[3] * self.ndim)) + self.n3
self.idx1, self.idx2, self.idx3 = self.f_shuffle[:self.n1], self.f_shuffle[self.n1:self.n2], self.f_shuffle[self.n2:self.n3]
self.idx4, self.idx5 = self.f_shuffle[self.n3:self.n4], self.f_shuffle[self.n4:self.ndim]
self.g1 = operator.katsuura_func
self.g2 = operator.happy_cat_func
self.g3 = operator.expanded_griewank_rosenbrock_func
self.g4 = operator.modified_schwefel_func
self.g5 = operator.ackley_func
self.paras = {"f_shift": self.f_shift, "f_bias": self.f_bias, "f_matrix": self.f_matrix, "f_shuffle": self.f_shuffle}

def evaluate(self, x, *args):
Expand All @@ -365,8 +357,11 @@ def evaluate(self, x, *args):
z = x - self.f_shift
z1 = np.concatenate((z[self.idx1], z[self.idx2], z[self.idx3], z[self.idx4], z[self.idx5]))
mz = np.dot(self.f_matrix, z1)
return self.g1(mz[:self.n1]) + self.g2(mz[self.n1:self.n2]) + self.g3(mz[self.n2:self.n3]) + \
self.g4(mz[self.n3:self.n4]) + self.g5(mz[self.n4:self.ndim]) + self.f_bias
return (operator.katsuura_func(mz[:self.n1]) +
operator.happy_cat_func(mz[self.n1:self.n2], shift=-1.0) +
operator.grie_rosen_cec_func(mz[self.n2:self.n3]) +
operator.modified_schwefel_func(mz[self.n3:self.n4]) +
operator.ackley_func(mz[self.n4:self.ndim]) + self.f_bias)


class F92022(CecBenchmark):
Expand Down Expand Up @@ -412,7 +407,7 @@ def __init__(self, ndim=None, bounds=None, f_shift="shift_data_9", f_matrix="M_9
self.f_global = f_bias
self.x_global = self.f_shift[0]
self.n_funcs = 5
self.xichmas = [10, 20, 30, 40, 50]
self.xichmas = [10, 20, 30, 40, 50] # aka delta in original CEC2022 logic
self.lamdas = [1, 1e-6, 1e-6, 1e-6, 1e-6]
self.bias = [0, 200, 300, 100, 400]
self.g0 = operator.rosenbrock_func
Expand Down Expand Up @@ -503,28 +498,25 @@ def __init__(self, ndim=None, bounds=None, f_shift="shift_data_10", f_matrix="M_
self.xichmas = [20, 10, 10]
self.lamdas = [1, 1, 1]
self.bias = [0, 200, 100]
self.g0 = operator.modified_schwefel_func
self.g1 = operator.rastrigin_func
self.g2 = operator.hgbat_func
self.paras = {"f_shift": self.f_shift, "f_bias": self.f_bias, "f_matrix": self.f_matrix}

def evaluate(self, x, *args):
self.n_fe += 1
self.check_solution(x, self.dim_max, self.dim_supported)

# 1. Rotated Schwefel's Function f12
z0 = np.dot(self.f_matrix[:self.ndim, :], 1000.*(x - self.f_shift[0])/100) + 1
g0 = self.lamdas[0] * self.g0(z0) + self.bias[0]
z0 = np.dot(self.f_matrix[:self.ndim, :], (1000./100)*(x - self.f_shift[0]))
g0 = self.lamdas[0] * operator.modified_schwefel_func(z0) + self.bias[0]
w0 = operator.calculate_weight(x - self.f_shift[0], self.xichmas[0])

# 2. Rotated Rastrigin’s Function f4
z1 = np.dot(self.f_matrix[self.ndim:2*self.ndim, :], 5.12*(x - self.f_shift[0])/100)
g1 = self.lamdas[1] * self.g1(z1) + self.bias[1]
z1 = np.dot(self.f_matrix[self.ndim:2*self.ndim, :], (5.12/100)*(x - self.f_shift[0]))
g1 = self.lamdas[1] * operator.rastrigin_func(z1) + self.bias[1]
w1 = operator.calculate_weight(x - self.f_shift[1], self.xichmas[1])

# 3. HGBat Function f7
z2 = np.dot(self.f_matrix[2*self.ndim:3*self.ndim, :], 5*(x - self.f_shift[0])/100)
g2 = self.lamdas[2] * self.g2(z2) + self.bias[2]
z2 = np.dot(self.f_matrix[2*self.ndim:3*self.ndim, :], (5/100)*(x - self.f_shift[0]))
g2 = self.lamdas[2] * operator.hgbat_func(z2, shift=-1.0) + self.bias[2]
w2 = operator.calculate_weight(x - self.f_shift[2], self.xichmas[2])

ws = np.array([w0, w1, w2])
Expand Down Expand Up @@ -579,7 +571,7 @@ def __init__(self, ndim=None, bounds=None, f_shift="shift_data_11", f_matrix="M_
self.xichmas = [20, 20, 30, 30, 20]
self.lamdas = [1e-26, 10, 1e-6, 10, 5e-4]
self.bias = [0, 200, 300, 400, 200]
self.g0 = operator.expanded_scaffer_f6_func
self.g0 = operator.rotated_expanded_schaffer_func
firestrand marked this conversation as resolved.
Show resolved Hide resolved
self.g1 = operator.modified_schwefel_func
self.g2 = operator.griewank_func
self.g3 = operator.rosenbrock_func
Expand Down Expand Up @@ -667,12 +659,6 @@ def __init__(self, ndim=None, bounds=None, f_shift="shift_data_12", f_matrix="M_
self.xichmas = [10, 20, 30, 40, 50, 60]
self.lamdas = [10, 10, 2.5, 1e-26, 1e-6, 5e-4]
self.bias = [0, 300, 500, 100, 400, 200]
self.g0 = operator.hgbat_func
self.g1 = operator.rastrigin_func
self.g2 = operator.modified_schwefel_func
self.g3 = operator.bent_cigar_func
self.g4 = operator.elliptic_func
self.g5 = operator.expanded_scaffer_f6_func
self.paras = {"f_shift": self.f_shift, "f_bias": self.f_bias, "f_matrix": self.f_matrix}

def evaluate(self, x, *args):
Expand All @@ -681,32 +667,32 @@ def evaluate(self, x, *args):

# 1. HGBat Function f7
z0 = np.dot(self.f_matrix[:self.ndim, :], 5.*(x - self.f_shift[0])/100)
g0 = self.lamdas[0] * self.g0(z0) + self.bias[0]
g0 = self.lamdas[0] * operator.hgbat_func(z0, shift=-1.0) + self.bias[0]
w0 = operator.calculate_weight(x - self.f_shift[0], self.xichmas[0])

# 2. Rastrigin’s Function f4
z1 = np.dot(self.f_matrix[self.ndim:2*self.ndim, :], 5.12*(x - self.f_shift[0])/100)
g1 = self.lamdas[1] * self.g1(z1) + self.bias[1]
g1 = self.lamdas[1] * operator.rastrigin_func(z1) + self.bias[1]
w1 = operator.calculate_weight(x - self.f_shift[1], self.xichmas[1])

# 3. Modified Schwefel's Function f12
z2 = np.dot(self.f_matrix[2*self.ndim:3*self.ndim, :], 1000.*(x - self.f_shift[0])/100)
g2 = self.lamdas[2] * self.g2(z2) + self.bias[2]
g2 = self.lamdas[2] * operator.modified_schwefel_func(z2) + self.bias[2]
w2 = operator.calculate_weight(x - self.f_shift[2], self.xichmas[2])

# 4. Bent Cigar Function f6
z3 = np.dot(self.f_matrix[3 * self.ndim:4 * self.ndim, :], x - self.f_shift[0])
g3 = self.lamdas[3] * self.g3(z3) + self.bias[3]
g3 = self.lamdas[3] * operator.bent_cigar_func(z3) + self.bias[3]
w3 = operator.calculate_weight(x - self.f_shift[3], self.xichmas[3])

# 5. High Conditioned Elliptic Function f8
z4 = np.dot(self.f_matrix[4 * self.ndim:5 * self.ndim, :], x - self.f_shift[0])
g4 = self.lamdas[4] * self.g4(z4) + self.bias[4]
g4 = self.lamdas[4] * operator.elliptic_func(z4) + self.bias[4]
w4 = operator.calculate_weight(x - self.f_shift[4], self.xichmas[4])

# 6. Expanded Schaffer’s F6 Function f3
z5 = np.dot(self.f_matrix[5 * self.ndim:6 * self.ndim, :], x - self.f_shift[0])
g5 = self.lamdas[5] * self.g5(z5) + self.bias[5]
g5 = self.lamdas[5] * operator.rotated_expanded_schaffer_func(z5) + self.bias[5]
w5 = operator.calculate_weight(x - self.f_shift[5], self.xichmas[5])

ws = np.array([w0, w1, w2, w3, w4, w5])
Expand Down
89 changes: 62 additions & 27 deletions opfunu/utils/operator.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,40 @@ def sphere_func(x):
return np.sum(x ** 2)


def rotated_expanded_schaffer_func(x):
x = np.asarray(x).ravel()
x_pairs = np.column_stack((x, np.roll(x, -1)))
sum_sq = x_pairs[:, 0] ** 2 + x_pairs[:, 1] ** 2
# Calculate the Schaffer function for all pairs simultaneously
schaffer_values = (0.5 + (np.sin(np.sqrt(sum_sq)) ** 2 - 0.5) /
(1 + 0.001 * sum_sq) ** 2)
return np.sum(schaffer_values)


def rotated_expanded_scaffer_func(x):
x = np.array(x).ravel()
results = [scaffer_func([x[idx], x[idx + 1]]) for idx in range(0, len(x) - 1)]
return np.sum(results) + scaffer_func([x[-1], x[0]])


def grie_rosen_cec_func(x):
"""This is based on the CEC version which unrolls the griewank and rosenbrock functions for better performance"""
z = np.array(x).ravel()
z += 1.0 # This centers the optimal solution of rosenbrock to 0

tmp1 = (z[:-1] * z[:-1] - z[1:]) ** 2
tmp2 = (z[:-1] - 1.0) ** 2
temp = 100.0 * tmp1 + tmp2
f = np.sum(temp ** 2 / 4000.0 - np.cos(temp) + 1.0)
# Last calculation
tmp1 = (z[-1] * z[-1] - z[0]) ** 2
tmp2 = (z[-1] - 1.0) ** 2
temp = 100.0 * tmp1 + tmp2
f += (temp ** 2) / 4000.0 - np.cos(temp) + 1.0

return f


def f8f2_func(x):
x = np.array(x).ravel()
results = [griewank_func(rosenbrock_func([x[idx], x[idx + 1]])) for idx in range(0, len(x) - 1)]
Expand Down Expand Up @@ -200,14 +228,6 @@ def gz_func(x):
conditions = [x < -500, (-500 <= x) & (x <= 500), x > 500]
choices = [t2, t3, t1]
y = np.select(conditions, choices, default=np.nan)
# y = x.copy()
# for idx in range(0, ndim):
# if x[idx] > 500:
# y[idx] = (500 - np.mod(x[idx], 500)) * np.sin(np.sqrt(np.abs(500 - np.mod(x[idx], 500)))) - (x[idx] - 500)**2/(10000*ndim)
# elif x[idx] < -500:
# y[idx] = (np.mod(x[idx], 500) - 500) * np.sin(np.sqrt(np.abs(np.mod(np.abs(x[idx]), 500) - 500))) - (x[idx]+500)**2/(10000*ndim)
# else:
# y[idx] = x[idx]*np.sin(np.abs(x[idx])**0.5)
return y


Expand All @@ -232,32 +252,47 @@ def lunacek_bi_rastrigin_func(x, z, miu0=2.5, d=1.):
return result1 + 10 * (ndim - np.sum(np.cos(2 * np.pi * z)))


def calculate_weight(x, xichma=1.):
def calculate_weight(x, delta=1.):
ndim = len(x)
weight = 1
temp = np.sum(x ** 2)
if temp != 0:
weight = (1.0 / np.sqrt(temp)) * np.exp(-temp / (2 * ndim * xichma ** 2))
weight = np.sqrt(1.0 / temp) * np.exp(-temp / (2 * ndim * delta ** 2))
else:
weight = 1e99 # this is the INF definition in original CEC Calculate logic

return weight


def modified_schwefel_func(x):
x = np.array(x).ravel()
ndim = len(x)
z = x + 4.209687462275036e+002
return 418.9829 * ndim - np.sum(gz_func(z))


def happy_cat_func(x):
x = np.array(x).ravel()
ndim = len(x)
t1 = np.sum(x)
t2 = np.sum(x ** 2)
"""
This is a direct conversion of the CEC2021 C-Code for the Modified Schwefel F11 Function
"""
z = np.array(x).ravel() + 4.209687462275036e+002
nx = len(z)

mask1 = z > 500
mask2 = z < -500
mask3 = ~mask1 & ~mask2
fx = np.zeros(nx)
fx[mask1] -= (500.0 + np.fmod(np.abs(z[mask1]), 500)) * np.sin(np.sqrt(500.0 - np.fmod(np.abs(z[mask1]), 500))) - (
(z[mask1] - 500.0) / 100.) ** 2 / nx
fx[mask2] -= (-500.0 + np.fmod(np.abs(z[mask2]), 500)) * np.sin(np.sqrt(500.0 - np.fmod(np.abs(z[mask2]), 500))) - (
(z[mask2] + 500.0) / 100.) ** 2 / nx
fx[mask3] -= z[mask3] * np.sin(np.sqrt(np.abs(z[mask3])))

return np.sum(fx) + 4.189828872724338e+002 * nx


def happy_cat_func(x, shift=0.0):
z = np.array(x).ravel() + shift
ndim = len(z)
t1 = np.sum(z)
t2 = np.sum(z ** 2)
return np.abs(t2 - ndim) ** 0.25 + (0.5 * t2 + t1) / ndim + 0.5


def hgbat_func(x):
x = np.array(x).ravel()
def hgbat_func(x, shift=0.0):
x = np.array(x).ravel() + shift
ndim = len(x)
t1 = np.sum(x)
t2 = np.sum(x ** 2)
Expand All @@ -270,9 +305,9 @@ def zakharov_func(x):
return np.sum(x ** 2) + temp ** 2 + temp ** 4


def levy_func(x):
x = np.array(x).ravel()
w = 1 + (x - 1) / 4
def levy_func(x, shift=0.0):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found a reference for levy function, looks like the original is correct but that the expectation for the cec function was a 0 optimal shifted version. So went with a shift parameter like the hgbat and happy_cat with a default.

x = np.array(x).ravel() + shift
w = 1. + (x - 1.) / 4
t1 = np.sin(np.pi * w[0]) ** 2 + (w[-1] - 1) ** 2 * (1 + np.sin(2 * np.pi * w[-1]) ** 2)
t2 = np.sum((w[:-1] - 1) ** 2 * (1 + 10 * np.sin(np.pi * w[:-1] + 1) ** 2))
return t1 + t2
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def readme():
long_description_content_type="text/markdown",
keywords=["optimization functions", "test functions", "benchmark functions", "mathematical functions",
"CEC competitions", "CEC-2008", "CEC-2009", "CEC-2010", "CEC-2011", "CEC-2012", "CEC-2013",
"CEC-2014", "CEC-2015", "CEC-2017", "CEC-2019", "CEC-2020", "CEC-2021", "CEC-2023", "soft computing",
"CEC-2014", "CEC-2015", "CEC-2017", "CEC-2019", "CEC-2020", "CEC-2021", "CEC-2022", "soft computing",
"Stochastic optimization", "Global optimization", "Convergence analysis", "Search space exploration",
"Local search", "Computational intelligence", "Performance analysis",
"Exploration versus exploitation", "Constrained optimization", "Simulations"],
Expand Down
12 changes: 12 additions & 0 deletions tests/cec_based/test_cec2022.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,15 @@ def test_F122022_results():
assert len(problem.lb) == ndim
assert problem.bounds.shape[0] == ndim
assert len(problem.x_global) == ndim


def test_all_optimal_results():
ndim = 10
known_failing = []
all_functions = [x for x in opfunu.get_all_cec_functions()
if x.__name__[-4:] == '2022' and x.__name__ not in known_failing]
for function in all_functions:
problem = function(ndim=ndim)
x = problem.x_global
result = problem.evaluate(x)
assert abs(result - problem.f_global) <= problem.epsilon, f'{function.__name__} Failed Optimal Test'
Loading