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.
Files changed (52) hide show
  1. specsy-0.9.dev3/PKG-INFO +76 -0
  2. specsy-0.9.dev3/README.md +40 -0
  3. {specsy-0.9.dev1 → specsy-0.9.dev3}/pyproject.toml +14 -6
  4. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/__init__.py +3 -1
  5. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/io.py +8 -0
  6. specsy-0.9.dev3/src/specsy/models/__init__.py +2 -0
  7. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/models/chemistry.py +126 -66
  8. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/models/chemistry_inference.py +3 -3
  9. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/models/fluxes_line.py +26 -7
  10. specsy-0.9.dev3/src/specsy/models/literature.py +130 -0
  11. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/observations.py +94 -21
  12. specsy-0.9.dev3/src/specsy/plotting/bokeh_functions.py +227 -0
  13. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/plotting/plots.py +188 -7
  14. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/plotting/specsy_theme.toml +13 -2
  15. specsy-0.9.dev3/src/specsy/resources/data/Benjamin1999_OpticalDepthFunctionCoefficients.txt +5 -0
  16. specsy-0.9.dev3/src/specsy/resources/data/HI_t3_elec.ascii +55 -0
  17. specsy-0.9.dev3/src/specsy/resources/data/HeII_t4_elec.ascii +110 -0
  18. specsy-0.9.dev3/src/specsy/resources/data/HeI_t5_elec.ascii +334 -0
  19. specsy-0.9.dev3/src/specsy/resources/data/gordon_2003_LMC2_supershell.txt +33 -0
  20. specsy-0.9.dev3/src/specsy/resources/data/gordon_2003_LMC_average.txt +32 -0
  21. specsy-0.9.dev3/src/specsy/resources/data/gordon_2003_SMC_bar.txt +37 -0
  22. specsy-0.9.dev3/src/specsy/resources/images/Specsy_logo_transparent_dark.PNG +0 -0
  23. specsy-0.9.dev3/src/specsy/resources/images/Specsy_logo_transparent_light.PNG +0 -0
  24. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/sampler.py +20 -9
  25. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/specsy.toml +56 -42
  26. specsy-0.9.dev3/src/specsy.egg-info/PKG-INFO +76 -0
  27. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy.egg-info/SOURCES.txt +11 -2
  28. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy.egg-info/requires.txt +7 -3
  29. specsy-0.9.dev1/PKG-INFO +0 -57
  30. specsy-0.9.dev1/README.rst +0 -25
  31. specsy-0.9.dev1/src/specsy/models/__init__.py +0 -2
  32. specsy-0.9.dev1/src/specsy/models/literature.py +0 -35
  33. specsy-0.9.dev1/src/specsy/plotting/bokeh_functions.py +0 -63
  34. specsy-0.9.dev1/src/specsy.egg-info/PKG-INFO +0 -57
  35. {specsy-0.9.dev1 → specsy-0.9.dev3}/MANIFEST.in +0 -0
  36. {specsy-0.9.dev1 → specsy-0.9.dev3}/setup.cfg +0 -0
  37. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/core.py +0 -0
  38. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/emission.py +0 -0
  39. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/inference.py +0 -0
  40. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/models/emissivity.py +0 -0
  41. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/models/extinction.py +0 -0
  42. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/models/nebular_continuum.py +0 -0
  43. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/operations/__init__.py +0 -0
  44. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/operations/interpolation.py +0 -0
  45. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/operations/pytensors.py +0 -0
  46. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/operations/tensors.py +0 -0
  47. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/operations/tests.py +0 -0
  48. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/plotting/__init__.py +0 -0
  49. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/tools.py +0 -0
  50. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy/treatement.py +0 -0
  51. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy.egg-info/dependency_links.txt +0 -0
  52. {specsy-0.9.dev1 → specsy-0.9.dev3}/src/specsy.egg-info/top_level.txt +0 -0
@@ -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.dev1"
8
- readme = "README.rst"
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.dev1",
19
- "lime-stable~=2.2.dev1",
20
- "innate-stable~=0.2.1",
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"""
@@ -0,0 +1,2 @@
1
+ from specsy.models.nebular_continuum import NebularContinua
2
+ from specsy.models.literature import TEM_FUNC_DICT, DEN_FUNC_DICT
@@ -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 _setup_cfg
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 _TEM_FUNC_DICT, _DEN_FUNC_DICT
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
- labels: np.ndarray
278
- flux_arr: np.ndarray
279
- err_arr: np.ndarray
280
- flambda_arr: np.ndarray
281
- ion_arr: np.ndarray
282
- temp_id_arr: np.ndarray
283
- den_id_arr: np.ndarray
284
- eq_tem_arr: np.ndarray
285
- eq_den_arr: np.ndarray
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 = _setup_cfg['direct_method_priors']
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, emissivity_source=None, prior_cfg=None, R_V=3.1, law='G03 LMC',
314
- norm_list='H1_4861A', normalize_flux=True, line_list=None, flux_column='profile_flux'):
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 = norm_list
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
- emis_dataset = load_dataset(emissivity_source)
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._lines_frame.copy(), line_list=line_list, norm_list=norm_list, flux_column=flux_column, clear_empty=True)
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 = self._lines_frame.copy()
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
- self.lines_structure.insert(3, 'f_lambda', flambda_arr)
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, norm_line=self.norm_line)
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
- self._review_inputs()
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', _TEM_FUNC_DICT), ('eq_den', _DEN_FUNC_DICT)):
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"Line emissivity not available in the input file: {bad}")
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
- def run(self):
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
- # Line inputs the data
393
- input_lines = self.lines_structure.index.to_numpy()
394
- input_flux = self.lines_structure.line_flux.to_numpy()
395
- input_err = self.lines_structure.line_flux_err.to_numpy()
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
- # Unpack the temp/den structure arrays
402
- temp_id_arr = self.lines_structure.temp.to_numpy()
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
- # Inputs object
408
- print(f'Multi-region direct method sampler')
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=_TEM_FUNC_DICT, den_EQDB=_DEN_FUNC_DICT)
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 save_trace(self, fname):
493
+ def save_line_structure(self, fname):
494
+ lime.save_frame(fname, self.lines_structure)
425
495
 
426
- # Save with arviz formating
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 _TEM_FUNC_DICT, _DEN_FUNC_DICT
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 _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]])
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
- from pytensor import tensor as tt, function
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 = tt.power(10, abund + emis_ratio - flambda * cHbeta - 12)
84
- recomb = tt.power(10, O3 + 0.9712758 + tt.log10(tt.power(T_high/10000.0, 0.44)) - flambda * cHbeta - 12)
85
- return tt.log10(col_ext + recomb)
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 = tt.dscalars(self.emFluxParamDict[label])
104
- self.emFluxEqDict[label] = function(inputs=func_params, outputs=func(*func_params),
105
- on_unused_input='ignore')
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()