Source code for ddmtolab.Problems.STMO.UF

import numpy as np
from ddmtolab.Methods.mtop import MTOP
from ddmtolab.Methods.Algo_Methods.uniform_point import uniform_point


[docs] class UF: """ Implementation of the UF test suite for multi-objective optimization. The UF test problems (UF1 to UF10) are unconstrained benchmark MOPs proposed by Zhang et al. (2009) for the CEC 2009 special session and competition. Each method in this class generates a Multi-Task Optimization Problem (MTOP) instance containing a single UF task. References ---------- [1] Q. Zhang, A. Zhou, S. Zhao, P. N. Suganthan, W. Liu, and S. Tiwari. "Multiobjective optimization test instances for the CEC 2009 special session and competition." School of CS & EE, University of Essex, Working Report CES-487, 2009. Notes ----- UF1-UF7 have M=2 objectives, UF8-UF10 have M=3 objectives. The decision space dimension can be adjusted via the `D` parameter. """ problem_information = { 'n_cases': 10, 'n_tasks': '1', 'n_dims': 'D', 'n_objs': '[2, 3]', 'n_cons': '0', 'type': 'synthetic', }
[docs] def UF1(self, D=30) -> MTOP: """ Generates the **UF1** problem. UF1 is a bi-objective problem with a convex Pareto front. Parameters ---------- D : int, optional Number of decision variables (default is 30). Returns ------- MTOP A Multi-Task Optimization Problem instance containing the UF1 task. """ def T1(x): x = np.atleast_2d(x) n_samples = x.shape[0] n_vars = x.shape[1] obj = np.zeros((n_samples, 2)) # J1: 3:2:D (MATLAB) -> indices 2,4,6,... (Python 0-indexed) # J2: 2:2:D (MATLAB) -> indices 1,3,5,... (Python 0-indexed) J1 = np.arange(2, n_vars, 2) J2 = np.arange(1, n_vars, 2) # Y transformation Y = x - np.sin( 6 * np.pi * x[:, 0:1] + np.pi * (np.arange(1, n_vars + 1)) / n_vars ) # Objectives obj[:, 0] = x[:, 0] + 2 * np.mean(Y[:, J1] ** 2, axis=1) obj[:, 1] = 1 - np.sqrt(x[:, 0]) + 2 * np.mean(Y[:, J2] ** 2, axis=1) return obj lb = np.hstack([0, -np.ones(D - 1)]) ub = np.ones(D) problem = MTOP() problem.add_task(objective_func=T1, dim=D, lower_bound=lb, upper_bound=ub) return problem
[docs] def UF2(self, D=30) -> MTOP: """ Generates the **UF2** problem. UF2 is a bi-objective problem with more complex variable linkage. Parameters ---------- D : int, optional Number of decision variables (default is 30). Returns ------- MTOP A Multi-Task Optimization Problem instance containing the UF2 task. """ def T1(x): x = np.atleast_2d(x) n_samples = x.shape[0] n_vars = x.shape[1] obj = np.zeros((n_samples, 2)) J1 = np.arange(2, n_vars, 2) J2 = np.arange(1, n_vars, 2) Y = np.zeros_like(x) # For J1 indices (even in MATLAB 1-indexing) if len(J1) > 0: X1 = x[:, 0:1] term = (0.3 * X1 ** 2 * np.cos(24 * np.pi * X1 + 4 * (J1 + 1) * np.pi / n_vars) + 0.6 * X1) Y[:, J1] = x[:, J1] - term * np.cos(6 * np.pi * X1 + (J1 + 1) * np.pi / n_vars) # For J2 indices (odd in MATLAB 1-indexing) if len(J2) > 0: X1 = x[:, 0:1] term = (0.3 * X1 ** 2 * np.cos(24 * np.pi * X1 + 4 * (J2 + 1) * np.pi / n_vars) + 0.6 * X1) Y[:, J2] = x[:, J2] - term * np.sin(6 * np.pi * X1 + (J2 + 1) * np.pi / n_vars) # Objectives obj[:, 0] = x[:, 0] + 2 * np.mean(Y[:, J1] ** 2, axis=1) obj[:, 1] = 1 - np.sqrt(x[:, 0]) + 2 * np.mean(Y[:, J2] ** 2, axis=1) return obj lb = np.hstack([0, -np.ones(D - 1)]) ub = np.ones(D) problem = MTOP() problem.add_task(objective_func=T1, dim=D, lower_bound=lb, upper_bound=ub) return problem
[docs] def UF3(self, D=30) -> MTOP: """ Generates the **UF3** problem. UF3 features a complex landscape with product terms. Parameters ---------- D : int, optional Number of decision variables (default is 30). Returns ------- MTOP A Multi-Task Optimization Problem instance containing the UF3 task. """ def T1(x): x = np.atleast_2d(x) n_samples = x.shape[0] n_vars = x.shape[1] obj = np.zeros((n_samples, 2)) J1 = np.arange(2, n_vars, 2) J2 = np.arange(1, n_vars, 2) # Y transformation power_exp = 0.5 * (1 + 3 * (np.arange(1, n_vars + 1) - 2) / (n_vars - 2)) Y = x - x[:, 0:1] ** power_exp # Product terms if len(J1) > 0: prod_J1 = np.prod(np.cos(20 * Y[:, J1] * np.pi / np.sqrt(J1 + 1)), axis=1) sum_J1 = np.sum(Y[:, J1] ** 2, axis=1) obj[:, 0] = x[:, 0] + 2 / len(J1) * (4 * sum_J1 - 2 * prod_J1 + 2) else: obj[:, 0] = x[:, 0] if len(J2) > 0: prod_J2 = np.prod(np.cos(20 * Y[:, J2] * np.pi / np.sqrt(J2 + 1)), axis=1) sum_J2 = np.sum(Y[:, J2] ** 2, axis=1) obj[:, 1] = 1 - np.sqrt(x[:, 0]) + 2 / len(J2) * (4 * sum_J2 - 2 * prod_J2 + 2) else: obj[:, 1] = 1 - np.sqrt(x[:, 0]) 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 UF4(self, D=30) -> MTOP: """ Generates the **UF4** problem. UF4 features a complex sine-based transformation with h function. Parameters ---------- D : int, optional Number of decision variables (default is 30). Returns ------- MTOP A Multi-Task Optimization Problem instance containing the UF4 task. """ def T1(x): x = np.atleast_2d(x) n_samples = x.shape[0] n_vars = x.shape[1] obj = np.zeros((n_samples, 2)) J1 = np.arange(2, n_vars, 2) J2 = np.arange(1, n_vars, 2) # Y transformation Y = x - np.sin( 6 * np.pi * x[:, 0:1] + np.pi * (np.arange(1, n_vars + 1)) / n_vars ) # h transformation hY = np.abs(Y) / (1 + np.exp(2 * np.abs(Y))) # Objectives obj[:, 0] = x[:, 0] + 2 * np.mean(hY[:, J1], axis=1) obj[:, 1] = 1 - x[:, 0] ** 2 + 2 * np.mean(hY[:, J2], axis=1) return obj lb = np.hstack([0, -2 * np.ones(D - 1)]) ub = np.hstack([1, 2 * np.ones(D - 1)]) problem = MTOP() problem.add_task(objective_func=T1, dim=D, lower_bound=lb, upper_bound=ub) return problem
[docs] def UF5(self, D=30) -> MTOP: """ Generates the **UF5** problem. UF5 features multimodal landscape with oscillatory Pareto front. Parameters ---------- D : int, optional Number of decision variables (default is 30). Returns ------- MTOP A Multi-Task Optimization Problem instance containing the UF5 task. """ def T1(x): x = np.atleast_2d(x) n_samples = x.shape[0] n_vars = x.shape[1] obj = np.zeros((n_samples, 2)) J1 = np.arange(2, n_vars, 2) J2 = np.arange(1, n_vars, 2) # Y transformation Y = x - np.sin( 6 * np.pi * x[:, 0:1] + np.pi * (np.arange(1, n_vars + 1)) / n_vars ) # h transformation hY = 2 * Y ** 2 - np.cos(4 * np.pi * Y) + 1 # Oscillatory term epsilon = 0.1 N = 10 osc_term = (1 / (2 * N) + epsilon) * np.abs(np.sin(2 * N * np.pi * x[:, 0])) # Objectives obj[:, 0] = x[:, 0] + osc_term + 2 * np.mean(hY[:, J1], axis=1) obj[:, 1] = 1 - x[:, 0] + osc_term + 2 * np.mean(hY[:, J2], axis=1) return obj lb = np.hstack([0, -np.ones(D - 1)]) ub = np.ones(D) problem = MTOP() problem.add_task(objective_func=T1, dim=D, lower_bound=lb, upper_bound=ub) return problem
[docs] def UF6(self, D=30) -> MTOP: """ Generates the **UF6** problem. UF6 features disconnected Pareto front with product terms. Parameters ---------- D : int, optional Number of decision variables (default is 30). Returns ------- MTOP A Multi-Task Optimization Problem instance containing the UF6 task. """ def T1(x): x = np.atleast_2d(x) n_samples = x.shape[0] n_vars = x.shape[1] obj = np.zeros((n_samples, 2)) J1 = np.arange(2, n_vars, 2) J2 = np.arange(1, n_vars, 2) # Y transformation Y = x - np.sin( 6 * np.pi * x[:, 0:1] + np.pi * (np.arange(1, n_vars + 1)) / n_vars ) # Oscillatory term with max(0, ...) epsilon = 0.1 N = 2 osc_term = np.maximum(0, 2 * (1 / (2 * N) + epsilon) * np.sin(2 * N * np.pi * x[:, 0])) # Product and sum terms if len(J1) > 0: prod_J1 = np.prod(np.cos(20 * Y[:, J1] * np.pi / np.sqrt(J1 + 1)), axis=1) sum_J1 = np.sum(Y[:, J1] ** 2, axis=1) term_J1 = 2 / len(J1) * (4 * sum_J1 - 2 * prod_J1 + 2) else: term_J1 = 0 if len(J2) > 0: prod_J2 = np.prod(np.cos(20 * Y[:, J2] * np.pi / np.sqrt(J2 + 1)), axis=1) sum_J2 = np.sum(Y[:, J2] ** 2, axis=1) term_J2 = 2 / len(J2) * (4 * sum_J2 - 2 * prod_J2 + 2) else: term_J2 = 0 # Objectives obj[:, 0] = x[:, 0] + osc_term + term_J1 obj[:, 1] = 1 - x[:, 0] + osc_term + term_J2 return obj lb = np.hstack([0, -np.ones(D - 1)]) ub = np.ones(D) problem = MTOP() problem.add_task(objective_func=T1, dim=D, lower_bound=lb, upper_bound=ub) return problem
[docs] def UF7(self, D=30) -> MTOP: """ Generates the **UF7** problem. UF7 features a power transformation in the objectives. Parameters ---------- D : int, optional Number of decision variables (default is 30). Returns ------- MTOP A Multi-Task Optimization Problem instance containing the UF7 task. """ def T1(x): x = np.atleast_2d(x) n_samples = x.shape[0] n_vars = x.shape[1] obj = np.zeros((n_samples, 2)) J1 = np.arange(2, n_vars, 2) J2 = np.arange(1, n_vars, 2) # Y transformation Y = x - np.sin( 6 * np.pi * x[:, 0:1] + np.pi * (np.arange(1, n_vars + 1)) / n_vars ) # Objectives with power 0.2 obj[:, 0] = x[:, 0] ** 0.2 + 2 * np.mean(Y[:, J1] ** 2, axis=1) obj[:, 1] = 1 - x[:, 0] ** 0.2 + 2 * np.mean(Y[:, J2] ** 2, axis=1) return obj lb = np.hstack([0, -np.ones(D - 1)]) ub = np.ones(D) problem = MTOP() problem.add_task(objective_func=T1, dim=D, lower_bound=lb, upper_bound=ub) return problem
[docs] def UF8(self, D=30) -> MTOP: """ Generates the **UF8** problem. UF8 is a three-objective problem with spherical Pareto front. Parameters ---------- D : int, optional Number of decision variables (default is 30). Returns ------- MTOP A Multi-Task Optimization Problem instance containing the UF8 task. """ def T1(x): x = np.atleast_2d(x) n_samples = x.shape[0] n_vars = x.shape[1] obj = np.zeros((n_samples, 3)) # J1: 4:3:D -> indices 3,6,9,... (Python 0-indexed) # J2: 5:3:D -> indices 4,7,10,... (Python 0-indexed) # J3: 3:3:D -> indices 2,5,8,... (Python 0-indexed) J1 = np.arange(3, n_vars, 3) J2 = np.arange(4, n_vars, 3) J3 = np.arange(2, n_vars, 3) # Y transformation Y = x - 2 * x[:, 1:2] * np.sin( 2 * np.pi * x[:, 0:1] + np.pi * (np.arange(1, n_vars + 1)) / n_vars ) # Objectives obj[:, 0] = (np.cos(0.5 * x[:, 0] * np.pi) * np.cos(0.5 * x[:, 1] * np.pi) + 2 * np.mean(Y[:, J1] ** 2, axis=1)) obj[:, 1] = (np.cos(0.5 * x[:, 0] * np.pi) * np.sin(0.5 * x[:, 1] * np.pi) + 2 * np.mean(Y[:, J2] ** 2, axis=1)) obj[:, 2] = np.sin(0.5 * x[:, 0] * np.pi) + 2 * np.mean(Y[:, J3] ** 2, axis=1) return obj lb = np.hstack([0, 0, -2 * np.ones(D - 2)]) ub = np.hstack([1, 1, 2 * np.ones(D - 2)]) problem = MTOP() problem.add_task(objective_func=T1, dim=D, lower_bound=lb, upper_bound=ub) return problem
[docs] def UF9(self, D=30) -> MTOP: """ Generates the **UF9** problem. UF9 is a three-objective problem with disconnected Pareto front. Parameters ---------- D : int, optional Number of decision variables (default is 30). Returns ------- MTOP A Multi-Task Optimization Problem instance containing the UF9 task. """ def T1(x): x = np.atleast_2d(x) n_samples = x.shape[0] n_vars = x.shape[1] obj = np.zeros((n_samples, 3)) J1 = np.arange(3, n_vars, 3) J2 = np.arange(4, n_vars, 3) J3 = np.arange(2, n_vars, 3) # Y transformation Y = x - 2 * x[:, 1:2] * np.sin( 2 * np.pi * x[:, 0:1] + np.pi * (np.arange(1, n_vars + 1)) / n_vars ) # Environment selection epsilon = 0.5 * (np.maximum(0, 1.1 * (1 - 4 * (2 * x[:, 0] - 1) ** 2)) + 2 * x[:, 0]) # Objectives obj[:, 0] = epsilon * x[:, 1] + 2 * np.mean(Y[:, J1] ** 2, axis=1) obj[:, 1] = (0.5 * (np.maximum(0, 1.1 * (1 - 4 * (2 * x[:, 0] - 1) ** 2)) - 2 * x[:, 0] + 2) * x[:, 1] + 2 * np.mean(Y[:, J2] ** 2, axis=1)) obj[:, 2] = 1 - x[:, 1] + 2 * np.mean(Y[:, J3] ** 2, axis=1) return obj lb = np.hstack([0, 0, -2 * np.ones(D - 2)]) ub = np.hstack([1, 1, 2 * np.ones(D - 2)]) problem = MTOP() problem.add_task(objective_func=T1, dim=D, lower_bound=lb, upper_bound=ub) return problem
[docs] def UF10(self, D=30) -> MTOP: """ Generates the **UF10** problem. UF10 is a three-objective problem with spherical Pareto front and complex landscape. Parameters ---------- D : int, optional Number of decision variables (default is 30). Returns ------- MTOP A Multi-Task Optimization Problem instance containing the UF10 task. """ def T1(x): x = np.atleast_2d(x) n_samples = x.shape[0] n_vars = x.shape[1] obj = np.zeros((n_samples, 3)) J1 = np.arange(3, n_vars, 3) J2 = np.arange(4, n_vars, 3) J3 = np.arange(2, n_vars, 3) # Y transformation Y = x - 2 * x[:, 1:2] * np.sin( 2 * np.pi * x[:, 0:1] + np.pi * (np.arange(1, n_vars + 1)) / n_vars ) # Apply h function Y = 4 * Y ** 2 - np.cos(8 * np.pi * Y) + 1 # Objectives obj[:, 0] = (np.cos(0.5 * x[:, 0] * np.pi) * np.cos(0.5 * x[:, 1] * np.pi) + 2 * np.mean(Y[:, J1], axis=1)) obj[:, 1] = (np.cos(0.5 * x[:, 0] * np.pi) * np.sin(0.5 * x[:, 1] * np.pi) + 2 * np.mean(Y[:, J2], axis=1)) obj[:, 2] = np.sin(0.5 * x[:, 0] * np.pi) + 2 * np.mean(Y[:, J3], axis=1) return obj lb = np.hstack([0, 0, -2 * np.ones(D - 2)]) ub = np.hstack([1, 1, 2 * np.ones(D - 2)]) problem = MTOP() problem.add_task(objective_func=T1, dim=D, lower_bound=lb, upper_bound=ub) return problem
# --- Pareto Front (PF) Functions --- def UF1_PF(N: int, M: int = 2) -> np.ndarray: """ Computes the Pareto Front (PF) for UF1. The PF is: f2 = 1 - sqrt(f1). Parameters ---------- N : int Number of points to generate on the PF. M : int, optional Number of objectives (must be 2 for UF1, default is 2). Returns ------- np.ndarray Array of shape (N, 2) representing the PF points. """ assert M == 2, "UF1 only supports M=2 objectives" f1 = np.linspace(0, 1, N).reshape(-1, 1) f2 = 1 - f1 ** 0.5 return np.hstack([f1, f2]) def UF2_PF(N: int, M: int = 2) -> np.ndarray: """Computes the Pareto Front for UF2.""" assert M == 2, "UF2 only supports M=2 objectives" f1 = np.linspace(0, 1, N).reshape(-1, 1) f2 = 1 - f1 ** 0.5 return np.hstack([f1, f2]) def UF3_PF(N: int, M: int = 2) -> np.ndarray: """Computes the Pareto Front for UF3.""" assert M == 2, "UF3 only supports M=2 objectives" f1 = np.linspace(0, 1, N).reshape(-1, 1) f2 = 1 - f1 ** 0.5 return np.hstack([f1, f2]) def UF4_PF(N: int, M: int = 2) -> np.ndarray: """Computes the Pareto Front for UF4.""" assert M == 2, "UF4 only supports M=2 objectives" f1 = np.linspace(0, 1, N).reshape(-1, 1) f2 = 1 - f1 ** 2 return np.hstack([f1, f2]) def UF5_PF(N: int, M: int = 2) -> np.ndarray: """ Computes the Pareto Front for UF5 (discrete with 21 points). Parameters ---------- N : int Number of points to generate (not used, returns fixed 21 points). M : int, optional Number of objectives (must be 2 for UF5, default is 2). Returns ------- np.ndarray Array of shape (21, 2) representing the PF points. """ assert M == 2, "UF5 only supports M=2 objectives" # UF5 has discrete PF with specific points f1 = (np.arange(0, 21) / 20).reshape(-1, 1) f2 = 1 - f1 return np.hstack([f1, f2]) def UF6_PF(N: int, M: int = 2) -> np.ndarray: """ Computes the Pareto Front for UF6 (disconnected). Parameters ---------- N : int Number of points to generate on the PF. M : int, optional Number of objectives (must be 2 for UF6, default is 2). Returns ------- np.ndarray Array representing the disconnected PF points. """ assert M == 2, "UF6 only supports M=2 objectives" f1 = np.linspace(0, 1, N).reshape(-1, 1) f2 = 1 - f1 # Remove disconnected regions: (0, 1/4) and (1/2, 3/4) mask = ~((f1 > 0) & (f1 < 1 / 4) | (f1 > 1 / 2) & (f1 < 3 / 4)) pf = np.hstack([f1, f2]) return pf[mask.flatten()] def UF7_PF(N: int, M: int = 2) -> np.ndarray: """Computes the Pareto Front for UF7.""" assert M == 2, "UF7 only supports M=2 objectives" f1 = np.linspace(0, 1, N).reshape(-1, 1) f2 = 1 - f1 return np.hstack([f1, f2]) def UF8_PF(N: int, M: int = 3) -> np.ndarray: """ Computes the Pareto Front for UF8 (unit sphere). Parameters ---------- N : int Number of points to generate on the PF. M : int, optional Number of objectives (must be 3 for UF8, default is 3). Returns ------- np.ndarray Array of shape (N_actual, 3) representing points on unit sphere. """ assert M == 3, "UF8 only supports M=3 objectives" R, _ = uniform_point(N, M) # Normalize to unit sphere R = R / np.sqrt(np.sum(R ** 2, axis=1, keepdims=True)) return R def UF9_PF(N: int, M: int = 3) -> np.ndarray: """ Computes the Pareto Front for UF9. Parameters ---------- N : int Approximate number of points to generate on the PF. M : int, optional Number of objectives (must be 3 for UF9, default is 3). Returns ------- np.ndarray Array of shape (n_points, 3) representing the disconnected PF points, where n_points ≤ N after filtering. """ assert M == 3, "UF9 only supports M=3 objectives" # Generate uniformly distributed points on the unit simplex R = uniform_point(N, 3)[0] # Define the disconnected region mask mask = (R[:, 0] > (1 - R[:, 2]) / 4) & (R[:, 0] < (1 - R[:, 2]) * 3 / 4) # Filter out points in the disconnected region R = R[~mask] return R def UF10_PF(N: int, M: int = 3) -> np.ndarray: """ Computes the Pareto Front for UF10 (unit sphere). Parameters ---------- N : int Number of points to generate on the PF. M : int, optional Number of objectives (must be 3 for UF10, default is 3). Returns ------- np.ndarray Array of shape (N_actual, 3) representing points on unit sphere. """ assert M == 3, "UF10 only supports M=3 objectives" R, _ = uniform_point(N, M) # Normalize to unit sphere R = R / np.sqrt(np.sum(R ** 2, axis=1, keepdims=True)) return R SETTINGS = { 'metric': 'IGD', 'n_ref': 10000, 'UF1': {'T1': UF1_PF}, 'UF2': {'T1': UF2_PF}, 'UF3': {'T1': UF3_PF}, 'UF4': {'T1': UF4_PF}, 'UF5': {'T1': UF5_PF}, 'UF6': {'T1': UF6_PF}, 'UF7': {'T1': UF7_PF}, 'UF8': {'T1': UF8_PF}, 'UF9': {'T1': UF9_PF}, 'UF10': {'T1': UF10_PF}, }