Design Principles¶
These are the criteria for deciding SOPRAN's public API and internal implementation. Check new features and data sources against them. The rules here are the ones the current implementation follows; decided plans and unimplemented ideas are kept in the last two sections.
Intended experience¶
- Users learn the structure and meaning of the data while using it, even without prior expertise.
spn.kaguya.esa1.energy_fluxreads as mission, instrument, and quantity, and types, completion,info(), units, frames, and time, energy, and viewing bins follow the same structure. File formats, distribution servers, and Rust implementation details never leak into quantity names. - One path leads from the standard entry point to a figure. A short call such as
plot(time)goes through retrieval, calibration, storage, and reloading to a figure whose axes and colorbar show quantities and units. Automation never hides the applied corrections, input files, settings, or processing history, and missing data or model misfits are never replaced with other values. - Source files and native bins stay reachable. Results are not locked into processed values: source files (
files) and records before calibration, rebinning, or time integration (such as PACEpace) remain in the loaded result.
API contract¶
- Getting attributes is discovery only. Mission, instrument, variable, and Moon map attributes return lightweight objects and never download, decode, scan, or compute.
- Work starts at a few well-defined points.
load()reads data,compute()derives products from inputs, andplot()draws figures. For pipelines these arerun(),collect(),scan(), andstream(). An endpoint'splot(time)is a convenience that loads data; outputs meant to keep their conditions usequicklook(). - Every level offers the same discovery tools:
info()(short description),guide()(detailed description),schema()(machine-readable definition),plan()(plan before running), andexample()(runnable example). - Meaningful names are canonical. Names such as
energy_fluxandmagnetic_fieldare the primary form in docs, plot labels, and schemas; short names such asefluxandbare aliases listed byinfo(). Aliases not in the specification are not added. - Results carry types, units, frames, times, and bins. Type boundaries with external libraries are explicit, and
Anyis not chained. - Configuration resolves in one place. Process defaults use
spn.config.use(), temporary changesspn.config.using(), and everything else resolves through existing settings, Projects, and Views. Onlyresolve_store_roots()decides the Store root and onlyresolve_download_mode()the download policy, so every entry point gets the same result. Creating a Store never changes global settings, and no per-instrument global state is introduced. - Data sources are added by registration. Missions and bodies register themselves with
register_source()in their own packages, andcorenever imports missions (Adding A Data Source).
Schemas live in code¶
Variable names, dimensions, units, data types, frames, and aliases are written in code as InstrumentSchema and VariableSchema.
info(), validation (validate_schema), the Store's schema.json, plot axis labels and default logarithmic scales (log_scale, log_dims),
and the Schemas page all come from them, which keeps hand-written tables and per-name special cases from drifting.
Storage and reproducibility¶
- Processed data is saved and reloaded through the existing product and cache mechanisms. Cache keys come from settings and input content, so results under different settings are never reused as the same entry.
- File lookup goes through the Store, layouts, and providers; machine-specific absolute paths are never embedded in the package.
- The Store stays copyable with
rsync. Manifests and catalogs contain only root-relative paths, and shard sidecars are the source of truth (Moving A Store). - Missing values, zeros, not-retrieved data, calibration state, and quality flags are kept distinct.
- Schemas of saved datasets change even more carefully than the public API; incompatible changes bump the schema version.
Dependencies¶
import sopran needs only numpy and pandas. Mission, map, and plotting dependencies are split into extras by use
and imported lazily on the path that needs them. Each extra lists only packages SOPRAN actually imports,
and tests/test_project_metadata.py checks that match. pyproject.toml is the source of truth for dependencies and build settings.
Reuse existing libraries¶
Time-scale conversion, ephemerides and attitude, standard coordinate transforms, generic CDF and PDS reading, and unit conversion are not reimplemented.
| Area | Library |
|---|---|
| Body and spacecraft geometry, coordinate transforms | spiceypy |
| Geospace frames (GSE, GSM, SM) | spacepy (planned) |
| CDF | cdflib |
| Maps and DEMs | rasterio |
| Tables and Parquet | polars, pyarrow |
| Multidimensional arrays | xarray |
| Figures | matplotlib, panel |
Own implementations are allowed for mission-specific binary decoding, fixes for broken provider metadata, instrument calibrations missing from existing libraries, and ports to Rust for performance. Even then, add tests against an external library or the official specification. When matching SPEDAS behavior, record the source, units, and correction conditions (License And Porting).
Rust extension boundary¶
Heavy deterministic processing (PACE decoding, pitch-angle binning, ER fits, field-line tracing) is delegated to the bundled Rust extension sopran._native.
- Data crosses between Python and Rust in coarse units (file groups, arrays, problems, batches), not per record or per bin.
- Results come back as regular Python types, and everyday APIs never expose the FFI.
- The backend name and settings are recorded for reproducibility and debugging.
- Before porting, small golden outputs are compared with a Python reference implementation or the official specification, and nothing silently falls back to Python.
Isolating experiments¶
Unsettled models and analysis steps live in sopran.experimental, and the regular API never depends on them. Experiments still get types, units, and tests,
and their Store keys stay separate from regular products. No compatibility aliases or shims are added for unpublished APIs.
Individual research runs, comparison figures, and interpretations go to working/, not the public documentation (Experimental).
Package layout¶
| Location | Role |
|---|---|
src/sopran/core/ |
Store, configuration, Project/Case/View, data types, Pipeline, plotting, time alignment |
src/sopran/missions/ |
KAGUYA, ARTEMIS, Lunar Prospector, and OMNI readers, instruments, and product APIs |
src/sopran/bodies/moon/ |
Moon products such as DEM, shadow, and SVM |
src/sopran/frames/, src/sopran/maps/ |
Coordinate and time conversion, shared map types |
src/sopran/experimental/ |
Experiments imported explicitly |
crates/sopran-native/ |
Rust extension sopran._native |
tests/ |
Regression tests built mostly on synthetic data and fixtures |
scripts/hpc/ |
Supercomputer environment setup and job templates |
docs/ |
Public documentation (Writing Documentation) |
Exceptions and error messages¶
All exceptions derive from SopranError and also from a standard exception.
| Exception | Also derives from | When |
|---|---|---|
ConfigError |
ValueError |
Invalid configuration value |
DatasetNotFoundError |
LookupError |
Dataset or source file not found |
DownloadError |
OSError |
Retrieval failed |
DecodeError |
ValueError |
File could not be decoded |
SchemaError |
ValueError |
Dimensions, data type, units, or frame do not match the schema |
FrameTransformError |
ValueError |
Missing inputs for a coordinate transform, or unsupported transform |
PipelineError |
RuntimeError |
No pipeline backend, or unsupported operation |
BackendError |
RuntimeError |
Backend unavailable |
Error messages include, where possible, the failing endpoint or dataset ID, the available candidates, the next call to try (info() or plan()),
and which stage failed: retrieval, decoding, schema, or coordinate transform.
Planned work¶
The following items are decided. Each gets its design, following these principles, when work starts.
| Item | Contents | Main work |
|---|---|---|
| Chandrayaan-1 SARA (SWIM, CENA) | PDS3 at ESA PSA. 2008–2009 overlaps KAGUYA, allowing simultaneous comparisons | Only raw counts (EDR) are published, so calibration has to be prepared |
| Wind SWE and MFI | CDF files at CDAWeb; the upstream solar-wind observations behind OMNI | Reuse the ARTEMIS retrieval (newest version from the SPDF listing) |
| LRO Diviner GCP | Surface (bolometric) brightness temperature by local time from PDS, for surface charging and exosphere studies | Return it as a Moon map on a grid with local time |
| Lunar Prospector ER extensions | Pitch angles and the high-rate ER (ER_HI_TS) |
Add pitch angles after checking the look-direction sign conventions in the literature |
| ARTEMIS extensions | Quality flags and selenocentric position | Read *_data_quality for the quality flags |
Ideas¶
The following are ideas, not promised.
- Interactive plotting (HoloViews, hvPlot, Datashader) and long-period quicklooks.
spacepyandastropycoordinate transform backends.- Moon map reprojection, shadows with penumbra, and faster
terrain_ray. - A mission-independent generic pipeline backend and Rust processing stages.
- Saving PACE viewing-direction and energy coordinate metadata.