import numpy as np
from ddmtolab.Methods.mtop import MTOP
from ddmtolab.Methods.Algo_Methods.algo_utils import nd_sort
from ddmtolab.Methods.Algo_Methods.uniform_point import uniform_point
[docs]
class MW:
"""
Implementation of the MW test suite for constrained multi-objective optimization.
The MW test problems are standard constrained multi-objective
optimization benchmarks proposed by Ma and Wang (2019).
References
----------
[1] Z. Ma and Y. Wang. "Evolutionary constrained multiobjective optimization: Test suite construction and performance comparisons." IEEE Transactions on Evolutionary Computation, 2019, 23(6): 972-986.
"""
problem_information = {
'n_cases': 14,
'n_tasks': '1',
'n_dims': 'D',
'n_objs': '[2, 3]',
'n_cons': '[1, 3]',
'type': 'synthetic',
}
[docs]
def MW1(self, M=2, D=None) -> MTOP:
"""
Generates the **MW1** problem.
MW1 features a linear Pareto front with a nonlinear constraint boundary
that creates a challenging feasible region.
Parameters
----------
M : int, optional
Number of objectives (default is 2).
D : int, optional
Number of decision variables. If None, it is set to 15 (default is None).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the MW1 task.
"""
if D is None:
D = 15
def T1(x):
x = np.atleast_2d(x)
N, D = x.shape
# Calculate g function
# g = 1 + sum(1 - exp(-10*((x_i^(D-M)) - 0.5 - (i-1)/(2*D))^2))
indices = np.arange(M, D)
terms = (x[:, M:] ** (D - M) - 0.5 - (indices - M) / (2 * D)) ** 2
g = 1 + np.sum(1 - np.exp(-10 * terms), axis=1)
obj = np.zeros((N, M))
obj[:, 0] = x[:, 0]
obj[:, 1] = g * (1 - 0.85 * obj[:, 0] / g)
return obj
def C1(x):
obj = T1(x)
# Constraint: f1 + f2 - 1 - 0.5*sin(2*pi*l)^8 <= 0
# where l = sqrt(2)*f2 - sqrt(2)*f1
l = np.sqrt(2) * obj[:, 1] - np.sqrt(2) * obj[:, 0]
con = np.sum(obj, axis=1) - 1 - 0.5 * np.sin(2 * np.pi * l) ** 8
return con
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
problem.add_task(objective_func=T1, dim=D, constraint_func=C1, lower_bound=lb, upper_bound=ub)
return problem
[docs]
def MW2(self, M=2, D=None) -> MTOP:
"""
Generates the **MW2** problem.
MW2 features a linear Pareto front (f2 = 1 - f1) with a multi-modal
g function and a nonlinear constraint boundary.
Parameters
----------
M : int, optional
Number of objectives (default is 2).
D : int, optional
Number of decision variables. If None, it is set to 15 (default is None).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the MW2 task.
"""
if D is None:
D = 15
def T1(x):
x = np.atleast_2d(x)
N, D = x.shape
# Calculate z transformation
# z = 1 - exp(-10*(x_i - (i-1)/D)^2)
indices = np.arange(M, D)
z = 1 - np.exp(-10 * (x[:, M:] - (indices - M) / D) ** 2)
# Calculate g function with multi-modal term
# g = 1 + sum(1.5 + (0.1/D)*z^2 - 1.5*cos(2*pi*z))
g = 1 + np.sum(1.5 + (0.1 / D) * z ** 2 - 1.5 * np.cos(2 * np.pi * z), axis=1)
obj = np.zeros((N, M))
obj[:, 0] = x[:, 0]
obj[:, 1] = g * (1 - obj[:, 0] / g)
return obj
def C1(x):
obj = T1(x)
# Constraint: f1 + f2 - 1 - 0.5*sin(3*pi*l)^8 <= 0
# where l = sqrt(2)*f2 - sqrt(2)*f1
l = np.sqrt(2) * obj[:, 1] - np.sqrt(2) * obj[:, 0]
con = np.sum(obj, axis=1) - 1 - 0.5 * np.sin(3 * np.pi * l) ** 8
return con
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
problem.add_task(objective_func=T1, dim=D, constraint_func=C1, lower_bound=lb, upper_bound=ub)
return problem
[docs]
def MW3(self, M=2, D=None) -> MTOP:
"""
Generates the **MW3** problem.
MW3 features a linear Pareto front (f2 = 1 - f1) with two constraints
that create a complex feasible region.
Parameters
----------
M : int, optional
Number of objectives (default is 2).
D : int, optional
Number of decision variables. If None, it is set to 15 (default is None).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the MW3 task.
"""
if D is None:
D = 15
def T1(x):
x = np.atleast_2d(x)
N, D = x.shape
# Calculate g function
# g = 1 + sum(2*(x_i + (x_{i-1} - 0.5)^2 - 1)^2)
# Note: x_{i-1} for i >= M means x[:, M-1:end-1]
term = x[:, M:] + (x[:, M - 1:-1] - 0.5) ** 2 - 1
g = 1 + np.sum(2 * term ** 2, axis=1)
obj = np.zeros((N, M))
obj[:, 0] = x[:, 0]
obj[:, 1] = g * (1 - obj[:, 0] / g)
return obj
def C1(x):
obj = T1(x)
# l = sqrt(2)*f2 - sqrt(2)*f1
l = np.sqrt(2) * obj[:, 1] - np.sqrt(2) * obj[:, 0]
# Two constraints:
# c1: f1 + f2 - 1.05 - 0.45*sin(0.75*pi*l)^6 <= 0
# c2: 0.85 - f1 - f2 + 0.3*sin(0.75*pi*l)^2 <= 0
c1 = np.sum(obj, axis=1) - 1.05 - 0.45 * np.sin(0.75 * np.pi * l) ** 6
c2 = 0.85 - np.sum(obj, axis=1) + 0.3 * np.sin(0.75 * np.pi * l) ** 2
return np.column_stack([c1, c2])
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
problem.add_task(objective_func=T1, dim=D, constraint_func=C1, lower_bound=lb, upper_bound=ub)
return problem
[docs]
def MW4(self, M=3, D=None) -> MTOP:
"""
Generates the **MW4** problem.
MW4 is a multi/many-objective constrained problem with a simplex-shaped
Pareto front and a nonlinear constraint.
Parameters
----------
M : int, optional
Number of objectives (default is 3).
D : int, optional
Number of decision variables. If None, it is set to 15 (default is None).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the MW4 task.
"""
if D is None:
D = 15
# Store M as a local variable to ensure it's captured correctly
num_obj = M
def T1(x):
x = np.atleast_2d(x)
N, D = x.shape
# Calculate g function
# g = sum(1 - exp(-10*((x_i^(D-M)) - 0.5 - (i-1)/(2*D))^2))
indices = np.arange(num_obj, D)
terms = (x[:, num_obj:] ** (D - num_obj) - 0.5 - (indices - num_obj) / (2 * D)) ** 2
g = np.sum(1 - np.exp(-10 * terms), axis=1)
# Calculate objectives using cumulative product
# PopObj = (1+g) .* flip(cumprod([ones, x(:,1:M-1)], 2), 2) .* [ones, 1-x(:,M-1:-1:1)]
obj = np.zeros((N, num_obj))
for i in range(num_obj):
obj[:, i] = 1 + g
# Cumulative product part
for j in range(num_obj - i - 1):
obj[:, i] *= x[:, j]
# (1 - x) part
if i > 0:
obj[:, i] *= (1 - x[:, num_obj - i - 1])
return obj
def C1(x):
obj = T1(x)
# l = f_M - sum(f_1, ..., f_{M-1})
l = obj[:, -1] - np.sum(obj[:, :-1], axis=1)
# Constraint: sum(f) - (1 + 0.4*sin(2.5*pi*l)^8) <= 0
con = np.sum(obj, axis=1) - (1 + 0.4 * np.sin(2.5 * np.pi * l) ** 8)
return con
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
problem.add_task(objective_func=T1, dim=D, constraint_func=C1, lower_bound=lb, upper_bound=ub)
return problem
[docs]
def MW5(self, M=2, D=None) -> MTOP:
"""
Generates the **MW5** problem.
MW5 features a quarter-circle Pareto front with three constraints
that create a complex feasible region with disconnected segments.
Parameters
----------
M : int, optional
Number of objectives (default is 2).
D : int, optional
Number of decision variables. If None, it is set to 15 (default is None).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the MW5 task.
"""
if D is None:
D = 15
def T1(x):
x = np.atleast_2d(x)
N, D = x.shape
# Calculate g function
indices = np.arange(M, D)
terms = (x[:, M:] ** (D - M) - 0.5 - (indices - M) / (2 * D)) ** 2
g = 1 + np.sum(1 - np.exp(-10 * terms), axis=1)
obj = np.zeros((N, M))
obj[:, 0] = g * x[:, 0]
obj[:, 1] = g * np.sqrt(1 - (obj[:, 0] / g) ** 2)
return obj
def C1(x):
obj = T1(x)
# Calculate angles
l1 = np.arctan2(obj[:, 1], obj[:, 0])
l2 = 0.5 * np.pi - 2 * np.abs(l1 - 0.25 * np.pi)
# Three constraints
c1 = obj[:, 0] ** 2 + obj[:, 1] ** 2 - (1.7 - 0.2 * np.sin(2 * l1)) ** 2
c2 = (1 + 0.5 * np.sin(6 * l2 ** 3)) ** 2 - obj[:, 0] ** 2 - obj[:, 1] ** 2
c3 = (1 - 0.45 * np.sin(6 * l2 ** 3)) ** 2 - obj[:, 0] ** 2 - obj[:, 1] ** 2
return np.column_stack([c1, c2, c3])
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
problem.add_task(objective_func=T1, dim=D, constraint_func=C1, lower_bound=lb, upper_bound=ub)
return problem
[docs]
def MW6(self, M=2, D=None) -> MTOP:
"""
Generates the **MW6** problem.
MW6 features an elliptical Pareto front with a complex constraint
based on angular position.
Parameters
----------
M : int, optional
Number of objectives (default is 2).
D : int, optional
Number of decision variables. If None, it is set to 15 (default is None).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the MW6 task.
"""
if D is None:
D = 15
def T1(x):
x = np.atleast_2d(x)
N, D = x.shape
# Calculate z transformation
indices = np.arange(M, D)
z = 1 - np.exp(-10 * (x[:, M:] - (indices - M) / D) ** 2)
# Calculate g function with multi-modal term
g = 1 + np.sum(1.5 + (0.1 / D) * z ** 2 - 1.5 * np.cos(2 * np.pi * z), axis=1)
obj = np.zeros((N, M))
obj[:, 0] = g * x[:, 0] * 1.0999
obj[:, 1] = g * np.sqrt(1.1 * 1.1 - (obj[:, 0] / g) ** 2)
return obj
def C1(x):
obj = T1(x)
# Calculate l factor based on angle
l = np.cos(6 * np.arctan2(obj[:, 1], obj[:, 0]) ** 4) ** 10
# Constraint: (f1/(1+0.15*l))^2 + (f2/(1+0.75*l))^2 - 1 <= 0
con = (obj[:, 0] / (1 + 0.15 * l)) ** 2 + (obj[:, 1] / (1 + 0.75 * l)) ** 2 - 1
return con
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
problem.add_task(objective_func=T1, dim=D, constraint_func=C1, lower_bound=lb, upper_bound=ub)
return problem
[docs]
def MW7(self, M=2, D=None) -> MTOP:
"""
Generates the **MW7** problem.
MW7 features a quarter-circle Pareto front with two constraints
that create a complex angular-dependent feasible region.
Parameters
----------
M : int, optional
Number of objectives (default is 2).
D : int, optional
Number of decision variables. If None, it is set to 15 (default is None).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the MW7 task.
"""
if D is None:
D = 15
def T1(x):
x = np.atleast_2d(x)
N, D = x.shape
# Calculate g function (same as MW3)
term = x[:, M:] + (x[:, M - 1:-1] - 0.5) ** 2 - 1
g = 1 + np.sum(2 * term ** 2, axis=1)
obj = np.zeros((N, M))
obj[:, 0] = g * x[:, 0]
obj[:, 1] = g * np.sqrt(1 - (obj[:, 0] / g) ** 2)
return obj
def C1(x):
obj = T1(x)
# Calculate angle
l = np.arctan2(obj[:, 1], obj[:, 0])
# Two constraints
c1 = obj[:, 0] ** 2 + obj[:, 1] ** 2 - (1.2 + 0.4 * np.sin(4 * l) ** 16) ** 2
c2 = (1.15 - 0.2 * np.sin(4 * l) ** 8) ** 2 - obj[:, 0] ** 2 - obj[:, 1] ** 2
return np.column_stack([c1, c2])
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
problem.add_task(objective_func=T1, dim=D, constraint_func=C1, lower_bound=lb, upper_bound=ub)
return problem
[docs]
def MW8(self, M=3, D=None) -> MTOP:
"""
Generates the **MW8** problem.
MW8 is a multi/many-objective constrained problem with a normalized
spherical Pareto front and a constraint based on the angular position
of the last objective.
Parameters
----------
M : int, optional
Number of objectives (default is 3).
D : int, optional
Number of decision variables. If None, it is set to 15 (default is None).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the MW8 task.
"""
if D is None:
D = 15
def T1(x):
x = np.atleast_2d(x)
N, D = x.shape
# Calculate z transformation
# z = 1 - exp(-10*(x_i - (i-1)/D)^2)
indices = np.arange(M, D)
z = 1 - np.exp(-10 * (x[:, M:] - (indices - M) / D) ** 2)
# Calculate g function with multi-modal term
# g = sum(1.5 + (0.1/D)*z^2 - 1.5*cos(2*pi*z))
g = np.sum(1.5 + (0.1 / D) * z ** 2 - 1.5 * np.cos(2 * np.pi * z), axis=1)
# Calculate objectives using cumulative product
# PopObj = (1+g) .* flip(cumprod([ones, cos(x(:,1:M-1)*pi/2)], 2), 2) .* [ones, sin(x(:,M-1:-1:1)*pi/2)]
obj = np.zeros((N, M))
for i in range(M):
obj[:, i] = 1 + g
# Cumulative product of cosines
for j in range(M - i - 1):
obj[:, i] *= np.cos(x[:, j] * np.pi / 2)
# Sine part
if i > 0:
obj[:, i] *= np.sin(x[:, M - i - 1] * np.pi / 2)
return obj
def C1(x):
obj = T1(x)
# Calculate l = asin(f_M / sqrt(sum(f^2)))
l = np.arcsin(obj[:, -1] / np.sqrt(np.sum(obj ** 2, axis=1)))
# Constraint: sum(f^2) - (1.25 - 0.5*sin(6*l)^2)^2 <= 0
con = np.sum(obj ** 2, axis=1) - (1.25 - 0.5 * np.sin(6 * l) ** 2) ** 2
return con
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
problem.add_task(objective_func=T1, dim=D, constraint_func=C1, lower_bound=lb, upper_bound=ub)
return problem
[docs]
def MW9(self, M=2, D=None) -> MTOP:
"""
Generates the **MW9** problem.
MW9 features a concave Pareto front (f2 = 1 - f1^0.6) with three
constraints that create a complex feasible region with multiple
disconnected segments.
Parameters
----------
M : int, optional
Number of objectives (default is 2).
D : int, optional
Number of decision variables. If None, it is set to 15 (default is None).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the MW9 task.
"""
if D is None:
D = 15
def T1(x):
x = np.atleast_2d(x)
N, D = x.shape
# Calculate g function
# g = 1 + sum(1 - exp(-10*((x_i^(D-M)) - 0.5 - (i-1)/(2*D))^2))
indices = np.arange(M, D)
terms = (x[:, M:] ** (D - M) - 0.5 - (indices - M) / (2 * D)) ** 2
g = 1 + np.sum(1 - np.exp(-10 * terms), axis=1)
obj = np.zeros((N, M))
obj[:, 0] = g * x[:, 0]
obj[:, 1] = g * (1 - (obj[:, 0] / g) ** 0.6)
return obj
def C1(x):
x = np.atleast_2d(x)
N, D = x.shape
# Recalculate g function
indices = np.arange(M, D)
terms = (x[:, M:] ** (D - M) - 0.5 - (indices - M) / (2 * D)) ** 2
g = 1 + np.sum(1 - np.exp(-10 * terms), axis=1)
# Recalculate objectives
obj = np.zeros((N, M))
obj[:, 0] = g * x[:, 0]
obj[:, 1] = g * (1 - (obj[:, 0] / g) ** 0.6)
# Three constraint terms
# T1 = (1 - 0.64*f1^2 - f2) * (1 - 0.36*f1^2 - f2)
T1 = (1 - 0.64 * obj[:, 0] ** 2 - obj[:, 1]) * \
(1 - 0.36 * obj[:, 0] ** 2 - obj[:, 1])
# T2 = 1.35^2 - (f1 + 0.35)^2 - f2
T2 = 1.35 ** 2 - (obj[:, 0] + 0.35) ** 2 - obj[:, 1]
# T3 = 1.15^2 - (f1 + 0.15)^2 - f2
T3 = 1.15 ** 2 - (obj[:, 0] + 0.15) ** 2 - obj[:, 1]
# Constraint: min(T1, T2*T3) <= 0
con = np.minimum(T1, T2 * T3)
return con
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
problem.add_task(objective_func=T1, dim=D, constraint_func=C1, lower_bound=lb, upper_bound=ub)
return problem
[docs]
def MW10(self, M=2, D=None) -> MTOP:
"""
Generates the **MW10** problem.
MW10 features a convex Pareto front (f2 = 1 - f1^2) with three
constraints that create a complex feasible region with multiple
disconnected segments.
Parameters
----------
M : int, optional
Number of objectives (default is 2).
D : int, optional
Number of decision variables. If None, it is set to 15 (default is None).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the MW10 task.
"""
if D is None:
D = 15
def T1(x):
x = np.atleast_2d(x)
N, D = x.shape
# Calculate z transformation
# z = 1 - exp(-10*(x_i - (i-1)/D)^2)
indices = np.arange(M, D)
z = 1 - np.exp(-10 * (x[:, M:] - (indices - M) / D) ** 2)
# Calculate g function with multi-modal term
# g = 1 + sum(1.5 + (0.1/D)*z^2 - 1.5*cos(2*pi*z))
g = 1 + np.sum(1.5 + (0.1 / D) * z ** 2 - 1.5 * np.cos(2 * np.pi * z), axis=1)
obj = np.zeros((N, M))
obj[:, 0] = g * (x[:, 0] ** D)
obj[:, 1] = g * (1 - (obj[:, 0] / g) ** 2)
return obj
def C1(x):
x = np.atleast_2d(x)
N, D = x.shape
# Recalculate z transformation
indices = np.arange(M, D)
z = 1 - np.exp(-10 * (x[:, M:] - (indices - M) / D) ** 2)
# Recalculate g function
g = 1 + np.sum(1.5 + (0.1 / D) * z ** 2 - 1.5 * np.cos(2 * np.pi * z), axis=1)
# Recalculate objectives
obj = np.zeros((N, M))
obj[:, 0] = g * (x[:, 0] ** D)
obj[:, 1] = g * (1 - (obj[:, 0] / g) ** 2)
# Three constraints
# c1: -(2 - 4*f1^2 - f2) * (2 - 8*f1^2 - f2) <= 0
c1 = -(2 - 4 * obj[:, 0] ** 2 - obj[:, 1]) * \
(2 - 8 * obj[:, 0] ** 2 - obj[:, 1])
# c2: (2 - 2*f1^2 - f2) * (2 - 16*f1^2 - f2) <= 0
c2 = (2 - 2 * obj[:, 0] ** 2 - obj[:, 1]) * \
(2 - 16 * obj[:, 0] ** 2 - obj[:, 1])
# c3: (1 - f1^2 - f2) * (1.2 - 1.2*f1^2 - f2) <= 0
c3 = (1 - obj[:, 0] ** 2 - obj[:, 1]) * \
(1.2 - 1.2 * obj[:, 0] ** 2 - obj[:, 1])
return np.column_stack([c1, c2, c3])
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
problem.add_task(objective_func=T1, dim=D, constraint_func=C1, lower_bound=lb, upper_bound=ub)
return problem
[docs]
def MW11(self, M=2, D=None) -> MTOP:
"""
Generates the **MW11** problem.
MW11 features a quarter-circle Pareto front with four constraints
that create a highly complex feasible region with multiple
disconnected segments.
Parameters
----------
M : int, optional
Number of objectives (default is 2).
D : int, optional
Number of decision variables. If None, it is set to 15 (default is None).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the MW11 task.
"""
if D is None:
D = 15
def T1(x):
x = np.atleast_2d(x)
N, D = x.shape
# Calculate g function (same as MW3 and MW7)
# g = 1 + sum(2*(x_i + (x_{i-1} - 0.5)^2 - 1)^2)
term = x[:, M:] + (x[:, M - 1:-1] - 0.5) ** 2 - 1
g = 1 + np.sum(2 * term ** 2, axis=1)
obj = np.zeros((N, M))
obj[:, 0] = g * x[:, 0] * np.sqrt(1.9999)
obj[:, 1] = g * np.sqrt(2 - (obj[:, 0] / g) ** 2)
return obj
def C1(x):
x = np.atleast_2d(x)
N, D = x.shape
# Recalculate g function
term = x[:, M:] + (x[:, M - 1:-1] - 0.5) ** 2 - 1
g = 1 + np.sum(2 * term ** 2, axis=1)
# Recalculate objectives
obj = np.zeros((N, M))
obj[:, 0] = g * x[:, 0] * np.sqrt(1.9999)
obj[:, 1] = g * np.sqrt(2 - (obj[:, 0] / g) ** 2)
# Four constraints
# c1: -(3 - f1^2 - f2) * (3 - 2*f1^2 - f2) <= 0
c1 = -(3 - obj[:, 0] ** 2 - obj[:, 1]) * \
(3 - 2 * obj[:, 0] ** 2 - obj[:, 1])
# c2: (3 - 0.625*f1^2 - f2) * (3 - 7*f1^2 - f2) <= 0
c2 = (3 - 0.625 * obj[:, 0] ** 2 - obj[:, 1]) * \
(3 - 7 * obj[:, 0] ** 2 - obj[:, 1])
# c3: -(1.62 - 0.18*f1^2 - f2) * (1.125 - 0.125*f1^2 - f2) <= 0
c3 = -(1.62 - 0.18 * obj[:, 0] ** 2 - obj[:, 1]) * \
(1.125 - 0.125 * obj[:, 0] ** 2 - obj[:, 1])
# c4: (2.07 - 0.23*f1^2 - f2) * (0.63 - 0.07*f1^2 - f2) <= 0
c4 = (2.07 - 0.23 * obj[:, 0] ** 2 - obj[:, 1]) * \
(0.63 - 0.07 * obj[:, 0] ** 2 - obj[:, 1])
return np.column_stack([c1, c2, c3, c4])
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
problem.add_task(objective_func=T1, dim=D, constraint_func=C1, lower_bound=lb, upper_bound=ub)
return problem
[docs]
def MW12(self, M=2, D=None) -> MTOP:
"""
Generates the **MW12** problem.
MW12 features a complex oscillating Pareto front with two constraints
involving sinusoidal terms that create intricate feasible regions.
Parameters
----------
M : int, optional
Number of objectives (default is 2).
D : int, optional
Number of decision variables. If None, it is set to 15 (default is None).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the MW12 task.
"""
if D is None:
D = 15
def T1(x):
x = np.atleast_2d(x)
N, D = x.shape
# Calculate g function
# g = 1 + sum(1 - exp(-10*((x_i^(D-M)) - 0.5 - (i-1)/(2*D))^2))
indices = np.arange(M, D)
terms = (x[:, M:] ** (D - M) - 0.5 - (indices - M) / (2 * D)) ** 2
g = 1 + np.sum(1 - np.exp(-10 * terms), axis=1)
obj = np.zeros((N, M))
obj[:, 0] = g * x[:, 0]
obj[:, 1] = g * (0.85 - 0.8 * (obj[:, 0] / g) -
0.08 * np.abs(np.sin(3.2 * np.pi * (obj[:, 0] / g))))
return obj
def C1(x):
x = np.atleast_2d(x)
N, D = x.shape
# Recalculate g function
indices = np.arange(M, D)
terms = (x[:, M:] ** (D - M) - 0.5 - (indices - M) / (2 * D)) ** 2
g = 1 + np.sum(1 - np.exp(-10 * terms), axis=1)
# Recalculate objectives
obj = np.zeros((N, M))
obj[:, 0] = g * x[:, 0]
obj[:, 1] = g * (0.85 - 0.8 * (obj[:, 0] / g) -
0.08 * np.abs(np.sin(3.2 * np.pi * (obj[:, 0] / g))))
# Two constraints with sinusoidal terms
# c1: (1 - 0.8*f1 - f2 + 0.08*sin(2*pi*(f2 - f1/1.5))) *
# (1.8 - 1.125*f1 - f2 + 0.08*sin(2*pi*(f2/1.8 - f1/1.6))) <= 0
c1 = (1 - 0.8 * obj[:, 0] - obj[:, 1] +
0.08 * np.sin(2 * np.pi * (obj[:, 1] - obj[:, 0] / 1.5))) * \
(1.8 - 1.125 * obj[:, 0] - obj[:, 1] +
0.08 * np.sin(2 * np.pi * (obj[:, 1] / 1.8 - obj[:, 0] / 1.6)))
# c2: -(1 - 0.625*f1 - f2 + 0.08*sin(2*pi*(f2 - f1/1.6))) *
# (1.4 - 0.875*f1 - f2 + 0.08*sin(2*pi*(f2/1.4 - f1/1.6))) <= 0
c2 = -(1 - 0.625 * obj[:, 0] - obj[:, 1] +
0.08 * np.sin(2 * np.pi * (obj[:, 1] - obj[:, 0] / 1.6))) * \
(1.4 - 0.875 * obj[:, 0] - obj[:, 1] +
0.08 * np.sin(2 * np.pi * (obj[:, 1] / 1.4 - obj[:, 0] / 1.6)))
return np.column_stack([c1, c2])
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
problem.add_task(objective_func=T1, dim=D, constraint_func=C1, lower_bound=lb, upper_bound=ub)
return problem
[docs]
def MW13(self, M=2, D=None) -> MTOP:
"""
Generates the **MW13** problem.
MW13 features a complex Pareto front involving exponential and
sinusoidal terms with two constraints that create intricate
feasible regions.
Parameters
----------
M : int, optional
Number of objectives (default is 2).
D : int, optional
Number of decision variables. If None, it is set to 15 (default is None).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the MW13 task.
"""
if D is None:
D = 15
def T1(x):
x = np.atleast_2d(x)
N, D = x.shape
# Calculate z transformation
# z = 1 - exp(-10*(x_i - (i-1)/D)^2)
indices = np.arange(M, D)
z = 1 - np.exp(-10 * (x[:, M:] - (indices - M) / D) ** 2)
# Calculate g function with multi-modal term
# g = 1 + sum(1.5 + (0.1/D)*z^2 - 1.5*cos(2*pi*z))
g = 1 + np.sum(1.5 + (0.1 / D) * z ** 2 - 1.5 * np.cos(2 * np.pi * z), axis=1)
obj = np.zeros((N, M))
obj[:, 0] = g * x[:, 0] * 1.5
obj[:, 1] = g * (5 - np.exp(obj[:, 0] / g) -
np.abs(0.5 * np.sin(3 * np.pi * obj[:, 0] / g)))
return obj
def C1(x):
x = np.atleast_2d(x)
N, D = x.shape
# Recalculate z transformation
indices = np.arange(M, D)
z = 1 - np.exp(-10 * (x[:, M:] - (indices - M) / D) ** 2)
# Recalculate g function
g = 1 + np.sum(1.5 + (0.1 / D) * z ** 2 - 1.5 * np.cos(2 * np.pi * z), axis=1)
# Recalculate objectives
obj = np.zeros((N, M))
obj[:, 0] = g * x[:, 0] * 1.5
obj[:, 1] = g * (5 - np.exp(obj[:, 0] / g) -
np.abs(0.5 * np.sin(3 * np.pi * obj[:, 0] / g)))
# Two constraints
# c1: (5 - exp(f1) - 0.5*sin(3*pi*f1) - f2) *
# (5 - (1 + 0.4*f1) - 0.5*sin(3*pi*f1) - f2) <= 0
c1 = (5 - np.exp(obj[:, 0]) - 0.5 * np.sin(3 * np.pi * obj[:, 0]) - obj[:, 1]) * \
(5 - (1 + 0.4 * obj[:, 0]) - 0.5 * np.sin(3 * np.pi * obj[:, 0]) - obj[:, 1])
# c2: -(5 - (1 + f1 + 0.5*f1^2) - 0.5*sin(3*pi*f1) - f2) *
# (5 - (1 + 0.7*f1) - 0.5*sin(3*pi*f1) - f2) <= 0
c2 = -(5 - (1 + obj[:, 0] + 0.5 * obj[:, 0] ** 2) -
0.5 * np.sin(3 * np.pi * obj[:, 0]) - obj[:, 1]) * \
(5 - (1 + 0.7 * obj[:, 0]) - 0.5 * np.sin(3 * np.pi * obj[:, 0]) - obj[:, 1])
return np.column_stack([c1, c2])
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
problem.add_task(objective_func=T1, dim=D, constraint_func=C1, lower_bound=lb, upper_bound=ub)
return problem
[docs]
def MW14(self, M=3, D=None) -> MTOP:
"""
Generates the **MW14** problem.
MW14 is a multi/many-objective constrained problem with a complex
Pareto front involving exponential and sinusoidal terms, and a
single constraint creating a disconnected feasible region.
Parameters
----------
M : int, optional
Number of objectives (default is 3).
D : int, optional
Number of decision variables. If None, it is set to 15 (default is None).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the MW14 task.
"""
if D is None:
D = 15
def T1(x):
x = np.atleast_2d(x)
N, D = x.shape
# Scale x by 1.5
X = 1.5 * x
# Calculate g function (same as MW3, MW7, MW11)
# g = sum(2*(X_i + (X_{i-1} - 0.5)^2 - 1)^2)
term = X[:, M:] + (X[:, M - 1:-1] - 0.5) ** 2 - 1
g = np.sum(2 * term ** 2, axis=1)
obj = np.zeros((N, M))
# First M-1 objectives are simply the scaled decision variables
obj[:, :M - 1] = X[:, :M - 1]
# Last objective
# f_M = ((1+g)/(M-1)) * sum(6 - exp(f_i) - 1.5*sin(1.1*pi*f_i^2)) for i=1 to M-1
obj[:, M - 1] = ((1 + g) / (M - 1)) * \
np.sum(6 - np.exp(obj[:, :M - 1]) -
1.5 * np.sin(1.1 * np.pi * obj[:, :M - 1] ** 2), axis=1)
return obj
def C1(x):
x = np.atleast_2d(x)
N, D = x.shape
# Scale x by 1.5
X = 1.5 * x
# Recalculate g function
term = X[:, M:] + (X[:, M - 1:-1] - 0.5) ** 2 - 1
g = np.sum(2 * term ** 2, axis=1)
# Recalculate objectives
obj = np.zeros((N, M))
obj[:, :M - 1] = X[:, :M - 1]
obj[:, M - 1] = ((1 + g) / (M - 1)) * \
np.sum(6 - np.exp(obj[:, :M - 1]) -
1.5 * np.sin(1.1 * np.pi * obj[:, :M - 1] ** 2), axis=1)
# Constraint:
# a = 1 + f_i + 0.5*f_i^2 + 1.5*sin(1.1*pi*f_i^2) for i=1 to M-1
# con = f_M - 1/(M-1) * sum(6.1 - a) <= 0
a = 1 + obj[:, :M - 1] + 0.5 * obj[:, :M - 1] ** 2 + \
1.5 * np.sin(1.1 * np.pi * obj[:, :M - 1] ** 2)
con = obj[:, M - 1] - (1 / (M - 1)) * np.sum(6.1 - a, axis=1)
return con
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
problem.add_task(objective_func=T1, dim=D, constraint_func=C1, lower_bound=lb, upper_bound=ub)
return problem
# --- Pareto Front (PF) Functions ---
def MW1_PF(N: int, M: int = 2) -> np.ndarray:
"""
Computes the Pareto Front (PF) for MW1.
The PF is a linear segment f2 = 1 - 0.85*f1, filtered by the constraint.
Parameters
----------
N : int
Number of points to generate on the PF.
M : int, optional
Number of objectives (default is 2).
Returns
-------
np.ndarray
Array of shape (N', M) representing the PF points (N' <= N due to filtering).
"""
R = np.zeros((N, M))
R[:, 0] = np.linspace(0, 1, N)
R[:, 1] = 1 - 0.85 * R[:, 0]
# Calculate constraint value
l = np.sqrt(2) * R[:, 1] - np.sqrt(2) * R[:, 0]
c = 1 - R[:, 0] - R[:, 1] + 0.5 * np.sin(2 * np.pi * l) ** 8
# Remove points where c < 0
R = R[c >= 0, :]
return R
def MW2_PF(N: int, M: int = 2) -> np.ndarray:
"""
Computes the Pareto Front (PF) for MW2.
The PF is a linear segment f2 = 1 - f1 (complete line).
Parameters
----------
N : int
Number of points to generate on the PF.
M : int, optional
Number of objectives (default is 2).
Returns
-------
np.ndarray
Array of shape (N, M) representing the PF points.
"""
f1 = np.linspace(0, 1, N)
f2 = 1 - f1
return np.column_stack([f1, f2])
def MW3_PF(N: int, M: int = 2) -> np.ndarray:
"""
Computes the Pareto Front (PF) for MW3.
The PF is a linear segment f2 = 1 - f1, adjusted to satisfy constraint c2.
Parameters
----------
N : int
Number of points to generate on the PF.
M : int, optional
Number of objectives (default is 2).
Returns
-------
np.ndarray
Array of shape (N, M) representing the PF points.
"""
R = np.zeros((N, M))
R[:, 0] = np.linspace(0, 1, N)
R[:, 1] = 1 - R[:, 0]
# Check constraint c2: 0.85 - f1 - f2 + 0.3*sin(0.75*pi*sqrt(2)*(f2-f1))^2 > 0
invalid = (0.85 - R[:, 0] - R[:, 1] +
0.3 * np.sin(0.75 * np.pi * np.sqrt(2) * (R[:, 1] - R[:, 0])) ** 2) > 0
# Adjust invalid points
max_iterations = 1000
iteration = 0
while np.any(invalid) and iteration < max_iterations:
R[invalid, :] = R[invalid, :] * 1.001
invalid = (0.85 - R[:, 0] - R[:, 1] +
0.3 * np.sin(0.75 * np.pi * np.sqrt(2) * (R[:, 1] - R[:, 0])) ** 2) > 0
iteration += 1
return R
def MW4_PF(N: int, M: int = 3) -> np.ndarray:
"""
Computes the Pareto Front (PF) for MW4.
The PF is a simplex with sum(f) = 1, filtered by the constraint.
Parameters
----------
N : int
Number of points to generate on the PF.
M : int, optional
Number of objectives (default is 3).
Returns
-------
np.ndarray
Array of shape (N', M) representing the PF points (N' <= N due to filtering).
"""
R, _ = uniform_point(N, M)
# Calculate l = f_M - sum(f_1, ..., f_{M-1})
l = R[:, -1] - np.sum(R[:, :-1], axis=1)
# Calculate constraint: (1 + 0.4*sin(2.5*pi*l)^8) - sum(R)
c = (1 + 0.4 * np.sin(2.5 * np.pi * l) ** 8) - np.sum(R, axis=1)
# Remove points where c < 0
R = R[c >= 0, :]
return R
def MW5_PF(N: int, M: int = 2) -> np.ndarray:
"""
Computes the Pareto Front (PF) for MW5.
The PF consists of specific disconnected segments on a quarter circle.
Parameters
----------
N : int
Number of points to generate on the PF (not used, returns fixed points).
M : int, optional
Number of objectives (default is 2).
Returns
-------
np.ndarray
Array of shape (16, M) representing the PF points.
"""
# First half of the PF
R = np.array([
[0.0000, 1.0000],
[0.3922, 0.9199],
[0.4862, 0.8739],
[0.5490, 0.8358],
[0.5970, 0.8023],
[0.6359, 0.7719],
[0.6686, 0.7436],
[0.6969, 0.7174]
])
# Second half (flipped)
R_flip = np.flip(R, axis=1)
# Combine both halves
R = np.vstack([R, R_flip])
return R
def MW6_PF(N: int, M: int = 2) -> np.ndarray:
"""
Computes the Pareto Front (PF) for MW6.
The PF is an arc normalized to radius 1.1, filtered by the constraint.
Parameters
----------
N : int
Number of points to generate on the PF.
M : int, optional
Number of objectives (default is 2).
Returns
-------
np.ndarray
Array of shape (N', M) representing the PF points (N' <= N due to filtering).
"""
R = np.zeros((N, M))
R[:, 0] = np.linspace(0, 1, N)
R[:, 1] = 1 - R[:, 0]
# Normalize to radius 1.1 (since sqrt(sum(R^2,2)/1.21) = sqrt(R^2)/1.1)
R = R / np.sqrt(np.sum(R ** 2, axis=1, keepdims=True) / 1.21)
# Calculate l factor
l = np.cos(6 * np.arctan2(R[:, 1], R[:, 0]) ** 4) ** 10
# Calculate constraint
c = 1 - (R[:, 0] / (1 + 0.15 * l)) ** 2 - (R[:, 1] / (1 + 0.75 * l)) ** 2
# Remove points where c < 0
R = R[c >= 0, :]
return R
# def MW7_PF(N: int, M: int = 2) -> np.ndarray:
# """
# Computes the Pareto Front (PF) for MW7.
#
# The PF is a quarter circle normalized to unit radius, adjusted to satisfy
# constraint c2, and then filtered by non-dominated sorting.
#
# Parameters
# ----------
# N : int
# Number of points to generate on the PF.
# M : int, optional
# Number of objectives (default is 2).
#
# Returns
# -------
# np.ndarray
# Array of shape (N', M) representing the non-dominated PF points.
# """
# R = np.zeros((N, M))
# R[:, 0] = np.linspace(0, 1, N)
# R[:, 1] = 1 - R[:, 0]
#
# # Normalize to unit circle
# R = R / np.sqrt(np.sum(R ** 2, axis=1, keepdims=True))
#
# # Check constraint c2: (1.15 - 0.2*sin(4*atan(f2/f1))^8)^2 - f1^2 - f2^2 > 0
# invalid = ((1.15 - 0.2 * np.sin(4 * np.arctan2(R[:, 1], R[:, 0])) ** 8) ** 2 -
# R[:, 0] ** 2 - R[:, 1] ** 2) > 0
#
# # Adjust invalid points
# max_iterations = 1000
# iteration = 0
# while np.any(invalid) and iteration < max_iterations:
# R[invalid, :] = R[invalid, :] * 1.001
# invalid = ((1.15 - 0.2 * np.sin(4 * np.arctan2(R[:, 1], R[:, 0])) ** 8) ** 2 -
# R[:, 0] ** 2 - R[:, 1] ** 2) > 0
# iteration += 1
#
# # Keep only non-dominated solutions (front_no == 1)
# front_no, _ = nd_sort(R, N)
# R = R[front_no == 1, :]
#
# return R
def MW7_PF(N: int, M: int = 2) -> np.ndarray:
"""
Computes the Pareto Front (PF) for MW7.
The PF is a quarter circle normalized to unit radius, adjusted to satisfy
constraint c2, and then filtered by non-dominated sorting.
Parameters
----------
N : int
Number of points to generate on the PF.
M : int, optional
Number of objectives (default is 2).
Returns
-------
np.ndarray
Array of shape (N', M) representing the non-dominated PF points.
"""
R = np.zeros((N, M))
R[:, 0] = np.linspace(0, 1, N)
R[:, 1] = 1 - R[:, 0]
# Normalize to unit circle
norms = np.sqrt(np.sum(R ** 2, axis=1, keepdims=True))
R = R / norms
# Pre-compute angle (only needs to be updated for invalid points)
angle = np.arctan2(R[:, 1], R[:, 0])
# Check constraint c2: (1.15 - 0.2*sin(4*angle)^8)^2 - f1^2 - f2^2 > 0
sin_term = np.sin(4 * angle) ** 8
r_squared = R[:, 0] ** 2 + R[:, 1] ** 2
invalid = ((1.15 - 0.2 * sin_term) ** 2 - r_squared) > 0
# Adjust invalid points
max_iterations = 1000
iteration = 0
while np.any(invalid) and iteration < max_iterations:
R[invalid, :] *= 1.001
# Only recompute for invalid points
angle[invalid] = np.arctan2(R[invalid, 1], R[invalid, 0])
sin_term[invalid] = np.sin(4 * angle[invalid]) ** 8
r_squared[invalid] = R[invalid, 0] ** 2 + R[invalid, 1] ** 2
invalid = ((1.15 - 0.2 * sin_term) ** 2 - r_squared) > 0
iteration += 1
# Keep only non-dominated solutions (front_no == 1)
front_no, _ = nd_sort(R, N)
R = R[front_no == 1, :]
return R
def MW8_PF(N: int, M: int = 3) -> np.ndarray:
"""
Computes the Pareto Front (PF) for MW8.
The PF is a normalized sphere filtered by the constraint based on
the angular position of the last objective.
Parameters
----------
N : int
Number of points to generate on the PF.
M : int, optional
Number of objectives (default is 3).
Returns
-------
np.ndarray
Array of shape (N', M) representing the PF points (N' <= N due to filtering).
"""
# Generate uniformly distributed points on a simplex
R, _ = uniform_point(N, M)
# Normalize to unit sphere
R = R / np.sqrt(np.sum(R ** 2, axis=1, keepdims=True))
# Calculate l = asin(f_M)
l = np.arcsin(R[:, -1])
# Calculate constraint: 1 - (1.25 - 0.5*sin(6*l)^2)^2 > 0
c = 1 - (1.25 - 0.5 * np.sin(6 * l) ** 2) ** 2
# Remove points where c > 0 (constraint violated)
R = R[c <= 0, :]
return R
def MW9_PF(N: int, M: int = 2) -> np.ndarray:
"""
Computes the Pareto Front (PF) for MW9.
The PF is a concave curve f2 = 1 - f1^0.6, adjusted to satisfy
the complex three-part constraint, and filtered by non-dominated sorting.
Parameters
----------
N : int
Number of points to generate on the PF.
M : int, optional
Number of objectives (default is 2).
Returns
-------
np.ndarray
Array of shape (N', M) representing the non-dominated PF points.
"""
R = np.zeros((N, M))
R[:, 0] = np.linspace(0, 1, N)
R[:, 1] = 1 - R[:, 0] ** 0.6
# Calculate constraint terms
T1 = (1 - 0.64 * R[:, 0] ** 2 - R[:, 1]) * \
(1 - 0.36 * R[:, 0] ** 2 - R[:, 1])
T2 = 1.35 ** 2 - (R[:, 0] + 0.35) ** 2 - R[:, 1]
T3 = 1.15 ** 2 - (R[:, 0] + 0.15) ** 2 - R[:, 1]
# Check constraint: min(T1, T2*T3) > 0
invalid = np.minimum(T1, T2 * T3) > 0
# Adjust invalid points
max_iterations = 1000
iteration = 0
while np.any(invalid) and iteration < max_iterations:
R[invalid, :] = R[invalid, :] * 1.001
# Recalculate constraint terms for invalid points
T1[invalid] = (1 - 0.64 * R[invalid, 0] ** 2 - R[invalid, 1]) * \
(1 - 0.36 * R[invalid, 0] ** 2 - R[invalid, 1])
T2[invalid] = 1.35 ** 2 - (R[invalid, 0] + 0.35) ** 2 - R[invalid, 1]
T3[invalid] = 1.15 ** 2 - (R[invalid, 0] + 0.15) ** 2 - R[invalid, 1]
invalid = np.minimum(T1, T2 * T3) > 0
iteration += 1
# Keep only non-dominated solutions (front_no == 1)
front_no, _ = nd_sort(R, N)
R = R[front_no == 1, :]
return R
def MW10_PF(N: int, M: int = 2) -> np.ndarray:
"""
Computes the Pareto Front (PF) for MW10.
The PF is a convex curve f2 = 1 - f1^2, adjusted to satisfy
three complex constraints, and filtered by non-dominated sorting.
Parameters
----------
N : int
Number of points to generate on the PF.
M : int, optional
Number of objectives (default is 2).
Returns
-------
np.ndarray
Array of shape (N', M) representing the non-dominated PF points.
"""
R = np.zeros((N, M))
R[:, 0] = np.linspace(0, 1, N)
R[:, 1] = 1 - R[:, 0] ** 2
# Calculate constraint terms
c1 = (2 - 4 * R[:, 0] ** 2 - R[:, 1]) * (2 - 8 * R[:, 0] ** 2 - R[:, 1])
c2 = (2 - 2 * R[:, 0] ** 2 - R[:, 1]) * (2 - 16 * R[:, 0] ** 2 - R[:, 1])
c3 = (1 - R[:, 0] ** 2 - R[:, 1]) * (1.2 - 1.2 * R[:, 0] ** 2 - R[:, 1])
# Check constraints: c1 < 0 OR c2 > 0 OR c3 > 0 means invalid
invalid = (c1 < 0) | (c2 > 0) | (c3 > 0)
# Adjust invalid points
max_iterations = 1000
iteration = 0
while np.any(invalid) and iteration < max_iterations:
R[invalid, :] = R[invalid, :] * 1.001
# Remove points that exceed threshold
mask = np.any(R > 1.3, axis=1)
R = R[~mask, :]
if len(R) == 0:
break
# Recalculate constraint terms
c1 = (2 - 4 * R[:, 0] ** 2 - R[:, 1]) * (2 - 8 * R[:, 0] ** 2 - R[:, 1])
c2 = (2 - 2 * R[:, 0] ** 2 - R[:, 1]) * (2 - 16 * R[:, 0] ** 2 - R[:, 1])
c3 = (1 - R[:, 0] ** 2 - R[:, 1]) * (1.2 - 1.2 * R[:, 0] ** 2 - R[:, 1])
invalid = (c1 < 0) | (c2 > 0) | (c3 > 0)
iteration += 1
if len(R) > 0:
# Keep only non-dominated solutions (front_no == 1)
front_no, _ = nd_sort(R, len(R))
R = R[front_no == 1, :]
return R
def MW11_PF(N: int, M: int = 2) -> np.ndarray:
"""
Computes the Pareto Front (PF) for MW11.
The PF is a quarter circle normalized to radius sqrt(2), adjusted to
satisfy four complex constraints, and filtered by non-dominated sorting.
Parameters
----------
N : int
Number of points to generate on the PF.
M : int, optional
Number of objectives (default is 2).
Returns
-------
np.ndarray
Array of shape (N', M) representing the non-dominated PF points.
"""
R = np.zeros((N, M))
R[:, 0] = np.linspace(0, 1, N)
R[:, 1] = 1 - R[:, 0]
# Normalize to radius sqrt(2)
R = R / np.sqrt(np.sum(R ** 2, axis=1, keepdims=True) / 2)
# Calculate constraint terms
c1 = (3 - R[:, 0] ** 2 - R[:, 1]) * (3 - 2 * R[:, 0] ** 2 - R[:, 1])
c2 = (3 - 0.625 * R[:, 0] ** 2 - R[:, 1]) * (3 - 7 * R[:, 0] ** 2 - R[:, 1])
c3 = (1.62 - 0.18 * R[:, 0] ** 2 - R[:, 1]) * (1.125 - 0.125 * R[:, 0] ** 2 - R[:, 1])
c4 = (2.07 - 0.23 * R[:, 0] ** 2 - R[:, 1]) * (0.63 - 0.07 * R[:, 0] ** 2 - R[:, 1])
# Check constraints: c1 < 0 OR c2 > 0 OR c3 < 0 OR c4 > 0 means invalid
invalid = (c1 < 0) | (c2 > 0) | (c3 < 0) | (c4 > 0)
# Adjust invalid points
max_iterations = 1000
iteration = 0
while np.any(invalid) and iteration < max_iterations:
R[invalid, :] = R[invalid, :] * 1.001
# Remove points that exceed threshold
mask = np.any(R > 2.2, axis=1)
R = R[~mask, :]
if len(R) == 0:
break
# Recalculate constraint terms
c1 = (3 - R[:, 0] ** 2 - R[:, 1]) * (3 - 2 * R[:, 0] ** 2 - R[:, 1])
c2 = (3 - 0.625 * R[:, 0] ** 2 - R[:, 1]) * (3 - 7 * R[:, 0] ** 2 - R[:, 1])
c3 = (1.62 - 0.18 * R[:, 0] ** 2 - R[:, 1]) * (1.125 - 0.125 * R[:, 0] ** 2 - R[:, 1])
c4 = (2.07 - 0.23 * R[:, 0] ** 2 - R[:, 1]) * (0.63 - 0.07 * R[:, 0] ** 2 - R[:, 1])
invalid = (c1 < 0) | (c2 > 0) | (c3 < 0) | (c4 > 0)
iteration += 1
# Add the point [1, 1] to complete the front
if len(R) > 0:
R = np.vstack([R, [1, 1]])
# Keep only non-dominated solutions (front_no == 1)
front_no, _ = nd_sort(R, len(R))
R = R[front_no == 1, :]
return R
def MW12_PF(N: int, M: int = 2) -> np.ndarray:
"""
Computes the Pareto Front (PF) for MW12.
The PF is an oscillating curve f2 = 0.85 - 0.8*f1 - 0.08*|sin(3.2*pi*f1)|,
adjusted to satisfy the first constraint.
Parameters
----------
N : int
Number of points to generate on the PF.
M : int, optional
Number of objectives (default is 2).
Returns
-------
np.ndarray
Array of shape (N', M) representing the PF points.
"""
R = np.zeros((N, M))
R[:, 0] = np.linspace(0, 1, N)
R[:, 1] = 0.85 - 0.8 * R[:, 0] - 0.08 * np.abs(np.sin(3.2 * np.pi * R[:, 0]))
# Calculate constraint c1
c1 = (1 - 0.8 * R[:, 0] - R[:, 1] +
0.08 * np.sin(2 * np.pi * (R[:, 1] - R[:, 0] / 1.5))) * \
(1.8 - 1.125 * R[:, 0] - R[:, 1] +
0.08 * np.sin(2 * np.pi * (R[:, 1] / 1.8 - R[:, 0] / 1.6)))
# Check constraint: c1 > 0 means invalid
invalid = c1 > 0
# Adjust invalid points
max_iterations = 1000
iteration = 0
while np.any(invalid) and iteration < max_iterations:
R[invalid, :] = R[invalid, :] * 1.001
# Recalculate constraint c1
c1[invalid] = (1 - 0.8 * R[invalid, 0] - R[invalid, 1] +
0.08 * np.sin(2 * np.pi * (R[invalid, 1] - R[invalid, 0] / 1.5))) * \
(1.8 - 1.125 * R[invalid, 0] - R[invalid, 1] +
0.08 * np.sin(2 * np.pi * (R[invalid, 1] / 1.8 - R[invalid, 0] / 1.6)))
invalid = c1 > 0
iteration += 1
return R
def MW13_PF(N: int, M: int = 2) -> np.ndarray:
"""
Computes the Pareto Front (PF) for MW13.
The PF is a complex curve f2 = 5 - exp(f1) - 0.5*|sin(3*pi*f1)|,
adjusted to satisfy the first constraint and filtered by non-dominated sorting.
Parameters
----------
N : int
Number of points to generate on the PF.
M : int, optional
Number of objectives (default is 2).
Returns
-------
np.ndarray
Array of shape (N', M) representing the non-dominated PF points.
"""
R = np.zeros((N, M))
R[:, 0] = np.linspace(0, 1.5, N)
R[:, 1] = 5 - np.exp(R[:, 0]) - 0.5 * np.abs(np.sin(3 * np.pi * R[:, 0]))
# Calculate constraint c1
c1 = (5 - np.exp(R[:, 0]) - 0.5 * np.sin(3 * np.pi * R[:, 0]) - R[:, 1]) * \
(5 - (1 + 0.4 * R[:, 0]) - 0.5 * np.sin(3 * np.pi * R[:, 0]) - R[:, 1])
# Check constraint: c1 > 0 means invalid
invalid = c1 > 0
# Adjust invalid points
max_iterations = 1000
iteration = 0
while np.any(invalid) and iteration < max_iterations:
R[invalid, :] = R[invalid, :] * 1.001
# Recalculate constraint c1
c1[invalid] = (5 - np.exp(R[invalid, 0]) -
0.5 * np.sin(3 * np.pi * R[invalid, 0]) - R[invalid, 1]) * \
(5 - (1 + 0.4 * R[invalid, 0]) -
0.5 * np.sin(3 * np.pi * R[invalid, 0]) - R[invalid, 1])
invalid = c1 > 0
iteration += 1
# Keep only non-dominated solutions (front_no == 1)
if len(R) > 0:
front_no, _ = nd_sort(R, len(R))
R = R[front_no == 1, :]
return R
def MW14_PF(N: int, M: int = 3) -> np.ndarray:
"""
Computes the Pareto Front (PF) for MW14.
The PF has a specific structure with disconnected regions defined
by intervals [0, 0.731], [1.331, 1.5] for each objective dimension.
Parameters
----------
N : int
Number of points to generate on the PF.
M : int, optional
Number of objectives (default is 3).
Returns
-------
np.ndarray
Array of shape (N', M) representing the PF points.
"""
# Define intervals for the disconnected PF
interval = np.array([0.0, 0.731, 1.331, 1.5])
median = (interval[1] - interval[0]) / (interval[3] - interval[2] + interval[1] - interval[0])
# Generate uniform grid points in [0,1]^(M-1)
X, _ = uniform_point(N, M - 1, method='grid')
# Map points to the disconnected intervals
mask_low = X <= median
mask_high = X > median
X[mask_low] = X[mask_low] * (interval[1] - interval[0]) / median + interval[0]
X[mask_high] = (X[mask_high] - median) * (interval[3] - interval[2]) / (1 - median) + interval[2]
# Calculate the last objective
# f_M = 1/(M-1) * sum(6 - exp(f_i) - 1.5*sin(1.1*pi*f_i^2))
f_M = (1 / (M - 1)) * np.sum(6 - np.exp(X) - 1.5 * np.sin(1.1 * np.pi * X ** 2), axis=1)
# Combine into full objective array
R = np.column_stack([X, f_M])
return R
SETTINGS = {
'metric': 'IGD',
'n_ref': 2000,
'MW1': {'T1': MW1_PF},
'MW2': {'T1': MW2_PF},
'MW3': {'T1': MW3_PF},
'MW4': {'T1': MW4_PF},
'MW5': {'T1': MW5_PF},
'MW6': {'T1': MW6_PF},
'MW7': {'T1': MW7_PF},
'MW8': {'T1': MW8_PF},
'MW9': {'T1': MW9_PF},
'MW10': {'T1': MW10_PF},
'MW11': {'T1': MW11_PF},
'MW12': {'T1': MW12_PF},
'MW13': {'T1': MW13_PF},
'MW14': {'T1': MW14_PF}
}