Source code for pygenalgo.genome.chromosome

from copy import deepcopy
from pygenalgo.genome.gene import Gene

# Public interface.
__all__ = ["Chromosome"]


[docs] class Chromosome: """ Description: Implements a dataclass for the Chromosome entity. This class is responsible for holding the individual solution(s), of the optimization problem, during the evolution process. """ # Object variables. __slots__ = ("_genome", "_fitness", "_valid") def __init__(self, genome: list[Gene], fitness: float = float("NaN"), valid: bool = True) -> None: """ Initialize a Chromosome object. :param genome: a list of genes. This list will encode a single solution to the problem :param fitness: the fitness of the chromosome. :param valid: whether the chromosome is valid. """ # Copy the genome. self._genome = genome # Get the initial fitness value. self._fitness = fitness # Set the bool flag. self._valid = valid # _end_def_ @property def valid(self) -> bool: """ Accessor (getter) of the validity parameter. :return: the valid value. """ return self._valid # _end_def_ @valid.setter def valid(self, new_value: bool) -> None: """ Accessor (setter) of the validity flag. :param new_value: (bool). """ # Check for the correct type. if not isinstance(new_value, bool): raise TypeError(f"{self.__class__.__name__}: " f"Validity flag should be bool: {type(new_value)}.") # _end_if_ # Update the flag value. self._valid = new_value # _end_def_ @property def fitness(self) -> float: """ Accessor of the fitness value of the chromosome. :return: the fitness (float) of the genome. """ return self._fitness # _end_def_ @fitness.setter def fitness(self, new_value: float) -> None: """ Accessor (setter) of the fitness value. :param new_value: (float). """ # Check for the correct type. if not isinstance(new_value, (int, float)): raise TypeError(f"{self.__class__.__name__}: " f"Fitness should be float: {type(new_value)}.") # _end_if_ # Update the fitness value. self._fitness = float(new_value) # _end_def_ @property def genome(self) -> list[Gene]: """ Accessor of the genome list of the chromosome. :return: the list (of Genes) of the chromosome. """ return self._genome # _end_def_
[docs] def invalidate_fitness(self) -> None: """ Invalidates the fitness of the chromosome by setting the value to NaN. This is used during the evolution process (mutation). :return: None. """ self._fitness = float("NaN")
# _end_def_
[docs] def has_valid_genome(self) -> bool: """ Checks the validity of the whole chromosome, by calling individually all genes is_valid method. :return: True if ALL genes are valid, else False. """ return all(x.is_valid for x in self._genome)
# _end_def_
[docs] def values(self) -> list: """ Returns the gene values of the chromosome as list. :return: the list values of the genome. """ return [gene.value for gene in self._genome]
# _end_def_
[docs] def hamming_distance(self, other) -> int: """ Compute the Hamming distance of the "self" object, with the "other" chromosome. In practice, it's the number of positions at which the corresponding genes are different. :param other: (Chromosome) to compare the Hamming distance. :return: (int) the number of dissimilarities between the two input chromosomes. """ # Make sure both objects are of the same type Chromosome. if not isinstance(other, Chromosome): raise TypeError(f"{self.__class__.__name__}: " f"Can't compute Hamming distance in different type objects.") # _end_if_ # Quick exit if both objects are the same or equal. if self is other or self == other: return 0 # _end_if_ # Compute the dissimilarities in their genomes. return [k != l for k, l in zip(self._genome, other.genome, strict=True)].count(True)
# _end_def_
[docs] def clone(self) -> "Chromosome": """ Makes a duplicate of the self object by deep-coping only the genome field. :return: a "deep-copy" of the object. """ return Chromosome(deepcopy(self._genome), self._fitness, self._valid)
# _end_def_ def __eq__(self, other) -> bool: """ Compares the genome of self, with the other chromosome and returns True if they are identical otherwise False. :param other: chromosome to compare. :return: True if the genomes are identical else False. """ # Check if they are the same instance. if self is other: return True # _end_if_ # Make sure both items are Chromosomes. if not isinstance(other, Chromosome): return NotImplemented # _end_if_ # Compare directly the two genomes. return self._genome == other.genome # _end_def_ def __hash__(self) -> int: """ Auxiliary method to hash the Chromosome object. :return: the hash value of the genome. """ return hash(tuple(self._genome)) # _end_def_ def __len__(self) -> int: """ Accessor of the total length of the genome. :return: the length (int) of the genome. """ return len(self._genome) # _end_def_ def __getitem__(self, index: int) -> Gene: """ Get the item at position 'index'. :param index: (int) the position that we want to return. :return: the reference to a Gene. """ return self._genome[index] # _end_def_ def __setitem__(self, index: int, item: Gene) -> None: """ Set the 'item' at position 'index'. :param index: (int) the position that we want to access. :param item: (Gene) the object we want to assign in the genome. :return: None. """ self._genome[index] = item # _end_def_ def __contains__(self, item: Gene) -> bool: """ Check for membership. :param item: an input Gene that we want to check. :return: true if the 'item' belongs in the genome. """ return item in self._genome # _end_def_ def __copy__(self): """ This custom method overrides the default copy method and is used when we call the copy() method on a class object. :return: a (shallow) copy of the self object. """ # Return the new copy. return Chromosome(self._genome, self._fitness, self._valid) # _end_copy_ def __deepcopy__(self, memo: dict) -> "Chromosome": """ This custom method overrides the default deepcopy method and is used when we call the "clone" method of the class. :param memo: dictionary of objects already copied during the current copying pass. :return: a new identical "clone" of the self object. """ # Create a new instance. new_object = Chromosome.__new__(Chromosome) # Don't copy self reference. memo[id(self)] = new_object # Deepcopy ONLY the genome because # it is a (mutable) list of Genes. setattr(new_object, "_genome", deepcopy(self._genome, memo)) # Simply copy the fitness value. setattr(new_object, "_fitness", self._fitness) # Simply copy the boolean flag. setattr(new_object, "_valid", self._valid) # Return identical instance. return new_object
# _end_def_ # _end_class_