Source code for evolutionary_optimization.genotype.implemented_genotypes.integer_list_genotype

import random
from random import randint
from typing import Tuple, Optional, List

import numpy as np

from evolutionary_optimization.genotype.genotype_model.abstract_genotype import AbstractGenotype
from evolutionary_optimization.genotype.genotype_model.genotype_utils import single_point_crossover


[docs]class IntegerListGenotype(AbstractGenotype):
[docs] def __init__( self, genotype: Optional[List[int]] = None, mutation_probability: float = 0.1, ratio_of_population_for_crossover: float = 0.5, number_of_genes: int = 1, value_range: Tuple[int, int] = (-10000, 10000), ): """Initialise instance of AbstractGenotype. Args: genotype: genotype used for mutation, crossover and to calculate phenotype_value. mutation_probability: probability of a gene mutating. ratio_of_population_for_crossover: ratio of population used for crossover when updating population. number_of_genes: number of genes in the genotype. value_range: minimum and maximum values of a gene. """ self._genotype = genotype self.mutation_probability = mutation_probability self.ratio_of_population_for_crossover = ratio_of_population_for_crossover self.number_of_genes = number_of_genes self.value_range = value_range
@property def genotype(self): """Genotype value used for evaluation of phenotype.""" return self._genotype @genotype.setter def genotype(self, value): """Genotype attribute setter.""" self._genotype = value
[docs] @classmethod def build_random_genotype( cls, number_of_genes: int = 1, value_range: Tuple[int, int] = (-10000, 10000), mutation_probability: Optional[float] = 0.5, ratio_of_population_for_crossover: Optional[float] = 0.5, ) -> "IntegerListGenotype": """Builds random genotype attribute based on requirements. Args: number_of_genes: number of genes in the genotype. value_range: minimum and maximum values of a gene. mutation_probability: probability of a gene mutating. ratio_of_population_for_crossover: ratio of population used for crossover when updating population. Returns: Genotype object with updated genotype attribute. Todo: * (Marta): set infinity as value range defaults """ genotype = [] for i in range(number_of_genes): new_gene = randint(value_range[0], value_range[1]) genotype.append(new_gene) return cls( genotype=genotype, mutation_probability=mutation_probability, ratio_of_population_for_crossover=ratio_of_population_for_crossover, number_of_genes=number_of_genes, value_range=value_range, )
[docs] @classmethod def from_genotype(cls, base_genotype: "IntegerListGenotype", new_genotype: List[int]) -> "IntegerListGenotype": """Create a new genotype using the parameters of an existing genotype.""" return cls( genotype=new_genotype, value_range=base_genotype.value_range, mutation_probability=base_genotype.mutation_probability, ratio_of_population_for_crossover=base_genotype.ratio_of_population_for_crossover, )
[docs] def mutate(self): """In place modification of the genotype by randomly changing genes based on mutation probability.""" new_genotype = [] for gene in self.genotype: mutation = np.random.choice([True, False], p=[self.mutation_probability, 1 - self.mutation_probability]) if mutation: noise = random.uniform(-1, 1) if noise > 0: new_gene = gene + 1 else: new_gene = gene - 1 else: new_gene = gene new_genotype.append(new_gene) self.genotype = new_genotype
[docs] def crossover( self, parent_2_genotype: "IntegerListGenotype", ) -> Tuple["IntegerListGenotype", "IntegerListGenotype"]: """Performs single point crossover operation for 1 set of parents. A random integer is generated to split the genotype of the two individuals - this is the gene slice index. Then two child genotypes are generated with the complementary parts of the parent genotypes. If the parent's genotype length is 1, crossover is impossible so the parent instances are returned. Example: parent_1.genotype = [1, 2, 3, 4] parent_2.genotype = [A, B, C, D] gene_slice_index = 1 child_1.genotype = [1, B, C, D] child_2.genotype = [A, 2, 3, 4] Args: parent_2_genotype: Individual which will be used to create an offspring. Returns: Tuple of AbstractGenotype, representing two children genotypes that are a combination of the parents. """ if len(self.genotype) != len(parent_2_genotype.genotype): raise NameError("The Individuals have genotypes of different lengths - crossover is impossible") if self.number_of_genes == 1: return self, parent_2_genotype else: last_slice_index = self.number_of_genes - 1 gene_slice_index = randint(1, last_slice_index) child_1_genotype = single_point_crossover(self.genotype, parent_2_genotype.genotype, gene_slice_index) child_2_genotype = single_point_crossover(parent_2_genotype.genotype, self.genotype, gene_slice_index) child_1 = self.from_genotype(parent_2_genotype, child_1_genotype) child_2 = self.from_genotype(parent_2_genotype, child_2_genotype) return child_1, child_2