"""Rest-frame spectral features used across the pipeline.
Three line sets appear here, and they are not interchangeable:
``LINE_LIST_REST_AA``
The full 98-feature list that SR2's line-token transformer attends over.
Breadth matters more than in-band coverage: a token whose line falls
outside the grism band is gated off by ``in_range`` in the line branch,
so an over-complete list costs a little compute and buys robustness
across the redshift range.
``SR1_LINES_AA``
The ten redshift-carrying lines SR1 supervises directly (line-flux L1 and
the anti-hallucination penalty), and the lines the dataset computes
per-row integrated S/N labels for. **Order is load-bearing** --- the
``line_snr`` column of :class:`specsr_roman.data.RomanFixedGridDataset` follows
this tuple, and the losses index into it positionally.
``STRONG_LINES_AA``
The four lines that decide whether a redshift is *identifiable* at all.
Two or more in band means the redshift is line-*pair* constrained; one is
alias-degenerate (Ha/[OIII]/[OII] all look alike on their own); zero
(z < 0.52 for the Roman band) means unconstrained. Used by the
``min_strong_lines`` split filter.
"""
from __future__ import annotations
import numpy as np
# Rest-frame features (Angstrom), mixing emission lines with the continuum
# breaks and absorption features that carry redshift information for quiescent
# galaxies. Deduplicated: identical wavelengths would waste attention capacity.
LINE_LIST_REST_AA: list[tuple[str, float]] = [
# --- Continuum breaks / edges ---
("Lyman_limit_912", 912.0),
("LyA_1216", 1215.670),
("Balmer_break_3646", 3646.0),
("D4000_break_4000", 4000.0),
# --- UV emission lines (AGN + star-forming) ---
("NV_1240", 1240.810),
("SiII_1260", 1260.422),
("OI_1302", 1302.168),
("SiII_1304", 1304.370),
("CII_1335", 1335.708),
("SiIV_1394", 1393.755),
("SiIV_1403", 1402.770),
("OIV]_1402", 1402.06),
("NIV]_1486", 1486.50),
("SiII_1527", 1526.707),
("CIV_1548", 1548.204),
("CIV_1551", 1550.781),
("HeII_1640", 1640.420),
("OIII]_1661", 1660.809),
("OIII]_1666", 1666.150),
("AlII_1671", 1670.788),
("SiII_1808", 1808.013),
("AlIII_1855", 1854.716),
("AlIII_1863", 1862.789),
("SiIII]_1892", 1892.03),
("CIII]_1907", 1906.680),
("CIII]_1909", 1908.734),
("FeII_UV_2344", 2344.214),
("FeII_UV_2374", 2374.461),
("FeII_UV_2382", 2382.765),
("MnII_2577", 2576.877),
("MnII_2594", 2594.499),
("MnII_2606", 2606.462),
("FeII_UV_2586", 2586.650),
("FeII_UV_2600", 2600.173),
("MgII_2796", 2796.352),
("MgII_2803", 2803.531),
("MgI_2853", 2852.964),
# --- Optical strong nebular emission (galaxies) ---
("[OII]_3726", 3726.032),
("[OII]_3729", 3728.815),
("Htheta_3798", 3797.900),
("Heta_3835", 3835.386),
("[NeIII]_3869", 3868.760),
("H8_3889", 3889.064),
("CaK_3934", 3933.663),
("[NeIII]_3968", 3967.470),
("CaH_3969", 3968.468),
("Hepsilon_3970", 3970.075),
("Hdelta_4102", 4101.734),
("Gband_4304", 4304.0),
("Hgamma_4341", 4340.472),
("[OIII]_4363", 4363.210),
("HeI_4471", 4471.479),
("FeII_opt_blend_4570", 4570.0),
("HeII_4686", 4685.710),
("Hbeta_4861", 4861.333),
("[OIII]_4959", 4958.911),
("[OIII]_5007", 5006.843),
("Mg_b_5167", 5167.321),
("Mg_b_5173", 5172.684),
("Mg_b_5184", 5183.604),
("[NI]_5198", 5197.902),
("[NI]_5200", 5200.257),
("FeII_opt_blend_5350", 5350.0),
("DIB_5780", 5780.5),
("HeI_5876", 5875.624),
("NaD_5890", 5889.951),
("NaD_5896", 5895.924),
("TiO_6159", 6159.0),
("DIB_6284", 6283.8),
("[OI]_6300", 6300.304),
("[OI]_6364", 6363.776),
("[NII]_6548", 6548.050),
("Halpha_6563", 6562.800),
("[NII]_6583", 6583.450),
("TiO_6651", 6651.0),
("HeI_6678", 6678.151),
("[SII]_6716", 6716.440),
("[SII]_6731", 6730.820),
("[ArIII]_7136", 7135.790),
("[OII]_7320", 7319.990),
("[OII]_7330", 7330.190),
# --- NIR ---
("CaII_triplet_8498", 8498.020),
("CaII_triplet_8542", 8542.090),
("CaII_triplet_8662", 8662.140),
("[SIII]_9069", 9068.600),
("Pa10_9015", 9014.910),
("Pa9_9229", 9229.014),
("[SIII]_9531", 9530.600),
("Pa8_9546", 9545.969),
("Pa7_10049", 10049.37),
("HeI_10830", 10830.340),
("Pa_gamma_10941", 10941.09),
("[FeII]_12567", 12566.770),
("Pa_beta_12818", 12818.08),
("[FeII]_16435", 16435.0),
("Br11_16811", 16811.0),
("Br10_17363", 17363.0),
("Br_gamma_21661", 21661.0),
]
LINE_NAMES: list[str] = [n for (n, _) in LINE_LIST_REST_AA]
LINE_WAVELENGTHS_AA: list[float] = [w for (_, w) in LINE_LIST_REST_AA]
# The four lines that decide whether a redshift is identifiable at all.
STRONG_LINES_AA: tuple[float, ...] = (6563.0, 5007.0, 4861.0, 3727.0)
# Lines SR1 supervises. The strong-line set extended so every redshift in
# 0 < z < 2 has at least one member in the Roman band (bright z < 0.5 galaxies
# land on the NIR set). ORDER IS LOAD-BEARING: the per-row line-S/N labels in
# RomanFixedGridDataset follow this tuple and the losses index it positionally.
SR1_LINES_AA: tuple[float, ...] = (
3727.0, # [OII]
4861.0, # Hb
5007.0, # [OIII]
6563.0, # Ha
6725.0, # [SII] doublet (blended at grism R)
9069.0, # [SIII]
9532.0, # [SIII]
10830.0, # HeI
12818.0, # Pab
18751.0, # Paa
)
# Lines worth labelling on a figure: the ones a reader looks for.
LABEL_LINES_AA: tuple[tuple[str, float], ...] = (
(r"[OII]", 3727.0),
(r"H$\beta$", 4861.0),
(r"[OIII]", 5007.0),
(r"H$\alpha$+[NII]", 6563.0),
(r"[SII]", 6725.0),
(r"[SIII]", 9531.0),
(r"HeI", 10830.0),
)
[docs]
def angstrom_to_micron(x) -> np.ndarray:
"""Angstrom -> micron, as float32 (the dtype every model buffer uses)."""
return np.asarray(x, dtype=np.float32) * 1e-4
[docs]
def count_strong_lines(z, lam_min_aa: float, lam_max_aa: float) -> np.ndarray:
"""Number of :data:`STRONG_LINES_AA` inside ``[lam_min_aa, lam_max_aa]`` at ``z``."""
z = np.asarray(z, dtype=np.float64)
c = np.zeros(z.shape, dtype=np.int64)
for w in STRONG_LINES_AA:
lam = (1.0 + z) * w
c += ((lam > lam_min_aa) & (lam < lam_max_aa)).astype(np.int64)
return c