Source code for jaxqsofit.defaults

from __future__ import annotations

import copy
from typing import Any, Dict, List

import numpy as np

from .config import PriorConfig
from .custom_components import CustomComponentSpec, make_custom_component
from .model import negative_gaussian_bal_component

MINSCA_DEFAULT = 0.0
MAXSCA_DEFAULT = 1e10
AMPLITUDE_FLOOR = 1e-32

inisig_broad = 5e-3
minsig_broad = 0.004
maxsig_broad = 0.05

inisig_narrow = 1e-3
minsig_narrow = 2.3e-4
maxsig_narrow = 0.00169

inisig_narrow_relaxed = 1e-3
minsig_narrow_relaxed = 5e-4
maxsig_narrow_relaxed = maxsig_narrow

inisig_narrow_uv = 1e-3
minsig_narrow_uv = 3.333e-4
maxsig_narrow_uv = maxsig_narrow

inisig_oiii_wing = 3e-3
minsig_oiii_wing = minsig_narrow
maxsig_oiii_wing = 0.004

inisig_uv_broad = 5e-3
minsig_uv_broad = 0.002
maxsig_uv_broad = 0.05

inisig_nv = 2e-3
minsig_nv = 0.001
maxsig_nv = 0.01

voff_broad = 0.015
voff_broad_balmer = 0.01
voff_narrow = 0.01
voff_narrow_tight = 5e-3
voff_uv_broad = 0.015
voff_lya = 0.02
voff_nv = 0.005
voff_elg = 0.01
voff_elg_red = 0.008


def _line_row(
    *,
    lam: float,
    compname: str,
    minwav: float,
    maxwav: float,
    linename: str,
    ngauss: int = 1,
    inisca: float = 0.0,
    minsca: float = MINSCA_DEFAULT,
    maxsca: float = MAXSCA_DEFAULT,
    inisig: float,
    minsig: float,
    maxsig: float,
    voff: float,
    vindex: int,
    windex: int,
    findex: int,
    fvalue: float,
    vary: int = 1,
) -> PriorConfig:
    """Build one line-prior row.

    Wavelength fields are rest-frame vacuum Angstroms, matching SDSS spectra
    and the rest-frame wavelength grid used by the fitter.
    """
    return {
        "lambda": lam,
        "compname": compname,
        "minwav": minwav,
        "maxwav": maxwav,
        "linename": linename,
        "ngauss": ngauss,
        "inisca": inisca,
        "minsca": minsca,
        "maxsca": maxsca,
        "inisig": inisig,
        "minsig": minsig,
        "maxsig": maxsig,
        "voff": voff,
        "vindex": vindex,
        "windex": windex,
        "findex": findex,
        "fvalue": fvalue,
        "vary": vary,
    }


def _lnlam_peak_ratio_for_flux_ratio(
    flux_ratio: float,
    numerator_lam: float,
    denominator_lam: float,
) -> float:
    """Convert an integrated-flux ratio to a tied peak-amplitude ratio.

    Line ties are applied to Gaussian peak amplitudes in ln-lambda space. For
    equal ln-lambda widths, integrated flux scales as peak * rest wavelength.
    """
    return flux_ratio * denominator_lam / numerator_lam


