import numpy as np
from typing import Union, Sequence
# ---------- SELECTION OPERATORS ------------
[docs]def sel_best(population: np.ndarray, fitness: np.ndarray, k: int) -> tuple[np.ndarray, np.ndarray]:
"""
Select the first `k` best individuals (with the smallest objective values) in the population.
Args:
population: A population of individuals as array of real parameters with (`pop_size`, `n_params`)
shape.
fitness: A set of fitness values associated to the population as array of real values with (`pop_size`,)
shape.
k: Number of individuals to be selected.
Returns:
Subsets of `k` best individuals and fitness values.
"""
sorted_idx = np.argsort(fitness)
return population[sorted_idx[:k]], fitness[sorted_idx[:k]]
[docs]def sel_permutation(population: np.ndarray, fitness: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
"""
Select the mating pool by permutation of the population indices.
Args:
population: A population of individuals as array of real parameters with (`pop_size`, `n_params`)
shape.
fitness: A set of fitness values associated to the population as array of real values with (`pop_size`,)
shape.
Returns:
Mating pool with individuals and fitness values with new indices.
"""
permutation_idx = np.random.permutation(len(population))
return population[permutation_idx], fitness[permutation_idx]
[docs]def sel_tournament(population: np.ndarray, fitness: np.ndarray, k: int, tournsize: int = 3) -> tuple[np.ndarray, np.ndarray]:
"""
Select the best individual among `tournsize` randomly chosen individuals, `k` times.
Args:
population: A population of individuals as array of real parameters with (`pop_size`, `n_params`)
shape.
fitness: A set of fitness values associated to the population as array of real values with (`pop_size`,)
shape.
k: Number of individuals to be selected.
tournsize: Number of random individuals participating in each tournament.
Returns:
Subsets of `k` individuals and fitness values.
"""
chosen = []
for _ in range(k):
aspirants = np.random.choice(len(population), size=tournsize)
fit_aspirants = fitness[aspirants]
winner = aspirants[np.argmin(fit_aspirants)]
chosen.append(winner)
return population[chosen], fitness[chosen]
[docs]def sel_random(population: np.ndarray, fitness: np.ndarray, k: int, replace: bool = True) -> tuple[np.ndarray, np.ndarray]:
"""
Select `k` individuals randomly.
Args:
population: A population of individuals as array of real parameters with (`pop_size`, `n_params`)
shape.
fitness: A set of fitness values associated to the population as array of real values with (`pop_size`,)
shape.
k: Number of individuals to be selected.
replace: Whether the sample is with or without replacement. Default is True, meaning that an element can be
selected multiple times.
Returns:
Subsets of `k` individuals and fitness values randomly chosen.
"""
random_idx = np.random.choice(len(population), size=k, replace=replace)
return population[random_idx], fitness[random_idx]
# ---------- CROSSOVER OPERATORS ------------
[docs]def cx_blx_alpha(par1: np.ndarray, par2: np.ndarray, alpha: float = 0.5) -> tuple[np.ndarray, np.ndarray]:
"""
BLX-alpha crossover.
Args:
par1: The first parent as array of real parameters with (`n_params`,) shape.
par2: The second parent as array of real parameters with (`n_params`,) shape.
alpha: Positive real hyperparameter.
Returns:
The two resulting children.
"""
d = np.abs(par1 - par2)
stacked = np.stack((par1, par2), axis=1)
lower_bounds = np.min(stacked, axis=1) - alpha * d
upper_bounds = np.max(stacked, axis=1) + alpha * d
child1 = np.random.uniform(lower_bounds, upper_bounds, par1.shape)
child2 = np.random.uniform(lower_bounds, upper_bounds, par2.shape)
return child1, child2
[docs]def cx_one_point(par1: np.ndarray, par2: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
"""
One-point crossover.
Args:
par1: The first parent as array of real parameters with (`n_params`,) shape.
par2: The second parent as array of real parameters with (`n_params`,) shape.
Returns:
The two resulting children.
"""
point = np.random.randint(1, par1.size - 1)
child1 = np.concatenate((par1[:point], par2[point:]))
child2 = np.concatenate((par2[:point], par1[point:]))
return child1, child2
[docs]def cx_two_point(par1: np.ndarray, par2: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
"""
Two-point crossover.
Args:
par1: The first parent as array of real parameters with (`n_params`,) shape.
par2: The second parent as array of real parameters with (`n_params`,) shape.
Returns:
The two resulting children.
"""
point1, point2 = sorted(np.random.choice(par1.size, 2, replace=False))
child1 = np.concatenate((par1[:point1], par2[point1:point2], par1[point2:]))
child2 = np.concatenate((par2[:point1], par1[point1:point2], par2[point2:]))
return child1, child2
# ---------- MUTATION OPERATORS ------------
[docs]def mut_gaussian(individual: np.ndarray, mu: Union[float, Sequence] = 0, sigma: Union[float, Sequence] = 1,
mut_indpb: float = 0.1) -> np.ndarray:
"""
Gaussian mutation.
Args:
individual: Individual to be mutated as array of real parameters with (`n_params`,) shape.
mu: Mean or sequence of means for the gaussian addition mutation.
sigma: Standard deviation or sequence of standard deviations for the gaussian addition mutation.
mut_indpb: Independent probability for each parameter to be mutated.
Returns:
Resulting mutated individual.
"""
mask = np.random.choice([True, False], size=individual.size, p=[mut_indpb, 1 - mut_indpb])
mutated_individual = individual + mask * np.random.normal(mu, sigma, size=individual.shape)
return mutated_individual
[docs]def mut_flip_bit(individual: np.ndarray, mut_indpb: float = 0.1) -> np.ndarray:
"""
Flip-bit mutation.
Args:
individual: Individual to be mutated as array of real parameters with (`n_params`,) shape.
mut_indpb: Independent probability for each parameter to be mutated.
Returns:
Resulting mutated individual.
"""
mutated_individual = individual.copy()
mask = np.random.choice([True, False], size=individual.size, p=[mut_indpb, 1 - mut_indpb])
mutated_individual[mask] = 1 - mutated_individual[mask]
return mutated_individual
SELECTION_ARGS = {
sel_tournament: ['tournsize']
}
CROSSOVER_ARGS = {
cx_blx_alpha: ['alpha'],
cx_uniform: ['cx_indpb']
}
MUTATION_ARGS = {
mut_gaussian: ['mu', 'sigma', 'mut_indpb'],
mut_flip_bit: ['mut_indpb']
}