Source code for beeoptimal.benchmarks

#++++++++++++++++++++++++++++++++++++
# Libraries and modules
#++++++++++++++++++++++++++++++++++++

import numpy as np

#++++++++++++++++++++++++++++++++++++
# BenchmarkFunction class
#++++++++++++++++++++++++++++++++++++

[docs] class BenchmarkFunction: """ Class to define a D-dimensional benchmark function for optimization. Attributes: fun (callable) : The function to evaluate. bounds (numpy-array) : The bounds of the function, provided as a numpy array of shape `(D,2)` or `(2,D)`. optimal_solution (numpy-array): The known solution achieving the optimal value, provided as numpy array of shape `(D,)`, `(D,1)` or `(1,D)`. name (str, optional) : The name of the function. Defaults to empty string. Examples: >>> from beeoptimal.benchmarks import BenchmarkFunction >>> import numpy as np >>> sphere = lambda point: np.sum(np.array(point)**2) >>> Sphere2d = BenchmarkFunction( name = "Sphere-2d", fun = sphere, bounds = np.array([(-5.12, 5.12)]*2), optimal_solution = np.zeros(2) ) """ def __init__(self, fun, bounds, optimal_solution, name = ''): """ Initialize a benchmark function. Args: fun (callable) : The function to evaluate. bounds (numpy-array) : The bounds of the function, provided as a numpy array of shape `(D,2)` or (2,D) optimal_solution (numpy-array): The known solution achieving the optimal value, provided as numpy array of shape `(D,)`, `(D,1)` or `(1,D)` name (str) : The name of the function. Defaults to empty string. Raises: TypeError : If `function` is not callable. TypeError : If `bounds` is not a numpy array. ValueError : If `bounds` does not have shape `(D, 2)` or `(2, D)`. ValueError : If any dimension has its lower bound greater than the upper bound. ValueError : If `optimal_position` and `bounds` do not have compatible dimensions. """ if not callable(fun): raise TypeError("`function` must be callable.") self.fun = fun if not isinstance(name, str): raise TypeError("`name` must be provided as a string.") self.name = name if not isinstance(bounds, np.ndarray): raise TypeError("`bounds` must be provided as a numpy array.") if not ((bounds.shape[0] == 2) or (bounds.shape[1] == 2)): raise ValueError(f"`bounds` must have shape `(D, 2)` or `(2, D)`, but got {bounds.shape}") self.bounds = bounds.reshape(-1,2) if not np.all(self.bounds[:, 0] <= self.bounds[:, 1]): raise ValueError("Each lower bound must be less than or equal to its upper bound.") if not isinstance(optimal_solution, np.ndarray): raise TypeError("`optimal_solution` must be provided as a numpy array.") if optimal_solution.reshape(-1, 1).shape[0] != self.bounds.shape[0]: raise ValueError(f"`optimal_solution` dimensionality ({optimal_solution.reshape(-1, 1).shape[0]}) is not compatible with the bounds provided.") self.optimal_solution = optimal_solution
[docs] def evaluate(self, point): """ Evaluate the function at the given point. Args: point (array-like) : The point at which to evaluate the function, provided as numpy array of shape `(D,)`, `(D,1)` or `(1,D)`. Returns: float: The value of the function computed at the given point. Raises: TypeError : If the point is not provided as a numpy array. ValueError : If the point dimensions are not consistent with the optimal solution """ if not isinstance(point,np.ndarray): raise TypeError("`point` must be provided as a numpy array") if point.shape != self.optimal_solution.shape: raise ValueError(f"`point` shape is not consistent with `optimalsolution`'s one. Got {point.shape} vs {self.optimal_solution.shape}") return self.fun(point)
@property def optimal_value(self): """Return the optimal value of the function.""" return self.evaluate(self.optimal_solution)
#++++++++++++++++++++++++++++++++++++ # Callable benchmark functions #++++++++++++++++++++++++++++++++++++ def sphere(point): """ Compute the value of the sphere function at the given point. Args: point (array-like): The point at which to evaluate the function, provided as numpy array of shape `(D,)`, `(D,1)` or `(1,D)` or list (or tuple) of length D. Returns: float: The value of the function computed at the given point. """ point = np.array(point).flatten() return np.sum(point**2) def rosenbrock(point): """ Compute the value of the rosenbrock function at the given point. Args: point (array-like): The point at which to evaluate the function, provided as numpy array of shape `(D,)`, `(D,1)` or `(1,D)` or list (or tuple) of length D. Returns: float: The value of the function computed at the given point. """ point = np.array(point).flatten() return np.sum(100*(point[1:] - point[:-1]**2)**2 + (point[:-1]-1)**2) def ackley(point): """ Compute the value of the acklet function at the given point. Args: point (array-like): The point at which to evaluate the function, provided as numpy array of shape `(D,)`, `(D,1)` or `(1,D)` or list (or tuple) of length D. Returns: float: The value of the function computed at the given point. """ point = np.array(point).flatten() D = len(point) return -20 * np.exp(-0.2 * np.sqrt(np.sum(point**2) / D)) - np.exp(np.sum(np.cos(2 * np.pi * point)) / D) + 20 + np.e def rastrigin(point): """ Compute the value of the rastrigin function at the given point. Args: point (array-like): The point at which to evaluate the function, provided as numpy array of shape `(D,)`, `(D,1)` or `(1,D)` or list (or tuple) of length D. Returns: float: The value of the function computed at the given point. """ point = np.array(point).flatten() return (10*len(point) + np.sum((point**2 - 10*np.cos(2*np.pi*point)))) def weierstrass(point,a=0.5,b=3,k_max=20): """ Compute the value of the weierstrass function at the given point. Args: point (array-like): The point at which to evaluate the function, provided as numpy array of shape `(D,)`, `(D,1)` or `(1,D)` or list (or tuple) of length D. Returns: float: The value of the function computed at the given point. """ point = np.array(point).flatten() k = np.arange(k_max + 1) term1 = np.sum(a**k[:, None] * np.cos(2 * np.pi * b**k[:, None] * (point + 0.5)), axis=0) term2 = np.sum(a**k * np.cos(2 * np.pi * b**k * 0.5)) return np.sum(term1) - len(point) * term2 def griewank(point): """ Compute the value of the griewank function at the given point. Args: point (array-like): The point at which to evaluate the function, provided as numpy array of shape `(D,)`, `(D,1)` or `(1,D)` or list (or tuple) of length D. Returns: float: The value of the function computed at the given point. """ point = np.array(point).flatten() return 1 + np.sum(point**2)/4000 - np.prod(np.cos(point/np.sqrt(np.arange(1, len(point)+1)))) def schwefel(point): """ Compute the value of the schwefel function at the given point. Args: point (array-like): The point at which to evaluate the function, provided as numpy array of shape `(D,)`, `(D,1)` or `(1,D)` or list (or tuple) of length D. Returns: float: The value of the function computed at the given point. """ point = np.array(point).flatten() return 418.9829*len(point) - np.sum(point*np.sin(np.sqrt(np.abs(point)))) def sumsquares(point): """ Compute the value of the sumsquares function at the given point. Args: point (array-like): The point at which to evaluate the function, provided as numpy array of shape `(D,)`, `(D,1)` or `(1,D)`or list (or tuple) of length D. Returns: float: The value of the function computed at the given point. """ point = np.array(point).flatten() return np.sum(np.arange(1, len(point)+1)* (point**2) ) def eggholder(point): """ Compute the value of the eggholder function at the given point. Args: point (array-like): The point at which to evaluate the function, provided as numpy array of shape `(2,)`, `(2,1)` or `(1,2)`. Returns: float: The value of the function computed at the given point. Raises: AssertionError: If the point is not provided as a numpy array """ point = np.array(point).flatten() assert len(point) == 2, "The Eggholder function is only defined for 2 dimensions" return -(point[1] + 47)*np.sin(np.sqrt(np.abs(point[0]/2 + point[1] + 47))) - point[0]*np.sin(np.sqrt(np.abs(point[0]-(point[1]+47)))) #++++++++++++++++++++++++++++++++++++ # Benchmark functions for testing #++++++++++++++++++++++++++++++++++++ # 2d functions Sphere2d = BenchmarkFunction( name = "Sphere-2d", fun = sphere, bounds = np.array([(-5.12, 5.12)]*2), optimal_solution = np.zeros(2) ) Rosenbrock2d = BenchmarkFunction( name = "Rosenbrock-2d", fun = rosenbrock, bounds = np.array([(-2.048, 2.048)]*2), optimal_solution = np.ones(2) ) Ackley2d = BenchmarkFunction( name = "Ackley-2d", fun = ackley, bounds = np.array([(-5, 5)]*2), optimal_solution = np.zeros(2) ) Rastrigin2d = BenchmarkFunction( name = "Rastrigin-2d", fun = rastrigin, bounds = np.array([(-5.12, 5.12)]*2), optimal_solution = np.zeros(2) ) Weierstrass2d = BenchmarkFunction( name = "Weierstrass-2d", fun = weierstrass, bounds = np.array([(-0.5, 0.5)]*2), optimal_solution = np.zeros(2) ) Griewank2d = BenchmarkFunction( name = "Griewank-2d", fun = griewank, bounds = np.array([(-600, 600)]*2), optimal_solution = np.zeros(2) ) Schwefel2d = BenchmarkFunction( name = "Schwefel-2d", fun = schwefel, bounds = np.array([(-500, 500)]*2), optimal_solution = np.full(2,420.9687) ) Sumsquares2d = BenchmarkFunction( name = "Sumsquares-2d", fun = sumsquares, bounds = np.array([(-10, 10)]*2), optimal_solution = np.zeros(2) ) Eggholder = BenchmarkFunction( name = "Eggholder", fun = eggholder, bounds = np.array([(-512, 512)]*2), optimal_solution = np.array([512,404.2319]) ) # 10d functions Sphere10d = BenchmarkFunction( name = "Sphere-10d", fun = sphere, bounds = np.array([(-5.12, 5.12)]*10), optimal_solution = np.zeros(10) ) Rosenbrock10d = BenchmarkFunction( name = "Rosenbrock-10d", fun = rosenbrock, bounds = np.array([(-2.048, 2.048)]*10), optimal_solution = np.ones(10) ) Ackley10d = BenchmarkFunction( name = "Ackley-10d", fun = ackley, bounds = np.array([(-5, 5)]*10), optimal_solution = np.zeros(10) ) Rastrigin10d = BenchmarkFunction( name = "Rastrigin-10d", fun = rastrigin, bounds = np.array([(-5.12, 5.12)]*10), optimal_solution = np.zeros(10) ) Weierstrass10d = BenchmarkFunction( name = "Weierstrass-10d", fun = weierstrass, bounds = np.array([(-0.5, 0.5)]*10), optimal_solution = np.zeros(10) ) Griewank10d = BenchmarkFunction( name = "Griewank-10d", fun = griewank, bounds = np.array([(-600, 600)]*10), optimal_solution = np.zeros(10) ) Schwefel10d = BenchmarkFunction( name = "Schwefel-10d", fun = schwefel, bounds = np.array([(-500, 500)]*10), optimal_solution = np.full(10,420.9687) ) Sumsquares10d = BenchmarkFunction( name = "Sumsquares-10d", fun = sumsquares, bounds = np.array([(-10, 10)]*10), optimal_solution = np.zeros(10) ) # 30d functions Sphere30d = BenchmarkFunction( name = "Sphere-30d", fun = sphere, bounds = np.array([(-5.12, 5.12)]*30), optimal_solution = np.zeros(30) ) Rosenbrock30d = BenchmarkFunction( name = "Rosenbrock-30d", fun = rosenbrock, bounds = np.array([(-2.048, 2.048)]*30), optimal_solution = np.ones(30) ) Ackley30d = BenchmarkFunction( name = "Ackley-30d", fun = ackley, bounds = np.array([(-5, 5)]*30), optimal_solution = np.zeros(30) ) Rastrigin30d = BenchmarkFunction( name = "Rastrigin-30d", fun = rastrigin, bounds = np.array([(-5.12, 5.12)]*30), optimal_solution = np.zeros(30) ) Weierstrass30d = BenchmarkFunction( name = "Weierstrass-30d", fun = weierstrass, bounds = np.array([(-0.5, 0.5)]*30), optimal_solution = np.zeros(30) ) Griewank30d = BenchmarkFunction( name = "Griewank-30d", fun = griewank, bounds = np.array([(-600, 600)]*30), optimal_solution = np.zeros(30) ) Schwefel30d = BenchmarkFunction( name = "Schwefel-30d", fun = schwefel, bounds = np.array([(-500, 500)]*30), optimal_solution = np.full(30,420.9687) ) Sumsquares30d = BenchmarkFunction( name = "Sumsquares-30d", fun = sumsquares, bounds = np.array([(-10, 10)]*30), optimal_solution = np.zeros(30) )