# Default line table in plain dict rows (same schema as notebook line config).
DEFAULT_LINE_PRIOR_ROWS: List[Dict[str, Any]] = [
    # Halpha complex
    _line_row(lam=6564.61, compname='Ha', minwav=6400, maxwav=6800, linename='Ha_br', ngauss=2, inisig=inisig_broad, minsig=minsig_broad, maxsig=maxsig_broad, voff=voff_broad, vindex=0, windex=0, findex=0, fvalue=0.05),
    _line_row(lam=6564.61, compname='Ha', minwav=6400, maxwav=6800, linename='Ha_na', inisig=inisig_narrow_relaxed, minsig=minsig_narrow_relaxed, maxsig=maxsig_narrow_relaxed, voff=voff_narrow, vindex=1, windex=1, findex=0, fvalue=0.002),
    _line_row(lam=6549.85, compname='Ha', minwav=6400, maxwav=6800, linename='NII6549', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_narrow_tight, vindex=1, windex=1, findex=1, fvalue=0.001),
    _line_row(lam=6585.28, compname='Ha', minwav=6400, maxwav=6800, linename='NII6585', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_narrow_tight, vindex=1, windex=1, findex=1, fvalue=0.003),
    _line_row(lam=6718.29, compname='Ha', minwav=6400, maxwav=6800, linename='SII6718', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_narrow_tight, vindex=1, windex=1, findex=2, fvalue=0.001),
    _line_row(lam=6732.67, compname='Ha', minwav=6400, maxwav=6800, linename='SII6732', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_narrow_tight, vindex=1, windex=1, findex=2, fvalue=0.001),
    # Hbeta / [OIII]
    _line_row(lam=4862.68, compname='Hb', minwav=4640, maxwav=5100, linename='Hb_br', ngauss=2, inisig=inisig_broad, minsig=minsig_broad, maxsig=maxsig_broad, voff=voff_broad_balmer, vindex=0, windex=0, findex=0, fvalue=0.01),
    _line_row(lam=4862.68, compname='Hb', minwav=4640, maxwav=5100, linename='Hb_na', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_narrow, vindex=1, windex=1, findex=0, fvalue=0.002),
    _line_row(lam=4960.30, compname='Hb', minwav=4640, maxwav=5100, linename='OIII4959c', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_narrow, vindex=1, windex=1, findex=3, fvalue=1.0),
    _line_row(lam=5008.24, compname='Hb', minwav=4640, maxwav=5100, linename='OIII5007c', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_narrow, vindex=1, windex=1, findex=3, fvalue=_lnlam_peak_ratio_for_flux_ratio(2.98, 5008.24, 4960.30)),
    _line_row(lam=4960.30, compname='Hb', minwav=4640, maxwav=5100, linename='OIII4959w', inisig=inisig_oiii_wing, minsig=minsig_oiii_wing, maxsig=maxsig_oiii_wing, voff=voff_narrow, vindex=2, windex=2, findex=4, fvalue=1.0),
    _line_row(lam=5008.24, compname='Hb', minwav=4640, maxwav=5100, linename='OIII5007w', inisig=inisig_oiii_wing, minsig=minsig_oiii_wing, maxsig=maxsig_oiii_wing, voff=voff_narrow, vindex=2, windex=2, findex=4, fvalue=_lnlam_peak_ratio_for_flux_ratio(2.98, 5008.24, 4960.30)),
    # Higher-order Balmer
    _line_row(lam=4341.68, compname='Hg', minwav=4200, maxwav=4400, linename='Hg_br', inisig=inisig_broad, minsig=minsig_broad, maxsig=maxsig_broad, voff=voff_broad_balmer, vindex=0, windex=0, findex=0, fvalue=0.01),
    _line_row(lam=4341.68, compname='Hg', minwav=4200, maxwav=4400, linename='Hg_na', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_narrow, vindex=1, windex=1, findex=0, fvalue=0.002),
    _line_row(lam=4102.89, compname='Hd', minwav=4000, maxwav=4150, linename='Hd_br', inisig=inisig_broad, minsig=minsig_broad, maxsig=maxsig_broad, voff=voff_broad_balmer, vindex=0, windex=0, findex=0, fvalue=0.01),
    _line_row(lam=4102.89, compname='Hd', minwav=4000, maxwav=4150, linename='Hd_na', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_narrow, vindex=1, windex=1, findex=0, fvalue=0.002),
    # Other optical/UV
    # CaII3934
    _line_row(lam=3728.48, compname='OII', minwav=3650, maxwav=3800, linename='OII3728', inisig=inisig_narrow_uv, minsig=minsig_narrow_uv, maxsig=maxsig_narrow_uv, voff=voff_narrow, vindex=1, windex=1, findex=0, fvalue=0.001),
    _line_row(lam=3426.84, compname='NeV', minwav=3380, maxwav=3480, linename='NeV3426', inisig=inisig_narrow_uv, minsig=minsig_narrow_uv, maxsig=maxsig_narrow_uv, voff=voff_narrow, vindex=0, windex=0, findex=0, fvalue=0.001),
    # Mg II complex
    _line_row(lam=2798.75, compname='MgII', minwav=2700, maxwav=2900, linename='MgII_br', ngauss=2, inisig=inisig_broad, minsig=minsig_broad, maxsig=maxsig_broad, voff=voff_broad, vindex=0, windex=0, findex=0, fvalue=0.05),
    _line_row(lam=2798.75, compname='MgII', minwav=2700, maxwav=2900, linename='MgII_na', inisig=inisig_narrow_relaxed, minsig=minsig_narrow_relaxed, maxsig=maxsig_narrow_relaxed, voff=voff_narrow, vindex=1, windex=1, findex=0, fvalue=0.002),
    # CIII complex
    _line_row(lam=1908.73, compname='CIII', minwav=1700, maxwav=1970, linename='CIII_br', ngauss=2, inisig=inisig_uv_broad, minsig=minsig_uv_broad, maxsig=maxsig_uv_broad, voff=voff_uv_broad, vindex=3, windex=0, findex=0, fvalue=0.01),
    _line_row(lam=1908.73, compname='CIII', minwav=1700, maxwav=1970, linename='CIII_na', inisig=inisig_narrow_relaxed, minsig=minsig_narrow_relaxed, maxsig=0.002, voff=voff_narrow, vindex=4, windex=4, findex=0, fvalue=0.002),
    _line_row(lam=1892.03, compname='CIII', minwav=1700, maxwav=1970, linename='SiIII1892', inisig=inisig_nv, minsig=minsig_nv, maxsig=0.015, voff=0.003, vindex=1, windex=1, findex=0, fvalue=0.005),
    _line_row(lam=1857.40, compname='CIII', minwav=1700, maxwav=1970, linename='AlIII1857', inisig=inisig_nv, minsig=minsig_nv, maxsig=0.015, voff=0.003, vindex=1, windex=1, findex=0, fvalue=0.005),
    _line_row(lam=1816.98, compname='CIII', minwav=1700, maxwav=1970, linename='SiII1816', inisig=inisig_nv, minsig=minsig_nv, maxsig=0.015, voff=voff_narrow, vindex=2, windex=2, findex=0, fvalue=0.0002),
    _line_row(lam=1750.26, compname='CIII', minwav=1700, maxwav=1970, linename='NIII1750', inisig=inisig_nv, minsig=minsig_nv, maxsig=0.015, voff=voff_narrow, vindex=2, windex=2, findex=0, fvalue=0.001),
    _line_row(lam=1718.55, compname='CIII', minwav=1700, maxwav=1900, linename='NIV1718', inisig=inisig_nv, minsig=minsig_nv, maxsig=0.015, voff=voff_narrow, vindex=2, windex=2, findex=0, fvalue=0.001),
    # CIV complex
    _line_row(lam=1549.06, compname='CIV', minwav=1500, maxwav=1700, linename='CIV_br', ngauss=3, inisig=inisig_uv_broad, minsig=0.001, maxsig=maxsig_uv_broad, voff=voff_uv_broad, vindex=0, windex=0, findex=0, fvalue=0.05),
    _line_row(lam=1549.06, compname='CIV', minwav=1500, maxwav=1700, linename='CIV_na', inisig=inisig_narrow_relaxed, minsig=minsig_narrow_relaxed, maxsig=0.002, voff=voff_narrow, vindex=1, windex=1, findex=0, fvalue=0.002),
    _line_row(lam=1640.42, compname='CIV', minwav=1500, maxwav=1700, linename='HeII1640', inisig=inisig_narrow_relaxed, minsig=minsig_narrow_relaxed, maxsig=0.002, voff=voff_elg_red, vindex=1, windex=1, findex=0, fvalue=0.002),
    _line_row(lam=1663.48, compname='CIV', minwav=1500, maxwav=1700, linename='OIII1663', inisig=inisig_narrow_relaxed, minsig=minsig_narrow_relaxed, maxsig=0.002, voff=voff_elg_red, vindex=1, windex=1, findex=0, fvalue=0.002),
    _line_row(lam=1640.42, compname='CIV', minwav=1500, maxwav=1700, linename='HeII1640_br', inisig=inisig_uv_broad, minsig=0.0025, maxsig=0.02, voff=voff_elg_red, vindex=2, windex=2, findex=0, fvalue=0.002),
    _line_row(lam=1663.48, compname='CIV', minwav=1500, maxwav=1700, linename='OIII1663_br', inisig=inisig_uv_broad, minsig=0.0025, maxsig=0.02, voff=voff_elg_red, vindex=2, windex=2, findex=0, fvalue=0.002),
    # SiIV complex
    _line_row(lam=1402.06, compname='SiIV', minwav=1290, maxwav=1450, linename='SiIV_OIV1', inisig=inisig_uv_broad, minsig=minsig_uv_broad, maxsig=maxsig_uv_broad, voff=voff_uv_broad, vindex=1, windex=1, findex=0, fvalue=0.05),
    _line_row(lam=1396.76, compname='SiIV', minwav=1290, maxwav=1450, linename='SiIV_OIV2', inisig=inisig_uv_broad, minsig=minsig_uv_broad, maxsig=maxsig_uv_broad, voff=voff_uv_broad, vindex=1, windex=1, findex=0, fvalue=0.05),
    _line_row(lam=1335.30, compname='SiIV', minwav=1290, maxwav=1450, linename='CII1335', inisig=inisig_nv, minsig=minsig_nv, maxsig=0.015, voff=voff_narrow, vindex=2, windex=2, findex=0, fvalue=0.001),
    _line_row(lam=1304.35, compname='SiIV', minwav=1290, maxwav=1450, linename='OI1304', inisig=inisig_nv, minsig=minsig_nv, maxsig=0.015, voff=voff_narrow, vindex=2, windex=2, findex=0, fvalue=0.001),
    # Lya complex
    _line_row(lam=1215.67, compname='Lya', minwav=1150, maxwav=1290, linename='Lya_br', ngauss=3, inisig=inisig_uv_broad, minsig=minsig_uv_broad, maxsig=maxsig_uv_broad, voff=voff_lya, vindex=0, windex=0, findex=0, fvalue=0.05),
    _line_row(lam=1240.14, compname='Lya', minwav=1150, maxwav=1290, linename='NV1240', inisig=inisig_nv, minsig=minsig_nv, maxsig=maxsig_nv, voff=voff_nv, vindex=0, windex=0, findex=0, fvalue=0.002),
]

