lated 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lated/__init__.py +35 -0
- lated/cli.py +72 -0
- lated/config.py +258 -0
- lated/data/filters/F070W.dat +201 -0
- lated/data/filters/F090W.dat +255 -0
- lated/data/filters/F1000W.dat +479 -0
- lated/data/filters/F1130W.dat +342 -0
- lated/data/filters/F115W.dat +342 -0
- lated/data/filters/F1280W.dat +609 -0
- lated/data/filters/F140M.dat +257 -0
- lated/data/filters/F1500W.dat +817 -0
- lated/data/filters/F150W.dat +447 -0
- lated/data/filters/F150W2.dat +1502 -0
- lated/data/filters/F162M.dat +296 -0
- lated/data/filters/F164N.dat +72 -0
- lated/data/filters/F1800W.dat +1035 -0
- lated/data/filters/F182M.dat +384 -0
- lated/data/filters/F187N.dat +80 -0
- lated/data/filters/F200W.dat +611 -0
- lated/data/filters/F2100W.dat +1455 -0
- lated/data/filters/F210M.dat +337 -0
- lated/data/filters/F212N.dat +85 -0
- lated/data/filters/F250M.dat +237 -0
- lated/data/filters/F2550W.dat +1482 -0
- lated/data/filters/F277W.dat +806 -0
- lated/data/filters/F300M.dat +457 -0
- lated/data/filters/F322W2.dat +1367 -0
- lated/data/filters/F323N.dat +89 -0
- lated/data/filters/F335M.dat +559 -0
- lated/data/filters/F356W.dat +918 -0
- lated/data/filters/F360M.dat +555 -0
- lated/data/filters/F405N.dat +124 -0
- lated/data/filters/F410M.dat +631 -0
- lated/data/filters/F430M.dat +333 -0
- lated/data/filters/F444W.dat +1029 -0
- lated/data/filters/F460M.dat +382 -0
- lated/data/filters/F466N.dat +135 -0
- lated/data/filters/F470N.dat +131 -0
- lated/data/filters/F480M.dat +455 -0
- lated/data/filters/F560W.dat +338 -0
- lated/data/filters/F770W.dat +539 -0
- lated/data/filters/RomanF062.dat +70 -0
- lated/data/filters/RomanF087.dat +62 -0
- lated/data/filters/RomanF106.dat +75 -0
- lated/data/filters/RomanF129.dat +90 -0
- lated/data/filters/RomanF146.dat +243 -0
- lated/data/filters/RomanF158.dat +109 -0
- lated/data/filters/RomanF184.dat +98 -0
- lated/data/filters/RomanF213.dat +109 -0
- lated/dust.py +40 -0
- lated/engine.py +582 -0
- lated/filters.py +198 -0
- lated/lines.py +183 -0
- lated/presets.py +26 -0
- lated/server/__init__.py +0 -0
- lated/server/app.py +140 -0
- lated/server/static/app.css +579 -0
- lated/server/static/app.js +1147 -0
- lated/server/static/index.html +244 -0
- lated/server/static/plot.js +718 -0
- lated-0.1.0.dist-info/METADATA +157 -0
- lated-0.1.0.dist-info/RECORD +66 -0
- lated-0.1.0.dist-info/WHEEL +5 -0
- lated-0.1.0.dist-info/entry_points.txt +2 -0
- lated-0.1.0.dist-info/licenses/LICENSE +21 -0
- lated-0.1.0.dist-info/top_level.txt +1 -0
lated/__init__.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""lated: photometric emission-line recovery.
|
|
2
|
+
|
|
3
|
+
Fit a transparent continuum + emission-line model through real filter
|
|
4
|
+
transmission curves to recover line fluxes, rest EWs and line ratios
|
|
5
|
+
(with upper limits) from broad/medium-band photometry.
|
|
6
|
+
|
|
7
|
+
>>> from lated import FitConfig, fit
|
|
8
|
+
>>> cfg = FitConfig.three_band(z=3.05) # paper 3-band mode
|
|
9
|
+
>>> res = fit({"F200W": (0.0136, 0.0019),
|
|
10
|
+
... "F277W": (0.0249, 0.0016),
|
|
11
|
+
... "F356W": (0.0040, 0.0014)}, cfg)
|
|
12
|
+
>>> res.ratios[0].name, res.ratios[0].is_limit
|
|
13
|
+
('R3', ...)
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from .config import (ContinuumConfig, DustConfig, FitConfig, LineConfig,
|
|
17
|
+
MCConfig, RatioConfig, default_lines)
|
|
18
|
+
from .engine import FitResult, LineResult, RatioResult, fit, model_spectrum
|
|
19
|
+
from .filters import Filter, FilterSet, default_filters, filter_group
|
|
20
|
+
from .lines import (BALMER_CASES, BALMER_TABLE, CASE_A_TEMPERATURES,
|
|
21
|
+
CASE_B_DENSITIES, CASE_B_TEMPERATURES, LINE_WAVELENGTHS,
|
|
22
|
+
balmer_ratios, case_b_ratios)
|
|
23
|
+
from .presets import WINDOW_PRESETS, WindowPreset
|
|
24
|
+
|
|
25
|
+
__version__ = "0.1.0"
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"ContinuumConfig", "DustConfig", "FitConfig", "LineConfig", "MCConfig",
|
|
29
|
+
"RatioConfig", "default_lines", "FitResult", "LineResult", "RatioResult",
|
|
30
|
+
"fit", "model_spectrum", "Filter", "FilterSet", "default_filters",
|
|
31
|
+
"filter_group", "LINE_WAVELENGTHS", "WINDOW_PRESETS", "WindowPreset",
|
|
32
|
+
"BALMER_CASES", "BALMER_TABLE", "CASE_A_TEMPERATURES",
|
|
33
|
+
"CASE_B_DENSITIES", "CASE_B_TEMPERATURES", "balmer_ratios",
|
|
34
|
+
"case_b_ratios", "__version__",
|
|
35
|
+
]
|
lated/cli.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Command-line interface: serve the web app, or fit from a JSON file.
|
|
2
|
+
|
|
3
|
+
lated serve [--host H] [--port P]
|
|
4
|
+
lated fit input.json # {"photometry": {...}, "config": {...}}
|
|
5
|
+
lated bands # list bundled filters
|
|
6
|
+
lated lines # show the default line registry
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
import sys
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main(argv=None):
|
|
15
|
+
ap = argparse.ArgumentParser(
|
|
16
|
+
prog="lated",
|
|
17
|
+
description=(__doc__ or "LATED line recovery").splitlines()[0])
|
|
18
|
+
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
19
|
+
|
|
20
|
+
ap_serve = sub.add_parser("serve", help="run the web application")
|
|
21
|
+
ap_serve.add_argument("--host", default="127.0.0.1")
|
|
22
|
+
ap_serve.add_argument("--port", type=int, default=8321)
|
|
23
|
+
|
|
24
|
+
ap_fit = sub.add_parser("fit", help="fit photometry from a JSON file")
|
|
25
|
+
ap_fit.add_argument("input", help="JSON with 'photometry' and 'config'")
|
|
26
|
+
|
|
27
|
+
sub.add_parser("bands", help="list available filters")
|
|
28
|
+
sub.add_parser("lines", help="show the default line registry")
|
|
29
|
+
|
|
30
|
+
args = ap.parse_args(argv)
|
|
31
|
+
|
|
32
|
+
if args.cmd == "serve":
|
|
33
|
+
import uvicorn
|
|
34
|
+
from .server.app import create_app
|
|
35
|
+
uvicorn.run(create_app(), host=args.host, port=args.port)
|
|
36
|
+
elif args.cmd == "fit":
|
|
37
|
+
from pydantic import ValidationError
|
|
38
|
+
|
|
39
|
+
from .config import FitConfig
|
|
40
|
+
from .engine import fit
|
|
41
|
+
try:
|
|
42
|
+
with open(args.input) as fh:
|
|
43
|
+
payload = json.load(fh)
|
|
44
|
+
phot_in = payload["photometry"]
|
|
45
|
+
cfg = FitConfig.model_validate(payload["config"])
|
|
46
|
+
phot = {b: (float(v[0]), float(v[1]))
|
|
47
|
+
for b, v in phot_in.items()}
|
|
48
|
+
res = fit(phot, cfg)
|
|
49
|
+
except KeyError as exc:
|
|
50
|
+
sys.exit(f"error: input JSON needs 'photometry' (band -> "
|
|
51
|
+
f"[flux_uJy, err_uJy]) and 'config' (missing {exc})")
|
|
52
|
+
except (ValidationError, ValueError, TypeError, OSError,
|
|
53
|
+
json.JSONDecodeError) as exc:
|
|
54
|
+
sys.exit(f"error: {exc}")
|
|
55
|
+
json.dump(res.model_dump(), sys.stdout, indent=2, allow_nan=False)
|
|
56
|
+
print()
|
|
57
|
+
elif args.cmd == "bands":
|
|
58
|
+
from .filters import default_filters, filter_group
|
|
59
|
+
fs = default_filters()
|
|
60
|
+
for name in fs.names():
|
|
61
|
+
f = fs[name]
|
|
62
|
+
print(f"{name:12s} {filter_group(name):22s} "
|
|
63
|
+
f"pivot {f.pivot:9.1f} AA")
|
|
64
|
+
elif args.cmd == "lines":
|
|
65
|
+
from .config import default_lines
|
|
66
|
+
for l in default_lines():
|
|
67
|
+
tie = (f" = {l.ratio:.4f} x {l.tied_to}" if l.role == "tied" else "")
|
|
68
|
+
print(f"{l.name:12s} {l.rest_wave:9.2f} AA {l.role:5s}{tie}")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
if __name__ == "__main__":
|
|
72
|
+
main()
|
lated/config.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""Fit configuration: every model parameter with an explicit role.
|
|
2
|
+
|
|
3
|
+
The spectral model is
|
|
4
|
+
|
|
5
|
+
f_lambda(lam) = C (lam/lam0)^beta + sum_l F_l delta(lam - lam_l (1+z))
|
|
6
|
+
|
|
7
|
+
and every parameter is declared here as *free*, *fixed* or *tied*:
|
|
8
|
+
|
|
9
|
+
=============================== =========================================
|
|
10
|
+
parameter role
|
|
11
|
+
=============================== =========================================
|
|
12
|
+
C (continuum amplitude) always free (>= 0)
|
|
13
|
+
beta (continuum slope) free (grid-searched) or fixed (with an
|
|
14
|
+
optional Gaussian sigma folded into the
|
|
15
|
+
Monte-Carlo error budget)
|
|
16
|
+
z always fixed (the Lya-slice redshift);
|
|
17
|
+
z_sigma > 0 propagates its uncertainty
|
|
18
|
+
A_V, dust law fixed; enters only as a differential on
|
|
19
|
+
tied line ratios
|
|
20
|
+
F_l (one per line) free (>= 0), tied (ratio x another line),
|
|
21
|
+
or off (excluded from the model)
|
|
22
|
+
=============================== =========================================
|
|
23
|
+
|
|
24
|
+
Unknown keys are rejected (``extra='forbid'``), so a misspelled option is an
|
|
25
|
+
error instead of a silent no-op.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
from typing import List, Literal, Optional
|
|
31
|
+
|
|
32
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
33
|
+
|
|
34
|
+
from .lines import (DEFAULT_REGISTRY, LINE_WAVELENGTHS, balmer_ratios,
|
|
35
|
+
contaminant_ties)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class ContinuumConfig(BaseModel):
|
|
39
|
+
"""The power-law continuum C (lam/lam0)^beta.
|
|
40
|
+
|
|
41
|
+
The amplitude C is always free (non-negative). The slope beta is either
|
|
42
|
+
``free`` (grid-searched over [grid_min, grid_max]) or ``fixed`` at
|
|
43
|
+
``beta`` — required when only the three selection bands are given, where
|
|
44
|
+
the slope is degenerate with the amplitudes. When fixed, ``beta_sigma``
|
|
45
|
+
(default 0.3) marginalises the assumed slope in the Monte-Carlo so the
|
|
46
|
+
systematic enters the error budget."""
|
|
47
|
+
model_config = ConfigDict(extra="forbid")
|
|
48
|
+
|
|
49
|
+
beta_mode: Literal["free", "fixed"] = "free"
|
|
50
|
+
beta: float = -2.0
|
|
51
|
+
beta_sigma: Optional[float] = Field(
|
|
52
|
+
None, ge=0, description="None -> 0.3 if fixed, 0.0 if free")
|
|
53
|
+
grid_min: float = -3.5
|
|
54
|
+
grid_max: float = 1.0
|
|
55
|
+
grid_n: int = Field(61, ge=2, le=1001)
|
|
56
|
+
pivot_wavelength: float = Field(20000.0, gt=0,
|
|
57
|
+
description="lam0 [AA, observed]")
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def beta_sigma_effective(self) -> float:
|
|
61
|
+
if self.beta_sigma is not None:
|
|
62
|
+
return self.beta_sigma
|
|
63
|
+
return 0.3 if self.beta_mode == "fixed" else 0.0
|
|
64
|
+
|
|
65
|
+
@model_validator(mode="after")
|
|
66
|
+
def _check_grid(self) -> "ContinuumConfig":
|
|
67
|
+
if self.beta_mode == "free" and self.grid_min >= self.grid_max:
|
|
68
|
+
raise ValueError("continuum grid_min must be < grid_max")
|
|
69
|
+
if self.beta_mode == "free" and self.beta_sigma:
|
|
70
|
+
raise ValueError(
|
|
71
|
+
"beta_sigma is only meaningful with beta_mode='fixed' (it is "
|
|
72
|
+
"the Gaussian prior on the assumed slope); with beta_mode="
|
|
73
|
+
"'free' the slope is profiled over the grid instead")
|
|
74
|
+
return self
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class LineConfig(BaseModel):
|
|
78
|
+
"""One emission line in the model.
|
|
79
|
+
|
|
80
|
+
role='free': amplitude F >= 0 is a fit parameter.
|
|
81
|
+
role='tied': F = ratio x F(tied_to) x dust differential; tie chains
|
|
82
|
+
(e.g. Hgamma -> Hbeta -> Halpha) resolve to a free root.
|
|
83
|
+
role='off': excluded from the model entirely.
|
|
84
|
+
"""
|
|
85
|
+
model_config = ConfigDict(extra="forbid")
|
|
86
|
+
|
|
87
|
+
name: str
|
|
88
|
+
rest_wave: float = Field(..., gt=0, description="rest wavelength [AA]")
|
|
89
|
+
role: Literal["free", "tied", "off"] = "free"
|
|
90
|
+
tied_to: Optional[str] = None
|
|
91
|
+
ratio: Optional[float] = Field(None, gt=0,
|
|
92
|
+
description="F_this / F_tied_to, intrinsic")
|
|
93
|
+
|
|
94
|
+
@model_validator(mode="after")
|
|
95
|
+
def _check_tie(self) -> "LineConfig":
|
|
96
|
+
if self.role == "tied":
|
|
97
|
+
if not self.tied_to or self.ratio is None:
|
|
98
|
+
raise ValueError(
|
|
99
|
+
f"line {self.name!r}: role='tied' requires tied_to and ratio")
|
|
100
|
+
return self
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class DustConfig(BaseModel):
|
|
104
|
+
"""Attenuation applied differentially to tied line ratios.
|
|
105
|
+
|
|
106
|
+
Like the fixed continuum slope, A_V carries a Gaussian uncertainty
|
|
107
|
+
``av_sigma`` (default 0 +/- 0.01) that is propagated through the
|
|
108
|
+
Monte-Carlo error budget (draws are truncated at A_V >= 0)."""
|
|
109
|
+
model_config = ConfigDict(extra="forbid")
|
|
110
|
+
|
|
111
|
+
av: float = Field(0.0, ge=0)
|
|
112
|
+
av_sigma: float = Field(0.01, ge=0)
|
|
113
|
+
law: Literal["calzetti", "smc"] = "calzetti"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class MCConfig(BaseModel):
|
|
117
|
+
"""Monte-Carlo error budget.
|
|
118
|
+
|
|
119
|
+
method='bootstrap' (paper default): resample the photometry ``n`` times
|
|
120
|
+
and refit; read percentiles of the refits. Near the non-negativity
|
|
121
|
+
boundary these can collapse to zero (an undetected line whose band
|
|
122
|
+
fluctuated low returns an upper limit of exactly 0), so for honest
|
|
123
|
+
limits prefer method='posterior': sample the truncated-Gaussian
|
|
124
|
+
posterior of the amplitudes (flat prior on F >= 0) with the slope
|
|
125
|
+
drawn from its profile weight (free) or Gaussian prior (fixed).
|
|
126
|
+
|
|
127
|
+
``fast`` (bootstrap only) pins the slope at the best fit instead of
|
|
128
|
+
re-searching the grid per resample. ``seed`` fixes the random stream;
|
|
129
|
+
vary it across a catalogue if you stack results (a shared seed
|
|
130
|
+
correlates the noise realisations)."""
|
|
131
|
+
model_config = ConfigDict(extra="forbid")
|
|
132
|
+
|
|
133
|
+
n: int = Field(300, ge=10, le=10000)
|
|
134
|
+
seed: int = 9496
|
|
135
|
+
fast: bool = False
|
|
136
|
+
method: Literal["bootstrap", "posterior"] = "bootstrap"
|
|
137
|
+
keep_draws: bool = Field(
|
|
138
|
+
False, description="return the (thinned) MC draws of the free "
|
|
139
|
+
"parameters in FitResult.posterior_draws, e.g. for corner plots")
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
class RatioConfig(BaseModel):
|
|
143
|
+
"""A derived line ratio to report, with upper-limit handling."""
|
|
144
|
+
model_config = ConfigDict(extra="forbid")
|
|
145
|
+
|
|
146
|
+
name: str = "R3"
|
|
147
|
+
numerator: str = "OIII5007"
|
|
148
|
+
denominator: str = "Hbeta"
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def default_lines() -> List[LineConfig]:
|
|
152
|
+
"""The paper's default registry (see :mod:`lated.lines`)."""
|
|
153
|
+
return [LineConfig(name=n, rest_wave=LINE_WAVELENGTHS[n], role=role,
|
|
154
|
+
tied_to=tied_to, ratio=ratio)
|
|
155
|
+
for n, role, tied_to, ratio in DEFAULT_REGISTRY]
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class FitConfig(BaseModel):
|
|
159
|
+
"""Complete, explicit specification of one fit."""
|
|
160
|
+
model_config = ConfigDict(extra="forbid")
|
|
161
|
+
|
|
162
|
+
z: float = Field(..., gt=0, description="Lya-slice redshift (fixed)")
|
|
163
|
+
z_sigma: float = Field(0.0, ge=0)
|
|
164
|
+
continuum: ContinuumConfig = Field(default_factory=ContinuumConfig)
|
|
165
|
+
lines: List[LineConfig] = Field(default_factory=default_lines)
|
|
166
|
+
dust: DustConfig = Field(default_factory=DustConfig)
|
|
167
|
+
mc: MCConfig = Field(default_factory=MCConfig)
|
|
168
|
+
ratios: List[RatioConfig] = Field(default_factory=lambda: [RatioConfig()])
|
|
169
|
+
|
|
170
|
+
@model_validator(mode="after")
|
|
171
|
+
def _check_lines(self) -> "FitConfig":
|
|
172
|
+
names = [l.name for l in self.lines]
|
|
173
|
+
if len(set(names)) != len(names):
|
|
174
|
+
dupes = sorted({n for n in names if names.count(n) > 1})
|
|
175
|
+
raise ValueError(f"duplicate line names: {dupes}")
|
|
176
|
+
enabled = {l.name: l for l in self.lines if l.role != "off"}
|
|
177
|
+
if not any(l.role == "free" for l in enabled.values()):
|
|
178
|
+
raise ValueError("at least one line must have role='free'")
|
|
179
|
+
for l in enabled.values():
|
|
180
|
+
if l.role != "tied":
|
|
181
|
+
continue
|
|
182
|
+
seen, cur = {l.name}, l
|
|
183
|
+
while cur.role == "tied":
|
|
184
|
+
target = enabled.get(cur.tied_to)
|
|
185
|
+
if target is None:
|
|
186
|
+
raise ValueError(
|
|
187
|
+
f"line {cur.name!r} is tied to {cur.tied_to!r}, which "
|
|
188
|
+
"is off or undefined")
|
|
189
|
+
if target.name in seen:
|
|
190
|
+
raise ValueError(f"tie cycle involving {target.name!r}")
|
|
191
|
+
seen.add(target.name)
|
|
192
|
+
cur = target
|
|
193
|
+
for r in self.ratios:
|
|
194
|
+
for side in (r.numerator, r.denominator):
|
|
195
|
+
if side not in enabled:
|
|
196
|
+
raise ValueError(
|
|
197
|
+
f"ratio {r.name!r} references {side!r}, which is not "
|
|
198
|
+
"an enabled line")
|
|
199
|
+
return self
|
|
200
|
+
|
|
201
|
+
# ---- convenience constructors matching the paper's two modes ----------
|
|
202
|
+
@classmethod
|
|
203
|
+
def paper_default(cls, z: float, **kwargs) -> "FitConfig":
|
|
204
|
+
"""Multi-band mode: slope fitted, Halpha + [O III] free, Case B ties."""
|
|
205
|
+
return cls(z=z, **kwargs)
|
|
206
|
+
|
|
207
|
+
@classmethod
|
|
208
|
+
def three_band(cls, z: float, beta: float = -2.0, **kwargs) -> "FitConfig":
|
|
209
|
+
"""Selection-band mode: slope fixed (default beta = -2, the metal-poor
|
|
210
|
+
expectation) with the 0.3 slope prior in the error budget."""
|
|
211
|
+
return cls(z=z, continuum=ContinuumConfig(beta_mode="fixed", beta=beta),
|
|
212
|
+
**kwargs)
|
|
213
|
+
|
|
214
|
+
def with_line_role(self, name: str, role: str,
|
|
215
|
+
tied_to: Optional[str] = None,
|
|
216
|
+
ratio: Optional[float] = None) -> "FitConfig":
|
|
217
|
+
"""Copy of this config with one line's role changed. The copy is
|
|
218
|
+
fully re-validated, so e.g. switching off a line that others are
|
|
219
|
+
tied to raises immediately rather than failing inside fit()."""
|
|
220
|
+
if name not in {l.name for l in self.lines}:
|
|
221
|
+
raise KeyError(f"no line named {name!r}")
|
|
222
|
+
data = self.model_dump()
|
|
223
|
+
for l in data["lines"]:
|
|
224
|
+
if l["name"] == name:
|
|
225
|
+
l["role"] = role
|
|
226
|
+
l["tied_to"] = tied_to if role == "tied" else None
|
|
227
|
+
l["ratio"] = ratio if role == "tied" else None
|
|
228
|
+
return FitConfig.model_validate(data)
|
|
229
|
+
|
|
230
|
+
def with_contaminant_mode(self, frac: float = 0.35) -> "FitConfig":
|
|
231
|
+
"""Tie [N II]+[S II] to Halpha (metal-rich contaminant mode)."""
|
|
232
|
+
cfg = self
|
|
233
|
+
for name, ratio in contaminant_ties(frac).items():
|
|
234
|
+
cfg = cfg.with_line_role(name, "tied", tied_to="Halpha", ratio=ratio)
|
|
235
|
+
return cfg
|
|
236
|
+
|
|
237
|
+
def with_balmer(self, case: str = "B", te: float = 10000.0,
|
|
238
|
+
ne: float = 100.0) -> "FitConfig":
|
|
239
|
+
"""Copy with the Balmer tie ratios set for the given recombination
|
|
240
|
+
case at (T_e, n_e) (see :func:`lated.lines.balmer_ratios`).
|
|
241
|
+
Only lines that are currently tied to their default parent
|
|
242
|
+
(Hbeta -> Halpha, higher orders -> Hbeta) are rewritten; free/off
|
|
243
|
+
lines and custom ties are left alone. Case B at
|
|
244
|
+
(1e4 K, 1e2 cm^-3) reproduces the paper defaults."""
|
|
245
|
+
ratios = balmer_ratios(case, te, ne)
|
|
246
|
+
parent = {"Hbeta": "Halpha"}
|
|
247
|
+
data = self.model_dump()
|
|
248
|
+
for l in data["lines"]:
|
|
249
|
+
r = ratios.get(l["name"])
|
|
250
|
+
if (r is not None and l["role"] == "tied"
|
|
251
|
+
and l["tied_to"] == parent.get(l["name"], "Hbeta")):
|
|
252
|
+
l["ratio"] = r
|
|
253
|
+
return FitConfig.model_validate(data)
|
|
254
|
+
|
|
255
|
+
def with_case_b(self, te: float = 10000.0,
|
|
256
|
+
ne: float = 100.0) -> "FitConfig":
|
|
257
|
+
"""Backwards-compatible alias for :meth:`with_balmer` (case='B')."""
|
|
258
|
+
return self.with_balmer("B", te, ne)
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
6031.5 0.000309961
|
|
2
|
+
6041.5 0.00123754
|
|
3
|
+
6051.5 0.00409764
|
|
4
|
+
6061.5 0.010342
|
|
5
|
+
6071.5 0.0207281
|
|
6
|
+
6081.5 0.0345091
|
|
7
|
+
6091.5 0.0494549
|
|
8
|
+
6101.5 0.0632327
|
|
9
|
+
6111.5 0.0743233
|
|
10
|
+
6121.5 0.0825961
|
|
11
|
+
6131.5 0.0884712
|
|
12
|
+
6141.5 0.0931323
|
|
13
|
+
6151.5 0.097091
|
|
14
|
+
6161.5 0.101308
|
|
15
|
+
6171.5 0.105977
|
|
16
|
+
6181.5 0.110771
|
|
17
|
+
6191.5 0.115565
|
|
18
|
+
6201.5 0.119845
|
|
19
|
+
6211.5 0.123279
|
|
20
|
+
6221.5 0.126114
|
|
21
|
+
6231.5 0.128232
|
|
22
|
+
6241.5 0.130247
|
|
23
|
+
6251.5 0.13192
|
|
24
|
+
6261.5 0.133178
|
|
25
|
+
6271.5 0.134178
|
|
26
|
+
6281.5 0.134887
|
|
27
|
+
6291.5 0.135268
|
|
28
|
+
6301.5 0.135521
|
|
29
|
+
6311.5 0.135661
|
|
30
|
+
6321.5 0.135902
|
|
31
|
+
6331.5 0.137156
|
|
32
|
+
6341.5 0.139277
|
|
33
|
+
6351.5 0.14183
|
|
34
|
+
6361.5 0.144646
|
|
35
|
+
6371.5 0.14706
|
|
36
|
+
6381.5 0.149009
|
|
37
|
+
6391.5 0.150766
|
|
38
|
+
6401.5 0.152204
|
|
39
|
+
6411.5 0.153924
|
|
40
|
+
6421.5 0.155673
|
|
41
|
+
6431.5 0.157162
|
|
42
|
+
6441.5 0.158391
|
|
43
|
+
6451.5 0.159284
|
|
44
|
+
6461.5 0.159984
|
|
45
|
+
6471.5 0.160807
|
|
46
|
+
6481.5 0.161983
|
|
47
|
+
6491.5 0.16336
|
|
48
|
+
6501.5 0.165113
|
|
49
|
+
6511.5 0.166385
|
|
50
|
+
6521.5 0.167236
|
|
51
|
+
6531.5 0.167801
|
|
52
|
+
6541.5 0.168033
|
|
53
|
+
6551.5 0.167927
|
|
54
|
+
6561.5 0.167929
|
|
55
|
+
6571.5 0.167631
|
|
56
|
+
6581.5 0.167511
|
|
57
|
+
6591.5 0.167843
|
|
58
|
+
6601.5 0.168728
|
|
59
|
+
6611.5 0.169699
|
|
60
|
+
6621.5 0.1713
|
|
61
|
+
6631.5 0.173464
|
|
62
|
+
6641.5 0.176055
|
|
63
|
+
6651.5 0.178398
|
|
64
|
+
6661.5 0.180203
|
|
65
|
+
6671.5 0.18098
|
|
66
|
+
6681.5 0.181266
|
|
67
|
+
6691.5 0.181391
|
|
68
|
+
6701.5 0.181189
|
|
69
|
+
6711.5 0.180787
|
|
70
|
+
6721.5 0.180361
|
|
71
|
+
6731.5 0.180272
|
|
72
|
+
6741.5 0.180103
|
|
73
|
+
6751.5 0.179942
|
|
74
|
+
6761.5 0.180319
|
|
75
|
+
6771.5 0.181147
|
|
76
|
+
6781.5 0.182099
|
|
77
|
+
6791.5 0.183778
|
|
78
|
+
6801.5 0.18605
|
|
79
|
+
6811.5 0.188199
|
|
80
|
+
6821.5 0.189765
|
|
81
|
+
6831.5 0.190714
|
|
82
|
+
6841.5 0.191931
|
|
83
|
+
6851.5 0.192864
|
|
84
|
+
6861.5 0.194002
|
|
85
|
+
6871.5 0.194906
|
|
86
|
+
6881.5 0.196068
|
|
87
|
+
6891.5 0.197647
|
|
88
|
+
6901.5 0.198913
|
|
89
|
+
6911.5 0.199266
|
|
90
|
+
6921.5 0.198333
|
|
91
|
+
6931.5 0.196206
|
|
92
|
+
6941.5 0.193351
|
|
93
|
+
6951.5 0.190192
|
|
94
|
+
6961.5 0.187901
|
|
95
|
+
6971.5 0.187081
|
|
96
|
+
6981.5 0.187895
|
|
97
|
+
6991.5 0.190337
|
|
98
|
+
7001.5 0.193836
|
|
99
|
+
7011.5 0.197938
|
|
100
|
+
7021.5 0.200988
|
|
101
|
+
7031.5 0.203635
|
|
102
|
+
7041.5 0.205375
|
|
103
|
+
7051.5 0.206029
|
|
104
|
+
7061.5 0.206845
|
|
105
|
+
7071.5 0.207391
|
|
106
|
+
7081.5 0.20855
|
|
107
|
+
7091.5 0.209964
|
|
108
|
+
7101.5 0.210253
|
|
109
|
+
7111.5 0.209846
|
|
110
|
+
7121.5 0.208012
|
|
111
|
+
7131.5 0.205859
|
|
112
|
+
7141.5 0.204222
|
|
113
|
+
7151.5 0.203804
|
|
114
|
+
7161.5 0.204045
|
|
115
|
+
7171.5 0.204292
|
|
116
|
+
7181.5 0.203746
|
|
117
|
+
7191.5 0.202009
|
|
118
|
+
7201.5 0.19976
|
|
119
|
+
7211.5 0.19814
|
|
120
|
+
7221.5 0.198677
|
|
121
|
+
7231.5 0.20182
|
|
122
|
+
7241.5 0.206824
|
|
123
|
+
7251.5 0.212809
|
|
124
|
+
7261.5 0.218645
|
|
125
|
+
7271.5 0.223363
|
|
126
|
+
7281.5 0.226589
|
|
127
|
+
7291.5 0.228757
|
|
128
|
+
7301.5 0.229885
|
|
129
|
+
7311.5 0.229396
|
|
130
|
+
7321.5 0.228432
|
|
131
|
+
7331.5 0.22666
|
|
132
|
+
7341.5 0.224067
|
|
133
|
+
7351.5 0.221453
|
|
134
|
+
7361.5 0.219258
|
|
135
|
+
7371.5 0.217786
|
|
136
|
+
7381.5 0.217562
|
|
137
|
+
7391.5 0.218243
|
|
138
|
+
7401.5 0.219014
|
|
139
|
+
7411.5 0.219954
|
|
140
|
+
7421.5 0.220665
|
|
141
|
+
7431.5 0.222086
|
|
142
|
+
7441.5 0.224779
|
|
143
|
+
7451.5 0.228542
|
|
144
|
+
7461.5 0.232279
|
|
145
|
+
7471.5 0.236426
|
|
146
|
+
7481.5 0.239395
|
|
147
|
+
7491.5 0.241841
|
|
148
|
+
7501.5 0.243029
|
|
149
|
+
7511.5 0.243551
|
|
150
|
+
7521.5 0.24347
|
|
151
|
+
7531.5 0.241723
|
|
152
|
+
7541.5 0.239053
|
|
153
|
+
7551.5 0.234895
|
|
154
|
+
7561.5 0.230498
|
|
155
|
+
7571.5 0.226996
|
|
156
|
+
7581.5 0.225189
|
|
157
|
+
7591.5 0.224385
|
|
158
|
+
7601.5 0.224738
|
|
159
|
+
7611.5 0.225587
|
|
160
|
+
7621.5 0.226597
|
|
161
|
+
7631.5 0.228928
|
|
162
|
+
7641.5 0.232252
|
|
163
|
+
7651.5 0.236955
|
|
164
|
+
7661.5 0.241852
|
|
165
|
+
7671.5 0.24604
|
|
166
|
+
7681.5 0.247924
|
|
167
|
+
7691.5 0.248015
|
|
168
|
+
7701.5 0.24673
|
|
169
|
+
7711.5 0.244373
|
|
170
|
+
7721.5 0.241802
|
|
171
|
+
7731.5 0.238095
|
|
172
|
+
7741.5 0.233777
|
|
173
|
+
7751.5 0.22837
|
|
174
|
+
7761.5 0.222046
|
|
175
|
+
7771.5 0.213078
|
|
176
|
+
7781.5 0.199583
|
|
177
|
+
7791.5 0.180581
|
|
178
|
+
7801.5 0.156823
|
|
179
|
+
7811.5 0.130334
|
|
180
|
+
7821.5 0.103585
|
|
181
|
+
7831.5 0.0788306
|
|
182
|
+
7841.5 0.0576372
|
|
183
|
+
7851.5 0.0406466
|
|
184
|
+
7861.5 0.0277262
|
|
185
|
+
7871.5 0.018528
|
|
186
|
+
7881.5 0.012305
|
|
187
|
+
7891.5 0.00826874
|
|
188
|
+
7901.5 0.00570614
|
|
189
|
+
7911.5 0.00400084
|
|
190
|
+
7921.5 0.00286948
|
|
191
|
+
7931.5 0.00203922
|
|
192
|
+
7941.5 0.00146461
|
|
193
|
+
7951.5 0.00106776
|
|
194
|
+
7961.5 0.000784746
|
|
195
|
+
7971.5 0.000581088
|
|
196
|
+
7981.5 0.000432432
|
|
197
|
+
7991.5 0.000324602
|
|
198
|
+
8001.5 0.000246103
|
|
199
|
+
8011.5 0.000187665
|
|
200
|
+
8021.5 0.000143742
|
|
201
|
+
8031.5 0.000111208
|