from typing import Tuple
import numpy as np
from evolutionary_optimization.genotype.genotype_model.abstract_genotype import AbstractGenotype
from evolutionary_optimization.phenotype.phenotype_model.abstract_phenotype import AbstractPhenotype
[docs]class BoothPhenotype(AbstractPhenotype):
# TODO (Marta): Import of abstract phenotype doesn't work
[docs] def __init__(self, genotype: AbstractGenotype): # Union[BinaryListGenotype, IntegerListGenotype]
"""Initialise InvertedParabolaPhenotype object.
Args
genotype: an AbstractGenotype that defines the phenotype.
"""
self._genotype = genotype
self._phenotype_value = None
@property
def genotype(self):
"""AbstractGenotype that defines the phenotype."""
return self._genotype
@genotype.setter
def genotype(self, value):
"""Setter for genotype property."""
self._genotype = value
@property
def phenotype_value(self):
"""Stores value of the phenotype based on the genotype - calculated using evaluate_phenotype."""
return self._phenotype_value
@phenotype_value.setter
def phenotype_value(self, value: int):
"""Setter for phenotype_value property."""
self._phenotype_value = value
[docs] @classmethod
def from_phenotype(cls, base_phenotype: "BoothPhenotype") -> "BoothPhenotype":
"""Create new phenotype with the same attributes as the base phenotype, but a new random genotype.genotype."""
new_genotype = base_phenotype.genotype.build_random_genotype(
number_of_genes=base_phenotype.genotype.number_of_genes,
value_range=base_phenotype.genotype.value_range,
mutation_probability=base_phenotype.genotype.mutation_probability,
ratio_of_population_for_crossover=base_phenotype.genotype.ratio_of_population_for_crossover,
)
return cls(new_genotype)
[docs] def evaluate_phenotype(self):
"""In place method to calculate phenotype value using genotype.
This phenotype follows x^2 and as such is a single parameter optimisation problem.
For a binary genotype the integer value is returned,for an integer list genotype only the first value is used.
It updates the phenotype_value property in place once the calculation is done.
"""
x_1 = self.genotype.genotype[0]
x_2 = self.genotype.genotype[1]
phenotype = (x_1 + x_2 - 7) ** 2 + (2 * x_1 + x_2 - 5) ** 2
self.phenotype_value = phenotype
[docs] @staticmethod
def evaluate_phenotype_using_arrays(x_values: np.ndarray, y_values: np.ndarray) -> np.ndarray:
return (x_values + y_values - 7) ** 2 + (2 * x_values + y_values - 5) ** 2
[docs] def crossover(self, parent_2: "BoothPhenotype") -> Tuple["BoothPhenotype", "BoothPhenotype"]:
"""Perform crossover between two phenotypes.
Calls crossover method from the genotype attribute. Combines a portion of this object's genotype
with that of parent_2 to return 2 new phenotypes based on the combined genotypes.
The new genotype length is the same as of the parents.
Args:
parent_2: a phenotype of the same class whose genotype will be mixed with
Returns:
Two new phenotype instances based on the combined genotypes of the two parents.
"""
child_genotype_1, child_genotype_2 = self.genotype.crossover(parent_2.genotype)
child_1 = BoothPhenotype(child_genotype_1)
child_2 = BoothPhenotype(child_genotype_2)
return child_1, child_2
[docs] def mutate(self):
"""In place modification of the genotype by randomly changing genes based on mutation probability.
Calls mutate method as implemented for the genotype attribute in order to perform mutation.
Updates genotype attribute in place.
"""
self.genotype.mutate()