DEFAULT_LINE_CONFIG: Dict[str, Any] = {
    "line_dmu_scale_mult": 0.25,
    "line_sig_scale_mult": 0.25,
    "line_amp_scale_mult": 0.25,
    "line": {"table": DEFAULT_LINE_PRIOR_ROWS},
}

# Additional narrow lines commonly used for emission-line galaxies (ELGs).
# These can be appended to the default line list via
# build_default_prior_config(..., include_elg_narrow_lines=True).
DEFAULT_ELG_NARROW_LINE_PRIOR_ROWS: List[Dict[str, Any]] = [
    _line_row(lam=3726.03, compname='OII', minwav=3650, maxwav=3800, linename='OII3726', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg, vindex=11, windex=11, findex=31, fvalue=1.0),
    _line_row(lam=3728.82, compname='OII', minwav=3650, maxwav=3800, linename='OII3729', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg, vindex=11, windex=11, findex=31, fvalue=1.0),
    _line_row(lam=3869.86, compname='NeIII', minwav=3800, maxwav=4020, linename='NeIII3869', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg, vindex=11, windex=11, findex=0, fvalue=0.001),
    _line_row(lam=3968.59, compname='NeIII', minwav=3900, maxwav=4100, linename='NeIII3968', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg, vindex=11, windex=11, findex=0, fvalue=0.001),
    _line_row(lam=4102.89, compname='Hd', minwav=4000, maxwav=4150, linename='Hd_na_elg', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg, vindex=11, windex=11, findex=0, fvalue=0.001),
    _line_row(lam=4341.68, compname='Hg', minwav=4200, maxwav=4450, linename='Hg_na_elg', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg, vindex=11, windex=11, findex=0, fvalue=0.001),
    _line_row(lam=4364.44, compname='OIII', minwav=4300, maxwav=4450, linename='OIII4363', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg, vindex=11, windex=11, findex=0, fvalue=0.001),
    _line_row(lam=4862.68, compname='Hb', minwav=4640, maxwav=5100, linename='Hb_na_elg', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg, vindex=11, windex=11, findex=0, fvalue=0.001),
    _line_row(lam=4687.02, compname='HeII', minwav=4620, maxwav=4760, linename='HeII4686', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=11, windex=11, findex=0, fvalue=0.001),
    _line_row(lam=4960.30, compname='OIII', minwav=4870, maxwav=5050, linename='OIII4959', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg, vindex=11, windex=11, findex=32, fvalue=1.0),
    _line_row(lam=5008.24, compname='OIII', minwav=4920, maxwav=5100, linename='OIII5007', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg, vindex=11, windex=11, findex=32, fvalue=_lnlam_peak_ratio_for_flux_ratio(2.98, 5008.24, 4960.30)),
    _line_row(lam=5877.25, compname='HeI', minwav=5800, maxwav=5950, linename='HeI5876', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=11, windex=11, findex=0, fvalue=0.001),
    _line_row(lam=6302.05, compname='OI', minwav=6200, maxwav=6420, linename='OI6300', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=11, windex=11, findex=33, fvalue=_lnlam_peak_ratio_for_flux_ratio(3.05, 6302.05, 6365.54)),
    _line_row(lam=6365.54, compname='OI', minwav=6280, maxwav=6460, linename='OI6363', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=11, windex=11, findex=33, fvalue=1.0),
    _line_row(lam=6549.85, compname='NII', minwav=6460, maxwav=6640, linename='NII6548', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=11, windex=11, findex=34, fvalue=1.0),
    _line_row(lam=6564.61, compname='Ha', minwav=6480, maxwav=6660, linename='Ha_na_elg', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg, vindex=11, windex=11, findex=0, fvalue=0.001),
    _line_row(lam=6585.28, compname='NII', minwav=6500, maxwav=6680, linename='NII6583', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=11, windex=11, findex=34, fvalue=_lnlam_peak_ratio_for_flux_ratio(3.0, 6585.28, 6549.85)),
    _line_row(lam=6718.29, compname='SII', minwav=6640, maxwav=6800, linename='SII6716', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=11, windex=11, findex=35, fvalue=1.0),
    _line_row(lam=6732.67, compname='SII', minwav=6660, maxwav=6820, linename='SII6731', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=11, windex=11, findex=35, fvalue=1.0),
    # Red optical / far-red forbidden + He I
    _line_row(lam=7067.17, compname='HeI', minwav=7000, maxwav=7125, linename='HeI7065', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=11, windex=11, findex=0, fvalue=0.001),
    _line_row(lam=7137.77, compname='ArIII', minwav=7050, maxwav=7220, linename='ArIII7138', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=11, windex=11, findex=0, fvalue=0.001),
    _line_row(lam=7322.19, compname='OII', minwav=7260, maxwav=7375, linename='OII7320', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=11, windex=11, findex=22, fvalue=0.001),
    _line_row(lam=7332.97, compname='OII', minwav=7270, maxwav=7385, linename='OII7330', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=11, windex=11, findex=22, fvalue=0.001),
    _line_row(lam=7753.19, compname='ArIII', minwav=7680, maxwav=7820, linename='ArIII7751', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=11, windex=11, findex=0, fvalue=0.001),
    # Paschen series (vacuum wavelengths, narrow by default)
    _line_row(lam=8752.87, compname='Paschen', minwav=8690, maxwav=8815, linename='Pa12', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=12, windex=12, findex=0, fvalue=0.001),
    _line_row(lam=8865.22, compname='Paschen', minwav=8800, maxwav=8930, linename='Pa11', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=12, windex=12, findex=0, fvalue=0.001),
    _line_row(lam=9017.38, compname='Paschen', minwav=8950, maxwav=9085, linename='Pa10', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=12, windex=12, findex=0, fvalue=0.001),
    _line_row(lam=9231.55, compname='Paschen', minwav=9160, maxwav=9300, linename='Pa9', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=12, windex=12, findex=0, fvalue=0.001),
    _line_row(lam=9548.59, compname='Paschen', minwav=9480, maxwav=9620, linename='Pae', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=12, windex=12, findex=0, fvalue=0.001),
    _line_row(lam=10052.13, compname='Paschen', minwav=9980, maxwav=10130, linename='Pad', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=12, windex=12, findex=0, fvalue=0.001),
    _line_row(lam=10941.09, compname='Paschen', minwav=10850, maxwav=11040, linename='Pag', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=12, windex=12, findex=0, fvalue=0.001),
    _line_row(lam=12821.67, compname='Paschen', minwav=12700, maxwav=12950, linename='Pab', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=12, windex=12, findex=0, fvalue=0.001),
    _line_row(lam=18756.13, compname='Paschen', minwav=18600, maxwav=18920, linename='Paa', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=12, windex=12, findex=0, fvalue=0.001),
    # Strong red/NIR forbidden lines
    _line_row(lam=9071.09, compname='SIII', minwav=9000, maxwav=9135, linename='SIII9069', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=11, windex=11, findex=23, fvalue=0.001),
    _line_row(lam=9533.20, compname='SIII', minwav=9460, maxwav=9605, linename='SIII9531', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=11, windex=11, findex=23, fvalue=0.0025),
]

