Navigation guide for the nullsim package: load-bearing modules, data flow,
and the tests that pin each area. Hand-maintained — if this map contradicts
the source, trust the source and flag the drift. For parameter-level stage
detail, use nullsim list-stages and the stage module docstrings.
nullsim is a Python package and CLI for simulating photonic nulling
instruments from a single TOML config, organized around an ordered pipeline:
TOML config
-> config load / preset merge / sweep expansion
-> ordered stage validation and execution
-> typed SimulationState budgets
-> registered plots and tables
-> results/<run>/<YYYYMMDD-HHMMSS>/
CLI surface (nullsim ... or python -m nullsim):
run <config>: execute, expand sweeps, write outputs.--dry-runprints the expanded plan, output names, and dependency graph without executing.validate <config>: schema, output registry, sweep, and stage dependency check, no execution.inspect <config>: non-executing run plan with hashes, output dir, stage consumes/produces, and physics/trust notes.list-stages [--family ...]/list-outputs [--kind ...]: registry introspection.
nullsim/
__init__.py import side effects: stage and output autoload
__main__.py python -m nullsim entry point
cli.py argparse CLI, sweep execution, manifests, outputs
config/ TOML schema, loader, hashing, sweep expansion
pipeline/ stage protocol, registry, state, runner, cache
stages/ built-in stage implementations
physics/ pure math / scientific primitives
outputs/ registered plot and table writers
catalogs/ versioned external catalog loaders
data/ packaged reference data and catalog snapshots
examples/ canonical user-facing TOML configs
tests/ unit, stage, output, and example tests
scripts/ maintenance, reference-generation, benchmarks
changelog.d/ fragment changelog entries
README.md user quick start and CLI overview
nullsim.md design document and roadmap
CONTRIBUTING.md development workflow
pyproject.toml package metadata, deps, pytest/ruff config
-
nullsim/__init__.py- Sets
__version__; importsnullsim.stages._autoloadso stage registration decorators run. - Plot/table registration is deferred:
nullsim.outputs._autoload_outputs()runs on output-writing/listing paths and when validation/inspection needs to check requested output names.
- Sets
-
nullsim/cli.py- Builds the subcommands; loads configs via
config.load_config; expands sweeps viaconfig.expand_sweeps. - Validates requested plot/table names before expensive stage execution.
- Runs each cell through
pipeline.run; parallel sweep cells use a spawn-basedProcessPoolExecutor. - Writes
config.toml,config.resolved.toml,manifest.json,sweep_results.*, registered outputs, and the opt-in paper bundle when[output].paper = true.
- Builds the subcommands; loads configs via
-
nullsim/config/loader.py- Parses TOML with
tomllib; merges presets fromconfig/presets/; materializes top-level fields into stage params viastage_materialize.py; validates with Pydantic models fromschema.py. - Computes
config_hash, initialrun_hash, and a timestamp.
- Parses TOML with
-
nullsim/config/sweeps.py- Expands
[[sweep]]axes into independentSweepCellobjects; re-materializes stage params after each sweep coordinate is applied.
- Expands
-
nullsim/pipeline/runner.py- Builds stage instances in the declared TOML order (no topological
reordering); validates that every required
consumeskey was produced by an earlier stage; wraps each stage in content-addressed cache lookup/store. - Returns
RunResult(state, diagnostics, timings, cache_hit_count, config_hash).
- Builds stage instances in the declared TOML order (no topological
reordering); validates that every required
-
nullsim/outputs/- Receives a
RunCollectionofRunRecords; writes selected plots/tables by registry lookup.
- Receives a
nullsim/config/schema.py— Pydantic models for the TOML surface. Top-level sections:run,grid,site,telescope,scene,observation,instrument,pipeline,sweep,output,cache,catalog.[instrument]is the shared port-topology section (n_modes = n_bright_ports + n_dark_ports + n_spectro_portswhen all set;n_input_modescounts physical guided modes before any duplicated-input fanout).StageSpecallows extra keys (each stage owns its own params model);PipelineConfigrejects duplicate stage IDs.nullsim/config/loader.py— publicload_config(path)entry point.nullsim/config/stage_materialize.py— bridges user-facing sections into stage params:[scene.*], merged[site]/[telescope]presets, and[instrument]flow into the stages that need them, preserving explicit stage-level overrides.nullsim/config/sweeps.py— sweep params targetscene.*,observation.*, orpipeline.stages.<id>.<field>; supportsvalues,range,linspace,logspace, grid axes, and zip axes.nullsim/config/hashes.py— deterministic TOML serialization;config_hashfrom resolved config text,run_hashadds package/runtime identity, dependency versions, and external dependency hashes.nullsim/config/catalog.py— validates[catalog.exoplanets]and resolves relative snapshot paths.nullsim/config/factories.py— band-sweep dict helpers for cross-band studies. (Site sky brightness tables live instages/background/sky_surface_brightness.py, not here.)
Tests: tests/config/test_loader.py, test_sweeps.py, test_hashes.py.
nullsim/pipeline/stage.py—Stageprotocol,BaseStage,ExternalDependency,consumed_state_keys(). Stage classes declaretype_name,family,consumes,produces, optionalcache_key_extras; an innerParamsPydantic model is validated once at construction.nullsim/pipeline/state.py— immutableSimulationStatewith eight budgets:scene,geometry,field,opd_budget,throughput,wavefront,photon_rates,results. Each budget supports namedcomponentsand deterministic content hashing.nullsim/pipeline/registry.py— shared singleton registry;@register_stage,@register_plot,@register_tableare the extension mechanism.nullsim/pipeline/runner.py— deterministic per-instance RNG contexts, dotted-pathconsumes/producesvalidation, declared-order execution, timings/diagnostics/cache-hit tracking.nullsim/pipeline/cache.py— content-addressed stage cache. Keys fold in validated stage params, consumed state digest, a per-stage-family source digest (shared core + the family's import closure; output and CLI code excluded), class/instance identity, RNG identity, package version, external dependencies, and cache version. Entries are pickles under.nullsim_cache.nullsim/pipeline/rng.py— RNG derived from root seed, the stage's own spec digest, sweep coordinate, and stage instance ID. Stage-local by design: config edits that do not touch a stage's spec cannot change its draws.nullsim/pipeline/manifest.py— writesmanifest.json(hashes, dependency versions, host info, seed, timings, cache stats, external dependencies).
Tests: tests/pipeline/test_cache.py, test_registry.py, test_rng.py,
test_runner_validation.py, test_state.py.
Built-ins are imported by nullsim/stages/_autoload.py. Stage type names are
the strings used in TOML under [[pipeline.stages]].
| Type | File | Consumes | Produces | Purpose |
|---|---|---|---|---|
scene.point_source |
stages/scene/point_source.py |
none | scene |
Source properties and pointing. |
telescope.fixed_aperture |
stages/telescope/fixed_aperture.py |
scene |
geometry, throughput |
Aperture area, ENU positions, baselines, telescope throughput. |
telescope.pupil_modes |
stages/telescope/pupil_modes.py |
geometry |
geometry |
Photonic input mode placement on a single circular pupil (single-primary geometry bridge). Raises when upstream n_apertures > 1; publishes per-mode mode_areas_m2, mode_diameters_m, and mode_origin_positions_m. |
atmosphere.kolmogorov |
stages/atmosphere/kolmogorov_screens.py |
geometry |
opd_budget, wavefront, throughput |
Seeing, atmospheric transmission, OPD/wavefront budgets. |
ao.pyramid_wfs |
stages/ao/pyramid_wfs.py |
geometry, opd_budget, wavefront, scene |
opd_budget, wavefront, geometry |
KILO-style pyramid WFS residuals and sub-aperture geometry. |
wavefront.space_residual |
stages/wavefront/space_residual.py |
none | wavefront, opd_budget |
Scalar space-telescope WFE/Strehl and jitter residual budget. |
injection.ideal |
stages/injection/ideal.py |
scene, geometry |
field, throughput, wavefront |
Seed ideal field amplitudes, optionally using upstream Strehl. |
injection.mode_splitter |
stages/injection/mode_splitter.py |
field, geometry |
field, geometry |
Guided-mode fanout for duplicated-input Clements mesh studies. |
injection.single_mode_fiber |
stages/injection/single_mode_fiber.py |
field, throughput, wavefront, geometry |
field, throughput |
Ruilier-Cassaing SMF coupling. |
delay_lines.geometric |
stages/delay_lines/geometric.py |
geometry, throughput, scene, field |
throughput, field |
Delay-line path equalization loss; opt-in chromatic phase diagnostics. |
transport.smf28 |
stages/transport/smf28.py |
field, throughput, opd_budget |
field, throughput, opd_budget |
SMF-28 loss, phase, and dispersion; publishes the per-mode GVD phase as the field component static_field_factor. |
transport.pm980 |
stages/transport/smf28.py |
field, throughput, opd_budget |
field, throughput, opd_budget |
PM980-XP PM fiber (SMF28Transport subclass). |
transport.pm780 |
stages/transport/smf28.py |
field, throughput, opd_budget |
field, throughput, opd_budget |
PM780-HP PM fiber for ~800 nm bands (SMF28Transport subclass). |
transport.photometric_pickoff |
stages/transport/photometric_pickoff.py |
field, throughput |
field, throughput |
Fixed photometric tap before the science mesh for amplitude sensing. |
background.nayra_sky |
stages/background/nayra_sky.py |
geometry, throughput, field |
results |
Per-port sky background rates for single-pupil ELT (NAYRA) pipelines. |
background.sky_surface_brightness |
stages/background/sky_surface_brightness.py |
geometry, throughput, field |
results |
Generic site sky surface brightness background. |
background.space_zodi |
stages/background/space_zodi.py |
geometry, throughput, field, scene, results |
results |
Space local-zodi and exozodi background rates per dark/spectro port. |
polarization.hwp |
stages/polarization/optics.py |
field, throughput |
field, throughput |
Half-wave plate Jones rotation per port. |
polarization.pbs_split |
stages/polarization/optics.py |
field, throughput |
field, throughput |
Polarizing beam splitter into two orthogonal polarization arms. |
polarization.pm_fiber |
stages/polarization/optics.py |
field, throughput |
field, throughput, results |
PM fiber Jones propagation with birefringence and leakage; publishes the per-mode, per-arm J00 as static_field_factor. |
instrument.nayra_six_chip |
stages/instrument/nayra_six_chip.py |
none | results |
NAYRA Y/J/H × dual-pol six-chip channel manifest. |
chip.identity |
stages/chip/identity.py |
field, throughput |
field, throughput |
Pass-through chip for smoke tests. |
chip.kernel_mzi_mesh |
stages/chip/kernel_mzi_mesh.py |
field, throughput (+ results when optimization_source is set) |
field, throughput, results |
Kernel-nulling Clements MZI mesh; supports per-pol-arm deployment. |
chip.quadrature_reference_mesh |
stages/chip/quadrature_reference_mesh.py |
field, throughput |
field, throughput, results |
Ideal 2N-output quadrature-reference mesh (N tracker + N science rows). |
chip_optimization.scipy |
stages/chip_optimization/scipy.py |
field, throughput, geometry (+ scene when disk samples enabled) |
results |
Scipy broadband chip optimization with optional finite-disk constraints. |
chip_optimization.torch |
stages/chip_optimization/torch.py |
field, throughput, geometry (+ scene when disk samples enabled) |
results |
Torch/autograd chip optimization: batched wavelength solves, disk rows, tracker/amplitude observability regularization. |
chip_optimization.ideal_kernel |
stages/chip_optimization/ideal_kernel.py |
field, throughput, geometry |
results |
Guyon et al. (2013) ideal achromatic kernel-null projector; warm-start source for Clements refinements. |
detector.ideal_counter |
stages/detector/ideal_counter.py |
field, throughput, scene, geometry |
photon_rates, results |
Simple ideal detector for the vertical slice. |
detector.mkid |
stages/detector/mkid.py |
field, throughput, scene, geometry |
photon_rates, results, throughput |
MKID QE, saturation, count rates, opt-in energy-resolution redistribution. |
detector.snspd |
stages/detector/snspd.py |
field, throughput, scene, geometry |
photon_rates, results, throughput |
SNSPD QE, saturation, dark counts. |
fringe_tracking.closed_loop |
stages/fringe_tracking/closed_loop.py |
photon_rates, opd_budget, wavefront, geometry |
opd_budget, results |
Closed-loop piston residuals from the full KILO servo model (the legacy servo-lag model is removed; use_full_piston_servo_residual = false raises), classical or photon-Kalman control; optional aperture-filtered von Karman piston and vibration disturbances. |
calibration.floor_lookup |
stages/calibration/floor_lookup.py |
results, scene |
results |
Calibration systematic floor lookup/subtraction. |
sensitivity.detection_curve |
stages/sensitivity/detection_curve.py |
results, geometry, wavefront, scene, field, throughput |
results |
Planet throughput curve, stellar-disk floor, realized/effective null, leakage rates. |
performance.standard |
stages/performance/standard.py |
results, scene |
results |
SNR, integration time, and contrast curve. |
performance.pdi |
stages/performance/pdi.py |
results |
results |
Polarimetric differential imaging SNR. |
performance.hdc |
stages/performance/hdc.py |
results, scene |
results |
Detection-mode high dispersion coronagraphy: inverts the matched filter at each sensitivity.detection_curve separation anchor for a CCF contrast curve, alongside the broadband curve and their ratio. Needs an upstream performance.standard. |
characterization.spectro |
stages/characterization/spectro.py |
results, geometry, scene, throughput, field |
results |
Known-position spectro-port retuning and SNR (AWG or MKID-native accounting). |
characterization.detection_curve |
stages/characterization/detection_curve.py |
results, geometry, scene, throughput, field |
results |
Characterization-mode contrast curve anchors. Its Params model is independent of characterization.spectro's: the two overlap heavily in field names but are separate models with their own defaults, and it reuses that stage's helper functions rather than its schema. |
characterization.hdc_ccf |
stages/characterization/hdc_ccf.py |
results, scene |
results |
High dispersion coronagraphy matched-filter cross-correlation on the spectro port (Wang et al. 2017); needs an upstream AWG-mode characterization.spectro. Rejects MKID-native spectro payloads (phase 1). |
diagnostics.fringe_observability |
stages/diagnostics/fringe_observability.py |
field, photon_rates, results, geometry |
results |
Fringe-tracker piston/amplitude observability audit (SVD/CRLB, acquisition, cophasing handoff). |
Contract notes (subtleties that are easy to get wrong):
sensitivity.detection_curveandperformance.standardreadopd_budgetopportunistically as acache_key_extrasentry, not a validated consume.sensitivity.detection_curve's disk floor is the excess<T(r) - T(0)>over 16 Gauss-Legendre nodes inside the stellar radius, so the separation grid no longer has to cover the stellar radius. Its rebuilt fields carry the product of upstreamstatic_field_factorcomponents.performance.standardrecomputes leakage from the sensitivity-published effective null (the published leakage-rate keys are debug-report inputs) and appliesnsc_systematic_reductiononly to the null-instability systematic term, not mean-leakage photon noise.characterization.spectroandcharacterization.detection_curve(whoseParamsmodel is its own, not shared) both honorNULLSIM_TORCH_COMPILEforcompile_objectiveand force serial restarts when compilation is on.characterization.hdc_ccfruns no optimizer and has neither knob.
Stage-family tests live under tests/stages/, with additional integration
coverage in tests/examples/.
nullsim/physics/ should remain independent of TOML, SimulationState, and
stage execution except where explicitly documented. Derivation notes and
pinning rationale live in tests/physics/notes/*.md.
| File | Responsibility |
|---|---|
kolmogorov.py |
Kolmogorov turbulence constants, r0 scaling, phase variance, effective wind speed. |
ruilier_cassaing.py |
Single-mode fiber coupling; exact off-axis overlap (angular_coupling_amplitude, signed, and angular_coupling_factor, by Gauss-Legendre quadrature); DIFFUSE_SINGLE_MODE_ACCEPTANCE (1 - exp(-2 rho^2) = 0.919) for diffuse backgrounds. |
clements.py |
Clements unitary decomposition and reconstruction. |
jones.py |
Jones matrices, waveplates, Stokes conversions, PM fiber birefringence/leakage, PBS arms, DualPolField. |
photometry.py |
Vega zero points and photon-rate conversions. |
pupil_geometry.py |
Keck/KILO/E-ELT sub-aperture packing, pupil constraints, seed strategies. |
fresnel.py |
Angular-spectrum scalar propagation. |
uv_coverage.py |
Baseline enumeration, ENU-to-UV projection, off-zenith sky-plane projection. |
stellar_disk.py |
Uniform-disk null floor as the disk excess over the on-axis throughput, on a Gauss-Legendre radial quadrature. |
planet_throughput.py |
Off-axis input fields, chip throughput vs separation, dark-port transmission maps. |
realized_null.py |
Monte Carlo AO-realized null-depth statistics, including IID + common-mode piston covariance; photon-weighted band leakage mean and within-azimuth RMS (band_leakage_statistics). |
quadrature_fringe_tracker.py |
Quadrature-reference mesh primitives and analytic observability/CRLB estimates. |
scintillation.py |
Scintillation and amplitude-servo residual model. |
fringe_tracking.py |
Piston-servo bandwidth, disturbance terms (aperture-filtered von Karman differential piston, Conan et al. 1995), photon-Kalman residual budget, AR(1) temporal-Kalman covariance surrogate. |
systematic_bandwidth.py |
Effective bandwidth for null-instability noise. |
spectroscopy.py |
Spectro-port SNR, max resolving power, AWG spectrograph model. |
mkid.py |
MKID resolving power and energy-response matrix primitives. |
spectra.py |
Template spectrum loading, constant-R grids, Doppler shift, resolving-power convolution, flux-conserving resampling. |
hdc.py |
High dispersion coronagraphy matched-filter CCF budget: channelization, template weights, self-subtraction (global and per island), the RV scan (ccf_rv_scan) with its grid-wide localization verdict under a modeled residual leak, and the inverse solve ccf_contrast_at_target_snr (faintest contrast reaching a target SNR). |
pdi.py |
Polarimetric differential imaging SNR primitives. |
characterization.py |
Known-position chip optimization for spectro mode; piston-averaged port power (piston_averaged_port_power, mode_piston_opd_covariance_m2). |
characterization_torch.py |
Torch autograd-Jacobian helper for characterization optimization. |
imaging.py |
Aperture-synthesis image reconstruction: beam patterns, photon-count observations, SNR maps, NNLS. |
fringe_observability.py |
Piston/amplitude observability primitives: response matrices, SVD/CRLB, Monte Carlo estimation, Gauss-Newton capture range, acquisition scans, cophasing handoff. |
reflected_light.py |
Reflected-light planet/star contrast (albedo × Lambertian phase × (R_p/a)²). |
ideal_coronagraph.py |
Guyon et al. (2006) ideal coronagraph bound and photon-limited 5σ contrast curve. |
Tests: tests/physics/test_*.py.
The output layer is registry-driven: configs name plots/tables in [output],
and the CLI resolves names against the shared registry.
nullsim/outputs/__init__.py—RunRecord(one sweep cell or single run) andRunCollection(all records plus output directory).nullsim/outputs/sweep_results.py— long-formatsweep_results.parquetwith scalar results, spectra, contrast curves, and sensitivity/ characterization scalars.nullsim/outputs/paper.py— opt-in[output].paper = truebundle writer. Createspaper/besideplots/andtables/; reuses registered writers and adds paper-only schematic PDFs and LaTeX result tables. The full emitted set is recorded inpaper_manifest.json.
Registered plots:
| Name | File | Purpose |
|---|---|---|
contrast_curve |
outputs/plots/__init__.py |
Basic contrast curve plot. |
detection_space |
outputs/plots/detection_space.py |
Exoplanet population overlay with contrast curves; overlays performance.hdc CCF curves as dash-dot lines in one fixed HDC colour, with each record's J/time/R identity carried by the label rather than the colour. |
subaperture_layout |
outputs/plots/subaperture_layout.py |
Pupil/sub-aperture layout panels. |
uv_coverage_grid |
outputs/plots/uv_coverage_grid.py |
KILO-style UV coverage grid. |
chip_diagnostics |
outputs/plots/chip_diagnostics.py |
Detection-chip power, matrix, DAC, and spectrum diagnostics. |
chip_diagnostics_characterization |
outputs/plots/chip_diagnostics.py |
Characterization retune diagnostics. |
cophasing_unitary |
outputs/plots/cophasing_unitary.py |
Cophasing-mode ABCD, pair graph, OPD-incidence, and handoff diagnostics. |
loss_budget |
outputs/plots/loss_budget.py |
Throughput/loss breakdown. |
contrast_curves_multiband |
outputs/plots/contrast_curves_multiband.py |
Multi-band/multi-time contrast curves. |
calibration_time |
outputs/plots/calibration_time.py |
Optimizer convergence / calibration-time plot. |
debug_report |
outputs/plots/debug_report.py |
Multi-page pipeline audit report. |
imaging_reconstruction |
outputs/plots/imaging_reconstruction.py |
Truth/SNR/beam imaging artifacts for space nuller reconstruction. |
null_transmission_map |
outputs/plots/null_transmission_map.py |
Summed dark-port transmission map over sky coordinates. |
fringe_observability |
outputs/plots/fringe_observability.py |
Fringe-tracker observability diagnostic panels. |
hdc_ccf |
outputs/plots/hdc_ccf.py |
HDC cross-correlation diagnostics: channelized spectra, template weights, SNR vs resolving power, SNR vs trial radial velocity. |
Registered tables:
| Name | File | Purpose |
|---|---|---|
performance_summary |
outputs/tables/__init__.py |
One row per run with headline scalar metrics. |
characterization_summary |
outputs/tables/__init__.py |
One row per characterization.spectro result component. |
hdc_detection |
outputs/tables/__init__.py |
Long-form detection-mode HDC curve: one row per performance.hdc separation anchor (both contrasts, gain, target SNR, time, R). |
Tests: tests/outputs/test_outputs.py, test_end_to_end.py, test_paper.py,
tests/outputs/plots/test_*.py.
nullsim/catalogs/exoplanets.py— validates NASA Exoplanet Archive-style CSV snapshots, computes/verifies snapshot hashes, classifies planets with versioned rules, derives separation in mas when needed.nullsim/data/exoplanet_catalogs/nasa_exoplanet_archive_paperv3.csv— packaged snapshot used by detection-space outputs.nullsim/data/kilo_reference/*.json— KILO reference data for plot overlays and calibration floors.nullsim/data/nayra_reference/*.json— NAYRA E-ELT reference data (calibration_time_J_M18.json,calibration_time_J_M36.json).nullsim/data/spectra/*.csv— HDC template snapshots, 0.5–1.8 um, all on one constant-R grid. Two resolving powers, kept distinct in the headers and never conflated:sampling_resolving_power= 90000 (the grid, three samples per FWHM) andlsf_resolving_power= 30000 (the content). Nine files: reflected planet albedos (earth_albedo,venus_albedo) from the NASA Planetary Spectrum Generator, PHOENIX stellar photospheres (stellar_g2v,stellar_k2v,stellar_m2v), ESO SkyCalc total transmission (telluric_maunakea,telluric_armazones), and SkyCalc sky radiance (sky_radiance_maunakea,sky_radiance_armazones, airglow plus atmospheric thermal emission; packaged and loadable, consumed by no stage yet).scripts/generate_hdc_spectra.pyregenerates all nine, each from its own upstream: the albedos from the PSG web API driven by the frozen configs inscripts/psg_config/*.cfg(which pin the entireGENERATORblock, because PSG fills in anything omitted from its live defaults), the photospheres from the PHOENIX HiRes FITS archive, transmission and radiance from the SkyCalc REST service viaskycalc_cli.characterization.hdc_ccfdeclares the ones it reads asExternalDependencyentries, so their bytes enter the run hash. Caveats that belong in the header and do:earth_albedois a clear-column Lambert disk with no clouds, not a representative Earth;venus_albedois reflected-only, its 1.7 um thermal emission measured and divided out; Armazones is SkyCalc's native 3060 m site model and Maunakea is a proxy.
Tests: tests/catalogs/test_exoplanets.py with
tests/catalogs/fixtures/sample_catalog.csv.
Reference/instrument configs in the root of examples/:
examples/kilo_keck.toml— KILO Keck-only paper-baseline pipeline with[output].paper = true.examples/kilo_keck_6.toml— default KILO Keck config: reduced 6-subaperture-per-pupil quadrature mesh extendingkilo_keck.toml; source of the phasing-paper bundle.examples/nayra_eelt.toml— single-pupil E-ELT NAYRA at Cerro Armazones; N=18 sub-aperture J-primary dual-pol baseline.examples/hwo_space.toml— HWO-class space nuller, no atmosphere/AO stages.
Regression and smoke-test configs under examples/test/:
vertical_slice.toml— fast architecture smoke test with a J-mag sweep.kilo_maunakea.toml— compact four-telescope Maunakea demonstrator.nayra_eelt_baseline.toml— fixed-point NAYRA regression reference (no sweeps).detection_space_example.toml— minimal detection-space output exercise.mkid_native_spectro.toml— MKID-native spectroscopy smoke config. This and the three HDC configs below are one 10 m telescope with four pupil modes (telescope.pupil_modes), no delay lines.hdc_ccf_smoke.toml— HDC cross-correlation smoke config: four modes, M2V host, AWG spectro port at R = 5000,characterization.hdc_ccf+hdc_ccfplot. Runs in seconds. Photon limited, so the CCF loses to the broadband band SNR — correctly, there being no systematic to reject.hdc_ccf_systematics.toml— the same chain with a quasi-static leak instability (residual_speckle_stability_factor = 4e-3,with_fringe_tracking = false), the regime where characterization-mode HDC wins. Companion tohdc_ccf_smoke.toml; the config header carries the budget and the caveats on the asserted correlation time.hdc_detection_smoke.toml— detection-mode HDC: the same systematics knobs (eps = 1e-3) onperformance.standard,performance.hdcover seven separation anchors, and thedetection_spaceoverlay.ccf_contrast_gain5.007 with the knobs, 0.163 without.kilo_vs_hwo_study.toml— cross-config comparison study.kilo_keck_quadrature_mesh.toml— KILO Keck with a 48-mode duplicated-input Clements mesh and cophasing diagnostics.
Example tests live under tests/examples/ and exercise config loading,
structural invariants, and runnable end-to-end slices.
- Create a module under the appropriate
nullsim/stages/<family>/directory. - Subclass
BaseStage. - Add class variables:
type_name = "<family>.<name>"family = "<family>"consumes = frozenset({...})produces = frozenset({...})
- Add an inner
Params(BaseModel)if the stage has parameters. - Implement
apply(self, state: SimulationState) -> SimulationState. - Return diagnostics from
diagnostics()if the stage has audit values. - Return
ExternalDependencyobjects fromexternal_dependencies()for data files or snapshots that affect results. - Decorate the class with
@register_stage. - Add the module path to
_STAGE_MODULESinnullsim/stages/_autoload.py. - Add focused tests under
tests/stages/<family>/and, if relevant, an example-level smoke test.
Important contracts:
- Do not mutate existing state budgets in place; return replaced budgets.
- Preserve existing components when adding a component to a budget.
- Declare every required state read in
consumes. - Use
cache_key_extrasonly for opportunistic reads that should affect cache identity but should not be enforced by dependency validation.
- Add a function that accepts
(collection, output_dir, formats). - Decorate it with
@register_plotor@register_table. - Import the module from
outputs/plots/__init__.pyoroutputs/tables/__init__.pyso registration runs. - Add the output name to an example config if it is user-facing.
- Add a focused test under
tests/outputs/.
tests/config/ config loading, hashing, sweeps
tests/pipeline/ registry, runner validation, cache, RNG, state
tests/physics/ pure mathematical primitives
tests/stages/ stage behavior and stage integration
tests/outputs/ output writers and plot modules
tests/catalogs/ exoplanet catalog loader
tests/examples/ end-to-end config checks
Default test command:
uv run pytest -n autoTarget subsets by appending a path: tests/pipeline, tests/stages,
tests/examples/test_vertical_slice.py.
- To understand a config field: start in
config/schema.py, then inspect the stageParamsmodel that consumes it. - To understand why a pipeline fails validation: inspect the failing stage's
consumes, the earlier stages'produces, andpipeline/runner.py. - To understand stale or surprising results: inspect
pipeline/cache.py,external_dependencies(), and the run'smanifest.json. - To understand a plotted quantity: inspect the output module first, then trace
the
state.results.componentsor budget field it reads. - To add a physics formula: put it in
physics/, test it intests/physics/, then wrap it with a stage only after the primitive is pinned. - To debug an end-to-end config: start with the relevant
examples/*.toml, then inspect the corresponding structure or smoke test undertests/examples/.