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): """Emits solutions by using operator provided. If the archive is empty and ``self._initial_solutions`` is set, a call to :meth:`ask` will return ``self._initial_solutions``. If ``self._initial_solutions`` is not set, we operate on self.x0. Args: archive (ribs.archives.ArchiveBase): An archive to use when creating and inserting solutions. For instance, this can be :class:`ribs.archives.GridArchive`. x0 (numpy.ndarray): Initial solution. operator (str): Internal Operator Class used to Mutate Solutions in ask method. operator_kwargs (dict): Additional arguments to pass to the operator. See :mod:`ribs.emitters.operators` for the arguments allowed by each operator. initial_solutions (array-like): An (n, solution_dim) array of solutions to be used when the archive is empty. If this argument is None, then solutions will be sampled from a Gaussian distribution centered at ``x0`` with standard deviation ``sigma``. 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`. Raises: ValueError: There is an error in x0 or initial_solutions. ValueError: There is an error in the bounds configuration. """ def __init__(self, archive, *, x0=None, initial_solutions=None, bounds=None, batch_size=64, operator_kwargs=None, operator=None): self._batch_size = batch_size self._x0 = x0 self._initial_solutions = None EmitterBase.__init__( self, archive, solution_dim=archive.solution_dim, bounds=bounds, ) if operator is None: raise ValueError("Operator must be provided.") 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.dtype) 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.dtype) check_batch_shape(self._initial_solutions, "initial_solutions", archive.solution_dim, "archive.solution_dim") self._operator = _get_op(operator)( lower_bounds=self._lower_bounds, upper_bounds=self._upper_bounds, **(operator_kwargs if operator_kwargs is not None else {})) @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
[docs] def ask(self): """Creates solutions with operator provided. If the archive is empty and ``self._initial_solutions`` is set, we return ``self._initial_solutions``. If ``self._initial_solutions`` is not set and the archive is still empty, we operate on the initial solution (x0) provided. Otherwise, we sample parents from the archive to be used as input 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 ``self._initial_solutions``, which might not have ``batch_size`` solutions. """ if self.archive.empty and self._initial_solutions is not None: return np.clip(self._initial_solutions, self.lower_bounds, self.upper_bounds) if 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._operator.ask( parents=parents.reshape(2, self._batch_size, -1)) else: # self._operator.parent_type == 1, gaussian 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._operator.ask(parents=parents)