# Optional high-ionization/coronal narrow-line set.
DEFAULT_HIGH_IONIZATION_LINE_PRIOR_ROWS: List[Dict[str, Any]] = [
    _line_row(lam=3346.79, compname='NeV', minwav=3300, maxwav=3385, linename='NeV3346', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg, vindex=12, windex=12, findex=41, fvalue=1.0),
    _line_row(lam=3426.84, compname='NeV', minwav=3380, maxwav=3480, linename='NeV3426_hi', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg, vindex=12, windex=12, findex=41, fvalue=_lnlam_peak_ratio_for_flux_ratio(2.7, 3426.84, 3346.79)),
    _line_row(lam=5721.0, compname='FeVII', minwav=5660, maxwav=5785, linename='FeVII5721', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=12, windex=12, findex=0, fvalue=0.001),
    _line_row(lam=6087.0, compname='FeVII', minwav=6030, maxwav=6145, linename='FeVII6087', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=12, windex=12, findex=0, fvalue=0.001),
    _line_row(lam=6374.0, compname='FeX', minwav=6320, maxwav=6430, linename='FeX6374', inisig=inisig_narrow, minsig=minsig_narrow, maxsig=maxsig_narrow, voff=voff_elg_red, vindex=12, windex=12, findex=0, fvalue=0.001),
]


