Source code for lightweight_genetic_algorithm.mutation
import numpy as np
from .population import Individual
[docs]
class Mutation:
"""
A class used to represent mutations in a genetic algorithm.
The Mutation class provides methods to mutate genes or individuals based on specified
mutation modes and probabilities.
Attributes:
mutation_modes (list of str): List of mutation modes for each gene.
mutation_probability (float): Probability of mutating each gene.
param_ranges (list of tuple): List of parameter ranges for each gene.
is_categorical (bool): Indicates if the genes are categorical.
"""
def __init__(self, mutation_modes, mutation_probability, param_ranges):
"""
Initializes the Mutation object with mutation modes, probability, and parameter ranges.
Args:
mutation_modes (list of str): A list of mutation modes for each gene.
mutation_probability (float): The probability of mutating each gene. If None, defaults to 1/len(param_ranges).
param_ranges (list of tuple): A list of parameter ranges (min, max) for each gene.
Raises:
ValueError: If mutation_probability is not between 0 and 1.
"""
if mutation_probability is not None and not (0 <= mutation_probability <= 1):
raise ValueError("mutation_probability must be between 0 and 1.")
self.mutation_modes = mutation_modes
self.mutation_probability = mutation_probability if mutation_probability else 1.0 / len(param_ranges)
self.param_ranges = param_ranges
self.is_categorical = len(param_ranges) != len(mutation_modes)
[docs]
def mutate_genes(self, genes, force_mutate=False):
"""
Mutates a list of genes based on the mutation probability and modes.
Args:
genes (list of Gene): A list of Gene objects to mutate.
force_mutate (bool, optional): If True, ensures at least one gene is mutated.
Returns:
list of Gene: The mutated list of Gene objects.
Raises:
ValueError: If a mutation mode is not compatible with a gene type.
"""
# Choose which genes to mutate
genes_to_mutate = [np.random.rand() < self.mutation_probability for _ in range(len(genes))]
# If no gene was chosen to mutate, force the mutation of one gene (unless force_mutate is False)
if not any(genes_to_mutate) and force_mutate:
genes_to_mutate[np.random.randint(len(genes))] = True
for i, gene in enumerate(genes):
if genes_to_mutate[i]:
if self.mutation_modes[i] not in gene.mutation_methods:
raise ValueError(
f"The mutation mode '{self.mutation_modes[i]}' is not compatible with the gene type."
)
# Call the appropriate mutation method
if self.is_categorical:
self.categorical(gene)
else:
mutation_method = getattr(self, self.mutation_modes[i])
mutation_method(gene, self.param_ranges[i])
return genes
[docs]
def mutate_individual(self, individual, force_mutate=False):
"""
Mutates an individual by mutating its genes.
Args:
individual (Individual): The Individual object to mutate.
force_mutate (bool, optional): If True, ensures at least one gene is mutated.
Returns:
Individual: A new Individual object with mutated genes.
Raises:
TypeError: If the input is not an instance of Individual.
"""
if not isinstance(individual, Individual):
raise TypeError("The mutate_individual method expects an instance of Individual.")
mutated_genes = self.mutate_genes(individual.get_genes(), force_mutate)
mutated_individual = Individual(
mutated_genes,
individual.get_fitness_function(),
individual.fitness_function_args
)
return mutated_individual
[docs]
def additive(self, gene, param_range):
"""
Applies an additive mutation to a gene.
The gene's value is adjusted by adding a random value drawn from a normal distribution.
Args:
gene (Gene): The gene to mutate.
param_range (tuple): The (min, max) range of the gene's parameter.
Returns:
Gene: The mutated gene.
"""
range_size = abs(param_range[1] - param_range[0])
std_dev = range_size / 10 # Standard deviation for mutation
mutation_value = np.random.normal(loc=0.0, scale=std_dev)
gene.set_value(gene.value + mutation_value)
[docs]
def multiplicative(self, gene, param_range=None):
"""
Applies a multiplicative mutation to a gene.
The gene's value is adjusted by multiplying it by a random factor drawn from a normal distribution centered at 1.
Args:
gene (Gene): The gene to mutate.
param_range (tuple, optional): Not used in this method.
Returns:
Gene: The mutated gene.
"""
mutation_factor = np.random.normal(loc=1, scale=0.5)
gene.set_value(gene.value * mutation_factor)
[docs]
def random(self, gene, param_range):
"""
Applies either an additive or multiplicative mutation to a gene at random.
Args:
gene (Gene): The gene to mutate.
param_range (tuple): The (min, max) range of the gene's parameter.
Returns:
Gene: The mutated gene.
"""
if np.random.rand() < 0.5:
self.multiplicative(gene, param_range)
else:
self.additive(gene, param_range)
[docs]
def categorical(self, gene):
"""
Mutates a categorical gene by randomly reinitializing its value.
Args:
gene (Gene): The categorical gene to mutate.
Returns:
Gene: The mutated gene.
"""
gene.set_value(gene.random_initialization())