pymolfit 0.1.0__tar.gz
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.
- pymolfit-0.1.0/PKG-INFO +753 -0
- pymolfit-0.1.0/README.md +718 -0
- pymolfit-0.1.0/THIRD_PARTY_NOTICES.md +40 -0
- pymolfit-0.1.0/pyproject.toml +74 -0
- pymolfit-0.1.0/setup.cfg +4 -0
- pymolfit-0.1.0/src/pymolfit/__init__.py +241 -0
- pymolfit-0.1.0/src/pymolfit/_version.py +1 -0
- pymolfit-0.1.0/src/pymolfit/aer_data.py +928 -0
- pymolfit-0.1.0/src/pymolfit/atmosphere.py +1987 -0
- pymolfit-0.1.0/src/pymolfit/cli.py +750 -0
- pymolfit-0.1.0/src/pymolfit/components.py +1915 -0
- pymolfit-0.1.0/src/pymolfit/continuum.py +1664 -0
- pymolfit-0.1.0/src/pymolfit/data/lblrtm_v12_11_co2_continuum.npz +0 -0
- pymolfit-0.1.0/src/pymolfit/data/lblrtm_v12_11_h2o_continuum.npz +0 -0
- pymolfit-0.1.0/src/pymolfit/data/lblrtm_v12_11_n2_fundamental.npz +0 -0
- pymolfit-0.1.0/src/pymolfit/data/lblrtm_v12_11_o2_continuum.npz +0 -0
- pymolfit-0.1.0/src/pymolfit/data/lblrtm_v12_11_tips.npz +0 -0
- pymolfit-0.1.0/src/pymolfit/data/profiles/gdas/GDAS_t0_s1.fits +0 -0
- pymolfit-0.1.0/src/pymolfit/data/profiles/gdas/GDAS_t0_s2.fits +0 -0
- pymolfit-0.1.0/src/pymolfit/data/profiles/gdas/GDAS_t0_s3.fits +0 -0
- pymolfit-0.1.0/src/pymolfit/data/profiles/gdas/GDAS_t0_s4.fits +0 -0
- pymolfit-0.1.0/src/pymolfit/data/profiles/gdas/GDAS_t0_s5.fits +0 -0
- pymolfit-0.1.0/src/pymolfit/data/profiles/gdas/GDAS_t0_s6.fits +0 -0
- pymolfit-0.1.0/src/pymolfit/data/profiles/mipas/equ.fits +57 -0
- pymolfit-0.1.0/src/pymolfit/data/profiles/mipas/std.fits +2 -1
- pymolfit-0.1.0/src/pymolfit/data/profiles/mipas/tro.fits +8 -0
- pymolfit-0.1.0/src/pymolfit/diagnostics.py +66 -0
- pymolfit-0.1.0/src/pymolfit/fit.py +2396 -0
- pymolfit-0.1.0/src/pymolfit/gdas.py +297 -0
- pymolfit-0.1.0/src/pymolfit/hitran.py +629 -0
- pymolfit-0.1.0/src/pymolfit/io.py +294 -0
- pymolfit-0.1.0/src/pymolfit/line_data.py +561 -0
- pymolfit-0.1.0/src/pymolfit/linelist.py +575 -0
- pymolfit-0.1.0/src/pymolfit/model.py +1141 -0
- pymolfit-0.1.0/src/pymolfit/partition.py +457 -0
- pymolfit-0.1.0/src/pymolfit/physics.py +1499 -0
- pymolfit-0.1.0/src/pymolfit/plotting.py +44 -0
- pymolfit-0.1.0/src/pymolfit/provenance.py +163 -0
- pymolfit-0.1.0/src/pymolfit/radiative_transfer.py +250 -0
- pymolfit-0.1.0/src/pymolfit/spectrum.py +249 -0
- pymolfit-0.1.0/src/pymolfit/systematics.py +413 -0
- pymolfit-0.1.0/src/pymolfit/validation.py +490 -0
- pymolfit-0.1.0/src/pymolfit/workflow.py +1391 -0
- pymolfit-0.1.0/src/pymolfit.egg-info/PKG-INFO +753 -0
- pymolfit-0.1.0/src/pymolfit.egg-info/SOURCES.txt +69 -0
- pymolfit-0.1.0/src/pymolfit.egg-info/dependency_links.txt +1 -0
- pymolfit-0.1.0/src/pymolfit.egg-info/entry_points.txt +2 -0
- pymolfit-0.1.0/src/pymolfit.egg-info/requires.txt +12 -0
- pymolfit-0.1.0/src/pymolfit.egg-info/top_level.txt +1 -0
- pymolfit-0.1.0/tests/test_aer_data.py +283 -0
- pymolfit-0.1.0/tests/test_atmosphere.py +467 -0
- pymolfit-0.1.0/tests/test_cli.py +237 -0
- pymolfit-0.1.0/tests/test_components.py +158 -0
- pymolfit-0.1.0/tests/test_continuum.py +436 -0
- pymolfit-0.1.0/tests/test_external_absorption.py +303 -0
- pymolfit-0.1.0/tests/test_fit.py +208 -0
- pymolfit-0.1.0/tests/test_fit_controls.py +499 -0
- pymolfit-0.1.0/tests/test_gdas.py +71 -0
- pymolfit-0.1.0/tests/test_io.py +167 -0
- pymolfit-0.1.0/tests/test_lblrtm_audit.py +628 -0
- pymolfit-0.1.0/tests/test_line_data.py +174 -0
- pymolfit-0.1.0/tests/test_model.py +386 -0
- pymolfit-0.1.0/tests/test_molecfit_reference_data.py +35 -0
- pymolfit-0.1.0/tests/test_physics_hitran.py +1047 -0
- pymolfit-0.1.0/tests/test_physics_parity.py +445 -0
- pymolfit-0.1.0/tests/test_release_tools.py +142 -0
- pymolfit-0.1.0/tests/test_spectrum.py +38 -0
- pymolfit-0.1.0/tests/test_systematics.py +122 -0
- pymolfit-0.1.0/tests/test_validation.py +137 -0
- pymolfit-0.1.0/tests/test_validation_review.py +198 -0
- pymolfit-0.1.0/tests/test_workflow.py +399 -0
pymolfit-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,753 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pymolfit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Generic pure-Python telluric fitting and correction with source-audited radiative-transfer physics.
|
|
5
|
+
Author: Nikolas Valsamidis
|
|
6
|
+
License: Scientific/research use; see THIRD_PARTY_NOTICES.md for bundled AER data terms
|
|
7
|
+
Project-URL: Homepage, https://github.com/nikolasvalsamidis97/PyMolFit
|
|
8
|
+
Project-URL: Issues, https://github.com/nikolasvalsamidis97/PyMolFit/issues
|
|
9
|
+
Project-URL: Repository, https://github.com/nikolasvalsamidis97/PyMolFit.git
|
|
10
|
+
Keywords: astronomy,radiative transfer,spectroscopy,telluric correction
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: THIRD_PARTY_NOTICES.md
|
|
25
|
+
Requires-Dist: numpy>=1.23
|
|
26
|
+
Requires-Dist: scipy>=1.9
|
|
27
|
+
Requires-Dist: astropy>=5.0
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
30
|
+
Provides-Extra: examples
|
|
31
|
+
Requires-Dist: matplotlib>=3.7; extra == "examples"
|
|
32
|
+
Provides-Extra: plot
|
|
33
|
+
Requires-Dist: matplotlib>=3.7; extra == "plot"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# PyMolFit
|
|
37
|
+
|
|
38
|
+
PyMolFit is a pure-Python generic telluric correction package inspired by ESO
|
|
39
|
+
Molecfit. The goal is to fit and remove telluric absorption from spectra
|
|
40
|
+
without requiring the user to know about ESO recipe files, EsoRex-style
|
|
41
|
+
workflow files, shell paths, or compiled C/Fortran executables.
|
|
42
|
+
|
|
43
|
+
The current release is a science-ready candidate pending independent blind
|
|
44
|
+
review; it is not claimed to be a bit-identical drop-in replacement for
|
|
45
|
+
Molecfit/LBLRTM. Automated validation covers synthetic recovery, uncertainty
|
|
46
|
+
coverage, failure behavior, and real HARPS, UVES, X-shooter, and CRIRES+
|
|
47
|
+
spectra, plus independent Keck/HIRES and Keck/KPF spectra.
|
|
48
|
+
The current version implements generic spectrum input/output, a vectorized
|
|
49
|
+
molecular-line absorption model, a self-contained HITRAN `.par` reader,
|
|
50
|
+
HITRAN/TIPS partition-sum tables, HITRAN isotopologue metadata and abundance
|
|
51
|
+
scaling, source-backed LBLRTM H2O, CO2, N2, and O2 continua, optional external
|
|
52
|
+
HITRAN CIA tables, LBLRTM `contnm.f90` Rayleigh scattering, layer-by-layer
|
|
53
|
+
radiative transfer, Molecfit-inspired line-window and LSF controls, fitting,
|
|
54
|
+
correction, diagnostics, and comparison tools. Scientific workflows use the
|
|
55
|
+
official versioned AER 3.9 catalogue. PyMolFit downloads it from its immutable
|
|
56
|
+
Zenodo release, verifies it, and caches it automatically;
|
|
57
|
+
the small built-in synthetic list is retained only for tests and examples.
|
|
58
|
+
|
|
59
|
+
## Why This Shape
|
|
60
|
+
|
|
61
|
+
Molecfit is built from several responsibilities:
|
|
62
|
+
|
|
63
|
+
- read spectra and wavelength/fit ranges
|
|
64
|
+
- create an atmospheric transmission model from molecular line data and an
|
|
65
|
+
atmospheric profile
|
|
66
|
+
- convolve the model with the instrumental profile
|
|
67
|
+
- fit continuum, wavelength/resolution adjustments, and molecule abundances
|
|
68
|
+
- divide the observed spectrum by the fitted telluric transmission
|
|
69
|
+
|
|
70
|
+
PyMolFit keeps the same conceptual split, but implements the first usable
|
|
71
|
+
layer in Python:
|
|
72
|
+
|
|
73
|
+
- `Spectrum`: generic 1D spectrum container
|
|
74
|
+
- `LineList`: molecular line centers, strengths, widths, and species labels
|
|
75
|
+
- `transmission_model`: vectorized optical-depth and transmission model
|
|
76
|
+
- `AbsorptionComponent`: plugin-style interface for physical opacity sources
|
|
77
|
+
- `HitranLineAbsorption`: HITRAN line-by-line component
|
|
78
|
+
- `IsotopologueMetadata`: HITRAN global/local isotopologue IDs, abundances,
|
|
79
|
+
masses, and q-file names
|
|
80
|
+
- `PartitionTable`: tabulated HITRAN/TIPS partition sums
|
|
81
|
+
- `physical_transmission_model`: self-contained HITRAN line-by-line model
|
|
82
|
+
- `MTCKDH2OContinuum`: vectorized MT_CKD H2O self/foreign continuum model
|
|
83
|
+
- `H2OContinuumAbsorption`: MT_CKD H2O continuum component
|
|
84
|
+
- `CO2ContinuumAbsorption`: CO2 continuum component from external tables
|
|
85
|
+
- `O2CIAAbsorption` / `N2CIAAbsorption`: HITRAN CIA-file components
|
|
86
|
+
- `RayleighScatteringAbsorption`: LBLRTM `contnm.f90` Rayleigh component
|
|
87
|
+
- `N2RototranslationalContinuumAbsorption`: LBLRTM N2 pure-rotation continuum
|
|
88
|
+
- `fit_tellurics`: nonlinear least-squares fit of molecule scales and continuum
|
|
89
|
+
- `correct_spectrum`: divides flux by the fitted transmission
|
|
90
|
+
|
|
91
|
+
## Install For Development
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
python -m pip install -e ".[dev]"
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
For diagnostic plots:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
python -m pip install -e ".[dev,plot]"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
For a normal release install, the intended interface is simply:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
python -m pip install pymolfit
|
|
107
|
+
pymolfit fit spectrum.fits corrected.ecsv
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The wheel does not contain the 759 MiB uncompressed AER catalogue. On first
|
|
111
|
+
scientific use PyMolFit downloads the official AER 3.9 archive from
|
|
112
|
+
[Zenodo record 18881607](https://doi.org/10.5281/zenodo.18881607), extracts its
|
|
113
|
+
line catalogue, broadening tables, and line-coupling table, verifies fixed
|
|
114
|
+
SHA-256 hashes, and stores them under `~/.cache/pymolfit/aer`. No HITRAN
|
|
115
|
+
account or API key is needed.
|
|
116
|
+
The data can be installed ahead of time and inspected explicitly:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
pymolfit install-aer
|
|
120
|
+
pymolfit aer-status
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Set `PYMOLFIT_AER_URL` only to use another location containing the exact
|
|
124
|
+
`aer_v_3.9.tgz` artifact. The extracted scientific payload must still match
|
|
125
|
+
the hashes pinned from the official Zenodo release.
|
|
126
|
+
|
|
127
|
+
## Python Example
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
from pymolfit import LineList, Spectrum, fit_tellurics
|
|
131
|
+
|
|
132
|
+
spectrum = Spectrum(wavelength=wavelength_micron, flux=flux)
|
|
133
|
+
result = fit_tellurics(spectrum, line_list=LineList.demo_near_ir())
|
|
134
|
+
|
|
135
|
+
corrected = result.corrected
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
For normal scripting, the higher-level workflow is shorter:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from pymolfit import correct_file
|
|
142
|
+
|
|
143
|
+
result = correct_file(
|
|
144
|
+
"spectrum.fits",
|
|
145
|
+
"corrected.txt",
|
|
146
|
+
wavelength_unit="nm",
|
|
147
|
+
hitran_species=("H2O", "CO2", "CH4"),
|
|
148
|
+
atmosphere_mode="mipas_gdas",
|
|
149
|
+
# alternatively: atmosphere_table="profile.ecsv",
|
|
150
|
+
pwv_mm=2.0,
|
|
151
|
+
solve_continuum_linear=True,
|
|
152
|
+
fit_wavelength_polynomial=True,
|
|
153
|
+
wavelength_polynomial_order=1,
|
|
154
|
+
fit_lsf_sigma=True,
|
|
155
|
+
estimate_uncertainties=True,
|
|
156
|
+
product_path="fit_product.ecsv",
|
|
157
|
+
)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
With no `line_list`, `line_list_path`, or `hitran_par`, this workflow selects
|
|
161
|
+
the required wavelength window from the managed AER catalogue and caches the
|
|
162
|
+
result as a provenance-bearing ECSV table. Pass `aer_catalog=None` (Python) or
|
|
163
|
+
`--no-auto-aer` (CLI) only when intentionally running a continuum-only model
|
|
164
|
+
or supplying another opacity source.
|
|
165
|
+
|
|
166
|
+
`solve_continuum_linear=True` removes the linear continuum coefficients from
|
|
167
|
+
the nonlinear optimizer and is recommended for spectra with large absolute
|
|
168
|
+
flux values. With `estimate_uncertainties=True`, the result includes a
|
|
169
|
+
linearized parameter covariance, molecular-scale standard errors,
|
|
170
|
+
transmission uncertainty, and corrected-flux uncertainty. These uncertainties
|
|
171
|
+
are conditional on the supplied atmosphere, line data, masks, continuum
|
|
172
|
+
family, and LSF model. Use `fit_tellurics_with_systematics` to refit named
|
|
173
|
+
alternative model configurations and add their transmission spread to the
|
|
174
|
+
corrected-flux uncertainty; no universal atmosphere/LSF ensemble is assumed.
|
|
175
|
+
Shared multi-segment fits propagate the shared covariance into every corrected
|
|
176
|
+
segment. Rank-deficient fits report `NaN` covariance/output uncertainty rather
|
|
177
|
+
than false precision. Full product tables retain input, fit-pixel, and
|
|
178
|
+
corrected masks; parameter-bound flags; and machine-readable provenance. The
|
|
179
|
+
provenance hashes the modeled spectrum, source file, full and selected line
|
|
180
|
+
lists, atmospheric layers, component data, and fit configuration, and records
|
|
181
|
+
the numerical library versions. Wavelength correction can be a constant shift, one polynomial
|
|
182
|
+
shared over the full wavelength span, or independent per-segment polynomials
|
|
183
|
+
in the lower-level multi-segment API. Covariance rank is evaluated after
|
|
184
|
+
Jacobian column scaling, so the result is invariant to parameter units.
|
|
185
|
+
|
|
186
|
+
## Science-readiness status
|
|
187
|
+
|
|
188
|
+
PyMolFit has an automated cross-band, cross-instrument validation campaign,
|
|
189
|
+
but it does not currently pass its full scientific acceptance gate. See
|
|
190
|
+
[`docs/science_readiness_validation.md`](docs/science_readiness_validation.md)
|
|
191
|
+
for the checklist, public-data provenance, numerical thresholds, current
|
|
192
|
+
caveats, and reproducible evidence files. Do not describe version `0.1.0` as a
|
|
193
|
+
validated drop-in replacement for Molecfit.
|
|
194
|
+
|
|
195
|
+
## Physical HITRAN Example
|
|
196
|
+
|
|
197
|
+
This path does not call Molecfit, LBLRTM, LNFL, or HAPI at model-evaluation
|
|
198
|
+
time. It parses a HITRAN `.par` file and calculates Voigt line absorption in Python. If you provide
|
|
199
|
+
HITRAN isotopologue metadata and TIPS q files, line intensities use tabulated
|
|
200
|
+
partition sums instead of the fallback power-law approximation.
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
from pymolfit import (
|
|
204
|
+
AtmosphereProfile,
|
|
205
|
+
FitConfig,
|
|
206
|
+
IsotopologueMetadata,
|
|
207
|
+
LineList,
|
|
208
|
+
PartitionTable,
|
|
209
|
+
Spectrum,
|
|
210
|
+
fit_tellurics,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
line_list = LineList.from_hitran_par(
|
|
214
|
+
"hitran_h2o_lband.par",
|
|
215
|
+
wavenumber_min=4200,
|
|
216
|
+
wavenumber_max=4400,
|
|
217
|
+
)
|
|
218
|
+
isotopologues = IsotopologueMetadata.from_hitran_iso_meta_html("hitran_iso_meta.html")
|
|
219
|
+
line_list = line_list.with_isotopologue_metadata(isotopologues)
|
|
220
|
+
partition = PartitionTable.from_hitran_q_directory("hitran_q", isotopologues)
|
|
221
|
+
|
|
222
|
+
atmosphere = AtmosphereProfile.single_layer(
|
|
223
|
+
pressure_atm=0.75,
|
|
224
|
+
temperature_k=280,
|
|
225
|
+
mixing_ratios={"H2O": 0.001, "CO2": 4.2e-4, "CH4": 1.9e-6},
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
spectrum = Spectrum(wavelength=wavelength_micron, flux=flux)
|
|
229
|
+
result = fit_tellurics(
|
|
230
|
+
spectrum,
|
|
231
|
+
line_list=line_list,
|
|
232
|
+
config=FitConfig(
|
|
233
|
+
atmosphere=atmosphere,
|
|
234
|
+
airmass=1.2,
|
|
235
|
+
partition_table=partition,
|
|
236
|
+
continuum_order=2,
|
|
237
|
+
),
|
|
238
|
+
)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
The same physical path is available through `correct_arrays` and `correct_file`.
|
|
242
|
+
See `examples/physical_hitran_demo.py` for a fully self-contained synthetic
|
|
243
|
+
HITRAN-style example.
|
|
244
|
+
|
|
245
|
+
A more realistic built-in atmosphere is also available:
|
|
246
|
+
|
|
247
|
+
```python
|
|
248
|
+
atmosphere = AtmosphereProfile.standard_midlatitude()
|
|
249
|
+
atmosphere = atmosphere.with_pwv_mm(2.0)
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
The default high-level physical workflow now uses a Molecfit-style
|
|
253
|
+
MIPAS+GDAS atmosphere:
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
from pymolfit import AtmosphereProfile
|
|
257
|
+
|
|
258
|
+
atmosphere = AtmosphereProfile.from_mipas_gdas(
|
|
259
|
+
observation_time="2022-01-02T05:17:35",
|
|
260
|
+
observatory_altitude_m=2635.0,
|
|
261
|
+
airmass=1.2,
|
|
262
|
+
)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
For FITS inputs, `correct_file(..., atmosphere_mode="mipas_gdas")` reads common
|
|
266
|
+
FITS, ESO, and legacy Keck header keywords for observation time, airmass, site
|
|
267
|
+
altitude, longitude, latitude, pressure, temperature, and relative humidity.
|
|
268
|
+
When coordinates are absent, named Paranal, La Silla, and Keck observatories
|
|
269
|
+
are resolved through the packaged site registry; explicit FITS coordinates
|
|
270
|
+
always take precedence and the resolution source is retained in provenance.
|
|
271
|
+
An unknown site without complete coordinates now raises an error instead of
|
|
272
|
+
silently using Paranal. Supply `observatory_latitude_deg`,
|
|
273
|
+
`observatory_longitude_deg`, and `observatory_altitude_m`, or explicitly opt in
|
|
274
|
+
to the legacy fallback with `allow_default_observatory=True`. Array and plain
|
|
275
|
+
text inputs using MIPAS/GDAS require the same explicit geometry because they do
|
|
276
|
+
not carry a FITS site header.
|
|
277
|
+
Known barycentric and heliocentric wavelength products are moved back to the
|
|
278
|
+
observatory frame before telluric fitting, with the applied velocity and frame
|
|
279
|
+
retained in the product provenance. By default
|
|
280
|
+
`gdas_mode="auto"` tries to download the exact ESO GDAS tarball for the
|
|
281
|
+
rounded observatory coordinates, interpolates the bracketing 3-hour profiles to
|
|
282
|
+
the observation time, and caches both the tarball and the interpolated FITS
|
|
283
|
+
profile under `~/.cache/pymolfit/gdas`. If the exact profile is unavailable,
|
|
284
|
+
PyMolFit falls back to the same bundled six two-month average GDAS profiles
|
|
285
|
+
that Molecfit ships, merged with the packaged MIPAS climatology.
|
|
286
|
+
|
|
287
|
+
GDAS source control:
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
result = correct_file(
|
|
291
|
+
"spectrum.fits",
|
|
292
|
+
"corrected.txt",
|
|
293
|
+
hitran_par="hitran_lband.par",
|
|
294
|
+
atmosphere_mode="mipas_gdas",
|
|
295
|
+
gdas_mode="auto", # exact download/cache, then monthly average fallback
|
|
296
|
+
# gdas_mode="online", # require exact download/cache
|
|
297
|
+
# gdas_mode="cache", # require exact cached tarball/profile, no download
|
|
298
|
+
# gdas_mode="average", # force bundled monthly averages
|
|
299
|
+
)
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
`with_pwv_mm()` scales the vertical H2O column. For manual `fit_tellurics`
|
|
303
|
+
calls, keep the atmosphere vertical and pass the observation airmass through
|
|
304
|
+
`FitConfig`. For `correct_arrays`, `correct_file`, and the CLI, pass `airmass`
|
|
305
|
+
there; internally built atmospheres are slanted once before fitting. If you
|
|
306
|
+
deliberately need pre-slanted layer paths, `standard_midlatitude` also accepts
|
|
307
|
+
`airmass` and uses spherical-shell geometry for its layer path lengths.
|
|
308
|
+
|
|
309
|
+
## Model-systematic sensitivity
|
|
310
|
+
|
|
311
|
+
The optimizer covariance is conditional on one chosen physical and
|
|
312
|
+
instrumental model. To measure sensitivity to defensible alternatives, refit
|
|
313
|
+
the same pixels under named configurations:
|
|
314
|
+
|
|
315
|
+
```python
|
|
316
|
+
from dataclasses import replace
|
|
317
|
+
from pymolfit import FitConfig, fit_tellurics_with_systematics
|
|
318
|
+
|
|
319
|
+
baseline = FitConfig(
|
|
320
|
+
atmosphere=atmosphere,
|
|
321
|
+
species=("H2O", "O2"),
|
|
322
|
+
lsf_sigma_pixels=1.2,
|
|
323
|
+
estimate_uncertainties=True,
|
|
324
|
+
)
|
|
325
|
+
variants = {
|
|
326
|
+
"warmer_profile": replace(
|
|
327
|
+
baseline,
|
|
328
|
+
atmosphere=atmosphere.perturbed(temperature_offset_k=2.0),
|
|
329
|
+
),
|
|
330
|
+
"broader_lsf": replace(baseline, lsf_sigma_pixels=1.3),
|
|
331
|
+
}
|
|
332
|
+
systematics = fit_tellurics_with_systematics(
|
|
333
|
+
spectrum,
|
|
334
|
+
line_list,
|
|
335
|
+
baseline,
|
|
336
|
+
variants,
|
|
337
|
+
)
|
|
338
|
+
systematics.write("fit_with_systematics.ecsv")
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
The result stores every variant transmission, the RMS systematic uncertainty,
|
|
342
|
+
the full envelope, and a correction whose uncertainty combines the fitted
|
|
343
|
+
statistical term and ensemble spread in quadrature. Perturbation sizes must
|
|
344
|
+
come from site, atmospheric-product, or instrument-calibration uncertainties;
|
|
345
|
+
they are not fitted to make one observed spectrum resemble Molecfit.
|
|
346
|
+
|
|
347
|
+
## Blocked Cross-Validation
|
|
348
|
+
|
|
349
|
+
Use blocked out-of-fold prediction to check whether a fitted configuration
|
|
350
|
+
generalizes to pixels that were not used by the optimizer:
|
|
351
|
+
|
|
352
|
+
```python
|
|
353
|
+
from dataclasses import replace
|
|
354
|
+
from pymolfit import cross_validate_telluric_segments
|
|
355
|
+
|
|
356
|
+
cross_validation = cross_validate_telluric_segments(
|
|
357
|
+
spectra,
|
|
358
|
+
line_list=line_list,
|
|
359
|
+
config=replace(config, estimate_uncertainties=False),
|
|
360
|
+
block_size=64,
|
|
361
|
+
n_folds=2,
|
|
362
|
+
)
|
|
363
|
+
cross_validation.write("cross_validation")
|
|
364
|
+
print(cross_validation.metrics)
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
The split uses alternating contiguous pixel blocks and does not inspect flux
|
|
368
|
+
residuals, fitted transmission, or a reference correction. Every valid pixel
|
|
369
|
+
is predicted once by a fit that did not consume that pixel. Reported metrics
|
|
370
|
+
separate complete prediction coverage from correction coverage lost to
|
|
371
|
+
saturated transmission, and compare held-out telluric residuals against the
|
|
372
|
+
continuum-only prediction.
|
|
373
|
+
|
|
374
|
+
## MT_CKD H2O Continuum
|
|
375
|
+
|
|
376
|
+
PyMolFit can add the LBLRTM/MT_CKD water-vapor continuum when you provide the
|
|
377
|
+
AER `absco-ref_wv-mt-ckd.nc` coefficient file. The implementation reads the
|
|
378
|
+
reference self and foreign continuum coefficients, applies density scaling,
|
|
379
|
+
self-continuum temperature scaling, the radiation term, and the same cubic
|
|
380
|
+
interpolation formula as the reference MT_CKD H2O routine.
|
|
381
|
+
|
|
382
|
+
Python:
|
|
383
|
+
|
|
384
|
+
```python
|
|
385
|
+
from pymolfit import correct_file
|
|
386
|
+
|
|
387
|
+
result = correct_file(
|
|
388
|
+
"spectrum.fits",
|
|
389
|
+
"corrected.txt",
|
|
390
|
+
hitran_par="hitran_h2o.par",
|
|
391
|
+
h2o_continuum="absco-ref_wv-mt-ckd.nc",
|
|
392
|
+
atmosphere_mode="standard",
|
|
393
|
+
pwv_mm=2.0,
|
|
394
|
+
)
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
Continuum-only modelling is also supported if you are testing broad H2O
|
|
398
|
+
continuum effects without a line list:
|
|
399
|
+
|
|
400
|
+
```python
|
|
401
|
+
result = correct_file(
|
|
402
|
+
"spectrum.fits",
|
|
403
|
+
"corrected.txt",
|
|
404
|
+
h2o_continuum="absco-ref_wv-mt-ckd.nc",
|
|
405
|
+
atmosphere_mode="standard",
|
|
406
|
+
pwv_mm=2.0,
|
|
407
|
+
)
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
Command line:
|
|
411
|
+
|
|
412
|
+
```bash
|
|
413
|
+
pymolfit fit spectrum.txt corrected.txt \
|
|
414
|
+
--hitran-par hitran_h2o.par \
|
|
415
|
+
--mtckd-h2o absco-ref_wv-mt-ckd.nc \
|
|
416
|
+
--atmosphere standard \
|
|
417
|
+
--pwv-mm 2.0
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
Use `--mtckd-h2o-foreign-closure` to use the optional foreign-closure
|
|
421
|
+
coefficient column from MT_CKD 4.2+ files.
|
|
422
|
+
|
|
423
|
+
## TIPS And Line Wings
|
|
424
|
+
|
|
425
|
+
HITRAN line intensities are weighted by natural terrestrial isotopologue
|
|
426
|
+
abundances. PyMolFit keeps that as the default. To model a non-terrestrial
|
|
427
|
+
isotopic mixture, attach `IsotopologueMetadata` and pass absolute abundance
|
|
428
|
+
overrides; PyMolFit applies `override / natural_abundance` internally.
|
|
429
|
+
|
|
430
|
+
```python
|
|
431
|
+
line_list = line_list.with_isotopologue_metadata(
|
|
432
|
+
isotopologues,
|
|
433
|
+
abundance_overrides={(1, 1): 0.95, (1, 2): 0.05},
|
|
434
|
+
)
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
The default line shape is an untruncated Voigt profile. For an LBLRTM-style
|
|
438
|
+
calculation window, use `line_wing_mode="lblrtm_dynamic"`. This ports the
|
|
439
|
+
LBLRTM `AVRAT`/`ALFV`/`ALFMAX`/`HWF3` per-line window sizing from
|
|
440
|
+
`oprop_voigt.f90`; HITRAN line selection also expands automatically for the
|
|
441
|
+
largest dynamic window implied by the wavelength grid. For simpler fixed-window
|
|
442
|
+
experiments, use `line_cutoff_cm`. Neither mode makes the whole calculation
|
|
443
|
+
bit-identical to LBLRTM by itself, but both avoid letting isolated Voigt wings
|
|
444
|
+
contribute indefinitely.
|
|
445
|
+
|
|
446
|
+
For experiments that should follow LBLRTM's tabulated Voigt subfunction and
|
|
447
|
+
panel interpolation machinery more closely, use `line_wing_mode="lblrtm_panel"`.
|
|
448
|
+
This builds the 0-4, 0-16, and 0-64 effective-halfwidth subfunction tables from
|
|
449
|
+
the LBLRTM source formula using SciPy's Faddeeva function, then applies the
|
|
450
|
+
source `PANEL` four-to-one interpolation coefficients. It is an expert parity
|
|
451
|
+
option and not guaranteed to be faster or better on every spectrum. The older
|
|
452
|
+
`line_wing_mode="lblrtm_table"` mode keeps only the tabulated subfunction
|
|
453
|
+
decomposition without panel accumulation.
|
|
454
|
+
|
|
455
|
+
```python
|
|
456
|
+
config = FitConfig(
|
|
457
|
+
atmosphere=atmosphere,
|
|
458
|
+
partition_table=partition,
|
|
459
|
+
line_wing_mode="lblrtm_dynamic",
|
|
460
|
+
)
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
The same controls are available on the CLI:
|
|
464
|
+
|
|
465
|
+
```bash
|
|
466
|
+
pymolfit fit spectrum.txt corrected.txt \
|
|
467
|
+
--hitran-par hitran_lband.par \
|
|
468
|
+
--partition-table tips_lband.ecsv \
|
|
469
|
+
--line-wing-mode lblrtm_dynamic
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
For Molecfit-style instrumental broadening, PyMolFit supports the synthetic
|
|
473
|
+
box/Gaussian/Lorentzian kernel controls. Molecfit's optional `kern_mode`
|
|
474
|
+
Voigt-approximation path is exposed as `lsf_molecfit_voigt=True` or
|
|
475
|
+
`--lsf-molecfit-voigt`.
|
|
476
|
+
|
|
477
|
+
Physical high-level runs default to the audited LBLRTM path: packaged
|
|
478
|
+
LBLRTM-12.11 TIPS tables, packaged MT_CKD/LBLRTM H2O and CO2 continuum data,
|
|
479
|
+
the `lblrtm_panel` line accumulator, an oversampled internal grid,
|
|
480
|
+
Molecfit overlap rebinning before LSF convolution, separate Gaussian and
|
|
481
|
+
Lorentzian kernels, and the source kernel support of 3 FWHM. Use
|
|
482
|
+
`line_wing_mode="lblrtm_dynamic"` for a faster approximation, or explicitly
|
|
483
|
+
pass `h2o_continuum="none"` / `co2_continuum="none"` to disable continua.
|
|
484
|
+
|
|
485
|
+
## Rayleigh, N2, CO2, And CIA
|
|
486
|
+
|
|
487
|
+
PyMolFit also supports external continuum and collision-induced absorption
|
|
488
|
+
tables, plus source-backed `contnm.f90` branches:
|
|
489
|
+
|
|
490
|
+
- `rayleigh=True` / `--rayleigh` enables the LBLRTM Rayleigh formula for
|
|
491
|
+
wavenumbers above 820 cm-1.
|
|
492
|
+
- `n2_continuum=True` / `--n2-continuum` enables the LBLRTM N2
|
|
493
|
+
rototranslational, fundamental, and first-overtone continua.
|
|
494
|
+
- `o2_continuum=True` / `--o2-continuum` enables the LBLRTM O2 fundamental,
|
|
495
|
+
1.27 micron, 9100--11000 cm-1, A-band, and visible continua.
|
|
496
|
+
- `co2_continuum=` reads an Astropy-readable table with `wavenumber_cm`,
|
|
497
|
+
optional `temperature_k`, and `coefficient` columns.
|
|
498
|
+
- `o2_cia=` and `n2_cia=` read HITRAN `.cia` files. HITRAN CIA coefficients
|
|
499
|
+
are in cm5 molecule-2 and are scaled by the two collision-partner number
|
|
500
|
+
densities and path length in each atmosphere layer.
|
|
501
|
+
|
|
502
|
+
Python:
|
|
503
|
+
|
|
504
|
+
```python
|
|
505
|
+
result = correct_file(
|
|
506
|
+
"spectrum.fits",
|
|
507
|
+
"corrected.txt",
|
|
508
|
+
hitran_par="hitran.par",
|
|
509
|
+
h2o_continuum="absco-ref_wv-mt-ckd.nc",
|
|
510
|
+
co2_continuum="co2_continuum.ecsv",
|
|
511
|
+
rayleigh=True,
|
|
512
|
+
n2_continuum=True,
|
|
513
|
+
o2_continuum=True,
|
|
514
|
+
atmosphere_mode="standard",
|
|
515
|
+
)
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
Command line:
|
|
519
|
+
|
|
520
|
+
```bash
|
|
521
|
+
pymolfit fit spectrum.txt corrected.txt \
|
|
522
|
+
--hitran-par hitran.par \
|
|
523
|
+
--mtckd-h2o absco-ref_wv-mt-ckd.nc \
|
|
524
|
+
--co2-continuum co2_continuum.ecsv \
|
|
525
|
+
--rayleigh \
|
|
526
|
+
--n2-continuum \
|
|
527
|
+
--o2-continuum \
|
|
528
|
+
--atmosphere standard
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
The source-backed N2/O2 continua and overlapping HITRAN CIA tables are
|
|
532
|
+
alternative representations. PyMolFit rejects combinations such as
|
|
533
|
+
`n2_continuum=True` with an N2-N2 CIA table instead of silently double-counting
|
|
534
|
+
the same collision-induced absorption.
|
|
535
|
+
|
|
536
|
+
The CO2 continuum remains table-driven rather than hardcoded from LBLRTM block
|
|
537
|
+
data. This keeps the package lightweight and lets users update or swap
|
|
538
|
+
continuum datasets without changing PyMolFit code.
|
|
539
|
+
|
|
540
|
+
## Component Architecture
|
|
541
|
+
|
|
542
|
+
The physical backend is now built from absorption components. Each component
|
|
543
|
+
returns species names and optical-depth basis rows on the requested wavelength
|
|
544
|
+
grid. Components with the same species are summed before fitting, so H2O line
|
|
545
|
+
absorption and H2O continuum share one fitted H2O scale factor.
|
|
546
|
+
|
|
547
|
+
```python
|
|
548
|
+
from pymolfit import (
|
|
549
|
+
AtmosphereProfile,
|
|
550
|
+
CO2ContinuumAbsorption,
|
|
551
|
+
FitConfig,
|
|
552
|
+
H2OContinuumAbsorption,
|
|
553
|
+
HitranLineAbsorption,
|
|
554
|
+
LineList,
|
|
555
|
+
MTCKDH2OContinuum,
|
|
556
|
+
N2RototranslationalContinuumAbsorption,
|
|
557
|
+
O2CIAAbsorption,
|
|
558
|
+
Spectrum,
|
|
559
|
+
TabulatedContinuum,
|
|
560
|
+
fit_tellurics,
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
line_list = LineList.from_hitran_par("hitran_lband.par")
|
|
564
|
+
h2o_continuum = MTCKDH2OContinuum.from_netcdf("absco-ref_wv-mt-ckd.nc")
|
|
565
|
+
co2_continuum = TabulatedContinuum.from_table("co2_continuum.ecsv")
|
|
566
|
+
atmosphere = AtmosphereProfile.standard_midlatitude().with_pwv_mm(2.0)
|
|
567
|
+
|
|
568
|
+
components = (
|
|
569
|
+
HitranLineAbsorption(line_list),
|
|
570
|
+
H2OContinuumAbsorption(h2o_continuum),
|
|
571
|
+
CO2ContinuumAbsorption(co2_continuum),
|
|
572
|
+
N2RototranslationalContinuumAbsorption(),
|
|
573
|
+
# O2CIAAbsorption(HitranCIATable.from_hitran_cia("O2-O2_2011.cia")),
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
result = fit_tellurics(
|
|
577
|
+
Spectrum(wavelength=wavelength_micron, flux=flux),
|
|
578
|
+
line_list=LineList.empty_hitran(),
|
|
579
|
+
config=FitConfig(
|
|
580
|
+
atmosphere=atmosphere,
|
|
581
|
+
airmass=1.2,
|
|
582
|
+
components=components,
|
|
583
|
+
continuum_order=2,
|
|
584
|
+
),
|
|
585
|
+
)
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
The old high-level arguments, such as `hitran_par=` and `h2o_continuum=`, still
|
|
589
|
+
work. They now build this component set internally.
|
|
590
|
+
|
|
591
|
+
## Command Line Example
|
|
592
|
+
|
|
593
|
+
PyMolFit can load several common 1D spectrum formats:
|
|
594
|
+
|
|
595
|
+
- whitespace numeric text: wavelength, flux, optional uncertainty
|
|
596
|
+
- CSV numeric text
|
|
597
|
+
- ECSV or Astropy-readable tables with names like `wave`, `wavelength`,
|
|
598
|
+
`flux`, `err`, or `uncertainty`
|
|
599
|
+
- FITS binary tables with inferred wavelength/flux/error columns
|
|
600
|
+
- gzip-compressed `.fits.gz`/`.fit.gz` tables and images
|
|
601
|
+
- 1D FITS image spectra with linear `CRVAL1`/`CDELT1` wavelength WCS
|
|
602
|
+
|
|
603
|
+
For a non-scientific format smoke test with the synthetic demo line list:
|
|
604
|
+
|
|
605
|
+
```bash
|
|
606
|
+
pymolfit fit spectrum.txt corrected.txt --demo-lines
|
|
607
|
+
```
|
|
608
|
+
|
|
609
|
+
For a FITS table or 1D FITS image, supply real line data:
|
|
610
|
+
|
|
611
|
+
```bash
|
|
612
|
+
pymolfit fit spectrum.fits corrected.txt \
|
|
613
|
+
--wavelength-unit nm \
|
|
614
|
+
--hitran-par hitran_window.par
|
|
615
|
+
```
|
|
616
|
+
|
|
617
|
+
PyMolFit deliberately refuses to run a line fit without `--hitran-par`,
|
|
618
|
+
`--line-list`, an explicit continuum-only component, or `--demo-lines`. The
|
|
619
|
+
packaged demo list is synthetic and must not be used for scientific spectra.
|
|
620
|
+
|
|
621
|
+
With a HITRAN `.par` file:
|
|
622
|
+
|
|
623
|
+
```bash
|
|
624
|
+
pymolfit fit spectrum.txt corrected.txt \
|
|
625
|
+
--hitran-par hitran_h2o_lband.par \
|
|
626
|
+
--mtckd-h2o absco-ref_wv-mt-ckd.nc \
|
|
627
|
+
--hitran-species H2O \
|
|
628
|
+
--hitran-min-strength 1e-28 \
|
|
629
|
+
--airmass 1.2 \
|
|
630
|
+
--atmosphere standard \
|
|
631
|
+
--pwv-mm 2.0 \
|
|
632
|
+
--partition-table partition_sums.ecsv \
|
|
633
|
+
--mixing-ratio H2O=0.001
|
|
634
|
+
```
|
|
635
|
+
|
|
636
|
+
The optional partition table should be readable by Astropy and contain:
|
|
637
|
+
`mol_id`, `iso_id`, `temperature_k`, and `q`. If a molecule/isotopologue is
|
|
638
|
+
not present in the table, PyMolFit falls back to its approximate internal
|
|
639
|
+
partition scaling for that line.
|
|
640
|
+
|
|
641
|
+
Instead of the built-in atmosphere, a profile table can be supplied:
|
|
642
|
+
|
|
643
|
+
```bash
|
|
644
|
+
pymolfit fit spectrum.txt corrected.txt \
|
|
645
|
+
--hitran-par hitran_lband.par \
|
|
646
|
+
--atmosphere-table profile.ecsv
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
The atmosphere table should be Astropy-readable and contain pressure,
|
|
650
|
+
temperature, path length, and volume mixing ratio columns. Supported pressure
|
|
651
|
+
columns are `pressure_atm`, `pressure_hpa`, `pressure_mbar`, or `pressure_pa`.
|
|
652
|
+
Supported path columns are `path_length_m`, `thickness_m`, or `dz_m`. Mixing
|
|
653
|
+
ratio columns should be named like `mix_H2O`, `mix_CO2`, or `vmr_H2O`.
|
|
654
|
+
|
|
655
|
+
To cache a filtered HITRAN file as a reusable PyMolFit line-list table:
|
|
656
|
+
|
|
657
|
+
```bash
|
|
658
|
+
pymolfit convert-hitran hitran_lband.par h2o_lband.ecsv \
|
|
659
|
+
--species H2O \
|
|
660
|
+
--wavenumber-min 4200 \
|
|
661
|
+
--wavenumber-max 4400 \
|
|
662
|
+
--min-strength 1e-28 \
|
|
663
|
+
--max-lines 50000
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
PyMolFit can optionally acquire a newer or custom wavelength window from
|
|
667
|
+
HITRAN's authenticated
|
|
668
|
+
API v2 without installing HAPI. Create an API key in your HITRAN account, keep
|
|
669
|
+
it in the environment, and request all molecules needed by the fit:
|
|
670
|
+
|
|
671
|
+
```bash
|
|
672
|
+
export HITRAN_API_KEY='your-private-key'
|
|
673
|
+
pymolfit fetch-hitran \
|
|
674
|
+
--species H2O --species CO2 --species CH4 \
|
|
675
|
+
--wavelength-min 2.29 --wavelength-max 2.36
|
|
676
|
+
```
|
|
677
|
+
|
|
678
|
+
The command prints paths to a standard `.par` file, a directly reusable ECSV
|
|
679
|
+
line table, and a JSON manifest. They are cached under
|
|
680
|
+
`~/.cache/pymolfit/hitran` (or `PYMOLFIT_HITRAN_CACHE`) and include the exact
|
|
681
|
+
query, database edition reported by the service, line coverage, isotopologue
|
|
682
|
+
metadata, and SHA-256 hashes. The API key is never persisted. A verified cache
|
|
683
|
+
hit works offline. Existing licensed files can be imported through the same
|
|
684
|
+
validation/provenance path:
|
|
685
|
+
|
|
686
|
+
```bash
|
|
687
|
+
pymolfit cache-hitran downloaded.par \
|
|
688
|
+
--species O2 --wavelength-min 0.75 --wavelength-max 0.78
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
Use the printed ECSV path as `--line-list`. Direct HITRAN API data remain
|
|
692
|
+
subject to HITRAN's terms and citation policy. This optional route is separate
|
|
693
|
+
from the official AER 3.9 catalogue used by the default workflow.
|
|
694
|
+
|
|
695
|
+
For a release checkout, exercise the real authenticated path once with a
|
|
696
|
+
fixed, narrow O2 request and write a credential-free validation receipt:
|
|
697
|
+
|
|
698
|
+
```bash
|
|
699
|
+
export HITRAN_API_KEY='your-private-key'
|
|
700
|
+
PYTHONPATH=src python local_tests/validate_authenticated_hitran.py
|
|
701
|
+
unset HITRAN_API_KEY
|
|
702
|
+
```
|
|
703
|
+
|
|
704
|
+
This forces a network request rather than accepting a cache hit. The receipt
|
|
705
|
+
contains request, database-edition, source-code, and artifact hashes, but no
|
|
706
|
+
API key or downloaded line records. The science-readiness campaign accepts it
|
|
707
|
+
only while its client-source hash matches the current checkout.
|
|
708
|
+
|
|
709
|
+
With common expert controls:
|
|
710
|
+
|
|
711
|
+
```bash
|
|
712
|
+
pymolfit fit spectrum_nm.txt corrected.txt \
|
|
713
|
+
--wavelength-unit nm \
|
|
714
|
+
--hitran-par hitran_lband.par \
|
|
715
|
+
--airmass 1.3 \
|
|
716
|
+
--fit-wavelength-polynomial \
|
|
717
|
+
--wavelength-polynomial-order 1 \
|
|
718
|
+
--wavelength-shift-bounds -0.0003 0.0003 \
|
|
719
|
+
--fit-lsf-sigma \
|
|
720
|
+
--lsf-molecfit-voigt \
|
|
721
|
+
--fit-range 2.318:2.322 \
|
|
722
|
+
--fit-range 2.330:2.334 \
|
|
723
|
+
--exclude-range 2.3205:2.3212 \
|
|
724
|
+
--loss soft_l1 \
|
|
725
|
+
--product fit_product.ecsv \
|
|
726
|
+
--plot fit_diagnostic.png
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
The main output is the corrected spectrum. The optional product table contains
|
|
730
|
+
the observed flux, fitted model, continuum, transmission, corrected flux,
|
|
731
|
+
input/fit/corrected masks, propagated uncertainties, fitted parameters,
|
|
732
|
+
covariance rank, parameters at active bounds, and exact model/input provenance.
|
|
733
|
+
|
|
734
|
+
To compare a corrected PyMolFit spectrum with a reference product, such as a
|
|
735
|
+
Molecfit-corrected spectrum:
|
|
736
|
+
|
|
737
|
+
```bash
|
|
738
|
+
pymolfit compare corrected.txt reference_corrected.txt --normalize
|
|
739
|
+
```
|
|
740
|
+
|
|
741
|
+
## Remaining Release Gates
|
|
742
|
+
|
|
743
|
+
1. Complete the independent blind packet and a reviewer-selected held-out
|
|
744
|
+
spectrum with an experienced telluric-correction user. Send only
|
|
745
|
+
`local_tests/science_readiness/results/independent_review_packet.zip`; its
|
|
746
|
+
manifest is checked automatically and the private answer key is excluded.
|
|
747
|
+
2. Resolve or scientifically explain the retained direct-transmission warnings
|
|
748
|
+
across X-shooter and CRIRES+ without observation-specific calibration.
|
|
749
|
+
|
|
750
|
+
The production and validation workflows both use the checksum-pinned official
|
|
751
|
+
AER 3.9 catalogue from Zenodo. Authenticated HITRAN validation remains an
|
|
752
|
+
optional check of the direct-API feature, not a requirement for normal
|
|
753
|
+
scientific use.
|