Quick start
This page walks through a minimal end-to-end session: load a spectrum, fit emission lines, compute abundances.
1. Load a spectrum
jwspecfit accepts a JWST x1d FITS file, a numpy/dict in memory, or a
stacked .npz:
import jwspecfit
# From a JWST x1d FITS file (grating and R read from header):
spec = jwspecfit.read_fits("spectrum.fits", z=6.0)
# From arrays (wavelength in μm, flux / error in μJy):
spec = jwspecfit.read_dict(
{"wave": wave_um, "flux": flux_ujy, "err": err_ujy},
z=6.0,
)
# From a stacked .npz with custom resolving power:
spec = jwspecfit.read_npz("stack.npz", z=2.0, R=1000)
2. Fit emission lines
result = jwspecfit.fit_lines(spec, z=6.0) # default: BIC broad-Balmer
By default, fit_lines performs:
Line-list selection from the grating coverage and redshift.
Polynomial continuum subtraction with iterative sigma-clipping.
Simultaneous resolution-aware Gaussian fit to all observable lines.
1000-iteration bootstrap for flux uncertainties.
BIC-based selection of broad-Balmer components.
Inspect detected lines:
for name, line in result.lines.items():
if line.snr > 3:
print(f"{name:12s} flux={line.flux:.2e}±{line.flux_err:.2e} SNR={line.snr:.1f}")
Plot the fit:
jwspecfit.plot_fit(result, save_path="fit.pdf")
# Or interactive (plotly) — zoomable with hover info:
fig = jwspecfit.plot_fit_interactive(result)
fig.show()
3. MCMC sampling
Swap the least-squares fit for a Bayesian one with full posteriors:
import jwspecmcmc
mc = jwspecmcmc.fit_lines(
spec, z=6.0,
sampler="nuts", # "nuts" (default), "emcee", or "nautilus"
n_warmup=500,
n_samples_nuts=2000,
n_chains=4,
)
line = mc.lines["OIII_5007"]
print(line.flux) # median
print(line.flux_err) # (lower, upper) 68% CI half-widths
print(mc.convergence) # R̂ and ESS per parameter
Sample-level flux-ratio posteriors come for free:
r32 = mc.flux_ratio_posterior("OIII_5007", "HBETA")
4. Chemical abundances
Feed either a FitResult (from jwspecfit) or an MCMCResult (from
jwspecmcmc) into compute_abundances:
import jwspecabund
abund = jwspecabund.compute_abundances(
result, z=6.0,
method="auto", # "direct" / "strong_line" / "forward"
balmer_anchor="HBETA", # or "Ha" if Hα is your best Balmer line
dust_law="salim", # or "cardelli"
)
print(abund.summary())
print(abund.OH) # 12 + log(O/H)
print(abund.NO) # log(N/O)
compute_abundances internally:
Derives A_V from the multi-Balmer decrement (or takes your
Av=value).Corrects every line flux at its own wavelength.
Measures electron density across three ionisation zones.
Selects direct-T_e, strong-line, or forward-model based on line availability.
Applies ICFs (Martinez+25 for N/O, Izotov+06 for S, Ne, Ar).
5. DLA fitting (optional)
For high-redshift spectra with damped Lyα absorption, fit the column density via Pollock+26-style joint nested sampling:
pip install jwspecfit[dla]
result = jwspecfit.fit_NHI(
spec.wave_A, spec.flux_ujy, spec.err_ujy,
z=0.0, # 0 for rest-frame stacks
R=spec.R,
fit_x_HI=False, # True at z > 6 to add the IGM damping wing
)
print(result.summary()) # joint posteriors on log_NHI, β, F0, [x_HI]
DLya = jwspecfit.compute_D_Lya(spec.wave_A, spec.flux_ujy, spec.err_ujy, z=0.0)
print(f"D_Lyα = {DLya['D_Lya']:.1f} ± {DLya['D_Lya_err']:.1f} Å") # Heintz+25
The Heintz+25 D_Lyα equivalent-width statistic is more robust than log N_HI at PRISM resolution where N_HI is degenerate with β. See the user guide for the full option list and caveats.
Quick interactive view
To open a FITS or NPZ spectrum directly in plotly without fitting:
fig = jwspecfit.plot_spectrum_interactive("spectrum.fits", z=6.0)
fig.show()
Next steps
Worked notebooks: see
docs/notebooks/.Full API reference: see each package’s API reference page (jwspecfit, jwspecmcmc, jwspecabund).
Abundance methodology in detail: Abundance methodology.