sed-model 0.3.2__tar.gz → 0.4.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.
- {sed_model-0.3.2 → sed_model-0.4.0}/CHANGELOG.md +58 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/CITATION.cff +1 -1
- {sed_model-0.3.2 → sed_model-0.4.0}/PKG-INFO +2 -2
- {sed_model-0.3.2 → sed_model-0.4.0}/README.md +1 -1
- {sed_model-0.3.2 → sed_model-0.4.0}/docs/guide/forward.md +1 -1
- {sed_model-0.3.2 → sed_model-0.4.0}/docs/guide/grids-and-filters.md +1 -1
- sed_model-0.4.0/docs/guide/inverse.md +116 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/docs/index.md +1 -1
- sed_model-0.4.0/docs/testing.md +62 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/fortran/cc_api.f90 +20 -9
- {sed_model-0.3.2 → sed_model-0.4.0}/fortran/private/bolometric.f90 +2 -2
- {sed_model-0.3.2 → sed_model-0.4.0}/fortran/private/synthetic.f90 +28 -34
- {sed_model-0.3.2 → sed_model-0.4.0}/fortran/public/colors_def.f90 +5 -2
- {sed_model-0.3.2 → sed_model-0.4.0}/meson.build +2 -1
- sed_model-0.4.0/sed_model/_inv_core.py +565 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/sed_model/filters.py +35 -21
- {sed_model-0.3.2 → sed_model-0.4.0}/sed_model/forward.py +14 -1
- {sed_model-0.3.2 → sed_model-0.4.0}/sed_model/grid.py +76 -16
- {sed_model-0.3.2 → sed_model-0.4.0}/sed_model/inverse.py +189 -26
- {sed_model-0.3.2 → sed_model-0.4.0}/sed_model/io.py +66 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/tests/test_inverse.py +174 -0
- sed_model-0.4.0/tests/test_kernels.py +309 -0
- sed_model-0.3.2/docs/guide/inverse.md +0 -91
- sed_model-0.3.2/docs/testing.md +0 -38
- sed_model-0.3.2/tests/test_kernels.py +0 -167
- {sed_model-0.3.2 → sed_model-0.4.0}/.f2py_f2cmap +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/.gitattributes +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/.github/workflows/build.yml +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/.github/workflows/docs.yml +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/.gitignore +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/CODE_OF_CONDUCT.md +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/CONTRIBUTING.md +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/LICENSE +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/MIGRATION.md +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/Makefile +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/SECURITY.md +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/demos/api_usage.py +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/demos/demo_forward.py +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/demos/demo_inverse.py +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/demos/demo_param_modes.py +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/demos/extinction_prescription_comparison.py +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/demos/plot_utils.py +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/demos/sed_playground_demo.py +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/demos/simbad_query_inverse.py +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/demos/single_test.py +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/docs/SED_Model_logo.jpeg +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/docs/api.md +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/docs/demos/api-usage.md +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/docs/demos/extinction-comparison.md +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/docs/demos/forward.md +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/docs/demos/index.md +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/docs/demos/inverse.md +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/docs/demos/param-modes.md +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/docs/demos/playground.md +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/docs/favicon.png +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/docs/guide/extinction.md +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/docs/guide/parameters.md +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/docs/guide/results-io.md +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/docs/installation.md +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/docs/quickstart.md +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/fortran/private/colors_utils.f90 +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/fortran/private/hermite_interp.f90 +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/fortran/private/hermite_interp_bounded.f90 +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/fortran/private/linear_interp.f90 +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/fortran/public/colors_lib.f90 +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/meson_options.txt +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/mkdocs.yml +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/pyproject.toml +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/sed_model/__init__.py +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/sed_model/_cc_ext.py +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/sed_model/params.py +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/sed_model/sed_extinction.py +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/tests/_data_paths.py +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/tests/fortran/smoke.f90 +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/tests/test_forward.py +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/tools/ci/build_fortran_lib.sh +0 -0
- {sed_model-0.3.2 → sed_model-0.4.0}/tools/post_install_fortran.py +0 -0
|
@@ -1,5 +1,63 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.4.0] — 2026-10-25
|
|
4
|
+
|
|
5
|
+
Validated against the MESA `colors` module, MSG and MIST (see
|
|
6
|
+
[Testing and Validation](docs/testing.md)); forward parity fixes and a rebuilt
|
|
7
|
+
inverse model.
|
|
8
|
+
|
|
9
|
+
### Forward model
|
|
10
|
+
|
|
11
|
+
Forward-model parity fixes found by sweeping against MESA `colors` (see
|
|
12
|
+
`tests/test_kernels.py`). SEDs and magnitudes otherwise agree with MESA to
|
|
13
|
+
rounding error.
|
|
14
|
+
|
|
15
|
+
- **`bol_mag` is `99.0` when the integrated flux is not positive** (MESA's
|
|
16
|
+
value), instead of `-99.9`.
|
|
17
|
+
- **`nearest_grid_distance` now equals MESA's `Interp_rad`**: the distance to
|
|
18
|
+
the nearest tabulated model (empty cube nodes excluded), with axes
|
|
19
|
+
normalised by the range of the tabulated models. Previously it measured to
|
|
20
|
+
every node of the rectangular cube.
|
|
21
|
+
- **`run_forward` warns** (`UserWarning`) when the query's grid cell touches a
|
|
22
|
+
node with no model; the interpolated result there is unreliable.
|
|
23
|
+
`AtmosphereGrid.cell_has_empty_nodes` exposes the check.
|
|
24
|
+
- **`clamped` is no longer set by `interp_method='linear'`** merely because some
|
|
25
|
+
wavelength bins fell back to the nearest node at a populated, in-bounds point.
|
|
26
|
+
|
|
27
|
+
### Inverse model
|
|
28
|
+
|
|
29
|
+
The new fast evaluator calls the same Fortran kernels and agrees with
|
|
30
|
+
`run_forward` to 1e-9 mag.
|
|
31
|
+
|
|
32
|
+
### Changed (default behaviour)
|
|
33
|
+
|
|
34
|
+
- **`run_inverse` now initialises walkers with a global search + Levenberg-
|
|
35
|
+
Marquardt refinement (`init='auto'`)** instead of a 2 % Gaussian ball at the
|
|
36
|
+
middle of the prior box. For the full Kurucz grid that ball sat in an empty
|
|
37
|
+
grid corner, and walkers that started there often never left it. In an
|
|
38
|
+
inject-and-recover benchmark (16-24 stars, 0.005-0.05 mag noise, Johnson /
|
|
39
|
+
optical+NIR / 11-band sets) the old default gave, with Av free, a Teff error
|
|
40
|
+
MAD of 10.9 % (now 1.6 %) and 12 % of fits with logg off by more than 4 sigma
|
|
41
|
+
(now 0.7 %); with Av and distance free the 95 % Teff coverage went from 0.72
|
|
42
|
+
to 0.91. Giving `p0_centre` (or `init='ball'`) restores the old behaviour
|
|
43
|
+
exactly, including emcee's stretch move.
|
|
44
|
+
- With `init='auto'` the default sampler move is a differential-evolution
|
|
45
|
+
mixture (`moves='de'`), which halves the autocorrelation time and lets
|
|
46
|
+
walkers rebalance between modes; `moves='stretch'` restores the old move.
|
|
47
|
+
- Grid nodes holding an all-zero SED (unphysical corners of Kurucz grids) now
|
|
48
|
+
give a zero posterior instead of finite magnitudes of ~770.
|
|
49
|
+
- The likelihood no longer builds a full `ForwardResult` per call.
|
|
50
|
+
|
|
51
|
+
### Added
|
|
52
|
+
|
|
53
|
+
- `run_inverse` options: `init`, `sys_floor`, `fit_jitter` / `jitter_max`,
|
|
54
|
+
`priors` (`normal`, `halfnormal`, `parallax`, `edsd`, or callables),
|
|
55
|
+
`upper_limits`, `auto_stop`, `moves`, `pool`; `n_burn=None` for automatic
|
|
56
|
+
burn-in. Non-finite magnitudes are dropped with a warning.
|
|
57
|
+
- `InverseResult`: `converged`, `n_eff`, `chi2_best`, `n_data`, `init_info`
|
|
58
|
+
(saved and loaded with `.npz`), and `credible_interval(name, level, method)`
|
|
59
|
+
with highest-posterior-density intervals.
|
|
60
|
+
|
|
3
61
|
## [0.3.2] — 2026-10-25
|
|
4
62
|
|
|
5
63
|
- **Filter files from SED_Tools 0.4.0 load again.** SED_Tools 0.4.0 puts its
|
|
@@ -7,7 +7,7 @@ authors:
|
|
|
7
7
|
- family-names: "Miller"
|
|
8
8
|
given-names: "Niall"
|
|
9
9
|
email: "niall.j.miller@gmail.com"
|
|
10
|
-
version: "0.
|
|
10
|
+
version: "0.4.0"
|
|
11
11
|
repository-code: "https://github.com/nialljmiller/SED_Model"
|
|
12
12
|
url: "https://nialljmiller.github.io/SED_Model/"
|
|
13
13
|
license: MIT
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: sed-model
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Forward and inverse stellar photometry from SED grids.
|
|
5
5
|
Keywords: astronomy,astrophysics,stellar astronomy,synthetic photometry,spectral energy distributions,sed,stellar parameters,bayesian inference,mcmc
|
|
6
6
|
Author-Email: Niall Miller <niall.j.miller@gmail.com>
|
|
@@ -36,7 +36,7 @@ Description-Content-Type: text/markdown
|
|
|
36
36
|
|
|
37
37
|
SED_Model is a Python package for computing observer-ready synthetic spectral energy distributions (SEDs) and broadband magnitudes from stellar atmosphere grids, and for recovering stellar parameters from observed photometry via Bayesian MCMC inference. It is designed to work directly downstream of `SED_Tools` and as a standalone drop-in for workflows that use MESA-style atmosphere grids and filter transmission curves.
|
|
38
38
|
|
|
39
|
-
Version: `0.
|
|
39
|
+
Version: `0.4.0`
|
|
40
40
|
|
|
41
41
|
**Documentation:** https://nialljmiller.github.io/SED_Model/
|
|
42
42
|
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
SED_Model is a Python package for computing observer-ready synthetic spectral energy distributions (SEDs) and broadband magnitudes from stellar atmosphere grids, and for recovering stellar parameters from observed photometry via Bayesian MCMC inference. It is designed to work directly downstream of `SED_Tools` and as a standalone drop-in for workflows that use MESA-style atmosphere grids and filter transmission curves.
|
|
10
10
|
|
|
11
|
-
Version: `0.
|
|
11
|
+
Version: `0.4.0`
|
|
12
12
|
|
|
13
13
|
**Documentation:** https://nialljmiller.github.io/SED_Model/
|
|
14
14
|
|
|
@@ -85,7 +85,7 @@ F_{\rm band} = \frac{\int F(\lambda)\, T(\lambda)\, \lambda\, d\lambda}{\int T(\
|
|
|
85
85
|
m = -2.5 \log_{10}\!\left( F_{\rm band} / F_{zp} \right)
|
|
86
86
|
\]
|
|
87
87
|
|
|
88
|
-
with the zero-point \(F_{zp}\) precomputed per filter and system at load time ([Grids and Filters](grids-and-filters.md#zero-points)). Failed integrations or non-positive fluxes produce a
|
|
88
|
+
with the zero-point \(F_{zp}\) precomputed per filter and system at load time ([Grids and Filters](grids-and-filters.md#zero-points)). Failed integrations or non-positive fluxes produce a non-zero error flag rather than crashing: band magnitudes become `NaN` in `ForwardResult.magnitudes`, and the bolometric magnitude is `99.0` (MESA's value) when the integrated flux is not positive.
|
|
89
89
|
|
|
90
90
|
## Batch evaluation
|
|
91
91
|
|
|
@@ -56,7 +56,7 @@ Filter files are two-column (wavelength Å, transmission 0–1); `#` comments ar
|
|
|
56
56
|
|
|
57
57
|
### Zero-points
|
|
58
58
|
|
|
59
|
-
Each `Filter` carries precomputed
|
|
59
|
+
Each `Filter` carries precomputed zero-points using the same integrals as MESA `colors/private/synthetic.f90` (photon counting, as MESA does by default). The photon-counting forms are:
|
|
60
60
|
|
|
61
61
|
| System | Definition |
|
|
62
62
|
|---|---|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# Inverse Model
|
|
2
|
+
|
|
3
|
+
The inverse model recovers a posterior over stellar parameters from observed broadband photometry, using [`emcee`](https://emcee.readthedocs.io/) ensemble MCMC.
|
|
4
|
+
|
|
5
|
+
## Statistical model
|
|
6
|
+
|
|
7
|
+
- **Prior** — flat within the bounds of each free parameter (from [`FitParams`](parameters.md)); \(-\infty\) outside.
|
|
8
|
+
- **Likelihood** — Gaussian per filter, summed:
|
|
9
|
+
|
|
10
|
+
\[
|
|
11
|
+
\ln \mathcal{L} = -\frac{1}{2} \sum_i \left[ \frac{(m_i^{\rm obs} - m_i^{\rm model})^2}{\sigma_i^2} + \ln\left(2\pi \sigma_i^2\right) \right]
|
|
12
|
+
\]
|
|
13
|
+
|
|
14
|
+
Model magnitudes come from the **same Fortran kernels, called in the same order, as `run_forward`** (interpolate, dilute, extinguish, convolve), minus the parts that do not affect magnitudes (bolometric integral, nearest-grid diagnostic, rebuilding the extinction model every call). A test checks agreement with `run_forward` to 1e-9 mag. Any exception inside the evaluation, a non-finite model magnitude, or a query that touches an *empty* grid node (Kurucz-type grids store unphysical corners, e.g. hot + low gravity, as all-zero SEDs that the forward model turns into finite magnitudes near 770) returns \(-\infty\) so the walker simply moves away.
|
|
15
|
+
|
|
16
|
+
Optional extensions, all off by default:
|
|
17
|
+
|
|
18
|
+
- `sys_floor=0.03` adds a systematic error floor (mag) in quadrature to every uncertainty.
|
|
19
|
+
- `fit_jitter=True` samples one extra common term `sigma_sys` (flat on `[0, jitter_max]`), added in quadrature to all uncertainties; it appears as the last column of `samples`.
|
|
20
|
+
- `priors={'d': ('parallax', 2.0, 0.05), 'a_v': ('halfnormal', 0.5), ...}` multiplies in extra priors (`normal`, `halfnormal`, `parallax` and `edsd` for distance, or any callable returning a log prior).
|
|
21
|
+
- `upper_limits=[False, True, ...]` flags bands whose entry is a magnitude lower limit; they contribute \(\ln\Phi((m^{\rm model}-m^{\rm obs})/\sigma)\).
|
|
22
|
+
- Non-finite magnitudes or uncertainties are dropped (with a warning) instead of poisoning the likelihood.
|
|
23
|
+
|
|
24
|
+
## Basic usage
|
|
25
|
+
|
|
26
|
+
```python
|
|
27
|
+
from sed_model import run_inverse
|
|
28
|
+
|
|
29
|
+
posterior = run_inverse(
|
|
30
|
+
obs_magnitudes=[5.03, 4.17],
|
|
31
|
+
obs_uncertainties=[0.01, 0.02],
|
|
32
|
+
filter_names=["G", "J"],
|
|
33
|
+
R=6.957e10, # cm — always required, not sampled
|
|
34
|
+
d=3.086e19, # cm — convenience: builds default FitParams with d fixed
|
|
35
|
+
grid=grid,
|
|
36
|
+
filters=filters,
|
|
37
|
+
mag_system="AB",
|
|
38
|
+
n_walkers=32, n_steps=2000, n_burn=500, n_thin=1,
|
|
39
|
+
seed=42,
|
|
40
|
+
progress=True,
|
|
41
|
+
)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
With no `fit_params` supplied, a default is built from the grid: `Teff`, `logg`, `[M/H]` free across the full grid, `Av` fixed at 0, distance fixed at `d` (default 1 pc). If `fit_params` **is** supplied, the `d` keyword is ignored — set distance through `fit_params`.
|
|
45
|
+
|
|
46
|
+
## Choosing what to sample
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from sed_model import fit_params_from_grid
|
|
50
|
+
from sed_model.params import PC_TO_CM
|
|
51
|
+
|
|
52
|
+
# Av free with flat prior over [0, 3] mag
|
|
53
|
+
params = fit_params_from_grid(grid, a_v=(0.0, 3.0), d_cm=500 * PC_TO_CM)
|
|
54
|
+
|
|
55
|
+
# Av and distance both free — expect a correlated Av–d posterior
|
|
56
|
+
params = fit_params_from_grid(
|
|
57
|
+
grid,
|
|
58
|
+
a_v=(0.0, 2.0),
|
|
59
|
+
d_cm=(100 * PC_TO_CM, 2000 * PC_TO_CM),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
posterior = run_inverse(..., fit_params=params)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
When `Av` is active (free, or fixed > 0) and no `extinction` model is supplied, a default Fitzpatrick (1999) model is created automatically; pass `extinction_law=...` or a full `ExtinctionModel` to control the prescription. See [Extinction](extinction.md).
|
|
66
|
+
|
|
67
|
+
## Walker initialisation
|
|
68
|
+
|
|
69
|
+
By default (`init='auto'`) the sampler does not start from a ball at the middle of the prior box (which, for the full Kurucz grid, is a 27 000 K, logg 2.5 point that lies in an empty corner of the grid). Instead it
|
|
70
|
+
|
|
71
|
+
1. evaluates the posterior on a stratified (Latin hypercube) draw over the prior box, log-uniform in Teff and distance;
|
|
72
|
+
2. refines the best few distinct candidates with Levenberg-Marquardt on the magnitude residuals (pure NumPy);
|
|
73
|
+
3. estimates each optimum's covariance from the Gauss-Newton Jacobian and its relative posterior mass by importance sampling, and
|
|
74
|
+
4. starts the walkers in those ellipsoids, giving every plausible mode a viable sub-ensemble.
|
|
75
|
+
|
|
76
|
+
With `init='auto'` the ensemble uses a differential-evolution move mixture (`moves='de'`, including mode-hopping jumps), which rebalances walkers between modes and decorrelates about twice as fast as emcee's stretch move. Pass `moves='stretch'` for the old behaviour.
|
|
77
|
+
|
|
78
|
+
To use the legacy Gaussian ball, give a starting point or ask for it:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
posterior = run_inverse(
|
|
82
|
+
...,
|
|
83
|
+
p0_centre={"teff": 5800, "logg": 4.4, "meta": 0.0}, # implies init='ball'
|
|
84
|
+
p0_scatter=0.02, # fraction of each range
|
|
85
|
+
)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`n_burn=None` picks the burn-in automatically (twice the autocorrelation time, at most half the chain), and `auto_stop=True` samples in blocks and stops once the chain exceeds 50 autocorrelation times.
|
|
89
|
+
|
|
90
|
+
## Input validation
|
|
91
|
+
|
|
92
|
+
`run_inverse` raises `ValueError` before any sampling when:
|
|
93
|
+
|
|
94
|
+
- `obs_magnitudes` and `obs_uncertainties` differ in length, or `filter_names` does not match them;
|
|
95
|
+
- any uncertainty is ≤ 0 (the likelihood would be undefined);
|
|
96
|
+
- a filter name is not among the supplied filters;
|
|
97
|
+
- `n_walkers < 6`, `n_walkers < 2 × n_free`, or an integer `n_burn >= n_steps`;
|
|
98
|
+
- every parameter is fixed (nothing to sample).
|
|
99
|
+
|
|
100
|
+
## Diagnostics
|
|
101
|
+
|
|
102
|
+
After sampling, a `UserWarning` is emitted if any walker's acceptance fraction falls below 0.1 or above 0.9. The result carries
|
|
103
|
+
|
|
104
|
+
- `autocorr_time` (integrated autocorrelation time per parameter, `None` if it could not be estimated),
|
|
105
|
+
- `converged` (`True` when the post-burn-in chain is longer than 50 autocorrelation times for every parameter) and `n_eff` (effective number of independent samples per parameter),
|
|
106
|
+
- `chi2_best` / `n_data` (data chi-square of the maximum-posterior sample and the number of detected bands), and
|
|
107
|
+
- `init_info` (how the walkers were started: method, number of modes, walkers per mode, likelihood evaluations).
|
|
108
|
+
|
|
109
|
+
`posterior.credible_interval('meta', 0.68, method='hpd')` returns the shortest interval containing the requested mass; unlike the equal-tailed intervals in `summary()`, it extends to the bound when the posterior mode sits on a prior bound.
|
|
110
|
+
|
|
111
|
+
!!! note "Calibration caveats"
|
|
112
|
+
Intervals are only as good as the model: with real photometry the atmosphere library, filter curves and zero points contribute systematic errors that the Gaussian likelihood does not know about (use `sys_floor` or `fit_jitter`). Fitting independent MIST bolometric-correction photometry (different atmospheres, filter curves and Vega zero point) with the Kurucz grid, the nominal 95 % Teff intervals covered the truth 46 % of the time; `sys_floor=0.03` raised that to 62 % and `fit_jitter=True` to 89 %. Weakly constrained parameters (logg from a few broad bands, Teff-Av degeneracy for few bands) have posteriors dominated by the flat prior and can be genuinely multimodal; check `converged`, and consider informative priors via `priors=`.
|
|
113
|
+
|
|
114
|
+
## The result
|
|
115
|
+
|
|
116
|
+
`run_inverse` returns an [`InverseResult`](results-io.md#inverseresult) holding the flattened post-burn-in chain (`samples`, shape `(n_samples, n_free)` with columns in `param_names` order), per-sample `log_prob`, the observations, sampler settings, acceptance fractions, and the fixed-parameter values used. See [Results and I/O](results-io.md) for summaries, MAP estimates, and `.npz` persistence.
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
SED_Model is a Python package for computing observer-ready synthetic spectral energy distributions (SEDs) and broadband magnitudes from stellar atmosphere grids, and for recovering stellar parameters from observed photometry via Bayesian MCMC inference. It works directly downstream of [SED_Tools](https://github.com/nialljmiller/SED_Tools) and as a standalone drop-in for workflows that use MESA-style atmosphere grids and filter transmission curves.
|
|
10
10
|
|
|
11
|
-
Current version: **0.
|
|
11
|
+
Current version: **0.4.0** ([PyPI](https://pypi.org/project/sed-model/))
|
|
12
12
|
|
|
13
13
|
## Two paired models, one parameter language
|
|
14
14
|
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Testing and Validation
|
|
2
|
+
|
|
3
|
+
```bash
|
|
4
|
+
pytest tests/test_forward.py
|
|
5
|
+
pytest tests/test_inverse.py
|
|
6
|
+
pytest tests/test_kernels.py
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Run them from outside the source tree against an installed build; inside it `import sed_model` finds the uncompiled sources, has no extension, and every test skips.
|
|
10
|
+
|
|
11
|
+
Both suites skip gracefully — with an explanatory reason — when the Fortran extension is not built or the required data products are not on disk, so `pytest` is always safe to run.
|
|
12
|
+
|
|
13
|
+
## Data locations
|
|
14
|
+
|
|
15
|
+
The tests look for SED_Tools data products in `~/.sed_tools/data`, then `~/SED_Tools/data`, then `$MESA_DIR/data/colors_data`, using the first location that exists. Each path can be overridden with an environment variable:
|
|
16
|
+
|
|
17
|
+
| Variable | Path under the data root |
|
|
18
|
+
|---|---|
|
|
19
|
+
| `CC_KURUCZ_DIR` | `stellar_models/Kurucz2003all` |
|
|
20
|
+
| `CC_JOHNSON_DIR` | `filters/Generic/Johnson` |
|
|
21
|
+
| `CC_VEGA_SED` | `stellar_models/vega_flam.csv` |
|
|
22
|
+
|
|
23
|
+
## Forward model: validated against MESA
|
|
24
|
+
|
|
25
|
+
`tests/test_forward.py` checks grid loading, bounds/clamping, both interpolation schemes, filter loading and zero-points, and then the headline validation: synthetic magnitudes and bolometric quantities are compared against **reference values produced by the MESA colors unit test** (`colors/test/test_output`, MESA Kurucz2003 grid) for three stars spanning the grid — solar (5778 K / 4.44 / 0.0), a hot main-sequence star (15000 K / 4.0, 5 R☉), and a cool giant (4000 K / 2.0, 20 R☉).
|
|
26
|
+
|
|
27
|
+
Tolerances are `TOL_MAG = 0.05` mag and `TOL_FLUX = 2%` relative — deliberately generous to absorb grid version differences and the trapezoid (Python zero-points) vs adaptive Simpson (Fortran in-band flux) integration difference, while still catching any real regression in the pipeline.
|
|
28
|
+
|
|
29
|
+
## Inverse model: self-consistent recovery
|
|
30
|
+
|
|
31
|
+
`tests/test_inverse.py` does not use external references. Instead it closes the loop on the package itself:
|
|
32
|
+
|
|
33
|
+
1. run the forward model at known parameters to produce synthetic magnitudes,
|
|
34
|
+
2. perturb them with a small known noise level (0.01 mag),
|
|
35
|
+
3. run the inverse model and require the posterior median to recover the truth within physically motivated tolerances (≲ 2 grid spacings: 500 K in Teff, 0.5 dex in logg and [M/H]).
|
|
36
|
+
|
|
37
|
+
Chains are kept deliberately short (hundreds of steps, 16 walkers) so the suite finishes quickly; the tolerances account for the resulting shot noise. The suite also covers the `InverseResult` API — summary structure and ordering, finite MAP estimates, `.npz` save/load round-trips, thinning behaviour — and the input-validation errors (mismatched array lengths, non-positive uncertainties, unknown filter names).
|
|
38
|
+
|
|
39
|
+
## Cross-code validation (0.4.0)
|
|
40
|
+
|
|
41
|
+
Before 0.4.0 the forward model was swept against the MESA `colors` module, MSG and MIST, and the inverse model was benchmarked by injection and recovery. Summary of what a user should expect:
|
|
42
|
+
|
|
43
|
+
| Against | Result |
|
|
44
|
+
|---|---|
|
|
45
|
+
| **MESA colors** (standalone driver built from the MESA sources; Kurucz2003all, its alpha-enhanced variant and blackbodies; Johnson, 2MASS, Gaia, TESS, JWST and Argus filters; AB, Vega and ST; all three interpolation methods; on-node, off-node, cell-edge, corner and out-of-range queries) | SEDs, bolometric quantities and wavelengths identical; magnitudes within 3e-13 mag (double-precision rounding). |
|
|
46
|
+
| **MSG** (same atmospheres, built into an MSG grid from the SED_Model cube) | `linear` agrees to about 1e-8. Hermite differs by 1e-5 to 1e-3 in clean cells because MSG's cubic includes cross-derivative terms. Photometry agrees to 1-4 mmag; U and B reach 13 mmag from quadrature on the coarse Johnson files. |
|
|
47
|
+
| **MIST v2.5** bolometric corrections (C3K atmospheres, a different library) | 0.01-0.03 mag in V, I, 2MASS and Gaia at equal Teff, logg and [Fe/H]; up to 0.05-0.1 mag in U. These are model differences, not code errors. |
|
|
48
|
+
|
|
49
|
+
Things that matter when comparing with other work:
|
|
50
|
+
|
|
51
|
+
- **Weighting.** `synthetic_magnitude` counts photons, as MESA does. Energy weighting would change Gaia G by up to 0.25 mag for cool stars, so curves defined for energy-counting detectors (e.g. Bessell UBVRI) are weighted differently from what SED_Model currently does; support for per-filter detector type is pending.
|
|
52
|
+
- **Filter definition.** The shipped `Generic/Johnson` curves are coarse and differ from Bessell/Cousins by up to 0.2 mag in R and I for cool stars.
|
|
53
|
+
- **2MASS Vega zero points** follow Blanton and Roweis, about 0.025 mag from MIST's Cohen-based values.
|
|
54
|
+
- **Empty grid cells.** A rectangular grid cube can contain nodes with no model (for Kurucz2003all about 40% of the cube). `run_forward` emits a `UserWarning` when the query's cell touches one, and the result there is unreliable. Hermite interpolation next to empty nodes can overshoot by 0.02-0.09 mag; this is MESA's behaviour and is reproduced exactly.
|
|
55
|
+
|
|
56
|
+
Inverse recovery on the Kurucz grid (16-24 injected stars per set, 0.005-0.05 mag noise): the old mid-box start left walkers stuck in an empty corner and chains unconverged. With the 0.4.0 defaults, fitting Teff, logg, [M/H] and Av gives a Teff bias near 0.1 % and 95 % intervals that cover the truth about 94 % of the time. Photometry from a different atmosphere library (the MIST test) is far outside the nominal intervals unless `fit_jitter` is used, which is recommended for real photometry.
|
|
57
|
+
|
|
58
|
+
The cross-code harnesses are not part of the repository; the in-repo regression tests (`tests/test_kernels.py`) encode the MESA cases that need no data.
|
|
59
|
+
|
|
60
|
+
## What this means for the documentation
|
|
61
|
+
|
|
62
|
+
Every numerical claim in these docs traces to one of three places: the source code (embedded directly in the [demo pages](demos/index.md)), the docstrings (rendered in the [API Reference](api.md)), or the test suite described here. Nothing is quoted from memory.
|
|
@@ -190,7 +190,9 @@ contains
|
|
|
190
190
|
!
|
|
191
191
|
! Steps performed internally:
|
|
192
192
|
! 1. Interpolate filter transmission onto the SED wavelength grid
|
|
193
|
-
! 2.
|
|
193
|
+
! 2. In-band flux integration (photon counting by default; energy
|
|
194
|
+
! counting when photon_counting = 0, for filters tagged
|
|
195
|
+
! '# DetectorType: energy' -- see sed_model.filters)
|
|
194
196
|
! 3. m = -2.5 log10(F_band / F_zp)
|
|
195
197
|
!
|
|
196
198
|
! Parameters
|
|
@@ -200,6 +202,7 @@ contains
|
|
|
200
202
|
! filt_wave(nf) : filter wavelength grid (A)
|
|
201
203
|
! filt_trans(nf) : filter transmission [0,1]
|
|
202
204
|
! zero_point : precomputed photometric zero-point
|
|
205
|
+
! photon_counting : optional, 1 (default) photon / 0 energy weighting
|
|
203
206
|
! mag : output magnitude
|
|
204
207
|
! band_flux : output in-band flux (before zero-point)
|
|
205
208
|
! ierr : 0=ok, 1=integration failure, 2=non-positive flux
|
|
@@ -207,11 +210,13 @@ contains
|
|
|
207
210
|
subroutine synthetic_magnitude(sed_wave, obs_flux, nw, &
|
|
208
211
|
filt_wave, filt_trans, nf, &
|
|
209
212
|
zero_point, &
|
|
210
|
-
mag, band_flux, ierr)
|
|
213
|
+
mag, band_flux, ierr, photon_counting)
|
|
211
214
|
!f2py intent(in) :: sed_wave, obs_flux, nw
|
|
212
215
|
!f2py intent(in) :: filt_wave, filt_trans, nf
|
|
213
216
|
!f2py intent(in) :: zero_point
|
|
214
217
|
!f2py intent(out) :: mag, band_flux, ierr
|
|
218
|
+
!f2py integer, intent(in) :: photon_counting = 1
|
|
219
|
+
integer, intent(in) :: photon_counting ! 1 = photon counting (default), 0 = energy counting
|
|
215
220
|
integer, intent(in) :: nw, nf
|
|
216
221
|
real(dp), intent(in) :: sed_wave(nw), obs_flux(nw)
|
|
217
222
|
real(dp), intent(in) :: filt_wave(nf), filt_trans(nf)
|
|
@@ -227,7 +232,7 @@ contains
|
|
|
227
232
|
mag = BAD_MAG; band_flux = -1.0_dp; return
|
|
228
233
|
end if
|
|
229
234
|
|
|
230
|
-
call lib_synthetic_flux(sed_wave, obs_flux * filt_on_sed, filt_on_sed, band_flux)
|
|
235
|
+
call lib_synthetic_flux(sed_wave, obs_flux * filt_on_sed, filt_on_sed, band_flux, photon_counting /= 0)
|
|
231
236
|
if (band_flux <= 0.0_dp) then
|
|
232
237
|
mag = BAD_MAG; ierr = 1; return
|
|
233
238
|
end if
|
|
@@ -265,43 +270,49 @@ contains
|
|
|
265
270
|
|
|
266
271
|
subroutine vega_zero_point(vega_wave, vega_flux, nv, &
|
|
267
272
|
filt_wave, filt_trans, nf, &
|
|
268
|
-
zp, ierr)
|
|
273
|
+
zp, ierr, photon_counting)
|
|
269
274
|
!f2py intent(in) :: vega_wave, vega_flux, nv
|
|
270
275
|
!f2py intent(in) :: filt_wave, filt_trans, nf
|
|
271
276
|
!f2py intent(out) :: zp, ierr
|
|
277
|
+
!f2py integer, intent(in) :: photon_counting = 1
|
|
278
|
+
integer, intent(in) :: photon_counting
|
|
272
279
|
integer, intent(in) :: nv, nf
|
|
273
280
|
real(dp), intent(in) :: vega_wave(nv), vega_flux(nv)
|
|
274
281
|
real(dp), intent(in) :: filt_wave(nf), filt_trans(nf)
|
|
275
282
|
real(dp), intent(out) :: zp
|
|
276
283
|
integer, intent(out) :: ierr
|
|
277
284
|
|
|
278
|
-
zp = lib_vega_zp(vega_wave, vega_flux, filt_wave, filt_trans)
|
|
285
|
+
zp = lib_vega_zp(vega_wave, vega_flux, filt_wave, filt_trans, photon_counting /= 0)
|
|
279
286
|
ierr = merge(0, 1, zp > 0.0_dp)
|
|
280
287
|
end subroutine vega_zero_point
|
|
281
288
|
|
|
282
289
|
|
|
283
|
-
subroutine ab_zero_point(filt_wave, filt_trans, nf, zp, ierr)
|
|
290
|
+
subroutine ab_zero_point(filt_wave, filt_trans, nf, zp, ierr, photon_counting)
|
|
284
291
|
!f2py intent(in) :: filt_wave, filt_trans, nf
|
|
285
292
|
!f2py intent(out) :: zp, ierr
|
|
293
|
+
!f2py integer, intent(in) :: photon_counting = 1
|
|
294
|
+
integer, intent(in) :: photon_counting
|
|
286
295
|
integer, intent(in) :: nf
|
|
287
296
|
real(dp), intent(in) :: filt_wave(nf), filt_trans(nf)
|
|
288
297
|
real(dp), intent(out) :: zp
|
|
289
298
|
integer, intent(out) :: ierr
|
|
290
299
|
|
|
291
|
-
zp = lib_ab_zp(filt_wave, filt_trans)
|
|
300
|
+
zp = lib_ab_zp(filt_wave, filt_trans, photon_counting /= 0)
|
|
292
301
|
ierr = merge(0, 1, zp > 0.0_dp)
|
|
293
302
|
end subroutine ab_zero_point
|
|
294
303
|
|
|
295
304
|
|
|
296
|
-
subroutine st_zero_point(filt_wave, filt_trans, nf, zp, ierr)
|
|
305
|
+
subroutine st_zero_point(filt_wave, filt_trans, nf, zp, ierr, photon_counting)
|
|
297
306
|
!f2py intent(in) :: filt_wave, filt_trans, nf
|
|
298
307
|
!f2py intent(out) :: zp, ierr
|
|
308
|
+
!f2py integer, intent(in) :: photon_counting = 1
|
|
309
|
+
integer, intent(in) :: photon_counting
|
|
299
310
|
integer, intent(in) :: nf
|
|
300
311
|
real(dp), intent(in) :: filt_wave(nf), filt_trans(nf)
|
|
301
312
|
real(dp), intent(out) :: zp
|
|
302
313
|
integer, intent(out) :: ierr
|
|
303
314
|
|
|
304
|
-
zp = lib_st_zp(filt_wave, filt_trans)
|
|
315
|
+
zp = lib_st_zp(filt_wave, filt_trans, photon_counting /= 0)
|
|
305
316
|
ierr = merge(0, 1, zp > 0.0_dp)
|
|
306
317
|
end subroutine st_zero_point
|
|
307
318
|
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
! ***********************************************************************
|
|
23
23
|
|
|
24
24
|
module bolometric
|
|
25
|
-
use colors_def, only: dp,
|
|
25
|
+
use colors_def, only: dp, BOL_MAG_NONPOSITIVE_FLUX, F_BOL_ZERO_POINT
|
|
26
26
|
use colors_utils, only: simpson_integration, is_finite
|
|
27
27
|
implicit none
|
|
28
28
|
|
|
@@ -75,7 +75,7 @@ contains
|
|
|
75
75
|
if (bol_flux > 0.0_dp) then
|
|
76
76
|
bol_mag = -2.5_dp * log10(bol_flux / F_BOL_ZERO_POINT)
|
|
77
77
|
else
|
|
78
|
-
bol_mag =
|
|
78
|
+
bol_mag = BOL_MAG_NONPOSITIVE_FLUX
|
|
79
79
|
ierr = 1
|
|
80
80
|
end if
|
|
81
81
|
end subroutine bolometric_magnitude
|
|
@@ -35,18 +35,30 @@ module synthetic
|
|
|
35
35
|
|
|
36
36
|
contains
|
|
37
37
|
|
|
38
|
-
!
|
|
38
|
+
! In-band flux from an already-convolved SED. Photon counting (the
|
|
39
|
+
! default, as in MESA):
|
|
39
40
|
! F_band = integral(convolved_flux * lambda) / integral(filter * lambda)
|
|
41
|
+
! Energy counting (filters tagged '# DetectorType: energy'):
|
|
42
|
+
! F_band = integral(convolved_flux) / integral(filter)
|
|
40
43
|
! `convolved_flux` is obs_flux * filter_on_sed_grid, computed by the
|
|
41
44
|
! caller -- matches MESA's calling convention in synthetic.f90.
|
|
42
|
-
subroutine calculate_synthetic_flux(wavelengths, convolved_flux, filter_on_sed_grid, synthetic_flux)
|
|
45
|
+
subroutine calculate_synthetic_flux(wavelengths, convolved_flux, filter_on_sed_grid, synthetic_flux, photon_counting)
|
|
43
46
|
real(dp), intent(in) :: wavelengths(:), convolved_flux(:), filter_on_sed_grid(:)
|
|
44
47
|
real(dp), intent(out) :: synthetic_flux
|
|
48
|
+
logical, optional, intent(in) :: photon_counting
|
|
45
49
|
|
|
46
50
|
real(dp) :: integrated_flux, integrated_filter
|
|
51
|
+
logical :: use_photons
|
|
47
52
|
|
|
48
|
-
|
|
49
|
-
|
|
53
|
+
use_photons = .true.
|
|
54
|
+
if (present(photon_counting)) use_photons = photon_counting
|
|
55
|
+
if (use_photons) then
|
|
56
|
+
call simpson_integration(wavelengths, convolved_flux * wavelengths, integrated_flux)
|
|
57
|
+
call simpson_integration(wavelengths, filter_on_sed_grid * wavelengths, integrated_filter)
|
|
58
|
+
else
|
|
59
|
+
call simpson_integration(wavelengths, convolved_flux, integrated_flux)
|
|
60
|
+
call simpson_integration(wavelengths, filter_on_sed_grid, integrated_filter)
|
|
61
|
+
end if
|
|
50
62
|
|
|
51
63
|
if (integrated_filter > 0.0_dp) then
|
|
52
64
|
synthetic_flux = integrated_flux / integrated_filter
|
|
@@ -72,36 +84,30 @@ contains
|
|
|
72
84
|
|
|
73
85
|
|
|
74
86
|
! Vega zero-point -- called once at filter-load time.
|
|
75
|
-
real(dp) function compute_vega_zero_point(vega_wave, vega_flux, filt_wave, filt_trans)
|
|
87
|
+
real(dp) function compute_vega_zero_point(vega_wave, vega_flux, filt_wave, filt_trans, photon_counting)
|
|
76
88
|
real(dp), intent(in) :: vega_wave(:), vega_flux(:)
|
|
77
89
|
real(dp), intent(in) :: filt_wave(:), filt_trans(:)
|
|
90
|
+
logical, optional, intent(in) :: photon_counting
|
|
78
91
|
|
|
79
|
-
real(dp) :: int_flux, int_filter
|
|
80
92
|
real(dp), allocatable :: filt_on_vega(:)
|
|
81
93
|
integer :: ierr
|
|
82
94
|
|
|
83
95
|
allocate (filt_on_vega(size(vega_wave)))
|
|
84
96
|
call interp_filter_onto_sed(filt_wave, filt_trans, vega_wave, filt_on_vega, ierr)
|
|
85
97
|
|
|
86
|
-
call
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
if (int_filter > 0.0_dp) then
|
|
90
|
-
compute_vega_zero_point = int_flux / int_filter
|
|
91
|
-
else
|
|
92
|
-
compute_vega_zero_point = -1.0_dp
|
|
93
|
-
end if
|
|
98
|
+
call calculate_synthetic_flux(vega_wave, vega_flux * filt_on_vega, filt_on_vega, &
|
|
99
|
+
compute_vega_zero_point, photon_counting)
|
|
94
100
|
|
|
95
101
|
deallocate (filt_on_vega)
|
|
96
102
|
end function compute_vega_zero_point
|
|
97
103
|
|
|
98
104
|
|
|
99
105
|
! AB zero-point -- f_nu = 3631 Jy converted to f_lambda on the filter grid.
|
|
100
|
-
real(dp) function compute_ab_zero_point(filt_wave, filt_trans)
|
|
106
|
+
real(dp) function compute_ab_zero_point(filt_wave, filt_trans, photon_counting)
|
|
101
107
|
real(dp), intent(in) :: filt_wave(:), filt_trans(:)
|
|
108
|
+
logical, optional, intent(in) :: photon_counting
|
|
102
109
|
|
|
103
110
|
integer :: i
|
|
104
|
-
real(dp) :: int_flux, int_filter
|
|
105
111
|
real(dp), allocatable :: f_ab(:)
|
|
106
112
|
|
|
107
113
|
allocate (f_ab(size(filt_wave)))
|
|
@@ -113,37 +119,25 @@ contains
|
|
|
113
119
|
end if
|
|
114
120
|
end do
|
|
115
121
|
|
|
116
|
-
call
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (int_filter > 0.0_dp) then
|
|
120
|
-
compute_ab_zero_point = int_flux / int_filter
|
|
121
|
-
else
|
|
122
|
-
compute_ab_zero_point = -1.0_dp
|
|
123
|
-
end if
|
|
122
|
+
call calculate_synthetic_flux(filt_wave, f_ab * filt_trans, filt_trans, &
|
|
123
|
+
compute_ab_zero_point, photon_counting)
|
|
124
124
|
|
|
125
125
|
deallocate (f_ab)
|
|
126
126
|
end function compute_ab_zero_point
|
|
127
127
|
|
|
128
128
|
|
|
129
129
|
! ST zero-point -- flat f_lambda = 3.63e-9 erg/s/cm^2/A.
|
|
130
|
-
real(dp) function compute_st_zero_point(filt_wave, filt_trans)
|
|
130
|
+
real(dp) function compute_st_zero_point(filt_wave, filt_trans, photon_counting)
|
|
131
131
|
real(dp), intent(in) :: filt_wave(:), filt_trans(:)
|
|
132
|
+
logical, optional, intent(in) :: photon_counting
|
|
132
133
|
|
|
133
|
-
real(dp) :: int_flux, int_filter
|
|
134
134
|
real(dp), allocatable :: f_st(:)
|
|
135
135
|
|
|
136
136
|
allocate (f_st(size(filt_wave)))
|
|
137
137
|
f_st = ST_FLAM_ZP
|
|
138
138
|
|
|
139
|
-
call
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
if (int_filter > 0.0_dp) then
|
|
143
|
-
compute_st_zero_point = int_flux / int_filter
|
|
144
|
-
else
|
|
145
|
-
compute_st_zero_point = -1.0_dp
|
|
146
|
-
end if
|
|
139
|
+
call calculate_synthetic_flux(filt_wave, f_st * filt_trans, filt_trans, &
|
|
140
|
+
compute_st_zero_point, photon_counting)
|
|
147
141
|
|
|
148
142
|
deallocate (f_st)
|
|
149
143
|
end function compute_st_zero_point
|
|
@@ -25,7 +25,10 @@ module colors_def
|
|
|
25
25
|
real(dp), parameter :: CLIGHT_CM_S = 2.99792458d10
|
|
26
26
|
real(dp), parameter :: AB_FNU_ZP = 3.631d-20 ! 3631 Jy in erg/s/cm^2/Hz
|
|
27
27
|
real(dp), parameter :: ST_FLAM_ZP = 3.63d-9 ! flat f_lambda zp erg/s/cm^2/A
|
|
28
|
-
real(dp), parameter :: BAD_MAG = -99.9d0 ! failure sentinel for magnitudes
|
|
28
|
+
real(dp), parameter :: BAD_MAG = -99.9d0 ! failure sentinel for band magnitudes
|
|
29
|
+
! MESA colors/private/bolometric.f90 returns 99.0 as the bolometric
|
|
30
|
+
! magnitude when the integrated flux is not positive.
|
|
31
|
+
real(dp), parameter :: BOL_MAG_NONPOSITIVE_FLUX = 99.0d0
|
|
29
32
|
real(dp), parameter :: TINY_VALUE = 1.0d-300 ! positivity floor for linear interpolation
|
|
30
33
|
|
|
31
34
|
! Bolometric zero-point: irradiance corresponding to M_bol = 0 (IAU 2015
|
|
@@ -39,7 +42,7 @@ module colors_def
|
|
|
39
42
|
LSUN_ERG_S * 10.0_dp**(0.4_dp*MBOL_SUN) / (4.0_dp*PI_DP*(10.0_dp*PC_CM)**2)
|
|
40
43
|
|
|
41
44
|
public :: dp
|
|
42
|
-
public :: CLIGHT_CM_S, AB_FNU_ZP, ST_FLAM_ZP, BAD_MAG, TINY_VALUE
|
|
45
|
+
public :: CLIGHT_CM_S, AB_FNU_ZP, ST_FLAM_ZP, BAD_MAG, BOL_MAG_NONPOSITIVE_FLUX, TINY_VALUE
|
|
43
46
|
public :: F_BOL_ZERO_POINT
|
|
44
47
|
|
|
45
48
|
end module colors_def
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# and for the others the checks below give a readable error if one is missing.
|
|
3
3
|
project(
|
|
4
4
|
'sed_model',
|
|
5
|
-
version: '0.
|
|
5
|
+
version: '0.4.0',
|
|
6
6
|
meson_version: '>=1.1.0',
|
|
7
7
|
)
|
|
8
8
|
|
|
@@ -34,6 +34,7 @@ if components in ['all', 'python']
|
|
|
34
34
|
files(
|
|
35
35
|
'sed_model/__init__.py',
|
|
36
36
|
'sed_model/_cc_ext.py',
|
|
37
|
+
'sed_model/_inv_core.py',
|
|
37
38
|
'sed_model/filters.py',
|
|
38
39
|
'sed_model/forward.py',
|
|
39
40
|
'sed_model/grid.py',
|