Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse Floers+25 final version data #10

Merged
merged 11 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions .flake8

This file was deleted.

17 changes: 8 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,6 @@ jobs:
run: |
python -m pip install -e .

- name: Lint with flake8
working-directory: artisatomic/
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics

- name: Lint with mypy
run: |
mypy --install-types --non-interactive
Expand Down Expand Up @@ -97,7 +89,7 @@ jobs:
tests:
strategy:
matrix:
testname: [cmfgen, jplt]
testname: [cmfgen, jplt, floers25]
fail-fast: false

runs-on: macos-15
Expand Down Expand Up @@ -147,6 +139,12 @@ jobs:
if: matrix.testname == 'jplt'
working-directory: atomic-data-tanaka-jplt/
run: source ./setup_jplt_data.sh

- name: Extract Floers25 atomic data
if: matrix.testname == 'floers25'
working-directory: atomic-data-floers25/
run: tar -zxvf testdata.tar.xz

- name: Generate artis atomic data files
run: |
cp tests/${{ matrix.testname }}/artisatomicionhandlers.json .
Expand All @@ -157,6 +155,7 @@ jobs:
run: |
cat compositiondata.txt
cat *.json
echo
md5sum *.txt
md5sum -c ../checksums.txt

Expand Down
5 changes: 0 additions & 5 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,6 @@ repos:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/pycqa/flake8
rev: 7.1.1
hooks:
- id: flake8
types: [python]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.13.0
hooks:
Expand Down
21 changes: 17 additions & 4 deletions artisatomic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from artisatomic import readcarsusdata
from artisatomic import readdreamdata
from artisatomic import readfacdata
from artisatomic import readfloers25data
from artisatomic import readhillierdata
from artisatomic import readnahardata
from artisatomic import readqubdata
Expand Down Expand Up @@ -98,6 +99,7 @@ def get_ion_handlers() -> list[tuple[int, list[int | tuple[int, str]]]]:
# ion_handlers = readcarsusdata.extend_ion_list(ion_handlers)
# ion_handlers = readdreamdata.extend_ion_list(ion_handlers)
# ion_handlers = readfacdata.extend_ion_list(ion_handlers)
# ion_handlers = readfloers25data.extend_ion_list(ion_handlers, calibrated=True)
# ion_handlers = readtanakajpltdata.extend_ion_list(ion_handlers)
# ion_handlers = groundstatesonlynist.extend_ion_list(ion_handlers)

