A spectrum is not just an array of brightness values. Its coordinate, sampling, physical meaning, provenance, and valid interpolation region all affect what a downstream model is allowed to do. Jaxstro makes those pieces explicit before a spectrum enters JAX.
jaxstro.atmospheres owns the host-side request:
AtmosphereQuery -> PreparationResult[PreparedAtmosphere]Interface notation, not Python
jaxstro.spectra owns the generic array types, transformations, resampling, and
prepared interpolation stencils. Fluxax keeps ownership of extinction, filters,
instruments, counts, magnitudes, PSFs, images, and likelihood-facing observables.

Figure 1:The filesystem boundary is one-way. Host code selects an exact product and a complete local topology, converts its vertices to one requested spectral axis, and returns a fixed-shape PyTree. Evaluation then uses arrays only.
Learning objectives¶
After this chapter, you should be able to separate source semantics, host-side preparation, and JAX evaluation; trace a spectral-density conversion; and state which evidence supports a prepared interpolation policy.
Concept check: what crosses the runtime boundary?¶
Predict which metadata and topology decisions must be resolved before jit.
Compute a prepared stencil evaluation, then audit units, coverage status,
interpolation evidence, and the source-to-observable ownership chain.
Three execution layers¶
Table 1:Spectra execution and ownership boundaries
Operation | Execution side | Owner and contract |
|---|---|---|
Product lookup and topology selection | Host-side Python |
|
Spectral conversion and preparation | Host-side Python with optional data dependencies | Each source vertex is converted to increasing wavelength in |
Prepared parameter interpolation | JAX-side array computation |
|
Observable rendering | Downstream package | Fluxax and domain packages own distance scaling, attenuation, passbands, detector response, images, and likelihood semantics. |
Read the physical semantic before the numbers¶
Atmosphere products return surface flux density. An observer flux density is a different semantic and generally requires geometry such as a radius and distance. Jaxstro does not silently apply that geometry.
The density coordinate matters too. F_lambda is per wavelength interval;
F_nu is per frequency interval. They satisfy
TLUSTY publishes Eddington flux H_nu, so its source-specific path first uses
F_nu = 4 pi H_nu. That factor is not universal: it belongs to the definition
of the released TLUSTY column. NewEra and Sonora publish different density
semantics and therefore use different conversions. The provenance attached to
every Spectrum records the native coordinate, density, unit, conversion, and
source.
Point samples and bin values are also different contracts. A point sample is a
value at one coordinate. A bin average or bin integral represents an interval
and requires edges. SpectralAxis.sampling records which meaning applies;
resampling never silently swaps one for another.
Portable JAX-side example¶
This complete example creates a prepared cell in memory. It requires no local atmosphere artifacts.
import jax
import jax.numpy as jnp
from jaxstro.spectra import (
FluxInterpolation,
PreparedRectilinearStencil,
SpectralAxis,
SpectralCoordinate,
SpectralSemantic,
Spectrum,
SpectrumProvenance,
SpectrumStatusCode,
)
axis = SpectralAxis.points(
jnp.array([500.0, 600.0, 700.0]),
coordinate=SpectralCoordinate.WAVELENGTH,
unit="nm",
)
template = Spectrum(
axis=axis,
values=jnp.array([1.0, 2.0, 3.0]),
semantic=SpectralSemantic.SURFACE_FLUX_LAMBDA,
provenance=SpectrumProvenance(
source_id="portable-fixture",
product_id="portable-fixture",
native_coordinate="wavelength_nm",
native_density="F_lambda",
native_unit="erg s^-1 cm^-2 nm^-1",
canonical_conversion="identity",
citations=("fixture:documentation",),
),
)
prepared = PreparedRectilinearStencil(
parameter_axes=(jnp.array([5000.0, 6000.0]), jnp.array([4.0, 5.0])),
vertex_values=jnp.array(
[
[[1.0, 2.0, 3.0], [2.0, 3.0, 4.0]],
[[3.0, 4.0, 5.0], [4.0, 5.0, 6.0]],
]
),
template=template,
interpolation=FluxInterpolation.LINEAR,
)
midpoint = prepared.evaluate(jnp.array([5500.0, 4.5]))
outside = prepared.evaluate(jnp.array([4500.0, 4.5]))
@jax.jit
def first_flux(teff):
return prepared.evaluate(jnp.array([teff, 4.0])).spectrum.values[0]
local_slope = jax.grad(first_flux)(5500.0)
assert jnp.allclose(midpoint.spectrum.values, jnp.array([2.5, 3.5, 4.5]))
assert int(midpoint.status.code) == SpectrumStatusCode.OK
assert int(outside.status.code) == SpectrumStatusCode.OUTSIDE_CONVEX_HULL
assert jnp.all(jnp.isnan(outside.spectrum.values))
assert jnp.isclose(local_slope, 0.002)The derivative is local to one fixed cell. Catalog ranking, artifact I/O, topology changes, and interpolation-policy selection are discrete host-side operations and are not advertised as differentiable.
Query a local atmosphere library¶
Execution contract — local processed artifacts required. This recipe also
requires the data optional dependencies.
import jax.numpy as jnp
from jaxstro.atmospheres import AtmosphereLibrary, AtmosphereParams, AtmosphereQuery
from jaxstro.spectra import SpectralAxis, SpectralCoordinate, SpectralPlan
library = AtmosphereLibrary.from_local("data")
query = AtmosphereQuery(
params=AtmosphereParams(teff=5772.0, logg=4.44),
product_id="newera-v3-lowres",
family="newera",
spectral_plan=SpectralPlan(
SpectralAxis.points(
jnp.linspace(500.0, 2500.0, 256),
coordinate=SpectralCoordinate.WAVELENGTH,
unit="nm",
)
),
requested_parameter_names=("teff", "logg"),
)
preparation = library.prepare(query)
if preparation.prepared is not None:
result = preparation.prepared.evaluate(query.params)Expected scientific gaps return a SpectrumStatusCode, including no exact
dataset, no complete cell, outside-convex-hull requests, unsupported spectral
windows, and policies that have not passed validation. Corrupt artifacts and
broken invariants raise exceptions instead of masquerading as coverage gaps.
Current evidence boundary¶
Real-artifact holdouts currently accept positive-log parameter interpolation for
NewEra and linear interpolation for the BOSZ resampled product and OSTAR2002.
Sonora Diamondback and both BSTAR modes remain POLICY_NOT_VALIDATED: their
linear and positive-log errors trade off under the declared selection rule.
Those adapters and artifacts exist, but AtmosphereLibrary.prepare(...) refuses
to present the unratified paths as supported science.
See Atmosphere capabilities for the product matrix, Query atmosphere spectra for the step-by-step recipe, and Validation for measured evidence.