Skip to content

Commit

Permalink
Add Gaussian, IsoLine Operators and Refactor GaussianEmitter/IsoLineE…
Browse files Browse the repository at this point in the history
…mitter (#418)

## Description

<!-- Provide a brief description of the PR's purpose here. -->

Add Gaussian, IsoLine Operators and Refactor GaussianEmitter/IsoLineEmitter

<!-- Notable points that this PR has either accomplished or will
accomplish. -->


## Questions

<!-- Any concerns or points of confusion? -->


## Status

- [x] I have read the guidelines in

[CONTRIBUTING.md](https://github.com/icaros-usc/pyribs/blob/master/CONTRIBUTING.md)
- [x] I have formatted my code using `yapf`
- [x] I have tested my code by running `pytest`
- [x] I have linted my code with `pylint`
- [x] I have added a one-line description of my change to the changelog
in
      `HISTORY.md`
- [x] This PR is ready to go

---------

Co-authored-by: Bryon Tjanaka <[email protected]>
  • Loading branch information
svott03 and btjanaka authored Nov 16, 2023
1 parent 66b8eb9 commit 4975a71
Show file tree
Hide file tree
Showing 6 changed files with 148 additions and 25 deletions.
2 changes: 2 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#### API

- Add Gaussian, IsoLine Operators and Refactor GaussianEmitter/IsoLineEmitter
({pr}`418`)
- **Backwards-incompatible:** Remove metadata in favor of custom fields
({pr}`420`)
- Add Base Operator Interface and Emitter Operator Retrieval ({pr}`416`)
Expand Down
14 changes: 7 additions & 7 deletions ribs/emitters/_gaussian_emitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from ribs._utils import check_1d_shape, check_batch_shape
from ribs.emitters._emitter_base import EmitterBase
from ribs.emitters.operators import GaussianOperator


class GaussianEmitter(EmitterBase):
Expand Down Expand Up @@ -85,6 +86,10 @@ def __init__(self,
solution_dim=archive.solution_dim,
bounds=bounds,
)
self._operator = GaussianOperator(sigma=self._sigma,
lower_bounds=self._lower_bounds,
upper_bounds=self._upper_bounds,
seed=seed)

@property
def x0(self):
Expand Down Expand Up @@ -130,13 +135,8 @@ def ask(self):
if self._initial_solutions is not None:
return np.clip(self._initial_solutions, self.lower_bounds,
self.upper_bounds)
parents = np.expand_dims(self.x0, axis=0)
parents = np.repeat(self.x0[None], repeats=self._batch_size, axis=0)
else:
parents = self.archive.sample_elites(self._batch_size)["solution"]

noise = self._rng.normal(
scale=self._sigma,
size=(self._batch_size, self.solution_dim),
).astype(self.archive.dtype)

return np.clip(parents + noise, self.lower_bounds, self.upper_bounds)
return self._operator.ask(parents=parents)
33 changes: 17 additions & 16 deletions ribs/emitters/_iso_line_emitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from ribs._utils import check_1d_shape, check_batch_shape
from ribs.emitters._emitter_base import EmitterBase
from ribs.emitters.operators import IsoLineOperator


class IsoLineEmitter(EmitterBase):
Expand Down Expand Up @@ -94,6 +95,12 @@ def __init__(self,
bounds=bounds,
)

self._operator = IsoLineOperator(line_sigma=self._line_sigma,
iso_sigma=self._iso_sigma,
lower_bounds=self._lower_bounds,
upper_bounds=self._upper_bounds,
seed=seed)

@property
def x0(self):
"""numpy.ndarray: Center of the Gaussian distribution from which to
Expand Down Expand Up @@ -144,22 +151,16 @@ def ask(self):
return np.clip(self._initial_solutions, self.lower_bounds,
self.upper_bounds)

iso_gaussian = self._rng.normal(
scale=self._iso_sigma,
size=(self._batch_size, self.solution_dim),
).astype(self.archive.dtype)

if self.archive.empty:
solution_batch = np.expand_dims(self._x0, axis=0) + iso_gaussian
else:
parents = self.archive.sample_elites(self._batch_size)["solution"]
directions = (
self.archive.sample_elites(self._batch_size)["solution"] -
parents)
line_gaussian = self._rng.normal(
scale=self._line_sigma,
size=(self._batch_size, 1),
iso_gaussian = self._rng.normal(
scale=self._iso_sigma,
size=(self._batch_size, self.solution_dim),
).astype(self.archive.dtype)
solution_batch = parents + iso_gaussian + line_gaussian * directions

return np.clip(solution_batch, self.lower_bounds, self.upper_bounds)
solution_batch = np.expand_dims(self._x0, axis=0) + iso_gaussian
return np.clip(solution_batch, self.lower_bounds, self.upper_bounds)
else:
parents = self.archive.sample_elites(2 *
self._batch_size)["solution"]
return self._operator.ask(
parents=parents.reshape(2, self._batch_size, -1))
13 changes: 11 additions & 2 deletions ribs/emitters/operators/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,20 @@
ribs.emitters.operators.OperatorBase
"""
from ribs.emitters.operators._gaussian import GaussianOperator
from ribs.emitters.operators._iso_line import IsoLineOperator
from ribs.emitters.operators._operator_base import OperatorBase

__all__ = ["OperatorBase"]
__all__ = [
"OperatorBase",
"GaussianOperator",
"IsoLineOperator",
]

_NAME_TO_OP_MAP = {}
_NAME_TO_OP_MAP = {
"gaussian": GaussianOperator,
"isoline": IsoLineOperator,
}


def _get_op(operator):
Expand Down
48 changes: 48 additions & 0 deletions ribs/emitters/operators/_gaussian.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""Gaussian Operator"""
import numpy as np

from ribs.emitters.operators._operator_base import OperatorBase


class GaussianOperator(OperatorBase):
"""Adds Gaussian noise to solutions.
Args:
sigma (float or array-like): Standard deviation of the Gaussian
distribution. Note we assume the Gaussian is diagonal, so if this
argument is an array, it must be 1D.
lower_bounds (array-like): Upper bounds of the solution space. Passed in
by emitter
upper_bounds (array-like): Upper bounds of the solution space. Passed in
by emitter
seed (int): Value to seed the random number generator. Set to None to
avoid a fixed seed.
"""

def __init__(self, sigma, seed, lower_bounds, upper_bounds):

self._sigma = sigma
self._lower_bounds = lower_bounds
self._upper_bounds = upper_bounds

self._rng = np.random.default_rng(seed)

def ask(self, parents):
"""Adds Gaussian noise to parents.
Args:
parents (array-like): (batch_size, solution_dim) array of
solutions to be mutated.
Returns:
numpy.ndarray: ``(batch_size, solution_dim)`` array that contains
``batch_size`` mutated solutions.
"""
parents = np.asarray(parents)

noise = self._rng.normal(
scale=self._sigma,
size=(parents.shape[0], parents.shape[1]),
).astype(parents.dtype)

return np.clip(parents + noise, self._lower_bounds, self._upper_bounds)
63 changes: 63 additions & 0 deletions ribs/emitters/operators/_iso_line.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Iso Line Operator"""
import numpy as np

from ribs.emitters.operators._operator_base import OperatorBase


class IsoLineOperator(OperatorBase):
"""Adds Isotropic Gaussian noise and directional noise to parents.
This operator was introduced in `Vassiliades 2018
<https://arxiv.org/abs/1804.03906>`_.
Args:
sigma (float or array-like): Standard deviation of the Gaussian
distribution. Note we assume the Gaussian is diagonal, so if this
argument is an array, it must be 1D.
lower_bounds (array-like): Upper bounds of the solution space. Passed in
by emitter
upper_bounds (array-like): Upper bounds of the solution space. Passed in
by emitter
seed (int): Value to seed the random number generator. Set to None to
avoid a fixed seed.
"""

def __init__(self, iso_sigma, line_sigma, lower_bounds, upper_bounds, seed):

self._iso_sigma = iso_sigma
self._line_sigma = line_sigma
self._lower_bounds = lower_bounds
self._upper_bounds = upper_bounds

self._rng = np.random.default_rng(seed)

def ask(self, parents):
""" Adds Isotropic Guassian noise and directional noise to parents.
Args:
parents (array-like): (2, batch_size, solution_dim)
parents[0] array of solutions selected by emitter
parents[1] array of second batch of solutions passed by
emitter. Used for calculating directional correlation.
Returns:
numpy.ndarray: ``(batch_size, solution_dim)`` array that contains
``batch_size`` mutated solutions.
"""
parents = np.asarray(parents)

elites = parents[0]
directions = parents[1] - parents[0]

iso_gaussian = self._rng.normal(
scale=self._iso_sigma,
size=(elites.shape[0], elites.shape[1]),
).astype(elites.dtype)

line_gaussian = self._rng.normal(
scale=self._line_sigma,
size=(elites.shape[0], 1),
).astype(elites.dtype)
solution_batch = elites + iso_gaussian + line_gaussian * directions

return np.clip(solution_batch, self._lower_bounds, self._upper_bounds)

0 comments on commit 4975a71

Please sign in to comment.