specsy 0.9.dev1__tar.gz → 0.9.dev3__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.
- specsy-0.9.dev3/PKG-INFO +76 -0
- specsy-0.9.dev3/README.md +40 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/pyproject.toml +14 -6
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/__init__.py +3 -1
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/io.py +8 -0
- specsy-0.9.dev3/src/specsy/models/__init__.py +2 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/models/chemistry.py +126 -66
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/models/chemistry_inference.py +3 -3
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/models/fluxes_line.py +26 -7
- specsy-0.9.dev3/src/specsy/models/literature.py +130 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/observations.py +94 -21
- specsy-0.9.dev3/src/specsy/plotting/bokeh_functions.py +227 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/plotting/plots.py +188 -7
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/plotting/specsy_theme.toml +13 -2
- specsy-0.9.dev3/src/specsy/resources/data/Benjamin1999_OpticalDepthFunctionCoefficients.txt +5 -0
- specsy-0.9.dev3/src/specsy/resources/data/HI_t3_elec.ascii +55 -0
- specsy-0.9.dev3/src/specsy/resources/data/HeII_t4_elec.ascii +110 -0
- specsy-0.9.dev3/src/specsy/resources/data/HeI_t5_elec.ascii +334 -0
- specsy-0.9.dev3/src/specsy/resources/data/gordon_2003_LMC2_supershell.txt +33 -0
- specsy-0.9.dev3/src/specsy/resources/data/gordon_2003_LMC_average.txt +32 -0
- specsy-0.9.dev3/src/specsy/resources/data/gordon_2003_SMC_bar.txt +37 -0
- specsy-0.9.dev3/src/specsy/resources/images/Specsy_logo_transparent_dark.PNG +0 -0
- specsy-0.9.dev3/src/specsy/resources/images/Specsy_logo_transparent_light.PNG +0 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/sampler.py +20 -9
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/specsy.toml +56 -42
- specsy-0.9.dev3/src/specsy.egg-info/PKG-INFO +76 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy.egg-info/SOURCES.txt +11 -2
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy.egg-info/requires.txt +7 -3
- specsy-0.9.dev1/PKG-INFO +0 -57
- specsy-0.9.dev1/README.rst +0 -25
- specsy-0.9.dev1/src/specsy/models/__init__.py +0 -2
- specsy-0.9.dev1/src/specsy/models/literature.py +0 -35
- specsy-0.9.dev1/src/specsy/plotting/bokeh_functions.py +0 -63
- specsy-0.9.dev1/src/specsy.egg-info/PKG-INFO +0 -57
- {specsy-0.9.dev1 → specsy-0.9.dev3}/MANIFEST.in +0 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/setup.cfg +0 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/core.py +0 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/emission.py +0 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/inference.py +0 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/models/emissivity.py +0 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/models/extinction.py +0 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/models/nebular_continuum.py +0 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/operations/__init__.py +0 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/operations/interpolation.py +0 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/operations/pytensors.py +0 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/operations/tensors.py +0 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/operations/tests.py +0 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/plotting/__init__.py +0 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/tools.py +0 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/treatement.py +0 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy.egg-info/dependency_links.txt +0 -0
- {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy.egg-info/top_level.txt +0 -0
specsy-0.9.dev3/PKG-INFO
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: specsy
|
|
3
|
+
Version: 0.9.dev3
|
|
4
|
+
Summary: Model fitting package for the chemical analysis of astronomical spectra
|
|
5
|
+
Author-email: Vital Fernández <vgf@umich.edu>
|
|
6
|
+
License-Expression: GPL-3.0-or-later
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
Requires-Dist: arviz~=0.23.4
|
|
12
|
+
Requires-Dist: corner~=2.2
|
|
13
|
+
Requires-Dist: h5netcdf~=1.3.0
|
|
14
|
+
Requires-Dist: jax~=0.9.2
|
|
15
|
+
Requires-Dist: jaxlib~=0.9.2
|
|
16
|
+
Requires-Dist: aspect-stable~=0.7.dev2
|
|
17
|
+
Requires-Dist: lime-stable~=2.2.dev5
|
|
18
|
+
Requires-Dist: innate-stable~=0.2.2
|
|
19
|
+
Requires-Dist: pymc~=5.28
|
|
20
|
+
Requires-Dist: toml~=0.10
|
|
21
|
+
Requires-Dist: xarray~=2026.2.0
|
|
22
|
+
Provides-Extra: tensors
|
|
23
|
+
Requires-Dist: pytensor~=2.38.2; extra == "tensors"
|
|
24
|
+
Requires-Dist: blackjax~=1.5; extra == "tensors"
|
|
25
|
+
Requires-Dist: numba~=0.65.1; extra == "tensors"
|
|
26
|
+
Requires-Dist: numpyro~=0.21.0; extra == "tensors"
|
|
27
|
+
Requires-Dist: nutpie~=0.16.8; extra == "tensors"
|
|
28
|
+
Provides-Extra: docs
|
|
29
|
+
Requires-Dist: sphinx-rtd-theme~=3.0; extra == "docs"
|
|
30
|
+
Requires-Dist: ipympl~=0.9; extra == "docs"
|
|
31
|
+
Requires-Dist: myst-nb~=1.3; extra == "docs"
|
|
32
|
+
Provides-Extra: tests
|
|
33
|
+
Requires-Dist: pytest~=8.4; extra == "tests"
|
|
34
|
+
Requires-Dist: pytest-cov~=7.0; extra == "tests"
|
|
35
|
+
Requires-Dist: pytest-mpl~=0.17; extra == "tests"
|
|
36
|
+
|
|
37
|
+
# Specsy
|
|
38
|
+
|
|
39
|
+
<p align="center">
|
|
40
|
+
<img src="https://github.com/Vital-Fernandez/specsy/blob/7e35568f6d154486f5603e94fe39dd08e5e54834/src/specsy/resources/images/Specsy_logo_transparent_dark.PNG" alt="Specsy Logo" width="300"/>
|
|
41
|
+
</p>
|
|
42
|
+
|
|
43
|
+
A Python library for the analysis of astronomical spectra. Specsy includes a Bayesian sampler for the
|
|
44
|
+
direct method parameter space, tools to fit photoionization model grids, and utilities for the analysis
|
|
45
|
+
of stellar and gas continua.
|
|
46
|
+
|
|
47
|
+
> **Note:** This package is currently in an alpha release. The preliminary documentation can be found at [ReadTheDocs](https://specsy.readthedocs.io/).
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
Install directly from [PyPI](https://pypi.org/project/specsy/):
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install specsy
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
For the recommended conda environment with PyMC sampler backends:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
conda create -c conda-forge -n specsy python=3.13 nutpie pymc numba numpyro blackjax
|
|
61
|
+
conda activate specsy
|
|
62
|
+
pip install specsy
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
To upgrade to the latest version:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install --upgrade specsy
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Development
|
|
72
|
+
|
|
73
|
+
SpecSy is currently in an alpha release. Please check the [GitHub repository](https://github.com/Vital-Fernandez/specsy)
|
|
74
|
+
for the latest version or to report any issues.
|
|
75
|
+
|
|
76
|
+
**Author:** Vital Fernández — [vgf@stsci.edu](mailto:vgf@stsci.edu)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Specsy
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://github.com/Vital-Fernandez/specsy/blob/7e35568f6d154486f5603e94fe39dd08e5e54834/src/specsy/resources/images/Specsy_logo_transparent_dark.PNG" alt="Specsy Logo" width="300"/>
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
A Python library for the analysis of astronomical spectra. Specsy includes a Bayesian sampler for the
|
|
8
|
+
direct method parameter space, tools to fit photoionization model grids, and utilities for the analysis
|
|
9
|
+
of stellar and gas continua.
|
|
10
|
+
|
|
11
|
+
> **Note:** This package is currently in an alpha release. The preliminary documentation can be found at [ReadTheDocs](https://specsy.readthedocs.io/).
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
Install directly from [PyPI](https://pypi.org/project/specsy/):
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install specsy
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
For the recommended conda environment with PyMC sampler backends:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
conda create -c conda-forge -n specsy python=3.13 nutpie pymc numba numpyro blackjax
|
|
25
|
+
conda activate specsy
|
|
26
|
+
pip install specsy
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
To upgrade to the latest version:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install --upgrade specsy
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Development
|
|
36
|
+
|
|
37
|
+
SpecSy is currently in an alpha release. Please check the [GitHub repository](https://github.com/Vital-Fernandez/specsy)
|
|
38
|
+
for the latest version or to report any issues.
|
|
39
|
+
|
|
40
|
+
**Author:** Vital Fernández — [vgf@stsci.edu](mailto:vgf@stsci.edu)
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specsy"
|
|
7
|
-
version = "0.9.
|
|
8
|
-
readme = "README.
|
|
7
|
+
version = "0.9.dev3"
|
|
8
|
+
readme = "README.md"
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
license = "GPL-3.0-or-later"
|
|
11
11
|
authors = [{name = "Vital Fernández", email = "vgf@umich.edu"}]
|
|
@@ -15,9 +15,9 @@ dependencies = ["arviz~=0.23.4",
|
|
|
15
15
|
"h5netcdf~=1.3.0",
|
|
16
16
|
"jax~=0.9.2",
|
|
17
17
|
"jaxlib~=0.9.2",
|
|
18
|
-
"aspect-stable~=0.7.
|
|
19
|
-
"lime-stable~=2.2.
|
|
20
|
-
"innate-stable~=0.2.
|
|
18
|
+
"aspect-stable~=0.7.dev2",
|
|
19
|
+
"lime-stable~=2.2.dev5",
|
|
20
|
+
"innate-stable~=0.2.2",
|
|
21
21
|
"pymc~=5.28",
|
|
22
22
|
"toml~=0.10",
|
|
23
23
|
"xarray~=2026.2.0"]
|
|
@@ -26,7 +26,11 @@ classifiers = ["Programming Language :: Python :: 3",
|
|
|
26
26
|
"Programming Language :: Python :: 3.11"]
|
|
27
27
|
|
|
28
28
|
[project.optional-dependencies]
|
|
29
|
-
tensors = ["pytensor~=2.38.2"
|
|
29
|
+
tensors = ["pytensor~=2.38.2",
|
|
30
|
+
"blackjax~=1.5",
|
|
31
|
+
"numba~=0.65.1",
|
|
32
|
+
"numpyro~=0.21.0",
|
|
33
|
+
"nutpie~=0.16.8"]
|
|
30
34
|
|
|
31
35
|
docs = ["sphinx-rtd-theme~=3.0",
|
|
32
36
|
"ipympl~=0.9",
|
|
@@ -43,3 +47,7 @@ mpl-baseline-path = 'tests/baseline'
|
|
|
43
47
|
mpl-results-path = 'tests/outputs'
|
|
44
48
|
mpl-results-always = false
|
|
45
49
|
addopts = "-p no:asdf_schema_tester"
|
|
50
|
+
|
|
51
|
+
[tool.setuptools.package-data]
|
|
52
|
+
specsy = ["resources/data/*",
|
|
53
|
+
"resources/images/*"]
|
|
@@ -23,5 +23,7 @@ __version__ = _setup_cfg['metadata']['version']
|
|
|
23
23
|
from lime import load_cfg as load_cfg, load_frame as load_frame, Line as Line, lines_frame as lines_frame, save_frame as save_frame
|
|
24
24
|
from lime import Spectrum as Spectrum, Cube as Cube
|
|
25
25
|
from innate import load_dataset as load_dataset
|
|
26
|
+
from specsy.io import specsy_cfg as cfg
|
|
26
27
|
from specsy.observations import Nebula
|
|
27
|
-
from specsy.models.extinction import extinction_coeff_calc
|
|
28
|
+
from specsy.models.extinction import extinction_coeff_calc
|
|
29
|
+
from specsy.plotting.plots import theme
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import logging
|
|
1
2
|
import os
|
|
2
3
|
import numpy as np
|
|
3
4
|
import configparser
|
|
@@ -5,6 +6,10 @@ from pathlib import Path
|
|
|
5
6
|
from collections.abc import Sequence
|
|
6
7
|
from astropy.io import fits
|
|
7
8
|
from innate import load_dataset
|
|
9
|
+
from lime import load_cfg
|
|
10
|
+
|
|
11
|
+
_logger = logging.getLogger('SpecSy')
|
|
12
|
+
|
|
8
13
|
|
|
9
14
|
FITS_INPUTS_EXTENSION = {'lines_list': '20A', 'line_fluxes': 'E', 'line_err': 'E'}
|
|
10
15
|
|
|
@@ -16,6 +21,9 @@ FITS_OUTPUTS_EXTENSION = {'parameter_list': '20A',
|
|
|
16
21
|
'p84th': 'E',
|
|
17
22
|
'true': 'E'}
|
|
18
23
|
|
|
24
|
+
# Load lime configuration
|
|
25
|
+
_cfg_file_path = Path(__file__).parent/'specsy.toml'
|
|
26
|
+
specsy_cfg = load_cfg(_cfg_file_path)
|
|
19
27
|
|
|
20
28
|
class SpecSyError(Exception):
|
|
21
29
|
"""SpecSy exception function"""
|
|
@@ -2,19 +2,20 @@ import logging
|
|
|
2
2
|
import warnings
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
|
|
5
|
+
import lime
|
|
5
6
|
import numpy as np
|
|
6
7
|
import arviz as az
|
|
7
8
|
import xarray as xr
|
|
8
9
|
from lime import label_decomposition, Line, lines_frame, normalize_fluxes
|
|
9
|
-
|
|
10
|
+
from pathlib import Path
|
|
10
11
|
from innate import load_dataset
|
|
11
12
|
|
|
12
|
-
from specsy import
|
|
13
|
-
from specsy.io import SpecSyError
|
|
13
|
+
from specsy.io import SpecSyError, specsy_cfg
|
|
14
14
|
from specsy.tools import truncated_gaussian, flux_distribution
|
|
15
15
|
from specsy.operations.interpolation import compile_bilinear_interp
|
|
16
16
|
from specsy.models.extinction import flambda_calc
|
|
17
|
-
from specsy.models.literature import
|
|
17
|
+
from specsy.models.literature import TEM_FUNC_DICT, DEN_FUNC_DICT
|
|
18
|
+
from specsy.models.fluxes_line import DEFAULT_PARTICLE_EQUATIONS_KEYS, FLUX_EQUATION_DICT
|
|
18
19
|
from specsy.sampler import direct_method_multi_region, run_model
|
|
19
20
|
from matplotlib import pyplot as plt
|
|
20
21
|
|
|
@@ -272,17 +273,52 @@ def package_results(fname, inference_data, inputs=None, prior_dict=None, true_va
|
|
|
272
273
|
return
|
|
273
274
|
|
|
274
275
|
|
|
275
|
-
@dataclass
|
|
276
276
|
class InputsDirectMethod:
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
277
|
+
|
|
278
|
+
def __init__(self, input_lines, input_flux, input_err, flambda_arr, ion_arr,
|
|
279
|
+
temp_id_arr, den_id_arr, tem_eq_arr, den_eq_arr, eq_flux_arr,):
|
|
280
|
+
|
|
281
|
+
self.labels = input_lines
|
|
282
|
+
self.flux_arr = input_flux
|
|
283
|
+
self.err_arr = input_err
|
|
284
|
+
self.flambda_arr = flambda_arr
|
|
285
|
+
self.ion_arr = ion_arr
|
|
286
|
+
self.temp_id_arr = temp_id_arr
|
|
287
|
+
self.den_id_arr = den_id_arr
|
|
288
|
+
self.eq_tem_arr = tem_eq_arr
|
|
289
|
+
self.eq_den_arr = den_eq_arr
|
|
290
|
+
self.eq_flux_arr = eq_flux_arr
|
|
291
|
+
|
|
292
|
+
@classmethod
|
|
293
|
+
def from_dataframe(cls, lines_structure):
|
|
294
|
+
|
|
295
|
+
# Remove normalization line
|
|
296
|
+
if 'norm_line' in lines_structure.columns:
|
|
297
|
+
idcs = ~lines_structure.index.isin(lines_structure['norm_line'].unique())
|
|
298
|
+
else:
|
|
299
|
+
idcs = np.ones(lines_structure.index.size).astype(bool)
|
|
300
|
+
|
|
301
|
+
# Line inputs the data
|
|
302
|
+
input_lines = lines_structure.loc[idcs].index.to_numpy()
|
|
303
|
+
input_flux = lines_structure.loc[idcs].line_flux.to_numpy()
|
|
304
|
+
input_err = lines_structure.loc[idcs].line_flux_err.to_numpy()
|
|
305
|
+
|
|
306
|
+
# Unpack physical parameters
|
|
307
|
+
flambda_arr = lines_structure.loc[idcs].f_lambda.to_numpy()
|
|
308
|
+
ion_arr = lines_structure.loc[idcs].particle.to_numpy()
|
|
309
|
+
|
|
310
|
+
# Unpack the temp/den structure arrays
|
|
311
|
+
temp_id_arr = lines_structure.loc[idcs].temp.to_numpy()
|
|
312
|
+
den_id_arr = lines_structure.loc[idcs].den.to_numpy()
|
|
313
|
+
tem_eq_arr = lines_structure.loc[idcs].eq_temp.to_numpy()
|
|
314
|
+
den_eq_arr = lines_structure.loc[idcs].eq_den.to_numpy()
|
|
315
|
+
eq_flux_arr = lines_structure.loc[idcs].eq_flux.to_numpy()
|
|
316
|
+
|
|
317
|
+
print(f'Multi-region direct method sampler')
|
|
318
|
+
print(f'- Readying inputs:')
|
|
319
|
+
|
|
320
|
+
return cls(input_lines, input_flux, input_err, flambda_arr, ion_arr,
|
|
321
|
+
temp_id_arr, den_id_arr, tem_eq_arr, den_eq_arr, eq_flux_arr)
|
|
286
322
|
|
|
287
323
|
|
|
288
324
|
class DirectMethod:
|
|
@@ -290,7 +326,7 @@ class DirectMethod:
|
|
|
290
326
|
def __init__(self, lines_df, ion_struct):
|
|
291
327
|
|
|
292
328
|
# Default prior cfg
|
|
293
|
-
self.prior_cfg =
|
|
329
|
+
self.prior_cfg = specsy_cfg['direct_method_priors']
|
|
294
330
|
|
|
295
331
|
# Default emissivity
|
|
296
332
|
self.emis_interp = None
|
|
@@ -310,43 +346,70 @@ class DirectMethod:
|
|
|
310
346
|
|
|
311
347
|
return
|
|
312
348
|
|
|
313
|
-
def prepare_inputs(self,
|
|
314
|
-
|
|
349
|
+
def prepare_inputs(self, line_list=None, emissivity_source=None, prior_cfg=None, kinematic_component=0,
|
|
350
|
+
R_V=3.1, law='G03 LMC', norm_line='H1_4861A', normalize_flux=True, flux_column='profile_flux',
|
|
351
|
+
review_model=True):
|
|
315
352
|
|
|
316
353
|
# Check the lines frame and normalization line
|
|
317
354
|
if self._lines_frame is None:
|
|
318
355
|
raise SpecSyError(f'The object does not have a lines_frame declared')
|
|
319
356
|
|
|
320
|
-
self.norm_line =
|
|
357
|
+
self.norm_line = norm_line
|
|
321
358
|
if self.norm_line not in self._lines_frame.index:
|
|
322
359
|
raise SpecSyError(f'The normalization line "{self.norm_line}" is not in the input lines frame')
|
|
323
360
|
|
|
324
361
|
# Prepare the emissivity interpolator
|
|
325
|
-
|
|
362
|
+
if isinstance(emissivity_source, (str, Path)):
|
|
363
|
+
emis_dataset = load_dataset(emissivity_source)
|
|
364
|
+
else:
|
|
365
|
+
emis_dataset = emissivity_source
|
|
326
366
|
self.emis_interp = compile_bilinear_interp(emis_dataset)
|
|
327
367
|
|
|
328
368
|
# Prepare the prior cfg
|
|
329
369
|
self.prior_cfg = self.prior_cfg if prior_cfg is None else prior_cfg
|
|
330
370
|
|
|
371
|
+
# Select the lines
|
|
372
|
+
if line_list is None:
|
|
373
|
+
self.lines_structure = self._lines_frame.copy()
|
|
374
|
+
else:
|
|
375
|
+
self.lines_structure = self._lines_frame.loc[self._lines_frame.index.isin(line_list)].copy()
|
|
376
|
+
|
|
377
|
+
# Remove the kinematic components
|
|
378
|
+
if kinematic_component == 0:
|
|
379
|
+
idcs_kinem = ~self.lines_structure.index.str.contains('_k-')
|
|
380
|
+
else:
|
|
381
|
+
idcs_kinem = self.lines_structure.index.contains(f'_k-{kinematic_component}')
|
|
382
|
+
self.lines_structure = self.lines_structure.loc[idcs_kinem]
|
|
383
|
+
|
|
331
384
|
# Make a copy of the frame and normalize the fluxes
|
|
332
385
|
if normalize_flux:
|
|
333
|
-
self.lines_structure = normalize_fluxes(self.
|
|
386
|
+
self.lines_structure = normalize_fluxes(self.lines_structure, norm_list=norm_line, flux_column=flux_column,
|
|
387
|
+
clear_empty=True)
|
|
334
388
|
else:
|
|
335
|
-
self.lines_structure
|
|
389
|
+
self.lines_structure.insert(3, 'norm_line', self.norm_line)
|
|
336
390
|
|
|
337
391
|
# Compute the reddening law
|
|
338
392
|
flambda_arr = flambda_calc(self.lines_structure.wavelength, R_V, law, self.lines_structure.loc['H1_4861A'].wavelength)
|
|
339
|
-
|
|
393
|
+
if 'f_lambda' not in self.lines_structure.columns:
|
|
394
|
+
self.lines_structure.insert(4, 'f_lambda', flambda_arr)
|
|
340
395
|
|
|
341
396
|
# Map the target lines to the ionization structure
|
|
342
|
-
self.lines_structure = self.ion_struct.map_line_structure(self.lines_structure
|
|
397
|
+
self.lines_structure = self.ion_struct.map_line_structure(self.lines_structure)
|
|
398
|
+
|
|
399
|
+
# Add the equations per ion
|
|
400
|
+
if 'eq_flux' not in self.lines_structure.columns:
|
|
401
|
+
self.lines_structure.insert(9, 'eq_flux', '-')
|
|
402
|
+
for line_label in self.lines_structure.index:
|
|
403
|
+
eq_name = DEFAULT_PARTICLE_EQUATIONS_KEYS.get(self.lines_structure.at[line_label, 'particle'], 'metals')
|
|
404
|
+
self.lines_structure.loc[line_label, 'eq_flux'] = eq_name
|
|
343
405
|
|
|
344
406
|
# Check for issues
|
|
345
|
-
|
|
407
|
+
if review_model:
|
|
408
|
+
self._review_inputs()
|
|
346
409
|
|
|
347
410
|
return
|
|
348
411
|
|
|
349
|
-
def _review_inputs(self):
|
|
412
|
+
def _review_inputs(self, return_message=False):
|
|
350
413
|
|
|
351
414
|
errors = []
|
|
352
415
|
no_dash = self.lines_structure['region'] != '-'
|
|
@@ -364,7 +427,7 @@ class DirectMethod:
|
|
|
364
427
|
errors.append(f"'{col}' is '-' for non-'-' region rows at lines: {bad}")
|
|
365
428
|
|
|
366
429
|
# 3) eq_temp / eq_den values must be keys in their respective dicts
|
|
367
|
-
for col, d in (('eq_temp',
|
|
430
|
+
for col, d in (('eq_temp', TEM_FUNC_DICT), ('eq_den', DEN_FUNC_DICT)):
|
|
368
431
|
mask = self.lines_structure[col] != '-'
|
|
369
432
|
bad = self.lines_structure.index[mask & ~self.lines_structure[col].isin(d)].tolist()
|
|
370
433
|
if bad:
|
|
@@ -380,64 +443,61 @@ class DirectMethod:
|
|
|
380
443
|
# 5) Check if we have emissivity data
|
|
381
444
|
bad = [idx for idx in self.lines_structure.index if idx not in self.emis_interp]
|
|
382
445
|
if bad:
|
|
383
|
-
errors.append(f"
|
|
384
|
-
|
|
385
|
-
if errors:
|
|
386
|
-
msg = "lines_structure validation failed:\n" + "\n".join(f" - {e}" for e in errors)
|
|
387
|
-
warnings.warn(msg)
|
|
388
|
-
raise ValueError(msg)
|
|
446
|
+
errors.append(f"Missing emissivity data for transitions: {bad}")
|
|
389
447
|
|
|
390
|
-
|
|
448
|
+
# 6) Check the flux equation is recognized
|
|
449
|
+
eq_flux_names = self.lines_structure.eq_flux.unique()
|
|
450
|
+
bad = [eq_name for eq_name in eq_flux_names if eq_name not in FLUX_EQUATION_DICT]
|
|
451
|
+
if bad:
|
|
452
|
+
errors.append(f"Flux equation not available in database for: {bad}")
|
|
391
453
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
454
|
+
if not return_message:
|
|
455
|
+
if errors:
|
|
456
|
+
msg = "lines_structure validation failed:\n" + "\n".join(f" - {e}" for e in errors)
|
|
457
|
+
warnings.warn(msg)
|
|
458
|
+
raise ValueError(msg)
|
|
459
|
+
else:
|
|
460
|
+
return None
|
|
461
|
+
else:
|
|
462
|
+
return errors
|
|
396
463
|
|
|
397
|
-
# Unpack physical parameters
|
|
398
|
-
flambda_arr = self.lines_structure.f_lambda.to_numpy()
|
|
399
|
-
ion_arr = self.lines_structure.particle.to_numpy()
|
|
400
464
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
den_id_arr = self.lines_structure.den.to_numpy()
|
|
404
|
-
tem_eq_arr = self.lines_structure.eq_temp.to_numpy()
|
|
405
|
-
den_eq_arr = self.lines_structure.eq_den.to_numpy()
|
|
465
|
+
def run(self, draws=1000, tune=2000, chains=8, cores=8, target_accept=0.8, nuts_sampler='numpyro', callback=None,
|
|
466
|
+
linear_scale_results=True):
|
|
406
467
|
|
|
407
|
-
#
|
|
408
|
-
|
|
409
|
-
print(f'- Readying inputs:')
|
|
410
|
-
self.inputs = InputsDirectMethod(input_lines, input_flux, input_err, flambda_arr, ion_arr,
|
|
411
|
-
temp_id_arr, den_id_arr, tem_eq_arr, den_eq_arr)
|
|
468
|
+
# Input data
|
|
469
|
+
self.inputs = InputsDirectMethod.from_dataframe(self.lines_structure)
|
|
412
470
|
|
|
413
471
|
# Create the model
|
|
414
472
|
print(f'- Compiling model:')
|
|
415
473
|
self.model = direct_method_multi_region(inputs=self.inputs, emis_interp=self.emis_interp, prior_dict=self.prior_cfg,
|
|
416
|
-
tem_EQDB=
|
|
474
|
+
tem_EQDB=TEM_FUNC_DICT, den_EQDB=DEN_FUNC_DICT)
|
|
417
475
|
|
|
418
476
|
# Run the model
|
|
419
477
|
print(f'- Launching sampler:')
|
|
420
|
-
self.trace = run_model(self.model
|
|
478
|
+
self.trace = run_model(self.model, draws=draws, tune=tune, chains=chains, cores=cores, target_accept=target_accept,
|
|
479
|
+
nuts_sampler=nuts_sampler, callback=callback)
|
|
480
|
+
|
|
481
|
+
# Remove the normalization from the fluxes
|
|
482
|
+
if linear_scale_results:
|
|
483
|
+
self.trace.posterior['theo_flux'] = np.power(10, self.trace.posterior['theo_flux'])
|
|
484
|
+
self.trace.observed_data['likelihood'] = np.power(10, self.trace.observed_data['likelihood'])
|
|
485
|
+
|
|
486
|
+
# Remove the log scale for the helium abundaces
|
|
487
|
+
for helium in ['He1', 'He2']:
|
|
488
|
+
if helium in self.inputs.ion_arr:
|
|
489
|
+
self.trace.posterior[helium] = np.power(10, self.trace.posterior[helium])
|
|
421
490
|
|
|
422
491
|
return
|
|
423
492
|
|
|
424
|
-
def
|
|
493
|
+
def save_line_structure(self, fname):
|
|
494
|
+
lime.save_frame(fname, self.lines_structure)
|
|
425
495
|
|
|
426
|
-
|
|
496
|
+
return
|
|
497
|
+
|
|
498
|
+
def save_trace(self, fname):
|
|
427
499
|
az.to_netcdf(self.trace, fname)
|
|
428
|
-
# results_dict = pack_results(fname)
|
|
429
|
-
# save_dataset(fname, results_dict)
|
|
430
500
|
|
|
431
501
|
return
|
|
432
502
|
|
|
433
|
-
def plot_trace(self, var_names = ["O2", "O3", "S2", "S3", "N2", "Ar3", "cHBeta", "den_low", "temp_low",
|
|
434
|
-
"temp_high"]):
|
|
435
|
-
# var_names = ["O2", "O3", "S2", "S3", "N2", "Ar3", "Ar4", "Ne3", "cHBeta", "den_low", "temp_low",
|
|
436
|
-
# "temp_high"]
|
|
437
|
-
az.plot_pair(self.trace, var_names=var_names, divergences=True)
|
|
438
|
-
az.plot_posterior(self.trace, var_names=var_names)
|
|
439
|
-
summary = az.summary(self.trace, var_names=var_names)
|
|
440
|
-
print(summary)
|
|
441
|
-
plt.show()
|
|
442
503
|
|
|
443
|
-
return
|
|
@@ -4,7 +4,7 @@ from pandas import unique
|
|
|
4
4
|
from pytensor import tensor as tt
|
|
5
5
|
from arviz import to_netcdf
|
|
6
6
|
|
|
7
|
-
from specsy.models.literature import
|
|
7
|
+
from specsy.models.literature import TEM_FUNC_DICT, DEN_FUNC_DICT
|
|
8
8
|
from lime.io import check_file_dataframe
|
|
9
9
|
|
|
10
10
|
def storeValueInTensor(idx, value, tensor1D):
|
|
@@ -244,8 +244,8 @@ def direct_method_multi_region(lines_df, emis_interp, prior_dict, fname=None):
|
|
|
244
244
|
for i in range_arr:
|
|
245
245
|
|
|
246
246
|
# Compute the emissivity
|
|
247
|
-
tem = dm_model[Tem_label_arr[i]] if temp_eq_check[i] else
|
|
248
|
-
den = dm_model[den_label_arr[i]] if den_eq_check[i] else
|
|
247
|
+
tem = dm_model[Tem_label_arr[i]] if temp_eq_check[i] else TEM_FUNC_DICT[tem_eq_arr[i]](dm_model[Tem_label_arr[i]])
|
|
248
|
+
den = dm_model[den_label_arr[i]] if den_eq_check[i] else DEN_FUNC_DICT[den_eq_arr[i]](dm_model[den_label_arr[i]])
|
|
249
249
|
emis = emis_interp[line_arr[i]](tem, den)
|
|
250
250
|
|
|
251
251
|
if particle_arr[i] == 'H1':
|
|
@@ -1,4 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
import pytensor
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def H1_flux(abund, emis, flambda, cHbeta):
|
|
5
|
+
return emis - flambda * cHbeta
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def helium_flux(abund, emis, flambda, cHbeta):
|
|
9
|
+
return abund + emis - flambda * cHbeta
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def metals_flux(abund, emis, flambda, cHbeta):
|
|
13
|
+
return abund + emis - flambda * cHbeta - 12
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
FLUX_EQUATION_DICT = {'hydrogen': H1_flux,
|
|
17
|
+
'helium': helium_flux,
|
|
18
|
+
'metals': metals_flux}
|
|
19
|
+
|
|
20
|
+
DEFAULT_PARTICLE_EQUATIONS_KEYS = {'H1': 'hydrogen', 'He1': 'helium', 'He2': 'helium'}
|
|
2
21
|
|
|
3
22
|
|
|
4
23
|
class EmissionFluxModel:
|
|
@@ -80,9 +99,9 @@ class EmissionFluxModel:
|
|
|
80
99
|
return abund + emis_ratio - flambda * cHbeta - 12
|
|
81
100
|
|
|
82
101
|
def ion_O2_7319A_b_flux_log(self, emis_ratio, cHbeta, flambda, abund, ftau, O3, T_high):
|
|
83
|
-
col_ext =
|
|
84
|
-
recomb =
|
|
85
|
-
return
|
|
102
|
+
col_ext = pytensor.tensor.power(10, abund + emis_ratio - flambda * cHbeta - 12)
|
|
103
|
+
recomb = pytensor.tensor.power(10, O3 + 0.9712758 + pytensor.tensor.log10(pytensor.tensor.power(T_high / 10000.0, 0.44)) - flambda * cHbeta - 12)
|
|
104
|
+
return pytensor.tensor.log10(col_ext + recomb)
|
|
86
105
|
|
|
87
106
|
|
|
88
107
|
class EmissionTensors(EmissionFluxModel):
|
|
@@ -100,9 +119,9 @@ class EmissionTensors(EmissionFluxModel):
|
|
|
100
119
|
|
|
101
120
|
# Compile the theano functions for all the input emission lines
|
|
102
121
|
for label, func in self.emFluxEqDict.items():
|
|
103
|
-
func_params =
|
|
104
|
-
self.emFluxEqDict[label] = function(inputs=func_params, outputs=func(*func_params),
|
|
105
|
-
|
|
122
|
+
func_params = pytensor.tensor.dscalars(self.emFluxParamDict[label])
|
|
123
|
+
self.emFluxEqDict[label] = pytensor.function(inputs=func_params, outputs=func(*func_params),
|
|
124
|
+
on_unused_input='ignore')
|
|
106
125
|
|
|
107
126
|
# Assign function dictionary with flexible arguments
|
|
108
127
|
self.assign_flux_eqtt()
|