import pkgutil
import io
import scipy.io
import numpy as np
from ddmtolab.Methods.mtop import MTOP
[docs]
class MTMOInstances:
"""
Additional Multi-Task Multi-Objective Optimization (MTMO) benchmark problems.
These problems consist of two multi-objective optimization tasks with different
characteristics to test knowledge transfer capabilities.
Attributes
----------
data_dir : str
The directory path for problem data files.
"""
problem_information = {
'n_cases': 2,
'n_tasks': '2',
'n_dims': '10',
'n_objs': '2',
'n_cons': '[0, 1]',
'type': 'synthetic',
}
def __init__(self):
self.data_dir = 'data_mtmo_instance'
[docs]
def P1(self) -> MTOP:
"""
Generates MTMO Instance 1: **T1 (ZDT4_R, Rastrigin) vs T2 (ZDT4_G, Griewank)**.
Both tasks are 2-objective, 10-dimensional.
- T1: Modified ZDT1-like with Rastrigin component in g-function. PF is continuous, convex.
- T2: Modified ZDT2-like with Griewank component in g-function. PF is continuous, non-convex.
- Relationship: Different g-functions create different landscape difficulties.
Returns
-------
MTOP
A Multi-Task Multi-Objective Optimization Problem instance.
"""
dim = 10
data_bytes = pkgutil.get_data('ddmtolab.Problems.MTMO',
f'{self.data_dir}/M_MTMO_Instance.mat')
mat_file = io.BytesIO(data_bytes)
mat_data = scipy.io.loadmat(mat_file)
M = mat_data['M']
def T1(x):
"""Task 1: ZDT4_R - Rastrigin component"""
x = np.atleast_2d(x)
D = x.shape[1]
# M = 1 (identity), y = x[:, 1:]
y = x[:, 1:]
gx = 1.0 + 10.0 * (D - 1) + np.sum(y ** 2 - 10.0 * np.cos(4.0 * np.pi * y), axis=1)
f1 = x[:, 0]
f2 = gx * (1.0 - np.sqrt(x[:, 0] / gx))
return np.column_stack([f1, f2])
def T2(x):
"""Task 2: ZDT4_G - Griewank component"""
x = np.atleast_2d(x)
D = x.shape[1]
y = (M @ x[:, 1:].T).T
gx = 2.0 + np.sum(y ** 2, axis=1) / 4000.0
gx_2 = np.ones(x.shape[0])
for i in range(2, D + 1):
gx_2 *= np.cos(x[:, i - 1] / np.sqrt(i))
gx = gx - gx_2
f1 = x[:, 0]
f2 = gx * (1.0 - np.sqrt(f1 / gx))
return np.column_stack([f1, f2])
# Task 1 bounds: Rastrigin uses [-5, 5]
lb1 = np.array([0.0] + [-5.0] * (dim - 1))
ub1 = np.array([1.0] + [5.0] * (dim - 1))
# Task 2 bounds: Griewank uses [-512, 512]
lb2 = np.array([0.0] + [-512.0] * (dim - 1))
ub2 = np.array([1.0] + [512.0] * (dim - 1))
problem = MTOP()
problem.add_task(T1, dim=dim, lower_bound=lb1, upper_bound=ub1)
problem.add_task(T2, dim=dim, lower_bound=lb2, upper_bound=ub2)
return problem
[docs]
def P2(self) -> MTOP:
"""
Generates MTMO Instance 2: **T1 (ZDT4_RC, Rastrigin + Constraint) vs T2 (ZDT4_A, Ackley)**.
T1 is 2-objective with 1 constraint (10-dimensional).
T2 is 2-objective without constraints (10-dimensional).
- T1: Modified ZDT1-like with Rastrigin component and a sinusoidal constraint.
PF is continuous, convex, but partially infeasible.
- T2: Modified ZDT2-like with Ackley component in g-function. PF is continuous, non-convex.
- Relationship: One task has constraints while the other doesn't, testing constraint handling transfer.
Returns
-------
MTOP
A Multi-Task Multi-Objective Optimization Problem instance.
"""
dim = 10
# Load rotation matrix M
data_bytes = pkgutil.get_data('ddmtolab.Problems.MTMO',
f'{self.data_dir}/M_MTMO_Instance.mat')
mat_file = io.BytesIO(data_bytes)
mat_data = scipy.io.loadmat(mat_file)
M = mat_data['M']
def T1(x):
"""Task 1: ZDT4_RC - Rastrigin component (constrained)"""
x = np.atleast_2d(x)
D = x.shape[1]
# M = 1 (identity), y = x[:, 1:]
y = x[:, 1:]
gx = 1.0 + 10.0 * (D - 1) + np.sum(y ** 2 - 10.0 * np.cos(4.0 * np.pi * y), axis=1)
f1 = x[:, 0]
f2 = gx * (1.0 - np.sqrt(x[:, 0] / gx))
return np.column_stack([f1, f2])
def T1_constraint(x):
"""Constraint for Task 1: Sinusoidal constraint (ZDT4_RC)"""
x = np.atleast_2d(x)
D = x.shape[1]
y = x[:, 1:]
gx = 1.0 + 10.0 * (D - 1) + np.sum(y ** 2 - 10.0 * np.cos(4.0 * np.pi * y), axis=1)
f1 = x[:, 0]
f2 = gx * (1.0 - np.sqrt(x[:, 0] / gx))
theta = -0.05 * np.pi
a, b, c, d, e = 40.0, 5.0, 1.0, 6.0, 0.0
constraint = (a * np.abs(np.sin(b * np.pi *
(np.sin(theta) * (f2 - e) + np.cos(theta) * f1) ** c)) ** d) - \
np.cos(theta) * (f2 - e) + np.sin(theta) * f1
# Constraint should be <= 0 (violation when > 0)
constraint = np.where(constraint < 0, 0, constraint)
return constraint.reshape(-1, 1)
def T2(x):
"""Task 2: ZDT4_A - Ackley component (no constraint)"""
x = np.atleast_2d(x)
D = x.shape[1]
y = (M @ x[:, 1:].T).T
# Ackley-like g function
sum_sq = np.sum(y ** 2, axis=1)
sum_cos = np.sum(np.cos(2.0 * np.pi * y), axis=1)
gx = -20.0 * np.exp(-0.2 * np.sqrt(sum_sq / (D - 1))) - \
np.exp(sum_cos / (D - 1)) + 21.0 + np.e
f1 = x[:, 0]
f2 = gx * (1.0 - np.sqrt(f1 / gx))
return np.column_stack([f1, f2])
# Task 1 bounds: Rastrigin uses [-5, 5]
lb1 = np.array([0.0] + [-5.0] * (dim - 1))
ub1 = np.array([1.0] + [5.0] * (dim - 1))
# Task 2 bounds: Ackley uses [-32, 32]
lb2 = np.array([0.0] + [-32.0] * (dim - 1))
ub2 = np.array([1.0] + [32.0] * (dim - 1))
problem = MTOP()
problem.add_task(T1, dim=dim, constraint_func=T1_constraint,
lower_bound=lb1, upper_bound=ub1)
problem.add_task(T2, dim=dim, lower_bound=lb2, upper_bound=ub2)
return problem
# --- True Pareto Front (PF) Functions ---
def P1_T1_PF(N, M=2) -> np.ndarray:
"""
Computes the True Pareto Front (PF) for Instance 1, Task 1.
The PF is inverse square root (convex): f_2 = 1 - sqrt(f_1).
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, 2) representing the PF points.
"""
f1 = np.linspace(0, 1, N)
f2 = 1 - np.sqrt(f1)
return np.column_stack([f1, f2])
P1_T2_PF = P1_T1_PF
P2_T1_PF = P1_T1_PF
P2_T2_PF = P1_T1_PF
# Settings dictionary for the new instances
SETTINGS = {
'metric': 'IGD',
'n_pf': 1000,
'pf_path': './MOReference',
'P1': {'T1': P1_T1_PF, 'T2': P1_T2_PF},
'P2': {'T1': P2_T1_PF, 'T2': P2_T2_PF},
}