def _apply_robust_line_scale_priors(
    line_rows: List[Dict[str, Any]],
    fscale: float,
    fmax: float,
) -> List[Dict[str, Any]]:
    """Apply flux-aware robust bounds/initialization to line-scale priors."""
    if len(line_rows) == 0:
        return line_rows

    # Keep dynamic range positive even for nearly flat/noisy spectra.
    delta = max(float(fmax - fscale), 0.1 * float(fscale), AMPLITUDE_FLOOR)

    for row in line_rows:
        linename = str(row.get("linename", "")).lower()
        is_broad = linename.endswith("_br") or ("_br" in linename)

        maxsca = float(row.get("maxsca", np.inf))
        minsca = float(row.get("minsca", 0.0))
        inisca = float(row.get("inisca", 0.0))

        # Broad lines get a tighter cap than narrow lines by default.
        if is_broad:
            max_cap = 1.0 * delta
        else:
            max_cap = 1.2 * delta
        maxsca = min(maxsca, max_cap)

        # Keep scales strictly positive and ordered.
        mins_floor = max(minsca, 1e-4 * float(fscale), AMPLITUDE_FLOOR)
        maxsca = max(maxsca, 1.01 * mins_floor)
        inisca = float(np.clip(inisca, mins_floor, maxsca))

        row["minsca"] = mins_floor
        row["maxsca"] = maxsca
        row["inisca"] = inisca

    return line_rows


