from math import fabs
from typing import Optional
from pygenalgo.genome.gene import Gene
from pygenalgo.utils.utilities import clamp
from pygenalgo.genome.chromosome import Chromosome
from pygenalgo.operators.crossover.crossover_operator import CrossoverOperator
[docs]
class BlendCrossover(CrossoverOperator):
"""
Description:
Blend-a crossover (BLX-a) creates two children chromosomes (offsprings) by
uniformly picking values that lie between two points that contain the two
parents but may extend equally on either side determined by a user specified
parameter 'a'.
NB: Used only for real coded genomes.
"""
def __init__(self, crossover_probability: float = 0.9, p_alpha: float = 0.5,
lower_val: Optional[float] = None, upper_val: Optional[float] = None) -> None:
"""
Construct a 'BlendCrossover' object with a given probability value.
:param crossover_probability: (float).
:param p_alpha: (float).
:param lower_val: (float) lower limit value for the gene.
:param upper_val: (float) upper limit value for the gene.
"""
# Call the super constructor with the provided
# probability value.
super().__init__(crossover_probability)
# Ensure p_alpha parameter is float.
p_alpha = clamp(float(p_alpha), 0.0, 1.0)
# Ensure that both lower and upper limits are provided.
if lower_val is None or upper_val is None:
raise ValueError(f"{self.__class__.__name__}: "
f"Lower or Upper limits are missing.")
# _end_if_
# Ensure lower_val parameter is float.
lower_val = float(lower_val)
# Ensure upper_val parameter is float.
upper_val = float(upper_val)
# Ensure the order is correct.
if upper_val <= lower_val:
raise ValueError(f"{self.__class__.__name__}: "
f"The limit values are incorrect.")
# _end_if_
# Assign variables to the _items placeholder.
self._items = [p_alpha, lower_val, upper_val]
# _end_def_
[docs]
def crossover(self, parent1: Chromosome, parent2: Chromosome) -> tuple[Chromosome, Chromosome]:
"""
Perform the crossover operation on the two input parent chromosomes.
:param parent1: (Chromosome).
:param parent2: (Chromosome).
:return: child1 and child2 (as Chromosomes).
"""
# If the crossover probability is higher than a uniformly
# random value and the parents aren't identical apply the
# changes.
if (parent1 != parent2) and self.is_operator_applicable():
# Extract the values from the placeholder variable.
p_alpha, xl, xu = self._items
# Get the length of the chromosome.
number_of_genes: int = len(parent1)
# Preallocate 1st genome.
genome_1: list = [None] * number_of_genes
# Preallocate 2nd genome.
genome_2: list = [None] * number_of_genes
# Generate uniform random numbers (floats) in the half-open
# interval [0.0, 1.0).
random_uniform = self.rng.random(size=(number_of_genes, 2))
# Set the genes accordingly.
for i, (r_val, gene_1, gene_2) in enumerate(zip(random_uniform,
parent1.genome,
parent2.genome)):
# Extract the gene values once.
g1, g2 = gene_1.value, gene_2.value
# Get the offset by scaling the distance
# between the two gene values with alpha.
offset_distance = p_alpha * fabs(g1 - g2)
# Get the min / max values.
if g1 < g2:
min_value, max_value = g1, g2
else:
min_value, max_value = g2, g1
# _end_if_
# Compute the lower and upper limits by
# removing / adding the offset distance.
min_value -= offset_distance
max_value += offset_distance
# Create two new gene values.
new_value_1, new_value_2 = min_value + (max_value - min_value) * r_val
# Ensure the new values are within limits.
new_value_1 = clamp(new_value_1, xl, xu)
new_value_2 = clamp(new_value_2, xl, xu)
# Update the genome of the new offsprings with new Genes.
genome_1[i] = Gene(datum=new_value_1, func=gene_1.func)
genome_2[i] = Gene(datum=new_value_2, func=gene_2.func)
# _end_for_
# Create two NEW offsprings.
child1 = Chromosome(genome_1)
child2 = Chromosome(genome_2)
# Increase the crossover counter.
self.inc_counter()
else:
# Each child points to a clone of a single parent.
child1 = parent1.clone()
child2 = parent2.clone()
# _end_if_
# Return the two offsprings.
return child1, child2
# _end_def_
# _end_class_