Expand Down Expand Up @@ -226,7 +228,7 @@ def process_files(ion_handlers: list[tuple[int, list[int | tuple[int, str]]]], a
# in Rydberg, cross section in Mb) tuples
nahar_phixs_tables: list[dict[tuple, list[tuple]]] = [{} for _ in listions]

ionization_energy_ev = [0.0 for x in listions]
ionization_energy_ev = [0.0 for _ in listions]
thresholds_ev_dict: list[dict] = [{} for _ in listions]

# list of named tuples (hillier_transition_row)
Expand Down Expand Up @@ -264,10 +266,10 @@ def process_files(ion_handlers: list[tuple[int, list[int | tuple[int, str]]]], a
else:
handler = "carsus"

logfilepath = os.path.join(
logfilepath = Path(
args.output_folder, args.output_folder_logs, f"{elsymbols[atomic_number].lower()}{ion_stage:d}.txt"
)
with open(logfilepath, "w") as flog:
with logfilepath.open("w") as flog:
log_and_print(
flog,
f"\n===========> Z={atomic_number} {elsymbols[atomic_number]} {roman_numerals[ion_stage]} input:",
Expand Down Expand Up @@ -478,7 +480,18 @@ def process_files(ion_handlers: list[tuple[int, list[int | tuple[int, str]]]], a
# transition_count_of_level_name[i],
# ) = readlisbondata.read_levels_and_transitions(atomic_number, ion_stage, flog)

elif handler in {"floers25calib", "floers25uncalib"}:
(
ionization_energy_ev[i],
energy_levels[i],
transitions[i],
transition_count_of_level_name[i],
) = readfloers25data.read_levels_and_transitions(
atomic_number, ion_stage, flog, calibrated=(handler == "floers25calib")
)

elif handler == "fac":
# early version of floers25 calib data
(
ionization_energy_ev[i],
energy_levels[i],
Expand Down Expand Up @@ -735,7 +748,7 @@ def energy_if_available(state_tuple):
hillier_energy_levels, hillier_level_ids_matching_this_nahar_state
)
sumhillierstatweights = sum(
[hillier_energy_levels[levelid].g for levelid in hillier_level_ids_matching_this_nahar_state]
hillier_energy_levels[levelid].g for levelid in hillier_level_ids_matching_this_nahar_state
)
flog.write(f"<E> = {avghillierenergyabovegsinev:.3f} eV, g_sum = {sumhillierstatweights:.1f}: \n")
if abs(nahar_energyabovegsinev / avghillierenergyabovegsinev - 1) > 0.5:
Expand Down
139 changes: 139 additions & 0 deletions artisatomic/readfloers25data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import typing as t
from pathlib import Path

import pandas as pd
import polars as pl

import artisatomic


def get_basepath() -> Path:
return artisatomic.PYDIR / ".." / "atomic-data-floers25" / "OutputFiles"


def extend_ion_list(ion_handlers, calibrated=True):
BASEPATH = get_basepath()
assert BASEPATH.is_dir()
# if calibrated is requested, also add uncalibrated data where calibrated data is not available
calibflags = [True, False] if calibrated else [False]
for searchcalib in calibflags:
calibstr = "calib" if searchcalib else "uncalib"
handlername = f"floers25{calibstr}"
for s in BASEPATH.glob(f"*_levels_{calibstr}.txt*"):
ionstr = s.name.lstrip("0123456789").split("_")[0]
elsym = ionstr.rstrip("IVX")
ion_stage_roman = ionstr.removeprefix(elsym)
atomic_number = artisatomic.elsymbols.index(elsym)

ion_stage = artisatomic.roman_numerals.index(ion_stage_roman)

found_element = False
for tmp_atomic_number, list_ions in ion_handlers:
if tmp_atomic_number == atomic_number:
if ion_stage not in [x[0] if len(x) > 0 else x for x in list_ions]:
list_ions.append((ion_stage, handlername))
list_ions.sort()
found_element = True
if not found_element:
ion_handlers.append(
(
atomic_number,
[(ion_stage, handlername)],
)
)

ion_handlers.sort(key=lambda x: x[0])

return ion_handlers


class FloersEnergyLevel(t.NamedTuple):
levelname: str
energyabovegsinpercm: float
g: float
parity: int


class FloersTransition(t.NamedTuple):
lowerlevel: int
upperlevel: int
A: float
coll_str: float


def read_levels_and_transitions(atomic_number: int, ion_stage: int, flog, calibrated: bool):
# ion_charge = ion_stage - 1
elsym = artisatomic.elsymbols[atomic_number]
ion_stage_roman = artisatomic.roman_numerals[ion_stage]
calibstr = "calib" if calibrated else "uncalib"

BASEPATH = get_basepath()
ionstr = f"{atomic_number}{elsym}{ion_stage_roman}"
levels_file = BASEPATH / f"{ionstr}_levels_{calibstr}.txt"
lines_file = BASEPATH / f"{ionstr}_transitions_{calibstr}.txt"

artisatomic.log_and_print(
flog,
f"Reading Floers+25 {calibstr}rated data for Z={atomic_number} ion_stage {ion_stage} ({elsym} {ion_stage_roman}) from {levels_file.name} and {lines_file.name}",
)

ionization_energy_in_ev = artisatomic.get_nist_ionization_energies_ev()[(atomic_number, ion_stage)]

dflevels = pl.from_pandas(
pd.read_csv(levels_file, sep=r"\s+", skiprows=18, dtype_backend="pyarrow", dtype={"J": str})
).with_columns(
pl.when(pl.col("J").str.ends_with("/2"))
.then(pl.col("J").str.strip_suffix("/2").cast(pl.Int32) + 1)
.otherwise(
pl.col("J").str.strip_suffix("/2").cast(pl.Int32) * 2 + 1
) # the strip_suffix should not be needed (does not end in "/2" but prevents a polars error)
.alias("g")
)

dflevels = dflevels.with_columns(pl.col("J").str.strip_suffix("/2").cast(pl.Float32).alias("2J"))

energy_levels_zerodindexed = [
FloersEnergyLevel(
levelname=row["Configuration"],
parity=row["Parity"],
g=row["g"],
energyabovegsinpercm=float(row["Energy"]),
)
for row in dflevels.iter_rows(named=True)
]

energy_levels = [None, *energy_levels_zerodindexed]

artisatomic.log_and_print(flog, f"Read {len(energy_levels[1:]):d} levels")

transitions = []
dftransitions = pl.from_pandas(pd.read_csv(lines_file, sep=r"\s+", skiprows=28, dtype_backend="pyarrow"))

transitions = [
FloersTransition(lowerlevel=lowerindex + 1, upperlevel=upperindex + 1, A=A, coll_str=-1)
for lowerindex, upperindex, A in dftransitions[["Lower", "Upper", "A"]].iter_rows()
]

transition_count_of_level_name = {
config: (
dftransitions.filter(pl.col("Config_Lower") == config).height
+ dftransitions.filter(pl.col("Config_Upper") == config).height
)
for config in dflevels["Configuration"]
}

# this check is slow
# assert sum(transition_count_of_level_name.values()) == len(transitions) * 2

artisatomic.log_and_print(flog, f"Read {len(transitions)} transitions")

return ionization_energy_in_ev, energy_levels, transitions, transition_count_of_level_name


def get_level_valence_n(levelname: str):
part = levelname.split(".")[-1]
if part[-1] not in "spdfg":
# end of string is a number of electrons in the orbital, not a principal quantum number, so remove it
assert part[-1].isdigit()
part = part.rstrip("0123456789")
return int(part.rstrip("spdfg"))
6 changes: 3 additions & 3 deletions artisatomic/readhillierdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -1121,7 +1121,7 @@ def read_coldata(atomic_number, ion_stage, energy_levels, flog, args):

for id_lower in level_ids_of_level_name[namefrom]:
id_upper_list = [levelid for levelid in level_ids_of_level_name[nameto] if levelid > id_lower]
upper_g_sum = sum([energy_levels[id_upper].g for id_upper in id_upper_list])
upper_g_sum = sum(energy_levels[id_upper].g for id_upper in id_upper_list)

for id_upper in id_upper_list:
# print(f'Transition {namefrom} (level {id_lower:d} in {level_ids_of_level_name[namefrom]}) -> {nameto} (level {id_upper:d} in {level_ids_of_level_name[nameto]})')
Expand All @@ -1137,7 +1137,7 @@ def read_coldata(atomic_number, ion_stage, energy_levels, flog, args):
else:
upsilondict[(id_lower, id_upper)] = upsilonscaled

# print(namefrom, nameto, upsilon)
# print(namefrom, nameto, upsilon)
except KeyError:
unlisted_from_message = " (unlisted)" if namefrom not in level_ids_of_level_name else ""
unlisted_to_message = " (unlisted)" if nameto not in level_ids_of_level_name else ""
Expand Down Expand Up @@ -1188,7 +1188,7 @@ def get_photoiontargetfractions(energy_levels, energy_levels_upperion, hillier_p
upperionlevelids = [1]
targetlist_of_targetconfig[targetconfig] = []

summed_statistical_weights = sum([float(energy_levels_upperion[index].g) for index in upperionlevelids])
summed_statistical_weights = sum(float(energy_levels_upperion[index].g) for index in upperionlevelids)
for upperionlevelid in sorted(upperionlevelids):
statweight_fraction = energy_levels_upperion[upperionlevelid].g / summed_statistical_weights
targetlist_of_targetconfig[targetconfig].append((upperionlevelid, statweight_fraction))
Expand Down
1 change: 1 addition & 0 deletions atomic-data-floers25/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
OutputFiles
4 changes: 4 additions & 0 deletions atomic-data-floers25/readme.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Suggested method is either copy or symlink the data to a subfolder called "OutputFiles"

e.g.
ln -s ~/Google\ Drive/Shared\ drives/Atomic\ Data\ Group/FloersOpacityPaper/OutputFiles OutputFiles
Binary file added atomic-data-floers25/testdata.tar.xz
Binary file not shown.
3 changes: 1 addition & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
argcomplete>=3.5.1
astropy>=6.1.4
ChiantiPy>=0.15.1
flake8>=6.1.0
h5py>=3.10.0
matplotlib>=3.9.2
mypy>=1.13.0
numpy>=1.26.1
pandas>=2.2.3
polars>=1.12.0
pre-commit>=4.0.1
pyarrow>=18.1.0
pytest>=8.3.3
pytest-runner>=6.0.1
ruff>=0.8.0
scipy>=1.11.3
tabulate>=0.9.0
Expand Down
3 changes: 3 additions & 0 deletions tests/floers25/artisatomicionhandlers.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
[66, [[3, "floers25uncalib"]]]
]
4 changes: 4 additions & 0 deletions tests/floers25/checksums.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
aedece91ee06ec904b99b027dc86f980 adata.txt
68ba442cebe4f96c4be79e8e64b86ddb compositiondata.txt
a673d82823ddddbb9b26ef8c8e6aed28 phixsdata_v2.txt
90038710e612fa8ab1197ceccd121528 transitiondata.txt