Skip to content

Commit

Permalink
Add file reader for autoionization files (#240)
Browse files Browse the repository at this point in the history
  • Loading branch information
jwreep committed Jan 9, 2024
1 parent 800de31 commit 0bec292
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 14 deletions.
10 changes: 8 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@ jobs:
posargs: '--ascii-dbase-root ~/.chianti'
toxdeps: "'tox<4' tox-pypi-filter"
envs: |
- macos: py38
- macos: py310
- windows: py38
- windows: py310
- linux: py38
- linux: py39
Expand All @@ -40,6 +38,14 @@ jobs:
- linux: py310
cache-path: ~/.chianti
cache-key: chianti-${{ github.event.number }}
test_database_v9:
needs: [test]
uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v1
with:
posargs: '--ascii-dbase-root ~/.chianti --ascii-dbase-url http://download.chiantidatabase.org/CHIANTI_v9.0.1_database.tar.gz --disable-file-hash --skip-version-check'
toxdeps: "'tox<4' tox-pypi-filter"
envs: |
- linux: py310
precommit:
uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v1
with:
Expand Down
10 changes: 10 additions & 0 deletions docs/references.bib
Original file line number Diff line number Diff line change
Expand Up @@ -404,3 +404,13 @@ @article{zatsarinny_dielectronic_2003
issn = {0004-6361},
doi = {10.1051/0004-6361:20031462}
}

@techreport{dere_chianti_2017,
title = {{{CHIANTI Technical Report No}}. 8: {{The CHIANTI}} Autoionization Rate Files (Auto)},
author = {Dere, K. P.},
year = {2017},
month = dec,
number = {8},
urldate = {2022-08-21},
langid = {english}
}
28 changes: 27 additions & 1 deletion fiasco/conftest.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pathlib
import pytest

from fiasco.util import check_database
from fiasco.util import check_database, read_chianti_version

# Force MPL to use non-gui backends for testing.
try:
Expand Down Expand Up @@ -148,3 +148,29 @@ def ascii_dbase_root(ascii_dbase_tree, hdf5_dbase_root):
# but in a worse way. What this means is that the HDF5 database is always built, even
# when testing the ASCII parsing code, but that is not a huge price to pay.
return ascii_dbase_tree


@pytest.fixture(scope="session")
def dbase_version(ascii_dbase_root):
return read_chianti_version(ascii_dbase_root)


@pytest.fixture(autouse=True)
def requires_dbase_version(request, dbase_version):
# NOTE: Fixtures that depend on other fixtures are awkward to implement.
# See this SO answer: https://stackoverflow.com/a/28198398
if marker := request.node.get_closest_marker('requires_dbase_version'):
version_condition = marker.args[0]
# NOTE: This currently only works for major versions. If we
# need tests that discriminate between minor versions or patches,
# this logic will need to be added.
conditional = f'{dbase_version["major"]}{version_condition}'
allowed_dbase_version = eval(conditional)
if not allowed_dbase_version:
pytest.skip(f'Test skipped because current dbase version {conditional}.')


def pytest_configure(config):
config.addinivalue_line(
"markers", "requires_dbase_version(dbase_version): Skip tests based on CHIANTI database version requirements.",
)
7 changes: 3 additions & 4 deletions fiasco/io/generic.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import fiasco

from fiasco.util.exceptions import MissingASCIIFileError
from fiasco.util.util import read_chianti_version


class GenericParser:
Expand All @@ -32,10 +33,8 @@ def __init__(self, filename, **kwargs):
if standalone:
self.chianti_version = ''
else:
version_file = self.ascii_dbase_root / 'VERSION'
with version_file.open() as f:
lines = f.readlines()
self.chianti_version = lines[0].strip()
version = read_chianti_version(self.ascii_dbase_root)
self.chianti_version = f"{version['major']}.{version['minor']}.{version['patch']}"
self.full_path = pathlib.Path(filename) if standalone else self.ascii_dbase_root / self.filename

def parse(self):
Expand Down
55 changes: 50 additions & 5 deletions fiasco/io/sources/ion_sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,22 @@

from fiasco.io.generic import GenericIonParser

__all__ = ['ElvlcParser', 'FblvlParser', 'ScupsParser',
'PsplupsParser', 'EasplomParser', 'EasplupsParser',
'WgfaParser', 'CilvlParser', 'ReclvlParser',
'RrparamsParser', 'TrparamsParser', 'DrparamsParser',
'DiparamsParser']
__all__ = [
'ElvlcParser',
'FblvlParser',
'ScupsParser',
'PsplupsParser',
'EasplomParser',
'EasplupsParser',
'WgfaParser',
'CilvlParser',
'ReclvlParser',
'RrparamsParser',
'TrparamsParser',
'DrparamsParser',
'DiparamsParser',
'AutoParser',
]


class ElvlcParser(GenericIonParser):
Expand Down Expand Up @@ -416,3 +427,37 @@ def preprocessor(self, table, line, index):
ionization_potential = tmp[0]
cs_spline = np.array(tmp[1:], dtype=float)*1e-14
table[-1] = [ionization_potential] + table[-1] + [cs_spline] + [0.0]


class AutoParser(GenericIonParser):
"""
Autoionization rates for each level in an ion.
The autoionization rate is the rate of decay of atomic level through autoionization to a
bound level. It is also needed to calculate the dielectronic recombination rate
from the more highly ionized ions, by means of the principle of detailed-balance.
For a full description of these files, see :cite:t:`dere_chianti_2017`.
"""
filetype = 'auto'
dtypes = [int, int, float, str, str]
units = [None, None, 1/u.s, None, None]
headings = [
'lower_level',
'upper_level',
'autoionization_rate',
'lower_label',
'upper_label'
]
descriptions = [
'lower level index',
'upper level index',
'autoionization rate',
'lower level label',
'upper level label'
]
fformat = fortranformat.FortranRecordReader('(2I7,E12.2,A30,A30)')

def preprocessor(self, table, line, index):
super().preprocessor(table, line, index)
# remove the dash in the second-to-last entry
table[-1][-2] = table[-1][-2].split('-')[0].strip()
1 change: 1 addition & 0 deletions fiasco/io/sources/tests/test_sources.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
'fe_2.trparams',
'fe_12.drparams',
'al_3.diparams',
pytest.param('fe_23.auto', marks=pytest.mark.requires_dbase_version('>=9')),
])
def test_ion_sources(ascii_dbase_root, filename,):
parser = fiasco.io.Parser(filename, ascii_dbase_root=ascii_dbase_root)
Expand Down
1 change: 1 addition & 0 deletions fiasco/tests/test_ion.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ def test_level_populations_normalized(pops_no_correction, pops_with_correction):
assert u.allclose(pops_no_correction.sum(axis=1), 1, atol=None, rtol=1e-15)


