Repository: icaros-usc/pyribs (language: python)

Issue:
Add proximity_archive_plot for visualizing ProximityArchive
## Description

<!-- Provide a brief description of the PR's purpose here. -->

Adds a tool for visualizing the ProximityArchive via a scatterplot.

## TODO

<!-- Notable points that this PR has either accomplished or will accomplish. -->

- [x] Implement function
- [x] Write tests

## 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



Discussion / hints:

Fix patch:
diff --git a/HISTORY.md b/HISTORY.md
index a0957bbc4..25f66e170 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -7,7 +7,8 @@
 #### API
 
 - Add NoveltyRanker for novelty search ({pr}`477`)
-- Add proximity_archive_plot for visualizing ProximityArchive ({pr}`476`)
+- Add proximity_archive_plot for visualizing ProximityArchive ({pr}`476`,
+  {pr}`480`)
 - 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


Test patch:
diff --git a/tests/visualize/proximity_archive_plot_test.py b/tests/visualize/proximity_archive_plot_test.py
new file mode 100644
index 000000000..50106dfcf
--- /dev/null
+++ b/tests/visualize/proximity_archive_plot_test.py
@@ -0,0 +1,135 @@
+"""Tests for proximity_archive_plot.
+
+See README.md for instructions on writing tests.
+"""
+import matplotlib.pyplot as plt
+import numpy as np
+import pytest
+from matplotlib.testing.decorators import image_comparison
+
+from ribs.archives import ProximityArchive
+from ribs.visualize import proximity_archive_plot
+
+# pylint: disable = redefined-outer-name
+
+#
+# Fixtures
+#
+
+
+def add_random_sphere(archive, x_range, y_range, use_objective=False):
+    """Adds 1000 random points from the negative sphere function.
+
+    Solutions, measures, and ranges are same as in add_uniform_sphere.
+    """
+    # Use random measures to make the boundaries shift.
+    rng = np.random.default_rng(10)
+
+    # Add over separate iterations so that novelty is actually considered.
+    for _ in range(10):
+        solutions = rng.uniform(
+            (x_range[0], y_range[0]),
+            (x_range[1], y_range[1]),
+            (100, 2),
+        )
+        objective = (-np.sum(np.square(solutions), axis=1)
+                     if use_objective else None)
+        archive.add(solution=solutions, objective=objective, measures=solutions)
+
+
+@pytest.fixture(scope="module")
+def proximity_archive_2d():
+    """Deterministically-created ProximityArchive."""
+    archive = ProximityArchive(solution_dim=2,
+                               measure_dim=2,
+                               k_neighbors=5,
+                               novelty_threshold=0.1,
+                               seed=42)
+    add_random_sphere(archive, (-1, 1), (-1, 1), use_objective=False)
+    return archive
+
+
+@pytest.fixture(scope="module")
+def proximity_archive_2d_long():
+    """Same as above, but the measure space is longer in one direction."""
+    archive = ProximityArchive(solution_dim=2,
+                               measure_dim=2,
+                               k_neighbors=5,
+                               novelty_threshold=0.1,
+                               seed=42)
+    add_random_sphere(archive, (-2, 2), (-1, 1), use_objective=False)
+    return archive
+
+
+#
+# Tests
+#
+
+
+@image_comparison(baseline_images=["2d"], remove_text=False, extensions=["png"])
+def test_2d(proximity_archive_2d):
+    plt.figure(figsize=(8, 6))
+    proximity_archive_plot(proximity_archive_2d,
+                           cmap=[[0.5, 0.5, 0.5]],
+                           cbar=None)
+
+
+@image_comparison(baseline_images=["2d"], remove_text=False, extensions=["png"])
+def test_2d_custom_axis(proximity_archive_2d):
+    _, ax = plt.subplots(figsize=(8, 6))
+    proximity_archive_plot(proximity_archive_2d,
+                           ax=ax,
+                           cmap=[[0.5, 0.5, 0.5]],
+                           cbar=None)
+
+
+@image_comparison(baseline_images=["2d_long"],
+                  remove_text=False,
+                  extensions=["png"])
+def test_2d_long(proximity_archive_2d_long):
+    plt.figure(figsize=(8, 6))
+    proximity_archive_plot(proximity_archive_2d_long,
+                           cmap=[[0.5, 0.5, 0.5]],
+                           cbar=None)
+
+
+@image_comparison(baseline_images=["2d_long_square"],
+                  remove_text=False,
+                  extensions=["png"])
+def test_2d_long_square(proximity_archive_2d_long):
+    plt.figure(figsize=(8, 6))
+    proximity_archive_plot(proximity_archive_2d_long,
+                           aspect="equal",
+                           cmap=[[0.5, 0.5, 0.5]],
+                           cbar=None)
+
+
+@image_comparison(baseline_images=["2d_long_transpose"],
+                  remove_text=False,
+                  extensions=["png"])
+def test_2d_long_transpose(proximity_archive_2d_long):
+    plt.figure(figsize=(8, 6))
+    proximity_archive_plot(proximity_archive_2d_long,
+                           transpose_measures=True,
+                           cmap=[[0.5, 0.5, 0.5]],
+                           cbar=None)
+
+
+@image_comparison(baseline_images=["bounds"],
+                  remove_text=False,
+                  extensions=["png"])
+def test_bounds(proximity_archive_2d):
+    plt.figure(figsize=(8, 6))
+    proximity_archive_plot(proximity_archive_2d,
+                           lower_bounds=[-5, -5],
+                           upper_bounds=[5, 5],
+                           cmap=[[0.5, 0.5, 0.5]],
+                           cbar=None)
+
+
+@image_comparison(baseline_images=["rasterized"],
+                  remove_text=False,
+                  extensions=["pdf"])
+def test_rasterized(proximity_archive_2d):
+    plt.figure(figsize=(8, 6))
+    proximity_archive_plot(proximity_archive_2d, rasterized=True)