Source code for opfunu.cec_based.cec

#!/usr/bin/env python
# Created by "Thieu" at 06:43, 30/06/2022 ----------%                                                                               
#       Email: nguyenthieu2102@gmail.com            %                                                    
#       Github: https://github.com/thieu1995        %                         
# --------------------------------------------------%

import numpy as np
import pkg_resources
from abc import ABC
from opfunu.benchmark import Benchmark


[docs]class CecBenchmark(Benchmark, ABC): """ Defines an abstract class for optimization benchmark problem. All subclasses should implement the ``evaluate`` method for a particular optimization problem. Attributes ---------- bounds : list The lower/upper bounds of the problem. This a 2D-matrix of [lower, upper] array that contain the lower and upper bounds. By default, each problem has its own bounds. But user can try to put different bounds to test the problem. ndim : int The dimensionality of the problem. It is calculated from bounds lb : np.ndarray The lower bounds for the problem ub : np.ndarray The upper bounds for the problem f_global : float The global optimum of the evaluated function. x_global : np.ndarray A list of vectors that provide the locations of the global minimum. Note that some problems have multiple global minima, not all of which may be listed. n_fe : int The number of function evaluations that the object has been asked to calculate. dim_changeable : bool Whether we can change the benchmark function `x` variable length (i.e., the dimensionality of the problem) """ name = "Benchmark name" latex_formula = r'f(\mathbf{x})' latex_formula_dimension = r'd \in \mathbb{N}_{+}^{*}' latex_formula_bounds = r'x_i \in [-2\pi, 2\pi], \forall i \in \llbracket 1, d\rrbracket' latex_formula_global_optimum = r'f(0, ..., 0)=-1, \text{ for}, m=5, \beta=15' continuous = True linear = False convex = True unimodal = False separable = False differentiable = True scalable = True randomized_term = False parametric = True shifted = True rotated = False modality = True # Number of ambiguous peaks, unknown # peaks # n_basins = 1 # n_valleys = 1 def __init__(self): super().__init__() self._bounds = None self._ndim = None self.dim_changeable = True self.dim_default = 30 self.dim_max = 100 self.dim_supported = None self.f_global = None self.x_global = None self.n_fe = 0 self.f_shift = None self.f_bias = None self.support_path = None self.verbose = False
[docs] def make_support_data_path(self, data_name): self.support_path = pkg_resources.resource_filename("opfunu", f"cec_based/{data_name}")
[docs] def check_shift_data(self, f_shift): if type(f_shift) is str: return self.load_shift_data(f_shift) else: if type(f_shift) in [list, tuple, np.ndarray]: return np.squeeze(f_shift) else: raise ValueError(f"The shift data should be a list/tuple or np.array!")
[docs] def check_shift_matrix(self, f_shift, selected_idx=None): if type(f_shift) is str: if selected_idx is None: return self.load_matrix_data(f_shift) else: return self.load_matrix_data(f_shift)[selected_idx, :self.ndim] else: if type(f_shift) in [list, tuple, np.ndarray]: return np.squeeze(f_shift) else: raise ValueError(f"The shift data should be a list/tuple or np.array!")
[docs] def check_matrix_data(self, f_matrix, needed_dim=True): if type(f_matrix) is str: if needed_dim: return self.load_matrix_data(f"{f_matrix}{self.ndim}") else: return self.load_matrix_data(f_matrix) else: if type(f_matrix) is np.ndarray: return np.squeeze(f_matrix) else: raise ValueError(f"The matrix data should be an orthogonal matrix (2D np.array)!")
[docs] def check_shuffle_data(self, f_shuffle, needed_dim=True): if type(f_shuffle) is str: if needed_dim: return self.load_shift_data(f"{f_shuffle}{self.ndim}") else: return self.load_shift_data(f_shuffle) else: if type(f_shuffle) in [list, tuple, np.ndarray]: return np.squeeze(f_shuffle) else: raise ValueError(f"The shuffle data should be a list/tuple or np.array!")
[docs] def check_m_group(self, m_group=None): if type(m_group) is int: if int(self.ndim / m_group) > 1: return m_group else: raise ValueError(f"ndim is too small or m_group is too large!") else: raise ValueError(f"m_group is positive integer!")
[docs] def load_shift_data(self, filename=None): data = np.genfromtxt(f"{self.support_path}/{filename}.txt", dtype=float) return data.reshape((-1))
[docs] def load_matrix_data(self, filename=None): try: data = np.genfromtxt(f"{self.support_path}/{filename}.txt", dtype=float) return data except FileNotFoundError: print(f'The file named: {filename}.txt is not found.') print(f"{self.__class__.__name__} problem is only supported ndim in {self.dim_supported}!") exit(1)
[docs] def load_shift_and_matrix_data(self, filename=None): data = np.genfromtxt(f"{self.support_path}/{filename}.txt", dtype=float) shift_data = data[:1, :].ravel() matrix_data = data[1:, :] return shift_data, matrix_data
[docs] def load_two_matrix_and_shift_data(self, filename=None): data = np.genfromtxt(f"{self.support_path}/{filename}.txt", dtype=float) a_matrix = data[:100, :] b_matrix = data[100:200, :] shift_data = data[200:, :].ravel() return shift_data, a_matrix, b_matrix
[docs] def check_solution(self, x, dim_max=None, dim_support=None): """ Raise the error if the problem size is not equal to the solution length Parameters ---------- x : np.ndarray The solution dim_max : The maximum number of variables that the function is supported dim_support : List of the supported dimensions """ # if not self.dim_changeable and (len(x) != self._ndim): if len(x) != self._ndim: raise ValueError(f"{self.__class__.__name__} problem, the length of solution should have {self._ndim} variables!") if (dim_max is not None) and (len(x) > dim_max): raise ValueError(f"{self.__class__.__name__} problem is not supported ndim > {dim_max}!") if (dim_support is not None) and (len(x) not in dim_support): raise ValueError(f"{self.__class__.__name__} problem is only supported ndim in {dim_support}!")
[docs] def check_ndim_and_bounds(self, ndim=None, dim_max=None, bounds=None, default_bounds=None): """ Check the bounds when initializing the object. Parameters ---------- ndim : int The number of dimensions (variables) dim_max : int The maximum number of dimensions (variables) that the problem is supported bounds : list, tuple, np.ndarray List of lower bound and upper bound, should use default None value default_bounds : np.ndarray List of initial lower bound and upper bound values """ if ndim is None: self._bounds = default_bounds if bounds is None else np.array(bounds).T self._ndim = self._bounds.shape[0] if dim_max is not None and self._ndim > dim_max: raise ValueError(f"{self.__class__.__name__} problem supports maximum {dim_max} variables!") else: if bounds is None: if self.dim_changeable: if type(ndim) is int and ndim > 1: if dim_max is None or ndim <= dim_max: # # Check if ndim in supported dimensions # if self.dim_supported is not None and ndim not in self.dim_supported: # raise ValueError(f'{self.__class__.__name__} ndim not in supported dimensions ' # f'{self.dim_supported}') self._ndim = int(ndim) self._bounds = np.array([default_bounds[0] for _ in range(self._ndim)]) else: raise ValueError(f"{self.__class__.__name__} problem supports maximum {dim_max} variables!") else: raise ValueError('ndim must be an integer and > 1!') else: self._ndim = self.dim_default self._bounds = default_bounds if self.verbose: print(f"{self.__class__.__name__} is fixed problem with {self.dim_default} variables!") else: if self.dim_changeable: self._bounds = np.array(bounds).T self._ndim = self._bounds.shape[0] if self._ndim > dim_max: raise ValueError(f"{self.__class__.__name__} problem supports maximum {dim_max} variables!") else: print(f"{self.__class__.__name__} problem is set with {self._ndim} variables!") else: self._bounds = np.array(bounds).T if self._bounds.shape[0] == self.dim_default: self._ndim = self.dim_default else: raise ValueError(f"{self.__class__.__name__} is fixed problem with {self._ndim} variables. Please setup the correct bounds!")