@pytest.mark.requires_dbase_version('<=8')
def test_level_populations_correction(fe20, pops_no_correction, pops_with_correction):
# Test level-resolved correction applied to correct levels
i_corrected = np.unique(np.concatenate([fe20._cilvl['upper_level'], fe20._reclvl['upper_level']]))
Expand Down
10 changes: 9 additions & 1 deletion fiasco/util/tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
import pytest

from fiasco.util import get_chianti_catalog, parse_ion_name
from fiasco.util import get_chianti_catalog, parse_ion_name, read_chianti_version


@pytest.mark.parametrize('ion_name', [
Expand Down Expand Up @@ -35,3 +35,11 @@ def test_get_chianti_catalog(ascii_dbase_root):
for k in keys:
assert k in catalog
assert isinstance(catalog[k], list)


def test_chianti_version(ascii_dbase_root):
version = read_chianti_version(ascii_dbase_root)
assert isinstance(version, dict)
for k in ['major', 'minor', 'patch']:
assert k in version
assert isinstance(version[k], int)
24 changes: 23 additions & 1 deletion fiasco/util/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
FIASCO_HOME = pathlib.Path.home() / '.fiasco'
FIASCO_RC = FIASCO_HOME / 'fiascorc'

__all__ = ['setup_paths', 'get_chianti_catalog', 'parse_ion_name']
__all__ = ['setup_paths', 'get_chianti_catalog', 'read_chianti_version', 'parse_ion_name']


def parse_ion_name(ion_name):
Expand Down Expand Up @@ -117,6 +117,28 @@ def walk_sub_dir(subdir):
return all_files


def read_chianti_version(ascii_dbase_root):
"""
Read the CHIANTI version number from the ASCII database.
"""
version_file = pathlib.Path(ascii_dbase_root) / 'VERSION'
with version_file.open() as f:
lines = f.readlines()
version = lines[0].strip().split('.')
version_dict = {'major': 0, 'minor': 0, 'patch': 0}
n = len(version)
if n > 0:
version_dict['major'] = int(version[0])
if n > 1:
version_dict['minor'] = int(version[1])
if n > 2:
version_dict['patch'] = int(version[2])
if n > 3:
import fiasco
fiasco.log.warning(f'Version {version} has an unexpected number of entries.')
return version_dict


def query_yes_no(question, default="yes"):
"""
Ask a yes/no question via raw_input() and return their answer.
Expand Down

0 comments on commit 0bec292

Please sign in to comment.