Skip to main content

SAGARI

Schwarzschild Adaptive Gaussian-process Acquisition, Refinement, and Inference

Tests PyPI Documentation License: MIT

Adaptive Gaussian-process sampling for Schwarzschild orbit-superposition modeling, and for any other model whose goodness of fit is an expensive, noisy χ².

An orbit-superposition model costs hours of CPU time, and a nested parameter grid needs ~10⁴ of them. SAGARI replaces the grid with a sequence of rounds, each placed where a surrogate of the χ² surface predicts the largest gain, and stops the campaign when the answer stops moving. On the archived models of two published black-hole mass measurements it recovered the published mass in every replayed trial with 9 to 30 times fewer models than the original campaigns.

It is the method of

Waters & Gültekin (2026), Schwarzschild Orbit-library Modeling with an Order of Magnitude Fewer Models: Adaptive Gaussian-process Sampling and a Case Study of M 86

and reproduces the code used there exactly (details).

The package never runs a model. It proposes parameter sets and reads back their χ², so it can drive any code: an orbit-superposition pipeline on a cluster, a Jeans model, or a lookup in an archive.

📖 Documentation: https://sagari.readthedocs.io

Installation

pip install sagari            # add [plot] for the example notebooks

Python ≥ 3.10. Depends on NumPy, SciPy, pandas, scikit-learn, emcee and PyYAML.

Quick start

import sagari as sg

space = sg.ParameterSpace.from_dict({"parameters": {
    "mbh":         {"bounds": [1e6, 1e10], "scale": "log"},
    "upsilon":     {"bounds": [1.0, 10.0]},
    "v_c":         {"bounds": [0.0, 500.0]},
    "inclination": {"value": 60.0},            # fixed
}})

def my_model(design):                          # a DataFrame of parameter sets -> one chi2 per row
    ...                                        # (NaN for a model that failed)

c = sg.Campaign.create("my_campaign", space, converge_on=["mbh", "upsilon"])
results = c.run(my_model)                      # Sobol design, search rounds, stopping rule, error-bar rounds

gp, _, _ = c.surrogate(results)                # the final surrogate of the chi2 surface
post = sg.posterior.sample_posterior(gp, space)

On a cluster

When each model is a batch job, the campaign lives in a directory and moves one round at a time:

sagari init my_campaign --space space.yaml --converge-on mbh,upsilon
sagari next my_campaign                          # writes my_campaign/rounds/round_000.csv
#   ... run the models of the round with your own code; add model_id,chi2 rows to results.csv ...
sagari next my_campaign --results results.csv    # the next round (search or error bars), or "done"
sagari status my_campaign --results results.csv  # ranges per round, stopping rule, out-of-sample checks
sagari posterior my_campaign --results results.csv --out posterior --profiles

Failed models are written as chi2 = nan and kept: the search learns to avoid where models fail. examples/sco_runalls.py connects a campaign to the Fortran pipeline of the paper (runallt commands file in, cres files out).

How it works

  1. Initial design: a scrambled Sobol sequence over the box, continued by every later round.
  2. Search rounds: a Gaussian process with a Matérn 5/2 kernel and an explicit nugget for the model-to-model scatter of χ², fitted near the minimum. A second GP fitted to every model keeps the search out of regions that are hopeless or where models fail. Each round places 65% of its models by feasibility-weighted Expected Improvement, with a denoised incumbent, two-scale candidates and a minimum separation, and verifies isolated low models by their neighbours. The other 35% continues the Sobol sequence.
  3. Stopping rule: smoothed 16–84% ranges of the parameters you care about. The search stops when they move by less than 0.2 of their width, after at least two refinement rounds.
  4. Error-bar rounds: models fill the acceptable region and the parameter profiles. Each model's χ² is predicted before it runs, so the surrogate is tested out of sample.
  5. Posterior: MCMC on the final surrogate where models constrain it, averaged over the surrogate's own uncertainty, plus Δχ² profile intervals.

The defaults are the settings adopted and validated in the paper.

Example notebooks

notebook
01 Your first campaign a complete campaign in Python on a noisy analytic χ²
02 A campaign on a cluster round files, results tables, failures, narrowing the box, the CLI
03 From the surrogate to a measurement profiles, posteriors, two solutions, surrogate uncertainty
04 Benchmark and replay the paper's two-basin benchmark and a replay on an archive

Fidelity to the paper

The package is a port of the campaign code used in the paper, not a reimplementation:

  • tests/test_parity.py runs the original code and the package side by side and requires identical output at every stage (needs the original code; see the docs).
  • tests/test_golden.py checks the same outputs against a fixture written by the original code, and runs anywhere.
  • Replays of the paper's NGC 5102 and NGC 3258 campaigns, and its two-basin benchmark (benchmarks/two_basin.py), are reproduced model for model.

Exact repetition from a seed also needs the same library versions and the same number of linear-algebra threads (OMP_NUM_THREADS=1).

Citing

Please cite Waters & Gültekin (2026) and the software (CITATION.cff).

License

MIT. See LICENSE.

Metadata

Release files for sagari 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for sagari 1.0.0
File Size Uploaded
sagari-1.0.0.tar.gz 90.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for sagari 1.0.0
File Interpreter ABI Platform
sagari-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 138.5 kB

Release files / sagari-1.0.0.tar.gz

Download URL sagari-1.0.0.tar.gz
Size 90.2 kB
Tags Source
SHA-256 checksum
How to use checksums
d43e40399d3ca5e050015515cfcde8eb92e49ad374db4b7c5023363aa3d3d200
BLAKE2b-256 checksum
How to use checksums
23889430d2789d98ee2d05073abb6d841499a4836844ea05c5d2b7b83d40104f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release files / sagari-1.0.0-py3-none-any.whl

Download URL sagari-1.0.0-py3-none-any.whl
Size 48.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1763db0d13bdbe13c88f1b8c3ddc5edc40d3a2853375cfe358ae98f8869de784
BLAKE2b-256 checksum
How to use checksums
c707dbe68dc3078b386d116de07b10a7d5c996d8a0d9d2446a894c9a2f7437dd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page