Skip to content

Commit 71ad3ba

Browse files
authored
Add type annotations to ribs.visualize and use ty for type checking (#606)
## Description <!-- Provide a brief description of the PR's purpose here. --> Proof of concept for adding type annotations to pyribs. For type checking, I am currently using [ty](https://docs.astral.sh/ty/) -- it lines up well with Ruff in terms of being really fast. However, ty is not quite production ready, so support for it in pre-commit and other tools is not quite ready yet (e.g., ALE: dense-analysis/ale#4971). However, I found it mostly sufficient for my purposes. In the future, the goal will be to add type annotations everywhere else in pyribs. This regex is helpful for catching docstrings that have old types: ``` grep -r "^ *[a-z_]* (.*):" ribs ``` ## TODO <!-- Notable points that this PR has either accomplished or will accomplish. --> - [x] Add ty to dev deps - [x] Add ty to pre-commit -- see astral-sh/ty#269 - [x] Skip ty in pre-commit CI - [x] Add ty instructions to docs - [x] Add type annotations in ribs.visualize - [x] Matplotlib imports in ribs.visualize had to be made more specific; see notes on submodules here: astral-sh/ty#445 - [x] `plt.axes(projection="3d")` isn't typed for the correct `Axes3D` object, so I had to add some ignores; see matplotlib/matplotlib#29341 - [x] Add py.typed and include it in MANIFEST.in ## Status - [x] I have read the guidelines in [CONTRIBUTING.md](https://github.com/icaros-usc/pyribs/blob/master/CONTRIBUTING.md) - [x] I have linted and formatted my code with `ruff` - [x] I have tested my code by running `pytest` - [x] I have added a description of my change to the changelog in `HISTORY.md` - [x] This PR is ready to go
1 parent 6b43c00 commit 71ad3ba

18 files changed

Lines changed: 527 additions & 442 deletions

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
- [ ] I have read the guidelines in
2020
[CONTRIBUTING.md](https://github.com/icaros-usc/pyribs/blob/master/CONTRIBUTING.md)
21-
- [ ] I have linted and formatted my code with `ruff`
21+
- [ ] I have linted and formatted my code with `ruff` and `ty`
2222
- [ ] I have tested my code by running `pytest`
2323
- [ ] I have added a description of my change to the changelog in `HISTORY.md`
2424
- [ ] This PR is ready to go

.github/workflows/testing.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ defaults:
2020
jobs:
2121
pre-commit:
2222
runs-on: ubuntu-latest
23+
env:
24+
# TODO (#609): Remove skip if possible.
25+
# Skip ty since it is not yet ready for pre-commit
26+
SKIP: ty
2327
steps:
2428
- uses: actions/checkout@v4
2529
- uses: conda-incubator/setup-miniconda@v3

.pre-commit-config.yaml

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
exclude: LICENSE
22
repos:
33
- repo: https://github.com/pre-commit/pre-commit-hooks
4-
rev: v4.4.0
4+
rev: v6.0.0
55
hooks:
66
# See https://pre-commit.com/hooks.html
77
- id: check-added-large-files
@@ -12,7 +12,7 @@ repos:
1212
- id: mixed-line-ending
1313
- id: trailing-whitespace
1414
- repo: https://github.com/astral-sh/ruff-pre-commit
15-
rev: v0.12.4
15+
rev: v0.12.10
1616
hooks:
1717
# Lint (without fixing).
1818
- id: ruff-check
@@ -21,8 +21,14 @@ repos:
2121
args: [--select, I, --fix]
2222
# Run formatter.
2323
- id: ruff-format
24+
- repo: local
25+
hooks:
26+
- id: ty
27+
name: ty-check
28+
language: system
29+
entry: ty check
2430
- repo: https://github.com/pre-commit/mirrors-prettier
25-
rev: v3.0.2
31+
rev: v3.1.0
2632
hooks:
2733
- id: prettier
2834
types_or: [markdown, yaml]

CONTRIBUTING.md

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,17 @@ Ready to contribute? Here's how to set up pyribs for local development.
5353
1. Now make the appropriate changes locally. If relevant, make sure to write
5454
tests for your code in the `tests/` folder.
5555

56-
1. Lint and auto-format your code using [ruff](https://docs.astral.sh/ruff/).
57-
Note that pre-commit will automatically run ruff whenever you commit your
58-
code; you can also run it with `pre-commit run`. You can also run the
59-
following commands on the command line:
56+
1. Lint and auto-format your code using [ruff](https://docs.astral.sh/ruff/) and
57+
[ty](https://docs.astral.sh/ty/). Note that pre-commit will automatically run
58+
ruff and ty whenever you commit your code; you can also run it with
59+
`pre-commit run`. You can also run the following commands on the command
60+
line:
6061

6162
```bash
6263
# Lint (without fixing).
6364
ruff check FILES
65+
# Run type checking.
66+
ty check FILES
6467
# Sort imports.
6568
ruff check --select I --fix FILES
6669
# Run formatter.

HISTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#### Improvements
1414

15+
- Add type annotations and use ty for type checking ({pr}`606`)
1516
- Migrate from pylint to ruff for linting ({pr}`605`, {pr}`607`)
1617
- Replace isort with ruff import check ({pr}`603`)
1718

MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ include HISTORY.md
33
include LICENSE
44
include README.md
55

6+
include ribs/py.typed
7+
68
exclude tests
79

810
global-exclude __pycache__

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ clean-test: ## remove test and coverage artifacts
3939

4040
lint: ## check style with ruff
4141
ruff check
42+
ty check
4243
.PHONY: lint
4344

4445
test: ## run tests with the default Python

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ all = [
5757
dev = [
5858
# Tools
5959
"ruff",
60+
"ty",
6061
"pre-commit",
6162

6263
# Testing

ribs/py.typed

Whitespace-only changes.

ribs/visualize/_cvt_archive_3d_plot.py

Lines changed: 68 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,22 @@
11
"""Provides cvt_archive_3d_plot."""
22

3+
from __future__ import annotations
4+
5+
from collections.abc import Iterable, Sequence
6+
from typing import Literal
7+
8+
import matplotlib.colors
39
import matplotlib.pyplot as plt
410
import numpy as np
11+
from matplotlib.axes import Axes
512
from matplotlib.cm import ScalarMappable
13+
from matplotlib.typing import ColorType
614
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
15+
from mpl_toolkits.mplot3d.axes3d import Axes3D
16+
from pandas import DataFrame
717
from scipy.spatial import Voronoi
818

19+
from ribs.archives import ArchiveDataFrame, CVTArchive
920
from ribs.visualize._utils import (
1021
retrieve_cmap,
1122
set_cbar,
@@ -15,26 +26,26 @@
1526

1627

1728
def cvt_archive_3d_plot(
18-
archive,
19-
ax=None,
29+
archive: CVTArchive,
30+
ax: Axes3D | None = None,
2031
*,
21-
df=None,
22-
measure_order=None,
23-
cmap="magma",
24-
lw=0.5,
25-
ec=(0.0, 0.0, 0.0, 0.1),
26-
cell_alpha=1.0,
27-
vmin=None,
28-
vmax=None,
29-
cbar="auto",
30-
cbar_kwargs=None,
31-
plot_elites=False,
32-
elite_ms=100,
33-
elite_alpha=0.5,
34-
plot_centroids=False,
35-
plot_samples=False,
36-
ms=1,
37-
):
32+
df: DataFrame | ArchiveDataFrame | None = None,
33+
measure_order: Iterable[int] | None = None,
34+
cmap: str | Sequence[ColorType] | matplotlib.colors.Colormap = "magma",
35+
lw: float = 0.5,
36+
ec: ColorType = (0.0, 0.0, 0.0, 0.1),
37+
cell_alpha: float = 1.0,
38+
vmin: float | None = None,
39+
vmax: float | None = None,
40+
cbar: Literal["auto"] | None | Axes = "auto",
41+
cbar_kwargs: dict | None = None,
42+
plot_elites: bool = False,
43+
elite_ms: float = 100,
44+
elite_alpha: float = 0.5,
45+
plot_centroids: bool = False,
46+
plot_samples: bool = False,
47+
ms: float = 1,
48+
) -> None:
3849
"""Plots a :class:`~ribs.archives.CVTArchive` with 3D measure space.
3950
4051
This function relies on Matplotlib's `mplot3d
@@ -152,52 +163,48 @@ def cvt_archive_3d_plot(
152163
>>> plt.show()
153164
154165
Args:
155-
archive (CVTArchive): A 3D :class:`~ribs.archives.CVTArchive`.
156-
ax (matplotlib.axes.Axes): Axes on which to plot the heatmap. If ``None``, we
157-
will create a new 3D axis.
158-
df (ribs.archives.ArchiveDataFrame): If provided, we will plot data from this
159-
argument instead of the data currently in the archive. This data can be
160-
obtained by, for instance, calling :meth:`ribs.archives.ArchiveBase.data`
161-
with ``return_type="pandas"`` and modifying the resulting
162-
:class:`~ribs.archives.ArchiveDataFrame`. Note that, at a minimum, the data
163-
must contain columns for index, objective, and measures. To display a custom
164-
metric, replace the "objective" column.
165-
measure_order (array-like of int): Specifies the axes order for plotting the
166-
measures. By default, the first measure (measure 0) in the archive appears
167-
on the x-axis, the second (measure 1) on y-axis, and third (measure 2) on
168-
z-axis. This argument is an array of length 3 that specifies which measure
169-
should appear on the x, y, and z axes. For instance, [2, 1, 0] will put
170-
measure 2 on the x-axis, measure 1 on the y-axis, and measure 0 on the
171-
z-axis.
172-
cmap (str, list, matplotlib.colors.Colormap): The colormap to use when plotting
173-
intensity. Either the name of a :class:`~matplotlib.colors.Colormap`, a list
174-
of RGB or RGBA colors (i.e. an :math:`N \\times 3` or :math:`N \\times 4`
175-
array), or a :class:`~matplotlib.colors.Colormap` object.
176-
lw (float): Line width when plotting the Voronoi diagram.
177-
ec (matplotlib color): Edge color of the cells in the Voronoi diagram. See
166+
archive: A 3D :class:`~ribs.archives.CVTArchive`.
167+
ax: Axes on which to plot the heatmap. If ``None``, we will create a new 3D
168+
axis.
169+
df: If provided, we will plot data from this argument instead of the data
170+
currently in the archive. This data can be obtained by, for instance,
171+
calling :meth:`ribs.archives.ArchiveBase.data` with ``return_type="pandas"``
172+
and modifying the resulting :class:`~ribs.archives.ArchiveDataFrame`. Note
173+
that, at a minimum, the data must contain columns for index, objective, and
174+
measures. To display a custom metric, replace the "objective" column.
175+
measure_order: Specifies the axes order for plotting the measures. By default,
176+
the first measure (measure 0) in the archive appears on the x-axis, the
177+
second (measure 1) on y-axis, and third (measure 2) on z-axis. This argument
178+
is an array of length 3 that specifies which measure should appear on the x,
179+
y, and z axes. For instance, [2, 1, 0] will put measure 2 on the x-axis,
180+
measure 1 on the y-axis, and measure 0 on the z-axis.
181+
cmap: The colormap to use when plotting intensity. Either the name of a
182+
:class:`~matplotlib.colors.Colormap`, a list of Matplotlib color
183+
specifications (e.g., an :math:`N \\times 3` or :math:`N \\times 4` array --
184+
see :class:`~matplotlib.colors.ListedColormap`), or a
185+
:class:`~matplotlib.colors.Colormap` object.
186+
lw: Line width when plotting the Voronoi diagram.
187+
ec: Edge color of the cells in the Voronoi diagram. See
178188
`here <https://matplotlib.org/stable/tutorials/colors/colors.html>`_ for
179189
more info on specifying colors in Matplotlib.
180190
cell_alpha: Alpha value for the cell colors. Set to 1.0 for opaque cells; set to
181191
0.0 for fully transparent cells.
182-
vmin (float): Minimum objective value to use in the plot. If ``None``, the
183-
minimum objective value in the archive is used.
184-
vmax (float): Maximum objective value to use in the plot. If ``None``, the
185-
maximum objective value in the archive is used.
186-
cbar ('auto', None, matplotlib.axes.Axes): By default, this is set to ``'auto'``
187-
which displays the colorbar on the archive's current
188-
:class:`~matplotlib.axes.Axes`. If ``None``, then colorbar is not displayed.
189-
If this is an :class:`~matplotlib.axes.Axes`, displays the colorbar on the
190-
specified Axes.
191-
cbar_kwargs (dict): Additional kwargs to pass to
192-
:func:`~matplotlib.pyplot.colorbar`.
193-
plot_elites (bool): If True, we will plot a scatter plot of the elites in the
192+
vmin: Minimum objective value to use in the plot. If ``None``, the minimum
193+
objective value in the archive is used.
194+
vmax: Maximum objective value to use in the plot. If ``None``, the maximum
195+
objective value in the archive is used.
196+
cbar: By default, this is set to ``'auto'`` which displays the colorbar on the
197+
archive's current :class:`~matplotlib.axes.Axes`. If ``None``, then colorbar
198+
is not displayed. If this is an :class:`~matplotlib.axes.Axes`, displays the
199+
colorbar on the specified Axes.
200+
cbar_kwargs: Additional kwargs to pass to :func:`~matplotlib.pyplot.colorbar`.
201+
plot_elites: If True, we will plot a scatter plot of the elites in the
194202
archive. The elites will be colored according to their objective value.
195-
elite_ms (float): Marker size for plotting elites.
196-
elite_alpha (float): Alpha value for plotting elites.
197-
plot_centroids (bool): Whether to plot the cluster centroids.
198-
plot_samples (bool): Whether to plot the samples used when generating the
199-
clusters.
200-
ms (float): Marker size for both centroids and samples.
203+
elite_ms: Marker size for plotting elites.
204+
elite_alpha: Alpha value for plotting elites.
205+
plot_centroids: Whether to plot the cluster centroids.
206+
plot_samples: Whether to plot the samples used when generating the clusters.
207+
ms: Marker size for both centroids and samples.
201208
202209
Raises:
203210
ValueError: The archive's measure dimension must be 1D or 2D.
@@ -269,7 +276,7 @@ def cvt_archive_3d_plot(
269276

270277
# Default ax behavior.
271278
if ax is None:
272-
ax = plt.axes(projection="3d")
279+
ax: Axes3D = plt.axes(projection="3d") # ty: ignore[invalid-assignment]
273280

274281
ax.set_xlim(lower_bounds[0], upper_bounds[0])
275282
ax.set_ylim(lower_bounds[1], upper_bounds[1])

0 commit comments

Comments
 (0)