lazuli-exo
Exoplanet transmission spectroscopy simulator for the Lazuli IFS
lazuli-exo returns the model transmission spectrum of a transiting planet binned to the Lazuli IFS, with photon/read-noise error bars for a chosen number of transits, plus a spectral-template S/N. Two ways in:
- Showcase planets: 14 well-known planets x 5 atmosphere scenarios from
the frozen NASA Exoplanet Archive registry, with precomputed slicersim
noise tables (
lazuli_exo.observe). - Parametric model: any planet from its star, radius, mass, T_eq, transit
duration and atmosphere knobs ([M/H], C/O, mu, cloud deck, haze), with
slicersim computing the noise live (
lazuli_exo.simulate).
Both paths evaluate the same parametric model (a POSEIDON-derived opacity grid), so a showcase planet and the same numbers typed by hand give the same spectrum.
What this is, and is not
The instrument half is quantitative. Photon and read noise come from slicersim with the Lazuli CBE throughput, the H4RG MACC readout with saturation tested at the last read, the LSF and within-pixel photon weighting. The photon budget is verified against first principles to < 1 % per channel.
The atmosphere half is a fast surrogate, not a retrieval-grade model.
simulate() interpolates a grid of POSEIDON isothermal, equilibrium-chemistry
spectra (gray deck, power-law haze) in about a millisecond. Inside its
validated box it reproduces POSEIDON to well under the single-transit noise
(median 2 ppm rms, worst bin < 1 sigma, gated in CI). It is a surrogate for
two separate reasons:
- deployment: POSEIDON needs ~70 GB of opacity inputs, ~6 GB of RAM and a numpy<2 environment that cannot hold slicersim; the runtime here is numpy plus 50 MB of tables, so it fits a wheel and a web ETC;
- physics class: even run live, this is a 1D isothermal model with equilibrium chemistry and statistical noise only. No P-T profile, no disequilibrium, no patchy clouds, no systematics floor.
Use it for target ranking, choosing R and the transit count, comparing
scenarios on one planet, and instrument trade studies: anything that is a
ratio or an ordering. Do not quote its sigmas as detection significances
in a proposal, read abundances off it, or trust it outside the validated box
(H/R_p > 0.015, 400-800 K hosts across the alkali transition, T_eq > 2000 K,
mean molecular weight > 15, many-transit forecasts where a systematics floor
would dominate). simulate() warns on the ones it can detect.
For an accurate atmosphere, bring your own spectrum. Run POSEIDON,
petitRADTRANS, PICASO or any forward model you trust, and pass
spectrum=(wl_um, depth) to simulate(): the star, noise, contamination and
instrument response are applied exactly as for the surrogate. Details,
physics and caveats: docs/MODEL.md.
Installation
From PyPI
pip install lazuli-exo # numpy + the lookup tables (~35 MB)
pip install "lazuli-exo[plot]" # matplotlib for the .plot() methods
pip install "lazuli-exo[transit,archive]" # jaxoplanet transit models, Exoplanet Archive cache
Extras: plot (matplotlib), transit (astropy, pandas, jaxoplanet),
archive (astroquery), web (Flask for the local applet), test (pytest),
docs (Sphinx, nbsphinx). The showcase path (observe) needs nothing
beyond numpy. The parametric model's live noise (simulate) needs
slicersim and sncosmo in the same environment; slicersim is not on PyPI,
install it from https://github.com/MickaelRigault/slicersim.
From source
git clone git@github.com:schmidt-observatories/lazuli-exo.git
cd lazuli-exo
pip install -e ".[plot,test]"
The same extras apply, e.g. pip install -e ".[plot,web,test,docs]".
Quickstart
import lazuli_exo
# showcase planet (precomputed noise table)
obs = lazuli_exo.observe("WASP-39 b", scenario="cloudy", n_transits=5, R=100)
obs.wavelength, obs.depth, obs.depth_err # binned spectrum + 1-sigma per bin
obs.detect() # {"template_snr_diagonal": ..., "band_snr": {...}}
obs.simulate_points(seed=0) # one noisy realization
obs.plot() # model + one realization [plot] extra
obs.plot_noise() # per-bin error bar vs wavelength
obs.light_curves(n_groups=5, seed=0).plot() # banded transit light curves
# any planet (needs slicersim + sncosmo)
p = lazuli_exo.presets.showcase("WASP-39 b", "clear") # or build the dict by hand
p.update(T_eq_K=900.0, mu=None, log_P_cloud_bar=-2.0)
obs = lazuli_exo.simulate(p, R=100, n_transits=5)
# your own forward model (POSEIDON, petitRADTRANS, PICASO, ...): (wl_um, depth)
# covering 0.40-1.70 um with a little margin; only the star keys are needed
star = dict(teff_K=5485., logg=4.45, feh=0.0, mag=10.66, t14_s=10092.)
obs = lazuli_exo.simulate(star, spectrum=(wl_um, depth), R=100, n_transits=5)
Parameter names for simulate: T_eq_K, R_p_RJ, M_p_MJ, R_s_RSun, teff_K, logg, feh, mag, band (J/H/Ks/V), mag_system ("vega" default or "ab"), t14_s, log_met, c_to_o, mu (None = chemistry), log_P_cloud_bar (None = clear), log_a_haze (None = no haze), haze_slope, f_spot, T_spot_K, f_fac, T_fac_K (stellar contamination, default off).
Transit light-curve models
lazuli_exo.transit (formerly the standalone lazuli-transit package) holds
the instrument-agnostic transit models the Lazuli simulators share. The WCC
ETC (wcc_etc) consumes them through relative_flux(time).
from lazuli_exo.transit import TransitModel, download_exoplanet_archive
model = TransitModel(per=1.09, rp=0.117, a=3.04, inc=83.3, u=(0.1, 0.3))
flux = model.relative_flux(time_days) # [transit]: jaxoplanet
df = download_exoplanet_archive() # [archive]: astroquery, cached in ~/.lazuli_exo/
model = TransitModel.from_planet("WASP-12 b", df=df)
Tutorial
See the notebooks/ directory:
| Notebook | Covers |
|---|---|
01_getting_started |
observe -> detect -> plot, SimulatedSpectrum fields, reproducible noise, meta provenance |
02_scenarios |
The five scenario presets, overlaying them with .plot(ax=ax), why hazy can out-score clear |
03_transits_and_binning |
n_transits and R, the sqrt(N) scaling, .plot_noise(), the two resolution limits |
04_light_curves |
.light_curves().plot(), white-light and banded transits, limb darkening, the trapezoid fallback |
05_tables_and_targets |
Table layout and the sha256 manifest, the frozen NEA registry, caveat flags in the data |
06_parametric_model |
The zeta grid, the parametric model, validation against POSEIDON |
07_custom_planet |
A planet not in the registry from a dozen numbers; the forms/report web path |
08_atmosphere_knobs |
Sweeps of mu, metallicity, deck, haze and T_eq; transits needed |
09_stars_and_noise |
PHOENIX SEDs, Vega->AB, error bar vs magnitude/band, saturation floor |
Running the Flask applet
pip install -e ".[web]"
./run.sh
Then open your browser and go to:
http://127.0.0.1:5000
Pick a showcase planet and scenario, or switch Mode to "Custom planet" to prefill from a preset and edit the star, planet and atmosphere. This is a local exploration tool; the public page lives on the Lazuli simulators site.
Notes
- Tables (~34 MB) live in
src/lazuli_exo/data/tables/and ship with the package;LAZULI_EXO_TABLESoverrides the location. The POSEIDON validation tables stay inprecompute/tables/and are not runtime data. - Regenerating tables needs POSEIDON (conda env
poseidon) and slicersim; see docs/MODEL.md. - Tests:
pytest(the slicersim-dependent tests skip when it is absent).
Viewing documentation locally
pip install -e ".[docs]" # plus a pandoc binary, for nbsphinx
./docs/rundocs.sh # live-reloading server (sphinx-autobuild) or a one-shot build
Then open docs/sphinx/_build/html/index.html. The tutorial notebooks are
rendered into the docs with their saved outputs; notebooks/build_0N.py
regenerates notebook N.
Metadata
Release files for lazuli-exo 0.1.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| lazuli_exo-0.1.2.tar.gz | 31.1 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| lazuli_exo-0.1.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 62.2 MB
Release files / lazuli_exo-0.1.2.tar.gz
| Download URL | lazuli_exo-0.1.2.tar.gz |
|---|---|
| Size | 31.1 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f5aa7480c1741ae17027d1f87ea08ba0736ef561d1101a113e12c9149df8042c
|
|
BLAKE2b-256 checksum How to use checksums |
e4fb2a44cf5dfd0620a3af8971eae869f558a73b0cafc5ca544fa62038499120
|
| 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 12, 2026.
Transparency logRelease files / lazuli_exo-0.1.2-py3-none-any.whl
| Download URL | lazuli_exo-0.1.2-py3-none-any.whl |
|---|---|
| Size | 31.1 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
179c88660dd9805541f43d71933773accc25bd3a9b064a077ebe7fcabbfdd0df
|
|
BLAKE2b-256 checksum How to use checksums |
af7240fe87570c7bcea4f4504421feb6b2742df4e99ed8e87126ba783c79dfc3
|
| 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 12, 2026.
Transparency log