from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from typing import Optional
from typing import Sequence
from typing import Tuple
from typing import Self
from typing import Type
from typing import Any
from abc import abstractmethod
from abc import ABC
from typing import Generic, TypeVar
from .population import Individual, Population
from typing import override
D = TypeVar("D", bound=Individual)
[docs]
class Variator(ABC, Generic[D]):
[docs]
def __new__(cls: Type[Self], *args: Any, **kwargs: Any) -> Self:
"""Machinery. Implement managed attributes.
:meta private:
"""
instance: Self = super().__new__(cls)
instance.arity = None
instance.coarity = None
return instance
[docs]
def __init__(self: Self) -> None:
self.arity: Optional[int]
self.coarity: Optional[int]
[docs]
@abstractmethod
def vary(self, parents: Sequence[D]) -> Tuple[D, ...]:
"""Apply the variator to a tuple of parents
Produce a tuple of individuals from a tuple of individuals.
The input and output tuple sizes should match the arity and coarity of
this selector, respectively.
Note:
If the result could have a different
:attr:`fitness .Individual.reset_fitness`
than the original individual, call :meth:`.Individual.reset_fitness`
to clear its fitness.
"""
pass
def _group_to_parents(self,
population: Population[D])\
-> Sequence[Sequence[D]]:
"""Machinery.
"""
# Tuple magic. Zipping an iterable with itself extracts a tuple of
# that size. The "discarding" behaviour is implemented this way.
parent_groups: Sequence[Sequence[D]]
if self.arity is None:
raise TypeError("Variator does not specify arity,"
"cannot create parent groups")
else:
parent_groups = tuple(zip(*(iter(population),) * self.arity))
return parent_groups
[docs]
def vary_population(self: Self, population: Population[D]) -> Population[D]:
"""Vary the population.
Separate ``population`` into groups of size `.arity`. For each group,
call `.vary` with that group as argument, then collect the result.
At the end, return a :class:`.Population` of collected results.
"""
next_population = Population[D]()
parent_groups: Sequence[Sequence[D]] =\
self._group_to_parents(population)
for group in parent_groups:
results = self.vary(group)
for individual in results:
next_population.append(individual)
return next_population
[docs]
class NullVariator(Variator[D]):
"""Variator that does not change anything
"""
[docs]
def __init__(self) -> None:
self.arity = 1
self.coarity = 1
[docs]
@override
def vary(self, parents: Sequence[D]) -> Tuple[D, ...]:
return tuple(parents)