def _append_unique_by_wavelength(
    base_rows: List[Dict[str, Any]],
    extra_rows: List[Dict[str, Any]],
    atol_angstrom: float = 1.0,
) -> List[Dict[str, Any]]:
    """Append rows from `extra_rows` only if no near-duplicate wavelength exists."""
    out = list(base_rows)
    for row in extra_rows:
        lam_new = float(row.get("lambda", np.nan))
        if not np.isfinite(lam_new):
            continue
        exists = False
        for old in out:
            lam_old = float(old.get("lambda", np.nan))
            if np.isfinite(lam_old) and abs(lam_old - lam_new) <= float(atol_angstrom):
                exists = True
                break
        if not exists:
            out.append(row)
    return out


[docs] def build_default_bal_components(flux: np.ndarray) -> tuple[CustomComponentSpec, ...]: """Return built-in BAL custom components with flux-scaled depth priors.""" f = np.asarray(flux, dtype=float) finite = np.isfinite(f) fscale = float(np.nanmedian(np.abs(f[finite]))) if np.any(finite) else 1.0 if not np.isfinite(fscale) or fscale <= 0: fscale = 1.0 def _bal_component( name: str, depth_frac: float, center: float, scale: float, low: float, high: float, sigma: float, sigma_scale: float = 0.35, ): """Build one negative-Gaussian BAL custom component spec.""" return make_custom_component( name=name, parameter_priors={ # Keep a zero-centered shrinkage prior, but with a broader scale so # moderate-to-deep troughs are still available when the data support them. "depth": {"dist": "HalfNormal", "scale": max(8.0 * depth_frac * fscale, 1e-6)}, "center": { # Force BAL trough centers to remain on the blue side of the # corresponding broad emission line. "dist": "TruncatedNormal", "loc": float(center), "scale": float(scale), "low": float(low), "high": float(high), }, "sigma": {"dist": "LogNormal", "loc": np.log(float(sigma)), "scale": float(sigma_scale)}, "shape_power": { "dist": "TruncatedNormal", "loc": 2.0, "scale": 1.5, "low": 2.0, "high": 12.0, }, }, evaluate=negative_gaussian_bal_component, ) # Trump et al. (2006) return ( _bal_component("bal_nv", depth_frac=0.04, center=1200.0, scale=70.0, low=1120.0, high=1240.0, sigma=22.0), # _bal_component("bal_nv_2", depth_frac=0.025, center=1160.0, scale=90.0, low=1100.0, high=1240.0, sigma=40.0), _bal_component("bal_siiv", depth_frac=0.04, center=1350.0, scale=70.0, low=1280.0, high=1397.0, sigma=22.0), # _bal_component("bal_siiv_2", depth_frac=0.025, center=1320.0, scale=90.0, low=1260.0, high=1397.0, sigma=40.0), _bal_component("bal_civ", depth_frac=0.05, center=1500.0, scale=80.0, low=1400.0, high=1549.0, sigma=24.0), # _bal_component("bal_civ_2", depth_frac=0.03, center=1450.0, scale=100.0, low=1350.0, high=1549.0, sigma=45.0), _bal_component("bal_ciii", depth_frac=0.03, center=1850.0, scale=80.0, low=1750.0, high=1909.0, sigma=30.0), # _bal_component("bal_ciii_2", depth_frac=0.02, center=1800.0, scale=100.0, low=1700.0, high=1909.0, sigma=50.0), # Fe ?? _bal_component("bal_fe1", depth_frac=0.03, center=2000.0, scale=80.0, low=1950.0, high=2050.0, sigma=30.0), _bal_component("bal_fe2", depth_frac=0.03, center=2200.0, scale=80.0, low=2150.0, high=2250.0, sigma=30.0), # _bal_component("bal_mgii", depth_frac=0.03, center=2798.0, scale=120.0, low=2750.0, high=2798.0, sigma=40.0), # _bal_component("bal_mgii_2", depth_frac=0.02, center=2760.0, scale=120.0, low=2700.0, high=2798.0, sigma=55.0), )
[docs] def build_default_prior_config( flux: np.ndarray, line_config: Dict[str, Any] | None = None, include_elg_narrow_lines: bool = False, include_high_ionization_lines: bool = False, pl_pivot: float | None = None, ) -> Dict[str, Any]: """Build a full PriorConfig with sane defaults from data flux scale. Parameters ---------- flux : ndarray Input flux array used to set data-scale-aware defaults. line_config : dict or None, optional Optional line configuration override. If None, default line config is used. include_elg_narrow_lines : bool, optional If True, append additional narrow ELG lines from ``DEFAULT_ELG_NARROW_LINE_PRIOR_ROWS`` to the active line table. include_high_ionization_lines : bool, optional If True, append additional high-ionization lines from ``DEFAULT_HIGH_IONIZATION_LINE_PRIOR_ROWS`` to the active line table. pl_pivot : float or None, optional Optional manual override for the power-law continuum pivot wavelength in Angstrom. If ``None``, the model uses the midpoint of the fitted rest-frame wavelength coverage. Notes ----- ``reddening_a2500`` controls the amplitude of the built-in SMC-like attenuation curve. Because the curve is normalized to unity at ``reddening_uv_ref`` (2500 Angstrom by default), this parameter is :math:`A(2500)` in magnitudes rather than literal ``E(B-V)``. """ f = np.asarray(flux, dtype=float) finite = np.isfinite(f) fscale = float(np.nanmedian(np.abs(f[finite]))) if np.any(finite) else 1.0 fmax = float(np.nanmax(np.abs(f[finite]))) if np.any(finite) else fscale if not np.isfinite(fscale) or fscale <= 0: fscale = 1.0 if not np.isfinite(fmax) or fmax <= 0: fmax = fscale cfg: Dict[str, Any] = { "log_cont_norm": {"dist": "LogNormal", "loc": np.log(max(fscale, AMPLITUDE_FLOOR)), "scale": 0.3}, "PL_norm": {"dist": "HalfNormal", "scale": max(0.5 * fscale, AMPLITUDE_FLOOR)}, "PL_slope": {"dist": "Normal", "loc": -1.5, "scale": 0.4}, "PL_pivot": None if pl_pivot is None else float(pl_pivot), "poly_pivot": None, "reddening_a2500": {"dist": "HalfNormal", "scale": 0.3}, "reddening_uv_ref": 2500.0, "reddening_alpha": 1.2, "log_frac_host": {"dist": "StudentT", "loc": 0.0, "scale": 2.0, "df": 3.0}, "host_redshift_prior": { "enabled": False, "z_mid": 1.0, "width": 0.2, "lowz_loc_offset": 0.0, "highz_loc_offset": -8.0, "lowz_scale_mult": 1.0, "highz_scale_mult": 0.05, "lowz_df": 3.0, "highz_df": 20.0, }, "tau_host": {"dist": "HalfNormal", "scale": 1.0}, "raw_w": {"dist": "Normal", "loc": -0.5, "scale": 1.0}, "log_stellar_mass": {"dist": "TruncatedNormal", "loc": 9.0, "scale": 0.75, "low": 7.0, "high": 12.0}, "log_host_aperture_scale": {"dist": "Normal", "loc": 0.0, "scale": 0.5}, "log_sfh_age_gyr": {"dist": "Normal", "loc": np.log(3.0), "scale": 1.0}, "log_sfh_tau_gyr": {"dist": "Normal", "loc": np.log(1.0), "scale": 1.0}, "gal_lgmet": {"dist": "Normal", "loc": 0.0, "scale": 0.5}, "gal_lgmet_scatter": {"dist": "HalfNormal", "scale": 0.2}, "mass_metallicity_relation": { "enabled": False, "pivot_mass": 10.0, "pivot_logzsol": -0.15, "slope": 0.35, "scale": 0.25, "min": -1.5, "max": 0.3, }, "gal_v_kms": {"dist": "Normal", "loc": 0.0, "scale": 120.0}, "gal_sigma_kms": {"dist": "HalfNormal", "scale": 200.0}, "log_Fe_uv_norm": {"dist": "LogNormal", "loc": np.log(max(0.03 * fscale, 1e-12)), "scale": 1.0}, "log_Fe_op_over_uv": {"dist": "Normal", "loc": 0.0, "scale": 1.0}, "log_Fe_uv_FWHM": {"dist": "LogNormal", "loc": np.log(3000.0), "scale": 0.5}, "log_Fe_op_FWHM": {"dist": "LogNormal", "loc": np.log(3000.0), "scale": 0.5}, "Fe_uv_shift": {"dist": "Normal", "loc": 0.0, "scale": 1e-3}, "Fe_op_shift": {"dist": "Normal", "loc": 0.0, "scale": 1e-3}, "log_Balmer_norm": {"dist": "LogNormal", "loc": np.log(max(1e-3 * fscale, AMPLITUDE_FLOOR)), "scale": 0.5}, "log_Balmer_Tau": {"dist": "LogNormal", "loc": np.log(0.5), "scale": 0.25}, "log_Balmer_vel": {"dist": "LogNormal", "loc": np.log(3000.0), "scale": 0.25}, "poly_c1": {"dist": "Normal", "loc": 0.0, "scale": 0.1}, "poly_c2": {"dist": "Normal", "loc": 0.0, "scale": 0.1}, "poly_c3": {"dist": "Normal", "loc": 0.0, "scale": 0.05}, "poly_c4": {"dist": "Normal", "loc": 0.0, "scale": 0.05}, "poly_c5": {"dist": "Normal", "loc": 0.0, "scale": 0.03}, "poly_c6": {"dist": "Normal", "loc": 0.0, "scale": 0.03}, "frac_jitter": {"dist": "HalfNormal", "scale": 0.02}, "add_jitter": {"dist": "HalfNormal", "scale_mult_err": 0.3}, "student_t_df": 3.0, "out_params": { "cont_loc": [1350.0, 2500.0, 3000.0, 4200.0, 5100.0], }, } lc = copy.deepcopy(DEFAULT_LINE_CONFIG if line_config is None else line_config) if isinstance(lc, dict): line_cfg = lc.get("line", {}) if isinstance(line_cfg, dict): table = line_cfg.get("table", None) if isinstance(table, list): if include_elg_narrow_lines: table = _append_unique_by_wavelength( list(table), copy.deepcopy(DEFAULT_ELG_NARROW_LINE_PRIOR_ROWS), atol_angstrom=1.0, ) if include_high_ionization_lines: table = _append_unique_by_wavelength( list(table), copy.deepcopy(DEFAULT_HIGH_IONIZATION_LINE_PRIOR_ROWS), atol_angstrom=1.0, ) line_cfg["table"] = _apply_robust_line_scale_priors(table, fscale=fscale, fmax=fmax) cfg.update(lc) return PriorConfig(overrides=cfg)