import numpy as np
from ddmtolab.Methods.mtop import MTOP
from ddmtolab.Methods.Algo_Methods.uniform_point import uniform_point
[docs]
class DTLZ:
"""
Implementation of the DTLZ test suite for multi-objective optimization.
The DTLZ test problems (DTLZ1 to DTLZ7) are standard unconstrained
multi-objective optimization benchmarks. DTLZ8 and DTLZ9 are constrained.
Each method in this class generates a Multi-Task Optimization Problem (MTOP)
instance containing a single DTLZ task.
Notes
-----
The decision variables (x) are typically split into M objectives (x[0:M-1])
and k complexity variables (x[M-1:]).
"""
problem_information = {
'n_cases': 9,
'n_tasks': '1',
'n_dims': 'D',
'n_objs': 'M',
'n_cons': '[0, M]',
'type': 'synthetic',
}
[docs]
def DTLZ1(self, M=3, D=None) -> MTOP:
"""
Generates the **DTLZ1** problem.
DTLZ1 features a simple linear Pareto-optimal front (PF) and a complex
multi-modal search space due to the g-function.
Parameters
----------
M : int, optional
Number of objectives (default is 3).
D : int, optional
Number of decision variables. If None, it is set to M + k - 1,
where k=5 for DTLZ1 (default is None).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the DTLZ1 task.
"""
k = 5
if D is None:
D = M + k - 1
def T1(x):
x = np.atleast_2d(x)
n_samples = x.shape[0]
xM = x[:, M - 1:]
g = 100 * (D - M + 1 + np.sum((xM - 0.5) ** 2 - np.cos(20 * np.pi * (xM - 0.5)), axis=1))
obj = np.zeros((n_samples, M))
for i in range(M):
obj[:, i] = 0.5 * (1 + g)
for j in range(M - i - 1):
obj[:, i] *= x[:, j]
if i > 0:
obj[:, i] *= (1 - x[:, M - i - 1])
return obj
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
problem.add_task(objective_func=T1, dim=D, lower_bound=lb, upper_bound=ub)
return problem
[docs]
def DTLZ2(self, M=3, D=None) -> MTOP:
"""
Generates the **DTLZ2** problem.
DTLZ2 features a simple convex spherical PF and a simple uni-modal
g-function.
Parameters
----------
M : int, optional
Number of objectives (default is 3).
D : int, optional
Number of decision variables. If None, it is set to M + k - 1,
where k=10 for DTLZ2 (default is None).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the DTLZ2 task.
"""
k = 10
if D is None:
D = M + k - 1
def T1(x):
x = np.atleast_2d(x)
n_samples = x.shape[0]
xM = x[:, M - 1:]
g = np.sum((xM - 0.5) ** 2, axis=1)
obj = np.zeros((n_samples, M))
for i in range(M):
obj[:, i] = (1 + g)
for j in range(M - i - 1):
obj[:, i] *= np.cos(x[:, j] * np.pi / 2)
if i > 0:
obj[:, i] *= np.sin(x[:, M - i - 1] * np.pi / 2)
return obj
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
problem.add_task(objective_func=T1, dim=D, lower_bound=lb, upper_bound=ub)
return problem
[docs]
def DTLZ3(self, M=3, D=None) -> MTOP:
"""
Generates the **DTLZ3** problem.
DTLZ3 features a convex spherical PF (similar to DTLZ2) but has a
multi-modal g-function, making it difficult to converge.
Parameters
----------
M : int, optional
Number of objectives (default is 3).
D : int, optional
Number of decision variables. If None, it is set to M + k - 1,
where k=10 for DTLZ3 (default is None).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the DTLZ3 task.
"""
k = 10
if D is None:
D = M + k - 1
def T1(x):
x = np.atleast_2d(x)
n_samples = x.shape[0]
xM = x[:, M - 1:]
g = 100 * (k + np.sum((xM - 0.5) ** 2 - np.cos(20 * np.pi * (xM - 0.5)), axis=1))
obj = np.zeros((n_samples, M))
for i in range(M):
obj[:, i] = (1 + g)
for j in range(M - i - 1):
obj[:, i] *= np.cos(x[:, j] * np.pi / 2)
if i > 0:
obj[:, i] *= np.sin(x[:, M - i - 1] * np.pi / 2)
return obj
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
problem.add_task(objective_func=T1, dim=D, lower_bound=lb, upper_bound=ub)
return problem
[docs]
def DTLZ4(self, M=3, D=None, alpha=100) -> MTOP:
"""
Generates the **DTLZ4** problem.
DTLZ4 features a convex spherical PF (similar to DTLZ2) but introduces
a bias towards certain objective regions due to the exponent :math:`\\alpha`,
making diversity maintenance challenging.
Parameters
----------
M : int, optional
Number of objectives (default is 3).
D : int, optional
Number of decision variables. If None, it is set to M + k - 1,
where k=10 for DTLZ4 (default is None).
alpha : int, optional
Exponent used to bias the solution (default is 100).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the DTLZ4 task.
"""
k = 10
if D is None:
D = M + k - 1
def T1(x):
x = np.atleast_2d(x)
n_samples = x.shape[0]
x_modified = x.copy()
x_modified[:, :M - 1] = x[:, :M - 1] ** alpha
xM = x[:, M - 1:]
g = np.sum((xM - 0.5) ** 2, axis=1)
obj = np.zeros((n_samples, M))
for i in range(M):
obj[:, i] = (1 + g)
for j in range(M - i - 1):
obj[:, i] *= np.cos(x_modified[:, j] * np.pi / 2)
if i > 0:
obj[:, i] *= np.sin(x_modified[:, M - i - 1] * np.pi / 2)
return obj
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
problem.add_task(objective_func=T1, dim=D, lower_bound=lb, upper_bound=ub)
return problem
[docs]
def DTLZ5(self, M=3, D=None) -> MTOP:
"""
Generates the **DTLZ5** problem.
DTLZ5 features a degenerated (curve-like) PF, lying on a :math:`(M-1)`-dimensional
manifold of the M-dimensional objective space.
Parameters
----------
M : int, optional
Number of objectives (default is 3).
D : int, optional
Number of decision variables. If None, it is set to M + k - 1,
where k=10 for DTLZ5 (default is None).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the DTLZ5 task.
"""
k = 10
if D is None:
D = M + k - 1
def T1(x):
x = np.atleast_2d(x)
n_samples = x.shape[0]
xM = x[:, M - 1:]
g = np.sum((xM - 0.5) ** 2, axis=1)
x_modified = x.copy()
if M > 2:
Temp = np.tile(g.reshape(-1, 1), (1, M - 2))
x_modified[:, 1:M - 1] = (1 + 2 * Temp * x[:, 1:M - 1]) / (2 + 2 * Temp)
obj = np.zeros((n_samples, M))
for i in range(M):
obj[:, i] = (1 + g)
for j in range(M - i - 1):
obj[:, i] *= np.cos(x_modified[:, j] * np.pi / 2)
if i > 0:
obj[:, i] *= np.sin(x_modified[:, M - i - 1] * np.pi / 2)
return obj
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
problem.add_task(objective_func=T1, dim=D, lower_bound=lb, upper_bound=ub)
return problem
[docs]
def DTLZ6(self, M=3, D=None) -> MTOP:
"""
Generates the **DTLZ6** problem.
DTLZ6 also features a degenerated (curve-like) PF (similar to DTLZ5),
but introduces a connectivity difficulty with its g-function (:math:`g(x_M) = \\sum x_M^{0.1}`).
Parameters
----------
M : int, optional
Number of objectives (default is 3).
D : int, optional
Number of decision variables. If None, it is set to M + k - 1,
where k=10 for DTLZ6 (default is None).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the DTLZ6 task.
"""
k = 10
if D is None:
D = M + k - 1
def T1(x):
x = np.atleast_2d(x)
n_samples = x.shape[0]
xM = x[:, M - 1:]
g = np.sum(xM ** 0.1, axis=1)
x_modified = x.copy()
if M > 2:
Temp = np.tile(g.reshape(-1, 1), (1, M - 2))
x_modified[:, 1:M - 1] = (1 + 2 * Temp * x[:, 1:M - 1]) / (2 + 2 * Temp)
obj = np.zeros((n_samples, M))
for i in range(M):
obj[:, i] = (1 + g)
for j in range(M - i - 1):
obj[:, i] *= np.cos(x_modified[:, j] * np.pi / 2)
if i > 0:
obj[:, i] *= np.sin(x_modified[:, M - i - 1] * np.pi / 2)
return obj
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
problem.add_task(objective_func=T1, dim=D, lower_bound=lb, upper_bound=ub)
return problem
[docs]
def DTLZ7(self, M=3, D=None) -> MTOP:
"""
Generates the **DTLZ7** problem.
DTLZ7 features a disconnected PF and is used to test an algorithm's
ability to converge to multiple disconnected regions.
Parameters
----------
M : int, optional
Number of objectives (default is 3).
D : int, optional
Number of decision variables. If None, it is set to M + k - 1,
where k=20 for DTLZ7 (default is None).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the DTLZ7 task.
"""
k = 20
if D is None:
D = M + k - 1
def T1(x):
x = np.atleast_2d(x)
n_samples = x.shape[0]
xM = x[:, M - 1:]
g = 1 + 9 * np.mean(xM, axis=1)
obj = np.zeros((n_samples, M))
obj[:, :M - 1] = x[:, :M - 1]
h = M - np.sum(
obj[:, :M - 1] / (1 + g.reshape(-1, 1)) *
(1 + np.sin(3 * np.pi * obj[:, :M - 1])),
axis=1
)
obj[:, M - 1] = (1 + g) * h
return obj
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
problem.add_task(objective_func=T1, dim=D, lower_bound=lb, upper_bound=ub)
return problem
[docs]
def DTLZ8(self, M=3, D=None) -> MTOP:
"""
Generates the **DTLZ8** problem (Constrained).
DTLZ8 has simple objective functions but complex constraints, typically
resulting in a PF that is a linear or piecewise linear manifold.
Parameters
----------
M : int, optional
Number of objectives (default is 3).
D : int, optional
Number of decision variables. If None, it is set to M * k,
where k=10 for DTLZ8 (default is None).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the DTLZ8 task.
"""
k = 10
if D is None:
D = M * k
def T1(x):
x = np.atleast_2d(x)
n_samples = x.shape[0]
n_vars = x.shape[1]
obj = np.zeros((n_samples, M))
# Calculate objective f_m as the mean of the m-th block of decision variables
for m in range(M):
start_idx = m * n_vars // M
end_idx = (m + 1) * n_vars // M
obj[:, m] = np.mean(x[:, start_idx:end_idx], axis=1)
return obj
def C1(x):
x = np.atleast_2d(x)
n_samples = x.shape[0]
n_vars = x.shape[1]
obj = np.zeros((n_samples, M))
for m in range(M):
start_idx = m * n_vars // M
end_idx = (m + 1) * n_vars // M
obj[:, m] = np.mean(x[:, start_idx:end_idx], axis=1)
cons = np.zeros((n_samples, M))
# Constraints c_i (i=1 to M-1)
cons[:, :M - 1] = 1 - np.tile(obj[:, M - 1:M], (1, M - 1)) - 4 * obj[:, :M - 1]
# Last constraint c_M (handled differently based on M)
if M == 2:
# If M=2, the last constraint is often defined to be non-binding or 0
cons[:, M - 1] = 0
else:
# For M > 2, a sum of the two smallest objectives is used
sorted_obj = np.sort(obj[:, :M - 1], axis=1)
cons[:, M - 1] = 1 - 2 * obj[:, M - 1] - np.sum(sorted_obj[:, :2], axis=1)
return cons
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
# DTLZ8 is a constrained problem (C1 is the constraint function)
problem.add_task(objective_func=T1, dim=D, constraint_func=C1, lower_bound=lb, upper_bound=ub)
return problem
[docs]
def DTLZ9(self, M=2, D=None) -> MTOP:
"""
Generates the **DTLZ9** problem (Constrained).
DTLZ9 has simple objective functions but constraints that define a
parabolic shape for the PF.
Parameters
----------
M : int, optional
Number of objectives (default is 3).
D : int, optional
Number of decision variables. If None, it is set to M * k,
where k=10 for DTLZ9 (default is None).
Returns
-------
MTOP
A Multi-Task Optimization Problem instance containing the DTLZ9 task.
"""
k = 10
if D is None:
D = M * k
def T1(x):
x = np.atleast_2d(x)
n_samples = x.shape[0]
n_vars = x.shape[1]
# Transform decision variables using power of 0.1
x_transformed = x ** 0.1
obj = np.zeros((n_samples, M))
# Calculate objective f_m as the sum of the transformed m-th block
for m in range(M):
start_idx = m * n_vars // M
end_idx = (m + 1) * n_vars // M
obj[:, m] = np.sum(x_transformed[:, start_idx:end_idx], axis=1)
return obj
def C1(x):
# Use T1 to compute objectives for consistency
obj = T1(x)
# Constraints c_i (i=1 to M-1) define the parabolic PF shape
# c_i = 1 - f_M^2 - f_i^2 >= 0, which implies f_i^2 + f_M^2 <= 1
cons = 1 - np.tile(obj[:, M - 1:M] ** 2, (1, M - 1)) - obj[:, :M - 1] ** 2
return cons
lb = np.zeros(D)
ub = np.ones(D)
problem = MTOP()
# DTLZ9 is a constrained problem with constraint function C1
problem.add_task(objective_func=T1, dim=D, constraint_func=C1, lower_bound=lb, upper_bound=ub)
return problem
# --- Pareto Front (PF) Functions ---
def DTLZ1_PF(N: int, M: int) -> np.ndarray:
"""
Computes the Pareto Front (PF) for DTLZ1.
The PF is a linear hyperplane in the objective space: :math:`\\sum_{i=1}^{M} f_i = 0.5`.
Parameters
----------
N : int
Number of points to generate on the PF.
M : int
Number of objectives.
Returns
-------
np.ndarray
Array of shape (N, M) representing the PF points.
"""
W, _ = uniform_point(N, M)
return W / 2
def DTLZ2_PF(N: int, M: int) -> np.ndarray:
"""
Computes the Pareto Front (PF) for DTLZ2.
The PF is the unit sphere in the positive orthant.
Parameters
----------
N : int
Number of points to generate on the PF.
M : int
Number of objectives.
Returns
-------
np.ndarray
Array of shape (N, M) representing the PF points.
"""
W, _ = uniform_point(N, M)
norms = np.sqrt(np.sum(W ** 2, axis=1, keepdims=True))
return W / norms
# DTLZ3, DTLZ4 share the same PF shape (unit sphere) as DTLZ2
DTLZ3_PF = DTLZ2_PF
DTLZ4_PF = DTLZ2_PF
def DTLZ5_PF(N: int, M: int) -> np.ndarray:
"""
Computes the Pareto Front (PF) for DTLZ5.
The PF is a degenerate curve (arc on the unit sphere).
Parameters
----------
N : int
Number of points to generate on the PF.
M : int
Number of objectives.
Returns
-------
np.ndarray
Array of shape (N, M) representing the PF points.
"""
t = np.linspace(0, 1, N)
R = np.column_stack([t, 1 - t])
norms = np.sqrt(np.sum(R ** 2, axis=1, keepdims=True))
R = R / norms
if M > 2:
first_col_repeated = np.tile(R[:, 0:1], (1, M - 2))
R = np.hstack([first_col_repeated, R])
powers = np.concatenate([[M - 2], np.arange(M - 2, -1, -1)])
scale_factors = np.sqrt(2) ** powers
R = R / scale_factors.reshape(1, -1)
return R
# DTLZ6 shares the same PF shape (degenerate curve) as DTLZ5
DTLZ6_PF = DTLZ5_PF
def DTLZ7_PF(N: int, M: int) -> np.ndarray:
"""
Computes the Pareto Front (PF) for DTLZ7.
The PF is composed of :math:`2^{M-1}` disconnected segments.
Parameters
----------
N : int
Number of points to generate on the PF.
M : int
Number of objectives.
Returns
-------
np.ndarray
Array of shape (N, M) representing the PF points.
"""
interval = np.array([0, 0.251412, 0.631627, 0.859401])
median = (interval[1] - interval[0]) / (interval[3] - interval[2] + interval[1] - interval[0])
X, _ = uniform_point(N, M - 1, 'grid')
mask_low = X <= median
X[mask_low] = X[mask_low] * (interval[1] - interval[0]) / median + interval[0]
mask_high = X > median
X[mask_high] = (X[mask_high] - median) * (interval[3] - interval[2]) / (1 - median) + interval[2]
h = M - np.sum(X / 2 * (1 + np.sin(3 * np.pi * X)), axis=1, keepdims=True)
optimum = np.hstack([X, 2 * h])
return optimum
def DTLZ8_PF(N: int, M: int) -> np.ndarray:
"""
Computes the Pareto Front (PF) for DTLZ8 (Constrained).
The PF is complex and constrained, generally a piecewise linear manifold.
Parameters
----------
N : int
Number of points to generate on the PF.
M : int
Number of objectives.
Returns
-------
np.ndarray
Array of shape (N', M) representing the PF points (N' <= N).
"""
if M == 2:
temp = np.linspace(0, 1, N).reshape(-1, 1)
optimum = np.hstack([(1 - temp) / 4, temp])
else:
# Complex calculation involving uniform points and filtering based on constraints
temp, _ = uniform_point(N // (M - 1), 3)
temp[:, 2] = temp[:, 2] / 2
mask = (temp[:, 0] >= (1 - temp[:, 2]) / 4) & \
(temp[:, 0] <= temp[:, 1]) & \
(temp[:, 2] <= 1 / 3)
temp = temp[mask, :]
n_temp = temp.shape[0]
optimum = np.zeros((n_temp * (M - 1), M))
for i in range(M - 1):
start_idx = i * n_temp
end_idx = (i + 1) * n_temp
optimum[start_idx:end_idx, :M - 1] = np.tile(temp[:, 1], (M - 1, 1)).T
optimum[start_idx:end_idx, M - 1] = temp[:, 2]
optimum[start_idx:end_idx, i] = temp[:, 0]
gap_values = np.unique(optimum[:, M - 1])
if len(gap_values) > 1:
gap = np.sort(gap_values)[1] - np.sort(gap_values)[0]
temp_extra = np.arange(1 / 3, 1 + gap, gap).reshape(-1, 1)
extra_points = np.hstack([
np.tile((1 - temp_extra) / 4, (1, M - 1)),
temp_extra
])
optimum = np.vstack([optimum, extra_points])
optimum = np.unique(optimum, axis=0)
return optimum
def DTLZ9_PF(N: int, M: int) -> np.ndarray:
"""
Computes the Pareto Front (PF) for DTLZ9 (Constrained).
The PF is a curve on a unit sphere segment (parabolic/quadric relationship).
Parameters
----------
N : int
Number of points to generate on the PF.
M : int
Number of objectives.
Returns
-------
np.ndarray
Array of shape (N, M) representing the PF points.
"""
Temp = np.linspace(0, 1, N).reshape(-1, 1)
optimum = np.hstack([
np.tile(np.cos(0.5 * np.pi * Temp), (1, M - 1)),
np.sin(0.5 * np.pi * Temp)
])
return optimum
SETTINGS = {
'metric': 'IGD',
'n_ref': 1000,
'DTLZ1': {'T1': DTLZ1_PF},
'DTLZ2': {'T1': DTLZ2_PF},
'DTLZ3': {'T1': DTLZ3_PF},
'DTLZ4': {'T1': DTLZ4_PF},
'DTLZ5': {'T1': DTLZ5_PF},
'DTLZ6': {'T1': DTLZ6_PF},
'DTLZ7': {'T1': DTLZ7_PF},
'DTLZ8': {'T1': DTLZ8_PF},
'DTLZ9': {'T1': DTLZ9_PF},
}