Source code for pygenalgo.genome.gene

from copy import deepcopy
from typing import Any, Callable
from collections.abc import Iterable

# Public interface.
__all__ = ["Gene"]


[docs] class Gene: """ Description: This is the main class that encodes the data of a single Gene in the chromosome. The class encapsulates not only the data, but also the way that this gene can be mutated using a random function. This Gene can be from a single 'bit' to a whole image. This way provides us with flexibility to parameterize the chromosome with different "kinds of genes" each one responsible for a specific function. """ # Object variables. __slots__ = ("_datum", "_func", "_valid") def __init__(self, datum: Any, func: Callable, valid: bool = True) -> None: """ Initialize a Gene object. :param datum: Datum holds a reference of the gene-data structure. :param func: This 'private' function is used in the 'random()' method to be used by the mutation operators. :param valid: This flag is used to set the Gene as valid or invalid. """ # Copy the data reference. self._datum = datum # Sanity check. if not callable(func): raise TypeError(f"{self.__class__.__name__}: Random function is not callable.") # _end_if_ # Get the random function. self._func = func # Copy the valid flag. Note that if the _datum field # is set to None, the Gene is automatically invalid. self._valid = False if self._datum is None else valid # _end_def_ @property def value(self) -> Any: """ Accessor (getter) of the data reference. :return: the datum value. """ return self._datum # _end_def_ @value.setter def value(self, new_value: Any) -> None: """ Accessor (setter) of the data. :return: None. """ self._datum = new_value # _end_def_ @property def func(self) -> Callable: """ Accessor (getter) of the gene function reference. :return: the _func reference. """ return self._func # _end_def_ @property def is_valid(self) -> bool: """ Accessor (getter) of the validity parameter. :return: the valid value. """ return self._valid # _end_def_ @is_valid.setter def is_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_
[docs] def random(self) -> None: """ This method should be different for each type of Gene. It describes how a specific type of Gene creates a random version of itself. The main idea is that inside the Chromosome, each Gene can represent a very different concept of the problem solution, so its Gene should have its own way to perform random mutation. This way by calling on the random() method, each Gene will know how to mutate itself without breaking any rules/constraints. :return: None. """ # Use the random function to set a new value at the data. self._datum = self._func()
# _end_def_
[docs] def flip(self) -> None: """ This method flips the value of the gene data. It is used only by the FlipMutator operator for problems where the chromosome is represented by a list of bits. 1) 1 -> 0 2) 0 -> 1 :return: None. """ # Flip the current gene value. self._datum = int(not self._datum)
# _end_def_
[docs] def clone(self) -> "Gene": """ Makes a duplicate of the self object by deep-coping only the datum field. :return: a "deep-copy" of the object. """ return Gene(deepcopy(self._datum), self._func, self._valid)
# _end_def_ def __eq__(self, other) -> bool: """ When we compare two Genes we care only about the data they hold. :param other: the second object we want to compare to. :return: true or false. """ # Check if they are the same instance. if self is other: return True # _end_if_ # Make sure both items are Gene. if not isinstance(other, Gene): return NotImplemented # _end_if_ # Compare only the datum fields. condition = self._datum == other._datum # If the condition is an Iterable make sure all the fields are checked. return all(condition) if isinstance(condition, Iterable) else condition # _end_def_ def __hash__(self) -> int: """ Auxiliary method to hash the Gene object. :return: the hash value of the datum. """ try: # Return directly the hash # value of the datum field. return hash(self._datum) except TypeError: # If it fails try to convert # it to a tuple first. return hash(tuple(self._datum)) # _end_def_ def __str__(self) -> str: """ Override to print a readable string presentation of the object. :return: a string representation of a Gene object. """ return f"{self.__class__.__name__}: datum={self._datum}" # _end_def_ def __repr__(self) -> str: """ Repr operator is called when a string representation is needed that can be evaluated. :return: Gene(). """ return (f"{self.__class__.__name__}(datum={self._datum}," f" func={self._func}, valid={self._valid})") # _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 Gene(self._datum, self._func, self._valid) # _end_def_ def __deepcopy__(self, memo: dict) -> "Gene": """ 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 = Gene.__new__(Gene) # Don't copy self reference. memo[id(self)] = new_object # Deepcopy ONLY the datum because it # might be a complex mutable object. setattr(new_object, "_datum", deepcopy(self._datum, memo)) # Simply copy the function handle. setattr(new_object, "_func", self._func) # Simply copy the boolean flag. setattr(new_object, "_valid", self._valid) # Return identical instance. return new_object
# _end_def_ # _end_class_