From 84c1625426e27693b9b596ad42505262a6131c17 Mon Sep 17 00:00:00 2001 From: Thomas Pinder Date: Sun, 4 Oct 2026 05:25:07 +0000 Subject: [PATCH 1/4] feat: add gpjax.plotting.use_style use_style applies the qc-core Calibrated Ledger style when qc-core is installed, and the GPJax Matplotlib style that now ships in the package when it is not. Matplotlib is imported only inside the function, so it stays an optional dependency. Co-Authored-By: Claude Opus 5.5 --- docs/reference/index.md | 1 + docs/reference/plotting.md | 11 ++++++ gpjax/__init__.py | 2 ++ {docs/examples => gpjax}/gpjax.mplstyle | 0 gpjax/plotting.py | 45 +++++++++++++++++++++++++ tests/test_plotting.py | 37 ++++++++++++++++++++ 6 files changed, 96 insertions(+) create mode 100644 docs/reference/plotting.md rename {docs/examples => gpjax}/gpjax.mplstyle (100%) create mode 100644 gpjax/plotting.py create mode 100644 tests/test_plotting.py diff --git a/docs/reference/index.md b/docs/reference/index.md index 36f8190bd..e13c3bb99 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -24,6 +24,7 @@ linalg integrators scan summary +plotting typing citation ``` diff --git a/docs/reference/plotting.md b/docs/reference/plotting.md new file mode 100644 index 000000000..9e1499fa8 --- /dev/null +++ b/docs/reference/plotting.md @@ -0,0 +1,11 @@ +# Plotting + +```{eval-rst} +.. currentmodule:: gpjax.plotting + +.. autosummary:: + :toctree: generated/ + :nosignatures: + + use_style +``` diff --git a/gpjax/__init__.py b/gpjax/__init__.py index 13cd56057..a0f257518 100644 --- a/gpjax/__init__.py +++ b/gpjax/__init__.py @@ -28,6 +28,7 @@ natural_gradients, objectives, parameters, + plotting, state_space, typing as typing, variational_families, @@ -82,6 +83,7 @@ "natural_gradients", "objectives", "parameters", + "plotting", "state_space", "summarise", "typing", diff --git a/docs/examples/gpjax.mplstyle b/gpjax/gpjax.mplstyle similarity index 100% rename from docs/examples/gpjax.mplstyle rename to gpjax/gpjax.mplstyle diff --git a/gpjax/plotting.py b/gpjax/plotting.py new file mode 100644 index 000000000..7f26cf7c8 --- /dev/null +++ b/gpjax/plotting.py @@ -0,0 +1,45 @@ +"""Matplotlib styling for GPJax figures. + +`use_style` applies QuantClimate's "Calibrated Ledger" style when the optional +`qc-core` package is installed, and the Matplotlib style that ships with GPJax +when it is not. Matplotlib is imported only when `use_style` is called, so it +stays an optional dependency of GPJax. +""" + +from pathlib import Path +import typing as tp + +__all__ = ["STYLE_PATH", "use_style"] + +#: The fallback Matplotlib style file that ships with GPJax. +STYLE_PATH: Path = Path(__file__).with_name("gpjax.mplstyle") + + +def use_style() -> tp.Literal["ledger", "gpjax"]: + """Apply the GPJax plotting style to Matplotlib globally. + + If `qc-core` is installed, this applies its Calibrated Ledger style, which + also registers the ledger colour names (for example, `color="oxblood"`). + Otherwise, it applies the style file at `STYLE_PATH`. + + Returns: + The name of the style that was applied: `"ledger"` or `"gpjax"`. + + Raises: + ImportError: If Matplotlib is not installed. + """ + try: + from matplotlib import style + except ImportError as error: + raise ImportError( + "gpjax.plotting needs Matplotlib. Install it with `pip install matplotlib`." + ) from error + + try: + from qc_core import plotting as qc_plotting + except ImportError: + style.use(STYLE_PATH) + return "gpjax" + + qc_plotting.use_ledger_style() + return "ledger" diff --git a/tests/test_plotting.py b/tests/test_plotting.py new file mode 100644 index 000000000..0bf2eeb2c --- /dev/null +++ b/tests/test_plotting.py @@ -0,0 +1,37 @@ +import sys + +import pytest + +mpl = pytest.importorskip("matplotlib") + +from gpjax import plotting + + +def test_use_style_applies_ledger_style_when_qc_core_is_installed() -> None: + pytest.importorskip("qc_core") + with mpl.rc_context(): + assert plotting.use_style() == "ledger" + assert mpl.rcParams["figure.facecolor"] == "#faf9f7" + + +def test_use_style_falls_back_to_packaged_style_without_qc_core( + monkeypatch: pytest.MonkeyPatch, +) -> None: + # A `None` entry in sys.modules makes `import qc_core` raise ImportError. + monkeypatch.setitem(sys.modules, "qc_core", None) + expected = mpl.rc_params_from_file(plotting.STYLE_PATH, use_default_template=False) + with mpl.rc_context(): + assert plotting.use_style() == "gpjax" + assert mpl.rcParams["axes.prop_cycle"] == expected["axes.prop_cycle"] + + +def test_use_style_raises_clear_error_without_matplotlib( + monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setitem(sys.modules, "matplotlib", None) + with pytest.raises(ImportError, match="needs Matplotlib"): + plotting.use_style() + + +def test_fallback_style_ships_with_the_package() -> None: + assert plotting.STYLE_PATH.is_file() From 47f52b937c38ab606bff309758600a5227444b6a Mon Sep 17 00:00:00 2001 From: Thomas Pinder Date: Sun, 4 Oct 2026 05:25:07 +0000 Subject: [PATCH 2/4] docs: draw the example plots in the qc-core ledger style Every example notebook now calls gpx.plotting.use_style(), replacing the local use_mpl_style helper and docs/examples/gpjax.mplstyle. The docs extra needs qc-core>=0.0.3 (the first release with a valid scatter edge colour), which is exempt from the 7-day exclude-newer cooldown because it is QuantClimate's own trusted-published package. A hidden cell in each notebook hides matplotlib's font fallback messages, as impulso does. Co-Authored-By: Claude Opus 5.5 --- docs/conf.py | 2 +- docs/examples/backend.py | 10 ++++++++-- docs/examples/barycentres.py | 10 ++++++++-- docs/examples/classification.py | 10 ++++++++-- docs/examples/collapsed_vi.py | 10 ++++++++-- docs/examples/constructing_new_kernels.py | 10 ++++++++-- docs/examples/deep_kernels.py | 10 ++++++++-- docs/examples/dual_svgp.py | 11 +++++++++-- docs/examples/graph_kernels.py | 10 ++++++++-- docs/examples/heteroscedastic_inference.py | 10 ++++++++-- docs/examples/intro_to_gps.py | 15 ++++++++++----- docs/examples/intro_to_kernels.py | 10 ++++++++-- docs/examples/likelihoods_guide.py | 10 ++++++++-- docs/examples/multioutput.py | 10 ++++++++-- docs/examples/natgrads.py | 11 +++++++++-- docs/examples/natural_gradients.py | 11 +++++++++-- docs/examples/numpyro_integration.py | 10 ++++++++-- docs/examples/oak.py | 10 ++++++++-- docs/examples/oceanmodelling.py | 10 ++++++++-- docs/examples/oilmm.py | 10 ++++++++-- docs/examples/poisson.py | 10 ++++++++-- docs/examples/regression.py | 14 +++++++++----- docs/examples/spatial_linear_gp.py | 10 ++++++++-- docs/examples/state_space_gps.py | 11 +++++++++-- docs/examples/uncollapsed_vi.py | 10 ++++++++-- docs/examples/utils.py | 8 -------- docs/examples/xarray_workflow.py | 10 ++++++++-- docs/examples/yacht.py | 10 ++++++++-- pyproject.toml | 5 +++++ uv.lock | 17 +++++++++++++++++ 30 files changed, 238 insertions(+), 67 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 357afc7b8..ef99b4dea 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -83,7 +83,7 @@ "examples/_*.py", "examples/**/_*.py", # GPJax's notebook helper predates that naming convention and is imported by - # name (`from utils import use_mpl_style`), so it cannot simply be renamed. + # name (`from utils import clean_legend`), so it cannot simply be renamed. "examples/utils.py", # Image/style assets, plus one stray legacy module (static/jaxkern/main.py) # that source_suffix would otherwise read as a notebook. diff --git a/docs/examples/backend.py b/docs/examples/backend.py index 33ccc5364..0b91c936b 100644 --- a/docs/examples/backend.py +++ b/docs/examples/backend.py @@ -27,11 +27,17 @@ # [official documentation](https://docs.kidger.site/equinox/). # +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% import typing as tp import equinox as eqx -from utils import use_mpl_style from gpjax.mean_functions import ( AbstractMeanFunction, Constant, @@ -72,7 +78,7 @@ def glue(*args, **kwargs): # set the default style for plotting -use_mpl_style() +gpx.plotting.use_style() cols = mpl.rcParams["axes.prop_cycle"].by_key()["color"] diff --git a/docs/examples/barycentres.py b/docs/examples/barycentres.py index 453e82546..3f4fb4bac 100644 --- a/docs/examples/barycentres.py +++ b/docs/examples/barycentres.py @@ -30,10 +30,16 @@ # significantly more favourable uncertainty estimation. # +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% import typing as tp -from utils import use_mpl_style import jax # Enable Float64 for more stable matrix inversions. @@ -60,7 +66,7 @@ def glue(*args, **kwargs): key = jr.key(123) # set the default style for plotting -use_mpl_style() +gpx.plotting.use_style() cols = plt.rcParams["axes.prop_cycle"].by_key()["color"] diff --git a/docs/examples/classification.py b/docs/examples/classification.py index fe3c5c24c..4c09bce96 100644 --- a/docs/examples/classification.py +++ b/docs/examples/classification.py @@ -25,9 +25,15 @@ # closed form, is covered in the [regression notebook](regression.py); everything that # follows is a consequence of losing that closed form. +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% import equinox as eqx -from utils import use_mpl_style from gpjax.linalg import add_jitter, cholesky_factor from gpjax.parameters import val import jax @@ -57,7 +63,7 @@ identity_matrix = jnp.eye # set the default style for plotting -use_mpl_style() +gpx.plotting.use_style() key = jr.key(42) cols = plt.rcParams["axes.prop_cycle"].by_key()["color"] diff --git a/docs/examples/collapsed_vi.py b/docs/examples/collapsed_vi.py index f22c2716b..d1108303f 100644 --- a/docs/examples/collapsed_vi.py +++ b/docs/examples/collapsed_vi.py @@ -32,9 +32,15 @@ # the uncollapsed bound of the # [sparse stochastic variational inference notebook](uncollapsed_vi.py). +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% # Enable Float64 for more stable matrix inversions. -from utils import use_mpl_style from jax import ( config, jit, @@ -54,7 +60,7 @@ # set the default style for plotting -use_mpl_style() +gpx.plotting.use_style() key = jr.key(42) diff --git a/docs/examples/constructing_new_kernels.py b/docs/examples/constructing_new_kernels.py index 6ce0edc6a..633b6804f 100644 --- a/docs/examples/constructing_new_kernels.py +++ b/docs/examples/constructing_new_kernels.py @@ -24,9 +24,15 @@ # to a GP prior; if not, our [introduction to kernels](intro_to_kernels.py) builds that # intuition first. +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% # Enable Float64 for more stable matrix inversions. -from utils import use_mpl_style from gpjax.kernels.base import val from gpjax.kernels.computations import DenseKernelComputation from gpjax.parameters import PositiveReal @@ -49,7 +55,7 @@ # set the default style for plotting -use_mpl_style() +gpx.plotting.use_style() key = jr.key(42) diff --git a/docs/examples/deep_kernels.py b/docs/examples/deep_kernels.py index 6003285cc..604b8a2fc 100644 --- a/docs/examples/deep_kernels.py +++ b/docs/examples/deep_kernels.py @@ -26,9 +26,15 @@ # {cite:t}`wilson2016deep`, transforming the inputs to our # Gaussian process model's kernel through a neural network can offer a solution to this. +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% import equinox as eqx -from utils import use_mpl_style from gpjax.kernels.computations import ( AbstractKernelComputation, DenseKernelComputation, @@ -58,7 +64,7 @@ # set the default style for plotting -use_mpl_style() +gpx.plotting.use_style() cols = mpl.rcParams["axes.prop_cycle"].by_key()["color"] key = jr.key(42) diff --git a/docs/examples/dual_svgp.py b/docs/examples/dual_svgp.py index 2918d7171..d342f145d 100644 --- a/docs/examples/dual_svgp.py +++ b/docs/examples/dual_svgp.py @@ -31,6 +31,13 @@ # denotes the stored sites; $\mathbf{a}_i = # \mathbf{K}_{zz}^{-1}\mathbf{k}_z(x_i)$. +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% # Enable Float64 for more stable matrix inversions. import time @@ -45,7 +52,7 @@ import matplotlib.pyplot as plt import optax as ox import paramax -from utils import clean_legend, use_mpl_style +from utils import clean_legend config.update("jax_enable_x64", True) @@ -67,7 +74,7 @@ key = jr.key(123) # set the default style for plotting -use_mpl_style() +gpx.plotting.use_style() cols = mpl.rcParams["axes.prop_cycle"].by_key()["color"] diff --git a/docs/examples/graph_kernels.py b/docs/examples/graph_kernels.py index 6811745b3..6d5f2b08d 100644 --- a/docs/examples/graph_kernels.py +++ b/docs/examples/graph_kernels.py @@ -25,10 +25,16 @@ # kernels supported within GPJax, see the # [kernels notebook](constructing_new_kernels.py). +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% import random -from utils import use_mpl_style # Enable Float64 for more stable matrix inversions. from jax import config @@ -52,7 +58,7 @@ def glue(*args, **kwargs): # set the default style for plotting -use_mpl_style() +gpx.plotting.use_style() key = jr.key(42) diff --git a/docs/examples/heteroscedastic_inference.py b/docs/examples/heteroscedastic_inference.py index 61f3d428c..8f4439ca0 100644 --- a/docs/examples/heteroscedastic_inference.py +++ b/docs/examples/heteroscedastic_inference.py @@ -46,6 +46,13 @@ # moments of each into an ELBO. For non-Gaussian likelihoods the same structure # remains; only the expected log-likelihood changes. +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% tags=["remove-cell"] import os @@ -60,7 +67,6 @@ import matplotlib.pyplot as plt import optax as ox -from utils import use_mpl_style import gpjax as gpx from gpjax.likelihoods import ( HeteroscedasticGaussian, @@ -76,7 +82,7 @@ config.update("jax_enable_x64", True) -use_mpl_style() +gpx.plotting.use_style() key = jr.key(123) cols = mpl.rcParams["axes.prop_cycle"].by_key()["color"] diff --git a/docs/examples/intro_to_gps.py b/docs/examples/intro_to_gps.py index 28dda00e0..f02e7b42f 100644 --- a/docs/examples/intro_to_gps.py +++ b/docs/examples/intro_to_gps.py @@ -120,6 +120,13 @@ # # We can plot three different parameterisations of this density. +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% import warnings @@ -131,13 +138,11 @@ import pandas as pd import seaborn as sns -from utils import ( - confidence_ellipse, - use_mpl_style, -) +from gpjax import plotting +from utils import confidence_ellipse # set the default style for plotting -use_mpl_style() +plotting.use_style() key = jr.key(42) diff --git a/docs/examples/intro_to_kernels.py b/docs/examples/intro_to_kernels.py index 2bca91465..73e168281 100644 --- a/docs/examples/intro_to_kernels.py +++ b/docs/examples/intro_to_kernels.py @@ -21,11 +21,17 @@ # # In this guide we provide an introduction to kernels, and the role they play in Gaussian process models. +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% # Enable Float64 for more stable matrix inversions. from pathlib import Path -from utils import use_mpl_style from gpjax.typing import Array from jax import config import jax.numpy as jnp @@ -50,7 +56,7 @@ key = jr.key(42) # set the default style for plotting -use_mpl_style() +gpx.plotting.use_style() cols = mpl.rcParams["axes.prop_cycle"].by_key()["color"] # %% [markdown] diff --git a/docs/examples/likelihoods_guide.py b/docs/examples/likelihoods_guide.py index dddebe37f..ef2efc1e6 100644 --- a/docs/examples/likelihoods_guide.py +++ b/docs/examples/likelihoods_guide.py @@ -72,6 +72,13 @@ # these methods in the forthcoming sections, but first, we will show how to instantiate # a likelihood object. To do this, we'll need a dataset. +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% mystnb={"figure": {"caption": "Fifty noisy observations of a sinusoid, shown alongside the latent function that generated them.", "name": "fig-likelihoods-guide-data"}} import jax @@ -81,14 +88,13 @@ import jax.random as jr import matplotlib.pyplot as plt -from utils import use_mpl_style import gpjax as gpx config.update("jax_enable_x64", True) # set the default style for plotting -use_mpl_style() +gpx.plotting.use_style() cols = plt.rcParams["axes.prop_cycle"].by_key()["color"] key = jr.key(42) diff --git a/docs/examples/multioutput.py b/docs/examples/multioutput.py index 2ece5d6ff..aa16b0068 100644 --- a/docs/examples/multioutput.py +++ b/docs/examples/multioutput.py @@ -31,8 +31,14 @@ # coregionalization matrix to see what the model has discovered about the output # structure. +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% -from utils import use_mpl_style from jax import config import jax.numpy as jnp import jax.random as jr @@ -46,7 +52,7 @@ import gpjax as gpx key = jr.key(42) -use_mpl_style() +gpx.plotting.use_style() cols = mpl.rcParams["axes.prop_cycle"].by_key()["color"] # %% [markdown] diff --git a/docs/examples/natgrads.py b/docs/examples/natgrads.py index 16fae1e2c..39d9b90bd 100644 --- a/docs/examples/natgrads.py +++ b/docs/examples/natgrads.py @@ -31,6 +31,13 @@ # [dual sparse GP notebook](dual_svgp.py) applies the same ideas to site # parameters. +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% # Enable Float64 for more stable matrix inversions. import time @@ -45,7 +52,7 @@ import matplotlib.pyplot as plt import optax as ox import paramax -from utils import clean_legend, use_mpl_style +from utils import clean_legend config.update("jax_enable_x64", True) @@ -65,7 +72,7 @@ key = jr.key(123) # set the default style for plotting -use_mpl_style() +gpx.plotting.use_style() cols = mpl.rcParams["axes.prop_cycle"].by_key()["color"] diff --git a/docs/examples/natural_gradients.py b/docs/examples/natural_gradients.py index 4d7cf7f2d..fc1416112 100644 --- a/docs/examples/natural_gradients.py +++ b/docs/examples/natural_gradients.py @@ -82,6 +82,13 @@ # [stochastic sparse GP notebook](uncollapsed_vi.py) first — everything below # assumes the SVGP evidence lower bound. +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% import equinox as eqx import jax @@ -93,7 +100,7 @@ import matplotlib as mpl import matplotlib.pyplot as plt import paramax -from utils import clean_legend, use_mpl_style +from utils import clean_legend config.update("jax_enable_x64", True) @@ -120,7 +127,7 @@ key = jr.key(123) # set the default style for plotting -use_mpl_style() +gpx.plotting.use_style() cols = mpl.rcParams["axes.prop_cycle"].by_key()["color"] diff --git a/docs/examples/numpyro_integration.py b/docs/examples/numpyro_integration.py index 6590bdb9c..10df140a8 100644 --- a/docs/examples/numpyro_integration.py +++ b/docs/examples/numpyro_integration.py @@ -25,6 +25,13 @@ # scenario where we have a structured mean function in the form of a linear model, and a GP # capturing the residuals. We will infer the parameters of both the linear model and the GP jointly. +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% tags=["remove-cell"] import os @@ -32,7 +39,6 @@ ci = os.environ.get("GPJAX_DOCS_CI") == "1" # %% -from utils import use_mpl_style import gpjax as gpx from jax import config import jax.numpy as jnp @@ -49,7 +55,7 @@ config.update("jax_enable_x64", True) -use_mpl_style() +gpx.plotting.use_style() cols = mpl.rcParams["axes.prop_cycle"].by_key()["color"] key = jr.key(123) diff --git a/docs/examples/oak.py b/docs/examples/oak.py index 2f610b191..9b4815f5f 100644 --- a/docs/examples/oak.py +++ b/docs/examples/oak.py @@ -36,13 +36,19 @@ # # We illustrate the full workflow on the UCI Auto MPG dataset. +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% # Enable Float64 for more stable matrix inversions. from jax import config config.update("jax_enable_x64", True) -from utils import use_mpl_style import jax.numpy as jnp import jax.random as jr from jaxtyping import install_import_hook @@ -60,7 +66,7 @@ ) key = jr.key(123) -use_mpl_style() +gpx.plotting.use_style() colours = mpl.rcParams["axes.prop_cycle"].by_key()["color"] # %% [markdown] diff --git a/docs/examples/oceanmodelling.py b/docs/examples/oceanmodelling.py index a4203fd49..3d51e3da5 100644 --- a/docs/examples/oceanmodelling.py +++ b/docs/examples/oceanmodelling.py @@ -33,10 +33,16 @@ # surface drifter trajectories from the Gulf of Mexico spanning 28 years. # +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% from pathlib import Path -from utils import use_mpl_style from gpjax.kernels.computations import DenseKernelComputation from jax import ( config, @@ -67,7 +73,7 @@ def glue(*args, **kwargs): # set the default style for plotting -use_mpl_style() +gpx.plotting.use_style() key = jr.key(42) diff --git a/docs/examples/oilmm.py b/docs/examples/oilmm.py index d8d876e49..ad5dff33d 100644 --- a/docs/examples/oilmm.py +++ b/docs/examples/oilmm.py @@ -43,10 +43,16 @@ # correlated North Atlantic wave-height outputs, optimises the model's parameters # via the OILMM log marginal likelihood, and visualises its predictions. +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% from pathlib import Path -from utils import use_mpl_style from jax import config import jax.numpy as jnp import jax.random as jr @@ -61,7 +67,7 @@ import gpjax as gpx key = jr.key(123) -use_mpl_style() +gpx.plotting.use_style() cols = mpl.rcParams["axes.prop_cycle"].by_key()["color"] # %% [markdown] diff --git a/docs/examples/poisson.py b/docs/examples/poisson.py index e45960524..d7fc9532e 100644 --- a/docs/examples/poisson.py +++ b/docs/examples/poisson.py @@ -26,6 +26,13 @@ # [classification notebook](classification.py) settles for a point estimate of the # latent function and a Gaussian approximation around it, we sample it here instead. +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% tags=["remove-cell"] import os @@ -37,7 +44,6 @@ import blackjax import equinox as eqx -from utils import use_mpl_style import jax from jax import config import jax.numpy as jnp @@ -56,7 +62,7 @@ config.update("jax_enable_x64", True) # set the default style for plotting -use_mpl_style() +gpx.plotting.use_style() cols = mpl.rcParams["axes.prop_cycle"].by_key()["color"] key = jr.key(42) diff --git a/docs/examples/regression.py b/docs/examples/regression.py index 6ca2c4ccb..7a9e870be 100644 --- a/docs/examples/regression.py +++ b/docs/examples/regression.py @@ -24,11 +24,15 @@ # [introduction to Gaussian processes](intro_to_gps.py) derives the same model from # Bayes' theorem. +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% # Enable Float64 for more stable matrix inversions. -from utils import ( - use_mpl_style, -) from jax import config import jax.numpy as jnp import jax.random as jr @@ -36,7 +40,7 @@ import matplotlib as mpl import matplotlib.pyplot as plt -from utils import use_mpl_style, clean_legend +from utils import clean_legend config.update("jax_enable_x64", True) @@ -48,7 +52,7 @@ key = jr.key(123) # set the default style for plotting -use_mpl_style() +gpx.plotting.use_style() cols = mpl.rcParams["axes.prop_cycle"].by_key()["color"] diff --git a/docs/examples/spatial_linear_gp.py b/docs/examples/spatial_linear_gp.py index 132c68864..996aa234a 100644 --- a/docs/examples/spatial_linear_gp.py +++ b/docs/examples/spatial_linear_gp.py @@ -43,6 +43,13 @@ # masks a non-linear residual. Composing models lets us represent both # behaviours without forcing a single mechanism to fit every feature of the data. +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% tags=["remove-cell"] import os @@ -65,12 +72,11 @@ Predictive, ) -from utils import use_mpl_style import gpjax as gpx jax.config.update("jax_enable_x64", True) -use_mpl_style() +gpx.plotting.use_style() cols = mpl.rcParams["axes.prop_cycle"].by_key()["color"] diff --git a/docs/examples/state_space_gps.py b/docs/examples/state_space_gps.py index 29ea147d3..3ea0223e2 100644 --- a/docs/examples/state_space_gps.py +++ b/docs/examples/state_space_gps.py @@ -37,6 +37,13 @@ # 4. contrast the smoothed posterior with the *causal* (filtered) one, and # 5. confirm empirically that inference scales linearly in $N$. +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% tags=["remove-cell"] import os @@ -47,7 +54,7 @@ # Enable Float64 for more stable matrix factorisations. from pathlib import Path -from utils import clean_legend, use_mpl_style +from utils import clean_legend import jax from jax import config import jax.numpy as jnp @@ -72,7 +79,7 @@ key = jr.key(123) # set the default style for plotting -use_mpl_style() +gpx.plotting.use_style() cols = mpl.rcParams["axes.prop_cycle"].by_key()["color"] # %% [markdown] diff --git a/docs/examples/uncollapsed_vi.py b/docs/examples/uncollapsed_vi.py index dd93ead97..1fb2ab90e 100644 --- a/docs/examples/uncollapsed_vi.py +++ b/docs/examples/uncollapsed_vi.py @@ -33,9 +33,15 @@ # [classification notebook](classification.py). +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% # Enable Float64 for more stable matrix inversions. -from utils import use_mpl_style from jax import config import jax.numpy as jnp import jax.random as jr @@ -54,7 +60,7 @@ key = jr.key(123) # set the default style for plotting -use_mpl_style() +gpx.plotting.use_style() cols = mpl.rcParams["axes.prop_cycle"].by_key()["color"] diff --git a/docs/examples/utils.py b/docs/examples/utils.py index 0409a37ab..0086fe555 100644 --- a/docs/examples/utils.py +++ b/docs/examples/utils.py @@ -1,8 +1,5 @@ -from pathlib import Path - from matplotlib import transforms from matplotlib.patches import Ellipse -import matplotlib.pyplot as plt import numpy as np @@ -74,8 +71,3 @@ def clean_legend(ax): ax.legend(by_label.values(), by_label.keys()) return ax - -def use_mpl_style(): - style_file = Path(__file__).parent / "gpjax.mplstyle" - plt.style.use(style_file) - diff --git a/docs/examples/xarray_workflow.py b/docs/examples/xarray_workflow.py index 28a3873f4..0801884ec 100644 --- a/docs/examples/xarray_workflow.py +++ b/docs/examples/xarray_workflow.py @@ -39,6 +39,13 @@ # # The module needs the optional extra: `pip install "gpjax[xarray]"`. +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% from jax import config import jax.numpy as jnp @@ -46,7 +53,6 @@ from jaxtyping import install_import_hook import matplotlib.pyplot as plt import numpy as np -from utils import use_mpl_style import xarray as xr config.update("jax_enable_x64", True) @@ -56,7 +62,7 @@ from gpjax.xarray import from_xarray key = jr.key(42) -use_mpl_style() +gpx.plotting.use_style() # %% [markdown] # ## A synthetic temperature field diff --git a/docs/examples/yacht.py b/docs/examples/yacht.py index 2a68826c8..9bda58390 100644 --- a/docs/examples/yacht.py +++ b/docs/examples/yacht.py @@ -25,9 +25,15 @@ # contained in this notebook can be adapted to applied problems concerning datasets # other than the one presented here. +# %% tags=["remove-cell"] +import logging + +# The build host may not have the ledger fonts (Public Sans, Spectral); hide +# matplotlib's font fallback messages. +logging.getLogger("matplotlib.font_manager").setLevel(logging.ERROR) + # %% # Enable Float64 for more stable matrix inversions. -from utils import use_mpl_style from jax import config import jax.numpy as jnp import jax.random as jr @@ -56,7 +62,7 @@ def glue(*args, **kwargs): # set the default style for plotting -use_mpl_style() +gpx.plotting.use_style() cols = mpl.rcParams["axes.prop_cycle"].by_key()["color"] key = jr.key(42) diff --git a/pyproject.toml b/pyproject.toml index 4e46de0f7..70f2eed41 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,6 +72,7 @@ docs = [ "playwright>=1.49", # -- Example notebook dependencies (unchanged) -- "matplotlib>=3.7.1", + "qc-core>=0.0.3", # QuantClimate "Calibrated Ledger" plot style "seaborn>=0.12.2", "networkx>=3.0", "ipython>=8.11.0", @@ -91,6 +92,10 @@ xarray = [ [tool.uv] exclude-newer = "7 days" +# qc-core is QuantClimate's own package, published by PyPI trusted publishing +# from QuantClimate/core, so it skips the cooldown. A new qc-core release can +# then reach the docs on the day it ships. +exclude-newer-package = { qc-core = false } managed = true # PEP 735 dependency group. Must stay here rather than under `[tool.uv]`: diff --git a/uv.lock b/uv.lock index e6a75fde6..e6a2ef55a 100644 --- a/uv.lock +++ b/uv.lock @@ -20,6 +20,9 @@ resolution-markers = [ exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values. exclude-newer-span = "P7D" +[options.exclude-newer-package] +qc-core = false + [[package]] name = "absl-py" version = "2.3.1" @@ -827,6 +830,7 @@ docs = [ { name = "networkx" }, { name = "pandas" }, { name = "playwright" }, + { name = "qc-core" }, { name = "scikit-learn" }, { name = "seaborn" }, { name = "shibuya" }, @@ -896,6 +900,7 @@ requires-dist = [ { name = "pandas", marker = "extra == 'docs'", specifier = ">=1.5.3" }, { name = "paramax", specifier = ">=0.0.5" }, { name = "playwright", marker = "extra == 'docs'", specifier = ">=1.49" }, + { name = "qc-core", marker = "extra == 'docs'", specifier = ">=0.0.3" }, { name = "rich", specifier = ">=13.0.0" }, { name = "scikit-learn", marker = "extra == 'docs'", specifier = ">=1.5.1" }, { name = "scipy", specifier = ">=1.14" }, @@ -2913,6 +2918,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/01/1b/5dbe84eefc86f48473947e2f41711aded97eecef1231f4558f1f02713c12/pyzmq-27.1.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:c9f7f6e13dff2e44a6afeaf2cf54cee5929ad64afaf4d40b50f93c58fc687355", size = 544862, upload-time = "2025-09-08T23:09:56.509Z" }, ] +[[package]] +name = "qc-core" +version = "0.0.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "matplotlib" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ea/98/dd422af8dc6091c7bbb237494dad9a4d6b4e407097a27901b68a0d15c470/qc_core-0.0.3.tar.gz", hash = "sha256:ee9b7d0df3d201ab10923e1fc3f2e4951fa1f687fd62a848ea5e43babde5fcc4", size = 10291, upload-time = "2026-10-04T05:16:32.895Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3b/fb/62f3f511fc1f341dd10fe7e5771e52a43cc8b0509c2e6ef7dd053abf92f7/qc_core-0.0.3-py3-none-any.whl", hash = "sha256:68745e964281914d600eebd580c1defbcfe3e55b8b005aea2c6b4cbaae63d395", size = 11991, upload-time = "2026-10-04T05:16:31.879Z" }, +] + [[package]] name = "readme-renderer" version = "44.0" From 00db220c48c1aabb107068b213093a2f769ac0ca Mon Sep 17 00:00:00 2001 From: Thomas Pinder Date: Sun, 4 Oct 2026 05:25:07 +0000 Subject: [PATCH 3/4] docs: use impulso's oxblood accent scale Switch shibuya's accent_color to crimson and re-tone the whole crimson scale to ledger oxblood, copied from impulso's extra.css. Before, only --accent-9 was pinned over the Radix red ramp, so washes, borders and hovers stayed red, and dark mode used #d07b76 instead of the ledger's #cf6f60. Co-Authored-By: Claude Opus 5.5 --- docs/conf.py | 20 ++----- docs/stylesheets/extra.css | 113 ++++++++++++++++++++++++------------- 2 files changed, 80 insertions(+), 53 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index ef99b4dea..fc7badce4 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -317,21 +317,11 @@ # GitHub Pages used to send. html_extra_path = ["_redirects", "_headers"] html_theme_options = { - # `accent_color` only accepts a radix ramp *name*. shibuya writes the value - # verbatim into `` and its stylesheet carries - # one `[data-accent-color=]` block per radix ramp, each mapping - # `--accent-1..12` onto that ramp. A hex value matches no block, so the whole - # accent ramp goes undefined -- confirmed in a browser: with - # `data-accent-color="#7a2e2a"` both `--accent-9` and the `--sy-c-link` it - # feeds compute to the empty string, the active sidebar entry drops back to - # body-text grey and code blocks lose their tint entirely. - # - # So the named ramp stays, and supplies the derived tints (code-block and - # admonition surfaces, hover states). `red` replaces the previous `crimson` - # because the brand is now #7a2e2a, a true red at hue 3 degrees; crimson is a - # pink-red at hue 348 and its tints read pink against it. The exact brand hex - # is pinned over `--accent-9` in stylesheets/extra.css. - "accent_color": "red", + # `accent_color` only accepts a radix ramp *name*: a hex value matches none of + # shibuya's `[data-accent-color=]` blocks and leaves `--accent-*` + # undefined. As in impulso, it names the token family, and + # stylesheets/extra.css re-tones the crimson scale to ledger oxblood. + "accent_color": "crimson", "color_mode": "auto", # follow the reader's light/dark preference "github_url": "https://github.com/QuantClimate/GPJax", "nav_links": [ diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index f5b83f963..9d5de487d 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -3,49 +3,86 @@ * Everything here is a survivor of the old mkdocs-material `extra.css`; the * ~110 lines that targeted `.md-*` classes, the Material colour variables and * the `permalinks.css` headerlink mask are all Material-only and were dropped - * with the theme. Shibuya's `accent_color` covers the palette that - * `--md-primary-fg-color` used to force. */ + * with the theme. Shibuya's `accent_color`, re-toned below, covers the palette + * that `--md-primary-fg-color` used to force. */ /* -- Brand accent ------------------------------------------------------------ * - * conf.py can only name a radix ramp (`accent_color = "red"`); shibuya has no - * way to take a hex. The ramp supplies every derived tint, and the exact brand - * hex is pinned over the one step that carries the visible accent. - * - * `--accent-9` is that step. shibuya defines `--sy-c-link: var(--accent-9)`, and - * `--sy-c-link` is what colours `.globaltoc > ul a.current`, `.localtoc > ul - * li.active > a` and (via `--yue-c-link-border`) the underline under every prose - * link, so overriding `--accent-9` alone reaches all of them. - * - * Scoped to `html[data-accent-color]` (specificity 0,2,0) to outrank shibuya's - * own `[data-accent-color=red]` block (0,1,0). It is deliberately NOT scoped to - * `html.light` / `html.dark`: shibuya switches colour mode with a class on this - * same element and never re-declares `--accent-9`, so one unscoped declaration - * holds in both modes. */ -html[data-accent-color] { - --gpjax-brand: #7a2e2a; - --accent-9: var(--gpjax-brand); - /* Local alias for the hand-styled pieces below. Note this is *not* a shibuya - variable -- the theme stylesheet never reads `--linkcolor`. */ - --linkcolor: var(--gpjax-brand); + * The same oxblood scale as impulso (../impulso/docs/stylesheets/extra.css), + * so the two sites share one accent. */ +/* Re-tone the theme's Radix "crimson" accent to the ledger's oxblood + (qc_core.plotting.COLORS.oxblood #7a2e2a light, #cf6f60 dark). shibuya + maps every --accent-* token from --crimson-* (conf.py `accent_color`), so + overriding the scale recolors links, active nav, hovers, and focus rings + in one place. Selectors mirror the theme's own; both blocks are needed + because this file loads after the theme, where a lone :root block would + also override the theme's .dark values. Steps follow Radix semantics: + 1-2 backgrounds, 3-5 washes, 6-8 borders, 9-10 solid, 11-12 text. */ +:root, +.light, +.light-theme { + --crimson-1: #fdf9f8; + --crimson-2: #fbf1ef; + --crimson-3: #f4e7e4; + --crimson-4: #eedad5; + --crimson-5: #e7cbc5; + --crimson-6: #ddb8b0; + --crimson-7: #cf9d93; + --crimson-8: #bd7d71; + --crimson-9: #7a2e2a; + --crimson-10: #5f231f; + --crimson-11: #7a2e2a; + --crimson-12: #43201d; + --crimson-a1: rgba(122, 46, 42, 0.03); + --crimson-a2: rgba(122, 46, 42, 0.05); + --crimson-a3: rgba(122, 46, 42, 0.1); + --crimson-a4: rgba(122, 46, 42, 0.15); + --crimson-a5: rgba(122, 46, 42, 0.21); + --crimson-a6: rgba(122, 46, 42, 0.28); + --crimson-a7: rgba(122, 46, 42, 0.38); + --crimson-a8: rgba(122, 46, 42, 0.52); + --crimson-a9: rgba(97, 6, 1, 0.84); + --crimson-a10: rgba(73, 5, 0, 0.88); + --crimson-a11: rgba(97, 6, 1, 0.84); + --crimson-a12: rgba(46, 7, 4, 0.9); + --crimson-contrast: #faf9f7; + --crimson-surface: #f8efeccc; } -/* Dark mode needs a lighter step of the same ramp. The brand hex is built for a - * white page: on shibuya's dark background (#111113) #7a2e2a measures 2.02:1, - * under both the 4.5:1 AA text threshold and the 3:1 threshold for UI. Links and - * the active sidebar entry are effectively unreadable. - * - * #d07b76 is the same colour lifted for a dark ground -- hue 3deg and saturation - * 0.49 are both unchanged, only lightness moves (0.32 -> 0.64), which is how a - * radix-style ramp is meant to be traversed. It measures 6.09:1, past AA and - * close to AAA. A true colour-theory complement (cyan-green, hue 183deg) was - * rejected: it reads as a different brand. - * - * `html.dark[data-accent-color]` is (0,2,1) against the (0,1,1) above, so it - * wins regardless of source order. shibuya always sets the mode class, including - * under `color_mode: "auto"`, so this holds when following the OS preference. */ -html.dark[data-accent-color] { - --gpjax-brand: #d07b76; +.dark, +.dark-theme { + --crimson-1: #1d1412; + --crimson-2: #241815; + --crimson-3: #35201b; + --crimson-4: #422620; + --crimson-5: #4e2c25; + --crimson-6: #5e352c; + --crimson-7: #754138; + --crimson-8: #955347; + --crimson-9: #cf6f60; + --crimson-10: #dd8171; + --crimson-11: #f2a291; + --crimson-12: #fbe1da; + --crimson-a1: rgba(207, 111, 96, 0.03); + --crimson-a2: rgba(207, 111, 96, 0.06); + --crimson-a3: rgba(207, 111, 96, 0.12); + --crimson-a4: rgba(207, 111, 96, 0.17); + --crimson-a5: rgba(207, 111, 96, 0.23); + --crimson-a6: rgba(207, 111, 96, 0.3); + --crimson-a7: rgba(207, 111, 96, 0.42); + --crimson-a8: rgba(207, 111, 96, 0.56); + --crimson-a9: rgba(252, 135, 117, 0.82); + --crimson-a10: rgba(254, 148, 130, 0.87); + --crimson-a11: rgba(255, 171, 153, 0.95); + --crimson-a12: rgba(255, 230, 222, 0.98); + --crimson-contrast: #201b19; + --crimson-surface: #35201b80; +} + +/* Local alias for the hand-styled pieces below. Note this is *not* a shibuya + variable -- the theme stylesheet never reads `--linkcolor`. */ +:root { + --linkcolor: var(--accent-9); } /* -- Notebook cells follow the site's colour mode ---------------------------- From 86cbb8c18d78f4a0cc77fdccf97092c14f361fdd Mon Sep 17 00:00:00 2001 From: Thomas Pinder Date: Sun, 4 Oct 2026 06:36:20 +0000 Subject: [PATCH 4/4] docs: require qc-core 0.0.4 (Noto Sans, brighter blue) qc-core 0.0.4 bundles Noto Sans and moves the second cycle colour to a brighter blue (QuantClimate/core#3). Co-Authored-By: Claude Opus 5.5 --- pyproject.toml | 2 +- uv.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 70f2eed41..588106caa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -72,7 +72,7 @@ docs = [ "playwright>=1.49", # -- Example notebook dependencies (unchanged) -- "matplotlib>=3.7.1", - "qc-core>=0.0.3", # QuantClimate "Calibrated Ledger" plot style + "qc-core>=0.0.4", # QuantClimate "Calibrated Ledger" plot style "seaborn>=0.12.2", "networkx>=3.0", "ipython>=8.11.0", diff --git a/uv.lock b/uv.lock index e6a2ef55a..f62a5a867 100644 --- a/uv.lock +++ b/uv.lock @@ -900,7 +900,7 @@ requires-dist = [ { name = "pandas", marker = "extra == 'docs'", specifier = ">=1.5.3" }, { name = "paramax", specifier = ">=0.0.5" }, { name = "playwright", marker = "extra == 'docs'", specifier = ">=1.49" }, - { name = "qc-core", marker = "extra == 'docs'", specifier = ">=0.0.3" }, + { name = "qc-core", marker = "extra == 'docs'", specifier = ">=0.0.4" }, { name = "rich", specifier = ">=13.0.0" }, { name = "scikit-learn", marker = "extra == 'docs'", specifier = ">=1.5.1" }, { name = "scipy", specifier = ">=1.14" }, @@ -2920,14 +2920,14 @@ wheels = [ [[package]] name = "qc-core" -version = "0.0.3" +version = "0.0.4" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "matplotlib" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ea/98/dd422af8dc6091c7bbb237494dad9a4d6b4e407097a27901b68a0d15c470/qc_core-0.0.3.tar.gz", hash = "sha256:ee9b7d0df3d201ab10923e1fc3f2e4951fa1f687fd62a848ea5e43babde5fcc4", size = 10291, upload-time = "2026-10-04T05:16:32.895Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a3/f9/455c4568d27a9103dce16d68b39034759837ac37315fced082590abc3e43/qc_core-0.0.4.tar.gz", hash = "sha256:57ca906269fee22a8bc63e10ab24e45a531fc925da910142bd0a786ead637871", size = 985836, upload-time = "2026-10-04T06:34:47.79Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/fb/62f3f511fc1f341dd10fe7e5771e52a43cc8b0509c2e6ef7dd053abf92f7/qc_core-0.0.3-py3-none-any.whl", hash = "sha256:68745e964281914d600eebd580c1defbcfe3e55b8b005aea2c6b4cbaae63d395", size = 11991, upload-time = "2026-10-04T05:16:31.879Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9d/5061a16bbe69e1a121ed12f3f9024571535cb04025fc6ac6bff964c96622/qc_core-0.0.4-py3-none-any.whl", hash = "sha256:1dab55e0cf797a5c88b2e2799009aee7d7306723562dc89879cbe14366f7c022", size = 985536, upload-time = "2026-10-04T06:34:46.535Z" }, ] [[package]]