Source code for ddmtolab.Problems.MTSO.cmt

"""CMT constrained multi-task benchmark suite.

9 two-task single-objective problems, each pairing two constrained landscapes.
Every task carries one inequality constraint, except the second task of CMT6 and
CMT8, which carries two. Tasks are ``D``-dimensional (50 by default).

The module also hosts the constrained function library the suite is built from:
each ``C_<Function><type>`` returns both the objective and the constraint
violation of one landscape under one of the constraint types.
"""

import numpy as np
from typing import Tuple
from ddmtolab.Methods.mtop import MTOP


# ==============================================================================
# Constrained Ackley Functions
# ==============================================================================

def C_Ackley1(var: np.ndarray, M: np.ndarray, opt: np.ndarray,
              opt_con: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
    """
    Ackley function with Type 1 constraint.

    Constraint Type 1: Complex periodic constraint
    g(x) = sum(x^2 - 5000*cos(0.1*pi*x) - 4000) <= 0

    Parameters
    ----------
    var : np.ndarray
        Design variables, shape (n_samples, dim).
    M : np.ndarray
        Rotation matrix, shape (dim, dim) or (1, dim).
    opt : np.ndarray
        Shift vector for objective, shape (dim,) or (1, dim).
    opt_con : np.ndarray
        Shift vector for constraint, shape (dim,) or (1, dim).

    Returns
    -------
    Obj : np.ndarray
        Objective values, shape (n_samples,).
    Con : np.ndarray
        Constraint violations, shape (n_samples,).
    """
    var = np.atleast_2d(var)
    ps, D = var.shape

    # Handle rotation matrix
    if M.size == 1 or (M.ndim == 2 and M.shape[0] == 1):
        M = np.eye(D) if M.size == 1 else float(M[0, 0]) * np.eye(D)

    # Handle shift vectors
    if opt.ndim == 1 or opt.shape[0] == 1:
        opt = np.atleast_1d(opt).flatten()
        if opt.size == 1:
            opt = np.full(D, opt[0])

    # Apply rotation and shift for objective
    x = (M[:D, :D] @ (var - opt[:D]).T).T

    # Compute Ackley objective
    sum1 = np.sum(x ** 2, axis=1) / D
    sum2 = np.sum(np.cos(2 * np.pi * x), axis=1) / D
    Obj = -20 * np.exp(-0.2 * np.sqrt(sum1)) - np.exp(sum2) + 20 + np.e

    # Compute constraint (Type 1: complex periodic)
    if opt_con.ndim == 1 or opt_con.shape[0] == 1:
        opt_con = np.atleast_1d(opt_con).flatten()
        if opt_con.size == 1:
            opt_con = np.full(D, opt_con[0])

    x_con = 2 * (var - opt_con[:D])
    g = np.sum(x_con ** 2 - 5000 * np.cos(0.1 * np.pi * x_con) - 4000, axis=1)
    g[g < 0] = 0
    Con = g

    return Obj, Con


def C_Ackley2(var: np.ndarray, M: np.ndarray, opt: np.ndarray,
              opt_con: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
    """
    Ackley function with Type 2 constraint.

    Constraint Type 2: Spherical constraint
    g(x) = sum(x^2) - 100*D <= 0

    Parameters
    ----------
    var : np.ndarray
        Design variables, shape (n_samples, dim).
    M : np.ndarray
        Rotation matrix, shape (dim, dim) or (1, dim).
    opt : np.ndarray
        Shift vector for objective, shape (dim,) or (1, dim).
    opt_con : np.ndarray
        Shift vector for constraint, shape (dim,) or (1, dim).

    Returns
    -------
    Obj : np.ndarray
        Objective values, shape (n_samples,).
    Con : np.ndarray
        Constraint violations, shape (n_samples,).
    """
    var = np.atleast_2d(var)
    ps, D = var.shape

    # Handle rotation matrix
    if M.size == 1 or (M.ndim == 2 and M.shape[0] == 1):
        M = np.eye(D) if M.size == 1 else float(M[0, 0]) * np.eye(D)

    # Handle shift vectors
    if opt.ndim == 1 or opt.shape[0] == 1:
        opt = np.atleast_1d(opt).flatten()
        if opt.size == 1:
            opt = np.full(D, opt[0])

    # Apply rotation and shift for objective
    x = (M[:D, :D] @ (var - opt[:D]).T).T

    # Compute Ackley objective
    sum1 = np.sum(x ** 2, axis=1) / D
    sum2 = np.sum(np.cos(2 * np.pi * x), axis=1) / D
    Obj = -20 * np.exp(-0.2 * np.sqrt(sum1)) - np.exp(sum2) + 20 + np.e

    # Compute constraint (Type 2: spherical)
    if opt_con.ndim == 1 or opt_con.shape[0] == 1:
        opt_con = np.atleast_1d(opt_con).flatten()
        if opt_con.size == 1:
            opt_con = np.full(D, opt_con[0])

    x_con = 2 * (var - opt_con[:D])
    g = np.sum(x_con ** 2, axis=1) - 100 * D
    g[g < 0] = 0
    Con = g

    return Obj, Con


# ==============================================================================
# Constrained Griewank Functions
# ==============================================================================

def C_Griewank1(var: np.ndarray, M: np.ndarray, opt: np.ndarray,
                opt_con: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
    """
    Griewank function with Type 1 constraint.

    Constraint Type 1: Complex periodic constraint
    g(x) = sum(x^2 - 5000*cos(0.1*pi*x) - 4000) <= 0

    Parameters
    ----------
    var : np.ndarray
        Design variables, shape (n_samples, dim).
    M : np.ndarray
        Rotation matrix, shape (dim, dim) or (1, dim).
    opt : np.ndarray
        Shift vector for objective, shape (dim,) or (1, dim).
    opt_con : np.ndarray
        Shift vector for constraint, shape (dim,) or (1, dim).

    Returns
    -------
    Obj : np.ndarray
        Objective values, shape (n_samples,).
    Con : np.ndarray
        Constraint violations, shape (n_samples,).
    """
    var = np.atleast_2d(var)
    ps, D = var.shape

    # Handle rotation matrix
    if M.size == 1 or (M.ndim == 2 and M.shape[0] == 1):
        M = np.eye(D) if M.size == 1 else float(M[0, 0]) * np.eye(D)

    # Handle shift vectors
    if opt.ndim == 1 or opt.shape[0] == 1:
        opt = np.atleast_1d(opt).flatten()
        if opt.size == 1:
            opt = np.full(D, opt[0])

    # Apply rotation and shift for objective
    x = (M[:D, :D] @ (var - opt[:D]).T).T

    # Compute Griewank objective
    sum1 = np.sum(x ** 2, axis=1)
    sum2 = np.ones(ps)
    for i in range(D):
        sum2 *= np.cos(x[:, i] / np.sqrt(i + 1))
    Obj = 1 + sum1 / 4000 - sum2

    # Compute constraint (Type 1: complex periodic)
    if opt_con.ndim == 1 or opt_con.shape[0] == 1:
        opt_con = np.atleast_1d(opt_con).flatten()
        if opt_con.size == 1:
            opt_con = np.full(D, opt_con[0])

    x_con = var - opt_con[:D]
    g = np.sum(x_con ** 2 - 5000 * np.cos(0.1 * np.pi * x_con) - 4000, axis=1)
    g[g < 0] = 0
    Con = g

    return Obj, Con


def C_Griewank2(var: np.ndarray, M: np.ndarray, opt: np.ndarray,
                opt_con: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
    """
    Griewank function with Type 2 constraint.

    Constraint Type 2: Spherical constraint
    g(x) = sum(x^2) - 100*D <= 0

    Parameters
    ----------
    var : np.ndarray
        Design variables, shape (n_samples, dim).
    M : np.ndarray
        Rotation matrix, shape (dim, dim) or (1, dim).
    opt : np.ndarray
        Shift vector for objective, shape (dim,) or (1, dim).
    opt_con : np.ndarray
        Shift vector for constraint, shape (dim,) or (1, dim).

    Returns
    -------
    Obj : np.ndarray
        Objective values, shape (n_samples,).
    Con : np.ndarray
        Constraint violations, shape (n_samples,).
    """
    var = np.atleast_2d(var)
    ps, D = var.shape

    # Handle rotation matrix
    if M.size == 1 or (M.ndim == 2 and M.shape[0] == 1):
        M = np.eye(D) if M.size == 1 else float(M[0, 0]) * np.eye(D)

    # Handle shift vectors
    if opt.ndim == 1 or opt.shape[0] == 1:
        opt = np.atleast_1d(opt).flatten()
        if opt.size == 1:
            opt = np.full(D, opt[0])

    # Apply rotation and shift for objective
    x = (M[:D, :D] @ (var - opt[:D]).T).T

    # Compute Griewank objective
    sum1 = np.sum(x ** 2, axis=1)
    sum2 = np.ones(ps)
    for i in range(D):
        sum2 *= np.cos(x[:, i] / np.sqrt(i + 1))
    Obj = 1 + sum1 / 4000 - sum2

    # Compute constraint (Type 2: spherical)
    if opt_con.ndim == 1 or opt_con.shape[0] == 1:
        opt_con = np.atleast_1d(opt_con).flatten()
        if opt_con.size == 1:
            opt_con = np.full(D, opt_con[0])

    x_con = var - opt_con[:D]
    g = np.sum(x_con ** 2, axis=1) - 100 * D
    g[g < 0] = 0
    Con = g

    return Obj, Con


# ==============================================================================
# Constrained Rastrigin Functions
# ==============================================================================

def C_Rastrigin1(var: np.ndarray, M: np.ndarray, opt: np.ndarray,
                 opt_con: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
    """
    Rastrigin function with Type 1 constraint.

    Constraint Type 1: Complex periodic constraint
    g(x) = sum(x^2 - 5000*cos(0.1*pi*x) - 4000) <= 0

    Parameters
    ----------
    var : np.ndarray
        Design variables, shape (n_samples, dim).
    M : np.ndarray
        Rotation matrix, shape (dim, dim) or (1, dim).
    opt : np.ndarray
        Shift vector for objective, shape (dim,) or (1, dim).
    opt_con : np.ndarray
        Shift vector for constraint, shape (dim,) or (1, dim).

    Returns
    -------
    Obj : np.ndarray
        Objective values, shape (n_samples,).
    Con : np.ndarray
        Constraint violations, shape (n_samples,).
    """
    var = np.atleast_2d(var)
    ps, D = var.shape

    # Handle rotation matrix
    if M.size == 1 or (M.ndim == 2 and M.shape[0] == 1):
        M = np.eye(D) if M.size == 1 else float(M[0, 0]) * np.eye(D)

    # Handle shift vectors
    if opt.ndim == 1 or opt.shape[0] == 1:
        opt = np.atleast_1d(opt).flatten()
        if opt.size == 1:
            opt = np.full(D, opt[0])

    # Apply rotation and shift for objective
    x = (M[:D, :D] @ (var - opt[:D]).T).T

    # Compute Rastrigin objective
    Obj = 10 * D + np.sum(x ** 2 - 10 * np.cos(2 * np.pi * x), axis=1)

    # Compute constraint (Type 1: complex periodic)
    if opt_con.ndim == 1 or opt_con.shape[0] == 1:
        opt_con = np.atleast_1d(opt_con).flatten()
        if opt_con.size == 1:
            opt_con = np.full(D, opt_con[0])

    x_con = 2 * (var - opt_con[:D])
    g = np.sum(x_con ** 2 - 5000 * np.cos(0.1 * np.pi * x_con) - 4000, axis=1)
    g[g < 0] = 0
    Con = g

    return Obj, Con


def C_Rastrigin2(var: np.ndarray, M: np.ndarray, opt: np.ndarray,
                 opt_con: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
    """
    Rastrigin function with Type 2 constraint.

    Constraint Type 2: Spherical constraint
    g(x) = sum(x^2) - 100*D <= 0

    Parameters
    ----------
    var : np.ndarray
        Design variables, shape (n_samples, dim).
    M : np.ndarray
        Rotation matrix, shape (dim, dim) or (1, dim).
    opt : np.ndarray
        Shift vector for objective, shape (dim,) or (1, dim).
    opt_con : np.ndarray
        Shift vector for constraint, shape (dim,) or (1, dim).

    Returns
    -------
    Obj : np.ndarray
        Objective values, shape (n_samples,).
    Con : np.ndarray
        Constraint violations, shape (n_samples,).
    """
    var = np.atleast_2d(var)
    ps, D = var.shape

    # Handle rotation matrix
    if M.size == 1 or (M.ndim == 2 and M.shape[0] == 1):
        M = np.eye(D) if M.size == 1 else float(M[0, 0]) * np.eye(D)

    # Handle shift vectors
    if opt.ndim == 1 or opt.shape[0] == 1:
        opt = np.atleast_1d(opt).flatten()
        if opt.size == 1:
            opt = np.full(D, opt[0])

    # Apply rotation and shift for objective
    x = (M[:D, :D] @ (var - opt[:D]).T).T

    # Compute Rastrigin objective
    Obj = 10 * D + np.sum(x ** 2 - 10 * np.cos(2 * np.pi * x), axis=1)

    # Compute constraint (Type 2: spherical)
    if opt_con.ndim == 1 or opt_con.shape[0] == 1:
        opt_con = np.atleast_1d(opt_con).flatten()
        if opt_con.size == 1:
            opt_con = np.full(D, opt_con[0])

    x_con = 2 * (var - opt_con[:D])
    g = np.sum(x_con ** 2, axis=1) - 100 * D
    g[g < 0] = 0
    Con = g

    return Obj, Con


def C_Rastrigin4(var: np.ndarray, M: np.ndarray, opt: np.ndarray,
                 opt_con: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
    """
    Rastrigin function with Type 4 constraint (equality-like).

    Constraint Type 4: Equality-like constraint
    h(x) = |sum(-x * sin(0.1*pi*x))| - 1e-4 <= 0

    Parameters
    ----------
    var : np.ndarray
        Design variables, shape (n_samples, dim).
    M : np.ndarray
        Rotation matrix, shape (dim, dim) or (1, dim).
    opt : np.ndarray
        Shift vector for objective, shape (dim,) or (1, dim).
    opt_con : np.ndarray
        Shift vector for constraint, shape (dim,) or (1, dim).

    Returns
    -------
    Obj : np.ndarray
        Objective values, shape (n_samples,).
    Con : np.ndarray
        Constraint violations, shape (n_samples,).
    """
    var = np.atleast_2d(var)
    ps, D = var.shape

    # Handle rotation matrix
    if M.size == 1 or (M.ndim == 2 and M.shape[0] == 1):
        M = np.eye(D) if M.size == 1 else float(M[0, 0]) * np.eye(D)

    # Handle shift vectors
    if opt.ndim == 1 or opt.shape[0] == 1:
        opt = np.atleast_1d(opt).flatten()
        if opt.size == 1:
            opt = np.full(D, opt[0])

    # Apply rotation and shift for objective
    x = (M[:D, :D] @ (var - opt[:D]).T).T

    # Compute Rastrigin objective
    Obj = 10 * D + np.sum(x ** 2 - 10 * np.cos(2 * np.pi * x), axis=1)

    # Compute constraint (Type 4: equality-like)
    if opt_con.ndim == 1 or opt_con.shape[0] == 1:
        opt_con = np.atleast_1d(opt_con).flatten()
        if opt_con.size == 1:
            opt_con = np.full(D, opt_con[0])

    x_con = 2 * (var - opt_con[:D])
    h = -np.sum(x_con * np.sin(0.1 * np.pi * x_con), axis=1)
    h = np.abs(h) - 1e-4
    h[h < 0] = 0
    Con = h

    return Obj, Con


# ==============================================================================
# Constrained Rosenbrock Functions
# ==============================================================================

def C_Rosenbrock1(var: np.ndarray, M: np.ndarray, opt: np.ndarray,
                  opt_con: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
    """
    Rosenbrock function with Type 1 constraint.

    Constraint Type 1: Complex periodic constraint
    g(x) = sum(x^2 - 5000*cos(0.1*pi*x) - 4000) <= 0

    Parameters
    ----------
    var : np.ndarray
        Design variables, shape (n_samples, dim).
    M : np.ndarray
        Rotation matrix, shape (dim, dim) or (1, dim).
    opt : np.ndarray
        Shift vector for objective, shape (dim,) or (1, dim).
    opt_con : np.ndarray
        Shift vector for constraint, shape (dim,) or (1, dim).

    Returns
    -------
    Obj : np.ndarray
        Objective values, shape (n_samples,).
    Con : np.ndarray
        Constraint violations, shape (n_samples,).
    """
    var = np.atleast_2d(var)
    ps, D = var.shape

    # Handle rotation matrix
    if M.size == 1 or (M.ndim == 2 and M.shape[0] == 1):
        M = np.eye(D) if M.size == 1 else float(M[0, 0]) * np.eye(D)

    # Handle shift vectors
    if opt.ndim == 1 or opt.shape[0] == 1:
        opt = np.atleast_1d(opt).flatten()
        if opt.size == 1:
            opt = np.full(D, opt[0])

    # Apply rotation and shift for objective
    x = (M[:D, :D] @ (var - opt[:D]).T).T

    # Compute Rosenbrock objective
    if D == 1:
        Obj = 100 * (x[:, 0] - x[:, 0] ** 2) ** 2 + (x[:, 0] - 1) ** 2
    else:
        Obj = np.sum(100 * (x[:, 1:] - x[:, :-1] ** 2) ** 2 +
                     (x[:, :-1] - 1) ** 2, axis=1)

    # Compute constraint (Type 1: complex periodic)
    if opt_con.ndim == 1 or opt_con.shape[0] == 1:
        opt_con = np.atleast_1d(opt_con).flatten()
        if opt_con.size == 1:
            opt_con = np.full(D, opt_con[0])

    x_con = 2 * (var - opt_con[:D])
    g = np.sum(x_con ** 2 - 5000 * np.cos(0.1 * np.pi * x_con) - 4000, axis=1)
    g[g < 0] = 0
    Con = g

    return Obj, Con


def C_Rosenbrock2(var: np.ndarray, M: np.ndarray, opt: np.ndarray,
                  opt_con: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
    """
    Rosenbrock function with Type 2 constraint.

    Constraint Type 2: Spherical constraint
    g(x) = sum(x^2) - 100*D <= 0

    Parameters
    ----------
    var : np.ndarray
        Design variables, shape (n_samples, dim).
    M : np.ndarray
        Rotation matrix, shape (dim, dim) or (1, dim).
    opt : np.ndarray
        Shift vector for objective, shape (dim,) or (1, dim).
    opt_con : np.ndarray
        Shift vector for constraint, shape (dim,) or (1, dim).

    Returns
    -------
    Obj : np.ndarray
        Objective values, shape (n_samples,).
    Con : np.ndarray
        Constraint violations, shape (n_samples,).
    """
    var = np.atleast_2d(var)
    ps, D = var.shape

    # Handle rotation matrix
    if M.size == 1 or (M.ndim == 2 and M.shape[0] == 1):
        M = np.eye(D) if M.size == 1 else float(M[0, 0]) * np.eye(D)

    # Handle shift vectors
    if opt.ndim == 1 or opt.shape[0] == 1:
        opt = np.atleast_1d(opt).flatten()
        if opt.size == 1:
            opt = np.full(D, opt[0])

    # Apply rotation and shift for objective
    x = (M[:D, :D] @ (var - opt[:D]).T).T

    # Compute Rosenbrock objective
    if D == 1:
        Obj = 100 * (x[:, 0] - x[:, 0] ** 2) ** 2 + (x[:, 0] - 1) ** 2
    else:
        Obj = np.sum(100 * (x[:, 1:] - x[:, :-1] ** 2) ** 2 +
                     (x[:, :-1] - 1) ** 2, axis=1)

    # Compute constraint (Type 2: spherical)
    if opt_con.ndim == 1 or opt_con.shape[0] == 1:
        opt_con = np.atleast_1d(opt_con).flatten()
        if opt_con.size == 1:
            opt_con = np.full(D, opt_con[0])

    x_con = 2 * (var - opt_con[:D])
    g = np.sum(x_con ** 2, axis=1) - 100 * D
    g[g < 0] = 0
    Con = g

    return Obj, Con


# ==============================================================================
# Constrained Schwefel Functions
# ==============================================================================

def C_Schwefel1(var: np.ndarray, M: np.ndarray, opt: np.ndarray,
                opt_con: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
    """
    Schwefel function with Type 1 constraint.

    Constraint Type 1: Complex periodic constraint (scaled by 0.2)
    g(x) = sum(x^2 - 5000*cos(0.1*pi*x) - 4000) <= 0

    Parameters
    ----------
    var : np.ndarray
        Design variables, shape (n_samples, dim).
    M : np.ndarray
        Rotation matrix, shape (dim, dim) or (1, dim).
    opt : np.ndarray
        Shift vector for objective, shape (dim,) or (1, dim).
    opt_con : np.ndarray
        Shift vector for constraint, shape (dim,) or (1, dim).

    Returns
    -------
    Obj : np.ndarray
        Objective values, shape (n_samples,).
    Con : np.ndarray
        Constraint violations, shape (n_samples,).
    """
    var = np.atleast_2d(var)
    ps, D = var.shape

    # Handle rotation matrix
    if M.size == 1 or (M.ndim == 2 and M.shape[0] == 1):
        M = np.eye(D) if M.size == 1 else float(M[0, 0]) * np.eye(D)

    # Handle shift vectors
    if opt.ndim == 1 or opt.shape[0] == 1:
        opt = np.atleast_1d(opt).flatten()
        if opt.size == 1:
            opt = np.full(D, opt[0])

    # Apply rotation and shift for objective
    x = (M[:D, :D] @ (var - opt[:D]).T).T

    # Compute Schwefel objective
    sum1 = np.sum(x * np.sin(np.sqrt(np.abs(x))), axis=1)
    Obj = 418.9829 * D - sum1

    # Compute constraint (Type 1: complex periodic, scaled by 0.2)
    if opt_con.ndim == 1 or opt_con.shape[0] == 1:
        opt_con = np.atleast_1d(opt_con).flatten()
        if opt_con.size == 1:
            opt_con = np.full(D, opt_con[0])

    x_con = 0.2 * (var - opt_con[:D])
    g = np.sum(x_con ** 2 - 5000 * np.cos(0.1 * np.pi * x_con) - 4000, axis=1)
    g[g < 0] = 0
    Con = g

    return Obj, Con


def C_Schwefel2(var: np.ndarray, M: np.ndarray, opt: np.ndarray,
                opt_con: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
    """
    Schwefel function with Type 2 constraint.

    Constraint Type 2: Spherical constraint (scaled by 0.2)
    g(x) = sum(x^2) - 100*D <= 0

    Parameters
    ----------
    var : np.ndarray
        Design variables, shape (n_samples, dim).
    M : np.ndarray
        Rotation matrix, shape (dim, dim) or (1, dim).
    opt : np.ndarray
        Shift vector for objective, shape (dim,) or (1, dim).
    opt_con : np.ndarray
        Shift vector for constraint, shape (dim,) or (1, dim).

    Returns
    -------
    Obj : np.ndarray
        Objective values, shape (n_samples,).
    Con : np.ndarray
        Constraint violations, shape (n_samples,).
    """
    var = np.atleast_2d(var)
    ps, D = var.shape

    # Handle rotation matrix
    if M.size == 1 or (M.ndim == 2 and M.shape[0] == 1):
        M = np.eye(D) if M.size == 1 else float(M[0, 0]) * np.eye(D)

    # Handle shift vectors
    if opt.ndim == 1 or opt.shape[0] == 1:
        opt = np.atleast_1d(opt).flatten()
        if opt.size == 1:
            opt = np.full(D, opt[0])

    # Apply rotation and shift for objective
    x = (M[:D, :D] @ (var - opt[:D]).T).T

    # Compute Schwefel objective
    sum1 = np.sum(x * np.sin(np.sqrt(np.abs(x))), axis=1)
    Obj = 418.9829 * D - sum1

    # Compute constraint (Type 2: spherical, scaled by 0.2)
    if opt_con.ndim == 1 or opt_con.shape[0] == 1:
        opt_con = np.atleast_1d(opt_con).flatten()
        if opt_con.size == 1:
            opt_con = np.full(D, opt_con[0])

    x_con = 0.2 * (var - opt_con[:D])
    g = np.sum(x_con ** 2, axis=1) - 100 * D
    g[g < 0] = 0
    Con = g

    return Obj, Con


# ==============================================================================
# Constrained Sphere Function
# ==============================================================================

def C_Sphere1(var: np.ndarray, M: np.ndarray, opt: np.ndarray,
              opt_con: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
    """
    Sphere function with Type 1 constraint.

    Constraint Type 1: Complex periodic constraint
    g(x) = sum(x^2 - 5000*cos(0.1*pi*x) - 4000) <= 0

    Parameters
    ----------
    var : np.ndarray
        Design variables, shape (n_samples, dim).
    M : np.ndarray
        Rotation matrix, shape (dim, dim) or (1, dim).
    opt : np.ndarray
        Shift vector for objective, shape (dim,) or (1, dim).
    opt_con : np.ndarray
        Shift vector for constraint, shape (dim,) or (1, dim).

    Returns
    -------
    Obj : np.ndarray
        Objective values, shape (n_samples,).
    Con : np.ndarray
        Constraint violations, shape (n_samples,).
    """
    var = np.atleast_2d(var)
    ps, D = var.shape

    # Handle rotation matrix
    if M.size == 1 or (M.ndim == 2 and M.shape[0] == 1):
        M = np.eye(D) if M.size == 1 else float(M[0, 0]) * np.eye(D)

    # Handle shift vectors
    if opt.ndim == 1 or opt.shape[0] == 1:
        opt = np.atleast_1d(opt).flatten()
        if opt.size == 1:
            opt = np.full(D, opt[0])

    # Apply rotation and shift for objective
    x = (M[:D, :D] @ (var - opt[:D]).T).T

    # Compute Sphere objective
    Obj = np.sum(x ** 2, axis=1)

    # Compute constraint (Type 1: complex periodic)
    if opt_con.ndim == 1 or opt_con.shape[0] == 1:
        opt_con = np.atleast_1d(opt_con).flatten()
        if opt_con.size == 1:
            opt_con = np.full(D, opt_con[0])

    x_con = var - opt_con[:D]
    g = np.sum(x_con ** 2 - 5000 * np.cos(0.1 * np.pi * x_con) - 4000, axis=1)
    g[g < 0] = 0
    Con = g

    return Obj, Con


# ==============================================================================
# Constrained Weierstrass Function
# ==============================================================================

def C_Weierstrass3(var: np.ndarray, M: np.ndarray, opt: np.ndarray,
                   opt_con: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
    """
    Weierstrass function with Type 3 constraint (two constraints).

    Constraint Type 3: Two constraints
    g1(x) = -sum(|x|) + 12*D <= 0
    g2(x) = sum(x^2) - 500*D <= 0

    Parameters
    ----------
    var : np.ndarray
        Design variables, shape (n_samples, dim).
    M : np.ndarray
        Rotation matrix, shape (dim, dim) or (1, dim).
    opt : np.ndarray
        Shift vector for objective, shape (dim,) or (1, dim).
    opt_con : np.ndarray
        Shift vector for constraint, shape (dim,) or (1, dim).

    Returns
    -------
    Obj : np.ndarray
        Objective values, shape (n_samples,).
    Con : np.ndarray
        Constraint violations, shape (n_samples, 2).
    """
    var = np.atleast_2d(var)
    ps, D = var.shape

    # Handle rotation matrix
    if M.size == 1 or (M.ndim == 2 and M.shape[0] == 1):
        M = np.eye(D) if M.size == 1 else float(M[0, 0]) * np.eye(D)

    # Handle shift vectors
    if opt.ndim == 1 or opt.shape[0] == 1:
        opt = np.atleast_1d(opt).flatten()
        if opt.size == 1:
            opt = np.full(D, opt[0])

    # Apply rotation and shift for objective
    x = (M[:D, :D] @ (var - opt[:D]).T).T

    # Compute Weierstrass objective
    a = 0.5
    b = 3
    kmax = 20
    Obj = np.zeros(ps)

    for i in range(D):
        for k in range(kmax + 1):
            Obj += a ** k * np.cos(2 * np.pi * b ** k * (x[:, i] + 0.5))

    for k in range(kmax + 1):
        Obj -= D * a ** k * np.cos(2 * np.pi * b ** k * 0.5)

    # Compute constraints (Type 3: two constraints, scaled by 200)
    if opt_con.ndim == 1 or opt_con.shape[0] == 1:
        opt_con = np.atleast_1d(opt_con).flatten()
        if opt_con.size == 1:
            opt_con = np.full(D, opt_con[0])

    x_con = 200 * (var - opt_con[:D])
    g1 = -np.sum(np.abs(x_con), axis=1) + 12 * D
    g2 = np.sum(x_con ** 2, axis=1) - 500 * D

    g1[g1 < 0] = 0
    g2[g2 < 0] = 0

    Con = np.column_stack([g1, g2])

    return Obj, Con



[docs] class CMT: """ CMT (Constrained Multi-Task) benchmark problems. This class provides constrained multi-task optimization problems with various function combinations and constraint types. """ problem_information = { 'n_cases': 9, 'n_tasks': '2', 'n_dims': 'D', 'n_objs': '1', 'n_cons': '[1, 2]', 'type': 'synthetic', } def __init__(self): pass
[docs] def CMT1(self, D: int = 50) -> MTOP: """ CMT Problem 1: Griewank (Type 1 constraint) + Rastrigin (Type 1 constraint). Parameters ---------- D : int, optional Number of decision variables (default is 50). Returns ------- MTOP A Multi-Task Optimization Problem instance. """ M1 = np.array([[1.0]]) opt1 = np.zeros(D) opt_con1 = -40 * np.ones(D) M2 = np.array([[1.0]]) opt2 = np.zeros(D) opt_con2 = 20 * np.ones(D) def task1_func(x): obj, con = C_Griewank1(x, M1, opt1, opt_con1) return obj, con def task2_func(x): obj, con = C_Rastrigin1(x, M2, opt2, opt_con2) return obj, con def task1_objective(x): obj, _ = task1_func(x) return obj def task1_constraint(x): _, con = task1_func(x) return con def task2_objective(x): obj, _ = task2_func(x) return obj def task2_constraint(x): _, con = task2_func(x) return con problem = MTOP() problem.add_task( objective_func=task1_objective, dim=D, constraint_func=task1_constraint, lower_bound=-100, upper_bound=100 ) problem.add_task( objective_func=task2_objective, dim=D, constraint_func=task2_constraint, lower_bound=-50, upper_bound=50 ) return problem
[docs] def CMT2(self, D: int = 50) -> MTOP: """ CMT Problem 2: Ackley (Type 2 constraint) + Rastrigin (Type 2 constraint). Parameters ---------- D : int, optional Number of decision variables (default is 50). Returns ------- MTOP A Multi-Task Optimization Problem instance. """ M1 = np.array([[1.0]]) opt1 = np.zeros(D) opt_con1 = -4 * np.ones(D) M2 = np.array([[1.0]]) opt2 = np.zeros(D) opt_con2 = 4 * np.ones(D) def task1_func(x): obj, con = C_Ackley2(x, M1, opt1, opt_con1) return obj, con def task2_func(x): obj, con = C_Rastrigin2(x, M2, opt2, opt_con2) return obj, con def task1_objective(x): obj, _ = task1_func(x) return obj def task1_constraint(x): _, con = task1_func(x) return con def task2_objective(x): obj, _ = task2_func(x) return obj def task2_constraint(x): _, con = task2_func(x) return con problem = MTOP() problem.add_task( objective_func=task1_objective, dim=D, constraint_func=task1_constraint, lower_bound=-50, upper_bound=50 ) problem.add_task( objective_func=task2_objective, dim=D, constraint_func=task2_constraint, lower_bound=-50, upper_bound=50 ) return problem
[docs] def CMT3(self, D: int = 50) -> MTOP: """ CMT Problem 3: Ackley (Type 2 constraint) + Schwefel (Type 1 constraint). Parameters ---------- D : int, optional Number of decision variables (default is 50). Returns ------- MTOP A Multi-Task Optimization Problem instance. """ M1 = np.array([[1.0]]) opt1 = 42.096 * np.ones(D) opt_con1 = 40 * np.ones(D) M2 = np.array([[1.0]]) opt2 = np.zeros(D) opt_con2 = 400 * np.ones(D) def task1_func(x): obj, con = C_Ackley2(x, M1, opt1, opt_con1) return obj, con def task2_func(x): obj, con = C_Schwefel1(x, M2, opt2, opt_con2) return obj, con def task1_objective(x): obj, _ = task1_func(x) return obj def task1_constraint(x): _, con = task1_func(x) return con def task2_objective(x): obj, _ = task2_func(x) return obj def task2_constraint(x): _, con = task2_func(x) return con problem = MTOP() problem.add_task( objective_func=task1_objective, dim=D, constraint_func=task1_constraint, lower_bound=-50, upper_bound=50 ) problem.add_task( objective_func=task2_objective, dim=D, constraint_func=task2_constraint, lower_bound=-500, upper_bound=500 ) return problem
[docs] def CMT4(self, D: int = 50) -> MTOP: """ CMT Problem 4: Rastrigin (Type 1 constraint) + Sphere (Type 1 constraint). Parameters ---------- D : int, optional Number of decision variables (default is 50). Returns ------- MTOP A Multi-Task Optimization Problem instance. """ M1 = np.array([[1.0]]) opt1 = np.zeros(D) opt_con1 = -20 * np.ones(D) M2 = np.array([[1.0]]) opt2 = np.zeros(D) opt_con2 = 30 * np.ones(D) def task1_func(x): obj, con = C_Rastrigin1(x, M1, opt1, opt_con1) return obj, con def task2_func(x): obj, con = C_Sphere1(x, M2, opt2, opt_con2) return obj, con def task1_objective(x): obj, _ = task1_func(x) return obj def task1_constraint(x): _, con = task1_func(x) return con def task2_objective(x): obj, _ = task2_func(x) return obj def task2_constraint(x): _, con = task2_func(x) return con problem = MTOP() problem.add_task( objective_func=task1_objective, dim=D, constraint_func=task1_constraint, lower_bound=-50, upper_bound=50 ) problem.add_task( objective_func=task2_objective, dim=D, constraint_func=task2_constraint, lower_bound=-100, upper_bound=100 ) return problem
[docs] def CMT5(self, D: int = 50) -> MTOP: """ CMT Problem 5: Ackley (Type 1 constraint) + Rosenbrock (Type 2 constraint). Parameters ---------- D : int, optional Number of decision variables (default is 50). Returns ------- MTOP A Multi-Task Optimization Problem instance. """ M1 = np.array([[1.0]]) opt1 = np.zeros(D) opt_con1 = -30 * np.ones(D) M2 = np.array([[1.0]]) opt2 = np.zeros(D) opt_con2 = np.zeros(D) def task1_func(x): obj, con = C_Ackley1(x, M1, opt1, opt_con1) return obj, con def task2_func(x): obj, con = C_Rosenbrock2(x, M2, opt2, opt_con2) return obj, con def task1_objective(x): obj, _ = task1_func(x) return obj def task1_constraint(x): _, con = task1_func(x) return con def task2_objective(x): obj, _ = task2_func(x) return obj def task2_constraint(x): _, con = task2_func(x) return con problem = MTOP() problem.add_task( objective_func=task1_objective, dim=D, constraint_func=task1_constraint, lower_bound=-50, upper_bound=50 ) problem.add_task( objective_func=task2_objective, dim=D, constraint_func=task2_constraint, lower_bound=-50, upper_bound=50 ) return problem
[docs] def CMT6(self, D: int = 50) -> MTOP: """ CMT Problem 6: Ackley (Type 2 constraint) + Weierstrass (Type 3 constraint). Parameters ---------- D : int, optional Number of decision variables (default is 50). Returns ------- MTOP A Multi-Task Optimization Problem instance. """ M1 = np.array([[1.0]]) opt1 = 2 * np.ones(D) opt_con1 = np.zeros(D) M2 = np.array([[1.0]]) opt2 = 0.1 * np.ones(D) opt_con2 = np.zeros(D) def task1_func(x): obj, con = C_Ackley2(x, M1, opt1, opt_con1) return obj, con def task2_func(x): obj, con = C_Weierstrass3(x, M2, opt2, opt_con2) return obj, con def task1_objective(x): obj, _ = task1_func(x) return obj def task1_constraint(x): _, con = task1_func(x) return con def task2_objective(x): obj, _ = task2_func(x) return obj def task2_constraint(x): _, con = task2_func(x) return con problem = MTOP() problem.add_task( objective_func=task1_objective, dim=D, constraint_func=task1_constraint, lower_bound=-50, upper_bound=50 ) problem.add_task( objective_func=task2_objective, dim=D, constraint_func=task2_constraint, lower_bound=-0.5, upper_bound=0.5 ) return problem
[docs] def CMT7(self, D: int = 50) -> MTOP: """ CMT Problem 7: Rosenbrock (Type 1 constraint) + Rastrigin (Type 1 constraint). Parameters ---------- D : int, optional Number of decision variables (default is 50). Returns ------- MTOP A Multi-Task Optimization Problem instance. """ M1 = np.array([[1.0]]) opt1 = -30 * np.ones(D) opt_con1 = -35 * np.ones(D) M2 = np.array([[1.0]]) opt2 = 35 * np.ones(D) opt_con2 = 40 * np.ones(D) def task1_func(x): obj, con = C_Rosenbrock1(x, M1, opt1, opt_con1) return obj, con def task2_func(x): obj, con = C_Rastrigin1(x, M2, opt2, opt_con2) return obj, con def task1_objective(x): obj, _ = task1_func(x) return obj def task1_constraint(x): _, con = task1_func(x) return con def task2_objective(x): obj, _ = task2_func(x) return obj def task2_constraint(x): _, con = task2_func(x) return con problem = MTOP() problem.add_task( objective_func=task1_objective, dim=D, constraint_func=task1_constraint, lower_bound=-50, upper_bound=50 ) problem.add_task( objective_func=task2_objective, dim=D, constraint_func=task2_constraint, lower_bound=-50, upper_bound=50 ) return problem
[docs] def CMT8(self, D: int = 50) -> MTOP: """ CMT Problem 8: Griewank (Type 2 constraint) + Weierstrass (Type 3 constraint). Parameters ---------- D : int, optional Number of decision variables (default is 50). Returns ------- MTOP A Multi-Task Optimization Problem instance. """ M1 = np.array([[1.0]]) opt1 = np.zeros(D) opt_con1 = -30 * np.ones(D) M2 = np.array([[1.0]]) opt2 = np.zeros(D) opt_con2 = 0.2 * np.ones(D) def task1_func(x): obj, con = C_Griewank2(x, M1, opt1, opt_con1) return obj, con def task2_func(x): obj, con = C_Weierstrass3(x, M2, opt2, opt_con2) return obj, con def task1_objective(x): obj, _ = task1_func(x) return obj def task1_constraint(x): _, con = task1_func(x) return con def task2_objective(x): obj, _ = task2_func(x) return obj def task2_constraint(x): _, con = task2_func(x) return con problem = MTOP() problem.add_task( objective_func=task1_objective, dim=D, constraint_func=task1_constraint, lower_bound=-100, upper_bound=100 ) problem.add_task( objective_func=task2_objective, dim=D, constraint_func=task2_constraint, lower_bound=-0.5, upper_bound=0.5 ) return problem
[docs] def CMT9(self, D: int = 50) -> MTOP: """ CMT Problem 9: Rastrigin (Type 4 constraint) + Schwefel (Type 2 constraint). Parameters ---------- D : int, optional Number of decision variables (default is 50). Returns ------- MTOP A Multi-Task Optimization Problem instance. """ M1 = np.array([[1.0]]) opt1 = -10 * np.ones(D) opt_con1 = np.zeros(D) M2 = np.array([[1.0]]) opt2 = np.zeros(D) opt_con2 = 100 * np.ones(D) def task1_func(x): obj, con = C_Rastrigin4(x, M1, opt1, opt_con1) return obj, con def task2_func(x): obj, con = C_Schwefel2(x, M2, opt2, opt_con2) return obj, con def task1_objective(x): obj, _ = task1_func(x) return obj def task1_constraint(x): _, con = task1_func(x) return con def task2_objective(x): obj, _ = task2_func(x) return obj def task2_constraint(x): _, con = task2_func(x) return con problem = MTOP() problem.add_task( objective_func=task1_objective, dim=D, constraint_func=task1_constraint, lower_bound=-50, upper_bound=50 ) problem.add_task( objective_func=task2_objective, dim=D, constraint_func=task2_constraint, lower_bound=-500, upper_bound=500 ) return problem