From eb5471e1640e7d6964d10faefffc6b5bf917559f Mon Sep 17 00:00:00 2001 From: Bryon Tjanaka <38124174+btjanaka@users.noreply.github.com> Date: Mon, 1 Jul 2024 17:24:32 -0700 Subject: [PATCH] Set novelty to the threshold when ProximityArchive is empty (#479) ## Description Previously, we returned inf novelty when the ProximityArchive was empty. However, this can cause issues in code that expects ranking values to be finite values, such as in `CMAEvolutionStrategy.check_stop`. Functionally, this value works just as well as inf because it shows that the solutions had enough novelty to be added to the archive. Strictly speaking, since the archive is empty in this case, the novelty is undefined, so there are many values that would work. ## 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 --- HISTORY.md | 2 +- ribs/archives/_proximity_archive.py | 11 +++++------ tests/archives/proximity_archive_test.py | 6 +++--- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index dfd0c4d16..a0957bbc4 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -8,7 +8,7 @@ - Add NoveltyRanker for novelty search ({pr}`477`) - Add proximity_archive_plot for visualizing ProximityArchive ({pr}`476`) -- Add ProximityArchive for novelty search ({pr}`472`) +- Add ProximityArchive for novelty search ({pr}`472`, {pr}`479`) - Support diversity optimization in Scheduler.tell ({pr}`473`) - Allow specifying separate dtypes for solution, objective, and measures ({pr}`471`) diff --git a/ribs/archives/_proximity_archive.py b/ribs/archives/_proximity_archive.py index 59257042c..9c53b42fa 100644 --- a/ribs/archives/_proximity_archive.py +++ b/ribs/archives/_proximity_archive.py @@ -244,9 +244,9 @@ def add(self, solution, objective, measures, **fields): - ``"novelty"`` (:class:`numpy.ndarray` of :attr:`dtypes` ["measures"]): The computed novelty of the solutions passed in. If - there were no solutions to compute novelty with respect to (e.g., - the archive was empty), the novelty is set to infinity - (``numpy.inf``). + there were no solutions to compute novelty with respect to (i.e., + the archive was empty), the novelty is set to the + :attr:`novelty_threshold`. Raises: ValueError: The array arguments do not match their specified shapes. @@ -271,9 +271,8 @@ def add(self, solution, objective, measures, **fields): # If there are no neighbors for computing nearest neighbors, there # is infinite novelty and all solutions are added. novelty = np.full(len(data["measures"]), - np.inf, + self.novelty_threshold, dtype=self.dtypes["measures"]) - eligible = np.ones(len(data["measures"]), dtype=bool) else: # Compute nearest neighbors. k_neighbors = min(len(self), self.k_neighbors) @@ -283,8 +282,8 @@ def add(self, solution, objective, measures, **fields): dists = dists[:, None] if k_neighbors == 1 else dists novelty = np.mean(dists, axis=1) - eligible = novelty >= self.novelty_threshold + eligible = novelty >= self.novelty_threshold n_eligible = np.sum(eligible) new_size = len(self) + n_eligible diff --git a/tests/archives/proximity_archive_test.py b/tests/archives/proximity_archive_test.py index 126833db3..ca969a891 100644 --- a/tests/archives/proximity_archive_test.py +++ b/tests/archives/proximity_archive_test.py @@ -187,7 +187,7 @@ def test_add_single(data, use_list, add_mode): # Objective should default to 0.0. assert_archive_elite(data.archive, data.solution, 0.0, data.measures) assert add_info["status"] == AddStatus.NEW - assert add_info["novelty"] == np.inf + assert add_info["novelty"] == data.archive.novelty_threshold def test_add_single_after_clear(data): @@ -199,14 +199,14 @@ def test_add_single_after_clear(data): add_info = data.archive.add_single(data.solution, None, data.measures) assert add_info["status"] == 2 - assert add_info["novelty"] == np.inf + assert add_info["novelty"] == data.archive.novelty_threshold data.archive.clear() add_info = data.archive.add_single(data.solution, None, data.measures) assert add_info["status"] == 2 - assert add_info["novelty"] == np.inf + assert add_info["novelty"] == data.archive.novelty_threshold def test_add_novel_solution():