From 602989307d37037893f0a14e35daba0689dcd6d9 Mon Sep 17 00:00:00 2001 From: "Michael J. Williams" Date: Wed, 14 Oct 2026 13:42:09 +0200 Subject: [PATCH 1/3] MAINT(examples): organize tracked tutorials under examples Move tracked scripts and their narratives together under examples/. Preserve local outputs and pre-existing untracked examples without adding them. Apply the import and formatting fixes required by Ruff to the moved CuPy script. --- .../adaptive_mutation_steps}/README.rst | 0 .../adaptive_mutation_steps.py | 0 .../basic_examples}/README.rst | 0 .../basic_examples}/gaussian.py | 0 .../basic_examples}/rosenbrock.py | 0 .../cupy_examples/run_minipcn_cupy.py | 26 +++++++++---------- {ns-smc => examples/ns-smc}/.gitignore | 0 {ns-smc => examples/ns-smc}/README.rst | 0 {ns-smc => examples/ns-smc}/example.py | 0 {ns-smc => examples/ns-smc}/gaussian.rst | 0 .../ns-smc}/run_spike_and_slab.py | 0 .../ns-smc}/spike_and_slab.rst | 0 .../particle_filtering}/README.md | 0 .../particle_filtering}/particle_filter.py | 0 .../persistent_sampling}/README.md | 0 .../persistent_sampling}/rosenbrock.py | 0 .../preconditioning}/README.rst | 0 .../preconditioning}/flow_preconditioning.py | 0 .../waste_free_smc}/README.rst | 0 .../waste_free_smc}/run_waste_free_smc.py | 0 20 files changed, 13 insertions(+), 13 deletions(-) rename {adaptive_mutation_steps => examples/adaptive_mutation_steps}/README.rst (100%) rename {adaptive_mutation_steps => examples/adaptive_mutation_steps}/adaptive_mutation_steps.py (100%) rename {basic_examples => examples/basic_examples}/README.rst (100%) rename {basic_examples => examples/basic_examples}/gaussian.py (100%) rename {basic_examples => examples/basic_examples}/rosenbrock.py (100%) rename {gpu_sampling => examples/gpu_sampling}/cupy_examples/run_minipcn_cupy.py (82%) rename {ns-smc => examples/ns-smc}/.gitignore (100%) rename {ns-smc => examples/ns-smc}/README.rst (100%) rename {ns-smc => examples/ns-smc}/example.py (100%) rename {ns-smc => examples/ns-smc}/gaussian.rst (100%) rename {ns-smc => examples/ns-smc}/run_spike_and_slab.py (100%) rename {ns-smc => examples/ns-smc}/spike_and_slab.rst (100%) rename {particle_filtering => examples/particle_filtering}/README.md (100%) rename {particle_filtering => examples/particle_filtering}/particle_filter.py (100%) rename {persistent_sampling => examples/persistent_sampling}/README.md (100%) rename {persistent_sampling => examples/persistent_sampling}/rosenbrock.py (100%) rename {preconditioning => examples/preconditioning}/README.rst (100%) rename {preconditioning => examples/preconditioning}/flow_preconditioning.py (100%) rename {waste_free_smc => examples/waste_free_smc}/README.rst (100%) rename {waste_free_smc => examples/waste_free_smc}/run_waste_free_smc.py (100%) diff --git a/adaptive_mutation_steps/README.rst b/examples/adaptive_mutation_steps/README.rst similarity index 100% rename from adaptive_mutation_steps/README.rst rename to examples/adaptive_mutation_steps/README.rst diff --git a/adaptive_mutation_steps/adaptive_mutation_steps.py b/examples/adaptive_mutation_steps/adaptive_mutation_steps.py similarity index 100% rename from adaptive_mutation_steps/adaptive_mutation_steps.py rename to examples/adaptive_mutation_steps/adaptive_mutation_steps.py diff --git a/basic_examples/README.rst b/examples/basic_examples/README.rst similarity index 100% rename from basic_examples/README.rst rename to examples/basic_examples/README.rst diff --git a/basic_examples/gaussian.py b/examples/basic_examples/gaussian.py similarity index 100% rename from basic_examples/gaussian.py rename to examples/basic_examples/gaussian.py diff --git a/basic_examples/rosenbrock.py b/examples/basic_examples/rosenbrock.py similarity index 100% rename from basic_examples/rosenbrock.py rename to examples/basic_examples/rosenbrock.py diff --git a/gpu_sampling/cupy_examples/run_minipcn_cupy.py b/examples/gpu_sampling/cupy_examples/run_minipcn_cupy.py similarity index 82% rename from gpu_sampling/cupy_examples/run_minipcn_cupy.py rename to examples/gpu_sampling/cupy_examples/run_minipcn_cupy.py index ede43d8..881f830 100644 --- a/gpu_sampling/cupy_examples/run_minipcn_cupy.py +++ b/examples/gpu_sampling/cupy_examples/run_minipcn_cupy.py @@ -1,11 +1,9 @@ -import matplotlib.pyplot as plt import cupy as cp - +import matplotlib.pyplot as plt +from orng import ArrayRNG from sequince import ParticleState from sequince.logging_utils import configure_logging -from sequince.posterior import draw_additional_samples from sequince.prebuilt_samplers import make_standard_bayesian_smc -from orng import ArrayRNG # Number of dimensions in the multivariate normal model dims = 4 @@ -20,10 +18,9 @@ def log_likelihood(particles: cp.ndarray, context) -> cp.ndarray: """Log-likelihood for a multivariate standard normal observation.""" - return ( - -0.5 * cp.sum(cp.square(particles - mu) / sigma**2, axis=-1) - - (dims / 2) * cp.log(2 * cp.pi * sigma**2) - ) + return -0.5 * cp.sum(cp.square(particles - mu) / sigma**2, axis=-1) - ( + dims / 2 + ) * cp.log(2 * cp.pi * sigma**2) def log_prior(particles: cp.ndarray, context) -> cp.ndarray: @@ -37,7 +34,7 @@ def log_prior(particles: cp.ndarray, context) -> cp.ndarray: def main() -> None: # Create a CuPy RNG - rng = ArrayRNG(backend='cupy', seed=42) + rng = ArrayRNG(backend="cupy", seed=42) # Enable logging configure_logging() @@ -45,12 +42,15 @@ def main() -> None: # Initialize particles from the prior lower_bound, upper_bound = bounds n_particles = 1000 - initial_particles = rng.uniform(low=lower_bound, high=upper_bound, size=(n_particles, dims)) - initial_log_weights = cp.full((n_particles,), -cp.log(cp.array(n_particles))) + initial_particles = rng.uniform( + low=lower_bound, high=upper_bound, size=(n_particles, dims) + ) + initial_log_weights = cp.full( + (n_particles,), -cp.log(cp.array(n_particles)) + ) initial_state = ParticleState( - particles=initial_particles, - log_weights=initial_log_weights + particles=initial_particles, log_weights=initial_log_weights ) # Use the prebuilt SMC with MiniPCN mutation diff --git a/ns-smc/.gitignore b/examples/ns-smc/.gitignore similarity index 100% rename from ns-smc/.gitignore rename to examples/ns-smc/.gitignore diff --git a/ns-smc/README.rst b/examples/ns-smc/README.rst similarity index 100% rename from ns-smc/README.rst rename to examples/ns-smc/README.rst diff --git a/ns-smc/example.py b/examples/ns-smc/example.py similarity index 100% rename from ns-smc/example.py rename to examples/ns-smc/example.py diff --git a/ns-smc/gaussian.rst b/examples/ns-smc/gaussian.rst similarity index 100% rename from ns-smc/gaussian.rst rename to examples/ns-smc/gaussian.rst diff --git a/ns-smc/run_spike_and_slab.py b/examples/ns-smc/run_spike_and_slab.py similarity index 100% rename from ns-smc/run_spike_and_slab.py rename to examples/ns-smc/run_spike_and_slab.py diff --git a/ns-smc/spike_and_slab.rst b/examples/ns-smc/spike_and_slab.rst similarity index 100% rename from ns-smc/spike_and_slab.rst rename to examples/ns-smc/spike_and_slab.rst diff --git a/particle_filtering/README.md b/examples/particle_filtering/README.md similarity index 100% rename from particle_filtering/README.md rename to examples/particle_filtering/README.md diff --git a/particle_filtering/particle_filter.py b/examples/particle_filtering/particle_filter.py similarity index 100% rename from particle_filtering/particle_filter.py rename to examples/particle_filtering/particle_filter.py diff --git a/persistent_sampling/README.md b/examples/persistent_sampling/README.md similarity index 100% rename from persistent_sampling/README.md rename to examples/persistent_sampling/README.md diff --git a/persistent_sampling/rosenbrock.py b/examples/persistent_sampling/rosenbrock.py similarity index 100% rename from persistent_sampling/rosenbrock.py rename to examples/persistent_sampling/rosenbrock.py diff --git a/preconditioning/README.rst b/examples/preconditioning/README.rst similarity index 100% rename from preconditioning/README.rst rename to examples/preconditioning/README.rst diff --git a/preconditioning/flow_preconditioning.py b/examples/preconditioning/flow_preconditioning.py similarity index 100% rename from preconditioning/flow_preconditioning.py rename to examples/preconditioning/flow_preconditioning.py diff --git a/waste_free_smc/README.rst b/examples/waste_free_smc/README.rst similarity index 100% rename from waste_free_smc/README.rst rename to examples/waste_free_smc/README.rst diff --git a/waste_free_smc/run_waste_free_smc.py b/examples/waste_free_smc/run_waste_free_smc.py similarity index 100% rename from waste_free_smc/run_waste_free_smc.py rename to examples/waste_free_smc/run_waste_free_smc.py From dcccc92e3ca7a0aa61a447c1d16e4ada835627be Mon Sep 17 00:00:00 2001 From: "Michael J. Williams" Date: Wed, 14 Oct 2026 13:42:54 +0200 Subject: [PATCH 2/3] ENH(docs): build tutorials from validated example outputs Add Pixi environments, an explicit execution manifest, isolated example runs, and checksummed output bundles with source and dependency provenance. Resolve Sequince main once per execution batch and archive that batch's lock for rebuilds. Organize Sphinx tutorials from Gaussian and Rosenbrock through advanced methods, with inline figures and script downloads. Keep numerical summaries in bundles. Add local Make targets and regression tests for adapters and provenance checks. Preserve sampling settings and seeds. Optional documentation exports add no sampler RNG calls. Full numerical runs remain unvalidated for the new adapters; worker and page-layout checks use mocks or explicitly marked plot fixtures. --- .gitignore | 4 + docs/Makefile | 66 + docs/conf.py | 25 + docs/examples.json | 192 + docs/index.rst | 49 + docs/requirements.txt | 3 + examples/adaptive_mutation_steps/README.rst | 22 + examples/basic_examples/README.rst | 263 +- examples/basic_examples/gaussian.rst | 216 + examples/basic_examples/rosenbrock.rst | 104 + examples/particle_filtering/README.md | 124 - examples/particle_filtering/README.rst | 159 + .../particle_filtering/particle_filter.py | 45 +- examples/persistent_sampling/README.md | 51 - examples/persistent_sampling/README.rst | 77 + examples/persistent_sampling/rosenbrock.py | 19 + examples/preconditioning/README.rst | 25 +- .../preconditioning/flow_preconditioning.py | 41 +- examples/waste_free_smc/README.rst | 17 +- examples/waste_free_smc/run_waste_free_smc.py | 37 +- pixi.lock | 3888 +++++++++++++++++ pyproject.toml | 55 + scripts/docs_pipeline.py | 555 +++ tests/test_docs_pipeline.py | 292 ++ tests/test_flow_summary.py | 43 + tests/test_particle_filter_docs.py | 74 + 26 files changed, 5987 insertions(+), 459 deletions(-) create mode 100644 docs/Makefile create mode 100644 docs/conf.py create mode 100644 docs/examples.json create mode 100644 docs/index.rst create mode 100644 docs/requirements.txt create mode 100644 examples/basic_examples/gaussian.rst create mode 100644 examples/basic_examples/rosenbrock.rst delete mode 100644 examples/particle_filtering/README.md create mode 100644 examples/particle_filtering/README.rst delete mode 100644 examples/persistent_sampling/README.md create mode 100644 examples/persistent_sampling/README.rst create mode 100644 pixi.lock create mode 100644 scripts/docs_pipeline.py create mode 100644 tests/test_docs_pipeline.py create mode 100644 tests/test_flow_summary.py create mode 100644 tests/test_particle_filter_docs.py diff --git a/.gitignore b/.gitignore index 0f77cd0..96d806a 100644 --- a/.gitignore +++ b/.gitignore @@ -225,3 +225,7 @@ outdir*/ # Checkpoint files *.pickle *.npz + +# Documentation execution, bundles and generated Sphinx sources +build-docs/ +docs/_generated/ diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..e13c724 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,66 @@ +.DEFAULT_GOAL := help + +DOCS_DIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) +REPO_ROOT := $(abspath $(DOCS_DIR)/..) + +PIXI ?= pixi +EXAMPLE ?= gaussian +ENV ?= default + +.PHONY: help setup test lint format-check check hooks example examples refresh docs docs-only validate + +help: + @printf '%s\n' \ + 'make setup Refresh Sequince main and install environments' \ + 'make refresh Resolve current Sequince main without sampling' \ + 'make check Run tests, lint and formatting checks' \ + 'make test Run pipeline tests' \ + 'make lint Run Ruff lint checks' \ + 'make format-check Check Python formatting' \ + 'make hooks Run all pre-commit hooks (may edit files)' \ + 'make example EXAMPLE=gaussian [ENV=default]' \ + ' Run one example in the selected environment' \ + 'make examples Run all documentation examples in their environments' \ + 'make docs Reuse valid outputs or run examples, then build docs' \ + 'make docs-only Build docs using existing validated outputs' \ + 'make validate Run checks, all examples, then build docs' + +refresh: + cd "$(REPO_ROOT)" && $(PIXI) update sequince + +setup: refresh + cd "$(REPO_ROOT)" && $(PIXI) install --locked -e default -e docs -e check + +test: + cd "$(REPO_ROOT)" && $(PIXI) run --locked -e check test + +lint: + cd "$(REPO_ROOT)" && $(PIXI) run --locked -e check lint + +format-check: + cd "$(REPO_ROOT)" && $(PIXI) run --locked -e check format-check + +check: test lint format-check + +hooks: + cd "$(REPO_ROOT)" && $(PIXI) run --locked -e check pre-commit run --all-files + +example: + cd "$(REPO_ROOT)" && $(PIXI) run --locked -e $(ENV) example $(EXAMPLE) + +examples: + cd "$(REPO_ROOT)" && $(PIXI) run --locked examples + +docs: refresh + @if ! (cd "$(REPO_ROOT)" && $(PIXI) run --locked -e docs validate-outputs); then \ + $(MAKE) -C "$(DOCS_DIR)" examples || exit $$?; \ + fi + $(MAKE) -C "$(DOCS_DIR)" docs-only + +docs-only: + cd "$(REPO_ROOT)" && $(PIXI) run --locked -e docs docs + +# Keep execution before Sphinx even when invoked with make -j. +validate: check + $(MAKE) -C "$(DOCS_DIR)" examples + $(MAKE) -C "$(DOCS_DIR)" docs-only diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..0e893bc --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,25 @@ +"""Sphinx configuration; generated outputs are mandatory.""" + +import os +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts")) +from docs_pipeline import validate # noqa: E402 + +validate() +project = "Sequince examples" +author = "Sequince contributors" +extensions = ["sphinx.ext.intersphinx"] +html_theme = "sphinx_book_theme" +html_theme_options = { + "repository_url": "https://github.com/sequince-dev/sequince-examples", + "use_repository_button": True, +} +exclude_patterns = ["_build"] +intersphinx_mapping = { + "sequince": ( + "https://sequince.readthedocs.io/en/latest/", + os.environ.get("SEQUINCE_INVENTORY"), + ), +} diff --git a/docs/examples.json b/docs/examples.json new file mode 100644 index 0000000..008cb6b --- /dev/null +++ b/docs/examples.json @@ -0,0 +1,192 @@ +{ + "schema": 1, + "examples": [ + { + "id": "gaussian", + "title": "Gaussian posterior", + "script": "examples/basic_examples/gaussian.py", + "working_directory": "examples/basic_examples", + "environment": "default", + "timeout": 600, + "narrative": "examples/basic_examples/gaussian.rst", + "parameters": [ + "n_dimensions", + "n_particles", + "lower_bound", + "upper_bound", + "mean", + "sigma", + "seed" + ], + "figures": { + "corner.png": "Weighted marginal and joint posterior distributions for the Gaussian target.", + "history.png": "Adaptive temperature and accumulated log evidence at each SMC stage." + }, + "required_outputs": [ + "corner.png", + "history.png", + "posterior.npz", + "result.h5" + ] + }, + { + "id": "rosenbrock", + "title": "Rosenbrock posterior", + "script": "examples/basic_examples/rosenbrock.py", + "working_directory": "examples/basic_examples", + "environment": "default", + "timeout": 600, + "narrative": "examples/basic_examples/rosenbrock.rst", + "parameters": [ + "n_dimensions", + "n_particles", + "lower_bound", + "upper_bound", + "a", + "b", + "seed" + ], + "figures": { + "corner.png": "Weighted posterior samples along the curved Rosenbrock valley.", + "history.png": "Temperature and log evidence for the unnormalized Rosenbrock likelihood." + }, + "required_outputs": [ + "corner.png", + "history.png", + "posterior.npz", + "result.h5" + ] + }, + { + "id": "adaptive-mutation", + "title": "Adaptive mutation steps", + "script": "examples/adaptive_mutation_steps/adaptive_mutation_steps.py", + "working_directory": "examples/adaptive_mutation_steps", + "environment": "default", + "timeout": 1200, + "narrative": "examples/adaptive_mutation_steps/README.rst", + "parameters": [ + "n_dimensions", + "n_particles", + "lower_bound", + "upper_bound", + "rosenbrock_scale", + "seed", + "target_steps", + "initial_steps", + "min_steps", + "max_steps" + ], + "figures": { + "adaptive_mutation_history.png": "Executed mutation budgets, acceptance rates, and expected accepted moves across SMC stages.", + "corner.png": "Weighted marginal and joint distributions for the eight-dimensional Rosenbrock posterior.", + "history.png": "Temperature and log evidence through the adaptive mutation run." + }, + "required_outputs": [ + "adaptive_mutation_history.png", + "corner.png", + "history.png", + "posterior.npz", + "result.h5" + ] + }, + { + "id": "flow-preconditioning", + "title": "Flow preconditioning", + "script": "examples/preconditioning/flow_preconditioning.py", + "working_directory": "examples/preconditioning", + "environment": "flow", + "timeout": 2400, + "narrative": "examples/preconditioning/README.rst", + "parameters": [ + "dims", + "n_particles", + "rosenbrock_scale", + "seed" + ], + "dependency_features": [ + "flow" + ], + "figures": { + "rosenbrock_corner.png": "Posterior marginals and joint distributions for the four-dimensional Rosenbrock target sampled with flow preconditioning." + }, + "required_outputs": [ + "rosenbrock_corner.png", + "summary.json" + ] + }, + { + "id": "persistent-sampling", + "title": "Persistent sampling", + "script": "examples/persistent_sampling/rosenbrock.py", + "working_directory": "examples/persistent_sampling", + "environment": "default", + "timeout": 1200, + "narrative": "examples/persistent_sampling/README.rst", + "parameters": [ + "dimensions", + "n_particles", + "target_ess", + "target_total_ess", + "mutation_steps", + "flow", + "bound", + "rosenbrock_scale", + "seed" + ], + "figures": { + "corner.png": "Posterior marginals and joint distributions from the persistent Rosenbrock run, using the script defaults." + }, + "required_outputs": [ + "corner.png", + "summary.json" + ] + }, + { + "id": "waste-free", + "title": "Waste-free SMC", + "script": "examples/waste_free_smc/run_waste_free_smc.py", + "working_directory": "examples/waste_free_smc", + "environment": "default", + "timeout": 1200, + "narrative": "examples/waste_free_smc/README.rst", + "parameters": [ + "dims", + "bounds", + "correlation", + "n_chains", + "chain_length", + "seed" + ], + "figures": { + "waste_free_history.png": "Adaptive tempering schedules for standard and waste-free SMC. Retained sample counts do not measure the number of independent samples." + }, + "required_outputs": [ + "waste_free_history.png", + "summary.json" + ] + }, + { + "id": "particle-filtering", + "title": "Particle filtering", + "script": "examples/particle_filtering/particle_filter.py", + "working_directory": "examples/particle_filtering", + "environment": "default", + "timeout": 600, + "narrative": "examples/particle_filtering/README.rst", + "parameters": [ + "particles", + "observations", + "seed", + "ess_threshold" + ], + "figures": { + "particle_filter.png": "Bootstrap and locally optimal particle filters compared with the exact Kalman filter, including effective sample size before resampling." + }, + "required_outputs": [ + "particle_filter.png", + "summary.json" + ] + } + ] +} diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..f80dd6a --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,49 @@ +Sequince examples +================= + +Start with the basic examples to learn how to define a prior and likelihood, +initialize particles, run Sequential Monte Carlo (SMC), and interpret weighted +posterior samples and evidence estimates. Work through the Gaussian target +first, then try the curved Rosenbrock posterior. + +.. toctree:: + :maxdepth: 1 + :caption: Getting started + + _generated/examples/basic_examples/gaussian + _generated/examples/basic_examples/rosenbrock + +Once you are comfortable with that workflow, explore ways to adapt mutation, +change the coordinates used by the sampler, and reuse particle populations. +Each example has its own tutorial page with explanations and script source. +Example plots appear directly on that page. + +.. toctree:: + :maxdepth: 1 + :caption: Sampling methods + + _generated/examples/adaptive_mutation_steps/README + _generated/examples/preconditioning/README + _generated/examples/persistent_sampling/README + _generated/examples/waste_free_smc/README + +For sequential observations, the particle-filtering example compares bootstrap +and observation-conditioned proposals with a Kalman-filter reference. + +.. toctree:: + :maxdepth: 1 + :caption: Sequential data + + _generated/examples/particle_filtering/README + +The basic, adaptive mutation, flow preconditioning, persistent sampling, and +waste-free SMC, and particle-filtering tutorials include plots alongside their +explanations. Their +run metadata identifies the source revision, parameters, seed, and pinned +dependencies. + +For background and the full interface reference, see Sequince's +`user guide `_ +and `API reference `_. +The examples use :external:py:class:`sequince.ParticleState` to represent the +weighted particle population. diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..085b8f5 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,3 @@ +# Match the direct Sphinx dependencies in the Pixi docs environment. +Sphinx==8.2.3 +sphinx-book-theme==1.1.4 diff --git a/examples/adaptive_mutation_steps/README.rst b/examples/adaptive_mutation_steps/README.rst index 8ff6aca..139f47f 100644 --- a/examples/adaptive_mutation_steps/README.rst +++ b/examples/adaptive_mutation_steps/README.rst @@ -117,6 +117,28 @@ Try lowering ``max_steps`` to see the budget saturate, or increasing ``min_steps <= initial_steps <= max_steps`` and ``n_dimensions >= 2``. Retain weights when loading ``posterior.npz`` for further analysis. +Example results +~~~~~~~~~~~~~~~ + +:download:`Download script `. + +.. figure:: /_generated/results/adaptive-mutation/adaptive_mutation_history.png + :alt: Executed mutation budgets, acceptance rates, and expected accepted moves across SMC stages. + + Executed mutation budgets, acceptance rates, and expected accepted moves across SMC stages. + +.. figure:: /_generated/results/adaptive-mutation/corner.png + :alt: Weighted marginal and joint distributions for the eight-dimensional Rosenbrock posterior. + + Weighted marginal and joint distributions for the eight-dimensional Rosenbrock posterior. + +.. figure:: /_generated/results/adaptive-mutation/history.png + :alt: Temperature and log evidence through the adaptive mutation run. + + Temperature and log evidence through the adaptive mutation run. + +:download:`Run provenance `. + The full sampler setup is shown below: .. literalinclude:: adaptive_mutation_steps.py diff --git a/examples/basic_examples/README.rst b/examples/basic_examples/README.rst index 491235a..f80aa5a 100644 --- a/examples/basic_examples/README.rst +++ b/examples/basic_examples/README.rst @@ -1,263 +1,6 @@ Basic examples ============== -These examples walk through Bayesian inference with Sequince's standard -Sequential Monte Carlo (SMC) sampler. Start with ``gaussian.py`` to learn the -workflow, then run ``rosenbrock.py`` to explore a curved posterior and -customize mutation and resampling. Each script -contains its own prior, likelihood, and sampler setup. Edit the parameters near -the top of the script to experiment. - -Setup and running ------------------ - -Use Python 3.11 or later and install Sequince with the optional plotting and -HDF5 dependencies:: - - python -m pip install 'sequince[corner,hdf5]' - -For installation options, including using a development checkout, see -"Getting started" in the main Sequince documentation. - -Run the scripts from this directory:: - - python gaussian.py - python rosenbrock.py - -The scripts write to ``outdir/gaussian`` and ``outdir/rosenbrock`` within this -directory. -Running a script again replaces its previous outputs. - -1. Define the target distribution ---------------------------------- - -Sequince needs functions that evaluate the log prior and log likelihood. Both -receive a batch of particles with shape ``(n_particles, n_dimensions)`` and a -``StepContext``, and return one value per particle, with shape -``(n_particles,)``. Sequince passes the context when evaluating the target. -Use ``context.shared`` to read static model parameters supplied to ``smc.run`` -through ``shared_context``. For example, both scripts define the prior as: - -.. code-block:: python - - def log_prior(particles, context): - lower_bound = context.shared["lower_bound"] - upper_bound = context.shared["upper_bound"] - inside = np.all( - (particles >= lower_bound) & (particles <= upper_bound), axis=-1 - ) - log_density = -particles.shape[1] * np.log(upper_bound - lower_bound) - return np.where(inside, log_density, -np.inf) - -The Gaussian likelihood reads ``mean`` and ``sigma`` from the same shared -mapping; the Rosenbrock likelihood reads its coefficients ``a`` and ``b``. -This makes the target functions independent of module-level model settings. - -For a parameter vector :math:`\boldsymbol{\theta}` with :math:`d` dimensions, -the examples use independent uniform priors between -5 and 5: - -.. math:: - - \log p(\boldsymbol{\theta}) = - \begin{cases} - -d \log 10, & \boldsymbol{\theta} \in [-5, 5]^d, \\ - -\infty, & \text{otherwise}. - \end{cases} - -Including the prior normalization is necessary for a correct evidence estimate. - -In ``gaussian.py``, the likelihood is a standard multivariate Gaussian, -including its normalization constant (the defaults are ``mean=0`` and -``sigma=1``). The resulting posterior is a standard -Gaussian restricted to the prior box. Its mean is zero, and its covariance is -very close to the identity for these wide bounds. The script also computes the -analytic evidence, including the Gaussian probability mass inside the box. - -2. Initialize particles from the prior --------------------------------------- - -Create a seeded NumPy generator, draw particles from the prior, and put them in -a ``ParticleState``: - -.. code-block:: python - - rng = np.random.default_rng(seed) - initial_state = ParticleState( - particles=rng.uniform( - lower_bound, upper_bound, size=(n_particles, n_dimensions) - ), - ) - -When ``log_weights`` is omitted, ``ParticleState`` supplies equal normalized -weights, each with log weight :math:`\log w_i = -\log N`, where :math:`N` is -the number of particles (``n_particles``). This is appropriate here -because the particles are drawn from the prior, the initial target distribution. -There is no need to set the weights explicitly. Supply ``log_weights`` when -constructing a state that already has nonuniform importance weights. - -Keep using the same generator for the sampler. Changing the initialization to a -different distribution requires accounting for that proposal; see the main -API reference for ``make_standard_bayesian_smc`` and its ``log_proposal`` option. - -3. Configure the sampler ------------------------- - -Use the prebuilt helper to assemble the standard Bayesian SMC workflow: - -.. code-block:: python - - smc, posterior, metadata = make_standard_bayesian_smc( - log_prior=log_prior, - log_likelihood=log_likelihood, - target_ess_ratio=0.8, - mutation="minipcn", - mutation_n_steps=16, - ) - -The sampler moves through tempered distributions - -.. math:: - - \pi_\beta(\boldsymbol{\theta}) \propto - p(\boldsymbol{\theta})\,L(\boldsymbol{\theta})^\beta, - \qquad 0 \leq \beta \leq 1, - -where :math:`p` is the prior and :math:`L` is the likelihood. It starts at the -prior with :math:`\beta=0` and finishes at the posterior with :math:`\beta=1`. -Adaptive tempering chooses increments using an effective sample size (ESS) -criterion. Resampling concentrates particles in -regions of higher weight, and mutation moves help the population explore each -intermediate distribution. - -The helper returns the SMC runner, a tempered posterior object, and initial -metadata. These scripts use the runner and metadata directly; the posterior -object is also available for further sampling with ``draw_additional_samples``. -See "Configuring the runner" in the main User guide and the helper's API -reference for resampling thresholds, mutation choices, and other options. - -4. Run SMC ----------- - -Pass the initial population, generator, and helper metadata to the runner: - -.. code-block:: python - - result = smc.run( - initial_state, - rng=rng, - shared_context={ - "lower_bound": lower_bound, - "upper_bound": upper_bound, - "mean": mean, - "sigma": sigma, - }, - metadata=metadata, - ) - -The runner makes ``shared_context`` available as ``context.shared`` in both -target functions. The Gaussian example supplies the bounds, mean, and standard -deviation shown above; the Rosenbrock example supplies bounds and its two -coefficients. Treat this mapping as read-only during the run. - -The helper's ``metadata`` is available as ``context.metadata`` and carries -evolving information such as the tempering parameter :math:`\beta` -(``context.metadata["beta"]``). Sequince's tempered posterior applies -:math:`\beta` to the likelihood; the likelihood functions return their ordinary -log likelihood, without applying :math:`\beta` themselves. -``context.t`` identifies the current SMC iteration. The configured sampler -stops when it reaches the posterior. Logging shows progress and the running -evidence estimate. - -See "Meta data & shared context" in the main Getting started guide and -"Working with particle state" in the User guide for these interfaces. - -5. Inspect and save the result ------------------------------- - -``result.final_state`` contains the final particles, their weights, and the log -evidence estimate. ``result.weighted_mean()`` and -``result.weighted_covariance()`` compute posterior summaries using those weights. -For the Gaussian, compare the summaries and evidence with the known values. -Monte Carlo estimates fluctuate; agreement improves with adequate particles and -mixing, rather than being exact for a single run. - -Each script saves: - -* ``corner.png``: marginal and joint posterior plots. -* ``history.png``: the tempering parameter and log evidence over iterations. -* ``posterior.npz``: particles, normalized weights, and the log evidence estimate. -* ``result.h5``: the SMC result saved using ``result.save_to_file``. - -The arrays can be loaded with ``numpy.load``. Retain the weights when analyzing -the particles. See the main API reference for ``SMCResult`` for posterior sample -extraction, diagnostics, plotting, and result persistence. - -6. Explore a curved posterior ------------------------------- - -``rosenbrock.py`` follows the same workflow with a Rosenbrock log likelihood: - -.. math:: - - \log L(\boldsymbol{\theta}) = -\sum_{j=1}^{d-1} - \left[b\left(\theta_{j+1} - \theta_j^2\right)^2 - + \left(a - \theta_j\right)^2\right]. - -The likelihood reads :math:`a` and :math:`b` from ``context.shared``. With the -default :math:`a=1` and :math:`b=100`, the two-dimensional likelihood concentrates -around a narrow curved valley near :math:`\theta_2 = \theta_1^2`, with a maximum -at :math:`(\theta_1, \theta_2) = (1, 1)`. Inspect the corner plot for this shape -and the history plot for how tempering progresses. This likelihood is -unnormalized: the reported evidence uses exactly the convention above, and the -script provides no analytic reference value. - -Configure mutation and resampling -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The Rosenbrock example also demonstrates more options in the prebuilt helper: - -.. code-block:: python - - smc, posterior, metadata = make_standard_bayesian_smc( - log_prior=log_prior, - log_likelihood=log_likelihood, - weighting_criterion="cess", - target_ess_ratio=0.8, - resampling="stratified", - always_resample=False, - resampling_ess_threshold=0.5, - mutation="differential-evolution", - mutation_n_steps=32, - mutation_kwargs={"gamma_one_probability": 0.1}, - ) - -``mutation="differential-evolution"`` proposes moves using scaled differences -between other particles in the ensemble. ``mutation_kwargs`` passes options to -the mutation implementation: here ``gamma_one_probability=0.1`` gives each -proposal a 10% chance of using a unit scale instead of the usual -dimension-dependent scale. Each mutation stage takes 32 steps. - -``resampling="stratified"`` changes the resampling scheme from the Gaussian -example's default systematic scheme. With ``always_resample=False``, resampling -occurs when the population ESS falls below :math:`0.5N`. By default, -``resample_at_end=True`` also resamples at the final posterior, even if that -threshold has not been crossed. When omitted, ``always_mutate`` follows the -resolved ``always_resample`` value, so mutation runs only on iterations that -resample here. Set ``always_mutate=True`` to run mutation every iteration. - -Use ``weighting_criterion="cess"`` with conditional resampling. Conditional ESS -(CESS) measures the effect of a proposed temperature increment given the -current weighted population; ``target_ess_ratio=0.8`` controls that increment. -The separate ``resampling_ess_threshold=0.5`` controls when to resample using the -population ESS. Sequince's ordinary ``"ess"`` tempering criterion requires -resampling every iteration, so simply setting ``always_resample=False`` with -that criterion is invalid. - -See the main API reference for ``make_standard_bayesian_smc`` and -``DifferentialEvolutionMutation`` for the available options. - -Try changing ``n_particles``, ``mutation_n_steps``, and the seed to assess the -stability of the posterior summaries. Increasing ``target_ess_ratio`` usually -produces smaller tempering increments and more iterations. Increasing particle -count or mutation steps increases computational cost. Compare several runs -before judging whether a setting explores the posterior adequately. +Start with `Gaussian posterior `_ to learn the SMC workflow, +then follow `Rosenbrock posterior `_ to explore a curved target +and conditional resampling. Both tutorials include their plots and script source. diff --git a/examples/basic_examples/gaussian.rst b/examples/basic_examples/gaussian.rst new file mode 100644 index 0000000..b941a7b --- /dev/null +++ b/examples/basic_examples/gaussian.rst @@ -0,0 +1,216 @@ +Gaussian posterior +================== + +These examples walk through Bayesian inference with Sequince's standard +Sequential Monte Carlo (SMC) sampler. Start with ``gaussian.py`` to learn the +workflow, then run ``rosenbrock.py`` to explore a curved posterior and +customize mutation and resampling. Each script +contains its own prior, likelihood, and sampler setup. Edit the parameters near +the top of the script to experiment. + +Setup and running +----------------- + +Use Python 3.11 or later and install Sequince with the optional plotting and +HDF5 dependencies:: + + python -m pip install 'sequince[corner,hdf5]' + +For installation options, including using a development checkout, see +"Getting started" in the main Sequince documentation. + +Run the script from this directory:: + + python gaussian.py + +The script writes to ``outdir/gaussian`` within this directory. +Running a script again replaces its previous outputs. + +1. Define the target distribution +--------------------------------- + +Sequince needs functions that evaluate the log prior and log likelihood. Both +receive a batch of particles with shape ``(n_particles, n_dimensions)`` and a +``StepContext``, and return one value per particle, with shape +``(n_particles,)``. Sequince passes the context when evaluating the target. +Use ``context.shared`` to read static model parameters supplied to ``smc.run`` +through ``shared_context``. For example, both scripts define the prior as: + +.. code-block:: python + + def log_prior(particles, context): + lower_bound = context.shared["lower_bound"] + upper_bound = context.shared["upper_bound"] + inside = np.all( + (particles >= lower_bound) & (particles <= upper_bound), axis=-1 + ) + log_density = -particles.shape[1] * np.log(upper_bound - lower_bound) + return np.where(inside, log_density, -np.inf) + +The Gaussian likelihood reads ``mean`` and ``sigma`` from the same shared +mapping; the Rosenbrock likelihood reads its coefficients ``a`` and ``b``. +This makes the target functions independent of module-level model settings. + +For a parameter vector :math:`\boldsymbol{\theta}` with :math:`d` dimensions, +the examples use independent uniform priors between -5 and 5: + +.. math:: + + \log p(\boldsymbol{\theta}) = + \begin{cases} + -d \log 10, & \boldsymbol{\theta} \in [-5, 5]^d, \\ + -\infty, & \text{otherwise}. + \end{cases} + +Including the prior normalization is necessary for a correct evidence estimate. + +In ``gaussian.py``, the likelihood is a standard multivariate Gaussian, +including its normalization constant (the defaults are ``mean=0`` and +``sigma=1``). The resulting posterior is a standard +Gaussian restricted to the prior box. Its mean is zero, and its covariance is +very close to the identity for these wide bounds. The script also computes the +analytic evidence, including the Gaussian probability mass inside the box. + +2. Initialize particles from the prior +-------------------------------------- + +Create a seeded NumPy generator, draw particles from the prior, and put them in +a ``ParticleState``: + +.. code-block:: python + + rng = np.random.default_rng(seed) + initial_state = ParticleState( + particles=rng.uniform( + lower_bound, upper_bound, size=(n_particles, n_dimensions) + ), + ) + +When ``log_weights`` is omitted, ``ParticleState`` supplies equal normalized +weights, each with log weight :math:`\log w_i = -\log N`, where :math:`N` is +the number of particles (``n_particles``). This is appropriate here +because the particles are drawn from the prior, the initial target distribution. +There is no need to set the weights explicitly. Supply ``log_weights`` when +constructing a state that already has nonuniform importance weights. + +Keep using the same generator for the sampler. Changing the initialization to a +different distribution requires accounting for that proposal; see the main +API reference for ``make_standard_bayesian_smc`` and its ``log_proposal`` option. + +3. Configure the sampler +------------------------ + +Use the prebuilt helper to assemble the standard Bayesian SMC workflow: + +.. code-block:: python + + smc, posterior, metadata = make_standard_bayesian_smc( + log_prior=log_prior, + log_likelihood=log_likelihood, + target_ess_ratio=0.8, + mutation="minipcn", + mutation_n_steps=16, + ) + +The sampler moves through tempered distributions + +.. math:: + + \pi_\beta(\boldsymbol{\theta}) \propto + p(\boldsymbol{\theta})\,L(\boldsymbol{\theta})^\beta, + \qquad 0 \leq \beta \leq 1, + +where :math:`p` is the prior and :math:`L` is the likelihood. It starts at the +prior with :math:`\beta=0` and finishes at the posterior with :math:`\beta=1`. +Adaptive tempering chooses increments using an effective sample size (ESS) +criterion. Resampling concentrates particles in +regions of higher weight, and mutation moves help the population explore each +intermediate distribution. + +The helper returns the SMC runner, a tempered posterior object, and initial +metadata. These scripts use the runner and metadata directly; the posterior +object is also available for further sampling with ``draw_additional_samples``. +See "Configuring the runner" in the main User guide and the helper's API +reference for resampling thresholds, mutation choices, and other options. + +4. Run SMC +---------- + +Pass the initial population, generator, and helper metadata to the runner: + +.. code-block:: python + + result = smc.run( + initial_state, + rng=rng, + shared_context={ + "lower_bound": lower_bound, + "upper_bound": upper_bound, + "mean": mean, + "sigma": sigma, + }, + metadata=metadata, + ) + +The runner makes ``shared_context`` available as ``context.shared`` in both +target functions. The Gaussian example supplies the bounds, mean, and standard +deviation shown above; the Rosenbrock example supplies bounds and its two +coefficients. Treat this mapping as read-only during the run. + +The helper's ``metadata`` is available as ``context.metadata`` and carries +evolving information such as the tempering parameter :math:`\beta` +(``context.metadata["beta"]``). Sequince's tempered posterior applies +:math:`\beta` to the likelihood; the likelihood functions return their ordinary +log likelihood, without applying :math:`\beta` themselves. +``context.t`` identifies the current SMC iteration. The configured sampler +stops when it reaches the posterior. Logging shows progress and the running +evidence estimate. + +See "Meta data & shared context" in the main Getting started guide and +"Working with particle state" in the User guide for these interfaces. + +5. Inspect and save the result +------------------------------ + +``result.final_state`` contains the final particles, their weights, and the log +evidence estimate. ``result.weighted_mean()`` and +``result.weighted_covariance()`` compute posterior summaries using those weights. +For the Gaussian, compare the summaries and evidence with the known values. +Monte Carlo estimates fluctuate; agreement improves with adequate particles and +mixing, rather than being exact for a single run. + +Each script saves: + +* ``corner.png``: marginal and joint posterior plots. +* ``history.png``: the tempering parameter and log evidence over iterations. +* ``posterior.npz``: particles, normalized weights, and the log evidence estimate. +* ``result.h5``: the SMC result saved using ``result.save_to_file``. + +The arrays can be loaded with ``numpy.load``. Retain the weights when analyzing +the particles. See the main API reference for ``SMCResult`` for posterior sample +extraction, diagnostics, plotting, and result persistence. + +Gaussian results +~~~~~~~~~~~~~~~~ + +:download:`Download script `. + +.. figure:: /_generated/results/gaussian/corner.png + :alt: Weighted marginal and joint posterior distributions for the Gaussian target. + + Weighted marginal and joint posterior distributions for the Gaussian target. + +.. figure:: /_generated/results/gaussian/history.png + :alt: Adaptive temperature and accumulated log evidence at each SMC stage. + + Adaptive temperature and accumulated log evidence at each SMC stage. + +:download:`Run provenance `. + +.. literalinclude:: gaussian.py + :language: python + :linenos: + + +Continue with :doc:`rosenbrock` to explore a curved posterior and conditional +resampling. diff --git a/examples/basic_examples/rosenbrock.rst b/examples/basic_examples/rosenbrock.rst new file mode 100644 index 0000000..f034fda --- /dev/null +++ b/examples/basic_examples/rosenbrock.rst @@ -0,0 +1,104 @@ +Rosenbrock posterior +==================== + +Start with :doc:`gaussian` for the prior, particle initialization, and standard +SMC workflow. This example keeps the same uniform prior and uses a curved +likelihood with differential-evolution mutation and conditional resampling. + +Run from this directory with the same dependencies as the Gaussian example:: + + python rosenbrock.py + +Outputs are written to ``outdir/rosenbrock``. + +Define the likelihood +--------------------- + + +``rosenbrock.py`` follows the same workflow with a Rosenbrock log likelihood: + +.. math:: + + \log L(\boldsymbol{\theta}) = -\sum_{j=1}^{d-1} + \left[b\left(\theta_{j+1} - \theta_j^2\right)^2 + + \left(a - \theta_j\right)^2\right]. + +The likelihood reads :math:`a` and :math:`b` from ``context.shared``. With the +default :math:`a=1` and :math:`b=100`, the two-dimensional likelihood concentrates +around a narrow curved valley near :math:`\theta_2 = \theta_1^2`, with a maximum +at :math:`(\theta_1, \theta_2) = (1, 1)`. Inspect the corner plot for this shape +and the history plot for how tempering progresses. This likelihood is +unnormalized: the reported evidence uses exactly the convention above, and the +script provides no analytic reference value. + +Configure mutation and resampling +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The Rosenbrock example also demonstrates more options in the prebuilt helper: + +.. code-block:: python + + smc, posterior, metadata = make_standard_bayesian_smc( + log_prior=log_prior, + log_likelihood=log_likelihood, + weighting_criterion="cess", + target_ess_ratio=0.8, + resampling="stratified", + always_resample=False, + resampling_ess_threshold=0.5, + mutation="differential-evolution", + mutation_n_steps=32, + mutation_kwargs={"gamma_one_probability": 0.1}, + ) + +``mutation="differential-evolution"`` proposes moves using scaled differences +between other particles in the ensemble. ``mutation_kwargs`` passes options to +the mutation implementation: here ``gamma_one_probability=0.1`` gives each +proposal a 10% chance of using a unit scale instead of the usual +dimension-dependent scale. Each mutation stage takes 32 steps. + +``resampling="stratified"`` changes the resampling scheme from the Gaussian +example's default systematic scheme. With ``always_resample=False``, resampling +occurs when the population ESS falls below :math:`0.5N`. By default, +``resample_at_end=True`` also resamples at the final posterior, even if that +threshold has not been crossed. When omitted, ``always_mutate`` follows the +resolved ``always_resample`` value, so mutation runs only on iterations that +resample here. Set ``always_mutate=True`` to run mutation every iteration. + +Use ``weighting_criterion="cess"`` with conditional resampling. Conditional ESS +(CESS) measures the effect of a proposed temperature increment given the +current weighted population; ``target_ess_ratio=0.8`` controls that increment. +The separate ``resampling_ess_threshold=0.5`` controls when to resample using the +population ESS. Sequince's ordinary ``"ess"`` tempering criterion requires +resampling every iteration, so simply setting ``always_resample=False`` with +that criterion is invalid. + +See the main API reference for ``make_standard_bayesian_smc`` and +``DifferentialEvolutionMutation`` for the available options. + +Try changing ``n_particles``, ``mutation_n_steps``, and the seed to assess the +stability of the posterior summaries. Increasing ``target_ess_ratio`` usually +produces smaller tempering increments and more iterations. Increasing particle +count or mutation steps increases computational cost. Compare several runs +before judging whether a setting explores the posterior adequately. + +Rosenbrock results +~~~~~~~~~~~~~~~~~~ + +:download:`Download script `. + +.. figure:: /_generated/results/rosenbrock/corner.png + :alt: Weighted posterior samples along the curved Rosenbrock valley. + + Weighted posterior samples along the curved Rosenbrock valley. + +.. figure:: /_generated/results/rosenbrock/history.png + :alt: Temperature and log evidence for the unnormalized Rosenbrock likelihood. + + Temperature and log evidence for the unnormalized Rosenbrock likelihood. + +:download:`Run provenance `. + +.. literalinclude:: rosenbrock.py + :language: python + :linenos: diff --git a/examples/particle_filtering/README.md b/examples/particle_filtering/README.md deleted file mode 100644 index 80c39da..0000000 --- a/examples/particle_filtering/README.md +++ /dev/null @@ -1,124 +0,0 @@ -# Particle filtering with Sequince - -This example shows how to implement particle filters in Sequince. - -It compares two particle filters for a scalar linear-Gaussian -state-space model: - -- a bootstrap filter, which proposes from the transition model; -- a locally optimal filter, which proposes conditional on the current - observation. - -Both filters process the same simulated observations. Their filtering means, -variances, and log evidence are compared with the exact Kalman-filter result. - -## Running the example - -```bash -python particle_filter.py -``` - -The script prints accuracy, effective-sample-size, and evidence diagnostics, -then writes `particle_filter.png` in this directory. - -Useful options include: - -```bash -python particle_filter.py --particles 10000 --observations 30 -python particle_filter.py --ess-threshold 0.5 -python particle_filter.py --output /tmp/particle-filter.png -``` - -By default both filters resample after every observation. Passing -`--ess-threshold FRACTION` instead resamples only when the effective sample -size divided by the particle count falls below that fraction. - -## Model - -The example uses - -```text -x_0 ~ Normal(0, sigma_0^2) -x_t | x_(t-1) ~ Normal(a x_(t-1), sigma_x^2) -y_t | x_t ~ Normal(x_t, sigma_y^2). -``` - -The model is linear and Gaussian, so a Kalman filter supplies exact filtering -moments and log evidence for comparison. - -## Why the filters use different steps - -For a proposal distribution `q`, particle filtering applies the incremental -weight - -```text -p(y_t | x_t) p(x_t | x_(t-1)) / q(x_t | x_(t-1), y_t). -``` - -In the bootstrap filter, `q` is the transition distribution itself. The -transition and proposal terms therefore cancel, leaving only the observation -likelihood. A regular propagation step followed by `IncrementalWeighting` is -enough: after propagation, the weighting step only needs the new particles. - -The locally optimal proposal is not the transition distribution, so its -weights also need the transition-to-proposal correction. That correction -depends on both the parent particles and their proposed children. A standalone -propagation step replaces the particles before the next step runs, so an -ordinary `IncrementalWeighting` step would no longer have both values. -`ImportanceTransition` instead samples the children and computes the complete -correction while parent and child particles are simultaneously available. - -The remaining steps are identical. Both filters record the weighted state and -then resample; only proposal generation and its associated weighting differ. - -## Bootstrap filter - -The bootstrap filter is assembled from this Sequince pipeline: - -```text -PropagationStep --> IncrementalWeighting --> RecordFilteringState --> SystematicResampling -``` - -`PropagationStep` samples from `p(x_t | x_(t-1))`, and -`IncrementalWeighting` applies `log p(y_t | x_t)`. The local -`RecordFilteringState` step saves a detached copy of the weighted population -before resampling. These pre-resampling populations give the filtering -moments; `SMCResult.states` contains the post-resampling populations passed to -the next iteration. - -## Locally optimal proposal filter - -The second filter samples from -`q(x_t | x_(t-1), y_t) = p(x_t | x_(t-1), y_t)`. The proposal parameters are - -```text -V_q = (1 / sigma_x^2 + 1 / sigma_y^2)^-1 -m_q = V_q (a x_(t-1) / sigma_x^2 + y_t / sigma_y^2). -``` - -Its pipeline is - -```text -ImportanceTransition(OptimalProposalKernel) --> RecordFilteringState --> SystematicResampling -``` - -The proposal kernel sees parent and child particles together and returns the -complete importance increment - -```text -log p(x_t | x_(t-1)) - log q(x_t | x_(t-1), y_t) -+ log p(y_t | x_t). -``` - -`ImportanceTransition` applies that increment, normalizes the weights, and -updates the log evidence. Keeping the proposal and its correction together -avoids storing an extra parent population in each state. - -The lower panel of the output figure compares effective sample size before -resampling. Conditioning on the observation generally gives the locally -optimal proposal a higher ESS than the bootstrap proposal. diff --git a/examples/particle_filtering/README.rst b/examples/particle_filtering/README.rst new file mode 100644 index 0000000..c925436 --- /dev/null +++ b/examples/particle_filtering/README.rst @@ -0,0 +1,159 @@ +Particle filtering with Sequince +================================ + +This example shows how to implement particle filters in Sequince. + +It compares two particle filters for a scalar linear-Gaussian +state-space model: + +- a bootstrap filter, which proposes from the transition model; +- a locally optimal filter, which proposes conditional on the current + observation. + +Both filters process the same simulated observations. Their filtering means, +variances, and log evidence are compared with the exact Kalman-filter result. + +Running the example +------------------- + +.. code-block:: bash + + python particle_filter.py + + +The script prints accuracy, effective-sample-size, and evidence diagnostics, +then writes ``particle_filter.png`` in this directory. + +Useful options include: + +.. code-block:: bash + + python particle_filter.py --particles 10000 --observations 30 + python particle_filter.py --ess-threshold 0.5 + python particle_filter.py --output /tmp/particle-filter.png + + +By default both filters resample after every observation. Passing +``--ess-threshold FRACTION`` instead resamples only when the effective sample +size divided by the particle count falls below that fraction. + +Model +----- + +The example uses + +.. code-block:: text + + x_0 ~ Normal(0, sigma_0^2) + x_t | x_(t-1) ~ Normal(a x_(t-1), sigma_x^2) + y_t | x_t ~ Normal(x_t, sigma_y^2). + + +The model is linear and Gaussian, so a Kalman filter supplies exact filtering +moments and log evidence for comparison. + +Why the filters use different steps +----------------------------------- + +For a proposal distribution ``q``, particle filtering applies the incremental +weight + +.. code-block:: text + + p(y_t | x_t) p(x_t | x_(t-1)) / q(x_t | x_(t-1), y_t). + + +In the bootstrap filter, ``q`` is the transition distribution itself. The +transition and proposal terms therefore cancel, leaving only the observation +likelihood. A regular propagation step followed by ``IncrementalWeighting`` is +enough: after propagation, the weighting step only needs the new particles. + +The locally optimal proposal is not the transition distribution, so its +weights also need the transition-to-proposal correction. That correction +depends on both the parent particles and their proposed children. A standalone +propagation step replaces the particles before the next step runs, so an +ordinary ``IncrementalWeighting`` step would no longer have both values. +``ImportanceTransition`` instead samples the children and computes the complete +correction while parent and child particles are simultaneously available. + +The remaining steps are identical. Both filters record the weighted state and +then resample; only proposal generation and its associated weighting differ. + +Bootstrap filter +---------------- + +The bootstrap filter is assembled from this Sequince pipeline: + +.. code-block:: text + + PropagationStep + -> IncrementalWeighting + -> RecordFilteringState + -> SystematicResampling + + +``PropagationStep`` samples from ``p(x_t | x_(t-1))``, and +``IncrementalWeighting`` applies ``log p(y_t | x_t)``. The local +``RecordFilteringState`` step saves a detached copy of the weighted population +before resampling. These pre-resampling populations give the filtering +moments; ``SMCResult.states`` contains the post-resampling populations passed to +the next iteration. + +Locally optimal proposal filter +------------------------------- + +The second filter samples from +``q(x_t | x_(t-1), y_t) = p(x_t | x_(t-1), y_t)``. The proposal parameters are + +.. code-block:: text + + V_q = (1 / sigma_x^2 + 1 / sigma_y^2)^-1 + m_q = V_q (a x_(t-1) / sigma_x^2 + y_t / sigma_y^2). + + +Its pipeline is + +.. code-block:: text + + ImportanceTransition(OptimalProposalKernel) + -> RecordFilteringState + -> SystematicResampling + + +The proposal kernel sees parent and child particles together and returns the +complete importance increment + +.. code-block:: text + + log p(x_t | x_(t-1)) - log q(x_t | x_(t-1), y_t) + + log p(y_t | x_t). + + +``ImportanceTransition`` applies that increment, normalizes the weights, and +updates the log evidence. Keeping the proposal and its correction together +avoids storing an extra parent population in each state. + +The lower panel of the output figure compares effective sample size before +resampling. Conditioning on the observation generally gives the locally +optimal proposal a higher ESS than the bootstrap proposal. + +Example plot +------------ + +.. figure:: /_generated/results/particle-filtering/particle_filter.png + :alt: Bootstrap and locally optimal particle filters compared with the exact Kalman filter, including effective sample size before resampling. + + Bootstrap and locally optimal particle filters compared with the exact + Kalman filter. The lower panel shows effective sample size before resampling; + read it alongside filtering accuracy and the evidence diagnostics. + +:download:`Run provenance `. + +Complete script +--------------- + +:download:`Download the script `. + +.. literalinclude:: particle_filter.py + :language: python + :linenos: diff --git a/examples/particle_filtering/particle_filter.py b/examples/particle_filtering/particle_filter.py index b1cb970..ca4fd3b 100644 --- a/examples/particle_filtering/particle_filter.py +++ b/examples/particle_filtering/particle_filter.py @@ -8,6 +8,7 @@ from __future__ import annotations import argparse +import json from dataclasses import dataclass from pathlib import Path from typing import TYPE_CHECKING @@ -26,6 +27,9 @@ from orng import RandomGenerator +documentation_output: Path | None = None + + @dataclass(frozen=True) class LinearGaussianModel: """Parameters of the scalar state-space model.""" @@ -553,7 +557,8 @@ def plot_comparison(comparison: ProposalComparison, output: Path) -> None: plt.close(figure) -def main() -> None: +def build_parser() -> argparse.ArgumentParser: + """Build the CLI parser used for execution and documentation metadata.""" parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--particles", type=int, default=1_000) parser.add_argument("--observations", type=int, default=20) @@ -569,7 +574,11 @@ def main() -> None: type=Path, default=Path(__file__).with_name("particle_filter.png"), ) - args = parser.parse_args() + return parser + + +def main() -> None: + args = build_parser().parse_args() comparison = run_comparison( n_observations=args.observations, n_particles=args.particles, @@ -601,6 +610,38 @@ def main() -> None: f"{comparison.bootstrap_log_evidence:.4f} / " f"{comparison.kalman_log_evidence:.4f}" ) + if documentation_output is not None: + args.output = documentation_output / "particle_filter.png" + summary = { + "mean_rmse": { + "optimal": rmse( + comparison.proposal_means, comparison.kalman_means + ), + "bootstrap": rmse( + comparison.bootstrap_means, comparison.kalman_means + ), + }, + "variance_rmse": { + "optimal": rmse( + comparison.proposal_variances, comparison.kalman_variances + ), + "bootstrap": rmse( + comparison.bootstrap_variances, comparison.kalman_variances + ), + }, + "mean_ess_before_resampling": { + "optimal": float(np.mean(comparison.proposal_ess)), + "bootstrap": float(np.mean(comparison.bootstrap_ess)), + }, + "log_evidence": { + "optimal": comparison.proposal_log_evidence, + "bootstrap": comparison.bootstrap_log_evidence, + "kalman": comparison.kalman_log_evidence, + }, + } + (documentation_output / "summary.json").write_text( + json.dumps(summary, allow_nan=False) + "\n" + ) plot_comparison(comparison, args.output) print(f"wrote {args.output}") diff --git a/examples/persistent_sampling/README.md b/examples/persistent_sampling/README.md deleted file mode 100644 index 5beaac4..0000000 --- a/examples/persistent_sampling/README.md +++ /dev/null @@ -1,51 +0,0 @@ -# Persistent sampling - -This example demonstrates how to use persistent sampling in `sequince` to -sample an n-dimensional Rosenbrock target. - -Persistent sampling is a variation of SMC proposed by [Karamanis 2025]. - -Run the example from the repository root: - -```bash -python persistent_sampling/rosenbrock.py -``` - -For a larger run: - -```bash -python persistent_sampling/rosenbrock.py \ - --dimensions 16 \ - --n-particles 1024 \ - --target-ess 2048 \ - --target-total-ess 8192 \ - --mutation-steps 32 -``` - -The script prints diagnostics from the final weighted persistent state, -including its archive size, effective sample size, weighted mean, best -Rosenbrock potential, and log evidence. Unless `--no-plot` is supplied, it also -saves a weighted corner plot as `persistent_sampling/rosenbrock_d.png`. -Use `--output PATH` to choose a different location. - -Run the following for all available options: - -```bash -python persistent_sampling/rosenbrock.py --help -``` - -## Preconditioning - -This example also shows how to use the normalizing-flow-based preconditioning -described in [Karamanis 2022] and [Karamanis 2025]. - -The `--flow` option enables learned normalizing-flow preconditioning: - -```bash -python persistent_sampling/rosenbrock.py --dimensions 6 --flow -``` - -The flow option requires `sequince-density` and its flow dependencies. - -[Karamanis 2022]: https://arxiv.org/abs/2207.05660 -[Karamanis 2025]: https://arxiv.org/abs/2407.20722 diff --git a/examples/persistent_sampling/README.rst b/examples/persistent_sampling/README.rst new file mode 100644 index 0000000..b70a8c2 --- /dev/null +++ b/examples/persistent_sampling/README.rst @@ -0,0 +1,77 @@ +Persistent sampling +=================== + +This example demonstrates how to use persistent sampling in ``sequince`` to +sample an n-dimensional Rosenbrock target. + +Persistent sampling is a variation of SMC proposed by `Karamanis 2025`_. + +Run the example from the repository root: + +.. code-block:: bash + + python persistent_sampling/rosenbrock.py + + +For a larger run: + +.. code-block:: bash + + python persistent_sampling/rosenbrock.py \ + --dimensions 16 \ + --n-particles 1024 \ + --target-ess 2048 \ + --target-total-ess 8192 \ + --mutation-steps 32 + + +The script prints diagnostics from the final weighted persistent state, +including its archive size, effective sample size, weighted mean, best +Rosenbrock potential, and log evidence. Unless ``--no-plot`` is supplied, it also +saves a weighted corner plot as ``examples/persistent_sampling/rosenbrock_d.png``. +Use ``--output PATH`` to choose a different location. + +Run the following for all available options: + +.. code-block:: bash + + python persistent_sampling/rosenbrock.py --help + + +Preconditioning +--------------- + +First read :doc:`../preconditioning/README` for an explicit flow-transform +setup. This example also shows how to use the normalizing-flow-based preconditioning +described in `Karamanis 2022`_ and `Karamanis 2025`_. + +The ``--flow`` option enables learned normalizing-flow preconditioning: + +.. code-block:: bash + + python persistent_sampling/rosenbrock.py --dimensions 6 --flow + + +The flow option requires ``sequince-density`` and its flow dependencies. + +.. _Karamanis 2022: https://arxiv.org/abs/2207.05660 +.. _Karamanis 2025: https://arxiv.org/abs/2407.20722 + +Example plots +------------- + +.. figure:: /_generated/results/persistent-sampling/corner.png + :alt: Posterior marginals and joint distributions from the persistent Rosenbrock run, using the script defaults. + + Posterior marginals and joint distributions from the persistent Rosenbrock run, using the script defaults. + +:download:`Run provenance `. + +Complete script +--------------- + +:download:`Download the script `. + +.. literalinclude:: rosenbrock.py + :language: python + :linenos: diff --git a/examples/persistent_sampling/rosenbrock.py b/examples/persistent_sampling/rosenbrock.py index 83746cc..1fc6938 100644 --- a/examples/persistent_sampling/rosenbrock.py +++ b/examples/persistent_sampling/rosenbrock.py @@ -13,6 +13,7 @@ from __future__ import annotations import argparse +import json from pathlib import Path import numpy as np @@ -26,6 +27,8 @@ from sequince.steps import StepContext from sequince.transforms import BoundedToUnboundedTransform +documentation_output: Path | None = None + def rosenbrock(particles: np.ndarray) -> np.ndarray: """Return the Rosenbrock potential for each particle.""" @@ -213,6 +216,22 @@ def main() -> None: print(f"best particle: {np.array2string(best, precision=3)}") print(f"final log evidence: {float(final_state.log_evidence):+.6f}") + if documentation_output is not None: + summary = { + "log_evidence": float(final_state.log_evidence), + "weighted_mean": weighted_mean.tolist(), + "archive_size": int(final_state.n_particles), + "effective_sample_size": float( + final_state.effective_sample_size() + ), + "weighted_mean_potential": float(weighted_mean_potential), + } + (documentation_output / "summary.json").write_text( + json.dumps(summary, allow_nan=False) + "\n" + ) + if args.output is None: + args.output = documentation_output / "corner.png" + if not args.no_plot: output = args.output or Path(__file__).with_name( f"rosenbrock_{args.dimensions}d.png" diff --git a/examples/preconditioning/README.rst b/examples/preconditioning/README.rst index c01fdb4..c5293b1 100644 --- a/examples/preconditioning/README.rst +++ b/examples/preconditioning/README.rst @@ -10,10 +10,6 @@ kernel can explore more efficiently. particle transform. The flow must implement the ``sequince.flows.NormalizingFlow`` protocol: -.. autoclass:: sequince.flows.NormalizingFlow - :members: - :undoc-members: - Construct the transform by passing it a compatible flow and, optionally, the keyword arguments used to fit that flow: @@ -33,11 +29,11 @@ The resulting object can be used anywhere a particle transform is accepted by Fitting the flow ---------------- -The fitting of the transform is handle by the mutation step. The frequency +The fitting of the transform is handled by the mutation step. The frequency of fitting can be controlled by the ``transform_refit`` argument. Current options are: -- ``"always"``: fit the every time the mutation step is called (default) +- ``"always"``: fit the flow every time the mutation step is called (default) - ``"once"``: fit the first time the mutation step is called, then reuse - ``"per-context"``: fit once per unique context, then reuse for that context @@ -48,10 +44,13 @@ Prebuilt flows -------------- ``sequince-density`` provides various normalizing flows that can be used for -preconditioning via the ``zuko` and `flowjax` packages. See the +preconditioning via the ``zuko`` and ``flowjax`` packages. See the ``sequince_density.flows.zuko`` and ``sequince_density.flows.flowjax`` modules for more information. +Continue with :doc:`../persistent_sampling/README` to combine this approach +with persistent sampling. + Example ------- @@ -59,6 +58,18 @@ The following example uses a Zuko masked autoregressive flow to precondition a four-dimensional Rosenbrock target. It requires ``sequince-density`` with its Zuko backend and PyTorch support. +Example plots +------------- + +.. figure:: /_generated/results/flow-preconditioning/rosenbrock_corner.png + :alt: Posterior marginals and joint distributions for the four-dimensional Rosenbrock target sampled with flow preconditioning. + + Posterior marginals and joint distributions for the four-dimensional Rosenbrock target sampled with flow preconditioning. + +:download:`Run provenance `. + +:download:`Download the script `. + .. literalinclude:: flow_preconditioning.py :language: python :linenos: diff --git a/examples/preconditioning/flow_preconditioning.py b/examples/preconditioning/flow_preconditioning.py index 74501f5..0ff1b38 100644 --- a/examples/preconditioning/flow_preconditioning.py +++ b/examples/preconditioning/flow_preconditioning.py @@ -6,19 +6,30 @@ from __future__ import annotations +import json import logging from pathlib import Path +import numpy as np import torch from orng import RandomGenerator +from sequince.array_api import to_numpy from sequince.callbacks import IntermediateParticlesPlot, ParticleTransformPlot from sequince.log import configure_logging from sequince.plot import plot_corner from sequince.prebuilt_samplers import make_standard_bayesian_smc +from sequince.result import SMCResult from sequince.state import ParticleState from sequince.transforms import AffineTransform, FlowTransform from sequince_density.flows.zuko import ZukoFlow +seed = 1234 +n_particles = 1000 +dims = 4 +rosenbrock_scale = 1.0 +outdir = Path("./") +documentation_output: Path | None = None + def rosenbrock(x: torch.Tensor) -> torch.Tensor: return torch.sum( @@ -43,19 +54,31 @@ def log_likelihood(particles: torch.Tensor, context) -> torch.Tensor | float: return values +def _documentation_summary(result: SMCResult) -> dict[str, object]: + """Summarize the weighted empirical measure on the host for JSON output.""" + state = result.final_state + particles = to_numpy(state.particles) + weights = to_numpy(state.weights) + mean = np.sum(particles * weights[:, None], axis=0) / weights.sum() + centered = particles - mean + # The pinned Sequince revision uses a NumPy-only transpose signature in + # weighted_covariance. Host arrays keep this documentation step portable. + covariance = (centered * weights[:, None]).T @ centered / weights.sum() + return { + "log_evidence": float(state.log_evidence), + "weighted_mean": mean.tolist(), + "weighted_population_covariance": covariance.tolist(), + "n_particles": len(particles), + } + + def main() -> None: logger = configure_logging(level=logging.INFO) - seed = 1234 - rng = RandomGenerator(backend="torch", seed=seed) - n_particles = 1000 - dims = 4 - rosenbrock_scale = 1.0 bounds = torch.tensor([[-5.0, 5.0]]).repeat(dims, 1) labels = [f"x{i + 1}" for i in range(dims)] - outdir = Path("./") # Define the normalizing flow to use for preconditioning. # We use a Masked Autoregressive Flow (MAF) with 2 transforms and 2 hidden @@ -119,6 +142,12 @@ def main() -> None: shared_context=shared_context, ) + if documentation_output is not None: + summary = _documentation_summary(result) + (documentation_output / "summary.json").write_text( + json.dumps(summary, allow_nan=False) + "\n" + ) + # Compute the log evidence and its standard deviation from the SMC result. log_evidence = result.log_evidence() log_evidence_std = result.log_evidence_error() diff --git a/examples/waste_free_smc/README.rst b/examples/waste_free_smc/README.rst index c8d84d4..29ad57c 100644 --- a/examples/waste_free_smc/README.rst +++ b/examples/waste_free_smc/README.rst @@ -1,7 +1,7 @@ Waste-free sequential Monte Carlo -================================ +================================= -`run_waste_free_smc.py `_ demonstrates waste-free +:download:`run_waste_free_smc.py ` demonstrates waste-free sequential Monte Carlo (SMC), as described by `Dau & Chopin`_, alongside a standard resample-move SMC run. Both sample a four-dimensional correlated Gaussian likelihood with a uniform prior on ``[-10, 10]^4``. @@ -63,6 +63,19 @@ zero and marginal variances should approach one. The evidence reference is ``-4 * log(20)`` (approximately -11.983), neglecting the tiny Gaussian mass outside the prior box. Both densities include their normalization constants. +Example plots +------------- + +.. figure:: /_generated/results/waste-free/waste_free_history.png + :alt: Adaptive tempering schedules for standard and waste-free SMC. Retained sample counts do not measure the number of independent samples. + + Adaptive tempering schedules for standard and waste-free SMC. Retained sample counts do not measure the number of independent samples. + +:download:`Run provenance `. + +Complete script +--------------- + The target densities and complete sampler setup are shown below: .. literalinclude:: run_waste_free_smc.py diff --git a/examples/waste_free_smc/run_waste_free_smc.py b/examples/waste_free_smc/run_waste_free_smc.py index f5edd81..1bd9e84 100644 --- a/examples/waste_free_smc/run_waste_free_smc.py +++ b/examples/waste_free_smc/run_waste_free_smc.py @@ -1,5 +1,8 @@ """Waste-free SMC for a correlated Gaussian likelihood and uniform prior.""" +import json +from pathlib import Path + import matplotlib.pyplot as plt import numpy as np from sequince import ParticleState, SequentialMonteCarlo, TemperedPosterior @@ -16,6 +19,14 @@ MetropolisHastingsMutation, ) +dims = 4 +bounds = (-10.0, 10.0) +correlation = 0.8 +n_chains = 100 +chain_length = 100 # Includes the resampled starting state. +seed = 123 +documentation_output: Path | None = None + def log_likelihood(particles: np.ndarray, context: StepContext) -> np.ndarray: """Return normalized zero-mean Gaussian log densities.""" @@ -34,17 +45,12 @@ def log_prior(particles: np.ndarray, context: StepContext) -> np.ndarray: def main() -> None: logger = configure_logging() - dims = 4 - bounds = (-10.0, 10.0) - correlation = 0.8 indices = np.arange(dims) covariance = correlation ** np.abs(indices[:, None] - indices[None, :]) cholesky = np.linalg.cholesky(covariance) log_norm = ( -0.5 * dims * np.log(2 * np.pi) - np.log(np.diag(cholesky)).sum() ) - n_chains = 100 - chain_length = 100 # Includes the resampled starting state. posterior = TemperedPosterior(log_prior, log_likelihood) # Truncation at +/-10 has negligible effect on the evidence reference. reference_log_evidence = -dims * np.log(bounds[1] - bounds[0]) @@ -53,6 +59,8 @@ def main() -> None: comparison_rows: list[str] = [] posterior_counts: dict[str, int] = {} + summaries: dict[str, dict] = {} + for name in ("Standard", "Waste-free"): mutation = MetropolisHastingsMutation( posterior, @@ -69,7 +77,7 @@ def main() -> None: moves = [MultinomialResampling(always_resample=True), mutation] logger.info("%s SMC: %d particles", name, n_particles) - rng = np.random.default_rng(123) + rng = np.random.default_rng(seed) initial = ParticleState(rng.uniform(*bounds, size=(n_particles, dims))) smc = SequentialMonteCarlo( steps=[ @@ -104,6 +112,16 @@ def main() -> None: name, particles.var(axis=0), ) + if documentation_output is not None: + summaries[name] = { + "log_evidence": float(results.final_state.log_evidence), + "weighted_mean": results.weighted_mean().tolist(), + "weighted_population_covariance": results.weighted_covariance().tolist(), + "n_particles": len(particles), + "likelihood_evaluations": int( + results.context.evaluation_counts.log_likelihood_evaluations + ), + } posterior_counts[name] = len(particles) comparison_rows.append( f"{name:<12} {len(particles):>10,d} {len(results.outputs):>8d} " @@ -142,7 +160,12 @@ def main() -> None: ax.spines[["top", "right"]].set_visible(False) ax.grid(alpha=0.2) ax.legend() - fig.savefig("waste_free_history.png", dpi=180) + output_directory = documentation_output or Path(".") + fig.savefig(output_directory / "waste_free_history.png", dpi=180) + if documentation_output is not None: + (documentation_output / "summary.json").write_text( + json.dumps(summaries, allow_nan=False) + "\n" + ) plt.close(fig) logger.info("Saved waste_free_history.png") diff --git a/pixi.lock b/pixi.lock new file mode 100644 index 0000000..98c9df7 --- /dev/null +++ b/pixi.lock @@ -0,0 +1,3888 @@ +version: 7 +platforms: +- name: linux-64 + virtual-packages: + - __unix=0=0 + - __linux=4.18 + - __glibc=2.28 + - __archspec=0=x86_64 +- name: osx-arm64 + virtual-packages: + - __unix=0=0 + - __osx=13.0 + - __archspec=0=m1 +environments: + check: + channels: + - url: https://conda.anaconda.org/conda-forge/ + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.1.1-py313ha52865f_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.5-hd2095e1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.7.0-h81df57d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-16.2.0-ha9f2e26_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-16.2.0-he0feb66_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpython-3.13.16-hdc7f604_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.54.0-h0737f62_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-16.2.0-h934c35e_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.4-hcfc3c73_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.5-h781a0a9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.16-hf47f18c_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-hd6e31c0_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.14.14-h40fa522_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h1df4ec4_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.1.0-py313h7037e92_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-hebe6cf0_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-4.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.20-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.13.0-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.6.2-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-3.11-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.21.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.1.1-pyhc364b38_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.6.2-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-9_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.5.0-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.14.7-pyh5ded981_0.conda + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.3-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-4.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.20-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.13.0-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.6.2-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-3.11-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.21.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.1.1-pyhc364b38_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.6.2-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-9_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.5.0-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.14.7-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h4e30115_10.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.1.1-py313h056b358_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-23.1.3-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.5-h6111c0a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.7.0-h47dc5ef_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpython-3.13.16-hb350d79_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.54.0-h34a968b_101.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-he64c551_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.5-h55eecbc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.16-hb59dee6_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py313h65a2061_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h8b90a29_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.14.14-h279115b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ukkonen-1.1.0-py313h5c29297_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h74c22ad_3.conda + default: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.5-hd2095e1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.7.0-h81df57d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-16.2.0-ha9f2e26_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-16.2.0-he0feb66_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpython-3.13.16-hdc7f604_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.54.0-h0737f62_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-16.2.0-h934c35e_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.4-hcfc3c73_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.5-h781a0a9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.16-hf47f18c_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-hd6e31c0_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h1df4ec4_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-9_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - pypi: git+https://github.com/sequince-dev/sequince.git?branch=main#9968074e158e13760a9af5d5a1be0c28e554d036 + - pypi: https://files.pythonhosted.org/packages/07/ef/9b77fc80fc9bf28978d5649a3c71c0e77efe9e1966e5f608e7c3d5ba86b4/acai_control-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/cf/83c3377e7018ee3c455153d9560c6a972b0a407075868f5e58979fc640a2/minipcn-0.3.0a1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/bb/d215ee7c73b61497b28a5503f9f53523f294fcc936762b7caf90e0c1c2b5/pyparsing-3.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/48/6450ed9243315322bbc19ac57b9b70d66a20bf1d38d124c96bc4bf6af9ea/scipy-1.18.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4c/1c/6ec22372362b03350fe3da7bf33491a07cc9a553a36dd2383b76ec1741eb/fonttools-4.66.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/68/77/8f93ecbde1a4d11dfd460cd19a0c159f5487352e751fa87f5d78cc9db08e/contourpy-1.4.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/76/3e/0fa5690a3a8303d821a46fd35691b1b6f8a9c06b97b41847ca41aed72e90/orng-0.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/86/16/1a8fd2b19544b84575cf84ef7aa3ad4c173b756d5f087c91f85d1b295777/array_api_compat-1.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/b2/696029cd979f5429ec25c12767734bf0e13175d3341a2aeac59127d00ca0/optree-0.20.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a7/03/921a3d3c75785aca9ebfbfcabfbc3a1be12e2ab5265deb026d55a5a3f83e/tqdm-4.70.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/df/e7c10292f348cf55178a5b3c0092a38ea68891a3945f199ae8b7b8ac9dc1/matplotlib-3.11.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ab/43/86aacc027959108b4c66eeae8b73cedb057dfa6eb3a335d05ad65197081c/kiwisolver-1.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c7/b3/fcf70b2ed935f26fc15dfd81f72f781d395b09e302af5c86ec285d63524e/array_api_extra-0.11.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d6/cb/17a130e548ee1777f2a66e09240c531a9bf79454add7ef1198fe1e41c412/corner-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/24/3bd070f3269dc609d8f26b2643f62ef91bb415841c0b294805aaf7fe06da/numpy-2.5.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-9_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h4e30115_10.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-23.1.3-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.5-h6111c0a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.7.0-h47dc5ef_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpython-3.13.16-hb350d79_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.54.0-h34a968b_101.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-he64c551_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.5-h55eecbc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.16-hb59dee6_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h8b90a29_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda + - pypi: git+https://github.com/sequince-dev/sequince.git?branch=main#9968074e158e13760a9af5d5a1be0c28e554d036 + - pypi: https://files.pythonhosted.org/packages/07/ef/9b77fc80fc9bf28978d5649a3c71c0e77efe9e1966e5f608e7c3d5ba86b4/acai_control-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/1e/cf/83c3377e7018ee3c455153d9560c6a972b0a407075868f5e58979fc640a2/minipcn-0.3.0a1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/f5/769f36d14922b8071a43e95d24d18b6bdafad10d7f5cf647867e1ac052bc/scipy-1.18.1-cp313-cp313-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/38/bb/d215ee7c73b61497b28a5503f9f53523f294fcc936762b7caf90e0c1c2b5/pyparsing-3.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/2f/103cdc5b7077cd569989d3ddb99ee3d4e96ee31bde8df2a6e426b2dc6195/contourpy-1.4.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/47/8b/40d33ffd2f378094ed462e9a9a0907e59d4de9845e65a59561272da350d4/kiwisolver-1.5.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/3e/0fa5690a3a8303d821a46fd35691b1b6f8a9c06b97b41847ca41aed72e90/orng-0.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/83/8c/b0ea9477fb1f0d4484bbc5cba21678cc9969704d8d7f3f158d1db35f8e14/numpy-2.5.4-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/86/16/1a8fd2b19544b84575cf84ef7aa3ad4c173b756d5f087c91f85d1b295777/array_api_compat-1.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8d/c0/85fa4951ed8fcfc54ea10367e20617b60292780999835f636a6bdbf01c34/optree-0.20.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a7/03/921a3d3c75785aca9ebfbfcabfbc3a1be12e2ab5265deb026d55a5a3f83e/tqdm-4.70.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/29/2a47b700545aca050e0ed73c89ba21e08407e11f5f42a567421b220de6c1/matplotlib-3.11.2-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/b3/fcf70b2ed935f26fc15dfd81f72f781d395b09e302af5c86ec285d63524e/array_api_extra-0.11.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/f4/e410b8c913da5b3fdbb4d16db0f2d2a0952f59c4db8d52dcf2d421d82044/fonttools-4.66.1-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/d6/cb/17a130e548ee1777f2a66e09240c531a9bf79454add7ef1198fe1e41c412/corner-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + docs: + channels: + - url: https://conda.anaconda.org/conda-forge/ + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.8.0-py313h8f72f4d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py313h2fc2bef_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/charset-normalizer-3.5.2-h1114479_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.5-hd2095e1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.7.0-h81df57d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-16.2.0-ha9f2e26_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-16.2.0-he0feb66_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpython-3.13.16-hdc7f604_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.54.0-h0737f62_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-16.2.0-h934c35e_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.4-hcfc3c73_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.4-py313h2af15c8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.5-h781a0a9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.16-hf47f18c_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-hd6e31c0_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h1df4ec4_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/accessible-pygments-0.0.5-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/alabaster-1.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.15.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.16-py313hd8ed1ab_101.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/docutils-0.21.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.20-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/imagesize-2.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.15.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.21.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.16-h4df99d1_101.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-9_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/roman-numerals-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/roman-numerals-py-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-3.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-3.0.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-8.2.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-book-theme-1.1.4-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-2.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.8.0-pyhd8ed1ab_0.conda + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/accessible-pygments-0.0.5-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/alabaster-1.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.15.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/docutils-0.21.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.20-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/imagesize-2.0.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.15.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.21.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-9_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/roman-numerals-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/roman-numerals-py-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-3.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-3.0.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-8.2.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-book-theme-1.1.4-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-2.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.8.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.8.0-py313h8c7ad59_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py313h3c37e74_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h4e30115_10.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-23.1.3-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.5-h6111c0a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.7.0-h47dc5ef_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpython-3.13.16-hb350d79_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.54.0-h34a968b_101.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.4-py313h996ddf1_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-he64c551_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.5-h55eecbc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.16-hb59dee6_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h8b90a29_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hf451053_7.conda + flow: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-8_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fmt-12.1.0-h76c4fd7_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hfd2156b_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.3.2-py313ha4a6282_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260526.0-cxx17_h0dc7533_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-11_h5875eb1_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-11_hfef963f_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.5-hd2095e1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.7.0-h81df57d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-16.2.0-ha9f2e26_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.15.0-default_hd7c4d0c_1001.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h0cb94f2_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-11_h5e43f62_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-7.35.1-h622638d_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpython-3.13.16-hd51c5da_102_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.54.0-h0737f62_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-16.2.0-h934c35e_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.14.1-cpu_mkl_hc3c961d_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.4-hcfc3c73_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.52.1-h280c20c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.4-hbdfff7e_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.4-ha7a5771_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-23.1.3-h7148c6a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.4-py313h2af15c8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2026.1.0-hd2095e1_245.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.4.0-ha2cb11d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.2-ha2cb11d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.5.4-py313hb5f73ae_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/onednn-3.13.4-omp_h64ed1e9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.5-h781a0a9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.20.0-py313h6bdb861_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.16-hf47f18c_102_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.14.1-cpu_mkl_py313_h7b4a8e5_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-cpu-2.14.1-cpu_mkl_hd61e0f4_100.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-hd6e31c0_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.9.0-ha0421bc_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2023.1.0-haa8c3ee_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h1df4ec4_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.16-py313hd8ed1ab_102.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-4.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.9.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.4.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.7-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-3.0.4-pyh293190f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-3.0.4-pyh648e204_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-9_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/scikit-build-core-1.1.1-pyhcb39dcd_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_106.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.5.0-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - pypi: git+https://github.com/sequince-dev/sequince-flows.git?rev=cbe32939713f17be2c5deb8f813ba1ec4550ea7e#cbe32939713f17be2c5deb8f813ba1ec4550ea7e + - pypi: git+https://github.com/sequince-dev/sequince.git?branch=main#9968074e158e13760a9af5d5a1be0c28e554d036 + - pypi: https://files.pythonhosted.org/packages/07/ef/9b77fc80fc9bf28978d5649a3c71c0e77efe9e1966e5f608e7c3d5ba86b4/acai_control-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/cf/83c3377e7018ee3c455153d9560c6a972b0a407075868f5e58979fc640a2/minipcn-0.3.0a1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/bb/d215ee7c73b61497b28a5503f9f53523f294fcc936762b7caf90e0c1c2b5/pyparsing-3.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/48/6450ed9243315322bbc19ac57b9b70d66a20bf1d38d124c96bc4bf6af9ea/scipy-1.18.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/4c/1c/6ec22372362b03350fe3da7bf33491a07cc9a553a36dd2383b76ec1741eb/fonttools-4.66.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/68/77/8f93ecbde1a4d11dfd460cd19a0c159f5487352e751fa87f5d78cc9db08e/contourpy-1.4.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/76/3e/0fa5690a3a8303d821a46fd35691b1b6f8a9c06b97b41847ca41aed72e90/orng-0.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/86/16/1a8fd2b19544b84575cf84ef7aa3ad4c173b756d5f087c91f85d1b295777/array_api_compat-1.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/95/10/ff159867f522cd98e039e748c5e9777446e8b797a79be691c6b730676094/zuko-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a7/03/921a3d3c75785aca9ebfbfcabfbc3a1be12e2ab5265deb026d55a5a3f83e/tqdm-4.70.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/df/e7c10292f348cf55178a5b3c0092a38ea68891a3945f199ae8b7b8ac9dc1/matplotlib-3.11.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ab/43/86aacc027959108b4c66eeae8b73cedb057dfa6eb3a335d05ad65197081c/kiwisolver-1.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c7/b3/fcf70b2ed935f26fc15dfd81f72f781d395b09e302af5c86ec285d63524e/array_api_extra-0.11.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d6/cb/17a130e548ee1777f2a66e09240c531a9bf79454add7ef1198fe1e41c412/corner-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.16-py313hd8ed1ab_102.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-4.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.9.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.4.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.7-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nomkl-1.0-h5ca1d4c_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-3.0.4-pyh293190f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-3.0.4-pyh648e204_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-9_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/scikit-build-core-1.1.1-pyhcb39dcd_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_106.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.5.0-pyh5ded981_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-8_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h4e30115_10.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fmt-12.1.0-h99e4e11_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gmp-6.3.0-hf5e55b1_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gmpy2-2.3.2-py313h0091f1d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260526.0-cxx17_h4c27e2a_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-11_h51639a9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-11_hb0561ab_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-23.1.3-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.5-h6111c0a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.7.0-h47dc5ef_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-16.2.0-h3cf6597_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-16.2.0-h07b0088_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-16.2.0-hdb7a957_7.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-11_hd9741b5_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.34-openmp_h5e6e99c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libprotobuf-7.35.1-h391e224_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpython-3.13.16-h0af5753_102_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.54.0-h34a968b_101.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtorch-2.14.1-cpu_generic_h8539c7d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.52.1-h1a92334_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-23.1.3-hdb3d66b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.4-py313h996ddf1_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpc-1.4.0-h071a35a_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpfr-4.2.2-h72846b0_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-he64c551_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.5.4-py313h24c19cf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/onednn-3.13.4-omp_h3fb3e40_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.5-h55eecbc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/optree-0.20.0-py313h5c89e09_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.16-hb59dee6_102_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytorch-2.14.1-cpu_generic_py313_hf1850e0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytorch-cpu-2.14.1-cpu_generic_hcc7c195_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h8b90a29_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/sleef-3.9.0-hb028509_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda + - pypi: git+https://github.com/sequince-dev/sequince-flows.git?rev=cbe32939713f17be2c5deb8f813ba1ec4550ea7e#cbe32939713f17be2c5deb8f813ba1ec4550ea7e + - pypi: git+https://github.com/sequince-dev/sequince.git?branch=main#9968074e158e13760a9af5d5a1be0c28e554d036 + - pypi: https://files.pythonhosted.org/packages/07/ef/9b77fc80fc9bf28978d5649a3c71c0e77efe9e1966e5f608e7c3d5ba86b4/acai_control-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/1e/cf/83c3377e7018ee3c455153d9560c6a972b0a407075868f5e58979fc640a2/minipcn-0.3.0a1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/f5/769f36d14922b8071a43e95d24d18b6bdafad10d7f5cf647867e1ac052bc/scipy-1.18.1-cp313-cp313-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/38/bb/d215ee7c73b61497b28a5503f9f53523f294fcc936762b7caf90e0c1c2b5/pyparsing-3.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/2f/103cdc5b7077cd569989d3ddb99ee3d4e96ee31bde8df2a6e426b2dc6195/contourpy-1.4.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/47/8b/40d33ffd2f378094ed462e9a9a0907e59d4de9845e65a59561272da350d4/kiwisolver-1.5.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/76/3e/0fa5690a3a8303d821a46fd35691b1b6f8a9c06b97b41847ca41aed72e90/orng-0.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/86/16/1a8fd2b19544b84575cf84ef7aa3ad4c173b756d5f087c91f85d1b295777/array_api_compat-1.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/95/10/ff159867f522cd98e039e748c5e9777446e8b797a79be691c6b730676094/zuko-1.6.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a7/03/921a3d3c75785aca9ebfbfcabfbc3a1be12e2ab5265deb026d55a5a3f83e/tqdm-4.70.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/29/2a47b700545aca050e0ed73c89ba21e08407e11f5f42a567421b220de6c1/matplotlib-3.11.2-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/b3/fcf70b2ed935f26fc15dfd81f72f781d395b09e302af5c86ec285d63524e/array_api_extra-0.11.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/f4/e410b8c913da5b3fdbb4d16db0f2d2a0952f59c4db8d52dcf2d421d82044/fonttools-4.66.1-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/d6/cb/17a130e548ee1777f2a66e09240c531a9bf79454add7ef1198fe1e41c412/corner-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + jax: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_103.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.5-hd2095e1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.7.0-h81df57d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-16.2.0-ha9f2e26_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-16.2.0-he0feb66_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpython-3.13.16-hdc7f604_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.54.0-h0737f62_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-16.2.0-h934c35e_7.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.4-hcfc3c73_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.5-h781a0a9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.16-hf47f18c_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-hd6e31c0_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h1df4ec4_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-9_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - pypi: git+https://github.com/sequince-dev/sequince.git?branch=main#9968074e158e13760a9af5d5a1be0c28e554d036 + - pypi: https://files.pythonhosted.org/packages/07/ef/9b77fc80fc9bf28978d5649a3c71c0e77efe9e1966e5f608e7c3d5ba86b4/acai_control-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/1e/cf/83c3377e7018ee3c455153d9560c6a972b0a407075868f5e58979fc640a2/minipcn-0.3.0a1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/24/ec/014b428c3a05837643768d08aeb8fb1289d119d3f9f6fba479dffae05dbc/jaxlib-0.11.2-cp313-cp313-manylinux_2_27_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/38/bb/d215ee7c73b61497b28a5503f9f53523f294fcc936762b7caf90e0c1c2b5/pyparsing-3.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/41/48/6450ed9243315322bbc19ac57b9b70d66a20bf1d38d124c96bc4bf6af9ea/scipy-1.18.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4c/1c/6ec22372362b03350fe3da7bf33491a07cc9a553a36dd2383b76ec1741eb/fonttools-4.66.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/68/77/8f93ecbde1a4d11dfd460cd19a0c159f5487352e751fa87f5d78cc9db08e/contourpy-1.4.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/76/3e/0fa5690a3a8303d821a46fd35691b1b6f8a9c06b97b41847ca41aed72e90/orng-0.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/86/16/1a8fd2b19544b84575cf84ef7aa3ad4c173b756d5f087c91f85d1b295777/array_api_compat-1.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/89/a5/da8ae6c6f1babe4b68e3e55d43d39b529e29774f10e0910671a6b8c86eb8/ml_dtypes-0.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/93/b2/696029cd979f5429ec25c12767734bf0e13175d3341a2aeac59127d00ca0/optree-0.20.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/a7/03/921a3d3c75785aca9ebfbfcabfbc3a1be12e2ab5265deb026d55a5a3f83e/tqdm-4.70.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/df/e7c10292f348cf55178a5b3c0092a38ea68891a3945f199ae8b7b8ac9dc1/matplotlib-3.11.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ab/43/86aacc027959108b4c66eeae8b73cedb057dfa6eb3a335d05ad65197081c/kiwisolver-1.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/c7/b3/fcf70b2ed935f26fc15dfd81f72f781d395b09e302af5c86ec285d63524e/array_api_extra-0.11.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/87/0c0ca8433e1135f6d1acd355cff21f31c3d433d937d6547aa545c8b060b8/jax-0.11.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d6/cb/17a130e548ee1777f2a66e09240c531a9bf79454add7ef1198fe1e41c412/corner-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/24/3bd070f3269dc609d8f26b2643f62ef91bb415841c0b294805aaf7fe06da/numpy-2.5.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-9_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h4e30115_10.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-23.1.3-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.5-h6111c0a_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.7.0-h47dc5ef_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpython-3.13.16-hb350d79_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.54.0-h34a968b_101.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-he64c551_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.5-h55eecbc_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.16-hb59dee6_101_cp313.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h8b90a29_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda + - pypi: git+https://github.com/sequince-dev/sequince.git?branch=main#9968074e158e13760a9af5d5a1be0c28e554d036 + - pypi: https://files.pythonhosted.org/packages/07/ef/9b77fc80fc9bf28978d5649a3c71c0e77efe9e1966e5f608e7c3d5ba86b4/acai_control-0.1.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/1e/cf/83c3377e7018ee3c455153d9560c6a972b0a407075868f5e58979fc640a2/minipcn-0.3.0a1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/f5/769f36d14922b8071a43e95d24d18b6bdafad10d7f5cf647867e1ac052bc/scipy-1.18.1-cp313-cp313-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/38/bb/d215ee7c73b61497b28a5503f9f53523f294fcc936762b7caf90e0c1c2b5/pyparsing-3.3.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/3f/2f/103cdc5b7077cd569989d3ddb99ee3d4e96ee31bde8df2a6e426b2dc6195/contourpy-1.4.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/47/7f/f0d33a414e2d84fca39438a30f0588c32f7365746e903293cadad9d52b0d/jaxlib-0.11.2-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/47/8b/40d33ffd2f378094ed462e9a9a0907e59d4de9845e65a59561272da350d4/kiwisolver-1.5.1-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/50/51/fd1582b8f5ed8a9e7be0e161a6ea0dff70cb280479a12178df0b3a72700e/ml_dtypes-0.6.0-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/76/3e/0fa5690a3a8303d821a46fd35691b1b6f8a9c06b97b41847ca41aed72e90/orng-0.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/83/8c/b0ea9477fb1f0d4484bbc5cba21678cc9969704d8d7f3f158d1db35f8e14/numpy-2.5.4-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/86/16/1a8fd2b19544b84575cf84ef7aa3ad4c173b756d5f087c91f85d1b295777/array_api_compat-1.15.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/8d/c0/85fa4951ed8fcfc54ea10367e20617b60292780999835f636a6bdbf01c34/optree-0.20.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/a7/03/921a3d3c75785aca9ebfbfcabfbc3a1be12e2ab5265deb026d55a5a3f83e/tqdm-4.70.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a9/29/2a47b700545aca050e0ed73c89ba21e08407e11f5f42a567421b220de6c1/matplotlib-3.11.2-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c7/b3/fcf70b2ed935f26fc15dfd81f72f781d395b09e302af5c86ec285d63524e/array_api_extra-0.11.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ca/87/0c0ca8433e1135f6d1acd355cff21f31c3d433d937d6547aa545c8b060b8/jax-0.11.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cb/f4/e410b8c913da5b3fdbb4d16db0f2d2a0952f59c4db8d52dcf2d421d82044/fonttools-4.66.1-cp313-cp313-macosx_10_13_universal2.whl + - pypi: https://files.pythonhosted.org/packages/d6/cb/17a130e548ee1777f2a66e09240c531a9bf79454add7ef1198fe1e41c412/corner-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl +packages: +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + build_number: 20 + sha256: 1dd3fffd892081df9726d7eb7e0dea6198962ba775bd88842135a4ddb4deb3c9 + md5: a9f577daf3de00bca7c3c76c0ecbd1de + depends: + - __glibc >=2.17,<3.0.a0 + - libgomp >=7.5.0 + constrains: + - openmp_impl <0.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + strong: + - _openmp_mutex >=4.5 + size: 28948 + timestamp: 1770939786096 +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-8_kmp_llvm.conda + build_number: 8 + sha256: b8de006376660eb12c7fc40803c97e1480bf4bc1253f0d59694b12f81b52fe14 + md5: b4d1b93dd1dab4eea23395fd5e5b357d + depends: + - llvm-openmp >=9.0.1 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - _openmp_mutex >=4.5 + size: 8050 + timestamp: 1788046427177 +- conda: https://conda.anaconda.org/conda-forge/linux-64/backports.zstd-1.8.0-py313h8f72f4d_0.conda + sha256: f174b8570aea9c80de141fe43e0cf3353df233e77aa396f945be4bb242a31649 + md5: b2b55be09675e65d54fb1709208bd250 + depends: + - python + - libgcc >=15 + - __glibc >=2.17,<3.0.a0 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause AND MIT AND EPL-2.0 + run_exports: {} + size: 244807 + timestamp: 1791654836417 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py313h2fc2bef_4.conda + sha256: 3621a39631aec2d6f4b8ac0cc9ee6d46cee1ae684ba9d2ed6f84b9993e14fbc4 + md5: 8beb0b96d9c701006ff5511a3a92329f + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - libstdcxx >=15 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + constrains: + - libbrotlicommon 1.2.0 h39a168f_4 + license: MIT + license_family: MIT + run_exports: {} + size: 367791 + timestamp: 1788480600802 +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_10.conda + sha256: 1a0d382c515ebf55f8ee1f38c8b81bc95af5c2acc42ad53b66bc5df932032f96 + md5: e675fabcf81499adc7edf58124fb1e01 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + run_exports: + weak: + - bzip2 >=1.0.8,<2.0a0 + size: 257808 + timestamp: 1785906269155 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.1.1-py313ha52865f_3.conda + sha256: e1968f1efd8491e32a84416a4994cea98bbf51aebd8c7c04c783e121088fec63 + md5: 2b17c17965f71b59158030dce83832e1 + depends: + - __glibc >=2.17,<3.0.a0 + - libffi >=3.7.0,<3.8.0a0 + - libgcc >=15 + - pycparser + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + run_exports: {} + size: 304628 + timestamp: 1788485299863 +- conda: https://conda.anaconda.org/conda-forge/linux-64/charset-normalizer-3.5.2-h1114479_0.conda + noarch: python + sha256: e4f04b45d68716fcc47c808aeacbf165f6f295fb91bd20b91aa38942b8d2cdc6 + md5: 78d5c6112c9320a1a797935cecfec7c2 + depends: + - python + - libgcc >=15 + - __glibc >=2.17,<3.0.a0 + - _python_abi3_support 1.* + - cpython >=3.9 + license: MIT + license_family: MIT + run_exports: {} + size: 217200 + timestamp: 1790766086110 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fmt-12.1.0-h76c4fd7_1.conda + sha256: 25d14a197601fdd616f2e501431b52887e69b31e7defbc1679381d718357ceb7 + md5: a70bb6d42ad121aef456e0007e92bed6 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - fmt >=12.1.0,<12.2.0a0 + size: 199072 + timestamp: 1785915412102 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gmp-6.3.0-hfd2156b_3.conda + sha256: f36c336efc874f346a53a9011f67e997f9faac505562cc18c13b6d399cfe61c7 + md5: 576e32739f323438bf69ab006c21be7b + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: GPL-2.0-or-later OR LGPL-3.0-or-later + purls: [] + run_exports: + weak: + - gmp >=6.3.0,<7.0a0 + size: 493498 + timestamp: 1786629164954 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gmpy2-2.3.2-py313ha4a6282_0.conda + sha256: 0883e75a80869d2eda1c78e3154eaac3a60cbcfdde56583e2c0dfc9eefb50bfb + md5: 2d829f59f3506bf5660fb6f0c4cc77a3 + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - mpfr >=4.2.2,<5.0a0 + - python_abi 3.13.* *_cp313 + - mpc >=1.4.0,<2.0a0 + - gmp >=6.3.0,<7.0a0 + license: LGPL-3.0-or-later + license_family: LGPL + purls: + - pkg:pypi/gmpy2?source=compressed-mapping + run_exports: {} + size: 327168 + timestamp: 1791166953052 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_103.conda + sha256: 901649b1edbe841fd4ee7e2d491a05184ed540eb01cdae506872f344babc1d28 + md5: 2362c70e7e79827c32db35547d9c179b + depends: + - __glibc >=2.17,<3.0.a0 + - zstd >=1.5.7,<1.6.0a0 + constrains: + - binutils_impl_linux-64 2.46.1 + license: GPL-3.0-only + license_family: GPL + purls: [] + run_exports: {} + size: 742109 + timestamp: 1791565468810 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libabseil-20260526.0-cxx17_h0dc7533_3.conda + sha256: 24708450d80f4d542fc357c941909446e0b6caffb8baca478a0da1f74820e688 + md5: d224f3a53f55029366c73161f15461fb + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + constrains: + - abseil-cpp =20260526.0 + - libabseil-static =20260526.0=cxx17* + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - libabseil >=20260526.0,<20260527.0a0 + - libabseil =*=cxx17* + size: 1440678 + timestamp: 1791681043356 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-11_h5875eb1_mkl.conda + build_number: 11 + sha256: 3b9862430914d13ae264398340117f80b85720de308f6a80ad463588abe10358 + md5: 8db4ae3941966215baa885eb004aded5 + depends: + - mkl >=2026.1.0,<2027.0a0 + constrains: + - blas 2.311 mkl + - libcblas 3.11.0 11*_mkl + - liblapack 3.11.0 11*_mkl + - liblapacke 3.11.0 11*_mkl + track_features: + - blas_mkl + - blas_mkl_2 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libblas >=3.11.0,<4.0a0 + size: 18622 + timestamp: 1789061051585 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-11_hfef963f_mkl.conda + build_number: 11 + sha256: 6ebc9042e96050ddb4d57b666415677164561b8229905c52e6f84defbc884b0f + md5: 5100a1707606a71eebcb00ace3b23339 + depends: + - libblas 3.11.0 11_h5875eb1_mkl + constrains: + - blas 2.311 mkl + - liblapack 3.11.0 11*_mkl + - liblapacke 3.11.0 11*_mkl + track_features: + - blas_mkl + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libcblas >=3.11.0,<4.0a0 + size: 18250 + timestamp: 1789061057597 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.5-hd2095e1_0.conda + sha256: 6de73033a7c5f712c9c5a54124e4581e025691029835e5bceaf10a333a7e7bc0 + md5: 72cfd2b95b0540247220b42f58a148d7 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + constrains: + - expat 2.8.5.* + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 77893 + timestamp: 1790346247149 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.7.0-h81df57d_1.conda + sha256: c8c7583ef063bc3c430f1d48298e5ad24f796a35c22ed5b14183325825a61106 + md5: 6525a0b06aa4fd390795f0740636a9dd + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libffi >=3.7.0,<3.8.0a0 + size: 68004 + timestamp: 1787753412298 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-16.2.0-ha9f2e26_7.conda + sha256: e031634c3a928f9594eba868bc46ef5230d6571a364a0c63215b06c4563bfcea + md5: 735629b400b70eeab21a2ba8f5b1c2ce + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + constrains: + - libgcc-ng ==16.2.0=*_7 + - libgomp 16.2.0 he0feb66_7 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + run_exports: {} + size: 1058444 + timestamp: 1790303109576 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-16.2.0-he0feb66_7.conda + sha256: 92a0bb350c7be6a4eedcb4e111c60aefd176018e6b1c009a6e025dbe9bbcd758 + md5: d904c7ddd95e026254b4b80808ee9e12 + depends: + - __glibc >=2.17,<3.0.a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + run_exports: + strong: + - _openmp_mutex >=4.5 + size: 640566 + timestamp: 1790303046034 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.15.0-default_hd7c4d0c_1001.conda + sha256: a140f8b94caad6ea27e9deba66a74ae2566976591db8a867677c652add6ee5a0 + md5: 66c755b3bd9ec82308fa6ec7674f473f + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - libstdcxx >=15 + - libxml2 + - libxml2-16 >=2.15.4 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libhwloc >=2.15.0,<2.15.1.0a0 + size: 2456090 + timestamp: 1790243647895 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h0cb94f2_3.conda + sha256: f943117edb9cd4d9c61cc972eee5a34291dc55ea7a6e9e38da104995841cbcb6 + md5: f92233bf33e24a25668bb2119e2c51f9 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + license: LGPL-2.1-only + purls: [] + run_exports: + weak: + - libiconv >=1.18,<2.0a0 + size: 789471 + timestamp: 1787033836207 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-11_h5e43f62_mkl.conda + build_number: 11 + sha256: 7b80fc195419463533262b9d327c75559dd820251902e3fa41cce02a3b23bb5b + md5: 7dee7c97a08b93f9d11f04d974e65724 + depends: + - libblas 3.11.0 11_h5875eb1_mkl + constrains: + - blas 2.311 mkl + - libcblas 3.11.0 11*_mkl + - liblapacke 3.11.0 11*_mkl + track_features: + - blas_mkl + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - liblapack >=3.11.0,<3.12.0a0 + size: 18295 + timestamp: 1789061064514 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda + sha256: 9787df8c22a59c9a70d3e5a10db9ad663485e75e9ccc3f09bd092cb7b95e0dab + md5: 1390b7c5ac0b1d8e447bc5efa6d3c8c2 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - xz 5.8.3.* + license: 0BSD + purls: [] + run_exports: + weak: + - liblzma >=5.8.3,<6.0a0 + size: 112995 + timestamp: 1786348617826 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_2.conda + sha256: 46820b4a835e175940ae20bec00fdddaff804cda27a1408ab1b95e77a2196437 + md5: fcfed1dc5053eb1901b66e7b1fc32588 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 92759 + timestamp: 1786650399772 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libprotobuf-7.35.1-h622638d_3.conda + sha256: 37c51fbc936a6bb8bf5f67c8f056edacaaa41e8603738bc8a4dee186292bb114 + md5: fd307b61cceb36fdca38e1718bdb2893 + depends: + - __glibc >=2.17,<3.0.a0 + - libabseil * cxx17* + - libabseil >=20260526.0,<20260527.0a0 + - libgcc >=15 + - libstdcxx >=15 + - libzlib >=1.3.2,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libprotobuf >=7.35.1,<7.35.2.0a0 + size: 3808338 + timestamp: 1787657986197 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpython-3.13.16-hd51c5da_102_cp313.conda + build_number: 102 + sha256: 87aa891c1e06aee56aabadf3ac0a1fd07432aa7f3834c24f3baccffcebe9f8bb + md5: de5467b4cae23d95c711f84b2033341d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + license: Python-2.0 + purls: [] + run_exports: + weak: + - libpython >=3.13,<4.0a0 + size: 9718549 + timestamp: 1791911455904 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpython-3.13.16-hdc7f604_101_cp313.conda + build_number: 101 + sha256: 7a01347dfc0d99c6592010fc278500ab12fc852d902f35beca69608c5f1282d5 + md5: 6c9b3aaaa24997d37c5e193cfdca3938 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - libstdcxx >=15 + license: Python-2.0 + purls: [] + run_exports: + weak: + - libpython >=3.13,<4.0a0 + size: 9718885 + timestamp: 1791334276485 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.54.0-h0737f62_101.conda + sha256: b4d5d4428bd1931eede11d2d69389c5b9d396a7f3c8df9d7c39c9efd80761eb3 + md5: a8b899d3c5fc1034b209045653b86ed2 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - libzlib >=1.3.2,<2.0a0 + constrains: + - icu <0.0a0 + license: blessing + purls: [] + run_exports: + weak: + - libsqlite >=3.54.0,<4.0a0 + size: 979215 + timestamp: 1791584361438 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-16.2.0-h934c35e_7.conda + sha256: fa7018298629fc429971fffd6ecba3d27a7b02e4e890d6a93ea01ffebe8cb745 + md5: 9b6707e0caca917fa6ea9ee473f37a5d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc 16.2.0 ha9f2e26_7 + constrains: + - libstdcxx-ng ==16.2.0=*_7 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + run_exports: {} + size: 6615436 + timestamp: 1790303132935 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libtorch-2.14.1-cpu_mkl_hc3c961d_100.conda + sha256: 771a151465e667c6165eace7d61f1eee94c1bb9cbeceaeca55bf14450ef511aa + md5: 0d4407c5f1fcbf718363cd1b0579d25d + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex * *_llvm + - _openmp_mutex >=4.5 + - fmt >=12.1.0,<12.2.0a0 + - libabseil * cxx17* + - libabseil >=20260526.0,<20260527.0a0 + - libblas * *mkl + - libcblas >=3.11.0,<4.0a0 + - libgcc >=15 + - libprotobuf >=7.35.1,<7.35.2.0a0 + - libstdcxx >=15 + - libuv >=1.52.1,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - llvm-openmp >=23.1.3 + - mkl >=2026.1.0,<2027.0a0 + - onednn >=3.13.4,<4.0a0 + - pybind11-abi 11 + - sleef >=3.9.0,<4.0a0 + constrains: + - pytorch 2.14.1 cpu_mkl_*_100 + - pytorch-cpu 2.14.1 + - pytorch-gpu <0.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libtorch >=2.14.1,<2.15.0a0 + size: 62087888 + timestamp: 1791586025454 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.4-hcfc3c73_0.conda + sha256: f34b8d1de9a2bf62476d8608057d9b8a918d97444236dc53a47128f2fae79041 + md5: 54a948337e66706380ff24d6b5fde63f + depends: + - libgcc >=15 + - __glibc >=2.17,<3.0.a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libuuid >=2.42.4,<3.0a0 + size: 34442 + timestamp: 1790121449161 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuv-1.52.1-h280c20c_1.conda + sha256: 67761d0206f84140047a367eaf9befe03a7e157a29ee25aee0047b40801cc6b6 + md5: 01c4ed87826af55996768af6dbc936b4 + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libuv >=1.52.1,<2.0a0 + size: 420040 + timestamp: 1785914567661 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.4-hbdfff7e_1.conda + sha256: 642d5d650a5f877a13ca60b720ae88643a145a30fb7a1065fd96c370595a5e2c + md5: 36e109ff64ea3bc459fdf0bfad9dd21b + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - icu <0.0a0 + - libxml2 2.15.4 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 566161 + timestamp: 1791465627683 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.4-ha7a5771_1.conda + sha256: ef0f51a53d5f5d8a0aa8a71115a36b1bb3cd3ac95d65ced2cbfec7f1f08229b0 + md5: 7c2fba53903a27aeb56a410a9510774b + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libxml2-16 2.15.4 hbdfff7e_1 + - libzlib >=1.3.2,<2.0a0 + constrains: + - icu <0.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libxml2 + - libxml2-16 >=2.15.4 + size: 46856 + timestamp: 1791465632103 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda + sha256: eb8a0db0aa570124f7d2a93d7c7f596e3390df5e047818d873baad32985fc736 + md5: 0de0122d9570a8ab637c6b73db268389 + depends: + - __glibc >=2.17,<3.0.a0 + constrains: + - zlib 1.3.2 *_3 + license: Zlib + license_family: Other + purls: [] + run_exports: + weak: + - libzlib >=1.3.2,<2.0a0 + size: 63713 + timestamp: 1785362952714 +- conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-23.1.3-h7148c6a_1.conda + sha256: 250d9e8413c3e45a97626e03125fcce2c172b41bcb8e6226717c467d6e9327c9 + md5: fd3aa819acbe2a94a3c332cfdab0799a + depends: + - __glibc >=2.17,<3.0.a0 + constrains: + - intel-openmp <0.0a0 + - openmp 23.1.3|23.1.3.* + license: Apache-2.0 WITH LLVM-exception + purls: [] + run_exports: + strong: + - llvm-openmp >=23.1.3 + - _openmp_mutex >=4.5 + - _openmp_mutex * *_llvm + size: 6121536 + timestamp: 1791950734069 +- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.4-py313h2af15c8_1.conda + sha256: 74e611bb15ffe1065c14e9007a6f04f15a53f350524c3cd1d033620ef9c3c035 + md5: d15255d7f81d9188edecde1e024a1649 + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - python_abi 3.13.* *_cp313 + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/markupsafe?source=compressed-mapping + run_exports: {} + size: 27785 + timestamp: 1791548971554 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2026.1.0-hd2095e1_245.conda + sha256: a2699b4db5f93ae7211249f5b8df4981c22765c392cc6d34c18d76994fe3b327 + md5: 5c8b5643ac9b96cd1c5b65a17337ce5c + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex * *_llvm + - _openmp_mutex >=4.5 + - libgcc >=15 + - libstdcxx >=15 + - llvm-openmp >=23.1.0 + - tbb >=2023.0.0 + license: LicenseRef-IntelSimplifiedSoftwareOct2022 + license_family: Proprietary + purls: [] + run_exports: {} + size: 142335878 + timestamp: 1787725704439 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mpc-1.4.0-ha2cb11d_1.conda + sha256: 3d56733fe0f44159787ad086d5e7bdab8b69e6a5a9b6b873a8beb3519f3d8d07 + md5: f0f6c8691a9ed4099d1f287a444e251a + depends: + - __glibc >=2.17,<3.0.a0 + - gmp >=6.3.0,<7.0a0 + - libgcc >=15 + - mpfr >=4.2.2,<5.0a0 + license: LGPL-3.0-or-later + license_family: LGPL + purls: [] + run_exports: + weak: + - mpc >=1.4.0,<2.0a0 + size: 101074 + timestamp: 1787668090174 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mpfr-4.2.2-ha2cb11d_1.conda + sha256: 0be79ef2ee536e0c5d3ddb4a52bbae9f26d08560623299f33db2f4c4a17c525a + md5: 7dcc10f6c0bc3596d5519a710a84dfd4 + depends: + - __glibc >=2.17,<3.0.a0 + - gmp >=6.3.0,<7.0a0 + - libgcc >=15 + license: LGPL-3.0-only + license_family: LGPL + purls: [] + run_exports: + weak: + - mpfr >=4.2.2,<5.0a0 + size: 730409 + timestamp: 1787236218159 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_1.conda + sha256: 5d46557214ed184381dafe835b7c94a474a1c3b307a08a250b1ea4779b44ffb3 + md5: ee6c0cd80a60961a1f48aa3e0b91f986 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: X11 AND BSD-3-Clause + purls: [] + run_exports: + weak: + - ncurses >=6.6,<7.0a0 + size: 911196 + timestamp: 1786355078102 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.5.4-py313hb5f73ae_0.conda + sha256: e8ffae51383b57192b018bdeeb95c84ec617aa1428656825b1d4a326b81410dd + md5: 1ddee4b1dc2f784b4137a43789be3816 + depends: + - python + - libstdcxx >=15 + - libgcc >=15 + - __glibc >=2.17,<3.0.a0 + - libcblas >=3.9.0,<4.0a0 + - libblas >=3.9.0,<4.0a0 + - liblapack >=3.9.0,<4.0a0 + - python_abi 3.13.* *_cp313 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/numpy?source=compressed-mapping + run_exports: + weak: + - numpy >=1.25,<3 + size: 9339599 + timestamp: 1791675323834 +- conda: https://conda.anaconda.org/conda-forge/linux-64/onednn-3.13.4-omp_h64ed1e9_0.conda + sha256: 9fd0ca36c2983d40bc07b14eecfaca6f9fafdf078f0ef5c45f6491d2cf1140d7 + md5: 474f6631d8b9c50f26ee383d6a155a7b + depends: + - libstdcxx >=15 + - libgcc >=15 + - __glibc >=2.28,<3.0.a0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - onednn >=3.13.4,<4.0a0 + size: 13798149 + timestamp: 1791401101750 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.5-h781a0a9_0.conda + sha256: 9c1533f1cf784f0e250919c0c9762d0b0e6bc727a79290ffb7cea1ceca1d038c + md5: 5a51a2ea653f120240b1a9bf76e0aae9 + depends: + - __glibc >=2.17,<3.0.a0 + - ca-certificates + - libgcc >=15 + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - openssl >=3.6.5,<4.0a0 + size: 3195299 + timestamp: 1791098052301 +- conda: https://conda.anaconda.org/conda-forge/linux-64/optree-0.20.0-py313h6bdb861_1.conda + sha256: f72bf4359c00908875c20eefc3328c994111837e33206f5c34c7c029dd770a94 + md5: fc3bae04198dde717798c6dc25044aa6 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - libstdcxx >=15 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - typing-extensions >=4.12 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/optree?source=hash-mapping + run_exports: {} + size: 586381 + timestamp: 1788504569784 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.16-hf47f18c_101_cp313.conda + build_number: 101 + sha256: 914ce0022ee08951329ce85d8a35f55fa11e74239568bca6102375d630ca9e61 + md5: 85450b9eb16e2b58d2cae71600e6fbde + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.8.5,<3.0a0 + - libffi >=3.7.0,<3.8.0a0 + - libgcc >=15 + - liblzma >=5.8.3,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libpython 3.13.16 hdc7f604_101_cp313 + - libsqlite >=3.53.4,<4.0a0 + - libuuid >=2.42.4,<3.0a0 + - libzlib >=1.3.2,<2.0a0 + - ncurses >=6.6,<7.0a0 + - openssl >=3.5.9,<4.0a0 + - python_abi 3.13.* *_cp313 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + license: Python-2.0 + purls: [] + run_exports: + weak: + - python_abi 3.13.* *_cp313 + noarch: + - python + size: 24430407 + timestamp: 1791334320130 + python_site_packages_path: lib/python3.13/site-packages +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.13.16-hf47f18c_102_cp313.conda + build_number: 102 + sha256: 38d9b47548304992a6bcdc9bc5472730a77178047ecee142381acf626984c5d2 + md5: d406e245f44390c9c3adcd86cda21899 + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.8.5,<3.0a0 + - libffi >=3.7.0,<3.8.0a0 + - libgcc >=15 + - liblzma >=5.8.3,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libpython 3.13.16 hd51c5da_102_cp313 + - libsqlite >=3.54.0,<4.0a0 + - libuuid >=2.42.4,<3.0a0 + - libzlib >=1.3.2,<2.0a0 + - ncurses >=6.6,<7.0a0 + - openssl >=3.5.9,<4.0a0 + - python_abi 3.13.* *_cp313 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + license: Python-2.0 + purls: [] + run_exports: + weak: + - python_abi 3.13.* *_cp313 + noarch: + - python + size: 24298220 + timestamp: 1791911488431 + python_site_packages_path: lib/python3.13/site-packages +- conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-2.14.1-cpu_mkl_py313_h7b4a8e5_100.conda + sha256: 154b2357b04753651586dac84fe2cdb673eb26aacf1c72d3eaa20620c2549cce + md5: 29ab64128c75eac81d31e6f35eab85ca + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex * *_llvm + - _openmp_mutex >=4.5 + - filelock + - fmt >=12.1.0,<12.2.0a0 + - fsspec >=0.8.5 + - jinja2 + - libabseil * cxx17* + - libabseil >=20260526.0,<20260527.0a0 + - libblas * *mkl + - libcblas >=3.11.0,<4.0a0 + - libgcc >=15 + - libprotobuf >=7.35.1,<7.35.2.0a0 + - libstdcxx >=15 + - libtorch 2.14.1 cpu_mkl_hc3c961d_100 + - libuv >=1.52.1,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - llvm-openmp >=23.1.3 + - mkl >=2026.1.0,<2027.0a0 + - networkx >=2.5.1 + - numpy >=1.25,<3 + - onednn >=3.13.4,<4.0a0 + - optree >=0.13.0 + - pybind11 + - pybind11-abi 11 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - scikit-build-core + - setuptools >=77.0.3 + - sleef >=3.9.0,<4.0a0 + - sympy >=1.13.3 + - typing_extensions >=4.13.0 + constrains: + - pytorch-cpu 2.14.1 + - pytorch-gpu <0.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/torch?source=hash-mapping + run_exports: + weak: + - pytorch >=2.14.1,<2.15.0a0 + - libtorch >=2.14.1,<2.15.0a0 + size: 29275859 + timestamp: 1791586803158 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pytorch-cpu-2.14.1-cpu_mkl_hd61e0f4_100.conda + sha256: c8a8b42cd076c41a7547c96ec9af52bb9d46f03465d83328913658ab57ab58d5 + md5: e3d9aa9875bf8cfc0a0f4eb6141d35f4 + depends: + - pytorch 2.14.1 cpu_mkl*100 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 56075 + timestamp: 1791587589299 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py313h3dea7bd_1.conda + sha256: ef7df29b38ef04ec67a8888a4aa039973eaa377e8c4b59a7be0a1c50cd7e4ac6 + md5: f256753e840c3cd3766488c9437a8f8b + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + run_exports: {} + size: 201616 + timestamp: 1770223543730 +- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-hd6e31c0_1.conda + sha256: 01b3fe073a66e321970d09e04b388708f8cbdca5cdfbfcb7c9eeb470ad10383d + md5: 69c01c781e7c8190bbdaf79e3848c5ca + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - ncurses >=6.6,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + run_exports: + weak: + - readline >=8.3,<9.0a0 + size: 348899 + timestamp: 1787033801506 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ruff-0.14.14-h40fa522_1.conda + noarch: python + sha256: 0c6c9825ff88195fd13936d63872213d6c88c1fe795d136881c0753c3037c5ff + md5: d3e1d08b141529c7fce6a13b4d670605 + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + run_exports: {} + size: 9131490 + timestamp: 1769520999080 +- conda: https://conda.anaconda.org/conda-forge/linux-64/sleef-3.9.0-ha0421bc_0.conda + sha256: 57afc2ab5bdb24cf979964018dddbc5dfaee130b415e6863765e45aed2175ee4 + md5: e8a0b4f5e82ecacffaa5e805020473cb + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + - libgcc >=14 + - libstdcxx >=14 + license: BSL-1.0 + purls: [] + run_exports: + weak: + - sleef >=3.9.0,<4.0a0 + size: 1951720 + timestamp: 1756274576844 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2023.1.0-haa8c3ee_2.conda + sha256: 5e47c01ea6c2bea041b78b4e45b3135b80bfc015533ec44e4c2d2326a755f8ba + md5: f805909f478c179e230baf7bd5878e26 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - libhwloc >=2.15.0,<2.15.1.0a0 + - libstdcxx >=15 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: {} + size: 186862 + timestamp: 1790591931727 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h1df4ec4_4.conda + build_number: 104 + sha256: a1a241d172c1ccab067ba245206dd048bc3c2d1b84504b53c9468e99adfc16a1 + md5: 676a2f9b1c4fcf57b70aa5c65f6c60d0 + depends: + - libgcc >=15 + - __glibc >=2.17,<3.0.a0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - xorg-libx11 >=1.8.13,<2.0a0 + license: TCL + purls: [] + run_exports: + weak: + - tk >=8.6.13,<8.7.0a0 + size: 3566806 + timestamp: 1787272857910 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ukkonen-1.1.0-py313h7037e92_0.conda + sha256: 7f2e4f38e57c17858c644259a1be868d6e98780239fd93bfa057cb5cfc24a928 + md5: cb423e0853b3dde2b3738db4dedf5ba2 + depends: + - __glibc >=2.17,<3.0.a0 + - cffi + - libgcc >=14 + - libstdcxx >=14 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + run_exports: {} + size: 14910 + timestamp: 1769438729201 +- conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-hebe6cf0_3.conda + sha256: d164dfa75ecd538f6fd68765defcc06aa875bc697b9b215362d79a2a73125dd0 + md5: e741576fb8f89821ac7c1c537322a33d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + license: MIT + license_family: MIT + run_exports: + weak: + - yaml >=0.2.5,<0.3.0a0 + size: 84936 + timestamp: 1787228426393 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_7.conda + sha256: 47d682b9f6d6ec9eb1a6e6c3e75ea6273e899e78fb7fc59f81d39745009fbc60 + md5: aa459086047c0e5e27023ab19f8cb86a + depends: + - __glibc >=2.17,<3.0.a0 + - libzlib >=1.3.2,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - zstd >=1.5.7,<1.6.0a0 + size: 601301 + timestamp: 1786599621503 +- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda + sha256: 2a7204314663eeda5dec482a956f0e2eaf289bd5b9953eaaaad0e81aa64638f2 + md5: 3845f3d75991bae0fb90884662f4327c + depends: + - cpython + - python-gil + license: MIT + license_family: MIT + run_exports: {} + size: 8144 + timestamp: 1784221492234 +- conda: https://conda.anaconda.org/conda-forge/noarch/accessible-pygments-0.0.5-pyhd8ed1ab_1.conda + sha256: 1307719f0d8ee694fc923579a39c0621c23fdaa14ccdf9278a5aac5665ac58e9 + md5: 74ac5069774cdbc53910ec4d631a3999 + depends: + - pygments + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 1326096 + timestamp: 1734956217254 +- conda: https://conda.anaconda.org/conda-forge/noarch/alabaster-1.0.0-pyhd8ed1ab_1.conda + sha256: 6c4456a138919dae9edd3ac1a74b6fbe5fd66c05675f54df2f8ab8c8d0cc6cea + md5: 1fd9696649f65fd6611fcdb4ffec738a + depends: + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 18684 + timestamp: 1733750512696 +- conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda + sha256: a14a9ad02101aab25570543a59c5193043b73dc311a25650134ed9e6cb691770 + md5: f1976ce927373500cc19d3c0b2c85177 + depends: + - python >=3.10 + - python + constrains: + - pytz >=2015.7 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 7684321 + timestamp: 1772555330347 +- conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.15.0-pyha770c72_0.conda + sha256: aed4b9dcf68ec2a75e5645fed14d77fd884d38d2e52bfa6ef4b278d90cd88781 + md5: 3b261da3fe9b4168738712832410b022 + depends: + - python >=3.10 + - soupsieve >=1.2 + - typing-extensions + license: MIT + license_family: MIT + run_exports: {} + size: 92704 + timestamp: 1780853175566 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + sha256: 0a0544cf95f64394fe4959286f5c71f5444ad58feb0602e53becb27448d24da6 + md5: 0f51e2391ade309db462a55611263e9c + depends: + - __unix + license: ISC + purls: [] + run_exports: {} + size: 131780 + timestamp: 1784754889428 +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + sha256: fb167de4388e64e52aa3907ed099afab944c1fa6e5f74b281a312dae1bcf7f3b + md5: 37e13edbe3b48f1095a9d085ef9cd83b + depends: + - python >=3.10 + license: ISC + run_exports: {} + size: 137015 + timestamp: 1784717699092 +- conda: https://conda.anaconda.org/conda-forge/noarch/cfgv-3.5.0-pyhd8ed1ab_0.conda + sha256: aa589352e61bb221351a79e5946d56916e3c595783994884accdb3b97fe9d449 + md5: 381bd45fb7aa032691f3063aff47e3a1 + depends: + - python >=3.10 + license: MIT + license_family: MIT + run_exports: {} + size: 13589 + timestamp: 1763607964133 +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda + sha256: cb60ef3e0631c8bacb4f7057196dee4496091a22baa3bb4b9bccb12c7e1c921b + md5: e0ac3accc64e23e40969d660e5f58ac8 + depends: + - python >=3.10 + license: MIT + license_family: MIT + run_exports: {} + size: 64487 + timestamp: 1786835648298 +- conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + sha256: ab29d57dc70786c1269633ba3dff20288b81664d3ff8d21af995742e2bb03287 + md5: 962b9857ee8e7018c22f2776ffa0b2d7 + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 27011 + timestamp: 1733218222191 +- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.16-py313hd8ed1ab_101.conda + noarch: generic + sha256: b50695cff97a76a961fe9837666c2f44c1a9048834bfc899b7945b5867af8ca1 + md5: 55a50f3a95f8622b6a2cf0373860fc53 + depends: + - python >=3.13,<3.14.0a0 + - python_abi * *_cp313 + license: Python-2.0 + run_exports: {} + size: 49892 + timestamp: 1791332598898 +- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.13.16-py313hd8ed1ab_102.conda + noarch: generic + sha256: 21e80bd39d647ccdeac2a6ab042d4f9cf66963b4696c6bb9ba052f445a6100e4 + md5: f06b4e55916af895989a48ea4c2732c1 + depends: + - python >=3.13,<3.14.0a0 + - python_abi * *_cp313 + license: Python-2.0 + purls: [] + run_exports: {} + size: 49980 + timestamp: 1791910441528 +- conda: https://conda.anaconda.org/conda-forge/noarch/distlib-0.4.3-pyhcf101f3_0.conda + sha256: e2753997b8bd34205f42be01b8bab8037423dc30c02a1ec12de23e5b4c0b0a2e + md5: 58638f77697c4f6726753eb8be34818b + depends: + - python >=3.10 + - python + license: Apache-2.0 + license_family: APACHE + run_exports: {} + size: 303705 + timestamp: 1781320269259 +- conda: https://conda.anaconda.org/conda-forge/noarch/docutils-0.21.2-pyhd8ed1ab_1.conda + sha256: fa5966bb1718bbf6967a85075e30e4547901410cc7cb7b16daf68942e9a94823 + md5: 24c1ca34138ee57de72a943237cde4cc + depends: + - python >=3.9 + license: CC-PDDC AND BSD-3-Clause AND BSD-2-Clause AND ZPL-2.1 + run_exports: {} + size: 402700 + timestamp: 1733217860944 +- conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + sha256: ee6cf346d017d954255bbcbdb424cddea4d14e4ed7e9813e429db1d795d01144 + md5: 8e662bd460bda79b1ea39194e3c4c9ab + depends: + - python >=3.10 + - typing_extensions >=4.6.0 + license: MIT and PSF-2.0 + purls: + - pkg:pypi/exceptiongroup?source=hash-mapping + run_exports: {} + size: 21333 + timestamp: 1763918099466 +- conda: https://conda.anaconda.org/conda-forge/noarch/filelock-4.1.1-pyhd8ed1ab_0.conda + sha256: 744790edc27f622ffa20af6029b8f628e8b7e0993ca1dbdafcf015de6c1fad6f + md5: fbff0cf6c30bdd9b38d0448fdadfe72d + depends: + - python >=3.11 + license: MIT + license_family: MIT + purls: + - pkg:pypi/filelock?source=compressed-mapping + run_exports: {} + size: 102017 + timestamp: 1791739638282 +- conda: https://conda.anaconda.org/conda-forge/noarch/fsspec-2026.9.0-pyhd8ed1ab_0.conda + sha256: 7d17272012e7479ba58f8a72b522d3e88e6837a04a111a278ff417aa82364255 + md5: 42de5cd170647599a84f468a42fabb89 + depends: + - python >=3.11 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/fsspec?source=compressed-mapping + run_exports: {} + size: 163722 + timestamp: 1790256848815 +- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.1-pyhcf101f3_0.conda + sha256: 307dd6ec90140c3cf4171071b0e5e870abec314f4565c1edd5bc433e942cdcc0 + md5: e652ac7756069c456d0da2a922cd7df5 + depends: + - python >=3.10 + - hyperframe >=6.1,<7 + - hpack >=4.2,<5 + - python + license: MIT + license_family: MIT + run_exports: {} + size: 100789 + timestamp: 1785796355216 +- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda + sha256: fdcea5d7cb314485d3907192ef024c704311548c5b0cbeb390cd1951051e29d2 + md5: b395909221b9bd1df066e5930e18855b + depends: + - python >=3.10 + license: MIT + license_family: MIT + run_exports: {} + size: 32884 + timestamp: 1782283986153 +- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + sha256: 77af6f5fe8b62ca07d09ac60127a30d9069fdc3c68d6b256754d0ffb1f7779f8 + md5: 8e6923fc12f1fe8f8c4e5c9f343256ac + depends: + - python >=3.9 + license: MIT + license_family: MIT + run_exports: {} + size: 17397 + timestamp: 1737618427549 +- conda: https://conda.anaconda.org/conda-forge/noarch/identify-2.6.20-pyhd8ed1ab_0.conda + sha256: 6375ae2e28de130843ab7ca98132bf83d988f7fd7034e3dbe646308dbeaca2fd + md5: be9ea4f030b4b2bc121b2a548b066c0b + depends: + - python >=3.11 + - ukkonen + license: MIT + license_family: MIT + run_exports: {} + size: 79783 + timestamp: 1790540591548 +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.20-pyh5ded981_0.conda + sha256: e044b1ec829e2e24f971fa3f3408eb26f1db836e521a58f08b0fd9a03fee47be + md5: ee73be987f9de9ea5c2fdaeefa7b6491 + depends: + - python >=3.11 + - python + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 178572 + timestamp: 1790020377150 +- conda: https://conda.anaconda.org/conda-forge/noarch/imagesize-2.0.1-pyhd8ed1ab_0.conda + sha256: 4e787f9ccc31053ccea56d98ecd284a4f788988a3f9c4627db72fdd0b127529b + md5: ebc56022e4ef7e74a829be94c53797ca + depends: + - python >=3.10 + license: MIT + license_family: MIT + run_exports: {} + size: 21467 + timestamp: 1787649248672 +- conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.3.1-pyhd8ed1ab_0.conda + sha256: 8f53876d0fb3525235e39635dd55633abe0a4307b1b3ab1415c80a9e0bbdf663 + md5: c0a803d97dd09da7e2c8b0fdfaa5739b + depends: + - python >=3.11 + license: MIT + license_family: MIT + run_exports: {} + size: 14485 + timestamp: 1791588553866 +- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + sha256: fc9ca7348a4f25fed2079f2153ecdcf5f9cf2a0bc36c4172420ca09e1849df7b + md5: 04558c96691bed63104678757beb4f8d + depends: + - markupsafe >=2.0 + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jinja2?source=hash-mapping + run_exports: {} + size: 120685 + timestamp: 1764517220861 +- conda: https://conda.anaconda.org/conda-forge/noarch/mpmath-1.4.1-pyhd8ed1ab_0.conda + sha256: 5bbf2f8179ec43d34d67ca8e4989d216c1bdb4b749fe6cb40e86ebf88c1b5300 + md5: 2e81b32b805f406d23ba61938a184081 + depends: + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/mpmath?source=hash-mapping + run_exports: {} + size: 464918 + timestamp: 1773662068273 +- conda: https://conda.anaconda.org/conda-forge/noarch/networkx-3.7-pyhc364b38_0.conda + sha256: 9458e13767779d5db48c035ba27a1af843fde5187d34c17b60076e18835e8637 + md5: ddc679d85e7c9c3738efd303b6580c69 + depends: + - python >=3.12 + - python + constrains: + - numpy >=1.25 + - scipy >=1.11.2 + - matplotlib-base >=3.8 + - pandas >=2.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/networkx?source=compressed-mapping + run_exports: {} + size: 1642541 + timestamp: 1790068597221 +- conda: https://conda.anaconda.org/conda-forge/noarch/nodeenv-1.10.0-pyhd8ed1ab_0.conda + sha256: 4fa40e3e13fc6ea0a93f67dfc76c96190afd7ea4ffc1bac2612d954b42cdc3ee + md5: eb52d14a901e23c39e9e7b4a1a5c015f + depends: + - python >=3.10 + - setuptools + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 40866 + timestamp: 1766261270149 +- conda: https://conda.anaconda.org/conda-forge/noarch/nomkl-1.0-h5ca1d4c_0.tar.bz2 + sha256: d38542a151a90417065c1a234866f97fd1ea82a81de75ecb725955ab78f88b4b + md5: 9a66894dfd07c4510beb6b3f9672ccc0 + constrains: + - mkl <0.a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 3843 + timestamp: 1582593857545 +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda + sha256: c432626b16768b8dab228bfb706f7060c2d462a21c516d240f68f2f902b5a044 + md5: 936687ed80f295a1f5dbcf8bd34c252c + depends: + - python >=3.9 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/packaging?source=hash-mapping + run_exports: {} + size: 116363 + timestamp: 1785888127370 +- conda: https://conda.anaconda.org/conda-forge/noarch/pathspec-1.1.1-pyhd8ed1ab_0.conda + sha256: 6eaee417d33f298db79bc7185ab1208604c0e6cf51dade34cd513c6f9db9c6f3 + md5: 11adc78451c998c0fd162584abfa3559 + depends: + - python >=3.10 + license: MPL-2.0 + license_family: MOZILLA + purls: + - pkg:pypi/pathspec?source=hash-mapping + run_exports: {} + size: 56559 + timestamp: 1777271601895 +- conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.13.0-pyh5ded981_0.conda + sha256: e75e5e85e0039c77f8b707baa862f029f25a582ff158bcae36cf361a409eaf59 + md5: 35cc2a176fc6f886d6efa41ecb5f1de0 + depends: + - python >=3.11 + - python + license: MIT + license_family: MIT + run_exports: {} + size: 34027 + timestamp: 1791727231159 +- conda: https://conda.anaconda.org/conda-forge/noarch/pluggy-1.6.0-pyhf9edf01_1.conda + sha256: e14aafa63efa0528ca99ba568eaf506eb55a0371d12e6250aaaa61718d2eb62e + md5: d7585b6550ad04c8c5e21097ada2888e + depends: + - python >=3.9 + - python + license: MIT + license_family: MIT + run_exports: {} + size: 25877 + timestamp: 1764896838868 +- conda: https://conda.anaconda.org/conda-forge/noarch/pre-commit-4.6.2-pyha770c72_0.conda + sha256: fc0a0620a8f829c46787a9a13ddbae8b6049f2e77da353a8a2adaa01af0da7e2 + md5: c36b88db560b4e74f294380613f1a239 + depends: + - cfgv >=2.0.0 + - identify >=1.0.0 + - nodeenv >=0.11.1 + - python >=3.10 + - pyyaml >=5.1 + - virtualenv >=20.10.0 + license: MIT + license_family: MIT + run_exports: {} + size: 201879 + timestamp: 1786408353117 +- conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-3.0.4-pyh293190f_0.conda + sha256: f213f735f0c2b9bfcc1358c8bdeb0dfcf3614bf2c47aad68227dabea99b0e0d7 + md5: 2e57132f130bffcb5a8b17092b2871bc + depends: + - python >=3.8 + - pybind11-global ==3.0.4 *_0 + - python + constrains: + - pybind11-abi ==11 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pybind11?source=compressed-mapping + run_exports: {} + size: 251088 + timestamp: 1786199539429 +- conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-abi-11-hc364b38_1.conda + sha256: 9e7fe12f727acd2787fb5816b2049cef4604b7a00ad3e408c5e709c298ce8bf1 + md5: f0599959a2447c1e544e216bddf393fa + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - pybind11-abi ==11 + size: 14671 + timestamp: 1752769938071 +- conda: https://conda.anaconda.org/conda-forge/noarch/pybind11-global-3.0.4-pyh648e204_0.conda + sha256: 25e887672e68188a5fa899efdc116acb01d689989493342aac05781ec1f09b81 + md5: e594cb485091100204e0b77cb5709896 + depends: + - python >=3.8 + - __unix + - python + constrains: + - pybind11-abi ==11 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pybind11-global?source=compressed-mapping + run_exports: {} + size: 244363 + timestamp: 1786199539429 +- conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-3.11-pyh5ded981_0.conda + sha256: 99af7c6061d7aa73cfcef615500ddf3e9f4a58c844646b7d7ce58147fb5758d7 + md5: 3cd164745122ab557cc30879390089e8 + depends: + - python >=3.11 + - python + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 58624 + timestamp: 1791592610066 +- conda: https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.15.4-pyhd8ed1ab_0.conda + sha256: 5ec877142ded763061e114e787a4e201c2fb3f0b1db2f04ace610a1187bb34ae + md5: c7c50dd5192caa58a05e6a4248a27acb + depends: + - accessible-pygments + - babel + - beautifulsoup4 + - docutils !=0.17.0 + - packaging + - pygments >=2.7 + - python >=3.9 + - sphinx >=5.0 + - typing_extensions + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 1393462 + timestamp: 1719344980505 +- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.21.0-pyhcf101f3_0.conda + sha256: f5f015ff1bc3e1b7fc08eee096b1865234189ae0b82bebdb86a5369116e42fa0 + md5: 2882dee445dfa45b0c5afce3ffb7730a + depends: + - python >=3.10 + - python + license: BSD-2-Clause + license_family: BSD + run_exports: {} + size: 959376 + timestamp: 1786995678795 +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + sha256: ba3b032fa52709ce0d9fd388f63d330a026754587a2f461117cac9ab73d8d0d8 + md5: 461219d1a5bd61342293efa2c0c90eac + depends: + - __unix + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 21085 + timestamp: 1733217331982 +- conda: https://conda.anaconda.org/conda-forge/noarch/pytest-9.1.1-pyhc364b38_2.conda + sha256: 430051d80765207a7d782b2b188230ba1489d35c6e75fd9903f76cb9fda4af16 + md5: 64c98a12c4e23eb238bf66bbecafdf3c + depends: + - colorama + - pygments >=2.7.2 + - python >=3.10 + - iniconfig >=1.0.1 + - packaging >=22 + - pluggy >=1.5,<2 + - tomli >=1 + - exceptiongroup >=1 + - python + constrains: + - pytest-faulthandler >=2 + license: MIT + license_family: MIT + run_exports: {} + size: 306724 + timestamp: 1782127176429 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-discovery-1.6.2-pyh5ded981_0.conda + sha256: 0d5fa564eae1e90cde8fbf71216f08f13e82791e02e38ade728ad2e8a01b88d8 + md5: 17e6c8b2fc2d021a217d317fe4f89fc4 + depends: + - python >=3.11 + - filelock >=3.16.1 + - python + license: MIT + license_family: MIT + run_exports: {} + size: 39339 + timestamp: 1791631961919 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.13.16-h4df99d1_101.conda + sha256: f8916e4d2625b894cb25dac41396ab315b6f8fefa45c89172d8a962c784c1598 + md5: 432007147854d0bcda8ba02040666756 + depends: + - cpython 3.13.16.* + - python_abi * *_cp313 + license: Python-2.0 + run_exports: {} + size: 49925 + timestamp: 1791332629849 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.13-9_cp313.conda + build_number: 9 + sha256: 9dc0cb6a20ebc7e9bcd8c6868f2bec083c114b0ba20b7118405c036533d5c522 + md5: c5abc97af551bc12d71305852392f75c + constrains: + - python 3.13.* *_cp313 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 6769 + timestamp: 1788302828005 +- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + sha256: 1715246b19c9f85ee022933b4845f2fc14ac9184981b7b7d9b728bec8e9588da + md5: 4a85203c1d80c1059086ae860836ffb9 + depends: + - python >=3.10 + - certifi >=2023.5.7 + - charset-normalizer >=2,<4 + - idna >=2.5,<4 + - urllib3 >=1.26,<3 + - python + constrains: + - chardet >=3.0.2,<8 + license: Apache-2.0 + license_family: APACHE + run_exports: {} + size: 68709 + timestamp: 1778851103479 +- conda: https://conda.anaconda.org/conda-forge/noarch/roman-numerals-4.1.0-pyhd8ed1ab_0.conda + sha256: 30f3c04fcfb64c44d821d392a4a0b8915650dbd900c8befc20ade8fde8ec6aa2 + md5: 0dc48b4b570931adc8641e55c6c17fe4 + depends: + - python >=3.10 + license: 0BSD OR CC0-1.0 + run_exports: {} + size: 13814 + timestamp: 1766003022813 +- conda: https://conda.anaconda.org/conda-forge/noarch/roman-numerals-py-4.1.0-pyhd8ed1ab_0.conda + sha256: ce21b50a412b87b388db9e8dfbf8eb16fc436c23750b29bf612ee1a74dd0beb2 + md5: 28687768633154993d521aecfa4a56ac + depends: + - python >=3.10 + - roman-numerals 4.1.0 + license: 0BSD OR CC0-1.0 + run_exports: {} + size: 11074 + timestamp: 1766025162370 +- conda: https://conda.anaconda.org/conda-forge/noarch/scikit-build-core-1.1.1-pyhcb39dcd_0.conda + sha256: bb3edbe12bdaf6f3283d6567ae180225afb89e85d80f75cc77f5e44dc09d9b8c + md5: 43b4de2a8d4a9c164697d6a44ebfb94c + depends: + - python >=3.9 + - exceptiongroup >=1.0 + - packaging >=23.2 + - pathspec >=0.12.0 + - tomli >=1.2.2 + - typing_extensions >=4 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/scikit-build-core?source=compressed-mapping + run_exports: {} + size: 365250 + timestamp: 1790997173721 +- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda + sha256: 9e200ee5f9ff19a4d94e4b51c4856d53dec849f91032f345cf0c6bc3d51a7183 + md5: 62ac906f1cd582c6c264c95625cb9d6f + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/setuptools?source=compressed-mapping + run_exports: {} + size: 524488 + timestamp: 1786282924579 +- conda: https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-3.1.1-pyhd8ed1ab_0.conda + sha256: ad89284ea94821c20ff87e64b948e4afc690cf5202d14c009355b0594cf23aea + md5: 46b6abe31482f6bca064b965696ae807 + depends: + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 74456 + timestamp: 1780468201547 +- conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-3.0.3-pyhd8ed1ab_0.conda + sha256: 858ae626017ccba80201e7431db6b517e53d6322b32f00fd21b64159592fa7cd + md5: bd2ea090567b9d0a53240bd1367c87ad + depends: + - python >=3.11 + license: MIT + run_exports: {} + size: 43039 + timestamp: 1791821664697 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-8.2.3-pyhd8ed1ab_0.conda + sha256: 995f58c662db0197d681fa345522fd9e7ac5f05330d3dff095ab2f102e260ab0 + md5: f7af826063ed569bb13f7207d6f949b0 + depends: + - alabaster >=0.7.14 + - babel >=2.13 + - colorama >=0.4.6 + - docutils >=0.20,<0.22 + - imagesize >=1.3 + - jinja2 >=3.1 + - packaging >=23.0 + - pygments >=2.17 + - python >=3.11 + - requests >=2.30.0 + - roman-numerals-py >=1.0.0 + - snowballstemmer >=2.2 + - sphinxcontrib-applehelp >=1.0.7 + - sphinxcontrib-devhelp >=1.0.6 + - sphinxcontrib-htmlhelp >=2.0.6 + - sphinxcontrib-jsmath >=1.0.1 + - sphinxcontrib-qthelp >=1.0.6 + - sphinxcontrib-serializinghtml >=1.1.9 + license: BSD-2-Clause + license_family: BSD + run_exports: {} + size: 1424416 + timestamp: 1740956642838 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-book-theme-1.1.4-pyh29332c3_0.conda + sha256: 78581f1ba538186fc4129191a8db4ee7798382b6b4a1a0c55dedb437da1a9fd8 + md5: f3d3f4e7e2c9198e88cd524633665081 + depends: + - pydata-sphinx-theme ==0.15.4 + - python >=3.9 + - sphinx >=6.1 + - python + license: BSD-3-Clause + license_family: BSD + run_exports: {} + size: 255445 + timestamp: 1740145414720 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-2.0.0-pyhd8ed1ab_1.conda + sha256: d7433a344a9ad32a680b881c81b0034bc61618d12c39dd6e3309abeffa9577ba + md5: 16e3f039c0aa6446513e94ab18a8784b + depends: + - python >=3.9 + - sphinx >=5 + license: BSD-2-Clause + license_family: BSD + run_exports: {} + size: 29752 + timestamp: 1733754216334 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-2.0.0-pyhd8ed1ab_1.conda + sha256: 55d5076005d20b84b20bee7844e686b7e60eb9f683af04492e598a622b12d53d + md5: 910f28a05c178feba832f842155cbfff + depends: + - python >=3.9 + - sphinx >=5 + license: BSD-2-Clause + license_family: BSD + run_exports: {} + size: 24536 + timestamp: 1733754232002 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.1.0-pyhd8ed1ab_1.conda + sha256: c1492c0262ccf16694bdcd3bb62aa4627878ea8782d5cd3876614ffeb62b3996 + md5: e9fb3fe8a5b758b4aff187d434f94f03 + depends: + - python >=3.9 + - sphinx >=5 + license: BSD-2-Clause + license_family: BSD + run_exports: {} + size: 32895 + timestamp: 1733754385092 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-pyhd8ed1ab_1.conda + sha256: 578bef5ec630e5b2b8810d898bbbf79b9ae66d49b7938bcc3efc364e679f2a62 + md5: fa839b5ff59e192f411ccc7dae6588bb + depends: + - python >=3.9 + license: BSD-2-Clause + license_family: BSD + run_exports: {} + size: 10462 + timestamp: 1733753857224 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-2.0.0-pyhd8ed1ab_1.conda + sha256: c664fefae4acdb5fae973bdde25836faf451f41d04342b64a358f9a7753c92ca + md5: 00534ebcc0375929b45c3039b5ba7636 + depends: + - python >=3.9 + - sphinx >=5 + license: BSD-2-Clause + license_family: BSD + run_exports: {} + size: 26959 + timestamp: 1733753505008 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-2.0.0-pyhd8ed1ab_0.conda + sha256: 20b49741065fd7d3fabf98caf6d19b6436badb06b6d41f66b58f1fc2b52f37a1 + md5: f77df1fcf9af03b7287342638befca77 + depends: + - python >=3.10 + - sphinx >=5 + license: BSD-2-Clause + license_family: BSD + run_exports: {} + size: 30640 + timestamp: 1781260357443 +- conda: https://conda.anaconda.org/conda-forge/noarch/sympy-1.14.0-pyh2585a3b_106.conda + sha256: 1c8057e6875eba958aa8b3c1a072dc9a75d013f209c26fd8125a5ebd3abbec0c + md5: 32d866e43b25275f61566b9391ccb7b5 + depends: + - __unix + - cpython + - gmpy2 >=2.0.8 + - mpmath >=1.1.0,<1.5 + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/sympy?source=hash-mapping + run_exports: {} + size: 4661767 + timestamp: 1771952371059 +- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.5.0-pyh5ded981_0.conda + sha256: 93b6f517fb7adff00b62be02b798d384a1b34d0e368fdb576fb248bcb3bb9ec7 + md5: 36d6a162326c52d6339c8b606f43932d + depends: + - python >=3.11 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/tomli?source=compressed-mapping + run_exports: {} + size: 21687 + timestamp: 1791394576814 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + sha256: b141933ece3518f6d7b75dfb59451e2f26b405a44c18e2518a83e9a02e09315c + md5: c680b5747e8c4c8f23dca0bb7042a8fc + depends: + - typing_extensions ==4.16.0 pyhcf101f3_0 + license: PSF-2.0 + license_family: PSF + purls: [] + run_exports: {} + size: 94080 + timestamp: 1783002732887 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + sha256: 2d888f90af0686044882c74193ec80a90ec1943145d94a7b1b048958acda1848 + md5: c70ad746c22219b9700931707482992c + depends: + - python >=3.10 + - python + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/typing-extensions?source=hash-mapping + run_exports: {} + size: 52631 + timestamp: 1783002732887 +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + sha256: b928c30ddcb0e3f544c6eade8352737e6e610e263276b90232db6a578ef899d8 + md5: fcb489df604d100968b737f2cb6076c6 + license: LicenseRef-Public-Domain + purls: [] + run_exports: {} + size: 118849 + timestamp: 1784250406640 +- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.8.0-pyhd8ed1ab_0.conda + sha256: c5511c190ab55168ca412f3c54592fbf677ae6250784cb72b227a7cf3f538f7d + md5: 407a3d3778570bae8e8fcf554e803e43 + depends: + - backports.zstd >=1.0.0 + - brotli-python >=1.2.0 + - h2 >=4,<5 + - pysocks >=1.5.6,<2.0,!=1.5.7 + - python >=3.11 + license: MIT + license_family: MIT + run_exports: {} + size: 107485 + timestamp: 1789566710244 +- conda: https://conda.anaconda.org/conda-forge/noarch/virtualenv-21.14.7-pyh5ded981_0.conda + sha256: 74caaf8d70f0858d99a1f480e5658839b09f86ee947d9907f676683f84062abe + md5: 1635534aa129eb9ab6ce5e01929d86ff + depends: + - python >=3.11 + - distlib >=0.4.3,<1 + - filelock >=3.24.2,<5 + - packaging >=26.3 + - platformdirs >=4.4,<5 + - python-discovery >=1.6.1 + - python + license: MIT + license_family: MIT + run_exports: {} + size: 3995083 + timestamp: 1791784412547 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-8_kmp_llvm.conda + build_number: 8 + sha256: c7c98ce74f6a7ff25aaa4706d06fa41d63a333add4d697b73aa4d8d2d7ad7651 + md5: a2d706b4a7d603524133037c34f7fb76 + depends: + - llvm-openmp >=9.0.1 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - _openmp_mutex >=4.5 + size: 8016 + timestamp: 1788046437162 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/backports.zstd-1.8.0-py313h8c7ad59_0.conda + sha256: 527968bdd299cf54f49dd88f6543470b1502cbc53a09ff128b8f75867154f436 + md5: adec8816f4cda3cd23f02f8f49964461 + depends: + - python + - __osx >=11.0 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.13.* *_cp313 + license: BSD-3-Clause AND MIT AND EPL-2.0 + run_exports: {} + size: 242793 + timestamp: 1791654845115 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py313h3c37e74_4.conda + sha256: 9f0d62630225a681b618685c29fd073436654717693344a3b2ce36d1cecebcb8 + md5: 1d46a945b575d98e60f2259e14140c3c + depends: + - __osx >=11.0 + - libcxx >=21 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + constrains: + - libbrotlicommon 1.2.0 h1dcdb26_4 + license: MIT + license_family: MIT + run_exports: {} + size: 365453 + timestamp: 1788480413282 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h4e30115_10.conda + sha256: 8ec22f0ba25cbfc2e64d70cf29459eccd7ffdf6436f6a6ff15bbfef799f7d4f6 + md5: b50612e7d190b8061ab4e7dc119cf4d5 + depends: + - __osx >=11.0 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + run_exports: + weak: + - bzip2 >=1.0.8,<2.0a0 + size: 124965 + timestamp: 1785906749812 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.1.1-py313h056b358_3.conda + sha256: be442fc43c5e77e4ff78c443c4026cf404a956fb90febfad89156d054eebd444 + md5: db79aff26a4e63a605a91b76d91573b7 + depends: + - __osx >=11.0 + - libffi >=3.7.0,<3.8.0a0 + - pycparser + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + run_exports: {} + size: 290137 + timestamp: 1788485303346 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/fmt-12.1.0-h99e4e11_1.conda + sha256: ca42427b99ff92228e907bed5f10a1df868180997571d29c230faad2c38e35fd + md5: 891b1202d7b835f215e26a4db685ec7c + depends: + - __osx >=11.0 + - libcxx >=19 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - fmt >=12.1.0,<12.2.0a0 + size: 181008 + timestamp: 1785915450316 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/gmp-6.3.0-hf5e55b1_3.conda + sha256: 980cb1bbc3656d6f923d44297a2c4ab75f20482b8930c70537fd7f1ef2378894 + md5: bbfa5bd261b68536ddcda39e03d3407f + depends: + - libcxx >=19 + - __osx >=11.0 + license: GPL-2.0-or-later OR LGPL-3.0-or-later + purls: [] + run_exports: + weak: + - gmp >=6.3.0,<7.0a0 + size: 399307 + timestamp: 1786629210135 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/gmpy2-2.3.2-py313h0091f1d_0.conda + sha256: a979a7088dfd128f9e6418767ab1d1a34910bb0824219a5895150af586a4da0b + md5: fd6b154752e536062cc670f445847974 + depends: + - python + - __osx >=11.0 + - mpc >=1.4.0,<2.0a0 + - python_abi 3.13.* *_cp313 + - mpfr >=4.2.2,<5.0a0 + - gmp >=6.3.0,<7.0a0 + license: LGPL-3.0-or-later + license_family: LGPL + purls: + - pkg:pypi/gmpy2?source=compressed-mapping + run_exports: {} + size: 253145 + timestamp: 1791167016923 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libabseil-20260526.0-cxx17_h4c27e2a_3.conda + sha256: 27859d85276442961b01bcfc488dc85426109330ffb157d3224772a7eaa72a75 + md5: 009ec10961e8426182e9d4c33e31db55 + depends: + - __osx >=11.0 + - libcxx >=19 + constrains: + - abseil-cpp =20260526.0 + - libabseil-static =20260526.0=cxx17* + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - libabseil >=20260526.0,<20260527.0a0 + - libabseil =*=cxx17* + size: 1277648 + timestamp: 1791681045738 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-11_h51639a9_openblas.conda + build_number: 11 + sha256: 718c3e1a16ad946caeeba041520dc18b7af461226bd8c28faef78b181920dbb4 + md5: 364639b771dbb4ede335d965f55a23ac + depends: + - libopenblas >=0.3.34,<0.3.35.0a0 + - libopenblas >=0.3.34,<1.0a0 + constrains: + - blas 2.311 openblas + - libcblas 3.11.0 11*_openblas + - liblapack 3.11.0 11*_openblas + - liblapacke 3.11.0 11*_openblas + - mkl <2027 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libblas >=3.11.0,<4.0a0 + size: 18300 + timestamp: 1789061064192 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-11_hb0561ab_openblas.conda + build_number: 11 + sha256: 3cebd672d8bd7c776a381422ea3fc48131343aeda2879974734715ac6a05c292 + md5: 73bde7d713e790b3168707e02102e87b + depends: + - libblas 3.11.0 11_h51639a9_openblas + constrains: + - blas 2.311 openblas + - liblapack 3.11.0 11*_openblas + - liblapacke 3.11.0 11*_openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libcblas >=3.11.0,<4.0a0 + size: 18324 + timestamp: 1789061068736 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-23.1.3-h55c6f16_0.conda + sha256: 2d9d31172d7919bd276a8d1be338de567dd5afdd181e8d3d3d8c2d8695e09de2 + md5: 424521e52e65ef7f6cf957c4966e6386 + depends: + - __osx >=11.0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + run_exports: {} + size: 577501 + timestamp: 1791337239735 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.5-h6111c0a_0.conda + sha256: 50aa761b83fc7a116ca88fe0cb4cefce5abababb10a926bf8afbafd5c298d102 + md5: 5383c550b785bdaafcc4eaf638071a7c + depends: + - __osx >=11.0 + constrains: + - expat 2.8.5.* + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 69565 + timestamp: 1790370026495 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.7.0-h47dc5ef_1.conda + sha256: 2783389a7b9dda04c62cc54c6bbeb03dd3cef24b3fc642715d02a3fa19464a9a + md5: 216bbcc23c11e9695b3db4a8771d76eb + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libffi >=3.7.0,<3.8.0a0 + size: 43637 + timestamp: 1787753403688 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-16.2.0-h3cf6597_7.conda + sha256: deacf62bd6cbbe05eb085dd69a2ca120053597c37066d53c8f7cf6815f26e9eb + md5: f100c8baeb0c816052b0d43948555bf6 + depends: + - _openmp_mutex + constrains: + - libgcc-ng ==16.2.0=*_7 + - libgomp 16.2.0 7 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + run_exports: {} + size: 365973 + timestamp: 1790301995634 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-16.2.0-h07b0088_7.conda + sha256: a5b4b63d0ef4c7c59c37225b9f0307b5cd2a3a8499b2a40b6ddd2b9f072712a8 + md5: 2efa9518e126c501a7c7d638b2fa3a21 + depends: + - libgfortran5 16.2.0 hdb7a957_7 + constrains: + - libgfortran-ng ==16.2.0=*_7 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + run_exports: {} + size: 99745 + timestamp: 1790302083840 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-16.2.0-hdb7a957_7.conda + sha256: 19297e5cf39de976cdd1a46499a4e15fdf54d249d3231f3b2a9d43af3be0bc66 + md5: c9d80dd8775b8fe76e825d63d8ccaf1b + depends: + - libgcc >=16.2.0 + constrains: + - libgfortran 16.2.0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + run_exports: {} + size: 554793 + timestamp: 1790302001166 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-11_hd9741b5_openblas.conda + build_number: 11 + sha256: e7697df761eeb243272801a8c883dc1de6018a7f738746b443ef1396f0a78075 + md5: d67ef1462e77d20cd3e0d27c876c3c9a + depends: + - libblas 3.11.0 11_h51639a9_openblas + constrains: + - blas 2.311 openblas + - libcblas 3.11.0 11*_openblas + - liblapacke 3.11.0 11*_openblas + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - liblapack >=3.11.0,<3.12.0a0 + size: 18284 + timestamp: 1789061072707 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_1.conda + sha256: 23d0630046a3e8b164d8f80f2b74ed2605af2e7050ab9913018056402fae4311 + md5: 8ab10323068b107661a4b9a4af84f3b5 + depends: + - __osx >=11.0 + constrains: + - xz 5.8.3.* + license: 0BSD + purls: [] + run_exports: + weak: + - liblzma >=5.8.3,<6.0a0 + size: 91720 + timestamp: 1786348695846 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_2.conda + sha256: 04cc136c5a956a73aa14e0a160b5822b0b29714e67b299fa1b1fd16dbcba5366 + md5: ff33a4dbd93abc8a798cc4e0e7c8136d + depends: + - __osx >=11.0 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 73289 + timestamp: 1786651074391 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libopenblas-0.3.34-openmp_h5e6e99c_1.conda + sha256: 18f167fdaa26f67a123c59766f4730b41866d2c0920b5c29d74a93d3a2e92a96 + md5: 9c874eb9d4441a658ef3acf6ac56dc3e + depends: + - __osx >=11.0 + - libgcc >=15 + - libgfortran + - libgfortran5 >=15.3.0 + - llvm-openmp >=21.1.8 + constrains: + - openblas >=0.3.34,<0.3.35.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libopenblas >=0.3.34,<1.0a0 + size: 4249236 + timestamp: 1789023664380 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libprotobuf-7.35.1-h391e224_3.conda + sha256: 4129c13739e30dfbab8c20594acd8c182de2008f4dfc94d54f52b556eb39050c + md5: 7666c238f885d0c7927607ebfc163f97 + depends: + - __osx >=12.0 + - libabseil * cxx17* + - libabseil >=20260526.0,<20260527.0a0 + - libcxx >=21 + - libzlib >=1.3.2,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libprotobuf >=7.35.1,<7.35.2.0a0 + size: 2877232 + timestamp: 1787657252942 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpython-3.13.16-h0af5753_102_cp313.conda + build_number: 102 + sha256: 245a479d398db406d8391081e7da897e4f161718b2c7cc81cd9eb177ff6ccd98 + md5: cd21726321310e0ed0261edf5c1129ee + depends: + - __osx >=11.0 + license: Python-2.0 + purls: [] + run_exports: + weak: + - libpython >=3.13,<4.0a0 + size: 1814014 + timestamp: 1791910695132 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpython-3.13.16-hb350d79_101_cp313.conda + build_number: 101 + sha256: dbae2e688e271f140bf5678257c7f671f06b76045b90418f086427e4dc7c2eca + md5: bd71eafbdb134f540ae4aacd52ecf977 + depends: + - __osx >=11.0 + - libcxx >=21 + license: Python-2.0 + purls: [] + run_exports: + weak: + - libpython >=3.13,<4.0a0 + size: 1816892 + timestamp: 1791332817737 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.54.0-h34a968b_101.conda + sha256: 7f0f2e4ca2eb6e5059d9593ffe88088debe453b146e2794641d94242b901d85c + md5: 42b7a7607b4c9838cc2e72a232af6809 + depends: + - __osx >=11.0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - icu <0.0a0 + license: blessing + purls: [] + run_exports: + weak: + - libsqlite >=3.54.0,<4.0a0 + size: 949146 + timestamp: 1791584347793 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtorch-2.14.1-cpu_generic_h8539c7d_0.conda + sha256: 611136e49805a77e6c37888a9f9615b9649bff7344adfc4b25df84628b128e4f + md5: 5e8ed7a3ce0988cb2730c298c8f16435 + depends: + - __osx >=12.0 + - fmt >=12.1.0,<12.2.0a0 + - libabseil * cxx17* + - libabseil >=20260526.0,<20260527.0a0 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - libcxx >=21 + - liblapack >=3.9.0,<4.0a0 + - libprotobuf >=7.35.1,<7.35.2.0a0 + - libuv >=1.52.1,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - llvm-openmp >=21.1.8 + - onednn >=3.13.4,<4.0a0 + - pybind11-abi 11 + - sleef >=3.9.0,<4.0a0 + constrains: + - libopenblas * openmp_* + - openblas * openmp_* + - pytorch 2.14.1 cpu_generic_*_0 + - pytorch-cpu 2.14.1 + - pytorch-gpu <0.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libtorch >=2.14.1,<2.15.0a0 + size: 35580021 + timestamp: 1791586159933 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.52.1-h1a92334_1.conda + sha256: 4f47de9de1990efd998edbbd6793f89c8f02ffde987ae8120b1c006acefd2a04 + md5: de09bd0f175611e94f21b28f8c708e80 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libuv >=1.52.1,<2.0a0 + size: 122729 + timestamp: 1785914645797 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda + sha256: a18fa5d5bac452401459f966cf0d872224e8080c4ff93c77e168d43ab42ef9d7 + md5: f39288f0ea63ae962e1a2e4f355a0d75 + depends: + - __osx >=11.0 + constrains: + - zlib 1.3.2 *_3 + license: Zlib + license_family: Other + purls: [] + run_exports: + weak: + - libzlib >=1.3.2,<2.0a0 + size: 47822 + timestamp: 1785277049190 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-23.1.3-hdb3d66b_1.conda + sha256: eec66f3d0a0c428411e22a59c4726069ccef81a46864876b6e270c8c254d4045 + md5: 5b7efd98534e6722de8d223b8654185b + depends: + - __osx >=11.0 + constrains: + - intel-openmp <0.0a0 + - openmp 23.1.3|23.1.3.* + license: Apache-2.0 WITH LLVM-exception + purls: [] + run_exports: + strong: + - llvm-openmp >=23.1.3 + size: 287944 + timestamp: 1791950643503 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.4-py313h996ddf1_1.conda + sha256: 9d47cb555572c552de411bb6d140a19cf6fee173dbc712aef4d3a1e36e028363 + md5: cf0b179746e4ff1ddf1440355d5f3914 + depends: + - python + - __osx >=11.0 + - python_abi 3.13.* *_cp313 + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/markupsafe?source=hash-mapping + run_exports: {} + size: 28234 + timestamp: 1791548956889 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpc-1.4.0-h071a35a_1.conda + sha256: 6238c857d6b6e575bcf26c79367f2faa22573a79ca67ee7f69694634fd865f5e + md5: 9d932443c0f21214dfa9821dc18881e1 + depends: + - __osx >=11.0 + - gmp >=6.3.0,<7.0a0 + - mpfr >=4.2.2,<5.0a0 + license: LGPL-3.0-or-later + license_family: LGPL + purls: [] + run_exports: + weak: + - mpc >=1.4.0,<2.0a0 + size: 85436 + timestamp: 1787668129803 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/mpfr-4.2.2-h72846b0_1.conda + sha256: 6a86fe9c09d708d71508c601dec53fb7a4fbfb23e6f57a590ef832afdca738c4 + md5: 6c990c07502330c3876dfc67d7bb6917 + depends: + - __osx >=11.0 + - gmp >=6.3.0,<7.0a0 + license: LGPL-3.0-only + license_family: LGPL + purls: [] + run_exports: + weak: + - mpfr >=4.2.2,<5.0a0 + size: 350536 + timestamp: 1787236978744 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-he64c551_1.conda + sha256: 7024a48c8c0d0114ed4ab53c76bf9275d50e91ba7cea367a9aead638d3c29c68 + md5: 3dfa0d0316dc246cd44937a557de4501 + depends: + - __osx >=11.0 + license: X11 AND BSD-3-Clause + purls: [] + run_exports: + weak: + - ncurses >=6.6,<7.0a0 + size: 804298 + timestamp: 1786355189145 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.5.4-py313h24c19cf_0.conda + sha256: f971a672568cf797cdca18f5644b4c08e88a7b5acf84bebb5b929c93e43971d6 + md5: 96efd3f5436f47a2ed3d1ade1b05c323 + depends: + - python + - __osx >=11.0 + - libcxx >=21 + - libcblas >=3.9.0,<4.0a0 + - liblapack >=3.9.0,<4.0a0 + - python_abi 3.13.* *_cp313 + - libblas >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/numpy?source=compressed-mapping + run_exports: + weak: + - numpy >=1.25,<3 + size: 7216969 + timestamp: 1791675324621 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/onednn-3.13.4-omp_h3fb3e40_0.conda + sha256: bf04fb73059bdf3189abeac938d224eace2a469a78dfe904a1c311b01ef49130 + md5: ca729a6795c531eb691801bc97e49933 + depends: + - llvm-openmp >=21.1.8 + - libcxx >=21 + - __osx >=11.0 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: + weak: + - onednn >=3.13.4,<4.0a0 + size: 5498840 + timestamp: 1791401174841 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.5-h55eecbc_0.conda + sha256: 44fbc526456571a9fb8fa18157f6f269d4d4bd40eef20183a93b9c9296f94497 + md5: 45d9179cdf6e53d88aec034246f0dad6 + depends: + - __osx >=11.0 + - ca-certificates + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - openssl >=3.6.5,<4.0a0 + size: 3111573 + timestamp: 1791097959641 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/optree-0.20.0-py313h5c89e09_1.conda + sha256: ab4022ab54ae3f36b182a46d24394f47ed449bc44e5f586639904e897d1e511e + md5: 2cdcb4aa251566f505cdb31e1007a977 + depends: + - __osx >=11.0 + - libcxx >=21 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + - typing-extensions >=4.12 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/optree?source=hash-mapping + run_exports: {} + size: 507081 + timestamp: 1788505310500 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.16-hb59dee6_101_cp313.conda + build_number: 101 + sha256: 456913ec9e3cb021b52c7055addd5ce7f7768905e9080de97ccf4aa9a6fa34d5 + md5: 77177bbe88744e60747483716fa30f90 + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.8.5,<3.0a0 + - libffi >=3.7.0,<3.8.0a0 + - liblzma >=5.8.3,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libpython 3.13.16 hb350d79_101_cp313 + - libsqlite >=3.53.4,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - ncurses >=6.6,<7.0a0 + - openssl >=3.5.9,<4.0a0 + - python_abi 3.13.* *_cp313 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + license: Python-2.0 + purls: [] + run_exports: + weak: + - python_abi 3.13.* *_cp313 + noarch: + - python + size: 11892664 + timestamp: 1791332850865 + python_site_packages_path: lib/python3.13/site-packages +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.13.16-hb59dee6_102_cp313.conda + build_number: 102 + sha256: 613c550dca35cfd16ff0f38c6e66ff72ab146df0aae83aa2be81c9b091b044ac + md5: 00ac7aa9499b4b74aad9f8e92d44dd0a + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.8.5,<3.0a0 + - libffi >=3.7.0,<3.8.0a0 + - liblzma >=5.8.3,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libpython 3.13.16 h0af5753_102_cp313 + - libsqlite >=3.54.0,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - ncurses >=6.6,<7.0a0 + - openssl >=3.5.9,<4.0a0 + - python_abi 3.13.* *_cp313 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + license: Python-2.0 + purls: [] + run_exports: + weak: + - python_abi 3.13.* *_cp313 + noarch: + - python + size: 11933817 + timestamp: 1791910728707 + python_site_packages_path: lib/python3.13/site-packages +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytorch-2.14.1-cpu_generic_py313_hf1850e0_0.conda + sha256: 0ae47b5ea2a73cd5f32cdb7ff068fa6a1be1ad45fc9cb9623c1fd1ce507793df + md5: e4e25350ef3367708d502085bf19c7ae + depends: + - __osx >=12.0 + - filelock + - fmt >=12.1.0,<12.2.0a0 + - fsspec >=0.8.5 + - jinja2 + - libabseil * cxx17* + - libabseil >=20260526.0,<20260527.0a0 + - libcblas >=3.9.0,<4.0a0 + - libcxx >=21 + - liblapack >=3.9.0,<4.0a0 + - libprotobuf >=7.35.1,<7.35.2.0a0 + - libtorch 2.14.1 cpu_generic_h8539c7d_0 + - libuv >=1.52.1,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - llvm-openmp >=21.1.8 + - networkx >=2.5.1 + - nomkl + - numpy >=1.25,<3 + - onednn >=3.13.4,<4.0a0 + - optree >=0.13.0 + - pybind11 + - pybind11-abi 11 + - python >=3.13,<3.14.0a0 + - python_abi 3.13.* *_cp313 + - scikit-build-core + - setuptools >=77.0.3 + - sleef >=3.9.0,<4.0a0 + - sympy >=1.13.3 + - typing_extensions >=4.13.0 + constrains: + - pytorch-cpu 2.14.1 + - pytorch-gpu <0.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/torch?source=hash-mapping + run_exports: + weak: + - pytorch >=2.14.1,<2.15.0a0 + - libtorch >=2.14.1,<2.15.0a0 + size: 27663073 + timestamp: 1791587577341 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytorch-cpu-2.14.1-cpu_generic_hcc7c195_0.conda + sha256: eb7529735a1e681efe37255fc6031743233b0351c3de7f26d809e3f1385e60e3 + md5: ba2b1a8ecf18af2746731ca973f97194 + depends: + - pytorch 2.14.1 cpu_generic*0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 56363 + timestamp: 1791587630647 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py313h65a2061_1.conda + sha256: 950725516f67c9691d81bb8dde8419581c5332c5da3da10c9ba8cbb1698b825d + md5: 5d0c8b92128c93027632ca8f8dc1190f + depends: + - __osx >=11.0 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + run_exports: {} + size: 188763 + timestamp: 1770224094408 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h8b90a29_1.conda + sha256: d6782b430e6dce4fe8c7ac118cd988d5a193eb4a7f60741edfaede58016b6110 + md5: 9347fd56a002e6b58946831d48fe62f6 + depends: + - __osx >=11.0 + - ncurses >=6.6,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + run_exports: + weak: + - readline >=8.3,<9.0a0 + size: 314395 + timestamp: 1787033878899 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ruff-0.14.14-h279115b_1.conda + noarch: python + sha256: 20f93b70375e6ad43ec507611cf28814277be17a2794a2a94e2df13a0b34f8d3 + md5: bcc5ef166c4de9a225c9ca9cb4fa631e + depends: + - python + - __osx >=11.0 + constrains: + - __osx >=11.0 + license: MIT + license_family: MIT + run_exports: {} + size: 8337249 + timestamp: 1769521105071 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/sleef-3.9.0-hb028509_0.conda + sha256: 799d0578369e67b6d0d6ecdacada411c259629fc4a500b99703c5e85d0a68686 + md5: 68f833178f171cfffdd18854c0e9b7f9 + depends: + - __osx >=11.0 + - libcxx >=19 + - llvm-openmp >=19.1.7 + license: BSL-1.0 + purls: [] + run_exports: + weak: + - sleef >=3.9.0,<4.0a0 + size: 587027 + timestamp: 1756274982526 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda + sha256: 857d89087ae7f2c328cf256728affcb7343031a148b4af847334d248a9cf564c + md5: 90a01bf394d1c594e0eb9258f967d828 + depends: + - __osx >=11.0 + - libzlib >=1.3.2,<2.0a0 + license: TCL + purls: [] + run_exports: + weak: + - tk >=8.6.13,<8.7.0a0 + size: 3342183 + timestamp: 1787272852357 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ukkonen-1.1.0-py313h5c29297_0.conda + sha256: d28d0242d3fa23784630c775d5b628ce25e2d45f5d3f1cfcdc3815bc954073fa + md5: 43b1eb729bd1cd9ea595548eb8100b65 + depends: + - __osx >=11.0 + - cffi + - libcxx >=19 + - python >=3.13,<3.14.0a0 + - python >=3.13,<3.14.0a0 *_cp313 + - python_abi 3.13.* *_cp313 + license: MIT + license_family: MIT + run_exports: {} + size: 14773 + timestamp: 1769439197815 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h74c22ad_3.conda + sha256: 3e78c43207502418fc5fb2317bbbefe5df8b6fc1051d90bdcd1c881c76bb4193 + md5: 31cf6dcc138abe673c9440cd6fe6c6fe + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + run_exports: + weak: + - yaml >=0.2.5,<0.3.0a0 + size: 77530 + timestamp: 1787228556857 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hf451053_7.conda + sha256: da867f5092eb0cb746d353694f0098031fd9817a4ce7d5743121209ae0f406ca + md5: 4ec2684c73812cc2c3d78379384a39cc + depends: + - __osx >=11.0 + - libzlib >=1.3.2,<2.0a0 + license: BSD-3-Clause + license_family: BSD + run_exports: + weak: + - zstd >=1.5.7,<1.6.0a0 + size: 433687 + timestamp: 1786599629846 +- pypi: git+https://github.com/sequince-dev/sequince-flows.git?rev=cbe32939713f17be2c5deb8f813ba1ec4550ea7e#cbe32939713f17be2c5deb8f813ba1ec4550ea7e + name: sequince-density + version: 0.1.dev30+gcbe329397 + requires_dist: + - array-api-compat>=1.5 + - numpy>=1.25.2 + - orng>=0.2.0a2 + - sequince + - scikit-learn>=1.4 ; extra == 'gmm' + - torch>=2 ; extra == 'zuko' + - zuko>=1.6 ; extra == 'zuko' + - jax>0.5.3 ; extra == 'flowjax' + - flowjax ; extra == 'flowjax' + - equinox ; extra == 'flowjax' + - h5py>=3 ; extra == 'flowjax' + requires_python: '>=3.11' +- pypi: git+https://github.com/sequince-dev/sequince.git?branch=main#9968074e158e13760a9af5d5a1be0c28e554d036 + name: sequince + version: 0.1.dev170+g9968074e1 + requires_dist: + - acai-control>=0.1.0 + - scipy>=1.16 + - matplotlib>=3.5 + - numpy>=1.25.2 + - minipcn>=0.3.0a1 + - array-api-compat>=1.13 + - array-api-extra>=0.10 + - optree>=0.19 + - orng>=0.2.1 + - jax>0.5.3 ; extra == 'jax' + - jaxlib ; extra == 'jax' + - blackjax>=1.2 ; extra == 'blackjax' + - emcee>=3.0 ; extra == 'emcee' + - eryn>=1.2.6 ; extra == 'eryn' + - numpy<2.4 ; extra == 'eryn' + - corner ; extra == 'corner' + - h5py>=3 ; extra == 'hdf5' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/07/ef/9b77fc80fc9bf28978d5649a3c71c0e77efe9e1966e5f608e7c3d5ba86b4/acai_control-0.1.0-py3-none-any.whl + name: acai-control + version: 0.1.0 + sha256: b61bc1aa7f50944175ea563c178ef52b066f4c3e8306c037d7120fdf6df6421a + requires_dist: + - array-api-compat>=1.9 + - optree>=0.14 + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/10/76/8803c13605b763d33d156c4678fc77f8443389c0c51c8aef707bb02015f4/pillow-12.3.0-cp313-cp313-macosx_11_0_arm64.whl + name: pillow + version: 12.3.0 + sha256: d69141514cc30b774ceea5e3ed3a6635c8d8a96edf664689b890f4089111fb35 + requires_dist: + - furo ; extra == 'docs' + - olefile ; extra == 'docs' + - sphinx>=8.2 ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - sphinxext-opengraph ; extra == 'docs' + - olefile ; extra == 'fpx' + - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' + - pyarrow ; extra == 'test-arrow' + - coverage>=7.4.2 ; extra == 'tests' + - defusedxml ; extra == 'tests' + - markdown2 ; extra == 'tests' + - olefile ; extra == 'tests' + - packaging ; extra == 'tests' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-timeout ; extra == 'tests' + - pytest-xdist ; extra == 'tests' + - setuptools ; extra == 'tests' + - trove-classifiers>=2024.10.12 ; extra == 'tests' + - defusedxml ; extra == 'xmp' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/1e/cf/83c3377e7018ee3c455153d9560c6a972b0a407075868f5e58979fc640a2/minipcn-0.3.0a1-py3-none-any.whl + name: minipcn + version: 0.3.0a1 + sha256: 4bc483602761d4ff95b3e1de32a8626d2d5f7ec034bc8a7be9290b76f9a90c3b + requires_dist: + - matplotlib + - numpy + - scipy + - tqdm + - array-api-compat + - orng>=0.1.0 + - acai-control>=0.1.0 ; extra == 'scan' + - jax>0.4.35 ; extra == 'jax' + - jaxlib ; extra == 'jax' + - torch ; extra == 'torch' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl + name: opt-einsum + version: 3.4.0 + sha256: 69bb92469f86a1565195ece4ac0323943e83477171b91d24c35afe028a90d7cd + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/24/ec/014b428c3a05837643768d08aeb8fb1289d119d3f9f6fba479dffae05dbc/jaxlib-0.11.2-cp313-cp313-manylinux_2_27_x86_64.whl + name: jaxlib + version: 0.11.2 + sha256: 5f3cac8d7030c1f80a182025d7171ea3b1245162a710f6e58d25ee3fe1748aac + requires_dist: + - scipy>=1.14 + - numpy>=2.0 + - ml-dtypes>=0.5.0 + requires_python: '>=3.12' +- pypi: https://files.pythonhosted.org/packages/2a/f5/769f36d14922b8071a43e95d24d18b6bdafad10d7f5cf647867e1ac052bc/scipy-1.18.1-cp313-cp313-macosx_12_0_arm64.whl + name: scipy + version: 1.18.1 + sha256: e6fb6a55cc0ba97b59a1f288fb86dc6fce8bdfc0fffcbfd015e3a954bf2a2d93 + requires_dist: + - numpy>=2.0.0,<2.8 + - pytest>=8.0.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - asv ; extra == 'test' + - mpmath ; extra == 'test' + - gmpy2 ; extra == 'test' + - threadpoolctl ; extra == 'test' + - scikit-umfpack ; extra == 'test' + - pooch ; extra == 'test' + - hypothesis>=6.30 ; extra == 'test' + - array-api-strict>=2.3.1 ; extra == 'test' + - cython ; extra == 'test' + - meson ; extra == 'test' + - ninja ; sys_platform != 'emscripten' and extra == 'test' + - scipy-doctest>=2.0.0 ; extra == 'test' + - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design>=0.4.0 ; extra == 'doc' + - matplotlib>=3.5 ; extra == 'doc' + - numpydoc ; extra == 'doc' + - jupytext ; extra == 'doc' + - myst-nb>=1.2.0 ; extra == 'doc' + - pooch ; extra == 'doc' + - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' + - jupyterlite-pyodide-kernel ; extra == 'doc' + - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' + - mypy==1.19.1 ; extra == 'dev' + - pyrefly==0.63.0 ; extra == 'dev' + - typing-extensions ; extra == 'dev' + - types-psutil ; extra == 'dev' + - pycodestyle ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' + - cython-lint>=0.12.2 ; extra == 'dev' + requires_python: '>=3.12' +- pypi: https://files.pythonhosted.org/packages/38/bb/d215ee7c73b61497b28a5503f9f53523f294fcc936762b7caf90e0c1c2b5/pyparsing-3.3.3-py3-none-any.whl + name: pyparsing + version: 3.3.3 + sha256: ece8c00a69cf01b45d0b1dedabb469c90d8caf996d4fda40f147627a122849a4 + requires_dist: + - railroad-diagrams ; extra == 'diagrams' + - jinja2 ; extra == 'diagrams' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/3f/2f/103cdc5b7077cd569989d3ddb99ee3d4e96ee31bde8df2a6e426b2dc6195/contourpy-1.4.0-cp313-cp313-macosx_11_0_arm64.whl + name: contourpy + version: 1.4.0 + sha256: 196759a3e4db60e1546167e361090ebb6d199aa3aafc9e34ea17a5ac3b23e814 + requires_dist: + - numpy>=2.0 + - myst-parser ; extra == 'docs' + - sphinx ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - bokeh ; extra == 'bokeh' + - selenium ; extra == 'bokeh' + - contourpy[bokeh,docs] ; extra == 'mypy' + - bokeh ; extra == 'mypy' + - docutils-stubs ; extra == 'mypy' + - mypy==2.1.0 ; extra == 'mypy' + - types-pillow ; extra == 'mypy' + - contourpy[test-no-images] ; extra == 'test' + - matplotlib ; extra == 'test' + - pre-commit ; extra == 'test' + - pillow ; extra == 'test' + - pytest ; extra == 'test-no-images' + - pytest-cov ; extra == 'test-no-images' + - pytest-rerunfailures<16 ; extra == 'test-no-images' + - pytest-xdist ; extra == 'test-no-images' + - wurlitzer ; extra == 'test-no-images' + requires_python: '>=3.12' +- pypi: https://files.pythonhosted.org/packages/41/48/6450ed9243315322bbc19ac57b9b70d66a20bf1d38d124c96bc4bf6af9ea/scipy-1.18.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: scipy + version: 1.18.1 + sha256: fdaf5ea890a6183d0565f51a61799d67081bd5b1cf03c5f4b3fd3732108625c9 + requires_dist: + - numpy>=2.0.0,<2.8 + - pytest>=8.0.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - asv ; extra == 'test' + - mpmath ; extra == 'test' + - gmpy2 ; extra == 'test' + - threadpoolctl ; extra == 'test' + - scikit-umfpack ; extra == 'test' + - pooch ; extra == 'test' + - hypothesis>=6.30 ; extra == 'test' + - array-api-strict>=2.3.1 ; extra == 'test' + - cython ; extra == 'test' + - meson ; extra == 'test' + - ninja ; sys_platform != 'emscripten' and extra == 'test' + - scipy-doctest>=2.0.0 ; extra == 'test' + - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design>=0.4.0 ; extra == 'doc' + - matplotlib>=3.5 ; extra == 'doc' + - numpydoc ; extra == 'doc' + - jupytext ; extra == 'doc' + - myst-nb>=1.2.0 ; extra == 'doc' + - pooch ; extra == 'doc' + - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' + - jupyterlite-pyodide-kernel ; extra == 'doc' + - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' + - mypy==1.19.1 ; extra == 'dev' + - pyrefly==0.63.0 ; extra == 'dev' + - typing-extensions ; extra == 'dev' + - types-psutil ; extra == 'dev' + - pycodestyle ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' + - cython-lint>=0.12.2 ; extra == 'dev' + requires_python: '>=3.12' +- pypi: https://files.pythonhosted.org/packages/47/7f/f0d33a414e2d84fca39438a30f0588c32f7365746e903293cadad9d52b0d/jaxlib-0.11.2-cp313-cp313-macosx_11_0_arm64.whl + name: jaxlib + version: 0.11.2 + sha256: 766bd90e27a0ff53b87bb9d70c73565ec80b9b1165320ccb8f72de46b85510df + requires_dist: + - scipy>=1.14 + - numpy>=2.0 + - ml-dtypes>=0.5.0 + requires_python: '>=3.12' +- pypi: https://files.pythonhosted.org/packages/47/8b/40d33ffd2f378094ed462e9a9a0907e59d4de9845e65a59561272da350d4/kiwisolver-1.5.1-cp313-cp313-macosx_11_0_arm64.whl + name: kiwisolver + version: 1.5.1 + sha256: 77a4c8187a5948d7f8795adb765a3c7b553d07d86d88e43038fc32fc1fb9a3f3 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/49/d3/b8441a820a491ddfc024b0b0cf0393375b75ea13866d9c66727e54c2fc80/typing_extensions-4.16.0-py3-none-any.whl + name: typing-extensions + version: 4.16.0 + sha256: 481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/4c/1c/6ec22372362b03350fe3da7bf33491a07cc9a553a36dd2383b76ec1741eb/fonttools-4.66.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: fonttools + version: 4.66.1 + sha256: 83572afe48733bad7a4a9c11721d3a726c2e976d82b063fc9bdd049d76955abd + requires_dist: + - lxml>=4.0 ; extra == 'lxml' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' + - zopfli>=0.1.4 ; extra == 'woff' + - unicodedata2>=18.0.0 ; python_full_version < '3.16' and extra == 'unicode' + - lz4>=1.7.4.2 ; extra == 'graphite' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' + - pycairo ; extra == 'interpolatable' + - matplotlib ; extra == 'plot' + - sympy ; extra == 'symfont' + - xattr ; sys_platform == 'darwin' and extra == 'type1' + - skia-pathops>=0.5.0 ; extra == 'pathops' + - uharfbuzz>=0.45.0 ; extra == 'repacker' + - lxml>=4.0 ; extra == 'all' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' + - zopfli>=0.1.4 ; extra == 'all' + - unicodedata2>=18.0.0 ; python_full_version < '3.16' and extra == 'all' + - lz4>=1.7.4.2 ; extra == 'all' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' + - pycairo ; extra == 'all' + - matplotlib ; extra == 'all' + - sympy ; extra == 'all' + - xattr ; sys_platform == 'darwin' and extra == 'all' + - skia-pathops>=0.5.0 ; extra == 'all' + - uharfbuzz>=0.45.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/50/51/fd1582b8f5ed8a9e7be0e161a6ea0dff70cb280479a12178df0b3a72700e/ml_dtypes-0.6.0-cp313-cp313-macosx_10_13_universal2.whl + name: ml-dtypes + version: 0.6.0 + sha256: 084dfe51a7ad58b171f05115f8226ed4233a454a1611371947e806e76f0c638d + requires_dist: + - numpy>=2.0.0 + - numpy>=2.1.0 ; python_full_version >= '3.13' + - numpy>=2.3.0 ; python_full_version >= '3.14' + - absl-py ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - pylint>=2.6.0 ; extra == 'dev' + - pyink ; extra == 'dev' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/63/34/ba1c580383c9eada3711951fef0795c80b829a078d72188184bcab9dd527/packaging-26.3-py3-none-any.whl + name: packaging + version: '26.3' + sha256: d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/68/77/8f93ecbde1a4d11dfd460cd19a0c159f5487352e751fa87f5d78cc9db08e/contourpy-1.4.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: contourpy + version: 1.4.0 + sha256: f1219a8898523cba821085f2da8a1b962cc696325763f09d7f93538ba43b2d70 + requires_dist: + - numpy>=2.0 + - myst-parser ; extra == 'docs' + - sphinx ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - bokeh ; extra == 'bokeh' + - selenium ; extra == 'bokeh' + - contourpy[bokeh,docs] ; extra == 'mypy' + - bokeh ; extra == 'mypy' + - docutils-stubs ; extra == 'mypy' + - mypy==2.1.0 ; extra == 'mypy' + - types-pillow ; extra == 'mypy' + - contourpy[test-no-images] ; extra == 'test' + - matplotlib ; extra == 'test' + - pre-commit ; extra == 'test' + - pillow ; extra == 'test' + - pytest ; extra == 'test-no-images' + - pytest-cov ; extra == 'test-no-images' + - pytest-rerunfailures<16 ; extra == 'test-no-images' + - pytest-xdist ; extra == 'test-no-images' + - wurlitzer ; extra == 'test-no-images' + requires_python: '>=3.12' +- pypi: https://files.pythonhosted.org/packages/76/3e/0fa5690a3a8303d821a46fd35691b1b6f8a9c06b97b41847ca41aed72e90/orng-0.2.1-py3-none-any.whl + name: orng + version: 0.2.1 + sha256: f8d98fc058ae7e8a516405235590c95f1dd3916c39f28893cc769bf0adb9f90f + requires_dist: + - array-api-compat + - numpy>=1.22 ; extra == 'numpy' + - torch>=2.0 ; extra == 'torch' + - cupy>=12.0 ; extra == 'cupy' + - jax>=0.4 ; extra == 'jax' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/83/8c/b0ea9477fb1f0d4484bbc5cba21678cc9969704d8d7f3f158d1db35f8e14/numpy-2.5.4-cp313-cp313-macosx_11_0_arm64.whl + name: numpy + version: 2.5.4 + sha256: 7415db95818b39ec475a5eea54d9e3b6bc83e3912158e46da3438cdce399804d + requires_python: '>=3.12' +- pypi: https://files.pythonhosted.org/packages/86/16/1a8fd2b19544b84575cf84ef7aa3ad4c173b756d5f087c91f85d1b295777/array_api_compat-1.15.0-py3-none-any.whl + name: array-api-compat + version: 1.15.0 + sha256: 7b1b9c53269061403fd5f45a8de349f16e7887653328bfa0c5f2d45299ff0a8e + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/89/a5/da8ae6c6f1babe4b68e3e55d43d39b529e29774f10e0910671a6b8c86eb8/ml_dtypes-0.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: ml-dtypes + version: 0.6.0 + sha256: 26b1f1fa4f0435a2946859823f6e2bf06796f1e9f10f5a05b08a5e3c8f46ff69 + requires_dist: + - numpy>=2.0.0 + - numpy>=2.1.0 ; python_full_version >= '3.13' + - numpy>=2.3.0 ; python_full_version >= '3.14' + - absl-py ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-xdist ; extra == 'dev' + - pylint>=2.6.0 ; extra == 'dev' + - pyink ; extra == 'dev' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/8d/c0/85fa4951ed8fcfc54ea10367e20617b60292780999835f636a6bdbf01c34/optree-0.20.0-cp313-cp313-macosx_11_0_arm64.whl + name: optree + version: 0.20.0 + sha256: eefb6f5cedc3ded670a79bcde0985d92e99e0c807787427c310adafafe1b9671 + requires_dist: + - typing-extensions>=4.10.0 + - typing-extensions>=4.12.0 ; python_full_version >= '3.13' + - typing-extensions>=4.14.0 ; python_full_version >= '3.15' + - attrs ; extra == 'attrs' + - jax ; extra == 'jax' + - numpy ; extra == 'numpy' + - torch ; extra == 'torch' + - cpplint ; extra == 'lint' + - doc8 ; extra == 'lint' + - mypy ; extra == 'lint' + - pre-commit ; extra == 'lint' + - pyenchant ; extra == 'lint' + - pylint[spelling] ; extra == 'lint' + - ruff ; extra == 'lint' + - xdoctest ; extra == 'lint' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - covdefaults ; extra == 'test' + - rich ; extra == 'test' + - typing-extensions==4.10.0 ; python_full_version < '3.13' and sys_platform == 'linux' and extra == 'test' + - typing-extensions==4.10.0 ; python_full_version < '3.13' and sys_platform == 'darwin' and extra == 'test' + - typing-extensions==4.10.0 ; python_full_version < '3.13' and sys_platform == 'win32' and extra == 'test' + - typing-extensions==4.12.0 ; python_full_version >= '3.13' and python_full_version < '3.15' and sys_platform == 'linux' and extra == 'test' + - typing-extensions==4.12.0 ; python_full_version >= '3.13' and python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'test' + - typing-extensions==4.12.0 ; python_full_version >= '3.13' and python_full_version < '3.15' and sys_platform == 'win32' and extra == 'test' + - typing-extensions==4.14.0 ; python_full_version >= '3.15' and sys_platform == 'linux' and extra == 'test' + - typing-extensions==4.14.0 ; python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'test' + - typing-extensions==4.14.0 ; python_full_version >= '3.15' and sys_platform == 'win32' and extra == 'test' + - sphinx~=8.0 ; extra == 'docs' + - sphinx-autoapi ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-rtd-theme ; extra == 'docs' + - sphinxcontrib-bibtex ; extra == 'docs' + - docutils ; extra == 'docs' + - attrs ; extra == 'docs' + - jax[cpu] ; extra == 'docs' + - numpy ; extra == 'docs' + - torch ; extra == 'docs' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/93/b2/696029cd979f5429ec25c12767734bf0e13175d3341a2aeac59127d00ca0/optree-0.20.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: optree + version: 0.20.0 + sha256: d1034c2cc02ec12ed413727664f015a681e2eea551e05640951b05b06c07f084 + requires_dist: + - typing-extensions>=4.10.0 + - typing-extensions>=4.12.0 ; python_full_version >= '3.13' + - typing-extensions>=4.14.0 ; python_full_version >= '3.15' + - attrs ; extra == 'attrs' + - jax ; extra == 'jax' + - numpy ; extra == 'numpy' + - torch ; extra == 'torch' + - cpplint ; extra == 'lint' + - doc8 ; extra == 'lint' + - mypy ; extra == 'lint' + - pre-commit ; extra == 'lint' + - pyenchant ; extra == 'lint' + - pylint[spelling] ; extra == 'lint' + - ruff ; extra == 'lint' + - xdoctest ; extra == 'lint' + - pytest ; extra == 'test' + - pytest-cov ; extra == 'test' + - covdefaults ; extra == 'test' + - rich ; extra == 'test' + - typing-extensions==4.10.0 ; python_full_version < '3.13' and sys_platform == 'linux' and extra == 'test' + - typing-extensions==4.10.0 ; python_full_version < '3.13' and sys_platform == 'darwin' and extra == 'test' + - typing-extensions==4.10.0 ; python_full_version < '3.13' and sys_platform == 'win32' and extra == 'test' + - typing-extensions==4.12.0 ; python_full_version >= '3.13' and python_full_version < '3.15' and sys_platform == 'linux' and extra == 'test' + - typing-extensions==4.12.0 ; python_full_version >= '3.13' and python_full_version < '3.15' and sys_platform == 'darwin' and extra == 'test' + - typing-extensions==4.12.0 ; python_full_version >= '3.13' and python_full_version < '3.15' and sys_platform == 'win32' and extra == 'test' + - typing-extensions==4.14.0 ; python_full_version >= '3.15' and sys_platform == 'linux' and extra == 'test' + - typing-extensions==4.14.0 ; python_full_version >= '3.15' and sys_platform == 'darwin' and extra == 'test' + - typing-extensions==4.14.0 ; python_full_version >= '3.15' and sys_platform == 'win32' and extra == 'test' + - sphinx~=8.0 ; extra == 'docs' + - sphinx-autoapi ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - sphinx-autodoc-typehints ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-rtd-theme ; extra == 'docs' + - sphinxcontrib-bibtex ; extra == 'docs' + - docutils ; extra == 'docs' + - attrs ; extra == 'docs' + - jax[cpu] ; extra == 'docs' + - numpy ; extra == 'docs' + - torch ; extra == 'docs' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/95/10/ff159867f522cd98e039e748c5e9777446e8b797a79be691c6b730676094/zuko-1.6.0-py3-none-any.whl + name: zuko + version: 1.6.0 + sha256: 5c073b613a84a7cd65470ddb94855169020ac49432f73b85f25207e377248a4a + requires_dist: + - numpy>=1.20.0 + - torch>=1.12.0 + - ipykernel>=6.29.0 ; extra == 'dev' + - matplotlib>=3.8.0 ; extra == 'dev' + - pre-commit>=3.7.0 ; extra == 'dev' + - ruff==0.9.9 ; extra == 'dev' + - docutils==0.19 ; extra == 'docs' + - furo==2024.5.6 ; extra == 'docs' + - myst-nb==1.0.0 ; extra == 'docs' + - sphinx==7.3.7 ; extra == 'docs' + - pytest>=8.0.0 ; extra == 'test' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/a7/03/921a3d3c75785aca9ebfbfcabfbc3a1be12e2ab5265deb026d55a5a3f83e/tqdm-4.70.1-py3-none-any.whl + name: tqdm + version: 4.70.1 + sha256: c293e525e6fef9c20e8728fd4612df02a0aa31bb5fe91ecd93e123b1b7bffa73 + requires_dist: + - colorama ; sys_platform == 'win32' + - requests ; extra == 'discord' + - envwrap ; extra == 'discord' + - slack-sdk ; extra == 'slack' + - envwrap ; extra == 'slack' + - requests ; extra == 'telegram' + - envwrap ; extra == 'telegram' + - ipywidgets>=6 ; extra == 'notebook' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/a9/29/2a47b700545aca050e0ed73c89ba21e08407e11f5f42a567421b220de6c1/matplotlib-3.11.2-cp313-cp313-macosx_11_0_arm64.whl + name: matplotlib + version: 3.11.2 + sha256: 2a8285cea8ef4d92aa041d1c33788bcae82248503300f93f1ca2136b9049452f + requires_dist: + - contourpy>=1.0.1 + - cycler>=0.10 + - fonttools>=4.28.2 + - kiwisolver>=1.3.1 + - numpy>=1.25 + - packaging>=20.0 + - pillow>=9 + - pyparsing>=3 + - python-dateutil>=2.7 + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/a9/df/e7c10292f348cf55178a5b3c0092a38ea68891a3945f199ae8b7b8ac9dc1/matplotlib-3.11.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: matplotlib + version: 3.11.2 + sha256: 1944895967f87c84c9b4bad29a707b31f5b36df4a3a2339ea1f8ea3ce5105539 + requires_dist: + - contourpy>=1.0.1 + - cycler>=0.10 + - fonttools>=4.28.2 + - kiwisolver>=1.3.1 + - numpy>=1.25 + - packaging>=20.0 + - pillow>=9 + - pyparsing>=3 + - python-dateutil>=2.7 + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/ab/43/86aacc027959108b4c66eeae8b73cedb057dfa6eb3a335d05ad65197081c/kiwisolver-1.5.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: kiwisolver + version: 1.5.1 + sha256: 74ad5c3dad54a4641b4c28cd15ded70899d04459c6c7aeacafea716be97cce6d + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl + name: h5py + version: 3.16.0 + sha256: 42108e93326c50c2810025aade9eac9d6827524cdccc7d4b75a546e5ab308edb + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl + name: six + version: 1.17.0 + sha256: 4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*' +- pypi: https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl + name: h5py + version: 3.16.0 + sha256: 9300ad32dea9dfc5171f94d5f6948e159ed93e4701280b0f508773b3f582f402 + requires_dist: + - numpy>=1.21.2 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/c7/b3/fcf70b2ed935f26fc15dfd81f72f781d395b09e302af5c86ec285d63524e/array_api_extra-0.11.4-py3-none-any.whl + name: array-api-extra + version: 0.11.4 + sha256: b2d5bd31b3072249fd3f90a2b1f7b398c78e3ccb7575e04e9e3348ae894da60f + requires_dist: + - array-api-compat>=1.15.0,<2 + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/ca/87/0c0ca8433e1135f6d1acd355cff21f31c3d433d937d6547aa545c8b060b8/jax-0.11.2-py3-none-any.whl + name: jax + version: 0.11.2 + sha256: 59e7ed9bd9ace049f4856752fe580c890e0164e1c023f40fadac386408daad66 + requires_dist: + - jaxlib<=0.11.2,>=0.11.2 + - ml-dtypes>=0.5.0 + - numpy>=2.1 + - opt-einsum + - scipy>=1.15 + - jaxlib==0.11.2 ; extra == 'minimum-jaxlib' + - jaxlib==0.11.1 ; extra == 'ci' + - jaxlib<=0.11.2,>=0.11.2 ; extra == 'tpu' + - libtpu==0.0.48.* ; extra == 'tpu' + - requests ; extra == 'tpu' + - jaxlib<=0.11.2,>=0.11.2 ; extra == 'cuda' + - jax-cuda12-plugin[with-cuda]<=0.11.2,>=0.11.2 ; extra == 'cuda' + - jaxlib<=0.11.2,>=0.11.2 ; extra == 'cuda12' + - jax-cuda12-plugin[with-cuda]<=0.11.2,>=0.11.2 ; extra == 'cuda12' + - jaxlib<=0.11.2,>=0.11.2 ; extra == 'cuda13' + - jax-cuda13-plugin[with-cuda]<=0.11.2,>=0.11.2 ; extra == 'cuda13' + - jaxlib<=0.11.2,>=0.11.2 ; extra == 'cuda12-local' + - jax-cuda12-plugin<=0.11.2,>=0.11.2 ; extra == 'cuda12-local' + - jaxlib<=0.11.2,>=0.11.2 ; extra == 'cuda13-local' + - jax-cuda13-plugin<=0.11.2,>=0.11.2 ; extra == 'cuda13-local' + - jaxlib<=0.11.2,>=0.11.2 ; extra == 'rocm7-local' + - jax-rocm7-plugin==0.11.2.* ; extra == 'rocm7-local' + - jaxlib<=0.11.2,>=0.11.2 ; extra == 'oneapi' + - jax-oneapi-plugin[with-oneapi]<=0.11.2,>=0.11.2 ; extra == 'oneapi' + - kubernetes ; extra == 'k8s' + - xprof ; extra == 'xprof' + requires_python: '>=3.12' +- pypi: https://files.pythonhosted.org/packages/cb/f4/e410b8c913da5b3fdbb4d16db0f2d2a0952f59c4db8d52dcf2d421d82044/fonttools-4.66.1-cp313-cp313-macosx_10_13_universal2.whl + name: fonttools + version: 4.66.1 + sha256: 53e5854ea8003efec34adc0863c18ce91da923018354d27366f7fee7db928d7a + requires_dist: + - lxml>=4.0 ; extra == 'lxml' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'woff' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'woff' + - zopfli>=0.1.4 ; extra == 'woff' + - unicodedata2>=18.0.0 ; python_full_version < '3.16' and extra == 'unicode' + - lz4>=1.7.4.2 ; extra == 'graphite' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'interpolatable' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'interpolatable' + - pycairo ; extra == 'interpolatable' + - matplotlib ; extra == 'plot' + - sympy ; extra == 'symfont' + - xattr ; sys_platform == 'darwin' and extra == 'type1' + - skia-pathops>=0.5.0 ; extra == 'pathops' + - uharfbuzz>=0.45.0 ; extra == 'repacker' + - lxml>=4.0 ; extra == 'all' + - brotli>=1.0.1 ; platform_python_implementation == 'CPython' and extra == 'all' + - brotlicffi>=0.8.0 ; platform_python_implementation != 'CPython' and extra == 'all' + - zopfli>=0.1.4 ; extra == 'all' + - unicodedata2>=18.0.0 ; python_full_version < '3.16' and extra == 'all' + - lz4>=1.7.4.2 ; extra == 'all' + - scipy ; platform_python_implementation != 'PyPy' and extra == 'all' + - munkres ; platform_python_implementation == 'PyPy' and extra == 'all' + - pycairo ; extra == 'all' + - matplotlib ; extra == 'all' + - sympy ; extra == 'all' + - xattr ; sys_platform == 'darwin' and extra == 'all' + - skia-pathops>=0.5.0 ; extra == 'all' + - uharfbuzz>=0.45.0 ; extra == 'all' + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/d6/cb/17a130e548ee1777f2a66e09240c531a9bf79454add7ef1198fe1e41c412/corner-2.3.0-py3-none-any.whl + name: corner + version: 2.3.0 + sha256: f93f0a4d77ff6852d6ddc80a6a99038117be64e3b5b49d96a82c9f5974cce126 + requires_dist: + - matplotlib>=2.1 + - arviz-base ; extra == 'arviz' + - arviz>=0.9 ; extra == 'arviz' + - arviz-base ; extra == 'docs' + - arviz>=0.9 ; extra == 'docs' + - ipython ; extra == 'docs' + - myst-nb ; extra == 'docs' + - pandoc ; extra == 'docs' + - sphinx-book-theme ; extra == 'docs' + - sphinx>=1.7.5 ; extra == 'docs' + - arviz-base ; extra == 'test' + - arviz>=0.9 ; extra == 'test' + - pytest ; extra == 'test' + - scipy ; extra == 'test' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/da/24/3bd070f3269dc609d8f26b2643f62ef91bb415841c0b294805aaf7fe06da/numpy-2.5.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: numpy + version: 2.5.4 + sha256: 6ffa07666f8da0eef81d149934a626d0d95fbd6838432a33e66245423a9062c0 + requires_python: '>=3.12' +- pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl + name: cycler + version: 0.12.1 + sha256: 85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30 + requires_dist: + - ipython ; extra == 'docs' + - matplotlib ; extra == 'docs' + - numpydoc ; extra == 'docs' + - sphinx ; extra == 'docs' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-xdist ; extra == 'tests' + requires_python: '>=3.8' +- pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + name: python-dateutil + version: 2.9.0.post0 + sha256: a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 + requires_dist: + - six>=1.5 + requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*' +- pypi: https://files.pythonhosted.org/packages/f7/62/de5bdd77d935331f4f802edc11e4d82950f642caad6cb2f949837b8560e2/pillow-12.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: pillow + version: 12.3.0 + sha256: 0847a763afefb695bc912d7c131e7e0632d4edc1d8698f58ddabec8e46b8b6d3 + requires_dist: + - furo ; extra == 'docs' + - olefile ; extra == 'docs' + - sphinx>=8.2 ; extra == 'docs' + - sphinx-autobuild ; extra == 'docs' + - sphinx-copybutton ; extra == 'docs' + - sphinx-inline-tabs ; extra == 'docs' + - sphinxext-opengraph ; extra == 'docs' + - olefile ; extra == 'fpx' + - olefile ; extra == 'mic' + - arro3-compute ; extra == 'test-arrow' + - arro3-core ; extra == 'test-arrow' + - nanoarrow ; extra == 'test-arrow' + - pyarrow ; extra == 'test-arrow' + - coverage>=7.4.2 ; extra == 'tests' + - defusedxml ; extra == 'tests' + - markdown2 ; extra == 'tests' + - olefile ; extra == 'tests' + - packaging ; extra == 'tests' + - pytest ; extra == 'tests' + - pytest-cov ; extra == 'tests' + - pytest-timeout ; extra == 'tests' + - pytest-xdist ; extra == 'tests' + - setuptools ; extra == 'tests' + - trove-classifiers>=2024.10.12 ; extra == 'tests' + - defusedxml ; extra == 'xmp' + requires_python: '>=3.10' diff --git a/pyproject.toml b/pyproject.toml index 704d496..32ea428 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,3 +7,58 @@ target-version = "py39" # Allow fix for all enabled rules (when `--fix`) is provided. fixable = ["ALL"] extend-select = ["I"] + +[tool.pixi.workspace] +name = "sequince-examples" +channels = ["conda-forge"] +platforms = ["linux-64", "osx-arm64"] +requires-pixi = ">=0.81.0" + +[tool.pixi.dependencies] +python = "3.13.*" + +[tool.pixi.feature.cpu.pypi-dependencies] +sequince = { git = "https://github.com/sequince-dev/sequince.git", branch = "main", extras = ["corner", "hdf5"] } + +[tool.pixi.feature.jax.pypi-dependencies] +jax = ">0.5.3" + +[tool.pixi.feature.jax.activation.env] +JAX_PLATFORMS = "cpu" + +[tool.pixi.feature.flow.dependencies] +pytorch-cpu = "*" + +[tool.pixi.feature.flow.pypi-dependencies] +sequince-density = { git = "https://github.com/sequince-dev/sequince-flows.git", rev = "cbe32939713f17be2c5deb8f813ba1ec4550ea7e", extras = ["zuko"] } + +[tool.pixi.feature.docs.dependencies] +sphinx = "8.2.*" +sphinx-book-theme = "1.1.*" + +[tool.pixi.feature.check.dependencies] +ruff = "0.14.*" +pre-commit = "*" +pytest = "*" + +[tool.pixi.environments] +default = { features = ["cpu"], solve-group = "cpu" } +jax = { features = ["cpu", "jax"], solve-group = "cpu" } +flow = ["cpu", "flow"] +docs = ["docs"] +check = ["check"] + +[tool.pixi.tasks] +example = "python scripts/docs_pipeline.py run" +examples = "python scripts/docs_pipeline.py run-all" +validate-outputs = "python scripts/docs_pipeline.py validate" +prepare-docs = "python scripts/docs_pipeline.py prepare" +bundle = "python scripts/docs_pipeline.py bundle" + +[tool.pixi.feature.docs.tasks] +docs = { cmd = "sphinx-build -W --keep-going -b html docs docs/_build/html", depends-on = ["prepare-docs"] } + +[tool.pixi.feature.check.tasks] +test = "pytest tests" +lint = "ruff check scripts tests docs/conf.py" +format-check = "ruff format --check scripts tests docs/conf.py" diff --git a/scripts/docs_pipeline.py b/scripts/docs_pipeline.py new file mode 100644 index 0000000..29b3a97 --- /dev/null +++ b/scripts/docs_pipeline.py @@ -0,0 +1,555 @@ +"""Execute, validate and transport commit-specific example documentation.""" + +from __future__ import annotations + +import argparse +import gzip +import hashlib +import importlib.metadata +import importlib.util +import io +import json +import os +import re +import shutil +import subprocess +import sys +import tarfile +import tempfile +import urllib.request +from pathlib import Path + +import tomllib + +ROOT = Path(__file__).resolve().parents[1] +BUNDLES = ROOT / "build-docs" +OUTPUT = BUNDLES / "outputs" +GENERATED = ROOT / "docs" / "_generated" +REPOSITORY = "sequince-dev/sequince-examples" + + +def git(*args: str) -> str: + """Run a read-only Git command in the source checkout.""" + return subprocess.check_output(["git", *args], cwd=ROOT, text=True).strip() + + +def digest(path: Path) -> str: + """Hash file contents.""" + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def write_json(path: Path, value: object) -> None: + """Write stable, readable JSON and reject nonfinite numerical values.""" + path.write_text( + json.dumps(value, indent=2, sort_keys=True, allow_nan=False) + "\n" + ) + + +def examples() -> list[dict]: + """Read the explicit publication manifest.""" + return json.loads((ROOT / "docs/examples.json").read_text())["examples"] + + +def entry(name: str) -> dict: + """Find an example or fail with a useful error.""" + for example in examples(): + if example["id"] == name: + return example + raise ValueError(f"Unknown example: {name}") + + +def dependency_commit() -> str: + """Read Sequince's resolved commit from the execution lock.""" + return git_dependencies({})["sequince"] + + +def git_dependencies(example: dict) -> dict[str, str]: + """Read configured Git dependencies' exact resolved lock revisions.""" + config = tomllib.loads((ROOT / "pyproject.toml").read_text())["tool"][ + "pixi" + ] + lock = (ROOT / "pixi.lock").read_text() + revisions = {} + for feature in ["cpu", *example.get("dependency_features", [])]: + for name, specification in ( + config["feature"][feature].get("pypi-dependencies", {}).items() + ): + if ( + not isinstance(specification, dict) + or "git" not in specification + ): + continue + pattern = ( + re.escape("git+" + specification["git"]) + + r"\?[^#\s]*#([0-9a-f]{40})" + ) + matches = set(re.findall(pattern, lock)) + if len(matches) != 1: + raise ValueError( + f"Missing or inconsistent locked Git commit: {name}" + ) + revision = matches.pop() + if "rev" in specification and specification["rev"] != revision: + raise ValueError( + f"Configured and locked revisions differ: {name}" + ) + revisions[name] = revision + return revisions + + +def refresh() -> None: + """Resolve current Sequince main once before a new execution batch.""" + subprocess.run(["pixi", "update", "sequince"], cwd=ROOT, check=True) + + +def source_hashes(example: dict) -> dict[str, str]: + """Fingerprint execution inputs as well as the checkout commit.""" + paths = [ + example["script"], + example["narrative"], + "pyproject.toml", + "pixi.lock", + "docs/examples.json", + "scripts/docs_pipeline.py", + ] + return {path: digest(ROOT / path) for path in paths} + + +def worker(name: str, raw: Path, destination: Path) -> None: + """Run a script with only its output destination changed.""" + import numpy as np + + example = entry(name) + revisions = git_dependencies(example) + versions = {} + for package, revision in revisions.items(): + distribution = importlib.metadata.distribution(package) + direct = json.loads(distribution.read_text("direct_url.json") or "{}") + resolved = direct.get("vcs_info", {}).get("commit_id") + if resolved != revision: + raise ValueError( + f"Installed {package} commit {resolved} != pinned {revision}" + ) + versions[package] = distribution.version + spec = importlib.util.spec_from_file_location( + "documentation_example", ROOT / example["script"] + ) + if spec is None or spec.loader is None: + raise ValueError(f"Cannot load {example['script']}") + module = importlib.util.module_from_spec(spec) + # Dataclasses with postponed annotations resolve their module by name. + sys.modules[spec.name] = module + spec.loader.exec_module(module) + defaults = ( + vars(module.build_parser().parse_args([])) + if hasattr(module, "build_parser") + else vars(module) + ) + parameters = {key: defaults[key] for key in example["parameters"]} + # Keep the script's main function, sampler, RNG and constants intact. + module.outdir = raw + module.documentation_output = raw + raw.mkdir(parents=True, exist_ok=True) + original_argv = sys.argv + try: + sys.argv = [str(ROOT / example["script"])] + module.main() + finally: + sys.argv = original_argv + for name in example["required_outputs"]: + path = raw / name + if not path.is_file() or not path.stat().st_size: + raise ValueError(f"Missing required output: {path}") + if (raw / "summary.json").is_file(): + summary = json.loads((raw / "summary.json").read_text()) + else: + with np.load(raw / "posterior.npz", allow_pickle=False) as posterior: + particles = posterior["particles"] + weights = posterior["weights"] + if not np.all(np.isfinite(particles)): + raise ValueError("Nonfinite posterior particles") + if ( + not np.all(np.isfinite(weights)) + or np.any(weights < 0) + or not np.isclose(weights.sum(), 1.0) + ): + raise ValueError("Invalid posterior weights") + mean = np.sum(particles * weights[:, None], axis=0) + centered = particles - mean + # This is the population covariance of the weighted empirical measure. + covariance = (centered * weights[:, None]).T @ centered + summary = { + "log_evidence": float(posterior["log_evidence"]), + "weighted_mean": mean.tolist(), + "weighted_population_covariance": covariance.tolist(), + "effective_sample_size": float(1 / np.sum(weights**2)), + "n_particles": len(particles), + } + destination.mkdir(parents=True, exist_ok=False) + for name in example["figures"]: + shutil.copyfile(raw / name, destination / name) + write_json(destination / "summary.json", summary) + write_json( + destination / "metadata.json", + { + "schema": 1, + "example": example["id"], + "source_commit": git("rev-parse", "HEAD"), + "source_hashes": source_hashes(example), + "sequince_version": importlib.metadata.version("sequince"), + "sequince_commit": revisions["sequince"], + "git_dependencies": revisions, + "dependency_versions": versions, + "python_version": sys.version.split()[0], + "environment": example["environment"], + "parameters": parameters, + "seed": parameters["seed"], + }, + ) + write_json( + destination / "checksums.json", + {path.name: digest(path) for path in sorted(destination.iterdir())}, + ) + + +def run(name: str) -> None: + """Execute with a hard timeout, a noninteractive backend and fresh outputs.""" + example = entry(name) + OUTPUT.mkdir(parents=True, exist_ok=True) + target = OUTPUT / name + if target.exists(): + shutil.rmtree(target) + log = ROOT / "build-docs" / f"{name}.log" + with tempfile.TemporaryDirectory(prefix=f"sequince-{name}-") as tmp: + env = os.environ | {"MPLBACKEND": "Agg", "MPLCONFIGDIR": tmp + "/mpl"} + try: + with log.open("w") as stream: + subprocess.run( + [ + sys.executable, + str(Path(__file__).resolve()), + "worker", + name, + "--raw", + tmp + "/raw", + "--destination", + str(target), + ], + cwd=ROOT / example["working_directory"], + env=env, + stdout=stream, + stderr=subprocess.STDOUT, + timeout=example["timeout"], + check=True, + ) + except (subprocess.CalledProcessError, subprocess.TimeoutExpired): + print(log.read_text(), file=sys.stderr) + raise + print(f"Completed {name}: {target}; execution log: {log}") + + +def validate(base: Path | None = None) -> None: + """Reject incomplete, corrupt, mixed-commit or incompatible outputs.""" + base = OUTPUT if base is None else base + expected = {example["id"] for example in examples()} + actual = {path.name for path in base.iterdir()} if base.is_dir() else set() + missing = expected - {name for name in actual if (base / name).is_dir()} + unexpected = actual - expected + if missing or unexpected: + details = [] + if missing: + details.append( + "missing example outputs: " + ", ".join(sorted(missing)) + ) + if unexpected: + details.append( + "unexpected entries: " + ", ".join(sorted(unexpected)) + ) + raise ValueError( + f"Invalid documentation outputs in {base}: " + + "; ".join(details) + + ". Generate or restore outputs for this source revision " + "before building documentation." + ) + for example in examples(): + directory = base / example["id"] + required = {*example["figures"], "summary.json", "metadata.json"} + checksums = json.loads((directory / "checksums.json").read_text()) + if set(checksums) != required: + raise ValueError(f"Incomplete checksum manifest: {directory}") + if {p.name for p in directory.iterdir()} != required | { + "checksums.json" + }: + raise ValueError(f"Unexpected files: {directory}") + for name, expected in checksums.items(): + path = directory / name + if not path.is_file() or not path.stat().st_size: + raise ValueError(f"Missing or empty output: {path}") + if digest(path) != expected: + raise ValueError(f"Checksum mismatch: {path}") + metadata = json.loads((directory / "metadata.json").read_text()) + expected_metadata = { + "schema": 1, + "example": example["id"], + "source_commit": git("rev-parse", "HEAD"), + "source_hashes": source_hashes(example), + "sequince_commit": dependency_commit(), + "git_dependencies": git_dependencies(example), + "environment": example["environment"], + } + for key, value in expected_metadata.items(): + if metadata.get(key) != value: + raise ValueError(f"Provenance mismatch: {directory}: {key}") + if set(metadata["parameters"]) != set(example["parameters"]): + raise ValueError(f"Missing parameters: {directory}") + if metadata["seed"] != metadata["parameters"]["seed"]: + raise ValueError(f"Seed mismatch: {directory}") + versions = metadata.get("dependency_versions", {}) + if set(versions) != set(git_dependencies(example)) or not all( + versions.values() + ): + raise ValueError( + f"Missing installed dependency versions: {directory}" + ) + if not metadata.get("sequince_version"): + raise ValueError(f"Missing installed version: {directory}") + json.loads((directory / "summary.json").read_text()) + print(f"Validated all outputs for {git('rev-parse', 'HEAD')}") + + +def prepare() -> None: + """Stage narrative, script source and validated results separately.""" + validate() + if GENERATED.exists(): + shutil.rmtree(GENERATED) + GENERATED.mkdir(parents=True) + narratives: set[str] = set() + for example in examples(): + narrative = example["narrative"] + folder = GENERATED / str(Path(narrative).parent) + folder.mkdir(parents=True, exist_ok=True) + shutil.copyfile( + ROOT / example["script"], folder / Path(example["script"]).name + ) + if narrative not in narratives: + shutil.copyfile(ROOT / narrative, GENERATED / narrative) + narratives.add(narrative) + target = GENERATED / "results" / example["id"] + shutil.copytree(OUTPUT / example["id"], target) + + +def bundle() -> Path: + """Create a deterministic archive containing only validated public outputs.""" + validate() + sha = git("rev-parse", "HEAD") + path = BUNDLES / f"docs-outputs-{sha}.tar.gz" + files = { + str(p.relative_to(OUTPUT)): p.read_bytes() + for p in sorted(OUTPUT.rglob("*")) + if p.is_file() + } + manifest = { + "schema": 1, + "source_commit": sha, + "files": { + name: hashlib.sha256(data).hexdigest() + for name, data in files.items() + }, + } + files["execution-pixi.lock"] = (ROOT / "pixi.lock").read_bytes() + manifest["files"]["execution-pixi.lock"] = hashlib.sha256( + files["execution-pixi.lock"] + ).hexdigest() + files["bundle-manifest.json"] = ( + json.dumps(manifest, indent=2, sort_keys=True) + "\n" + ).encode() + buffer = io.BytesIO() + with gzip.GzipFile( + fileobj=buffer, mode="wb", mtime=0, filename="" + ) as zipped: + with tarfile.open(fileobj=zipped, mode="w") as archive: + for name, data in sorted(files.items()): + info = tarfile.TarInfo(name) + info.size = len(data) + info.mode = 0o644 + archive.addfile(info, io.BytesIO(data)) + if path.exists() and path.read_bytes() != buffer.getvalue(): + raise ValueError( + f"Refusing to replace different existing bundle: {path}" + ) + path.write_bytes(buffer.getvalue()) + path.with_suffix(path.suffix + ".sha256").write_text( + f"{digest(path)} {path.name}\n" + ) + print(path) + return path + + +def unpack( + path: Path, + expected_sha: str, + destination: Path, + *, + restore_lock: bool = False, +) -> None: + """Validate archive paths, completeness and checksums before extraction.""" + with tarfile.open(path) as archive: + members = archive.getmembers() + names = [m.name for m in members] + if len(names) != len(set(names)): + raise ValueError("Duplicate archive members") + files = {} + for member in members: + name = Path(member.name) + if ( + not member.isfile() + or name.is_absolute() + or ".." in name.parts + or str(name) != member.name + ): + raise ValueError(f"Unsafe archive member: {member.name}") + stream = archive.extractfile(member) + if stream is None: + raise ValueError(f"Cannot read {member.name}") + files[member.name] = stream.read() + manifest = json.loads(files.pop("bundle-manifest.json")) + if manifest["schema"] != 1 or manifest["source_commit"] != expected_sha: + raise ValueError("Bundle source commit mismatch") + hashes = { + name: hashlib.sha256(data).hexdigest() for name, data in files.items() + } + if manifest["files"] != hashes: + raise ValueError("Bundle completeness/checksum mismatch") + if destination.exists(): + raise ValueError(f"Destination already exists: {destination}") + execution_lock = files.pop("execution-pixi.lock") + if restore_lock: + (ROOT / "pixi.lock").write_bytes(execution_lock) + elif execution_lock != (ROOT / "pixi.lock").read_bytes(): + raise ValueError("Bundle execution lock differs from the active lock") + for name, data in files.items(): + target = destination / name + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes(data) + validate(destination) + + +def check_latest() -> None: + """Stop stale latest builds both before Sphinx and before RTD upload.""" + if os.environ.get("READTHEDOCS_VERSION") == "latest": + remote = git("ls-remote", "origin", "refs/heads/main").split()[0] + if git("rev-parse", "HEAD") != remote: + raise ValueError( + "Stale latest checkout: main advanced; rebuild latest" + ) + + +def fetch() -> None: + """Fetch only the durable bundle for RTD's actual checked-out commit.""" + check_latest() + sha = git("rev-parse", "HEAD") + # No credentials: RTD Community and these assets must be publicly readable. + name = f"docs-outputs-{sha}.tar.gz" + base = ( + f"https://github.com/{REPOSITORY}/releases/download/docs-output-{sha}" + ) + with tempfile.TemporaryDirectory() as tmp: + path = Path(tmp) / name + for suffix in ("", ".sha256"): + with urllib.request.urlopen( + base + "/" + name + suffix, timeout=60 + ) as response: + Path(str(path) + suffix).write_bytes(response.read()) + expected = Path(str(path) + ".sha256").read_text().split() + if expected != [digest(path), name]: + raise ValueError("Downloaded bundle checksum mismatch") + if OUTPUT.exists(): + shutil.rmtree(OUTPUT) + unpack(path, sha, OUTPUT, restore_lock=True) + prepare() + + +def main() -> None: + """Dispatch local, Actions and RTD commands.""" + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "command", + choices=[ + "matrix", + "run", + "run-all", + "worker", + "validate", + "prepare", + "bundle", + "fetch", + "check-latest", + "refresh", + ], + ) + parser.add_argument("name", nargs="?") + parser.add_argument("--raw", type=Path) + parser.add_argument("--destination", type=Path) + args = parser.parse_args() + if args.command == "matrix": + print( + json.dumps( + { + "include": [ + {"id": e["id"], "environment": e["environment"]} + for e in examples() + ] + } + ) + ) + elif args.command == "worker": + worker(args.name, args.raw, args.destination) + elif args.command == "run": + if os.environ.get("SEQUINCE_LOCK_READY") != "1": + refresh() + subprocess.run( + [ + "pixi", + "install", + "--locked", + "-e", + entry(args.name)["environment"], + ], + cwd=ROOT, + check=True, + ) + run(args.name) + elif args.command == "run-all": + if os.environ.get("SEQUINCE_LOCK_READY") != "1": + refresh() + for example in examples(): + subprocess.run( + [ + "pixi", + "run", + "--locked", + "-e", + example["environment"], + "example", + example["id"], + ], + cwd=ROOT, + env=os.environ | {"SEQUINCE_LOCK_READY": "1"}, + check=True, + ) + else: + { + "refresh": refresh, + "validate": validate, + "prepare": prepare, + "bundle": bundle, + "fetch": fetch, + "check-latest": check_latest, + }[args.command]() + + +if __name__ == "__main__": + try: + main() + except (ValueError, FileNotFoundError) as error: + raise SystemExit(f"Documentation pipeline error: {error}") from None diff --git a/tests/test_docs_pipeline.py b/tests/test_docs_pipeline.py new file mode 100644 index 0000000..c283211 --- /dev/null +++ b/tests/test_docs_pipeline.py @@ -0,0 +1,292 @@ +"""Exercise provenance and transport failures independently of sampling.""" + +from __future__ import annotations + +import io +import json +import sys +import tarfile +from pathlib import Path + +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts")) +import docs_pipeline as pipeline # noqa: E402 + + +@pytest.fixture +def outputs(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path: + """Make a complete, checksummed output set for the current source.""" + monkeypatch.setattr(pipeline, "OUTPUT", tmp_path / "outputs") + monkeypatch.setattr(pipeline, "git", lambda *args: "a" * 40) + for example in pipeline.examples(): + directory = pipeline.OUTPUT / example["id"] + directory.mkdir(parents=True) + for name in example["figures"]: + (directory / name).write_bytes(b"plot") + pipeline.write_json(directory / "summary.json", {"log_evidence": -1.0}) + pipeline.write_json( + directory / "metadata.json", + { + "schema": 1, + "example": example["id"], + "source_commit": "a" * 40, + "source_hashes": pipeline.source_hashes(example), + "sequince_version": "0.1.dev1", + "sequince_commit": pipeline.dependency_commit(), + "git_dependencies": pipeline.git_dependencies(example), + "dependency_versions": { + name: "test-version" + for name in pipeline.git_dependencies(example) + }, + "environment": example["environment"], + "seed": 42, + "parameters": {key: 42 for key in example["parameters"]}, + }, + ) + pipeline.write_json( + directory / "checksums.json", + {path.name: pipeline.digest(path) for path in directory.iterdir()}, + ) + return pipeline.OUTPUT + + +def test_complete_outputs(outputs: Path) -> None: + pipeline.validate(outputs) + + +@pytest.mark.parametrize("failure", ["missing", "corrupt", "commit", "input"]) +def test_invalid_outputs_fail(outputs: Path, failure: str) -> None: + directory = outputs / "gaussian" + if failure == "missing": + (directory / "corner.png").unlink() + elif failure == "corrupt": + (directory / "corner.png").write_bytes(b"corrupt") + else: + path = directory / "metadata.json" + metadata = json.loads(path.read_text()) + if failure == "commit": + metadata["source_commit"] = "b" * 40 + else: + metadata["source_hashes"]["pixi.lock"] = "wrong" + pipeline.write_json(path, metadata) + checksums = json.loads((directory / "checksums.json").read_text()) + checksums[path.name] = pipeline.digest(path) + pipeline.write_json(directory / "checksums.json", checksums) + with pytest.raises((ValueError, FileNotFoundError)): + pipeline.validate(outputs) + + +def test_bundle_roundtrip( + outputs: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + monkeypatch.setattr(pipeline, "BUNDLES", tmp_path) + path = pipeline.bundle() + checksum = pipeline.digest(path) + assert pipeline.digest(pipeline.bundle()) == checksum + destination = tmp_path / "restored" + pipeline.unpack(path, "a" * 40, destination) + assert (destination / "gaussian/summary.json").read_bytes() == ( + outputs / "gaussian/summary.json" + ).read_bytes() + with pytest.raises(ValueError, match="commit mismatch"): + pipeline.unpack(path, "b" * 40, tmp_path / "wrong") + path.write_bytes(b"existing different bundle") + with pytest.raises(ValueError, match="Refusing to replace"): + pipeline.bundle() + + +def test_unsafe_archive_rejected(tmp_path: Path) -> None: + path = tmp_path / "unsafe.tar" + with tarfile.open(path, "w") as archive: + info = tarfile.TarInfo("../escape") + info.size = 3 + archive.addfile(info, io.BytesIO(b"bad")) + with pytest.raises(ValueError, match="Unsafe archive"): + pipeline.unpack(path, "a" * 40, tmp_path / "output") + assert not (tmp_path / "escape").exists() + + +def test_latest_checkout_guard(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("READTHEDOCS_VERSION", "latest") + monkeypatch.setattr( + pipeline, + "git", + lambda *args: ( + "b" * 40 + "\trefs/heads/main" + if args[0] == "ls-remote" + else "a" * 40 + ), + ) + with pytest.raises(ValueError, match="Stale latest checkout"): + pipeline.check_latest() + + +def test_missing_output_root_is_actionable(tmp_path: Path) -> None: + with pytest.raises(ValueError, match="missing example outputs:.*gaussian"): + pipeline.validate(tmp_path / "absent") + + +def test_missing_example_is_named(outputs: Path) -> None: + import shutil + + shutil.rmtree(outputs / "adaptive-mutation") + with pytest.raises( + ValueError, match="missing example outputs: adaptive-mutation" + ): + pipeline.validate(outputs) + + +def test_unexpected_output_is_named(outputs: Path) -> None: + (outputs / "stray.txt").write_text("unexpected") + with pytest.raises(ValueError, match="unexpected entries: stray.txt"): + pipeline.validate(outputs) + + +def test_prepare_preserves_narrative_and_source_paths( + outputs: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + generated = tmp_path / "generated" + monkeypatch.setattr(pipeline, "GENERATED", generated) + pipeline.prepare() + for example in pipeline.examples(): + for key in ("script", "narrative"): + relative = example[key] + assert (generated / relative).read_bytes() == ( + pipeline.ROOT / relative + ).read_bytes() + assert ( + generated / "results" / example["id"] / "summary.json" + ).is_file() + assert not (generated / "index.rst").exists() + for example in pipeline.examples(): + narrative = (generated / example["narrative"]).read_text() + for name in example["figures"]: + assert ( + f".. figure:: /_generated/results/{example['id']}/{name}" + in narrative + ) + assert "Numerical summary" not in narrative + assert "/summary.json" not in narrative + + +@pytest.mark.parametrize( + "name", + [ + "flow-preconditioning", + "persistent-sampling", + "waste-free", + "particle-filtering", + ], +) +def test_prepared_worker_without_sampling( + name: str, tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """Exercise adapters and provenance using a fake main, never a sampler.""" + from types import SimpleNamespace + + example = pipeline.entry(name) + parameters = {key: 42 for key in example["parameters"]} + module = SimpleNamespace(**parameters) + if name in {"persistent-sampling", "particle-filtering"}: + module.build_parser = lambda: SimpleNamespace( + parse_args=lambda argv: SimpleNamespace(**parameters) + ) + original_argv = sys.argv + + def main() -> None: + assert sys.argv == [str(pipeline.ROOT / example["script"])] + for figure in example["figures"]: + (module.documentation_output / figure).write_bytes(b"mock plot") + pipeline.write_json( + module.documentation_output / "summary.json", + { + "log_evidence": -1.0, + }, + ) + + module.main = main + monkeypatch.setitem(sys.modules, "documentation_example", None) + spec = SimpleNamespace( + name="documentation_example", + loader=SimpleNamespace(exec_module=lambda module: None), + ) + monkeypatch.setattr( + pipeline.importlib.util, "spec_from_file_location", lambda *args: spec + ) + monkeypatch.setattr( + pipeline.importlib.util, "module_from_spec", lambda spec: module + ) + revisions = pipeline.git_dependencies(example) + monkeypatch.setattr( + pipeline.importlib.metadata, + "distribution", + lambda package: SimpleNamespace( + version="test-version", + read_text=lambda filename: json.dumps( + {"vcs_info": {"commit_id": revisions[package]}} + ), + ), + ) + monkeypatch.setattr(pipeline, "git", lambda *args: "a" * 40) + monkeypatch.setitem(sys.modules, "numpy", SimpleNamespace()) + destination = tmp_path / "outputs" + pipeline.worker(name, tmp_path / "raw", destination) + assert sys.argv is original_argv + metadata = json.loads((destination / "metadata.json").read_text()) + assert metadata["parameters"] == parameters + assert metadata["git_dependencies"] == revisions + for figure in example["figures"]: + assert (destination / figure).read_bytes() == b"mock plot" + assert ( + json.loads((destination / "summary.json").read_text())["log_evidence"] + == -1.0 + ) + + +def test_bundle_restores_execution_lock( + outputs: Path, tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + """RTD restores the execution snapshot even when its checkout lock differs.""" + import shutil + + monkeypatch.setattr(pipeline, "BUNDLES", tmp_path) + archive = pipeline.bundle() + checkout = tmp_path / "checkout" + source_paths = set() + for example in pipeline.examples(): + source_paths.update(pipeline.source_hashes(example)) + for relative in source_paths: + target = checkout / relative + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(pipeline.ROOT / relative, target) + archived_lock = (checkout / "pixi.lock").read_bytes() + (checkout / "pixi.lock").write_bytes( + archived_lock + b"\n# Different checkout lock\n" + ) + monkeypatch.setattr(pipeline, "ROOT", checkout) + with pytest.raises(ValueError, match="execution lock differs"): + pipeline.unpack(archive, "a" * 40, tmp_path / "refused") + pipeline.unpack( + archive, "a" * 40, tmp_path / "restored", restore_lock=True + ) + assert (checkout / "pixi.lock").read_bytes() == archived_lock + + +def test_execution_batch_refreshes_once( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """All child executions share one main resolution instead of racing updates.""" + calls = [] + monkeypatch.delenv("SEQUINCE_LOCK_READY", raising=False) + monkeypatch.setattr(sys, "argv", ["docs_pipeline.py", "run-all"]) + monkeypatch.setattr(pipeline, "refresh", lambda: calls.append("refresh")) + monkeypatch.setattr( + pipeline.subprocess, + "run", + lambda command, **kwargs: calls.append(kwargs), + ) + pipeline.main() + assert calls[0] == "refresh" + assert len(calls) == len(pipeline.examples()) + 1 + assert all(call["env"]["SEQUINCE_LOCK_READY"] == "1" for call in calls[1:]) diff --git a/tests/test_flow_summary.py b/tests/test_flow_summary.py new file mode 100644 index 0000000..057cc77 --- /dev/null +++ b/tests/test_flow_summary.py @@ -0,0 +1,43 @@ +"""Check the flow example's host summary without constructing a sampler.""" + +from __future__ import annotations + +import importlib.util +from pathlib import Path + +import pytest + + +@pytest.mark.parametrize("backend", ["numpy", "torch"]) +def test_flow_documentation_summary(backend: str) -> None: + np = pytest.importorskip("numpy") + torch = pytest.importorskip("torch") + pytest.importorskip("sequince_density") + from sequince import ParticleState + from sequince.result import SMCResult + from sequince.steps import StepContext + + path = ( + Path(__file__).resolve().parents[1] + / "examples/preconditioning/flow_preconditioning.py" + ) + spec = importlib.util.spec_from_file_location("flow_example", path) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + particles = np.array([[0.0, 0.0], [2.0, 0.0], [0.0, 4.0]]) + log_weights = np.log([0.5, 0.25, 0.25]) + if backend == "torch": + particles = torch.as_tensor(particles) + log_weights = torch.as_tensor(log_weights) + state = ParticleState(particles, log_weights=log_weights) + result = SMCResult([state], StepContext(t=0)) + + summary = module._documentation_summary(result) + + np.testing.assert_allclose(summary["weighted_mean"], [0.5, 1.0]) + np.testing.assert_allclose( + summary["weighted_population_covariance"], + [[0.75, -0.5], [-0.5, 3.0]], + ) + assert summary["n_particles"] == 3 diff --git a/tests/test_particle_filter_docs.py b/tests/test_particle_filter_docs.py new file mode 100644 index 0000000..591f268 --- /dev/null +++ b/tests/test_particle_filter_docs.py @@ -0,0 +1,74 @@ +"""Check filtering documentation exports without running either filter.""" + +from __future__ import annotations + +import importlib.util +import json +import sys +from pathlib import Path +from types import SimpleNamespace + +import pytest + + +@pytest.mark.parametrize("documentation", [False, True]) +def test_particle_filter_outputs_without_sampling( + documentation: bool, tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + np = pytest.importorskip("numpy") + pytest.importorskip("sequince") + source = ( + Path(__file__).resolve().parents[1] + / "examples/particle_filtering/particle_filter.py" + ) + spec = importlib.util.spec_from_file_location("filter_example", source) + assert spec is not None and spec.loader is not None + module = importlib.util.module_from_spec(spec) + monkeypatch.setitem(sys.modules, spec.name, module) + spec.loader.exec_module(module) + calls = [] + comparison = SimpleNamespace( + proposal_means=np.array([1.0, 2.0]), + bootstrap_means=np.array([0.0, 3.0]), + kalman_means=np.array([1.0, 1.0]), + proposal_variances=np.array([1.0, 1.0]), + bootstrap_variances=np.array([1.0, 2.0]), + kalman_variances=np.array([1.0, 1.0]), + proposal_ess=np.array([10.0, 20.0]), + bootstrap_ess=np.array([5.0, 10.0]), + proposal_log_evidence=-1.0, + bootstrap_log_evidence=-2.0, + kalman_log_evidence=-0.5, + ) + monkeypatch.setattr( + module, + "run_comparison", + lambda **kwargs: (calls.append(kwargs) or comparison), + ) + monkeypatch.setattr( + module, + "plot_comparison", + lambda result, output: (output.write_bytes(b"mock plot")), + ) + requested = tmp_path / "custom.png" + monkeypatch.setattr(sys, "argv", [str(source), "--output", str(requested)]) + if documentation: + module.documentation_output = tmp_path + module.main() + assert calls == [ + { + "n_observations": 20, + "n_particles": 1000, + "seed": 2026, + "always_resample": True, + "ess_threshold": 0.5, + } + ] + if documentation: + assert (tmp_path / "particle_filter.png").is_file() + summary = json.loads((tmp_path / "summary.json").read_text()) + assert summary["log_evidence"]["kalman"] == -0.5 + assert summary["mean_ess_before_resampling"]["optimal"] == 15.0 + else: + assert requested.is_file() + assert not (tmp_path / "summary.json").exists() From bb8d9155b778a4802f07c54959fd3f0b6ca48453 Mon Sep 17 00:00:00 2001 From: "Michael J. Williams" Date: Wed, 14 Oct 2026 13:43:29 +0200 Subject: [PATCH 3/3] CI(docs): validate artifacts and prepare Read the Docs hosting Run examples in parallel using one resolved Pixi lock per workflow, then validate same-run outputs and build Sphinx. Separate credential-free PR validation from trusted release-asset creation and serialized Read the Docs API triggers. Prepare standard Community build hooks that retrieve exact-commit bundles and restore the execution lock. Document private-repository access prerequisites, external setup, immutable asset handling, and build ordering limitations. No remote releases, builds, credentials, or service settings were created. --- .github/workflows/documentation.yml | 173 +++++++++++++++++ .readthedocs.yaml | 16 ++ MAINTENANCE.rst | 284 ++++++++++++++++++++++++++++ README.md | 33 +++- scripts/publish_docs.py | 128 +++++++++++++ tests/test_publish_docs.py | 122 ++++++++++++ 6 files changed, 755 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/documentation.yml create mode 100644 .readthedocs.yaml create mode 100644 MAINTENANCE.rst create mode 100644 scripts/publish_docs.py create mode 100644 tests/test_publish_docs.py diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml new file mode 100644 index 0000000..697ac93 --- /dev/null +++ b/.github/workflows/documentation.yml @@ -0,0 +1,173 @@ +name: Example documentation +on: + pull_request: + push: + branches: [main] + workflow_dispatch: + +permissions: + contents: read + +env: + SEQUINCE_LOCK_READY: "1" + +jobs: + manifest: + runs-on: ubuntu-latest + outputs: + matrix: ${{ steps.manifest.outputs.matrix }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.sha }} + persist-credentials: false + - uses: prefix-dev/setup-pixi@v0.9.3 + with: + pixi-version: v0.81.0 + manifest-path: pyproject.toml + run-install: false + cache: false + - name: Resolve current Sequince main once for this workflow + run: pixi update sequince + - uses: actions/upload-artifact@v4 + with: + name: execution-lock + path: pixi.lock + retention-days: 14 + if-no-files-found: error + - id: manifest + run: echo "matrix=$(python3 scripts/docs_pipeline.py matrix)" >> "$GITHUB_OUTPUT" + + examples: + needs: manifest + runs-on: ubuntu-latest + timeout-minutes: 60 + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.manifest.outputs.matrix) }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.sha }} + persist-credentials: false + - uses: actions/download-artifact@v4 + with: + name: execution-lock + path: . + - uses: prefix-dev/setup-pixi@v0.9.3 + with: + pixi-version: v0.81.0 + manifest-path: pyproject.toml + environments: ${{ matrix.environment }} + cache: true + locked: true + - run: pixi run --locked -e '${{ matrix.environment }}' example '${{ matrix.id }}' + - uses: actions/upload-artifact@v4 + with: + name: example-${{ matrix.id }} + path: build-docs/outputs/${{ matrix.id }}/ + if-no-files-found: error + retention-days: 14 + - uses: actions/upload-artifact@v4 + if: always() + with: + name: log-${{ matrix.id }} + path: build-docs/*.log + retention-days: 7 + + documentation: + needs: examples + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.sha }} + persist-credentials: false + - uses: actions/download-artifact@v4 + with: + name: execution-lock + path: . + - uses: prefix-dev/setup-pixi@v0.9.3 + with: + pixi-version: v0.81.0 + manifest-path: pyproject.toml + environments: docs check + cache: true + locked: true + # No run-id: download-artifact takes artifacts from this workflow run. + - uses: actions/download-artifact@v4 + with: + pattern: example-* + path: build-docs/downloads + - name: Collect example artifacts from this workflow + run: | + python3 - <<'PYTHON' + import json + import shutil + from pathlib import Path + destination = Path('build-docs/outputs') + destination.mkdir(parents=True) + for example in json.loads(Path('docs/examples.json').read_text())['examples']: + shutil.move(Path('build-docs/downloads') / ('example-' + example['id']), destination / example['id']) + PYTHON + - run: pixi run --locked -e check test + - run: pixi run --locked -e check lint + - run: pixi run --locked -e check format-check + - run: pixi run --locked -e docs docs + - run: pixi run --locked -e docs bundle + - uses: actions/upload-artifact@v4 + with: + name: validated-bundle + path: | + build-docs/docs-outputs-*.tar.gz + build-docs/docs-outputs-*.tar.gz.sha256 + if-no-files-found: error + retention-days: 14 + - uses: actions/upload-artifact@v4 + with: + name: documentation-html + path: docs/_build/html/ + if-no-files-found: error + retention-days: 14 + + publish: + needs: documentation + if: github.repository == 'sequince-dev/sequince-examples' && github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + runs-on: ubuntu-latest + timeout-minutes: 55 + concurrency: + group: examples-rtd-latest + cancel-in-progress: false + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.sha }} + persist-credentials: false + - uses: actions/download-artifact@v4 + with: + name: execution-lock + path: . + - uses: actions/download-artifact@v4 + with: + name: validated-bundle + path: build-docs + - name: Verify same-run bundle before publication + run: | + python3 - <<'PY' + import sys + from pathlib import Path + sys.path.insert(0, 'scripts') + from docs_pipeline import ROOT, OUTPUT, digest, git, unpack + sha = git('rev-parse', 'HEAD') + path = ROOT / 'build-docs' / f'docs-outputs-{sha}.tar.gz' + assert Path(str(path) + '.sha256').read_text().split() == [digest(path), path.name] + unpack(path, sha, OUTPUT) + PY + - name: Publish durable assets and wait for RTD + run: python3 scripts/publish_docs.py + env: + GH_TOKEN: ${{ github.token }} + RTD_TOKEN: ${{ secrets.RTD_TOKEN }} + RTD_PROJECT: ${{ vars.RTD_PROJECT }} diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..418df24 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,16 @@ +version: 2 +build: + os: ubuntu-24.04 + tools: + python: "3.13" + jobs: + pre_build: + - python scripts/docs_pipeline.py fetch + post_build: + - python scripts/docs_pipeline.py check-latest +python: + install: + - requirements: docs/requirements.txt +sphinx: + configuration: docs/conf.py + fail_on_warning: true diff --git a/MAINTENANCE.rst b/MAINTENANCE.rst new file mode 100644 index 0000000..7fb8818 --- /dev/null +++ b/MAINTENANCE.rst @@ -0,0 +1,284 @@ +Setup and maintenance +===================== + +Local execution +--------------- + +Install Pixi 0.81.0 or newer, then run from the repository root:: + + pixi install --locked -e default -e docs -e check + pixi run --locked example gaussian + pixi run --locked example rosenbrock + pixi run --locked example adaptive-mutation + pixi run --locked -e docs docs + pixi run --locked -e check test + pixi run --locked -e check lint + pixi run --locked -e check format-check + pixi run --locked -e check pre-commit run --all-files + +The Makefile in ``docs/`` wraps these Pixi tasks. From the repository root:: + + make -C docs setup + make -C docs check + make -C docs example EXAMPLE=rosenbrock + make -C docs examples + make -C docs docs + make -C docs docs-only + make -C docs validate + +``make -C docs docs`` checks existing output completeness and provenance, then builds +the full Sphinx site. It reruns examples only if outputs are missing or stale. +``make -C docs validate`` runs checks, explicitly reruns all examples, then builds the +site. Both preserve execution order with ``make -C docs -j``. +You can also change into ``docs/`` and run these targets without ``-C docs``. ``make -C docs docs-only`` never +executes examples and fails if outputs are missing or stale. Use this target +when editing documentation on a machine with limited memory. Use ``make -C docs example EXAMPLE= ENV=`` for +a single example requiring another backend. ``make -C docs hooks`` runs all pre-commit +hooks and may edit files. ``make -C docs help`` lists the available targets. The +``SEQUINCE_INVENTORY`` environment override described below also works with make. + +``pixi run --locked examples`` runs every manifest entry using its declared +environment. Open ``docs/_build/html/index.html`` after a successful build. +The maintained narratives remain beside the scripts under ``examples/``. +The pipeline copies them and script sources into ``docs/_generated`` so relative ``literalinclude`` +paths still resolve. Both that directory and ``build-docs`` are ignored. + +The runner imports each script and overrides only its output-directory global. +It preserves the original working directory, parameters, sampler, seed and RNG +calls. For CLI examples the runner passes the script defaults without the +pipeline's own arguments. The three additional scripts write concise JSON +statistics only when the runner sets ``documentation_output``; ordinary script +execution keeps its existing output behavior. Matplotlib uses Agg. Raw particles and HDF5 results live in a temporary +run directory and are removed after extracting plots and weighted summaries. +Logs remain in ``build-docs``. The covariance in the summary is the weighted +population covariance; it does not apply a finite-sample correction. No target, +weighting, evidence convention or mutation setting is changed for publication. + +Dependencies and new examples +----------------------------- + +During development, Sequince follows the latest ``main`` branch in +``pyproject.toml``. Normal example commands refresh that dependency before +execution. ``make -C docs setup`` and ``make -C docs docs`` refresh it too; +``make -C docs docs-only`` uses the existing snapshot and never runs sampling. +Use ``make -C docs refresh`` to resolve main without executing examples. +Direct standalone Python script execution uses whichever Sequince is installed; +use the runner commands for automatic refresh. + +Actions resolves main once in the manifest job, shares the resulting +``pixi.lock`` with every matrix job, and uses locked installs afterward. This +keeps one workflow internally consistent even if main advances during the run. +``SEQUINCE_LOCK_READY=1`` is reserved for such already-resolved batches or +intentional reproduction of an archived execution; it suppresses refresh. + +``pixi.lock`` remains versioned as a bootstrap snapshot. Metadata verifies and +records the installed version and exact resolved Git commit. Each output bundle +includes ``execution-pixi.lock`` with checksums. RTD restores that archived lock +before validating outputs; it does not fetch today's main. The publishing job +allows this intentional lock-file change but rejects other tracked edits. + +Separate executions of the same example source commit can now produce different +results as Sequince main changes. Existing immutable-by-policy bundles still +cannot be overwritten with different bytes: publish a new example source commit +for a new dependency snapshot. To reproduce an old run, use its archived lock +and ``SEQUINCE_LOCK_READY=1``. This development policy supersedes the original +requirement that dependency resolution be identical for every run of a source +commit. When Sequince releases are available, replace the branch dependency with +the latest compatible release and revise the refresh command at the same time. + +The docs environment contains no sampler or optional backend. The separate +``flow`` environment uses CPU-only PyTorch and pins ``sequince-density`` to a +full Git commit in its repository (currently named ``sequince-flows``). Both Git +dependency commits and installed versions are verified and recorded for flow +runs. The separate ``jax`` environment is a CPU template for future examples. + +The Sequince repository is currently private and there is no public PyPI +release. Existing SSH access was used to resolve the local lock. To use existing +SSH access locally without changing Git configuration permanently:: + + GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=url.git@github.com:.insteadOf \ + GIT_CONFIG_VALUE_0=https://github.com/ pixi install --locked + +Before enabling this workflow for public/fork PRs, make the dependency +publicly readable (or replace it with a compatible exact public release and +regenerate the lock). Never add a private dependency token to PR execution. +RTD only installs Sphinx and reads public output assets, so it needs no Sequince +repository access or sampling environment. The Sequince documentation inventory +at ``https://sequince.readthedocs.io/en/latest/objects.inv`` must also be publicly +readable; current probes returned HTTP 403/404 in this environment. For local +validation against an inventory generated by the main Sequince Sphinx build, +set ``SEQUINCE_INVENTORY=/absolute/path/to/objects.inv``. This overrides only the +inventory location; generated API links still point to the main Sequince docs. + +Place new example directories under ``examples/`` and add a record to +``docs/examples.json`` with a unique ID, script, working +directory, environment, timeout in seconds, narrative, parameter globals, +required raw outputs and captioned figures. Scripts must expose ``main()`` and +``outdir``, save weighted particles in ``posterior.npz`` with ``particles``, +``weights`` and scalar ``log_evidence``, and list every required output. +Seed must be a declared parameter. Examples can provide a raw ``summary.json`` +instead of ``posterior.npz``; the runner validates and packages it without +rendering it in the tutorial. CLI examples expose ``build_parser()`` so metadata +records their actual defaults. Optional Git dependencies list their feature +names under ``dependency_features`` in the execution manifest. Add backend features/environments to +``pyproject.toml`` and regenerate the lock as needed. The execution matrix comes from the manifest; no workflow edit is needed for +another entry. Put figure directives, captions, and summary includes directly +in the example README using ``/_generated/results//`` paths. The staging +step copies the narrative, script, and validated outputs without creating a +separate result page. Link its staged README from ``docs/index.rst`` in reading +order. Narrative-only tutorials keep their ``README.rst`` beside the script in +``examples/``. Add their directory and source filename to the staging list in +``prepare()`` and link the staged README from ``docs/index.rst``. This preserves +relative source includes without executing optional backends or adding an +execution-manifest entry. Keep contributor and hosting instructions +in this repository document rather than the tutorial navigation. + +Provenance and durable bundles +------------------------------ + +Every example records the checked-out source SHA, hashes of its script and +narrative, manifest, runner and Pixi inputs, installed Sequince version/resolved main commit, +Python version, environment, parameters and seed. Per-example checksums are +validated before Sphinx. Local edited inputs are fingerprinted, but publication +requires a clean tracked checkout except for the resolved execution lock. Actions checks out the same SHA in each job +(including the synthetic merge commit on PRs) and downloads only same-run artifacts. + +Trusted main runs create ``docs-outputs-.tar.gz`` and a companion +``.sha256`` asset on the dedicated prerelease ``docs-output-`` in +**sequince-examples**, never the library repository. The archive includes plots, +concise summaries, provenance, checksums, ``execution-pixi.lock`` and +``bundle-manifest.json``. It excludes +raw chains, checkpoints and HDF5 files. Tar and gzip headers are deterministic. +Existing assets are downloaded and compared before reuse; different bytes fail +rather than replacing archived results. Draft releases allow recovery from a +partial upload. Do not move these tags or edit release assets. + +Retain release assets for as long as any corresponding RTD version may be +rebuilt. Actions artifacts expire after 14 days (logs after 7) and serve only +intermediate/PR validation. To regenerate, check out the exact source revision, +restore the bundle execution lock and set ``SEQUINCE_LOCK_READY=1`` before +executing examples, building docs and creating the bundle. A rerun +with different results intentionally fails publication: investigate the mismatch, +then publish a new source commit instead of replacing history. Hardware/BLAS +variation can change numerical or figure bytes despite a fixed seed. + +Read the Docs Community setup +----------------------------- + +These are manual maintainer steps; no external settings or credentials are +created by this change. + +#. Make sequince-examples and its output release assets public. Import the repo + at ``app.readthedocs.org`` as a separate Community project, with main as the + default branch and ``.readthedocs.yaml`` as its configuration. +#. Keep ``latest`` active/default. Disable automatic push/tag builds and PR + builds for this project. For a GitHub App integration, use automation rules + with an enabled **Trigger build for version** rule that never matches real + refs (for example a reserved ``^disabled-auto-build$`` ref), and remove other + build-trigger rules. Such rules gate webhook builds; explicit API builds + remain available. For legacy webhook integrations, disable automatic builds + on the integration. Verify no ordinary push starts a build before enabling + publication. Do not enable rules that activate ``docs-output-*`` tags. +#. In the main Sequince project's **Subprojects** settings attach this imported + project with alias ``examples``. Select the examples default version as + latest. Its URL is ``https://sequince.readthedocs.io/projects/examples/en/latest/``. + The main Sequince build system remains unchanged. A parent navigation link to + this URL can be added when the subproject is live. +#. Set GitHub Actions variable ``RTD_PROJECT`` to the imported project's slug + and secret ``RTD_TOKEN`` to an RTD Community API token. Tokens inherit their + user's permissions: use a dedicated maintainer account with access only to + this project where possible. RTD needs no GitHub publishing token. The workflow + uses the automatic GitHub token with ``contents: read`` for validation and + ``contents: write`` only for the trusted publishing job. Allow Actions to + create releases in the examples repository. No secrets enter PR jobs. +#. Merge the files, then run the workflow on main once dependency access is + ready. Inspect the release assets and monitored RTD result. Initial import + builds can fail until the first matching bundle is uploaded; rerun afterward. + +RTD runs standard Sphinx on Community, downloading and verifying the exact bundle +for ``git rev-parse HEAD`` in ``pre_build``. It never falls back to an older +bundle. A trigger for ``latest`` does not pin a commit: the actual checkout is +verified and must have its own matching bundle. Main freshness is checked before +Sphinx and in ``post_build``, before RTD's upload. + +The publishing job holds a non-cancelling concurrency lock until RTD finishes, +waits for any existing RTD builds, and checks current main before triggering. +Stale workflow runs skip triggering. A newer push during an older RTD build +causes the freshness hook to fail; its workflow then releases the lock and the +newer run can build. This prevents older workflow builds from overwriting newer +successful docs. Do not run concurrent manual builds or enable automatic builds: +RTD has no customizable upload hook or API commit-pinning guarantee. After a +monitor timeout or manual cancellation, inspect/drain RTD builds before retrying; +the next publishing job also waits for active builds. + +Tagged versions and stable +-------------------------- + +Initially publish only main/latest. Do not automatically activate artifact tags +or a stable alias. For a curated example release, choose an immutable ``vX.Y.Z`` +tag on a main commit whose output bundle already exists; sync and activate that +version in RTD, then manually build it. The hook downloads the bundle for the +tag's actual checkout. Configure stable to the newest curated release only +when adopting this policy. Never retarget a release tag; artifact prereleases +are not example releases. The current workflow intentionally does not trigger +tag/stable builds or create semantic version tags. + +PRs and troubleshooting +----------------------- + +PRs execute the full matrix and build Sphinx, with downloadable HTML artifacts. +They do not publish releases, receive RTD credentials or create RTD previews. +Use the HTML artifact for review. RTD PR previews would require a separate +trusted bundle/authorization policy and are disabled here. + +For failed examples, inspect the named job and its log artifact, reproduce with +``pixi run --locked example ``, and check required filenames and the timeout. +Do not reduce particle counts or change seeds just to pass CI. For a missing +bundle, verify the full RTD checkout SHA has a successful publishing workflow +and matching release assets; execute that revision instead of selecting another +commit's outputs. A checksum/provenance failure means corruption or mixed inputs: +re-download or reproduce the exact locked revision. Missing figures fail before +Sphinx. Warnings (including missing Intersphinx inventories) fail the build. + +For RTD API failures check the project slug, account permissions, token, rate +limits and build logs. A main-advanced error is expected during rapid pushes; +check that the newest workflow eventually succeeds. A private dependency error +in Actions requires public dependency access, not credentials in PR jobs. + +Official integration references +------------------------------- + +* `Pixi setup action and cache/locked inputs `_ +* `GitHub release and asset API `_ +* `RTD build hooks `_ +* `RTD API v3, builds and authentication `_ +* `RTD webhook automation rules `_ +* `RTD subprojects `_ + +Prepared plot integrations +-------------------------- + +The flow preconditioning, persistent sampling, and waste-free examples are now +configured for the matrix and durable bundles, with inline figure directives. +Their new integrations have not been executed or numerically validated locally. +The standalone scripts retain their particle counts, dimensions, seeds, fitting +budgets, and mutation settings. Some constants have moved to module scope so +the runner can record them. Documentation-only summary writes do not draw RNG. +Flow preconditioning uses its existing four-dimensional, 1,000-particle setup; +persistent sampling uses the parser defaults (four dimensions, 256 active +particles); waste-free SMC keeps 100 chains of length 100 and seed 123. + +Do not treat pre-existing loose PNG files as verified documentation outputs. +A full build requires fresh, matching provenance for all seven execution entries. +In particular, ``make -C docs docs`` may launch sampling when outputs are stale +or missing; ``make -C docs docs-only`` never launches it. The runner has timeouts +but no enforced memory ceiling yet. Validate the new runs on suitable hardware +or after adding memory limits. Dependency resolution and mocked worker tests +do not validate numerical behavior or plot quality. + +Particle filtering is also configured for automated execution, with its existing +defaults of 1,000 particles, 20 observations, seed 2026, and resampling after +every observation. The runner isolates its plot and captures accuracy, ESS, +and evidence diagnostics in the bundle without rendering numerical summaries. +This integration has mocked worker and layout checks; no filtering run was +performed while preparing it. diff --git a/README.md b/README.md index 56257bf..61ebadb 100644 --- a/README.md +++ b/README.md @@ -1 +1,32 @@ -# sequince-examples \ No newline at end of file +# Sequince examples + +Example scripts and their narrative tutorials live together under `examples/`. +Start with the [basic examples](examples/basic_examples/README.rst), then explore +[adaptive mutation](examples/adaptive_mutation_steps/README.rst), +[flow preconditioning](examples/preconditioning/README.rst), and +[persistent sampling](examples/persistent_sampling/README.rst). The documentation +also covers waste-free SMC and particle filtering. + +The documentation pipeline is configured to execute Gaussian, Rosenbrock, +adaptive mutation, flow preconditioning, persistent sampling, and waste-free +SMC and particle-filtering examples using the latest Sequince main, resolved +once per execution batch. Their plots appear directly in the tutorial pages. + +```bash +pixi install --locked -e default -e docs +pixi run --locked examples +pixi run --locked -e docs docs +``` + +Use `make -C docs setup` followed by `make -C docs docs` to build the full documentation. +Valid example outputs are reused; missing or stale outputs trigger example +execution. `make -C docs docs-only` never executes examples. Use `make -C docs validate` to run +checks, rerun all examples, and build the documentation. +Run `make -C docs check` for tests, lint, and formatting checks, or `make -C docs help` for +individual targets. New example runs refresh Sequince main; all jobs in a batch +use the same resolved lock. Later, the dependency will switch to releases. + +See [setup and maintenance](MAINTENANCE.rst) for dependency access, adding +examples, publication and Read the Docs Community subproject setup. Generated +results are ignored. Environments, dependencies, and tasks are defined in +`pyproject.toml`; `pixi.lock` is intentionally tracked for reproducibility. diff --git a/scripts/publish_docs.py b/scripts/publish_docs.py new file mode 100644 index 0000000..17bdf21 --- /dev/null +++ b/scripts/publish_docs.py @@ -0,0 +1,128 @@ +"""Trusted publication only: immutable-by-policy assets, then serialized RTD.""" + +from __future__ import annotations + +import json +import os +import subprocess +import time +import urllib.parse +import urllib.request +from pathlib import Path + +from docs_pipeline import ROOT, bundle, digest, git + + +def gh(*args: str) -> str: + """Call the GitHub CLI, failing on API or permission errors.""" + return subprocess.check_output(["gh", *args], cwd=ROOT, text=True).strip() + + +def rtd(path: str, method: str = "GET") -> dict: + """Call the documented Community API without logging credentials.""" + request = urllib.request.Request( + "https://app.readthedocs.org/api/v3/" + path, + headers={"Authorization": "Token " + os.environ["RTD_TOKEN"]}, + method=method, + ) + with urllib.request.urlopen(request, timeout=60) as response: + return json.load(response) + + +def wait_idle(project: str, deadline: float) -> None: + """Wait through the RTD upload before allowing another publication.""" + while rtd(f"projects/{project}/builds/?running=true")["count"]: + if time.monotonic() > deadline: + raise TimeoutError( + "RTD still running; inspect builds before retrying" + ) + time.sleep(20) + + +def main() -> None: + """Publish only main, verify assets, and monitor the triggered build.""" + sha = git("rev-parse", "HEAD") + if git("ls-remote", "origin", "refs/heads/main").split()[0] != sha: + print("Main has advanced; skipping stale publication") + return + if set(git("diff", "--name-only", "HEAD").splitlines()) - {"pixi.lock"}: + raise ValueError("Refusing publication from a modified checkout") + path = bundle() + tag = f"docs-output-{sha}" + # Dedicated prereleases in the examples repository cannot become a library + # release or GitHub's latest software release. + view = subprocess.run( + ["gh", "release", "view", tag, "--json", "assets,isDraft"], + cwd=ROOT, + text=True, + capture_output=True, + ) + if view.returncode: + gh( + "release", + "create", + tag, + "--target", + sha, + "--title", + tag, + "--notes", + f"Validated example documentation outputs for {sha}.", + "--prerelease", + "--latest=false", + "--draft", + ) + release = {"assets": [], "isDraft": True} + else: + release = json.loads(view.stdout) + names = {asset["name"] for asset in release["assets"]} + for asset in (path, Path(str(path) + ".sha256")): + if asset.name in names: + existing = ROOT / "build-docs" / "existing" + existing.mkdir(exist_ok=True) + gh( + "release", + "download", + tag, + "--pattern", + asset.name, + "--dir", + str(existing), + "--clobber", + ) + if digest(existing / asset.name) != digest(asset): + raise ValueError( + f"Refusing to replace different asset: {asset.name}" + ) + else: + gh("release", "upload", tag, str(asset)) + if release["isDraft"]: + gh("release", "edit", tag, "--draft=false", "--latest=false") + # The workflow's non-cancelling concurrency lock covers this whole wait. + project = urllib.parse.quote(os.environ["RTD_PROJECT"], safe="") + deadline = time.monotonic() + 2400 + wait_idle(project, deadline) + if git("ls-remote", "origin", "refs/heads/main").split()[0] != sha: + print("Bundle uploaded; main advanced, skipping latest trigger") + return + result = rtd(f"projects/{project}/versions/latest/builds/", "POST") + build_id = result["build"]["id"] + while True: + build = rtd(f"projects/{project}/builds/{build_id}/") + if build["state"]["code"] in {"finished", "cancelled"}: + break + if time.monotonic() > deadline: + raise TimeoutError( + "RTD build timed out; inspect it before retrying" + ) + time.sleep(20) + if not build["success"] or build["commit"] != sha: + raise ValueError( + f"RTD build {build_id} failed or checked out another commit; " + "inspect RTD and rerun the workflow for current main" + ) + print(f"RTD build {build_id} succeeded for {sha}") + + +if __name__ == "__main__": + main() diff --git a/tests/test_publish_docs.py b/tests/test_publish_docs.py new file mode 100644 index 0000000..204d07c --- /dev/null +++ b/tests/test_publish_docs.py @@ -0,0 +1,122 @@ +"""Check publication ordering and refusal paths without external writes.""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path +from types import SimpleNamespace + +import pytest + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts")) +import publish_docs as publisher # noqa: E402 + + +@pytest.fixture +def publication( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> list[tuple]: + """Replace GitHub and RTD calls with a recorded successful publication.""" + calls: list[tuple] = [] + sha = "a" * 40 + monkeypatch.setattr(publisher, "ROOT", tmp_path) + monkeypatch.setattr( + publisher, "git", lambda *args: ("" if args[0] == "diff" else sha) + ) + bundle = tmp_path / "bundle.tar.gz" + bundle.write_bytes(b"validated outputs") + Path(str(bundle) + ".sha256").write_text("checksum") + monkeypatch.setattr(publisher, "bundle", lambda: bundle) + monkeypatch.setattr( + publisher.subprocess, + "run", + lambda *args, **kwargs: (SimpleNamespace(returncode=1, stdout="")), + ) + monkeypatch.setattr( + publisher, "gh", lambda *args: calls.append(args) or "" + ) + monkeypatch.setenv("RTD_PROJECT", "examples") + + def api(path: str, method: str = "GET") -> dict: + calls.append((method, path)) + if "running=true" in path: + return {"count": 0} + if method == "POST": + return {"build": {"id": 123}} + return {"state": {"code": "finished"}, "success": True, "commit": sha} + + monkeypatch.setattr(publisher, "rtd", api) + return calls + + +def test_publish_before_trigger(publication: list[tuple]) -> None: + publisher.main() + uploaded = publication.index( + ( + "release", + "edit", + "docs-output-" + "a" * 40, + "--draft=false", + "--latest=false", + ) + ) + triggered = publication.index( + ( + "POST", + "projects/examples/versions/latest/builds/", + ) + ) + assert uploaded < triggered + assert publication[-1] == ("GET", "projects/examples/builds/123/") + + +def test_stale_run_does_not_publish( + publication: list[tuple], monkeypatch: pytest.MonkeyPatch +) -> None: + monkeypatch.setattr( + publisher, + "git", + lambda *args: ("b" * 40 if args[0] == "ls-remote" else "a" * 40), + ) + publisher.main() + assert not publication + + +def test_different_existing_asset_fails( + publication: list[tuple], tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + monkeypatch.setattr( + publisher.subprocess, + "run", + lambda *args, **kwargs: ( + SimpleNamespace( + returncode=0, + stdout=json.dumps( + { + "assets": [{"name": "bundle.tar.gz"}], + "isDraft": False, + } + ), + ) + ), + ) + (tmp_path / "build-docs").mkdir() + + def download(*args: str) -> str: + publication.append(args) + target = Path(args[args.index("--dir") + 1]) / "bundle.tar.gz" + target.write_bytes(b"different uploaded content") + return "" + + monkeypatch.setattr(publisher, "gh", download) + with pytest.raises(ValueError, match="Refusing to replace"): + publisher.main() + assert not any(call[0] == "POST" for call in publication) + + +def test_wait_idle_times_out(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr(publisher, "rtd", lambda path: {"count": 1}) + monkeypatch.setattr(publisher.time, "monotonic", lambda: 10.0) + with pytest.raises(TimeoutError, match="RTD still running"): + publisher.wait_idle("examples", 0.0)