pyathena tests + microphysics regression suite + O I CT dispatch fix - #67
Merged
Conversation
jeonggyukim
added a commit
that referenced
this pull request
Jun 8, 2026
photchem-full was opened 2025-02-25 (PR #48) and had not been updated since `Add PhotChem class` (58c6405). master moved 131 commits forward in the interim, including: - numpy 2 migration (numpy>=2, yt>=4.4) + removed-API fixes - SB99 dataset refactor + Zari+22 SFH support - ISRF API rewrite - find_files.py / load_sim.py docstring + behavior cleanups - README rewrites; conda/miniforge install docs - Various docs / autodoc improvements Dry-run on pyathena-ct-mvp showed zero conflicts (photchem-full's new files under data/microphysics/ + pyathena/microphysics/ touch disjoint paths from master's changes). Confirmed by re-running this merge with --no-commit -- same clean auto-merge here. The sibling sub-PR #67 (pyathena-ct-mvp) will rebase on this state before merging.
Static method `get_ct_ion_HII_OI_Draine11` was misnamed `get_ct_ion_OI_HII_Draine11` at the dispatch site (line 183), causing `AttributeError` when O I charge-exchange ionization rate is requested. Symptom: `get_ct_ion_rate(8, 8, T)` crashes. Caught by new charge-exchange rate test `test_ct_rate_balance.py::test_ct_ion_rate_finite_nonneg[8-8-O I]`.
Pin the current behavior of `ChargeTransferRate.get_ct_rec_rate` and `get_ct_ion_rate` before downstream code wires charge-exchange into the abundance sweep in `evolve_one_species`. Tests document: - API conventions (which (Z, N) labels reactant vs product per method); - Sign of the Boltzmann factor in `get_ct_ion_rate` via the N I endothermic pair (ct_ion must grow with T faster than ct_rec); - Near-resonance behavior for O+H (similar T-growth, Draine 2011 per-J-level detailed balance at high T); - Order-of-magnitude reference at T = 1e4 K; - Known-quirk pinning: S I CT-ion is a constant ~1e-14 placeholder in the Cloudy data table (`a=1e-5, b=c=d=dE=0`) -- documented so future rate upgrades flag this site. - High-q fallback behavior (q > 3 in `get_ct_ion_rate` returns 0; in `get_ct_rec_rate` returns Dalgarno generic estimate `1.92e-9*(q+1)`). These tests are the regression anchor for the charge-exchange completeness fixes described in `tigris-notes/docs-claude/tigris-ncr/pyathena_ct_fixes_plan.md` (MVP items 1-3); the wiring of CT into the abundance sweep follows in later commits on this branch. Pytest infrastructure (empty `tests/__init__.py` + this test file) is new to pyathena -- run with `pytest pyathena/microphysics/tests/`.
Relocate `pyathena/microphysics/tests/` -> `tests/microphysics/` so
new test suites (io, fields, obs, ...) can drop into sibling subdirs
without nesting inside the package. Adds:
- `tests/README.md` documenting layout, run commands, and the
convention for adding new tests + diagnostic plots.
- `tests/conftest.py` providing:
* `figures_dir` fixture -> `tests/figures/` (created on demand).
* `--no-figures` pytest CLI option to skip plot generation.
* `save_figures` fixture (True unless `--no-figures` passed).
* `ion_color(Z, q)` helper + `ion_colors` fixture matching the
per-ion color convention in
`pyathena/microphysics/photchem.py:_set_colors` (H=Greys,
He=Purples, C=Blues, N=Oranges, O=Greens, S=Reds; intensity
by ionization stage). Tests that produce diagnostic plots
use this so the same ion gets the same color across every
figure in the suite and in notebooks that use
`PhotChem.plt_rate_coeffs` / `plt_sed_sigma_pi`.
- `tests/figures/.gitignore` excluding all PNG/PDF output.
Also enriches the existing CT-rate balance test docstrings with a
provenance note clarifying that Draine 2011 and the Cloudy O+H
polynomial both fit Stancil et al. 1999 (+ Barragan et al. 2006),
so their close agreement is expected rather than independent
cross-validation. Cloudy poly is only valid for T > 200 K (see
`cloudy/source/atmdat_char_tran.cpp:103-134` reference comment).
Three new test files under `tests/microphysics/`, mirroring the existing `test_ct_rate_balance.py` style: smoke tests for finiteness and sign, reference-value spot-checks against published numbers, shape / monotonicity probes, and a diagnostic-plot test per file. `test_photx_sigma.py` (Verner+96 photoionization cross sections): - Threshold zero-cutoff, finite + positive above threshold for an ion catalog covering H, He, C, N, O, S. - Reference values: H I sigma at 13.6 eV ~ 6.3e-18, He I at 24.6 eV ~ 7.4e-18, He II at 54.4 eV ~ 1.6e-18 (Draine 2011 Tables 13.1-13.3; rtol 10-20%). - High-E decay shape (~E^-3 to E^-3.5 hydrogenic asymptote). - Round-trip `get_Eth` consistency with the sigma=0 boundary. - Diagnostic plot: sigma_pi(E) for the full catalog, per-ion colored by the `photchem.py` convention. `test_rec_rate.py` (Badnell radiative + dielectronic recomb): - Pins the (Z, N) = initial-ion convention used by `get_rr_rate`, `get_dr_rate`, `get_rec_rate`. The initial ion captures the electron: H II rec to H I is queried as (1, 0); He II rec is (2, 1); O III rec is (8, 6). - H II Case B at T = 1e4 K = 2.59e-13 cm^3/s (Draine 2011 Eq. 14.6). Case A > Case B verified. - DR temperature shape: O III DR rises from T=1e3 to T=1e5 K (Badnell 2006a Fig. 1). - Consistency: total = RR + DR exactly for non-H ions. - Diagnostic plot: RR (left panel) and DR (right panel) vs T for HII / HeII / HeIII / CII / NII / OII / OIII. `test_ci_rate.py` (Voronov 1997 collisional ionization): - (Z, N) = REACTANT-ion convention pinned. - Endothermic Boltzmann sign verified (rate grows with T for H I, He I, O I). - Cold cutoff: rate is exactly 0 at T = 100 K (the U > 80 branch). - H I CI at T = 2e5 K matches Voronov fit to 10%. - Isoelectronic ordering: O I -> O II -> O III ionization rate decreases with charge at fixed T = 1e5 K. - Diagnostic plot: beta_CI(T) for the followed-ion set. All plot tests use the shared `ion_colors` fixture from `tests/conftest.py` and the LaTeX `\,` thin-space convention for labels. Plot generation is on by default; pass `pytest --no-figures` to skip. 121 tests + 5 skipped (DR not defined for Z=1 or N=0; 3 plot tests skipped under `--no-figures`).
Replace the legend-on-the-side layout with inline rotated labels via `pyathena.plt_tools.line_annotation.line_annotate`, matched to each line's slope and underlined with a white-stroke patheffect so they stay legible over neighbours. Same-color-per-ion is preserved (via the `ion_colors` fixture), so visual identification across figures stays consistent. Additional polish: * `photx_sigma_overview.png`: clamp xlim to [3, 1e3] eV and ylim to [1e-20, 1e-16] cm^2 so the most relevant range (HII + soft X-ray) fills the panel. Add minor+major log grids. * `rec_rate_overview.png`: per-ion annotation x-coordinate chosen per-line to avoid overlapping labels in the dense Badnell-RR-decline / DR-peak regions. Grids added. * `ci_rate_overview.png`: same pattern -- per-line annotation x in the steep-rise portion. Grids added. * `ct_OH_rec_comparison.png`: keep the side legend (only 3 source lines + Draine per-J decomposition, dense overlap at low T makes inline labels overlap badly). Add grid. No assertion changes; 124 tests + 2 skipped continue to pass.
Round of figure refinements based on visual review:
* `rec_rate_overview.png`:
- Add S II, S III recombination -- completes the H/He/C/N/O/S
coverage (S is in the 10-followed-coolant set and a key
observational tracer; missing-from-figure was an omission).
- Split y-labels: left panel `\alpha_{rr}`, right panel
`\alpha_{dr}`.
- Tighten ylim top to 2e-10 (was 1e-9; the upper decade was
empty space).
* `ci_rate_overview.png`:
- Extend the ion catalog to high charge states (C IV/VI,
N IV/VII, O V/VIII, S VII/XII) since the T-range goes up to
1e8 K and at those temperatures the CIE balance is dominated
by highly-ionized species.
- `ion_color` now accepts an optional `num_ions` arg in
`conftest.py` so the per-ion color gradient spans
neutral->fully-stripped (Z+1) per element when high-q stages
appear. Low-q stages keep the photchem.py default.
- ylim top -> 1e-6 (was 1e-7) to show the full plateau region
at high T.
* `photx_sigma_overview.png`:
- Add S III (Eth=34.83 eV) since S III is in the followed-ion
set and a major observational tracer.
- Hand-tune each ion's inline-label x-position to avoid label
overlaps near the H I / O I (Eth ~ 13.6) and He I / C II
(Eth ~ 24) clusters.
* `ct_OH_rec_comparison.png`:
- ylim bottom raised 1e-12 -> 3e-11 to focus on the HII-region
regime where all three sources are within factor of 2.
* Whitespace normalization: collapse all " " (multiple spaces) after
commas and colons to single space across all four test files,
matching the surrounding pyathena code style.
127 tests + 2 skipped pass.
jeonggyukim
force-pushed
the
pyathena-ct-mvp
branch
from
June 8, 2026 09:49
2b0ea62 to
58098f5
Compare
jeonggyukim
marked this pull request as ready for review
June 8, 2026 09:50
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Sub-PR into #48
photchem-full. Adds a pytest test suite for the four microphysics rate modules and fixes one bug that the new tests caught. Does not wire charge transfer into the chemistry sweep -- that follows on a separate sub-branchpyathena-ct-wiring.Bug fix
ct_rate.pyreferenced a misspelled method name on the dispatch path for oxygen, so the oxygen-with-hydrogen charge-transfer ionization rate raisedAttributeError. Renamed to match the actual method.New tests under
tests/Four files cover the four rate modules in
pyathena/microphysics/: charge-transfer rates (ct_rate.py), photoionization cross sections (photx.py), radiative and dielectronic recombination (rec_rate.py), and collisional ionization rates (ci_rate.py). Each file checks the indexing conventions used by the rate functions, threshold and cutoff behavior, the sign of the temperature dependence, and published reference values for a few representative ions. Test docstrings cite the relevant atomic-data sources (Verner et al. 1996, Badnell, Voronov 1997, Kingdon & Ferland 1996, Draine 2011, Stancil et al. 1999) and describe what each test checks.Test infrastructure
tests/README.mddocuments how to run the suite.tests/conftest.pyprovides fixtures for a figures output directory, a--no-figurescommand-line option, and a per-ion color helper that matchesPhotChem._set_colorsso plots from tests use the same colors as plots fromPhotChem.plt_rate_coeffs.tests/figures/is git-ignored.Diagnostic plots
Each rate-module test file writes a diagnostic plot under
tests/figures/.Test plan
pytest tests/from the repo root. Seetests/README.md.Scope boundary
This PR adds tests and one bug fix only. Wiring charge transfer into
evolve_one_species, the cross-element coupling with hydrogen, and the heating term incool.pywill follow onpyathena-ct-wiring.