evobench-lib 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
evobench/__init__.py ADDED
@@ -0,0 +1,80 @@
1
+ """
2
+ evobench: Comprehensive Benchmarking Suite for Evolutionary Algorithms
3
+
4
+ A Python framework for testing, comparing, and analyzing evolutionary algorithms
5
+ and metaheuristics. Includes three baseline implementations (PSO, EDA, ABC),
6
+ benchmark functions (Sphere, Rosenbrock, Ackley, Schwefel, Trid), and built-in
7
+ statistical analysis tools.
8
+
9
+ Basic Usage:
10
+ >>> from evobench import PSO, sphere
11
+ >>> bounds = [(-5, 5)] * 10
12
+ >>> optimizer = PSO(sphere, bounds, max_iterations=100)
13
+ >>> best_solution, best_fitness = optimizer.run()
14
+ >>> print(f"Best fitness: {best_fitness:.6e}")
15
+
16
+ For advanced usage, reproducibility, and statistical comparison, see:
17
+ - docs/getting-started/SETUP_CONFIG.md
18
+ - docs/guide/PERFORMANCE_AND_REPRODUCIBILITY.md
19
+ - docs/reference/index.md
20
+ """
21
+
22
+ __version__ = "1.0.0"
23
+ __author__ = "Enrique Gómez Linares, Victoria Galván Delgadillo"
24
+ __license__ = "GPL-3"
25
+
26
+
27
+ # ALGORITHM EXPORTS
28
+
29
+ from evobench.algorithms import PSO, EDA, ABC
30
+
31
+ from evobench.base import EvolutionaryAlgorithm
32
+
33
+
34
+ # BENCHMARK FUNCTION EXPORTS
35
+
36
+ from evobench.benchmarks import (
37
+ sphere,
38
+ rosenbrock,
39
+ ackley,
40
+ schwefel,
41
+ trid,
42
+ get_benchmark,
43
+ BENCHMARK_REGISTRY,
44
+ )
45
+
46
+
47
+ # STATISTICAL ANALYSIS EXPORTS
48
+
49
+ from .stats import analyze, stat_report
50
+
51
+
52
+ # UTILITIES EXPORTS
53
+
54
+ from .tools import run as run_automated_experiment
55
+
56
+
57
+ # PUBLIC API DEFINITION
58
+
59
+ __all__ = [
60
+ # Core algorithms
61
+ "PSO",
62
+ "EDA",
63
+ "ABC",
64
+ "EvolutionaryAlgorithm",
65
+ # Benchmark functions (individual)
66
+ "sphere",
67
+ "rosenbrock",
68
+ "ackley",
69
+ "schwefel",
70
+ "trid",
71
+ # Benchmark utilities
72
+ "get_benchmark",
73
+ "BENCHMARK_REGISTRY",
74
+ # Statistical analysis
75
+ "analyze",
76
+ "stat_report",
77
+ # Experiment tools
78
+ "run_automated_experiment",
79
+
80
+ ]
@@ -0,0 +1,19 @@
1
+ """
2
+ Optimization Algorithms Module.
3
+
4
+ This module provides implementations of various evolutionary and swarm-based
5
+ metaheuristics. The algorithms are exported with their standard acronyms
6
+ for ease of use.
7
+ """
8
+
9
+ # Importamos las clases con sus nombres completos y les asignamos el alias oficial
10
+ from .eda import EstimationOfDistributionAlgorithm as EDA
11
+ from .pso import ParticleSwarmOptimization as PSO
12
+ from .bee import ArtificialBeeColony as ABC
13
+
14
+ # Definimos exactamente qué se expone cuando alguien importa este módulo
15
+ __all__ = [
16
+ "EDA",
17
+ "PSO",
18
+ "ABC"
19
+ ]
@@ -0,0 +1,155 @@
1
+ import numpy as np
2
+ from typing import Callable, Tuple
3
+ from evobench.base import EvolutionaryAlgorithm
4
+ import evobench.tools.operators as ops
5
+
6
+ class ArtificialBeeColony(EvolutionaryAlgorithm):
7
+ """
8
+ Artificial Bee Colony (ABC) metaheuristic for continuous optimization.
9
+
10
+ This algorithm mimics the intelligent foraging behavior of a honey bee swarm.
11
+ The population is divided into three groups: Employed Bees, Onlooker Bees,
12
+ and Scout Bees. It utilizes a trial-counter mechanism to manage food source
13
+ exhaustion, ensuring the swarm escapes local optima through a structured
14
+ stagnation-replacement strategy.
15
+ """
16
+
17
+ def __init__(
18
+ self,
19
+ objective_function: Callable[[np.ndarray], float],
20
+ bounds: np.ndarray,
21
+ population_size: int = 50,
22
+ max_iterations: int = 100,
23
+ limit: int = 20
24
+ ) -> None:
25
+ """
26
+ Initializes the ABC hyperparameters and structural attributes.
27
+
28
+ Args:
29
+ objective_function (Callable): The mathematical function to minimize.
30
+ bounds (np.ndarray): Spatial boundaries [min, max] for each dimension.
31
+ population_size (int): Total number of bees. Half will be employed.
32
+ max_iterations (int): Maximum number of search cycles.
33
+ limit (int): Trials allowed before a food source is abandoned.
34
+ """
35
+ super().__init__(objective_function, bounds, population_size, max_iterations)
36
+
37
+ # In ABC, the number of food sources is equal to half of the population
38
+ self.food_sources_count = population_size // 2
39
+
40
+ # Maximum trials allowed for a food source to improve before being discarded
41
+ self.limit = limit
42
+
43
+ # Array to track the number of failed attempts at improving each food source
44
+ self.trial_counters = np.zeros(self.food_sources_count)
45
+
46
+ def _apply_boundary_constraints(self, individual: np.ndarray) -> np.ndarray:
47
+ """
48
+ Clips the coordinates of a candidate solution to the defined search space.
49
+
50
+ Args:
51
+ individual (np.ndarray): The continuous vector to be constrained.
52
+
53
+ Returns:
54
+ np.ndarray: The bounded candidate solution.
55
+ """
56
+ lower_bounds = self.bounds[:, 0]
57
+ upper_bounds = self.bounds[:, 1]
58
+
59
+ return np.clip(individual, lower_bounds, upper_bounds)
60
+
61
+ def run(self) -> Tuple[np.ndarray, float]:
62
+ """
63
+ Executes the three-phase artificial foraging cycle.
64
+
65
+ Returns:
66
+ Tuple[np.ndarray, float]: The global best solution and its fitness.
67
+ """
68
+ # Initial food sources are sampled uniformly from the search domain
69
+ # In this algorithm, population size reflects the number of active food sources
70
+ food_sources = self._initialize_population()[:self.food_sources_count]
71
+
72
+ # Initial evaluation of the food source fitness
73
+ fitness_values = np.apply_along_axis(self.objective_function, 1, food_sources)
74
+
75
+ for iteration in range(self.max_iterations):
76
+
77
+ # --- EMPLOYED BEES PHASE ---
78
+ # Each employed bee explores a neighbor of its assigned food source
79
+ for i in range(self.food_sources_count):
80
+
81
+ # Select a random neighbor index different from current source
82
+ neighbor_idx = np.random.choice([idx for idx in range(self.food_sources_count) if idx != i])
83
+
84
+ # Select a random dimension to perturb
85
+ dimension_idx = np.random.randint(self.dimension)
86
+
87
+ # Calculate the neighbor solution using the ABC search formula
88
+ # Phi is a random scaling factor in the range [-1, 1]
89
+ phi = np.random.uniform(-1, 1)
90
+ candidate_solution = np.copy(food_sources[i])
91
+ candidate_solution[dimension_idx] += phi * (food_sources[i][dimension_idx] - food_sources[neighbor_idx][dimension_idx])
92
+
93
+ # Enforce space boundaries and evaluate
94
+ candidate_solution = self._apply_boundary_constraints(candidate_solution)
95
+ candidate_fitness = self.objective_function(candidate_solution)
96
+
97
+ # Greedy selection: update source if the new candidate is better
98
+ if candidate_fitness < fitness_values[i]:
99
+ food_sources[i] = candidate_solution
100
+ fitness_values[i] = candidate_fitness
101
+ self.trial_counters[i] = 0
102
+ else:
103
+ self.trial_counters[i] += 1
104
+
105
+ # --- ONLOOKER BEES PHASE ---
106
+ # Bees in the hive choose food sources to exploit based on probability (Roulette)
107
+ for _ in range(self.food_sources_count):
108
+
109
+ # Use the roulette selection from operators.py to favor better sources
110
+ # We pass the full matrix and fitness values to pick a winning source index
111
+ # Note: We simulate the selection of a source for additional local search
112
+ selected_idx = ops.roulette_wheel_selection_index(food_sources, fitness_values)
113
+
114
+ # Repeat the local search logic for the chosen source
115
+ neighbor_idx = np.random.choice([idx for idx in range(self.food_sources_count) if idx != selected_idx])
116
+ dimension_idx = np.random.randint(self.dimension)
117
+ phi = np.random.uniform(-1, 1)
118
+
119
+ candidate_solution = np.copy(food_sources[selected_idx])
120
+ candidate_solution[dimension_idx] += phi * (food_sources[selected_idx][dimension_idx] - food_sources[neighbor_idx][dimension_idx])
121
+
122
+ candidate_solution = self._apply_boundary_constraints(candidate_solution)
123
+ candidate_fitness = self.objective_function(candidate_solution)
124
+
125
+ # Greedy selection for the onlooker bee's target
126
+ if candidate_fitness < fitness_values[selected_idx]:
127
+ food_sources[selected_idx] = candidate_solution
128
+ fitness_values[selected_idx] = candidate_fitness
129
+ self.trial_counters[selected_idx] = 0
130
+ else:
131
+ self.trial_counters[selected_idx] += 1
132
+
133
+ # --- SCOUT BEES PHASE ---
134
+ # Identify food sources that have exceeded the trial limit without improvement
135
+ for i in range(self.food_sources_count):
136
+ if self.trial_counters[i] > self.limit:
137
+
138
+ # Abandon the exhausted source and re-initialize it randomly
139
+ # This step prevents premature convergence to local optima
140
+ lower_bounds = self.bounds[:, 0]
141
+ upper_bounds = self.bounds[:, 1]
142
+ food_sources[i] = np.random.uniform(lower_bounds, upper_bounds, self.dimension)
143
+ fitness_values[i] = self.objective_function(food_sources[i])
144
+ self.trial_counters[i] = 0
145
+
146
+ # Update the global optimum record
147
+ best_idx = np.argmin(fitness_values)
148
+ if fitness_values[best_idx] < self.best_fitness:
149
+ self.best_fitness = fitness_values[best_idx]
150
+ self.best_individual = np.copy(food_sources[best_idx])
151
+
152
+ # Store best fitness for convergence history tracking
153
+ self.fitness_history.append(self.best_fitness)
154
+
155
+ return self.best_individual, self.best_fitness
@@ -0,0 +1,122 @@
1
+ import numpy as np
2
+ from typing import Callable, Tuple
3
+ from evobench.base import EvolutionaryAlgorithm
4
+ import evobench.tools.operators as ops
5
+
6
+ class EstimationOfDistributionAlgorithm(EvolutionaryAlgorithm):
7
+ """
8
+ Continuous Estimation of Distribution Algorithm (EDA) with static typing.
9
+
10
+ This metaheuristic models the search space using a probabilistic approach.
11
+ Instead of applying classical genetic operators like crossover or mutation,
12
+ it selects a subset of the best-performing individuals to estimate the
13
+ parameters of a Gaussian distribution (mean and standard deviation).
14
+ The subsequent generation is then entirely sampled from this updated
15
+ probability model, effectively guiding the search towards promising regions.
16
+ """
17
+
18
+ def __init__(
19
+ self,
20
+ objective_function: Callable[[np.ndarray], float],
21
+ bounds: np.ndarray,
22
+ population_size: int = 50,
23
+ max_iterations: int = 100,
24
+ selection_ratio: float = 0.5
25
+ ) -> None:
26
+ """
27
+ Initializes the EDA-specific hyperparameters alongside the standard
28
+ evolutionary algorithm attributes.
29
+
30
+ Args:
31
+ objective_function (Callable[[np.ndarray], float]): The mathematical
32
+ function to minimize.
33
+ bounds (np.ndarray): The spatial boundaries for the search domain.
34
+ population_size (int): Total number of candidate solutions per generation.
35
+ max_iterations (int): The termination criterion.
36
+ selection_ratio (float): The fraction of the population selected to
37
+ estimate the probability distribution.
38
+ """
39
+ super().__init__(objective_function, bounds, population_size, max_iterations)
40
+
41
+ # Determine the exact number of individuals that will form the elite pool
42
+ self.selection_size = int(self.population_size * selection_ratio)
43
+
44
+ def _apply_boundary_constraints(self, population: np.ndarray) -> np.ndarray:
45
+ """
46
+ Enforces spatial boundaries on a newly sampled population.
47
+
48
+ Since the Gaussian distribution spans infinitely, sampled individuals
49
+ might fall outside the mathematical domain of the objective function.
50
+ This method clips the values strictly within the permitted limits.
51
+
52
+ Args:
53
+ population (np.ndarray): The unbounded matrix of candidate solutions.
54
+
55
+ Returns:
56
+ np.ndarray: The spatially bounded population matrix.
57
+ """
58
+ lower_bounds = self.bounds[:, 0]
59
+ upper_bounds = self.bounds[:, 1]
60
+
61
+ # Apply strict clipping across all dimensions simultaneously
62
+ bounded_population: np.ndarray = np.clip(population, lower_bounds, upper_bounds)
63
+ return bounded_population
64
+
65
+ def run(self) -> Tuple[np.ndarray, float]:
66
+ """
67
+ Executes the main probabilistic evolutionary loop.
68
+
69
+ The process iteratively evaluates the population, selects the elite
70
+ subset using tournament selection, calculates the mean and standard
71
+ deviation vectors, and samples the new candidate solutions.
72
+
73
+ Returns:
74
+ Tuple[np.ndarray, float]: The global best candidate vector and
75
+ its corresponding fitness.
76
+ """
77
+ # Generate the initial uniform random population
78
+ population = self._initialize_population()
79
+
80
+ for _ in range(self.max_iterations):
81
+ # Evaluate the objective function for every individual in the matrix
82
+ fitness_values = np.apply_along_axis(self.objective_function, 1, population)
83
+
84
+ # Track the best individual found in the current generation
85
+ current_best_index = np.argmin(fitness_values)
86
+ current_best_fitness = float(fitness_values[current_best_index])
87
+
88
+ if current_best_fitness < self.best_fitness:
89
+ self.best_fitness = current_best_fitness
90
+ self.best_individual = np.copy(population[current_best_index])
91
+
92
+ # Record the convergence history for later statistical analysis
93
+ self.fitness_history.append(self.best_fitness)
94
+
95
+ # Construct the elite pool using the previously defined selection operator
96
+ # The tournament selection provides selection pressure while maintaining diversity
97
+ elite_pool = np.empty((self.selection_size, self.dimension))
98
+ for i in range(self.selection_size):
99
+ elite_pool[i] = ops.tournament_selection(population, fitness_values, tournament_size=3)
100
+
101
+ # Estimate the probability distribution parameters from the elite pool
102
+ # The mean acts as the center of mass for the promising region
103
+ mean_vector = np.mean(elite_pool, axis=0)
104
+
105
+ # The standard deviation acts as the search radius or exploration threshold
106
+ # A tiny constant is added to prevent mathematical errors if variance collapses to zero
107
+ standard_deviation_vector = np.std(elite_pool, axis=0) + 1e-8
108
+
109
+ # Sample the completely new population from the estimated Gaussian model
110
+ # Elitism: Preserve the global best individual to guarantee monotonic improvement
111
+ population = np.random.normal(
112
+ loc=mean_vector,
113
+ scale=standard_deviation_vector,
114
+ size=(self.population_size, self.dimension)
115
+ )
116
+
117
+ population[0] = self.best_individual
118
+
119
+ # Ensure the newly sampled coordinates do not violate the problem domain
120
+ population = self._apply_boundary_constraints(population)
121
+
122
+ return self.best_individual, self.best_fitness
@@ -0,0 +1,115 @@
1
+ import numpy as np
2
+ from typing import Callable, Tuple
3
+ from evobench.base import EvolutionaryAlgorithm
4
+
5
+ class ParticleSwarmOptimization(EvolutionaryAlgorithm):
6
+ """
7
+ Standard Particle Swarm Optimization (PSO) for continuous spaces.
8
+
9
+ This algorithm simulates a swarm of particles moving through a multi-dimensional
10
+ search space. Each particle maintains a velocity and a memory of its personal
11
+ best position. The movement is guided by an inertia weight, a cognitive
12
+ component (personal experience), and a social component (global best),
13
+ effectively balancing exploration and exploitation.
14
+ """
15
+
16
+ def __init__(
17
+ self,
18
+ objective_function: Callable[[np.ndarray], float],
19
+ bounds: np.ndarray,
20
+ population_size: int = 50,
21
+ max_iterations: int = 100,
22
+ inertia_weight: float = 0.7,
23
+ cognitive_constant: float = 1.5,
24
+ social_constant: float = 1.5
25
+ ) -> None:
26
+ """
27
+ Initializes the PSO hyperparameters and the base evolutionary attributes.
28
+
29
+ Args:
30
+ objective_function (Callable): The mathematical function to minimize.
31
+ bounds (np.ndarray): Spatial boundaries [min, max] for each dimension.
32
+ population_size (int): Number of particles in the swarm.
33
+ max_iterations (int): Maximum number of iterations for the loop.
34
+ inertia_weight (float): Factor that controls the impact of previous velocity.
35
+ cognitive_constant (float): Acceleration coefficient for personal best.
36
+ social_constant (float): Acceleration coefficient for global best.
37
+ """
38
+ super().__init__(objective_function, bounds, population_size, max_iterations)
39
+
40
+ # Hyperparameters for the velocity update equation
41
+ self.w = inertia_weight
42
+ self.c1 = cognitive_constant
43
+ self.c2 = social_constant
44
+
45
+ def _apply_boundary_constraints(self, population: np.ndarray) -> np.ndarray:
46
+ """
47
+ Keeps particles within the search space and handles velocity reflections.
48
+
49
+ Args:
50
+ population (np.ndarray): The current positions of the swarm.
51
+
52
+ Returns:
53
+ np.ndarray: The bounded population matrix.
54
+ """
55
+ lower_bounds = self.bounds[:, 0]
56
+ upper_bounds = self.bounds[:, 1]
57
+
58
+ # Clip positions that exceed the established search domain
59
+ return np.clip(population, lower_bounds, upper_bounds)
60
+
61
+ def run(self) -> Tuple[np.ndarray, float]:
62
+ """
63
+ Executes the main PSO swarm intelligence loop.
64
+
65
+ Returns:
66
+ Tuple[np.ndarray, float]: The global best position and its fitness.
67
+ """
68
+ # Initializing swarm positions using the base uniform sampling method
69
+ current_positions = self._initialize_population()
70
+
71
+ # Velocities are initialized to zero for all particles and dimensions
72
+ velocities = np.zeros((self.population_size, self.dimension))
73
+
74
+ # Personal best positions (p_best) are initialized to the starting positions
75
+ personal_best_positions = np.copy(current_positions)
76
+
77
+ # Personal best fitness values initialized to infinity for minimization
78
+ personal_best_fitness = np.full(self.population_size, float('inf'))
79
+
80
+ for iteration in range(self.max_iterations):
81
+ # Evaluate objective function for every particle in the swarm
82
+ fitness_values = np.apply_along_axis(self.objective_function, 1, current_positions)
83
+
84
+ # Update personal and global bests based on current evaluations
85
+ for i in range(self.population_size):
86
+ if fitness_values[i] < personal_best_fitness[i]:
87
+ personal_best_fitness[i] = fitness_values[i]
88
+ personal_best_positions[i] = np.copy(current_positions[i])
89
+
90
+ # Update the global optimum if a better solution is found
91
+ if fitness_values[i] < self.best_fitness:
92
+ self.best_fitness = fitness_values[i]
93
+ self.best_individual = np.copy(current_positions[i])
94
+
95
+ # Store the best fitness of the iteration for convergence tracking
96
+ self.fitness_history.append(self.best_fitness)
97
+
98
+ # Generate random coefficients for the stochastic components of the velocity
99
+ r1 = np.random.rand(self.population_size, self.dimension)
100
+ r2 = np.random.rand(self.population_size, self.dimension)
101
+
102
+ # Calculate the cognitive and social attraction vectors
103
+ cognitive_acceleration = self.c1 * r1 * (personal_best_positions - current_positions)
104
+ social_acceleration = self.c2 * r2 * (self.best_individual - current_positions)
105
+
106
+ # Update velocity applying inertia and acceleration components
107
+ velocities = (self.w * velocities) + cognitive_acceleration + social_acceleration
108
+
109
+ # Update particle positions based on the new velocity vectors
110
+ current_positions += velocities
111
+
112
+ # Enforce boundary constraints to keep the swarm within the domain
113
+ current_positions = self._apply_boundary_constraints(current_positions)
114
+
115
+ return self.best_individual, self.best_fitness
evobench/base.py ADDED
@@ -0,0 +1,87 @@
1
+ from typing import Callable, List, Tuple
2
+ import numpy as np
3
+ from abc import ABC, abstractmethod
4
+
5
+ class EvolutionaryAlgorithm(ABC):
6
+ """
7
+ Abstract base class defining the standard architecture for population-based
8
+ evolutionary algorithms and metaheuristics.
9
+
10
+ This class enforces a uniform interface across different optimization strategies,
11
+ ensuring that all derived algorithms share common initialization protocols,
12
+ state tracking, and execution signatures. This standardization is critical
13
+ for executing rigorous statistical benchmarking over continuous search spaces.
14
+ """
15
+
16
+ def __init__(self, objective_function: Callable, bounds: List, population_size: int = 50, max_iterations: int = 100) -> None:
17
+ """
18
+ Initializes the fundamental hyperparameters and state variables required
19
+ for the optimization process.
20
+
21
+ Args:
22
+ objective_function (callable): The mathematical function to be minimized.
23
+ bounds (list of tuples or numpy.ndarray): The continuous search domain
24
+ defined as [lower_bound, upper_bound]
25
+ for each dimension.
26
+ population_size (int): The number of candidate solutions evaluated per generation.
27
+ max_iterations (int): The termination criterion defining the maximum
28
+ number of evolutionary cycles.
29
+ """
30
+ self.objective_function = objective_function
31
+
32
+ # Array conversion ensures vectorized operations can be applied efficiently
33
+ # during mutation or boundary handling
34
+ self.bounds = np.array(bounds)
35
+ self.population_size = population_size
36
+ self.max_iterations = max_iterations
37
+
38
+ # The dimensionality of the problem is implicitly derived from the bounds matrix
39
+ self.dimension = len(self.bounds)
40
+
41
+ # State trackers required to store the global optimum throughout the execution
42
+ self.best_individual = None
43
+ self.best_fitness = float('inf')
44
+
45
+ # Convergence tracker used to store the best fitness value of each generation,
46
+ # which is later utilized to generate convergence plots and statistical analysis
47
+ self.fitness_history = []
48
+
49
+ def _initialize_population(self) -> np.ndarray:
50
+ """
51
+ Generates the initial set of candidate solutions.
52
+
53
+ The initial population is generated by sampling from a uniform probability
54
+ distribution bounded by the defined search space. This ensures an unbiased
55
+ exploration of the domain during the first generation.
56
+
57
+ Returns:
58
+ numpy.ndarray: A matrix of shape (population_size, dimension) containing
59
+ the initial candidate vectors.
60
+ """
61
+ # Vectorized extraction of the lower and upper limits for the search space
62
+ lower_bounds = self.bounds[:, 0]
63
+ upper_bounds = self.bounds[:, 1]
64
+
65
+ # Matrix generation using uniform sampling across all dimensions simultaneously
66
+ initial_population = np.random.uniform(
67
+ low=lower_bounds,
68
+ high=upper_bounds,
69
+ size=(self.population_size, self.dimension)
70
+ )
71
+
72
+ return initial_population
73
+
74
+ @abstractmethod
75
+ def run(self) -> Tuple[np.ndarray, float]:
76
+ """
77
+ Executes the core evolutionary loop.
78
+
79
+ This method acts as the main algorithmic contract. Every subclass must implement
80
+ its own stochastic search mechanisms, position updates, or probabilistic models
81
+ within this function.
82
+
83
+ Returns:
84
+ tuple: A pair containing the best candidate vector (numpy.ndarray) and
85
+ its corresponding fitness value (float).
86
+ """
87
+ pass
@@ -0,0 +1,56 @@
1
+ """
2
+ Evolutionary Benchmarking Registry Module
3
+
4
+ This module acts as a central hub for all optimization test functions.
5
+ It utilizes a registry pattern to map string identifiers directly to
6
+ their corresponding mathematical implementations.
7
+ """
8
+
9
+ # We use relative imports (with the dot) to tell Python:
10
+ # "Look inside the current folder"
11
+ from .unimodal import (
12
+ rosenbrock_function as rosenbrock,
13
+ sphere_function as sphere,
14
+ schwefel_1_2_function as schwefel,
15
+ trid_function as trid
16
+ )
17
+ from .multimodal import ackley_function as ackley
18
+
19
+ # Central dictionary mapping string names to function references
20
+ # Note: Using lowercase keys is a good practice for robustness
21
+ BENCHMARK_REGISTRY = {
22
+ "sphere": sphere,
23
+ "rosenbrock": rosenbrock,
24
+ "ackley": ackley,
25
+ "schwefel 1.2": schwefel,
26
+ "trid": trid
27
+ }
28
+
29
+ def get_benchmark(name: str):
30
+ """
31
+ Retrieves the callable mathematical function based on its registry name.
32
+
33
+ Args:
34
+ name: The string identifier of the benchmark function.
35
+
36
+ Returns:
37
+ callable: The requested benchmarking function.
38
+ """
39
+ # Normalize the input name to lowercase to match the registry keys
40
+ search_name = name.lower()
41
+
42
+ if search_name not in BENCHMARK_REGISTRY:
43
+ raise ValueError(f"Benchmark '{name}' is not implemented in the registry.")
44
+
45
+ return BENCHMARK_REGISTRY[search_name]
46
+
47
+ # List of publicly available objects when using 'from evobench.benchmarks import *'
48
+ # Fixed the typos and ensured names match the aliases defined above
49
+ __all__ = [
50
+ "rosenbrock",
51
+ "sphere",
52
+ "schwefel",
53
+ "trid",
54
+ "ackley",
55
+ "get_benchmark"
56
+ ]
@@ -0,0 +1,38 @@
1
+ import numpy as np
2
+
3
+ def ackley_function(x: np.ndarray) -> float:
4
+ """
5
+ Evaluates the Ackley function for a given continuous vector.
6
+
7
+ The Ackley function is characterized by a nearly flat outer region and
8
+ a large hole at the center. It poses a risk for optimization algorithms
9
+ to be trapped in one of its many local minima.
10
+
11
+ Search space bounds: [-10, 10]
12
+ Global optimum: F(0) = 0
13
+
14
+ Args:
15
+ x: The candidate solution vector representing coordinates.
16
+
17
+ Returns:
18
+ The evaluated fitness value (objective to minimize).
19
+ """
20
+ # Extract the dimensionality of the search space directly from the vector length
21
+ dimension = len(x)
22
+
23
+ # Calculate the sum of squared elements for the first exponential term
24
+ sum_of_squares = float(np.sum(x**2))
25
+
26
+ # Calculate the sum of cosine evaluations for the second exponential term
27
+ sum_of_cosines = float(np.sum(np.cos(2.0 * np.pi * x)))
28
+
29
+ # Compute the first component of the Ackley mathematical equation
30
+ term_one = -20.0 * np.exp(-0.2 * np.sqrt(sum_of_squares / dimension))
31
+
32
+ # Compute the second component involving the trigonometric summation
33
+ term_two = -np.exp(sum_of_cosines / dimension)
34
+
35
+ # Aggregate all terms alongside the mathematical constants
36
+ final_fitness = term_one + term_two + 20.0 + np.exp(1.0)
37
+
38
+ return final_fitness