Source code for ddmtolab.Problems.RWO.pkacp

import numpy as np
import os
import scipy.io
from scipy.cluster.vq import kmeans2
from ddmtolab.Methods.mtop import MTOP


[docs] class PKACP: """ Implementation of Planar Kinematic Arm Control Problem (PKACP) for Multi-Task/Many-Task Single-Objective Optimization. This problem involves controlling a planar kinematic arm with multiple joints to reach a target position. Each task has different constraints on the maximum angular range and link lengths, creating diverse optimization landscapes. The objective is to minimize the Euclidean distance between the end effector position and the target position (0.5, 0.5). References ---------- [1] Y. Jiang et al., "A Bi-Objective Knowledge Transfer Framework for Evolutionary Many-Task Optimization," IEEE TEVC, 2022. [2] H. Xu et al., "Evolutionary Multi-Task Optimization with Adaptive Knowledge Transfer," IEEE TEVC, 2021. Attributes ---------- data_dir : str Directory for task parameter files (in user's home directory) """ problem_information = { 'n_cases': 1, 'n_tasks': 'K', 'n_dims': 'D', 'n_objs': '1', 'n_cons': '0', 'type': 'real_world', } def __init__(self): """Initialize PKACP problem.""" # Use user's home directory for storing generated parameters home_dir = os.path.expanduser('~') self.data_dir = os.path.join(home_dir, '.ddmtolab', 'PKACP_data') # Create directory if it doesn't exist if not os.path.exists(self.data_dir): os.makedirs(self.data_dir)
[docs] def P1(self, task_num=20, dim=20) -> MTOP: """ Generates PKACP Problem 1: Planar Kinematic Arm Control. Creates multiple tasks with different maximum angular ranges and link lengths. Each task optimizes joint angles to minimize the distance between the end effector and target position. Parameters ---------- task_num : int, optional Number of tasks to create (default: 20) dim : int, optional Number of joints (dimensionality) for each task (default: 20) Task Structure: - T_i: 1-objective, dim-dimensional * Decision variables: Joint angles in [0, 1] * These are scaled to actual angular ranges based on task parameters * Objective: Euclidean distance to target (0.5, 0.5) Task Parameters: - Amax: Maximum angular range for the task - Lmax: Maximum total link length for the task - These parameters are generated using CVT (Centroidal Voronoi Tessellation) to create diverse but structured task variations - Relationship: Tasks differ in kinematic constraints, testing transfer learning across different arm configurations. Returns ------- MTOP A Multi-Task Optimization Problem instance with task_num tasks. """ # Generate or load task parameters task_para = self._generate_task_parameters(task_num, dim) problem = MTOP() for t in range(task_num): # Extract task-specific parameters Amax = task_para[t, 0] # Maximum angular range Lmax = task_para[t, 1] # Maximum link length # All joints have angles in [0, 1] lb = np.zeros(dim) ub = np.ones(dim) # Create task function with closure def create_task_function(amax, lmax, d): def task_func(x): return self._evaluate_pkacp(x, amax, lmax, d) return task_func task_function = create_task_function(Amax, Lmax, dim) problem.add_task( task_function, dim=dim, lower_bound=lb, upper_bound=ub ) return problem
def _generate_task_parameters(self, task_num, dim): """ Generate or load task parameters using CVT. Parameters ---------- task_num : int Number of tasks dim : int Dimensionality Returns ------- task_para : ndarray, shape (task_num, 2) Task parameters [Amax, Lmax] for each task """ # Check if parameters already exist file_name = os.path.join( self.data_dir, f'cvt_d{dim}_nt{task_num}.mat' ) if os.path.exists(file_name): # Load existing parameters data = scipy.io.loadmat(file_name) task_para = data['task_para'] else: # Generate new parameters using CVT (k-means approximation) samples = 50 * task_num x = np.random.rand(samples, 2) # Use k-means to approximate CVT task_para, _ = kmeans2(x, task_num, minit='points') # Save parameters scipy.io.savemat(file_name, {'task_para': task_para}) return task_para def _evaluate_pkacp(self, angles_var, Amax, Lmax, num_joints): """ Evaluate the kinematic arm control objective. Parameters ---------- angles_var : array-like, shape (n_samples, dim) or (dim,) Joint angles in [0, 1] Amax : float Maximum angular range for this task Lmax : float Maximum total link length for this task num_joints : int Number of joints Returns ------- objs : ndarray, shape (n_samples,) Distance to target for each sample """ angles_var = np.atleast_2d(angles_var) n_samples = angles_var.shape[0] # Calculate angular range and link lengths angular_range = Amax / num_joints lengths = np.ones(num_joints) * Lmax / num_joints # Target position target = np.array([0.5, 0.5]) objs = np.zeros(n_samples) for i in range(n_samples): angles = angles_var[i, :] # Convert to actual commands (centered around 0) command = (angles - 0.5) * angular_range * np.pi * 2 # Forward kinematics ef = self._forward_kinematics(command, lengths) # Calculate distance to target fitness = np.sqrt(np.sum((ef - target) ** 2)) objs[i] = fitness return objs def _forward_kinematics(self, joint_angles, lengths): """ Calculate end effector position using forward kinematics. Parameters ---------- joint_angles : ndarray, shape (n_joints,) Joint angles in radians lengths : ndarray, shape (n_joints,) Link lengths Returns ------- joint_xy : ndarray, shape (2,) End effector position (x, y) """ mat = np.eye(4) # Add zero angle at the end angles = np.append(joint_angles, 0) n_dofs = len(angles) # Prepend zero length lengths = np.insert(lengths, 0, 0) joint_xy = np.zeros(2) for i in range(n_dofs): # Create transformation matrix for this joint c = np.cos(angles[i]) s = np.sin(angles[i]) m = np.array([ [c, -s, 0, lengths[i]], [s, c, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1] ]) # Accumulate transformations mat = mat @ m # Get position of current joint v = mat @ np.array([0, 0, 0, 1]) joint_xy = v[:2] return joint_xy