def _remap(self): """Remaps the archive. The boundaries are relocated to the percentage marks of the distribution of solutions stored in the archive. Also re-adds all of the solutions to the archive. Returns: tuple: The result of calling :meth:`ArchiveBase.add` on the last item in the buffer. """ # Sort all behavior values along the axis of each bc. sorted_bc = self._buffer.sorted_behavior_values # Calculate new boundaries. SlidingBoundariesArchive._remap_numba_helper(sorted_bc, self._buffer.size, self._boundaries, self._behavior_dim, self.dims) index_columns = tuple(map(list, zip(*self._occupied_indices))) old_sols = self._solutions[index_columns].copy() old_objs = self._objective_values[index_columns].copy() old_behs = self._behavior_values[index_columns].copy() self._reset_archive() for sol, obj, beh in zip(old_sols, old_objs, old_behs): # Add solutions from old archive. status, value = ArchiveBase.add(self, sol, obj, beh) for sol, obj, beh in self._buffer: # Add solutions from buffer. status, value = ArchiveBase.add(self, sol, obj, beh) return status, value
def __init__(self, dims, ranges, seed=None, dtype=np.float64): self._dims = np.array(dims) if len(self._dims) != len(ranges): raise ValueError(f"dims (length {len(self._dims)}) and ranges " f"(length {len(ranges)}) must be the same length") ArchiveBase.__init__( self, storage_dims=tuple(self._dims), behavior_dim=len(self._dims), seed=seed, dtype=dtype, ) ranges = list(zip(*ranges)) self._lower_bounds = np.array(ranges[0], dtype=self.dtype) self._upper_bounds = np.array(ranges[1], dtype=self.dtype) self._interval_size = self._upper_bounds - self._lower_bounds self._boundaries = [] for dim, lower_bound, upper_bound in zip(self._dims, self._lower_bounds, self._upper_bounds): self._boundaries.append( np.linspace(lower_bound, upper_bound, dim + 1))
def add(self, solution, objective_value, behavior_values): """Attempts to insert a new solution into the archive. This method remaps the archive after every ``self.remap_frequency`` solutions are added. Remapping involves changing the boundaries of the archive to the percentage marks of the behavior values stored in the buffer and re-adding all of the solutions stored in the buffer `and` the current archive. Args: solution (array-like): See :meth:`ArchiveBase.add` objective_value (float): See :meth:`ArchiveBase.add` behavior_values (array-like): See :meth:`ArchiveBase.add` Returns: See :meth:`ArchiveBase.add` """ solution = np.asarray(solution) behavior_values = np.asarray(behavior_values) self._buffer.add(solution, objective_value, behavior_values) self._total_num_sol += 1 if self._total_num_sol % self._remap_frequency == 0: status, value = self._remap() self._lower_bounds = np.array( [bound[0] for bound in self._boundaries]) self._upper_bounds = np.array([ bound[dim] for bound, dim in zip(self._boundaries, self._dims) ]) else: status, value = ArchiveBase.add(self, solution, objective_value, behavior_values) return status, value
def _remap(self): """Remaps the archive. The boundaries are relocated to the percentage marks of the distribution of solutions stored in the archive. Also re-adds all of the solutions to the archive. Returns: tuple: The result of calling :meth:`ArchiveBase.add` on the last item in the buffer. """ # Sort all behavior values along the axis of each bc. sorted_bc = self._buffer.sorted_behavior_values # Calculate new boundaries. SlidingBoundariesArchive._remap_numba_helper(sorted_bc, self._buffer.size, self._boundaries, self._behavior_dim, self.dims) # TODO (btjanaka): Add an option that allows adding solutions from the # previous archive that are not in the buffer. # Add all solutions to the new empty archive. self._reset_archive() for solution, objective_value, behavior_value in self._buffer: status, value = ArchiveBase.add(self, solution, objective_value, behavior_value) return status, value
def __init__(self, dims, ranges, seed=None, dtype=np.float64, remap_frequency=100, buffer_capacity=1000): self._dims = np.array(dims) if len(self._dims) != len(ranges): raise ValueError(f"dims (length {len(self._dims)}) and ranges " f"(length {len(ranges)}) must be the same length") ArchiveBase.__init__( self, storage_dims=tuple(self._dims), behavior_dim=len(self._dims), seed=seed, dtype=dtype, ) ranges = list(zip(*ranges)) self._lower_bounds = np.array(ranges[0], dtype=self.dtype) self._upper_bounds = np.array(ranges[1], dtype=self.dtype) self._interval_size = self._upper_bounds - self._lower_bounds # Specifics for sliding boundaries. self._remap_frequency = remap_frequency # Allocate an extra entry in each row so we can put the upper bound at # the end. self._boundaries = np.full( (self._behavior_dim, np.max(self._dims) + 1), np.nan, dtype=self.dtype) # Initialize the boundaries. for i, (dim, lower_bound, upper_bound) in enumerate( zip(self._dims, self._lower_bounds, self._upper_bounds)): self._boundaries[i, :dim + 1] = np.linspace( lower_bound, upper_bound, dim + 1) # Create buffer. self._buffer = SolutionBuffer(buffer_capacity, self._behavior_dim) # Total number of solutions encountered. self._total_num_sol = 0
def initialize(self, solution_dim): """Initializes the archive storage space and centroids. This method may take a while to run. In addition to allocating storage space, it runs :func:`~sklearn.cluster.k_means` to create an approximate CVT, and it constructs a :class:`~scipy.spatial.cKDTree` object containing the centroids found by k-means. k-means is not run if ``custom_centroids`` was passed in during construction. Args: solution_dim (int): The dimension of the solution space. Raises: RuntimeError: The archive is already initialized. RuntimeError: The number of centroids returned by k-means clustering was fewer than the number of bins specified during construction. This is most likely caused by having too few samples and too many bins. """ ArchiveBase.initialize(self, solution_dim) if self._centroids is None: self._samples = self._rng.uniform( self._lower_bounds, self._upper_bounds, size=(self._samples, self._behavior_dim), ).astype(self.dtype) if isinstance(self._samples, int) else self._samples self._centroids = k_means(self._samples, self._bins, **self._k_means_kwargs)[0] if self._centroids.shape[0] < self._bins: raise RuntimeError( "While generating the CVT, k-means clustering found " f"{self._centroids.shape[0]} centroids, but this archive " f"needs {self._bins} bins. This most likely happened " "because there are too few samples and/or too many bins.") if self._use_kd_tree: self._centroid_kd_tree = cKDTree(self._centroids, **self._ckdtree_kwargs)
def as_pandas(self, include_solutions=True): """Converts the archive into a Pandas dataframe. Args: include_solutions (bool): Whether to include solution columns. Returns: pandas.DataFrame: A dataframe where each row is an elite in the archive. The dataframe has ``behavior_dim`` columns called ``index_{i}`` for the archive index, ``behavior_dim`` columns called ``behavior_{i}`` for the behavior values, 1 column for the objective function value called ``objective``, and ``solution_dim`` columns called ``solution_{i}`` for the solution values. """ return ArchiveBase.as_pandas(self, include_solutions)
def as_pandas(self, include_solutions=True): """Converts the archive into a Pandas dataframe. Args: include_solutions (bool): Whether to include solution columns. Returns: pandas.DataFrame: A dataframe where each row is an elite in the archive. The dataframe consists of 1 ``index`` column indicating the index of the centroid in ``self._centroids``, ``behavior_dim`` columns called ``behavior_{i}`` for the behavior values, 1 column for the objective function value called ``objective``, and ``solution_dim`` columns called ``solution_{i}`` for the solution values. """ df = ArchiveBase.as_pandas(self, include_solutions) df.rename(columns={"index_0": "index"}, inplace=True) return df
the wrong shape. """ def __init__(self, bins, ranges, seed=None, dtype=np.float64, samples=100_000, custom_centroids=None, k_means_kwargs=None, use_kd_tree=False, ckdtree_kwargs=None): ArchiveBase.__init__( self, storage_dims=(bins, ), behavior_dim=len(ranges), seed=seed, dtype=dtype, ) ranges = list(zip(*ranges)) self._lower_bounds = np.array(ranges[0], dtype=self.dtype) self._upper_bounds = np.array(ranges[1], dtype=self.dtype) self._bins = bins # Apply default args for k-means. Users can easily override these, # particularly if they want higher quality clusters. self._k_means_kwargs = ({} if k_means_kwargs is None else k_means_kwargs.copy()) if "n_init" not in self._k_means_kwargs: