Source code for ribs.emitters._genetic_algorithm_emitter

"""Provides the GeneticAlgorithmEmitter."""
import numpy as np

from ribs._utils import check_batch_shape, check_shape
from ribs.emitters._emitter_base import EmitterBase
from ribs.emitters.operators import _get_op


[docs]class GeneticAlgorithmEmitter(EmitterBase): """Creates solutions with a genetic algorithm. If the archive is empty and ``initial_solutions`` is set, a call to :meth:`ask` will return ``initial_solutions``. If ``initial_solutions`` is not set, we pass ``x0`` through the operator. Args: archive (ribs.archives.ArchiveBase): Archive of solutions, e.g., :class:`ribs.archives.GridArchive`. operator (str): Internal operator for mutating solutions. See :mod:`ribs.emitters.operators` for the list of allowed names. operator_kwargs (dict): Additional arguments to pass to the operator. See :mod:`ribs.emitters.operators` for the arguments allowed by each operator. x0 (numpy.ndarray): Initial solution. initial_solutions (array-like): An (n, solution_dim) array of solutions to be used when the archive is empty, in lieu of ``x0``. bounds (None or array-like): Bounds of the solution space. Solutions are clipped to these bounds. Pass None to indicate there are no bounds. Alternatively, pass an array-like to specify the bounds for each dim. Each element in this array-like can be None to indicate no bound, or a tuple of ``(lower_bound, upper_bound)``, where ``lower_bound`` or ``upper_bound`` may be None to indicate no bound. batch_size (int): Number of solutions to return in :meth:`ask`. seed (int): Value to seed the random number generator. Set to None to avoid a fixed seed. Raises: ValueError: There is an error in x0 or initial_solutions. ValueError: There is an error in the bounds configuration. """ def __init__(self, archive, *, operator, operator_kwargs=None, x0=None, initial_solutions=None, bounds=None, batch_size=64, seed=None): EmitterBase.__init__( self, archive, solution_dim=archive.solution_dim, bounds=bounds, ) self._batch_size = batch_size self._x0 = x0 self._initial_solutions = None if x0 is None and initial_solutions is None: raise ValueError("Either x0 or initial_solutions must be provided.") if x0 is not None and initial_solutions is not None: raise ValueError( "x0 and initial_solutions cannot both be provided.") if x0 is not None: self._x0 = np.array(x0, dtype=archive.dtypes["solution"]) check_shape(self._x0, "x0", archive.solution_dim, "archive.solution_dim") elif initial_solutions is not None: self._initial_solutions = np.asarray( initial_solutions, dtype=archive.dtypes["solution"]) check_batch_shape(self._initial_solutions, "initial_solutions", archive.solution_dim, "archive.solution_dim") operator_class = _get_op(operator) self._operator = operator_class( **(operator_kwargs if operator_kwargs is not None else {}), seed=seed, ) @property def x0(self): """numpy.ndarray: Initial Solution (if ``initial_solutions`` is not set).""" return self._x0 @property def initial_solutions(self): """numpy.ndarray: The initial solutions which are returned when the archive is empty (if x0 is not set).""" return self._initial_solutions @property def batch_size(self): """int: Number of solutions to return in :meth:`ask`.""" return self._batch_size def _clip(self, solutions): """Clips solutions to the bounds of the solution space.""" return np.clip(solutions, self.lower_bounds, self.upper_bounds)
[docs] def ask(self): """Creates solutions with the provided operator. If the archive is empty and ``initial_solutions`` is set, a call to :meth:`ask` will return ``initial_solutions``. If ``initial_solutions`` is not set, we pass ``x0`` through the operator. Otherwise, we sample parents from the archive to be passed to the operator. Returns: numpy.ndarray: If the archive is not empty, ``(batch_size, solution_dim)`` array -- contains ``batch_size`` new solutions to evaluate. If the archive is empty, we return ``initial_solutions``, which might not have ``batch_size`` solutions. Raises: ValueError: The ``parent_type`` of the operator is unknown. """ if self.archive.empty and self.initial_solutions is not None: return self._clip(self.initial_solutions) if self._operator.parent_type == 1: if self.archive.empty: parents = np.repeat(self.x0[None], repeats=self.batch_size, axis=0) else: parents = self.archive.sample_elites( self.batch_size)["solution"] return self._clip(self._operator.ask(parents)) elif self._operator.parent_type == 2: if self.archive.empty: parents = np.repeat(self.x0[None], repeats=2 * self.batch_size, axis=0) else: parents = self.archive.sample_elites( 2 * self.batch_size)["solution"] return self._clip( self._operator.ask(parents.reshape(2, self.batch_size, -1))) else: raise ValueError( f"Unknown operator `parent_type` {self._operator.parent_type}")