Skip to content

fix(classification): fast_class regression + JPP 2023 paper reproduction#323

Merged
krystophny merged 4 commits into
mainfrom
fix/classification-regression
May 22, 2026
Merged

fix(classification): fast_class regression + JPP 2023 paper reproduction#323
krystophny merged 4 commits into
mainfrom
fix/classification-regression

Conversation

@krystophny
Copy link
Copy Markdown
Member

@krystophny krystophny commented Mar 19, 2026

Summary

Three logical commits that together reproduce the orbit-classification
figures of the 2023 JPP paper (Albert, Buchholz, Kasilov,
Kernbichler, Rath, J. Plasma Phys. 89(3), 955890301,
doi:10.1017/S0022377823000351)
on current main and fix two classification regressions blocking it.

  1. fix(classification): allow fast_class + tcut together

    • fast_class no longer marks regular orbits as lost. Previously
      ierr = ierr_cot propagated the classifier completion status to
      the integration error flag and every classified orbit was reported
      as lost (confined fraction ~ 0.57 instead of 0.94 for QI s=0.3).
      The fix sets regular = .True. for regular J_par orbits under
      fast_class .and. .not. class_plot; class_plot mode keeps the
      old ntcut-termination so classification output still gets written.
    • read_config no longer rejects fast_class .and. tcut > 0. The
      paper recipe combines both flags.
    • A classifier_combined golden record exercises both together.
  2. feat(examples/classification): JPP 2023 paper reproduction recipe

    • 12 input files (q{a,h,i}_{volume,s03,s06,fig8}.in) covering
      Figs 5-11 of the paper.
    • plot_paper_results.py produces fig5-7 losses, fig8 dashboard,
      and volume_classification + volume_topology (s, J_perp) maps.
    • run_all.sh downloads the wout files from the companion repo and
      runs all 12 cases under /tmp/simple_classification.
    • Removes the legacy plot_classification.py,
      postprocess_class.f90, simple.in, and example README.md
      superseded by the new script set.
  3. test(magfie): skip orbit chartmap comparison plot when matplotlib unavailable

    • Plot test no longer ImportError-crashes when optional Python deps
      are missing; it now exits 0 with a Skipping plot test: message.

Companion repository with equilibrium files, plot-essential run
outputs, and generated figures:
https://github.com/itpplasma/proxima-simple-classification

Verification

Full make test-fast locally (100% pass)

$ make test-fast
...
100% tests passed, 0 tests failed out of 44
Total Test time (real) = 343.46 sec

Failing test before the fix (test_bminmax_driver, case
bminmax_classifier_num_surf_zero) now passes; was caused by an
earlier branch revision that bumped num_surf for startmode=1 + num_surf=0. The volume configs now use startmode = 5 (the standard
volume-sampling entry) instead, which keeps the bminmax cache
semantics unchanged from #348.

Confined fractions match paper recipe (volume runs, 200k particles)

QA, QH, QI volume (s, J_perp) classification maps regenerated with
this branch are at:
https://github.com/itpplasma/proxima-simple-classification/tree/main/plots

Single-surface loss curves (Figs 5-7) and dashboard (Fig 8) are being
regenerated this week and will land in the same plots/ directory.

Test plan

  • make test-fast passes 100% (44/44) locally
  • test_bminmax_driver (which was failing on prior branch state)
    passes after switching volume configs to startmode=5
  • classifier_combined golden record covers fast_class + tcut
  • Volume (s, J_perp) plots reproduce the paper's QI, QH, QA
    maps with both J_par and ideal-topology classifier columns
  • Loss curves (Fig 5-7) and dashboard (Fig 8) regenerated with
    multharm=7 (in flight, ~4-6 days serial)

@qodo-code-review
Copy link
Copy Markdown

ⓘ You are approaching your monthly quota for Qodo. Upgrade your plan

Review Summary by Qodo

Restore volume sampling and bminmax output for classification

🐞 Bug fix ✨ Enhancement

Grey Divider

Walkthroughs

Description
• Restore volume sampling for num_surf=0 to enable classification diagnostics
• Fix fast_class and tcut compatibility by removing incorrect mutual exclusivity check
• Generate bminmax.dat output file with 101 rows for classification plotting
• Migrate classification binning from Fortran postprocessor to Python script
• Fix hardcoded array index and VMEC file reference in classification example
Diagram
flowchart LR
  A["startmode=1<br/>num_surf=0"] -->|"volume sampling<br/>uniform s in [0,1]"| B["sample_particles"]
  B --> C["compute_pitch_angle_params"]
  C -->|"get_bminmax<br/>for all num_surf"| D["bmin/bmax arrays"]
  D --> E["write_output"]
  E -->|"generates"| F["bminmax.dat<br/>101 rows"]
  G["class_parts.dat<br/>6 columns"] --> H["plot_classification.py"]
  F --> H
  H -->|"bin in Python"| I["class_jpar.pdf<br/>class_ideal.pdf"]
Loading

Grey Divider

File Changes

1. examples/classification/postprocess_class.f90 Miscellaneous +0/-100

Remove Fortran postprocessor for classification

• Deleted entire Fortran postprocessor file (100 lines)
• Functionality replaced by Python binning in plot_classification.py

examples/classification/postprocess_class.f90


2. src/params.f90 🐞 Bug fix +0/-3

Fix fast_class and tcut compatibility check

• Removed incorrect mutual exclusivity check between fast_class and tcut
• Both classifiers are complementary and can be used together

src/params.f90


3. src/simple_main.f90 🐞 Bug fix +20/-2

Enable volume sampling and bminmax output for classification

• Add volume sampling branch for num_surf=0 case in sample_particles
• Change get_bminmax condition from num_surf > 1 to num_surf /= 1 to include num_surf=0
• Add bminmax.dat output generation with 101 rows when classification is active
• Fix hardcoded array index by using dynamic index lookup for vertical line at s=0.25

src/simple_main.f90


View more (5)
4. examples/classification/plot_classification.py ✨ Enhancement +98/-63

Migrate classification binning from Fortran to Python

• Rewrite script to read class_parts.dat directly (all 6 columns) instead of separate
 Fortran-generated files
• Implement bin_classification function to bin particles in Python replacing Fortran postprocessor
• Fix hardcoded index bminmax[250,1] with dynamic index lookup using np.argmin
• Add main function wrapper and improve code structure

examples/classification/plot_classification.py


5. test/golden_record/compare_golden_results.sh 🧪 Tests +2/-2

Add classifier_combined to golden record tests

• Extend classifier test case handling to include new classifier_combined case
• Update condition to check for both classifier_fast and classifier_combined cases

test/golden_record/compare_golden_results.sh


6. examples/classification/Makefile ⚙️ Configuration changes +3/-12

Simplify Makefile by removing Fortran postprocessor

• Remove Fortran compiler variables and compilation rules for postprocess_class.x
• Simplify dependencies to directly generate class_parts.dat and bminmax.dat from simple.x
• Remove intermediate file targets (prompt*.dat, regular*.dat, stochastic*.dat)

examples/classification/Makefile


7. examples/classification/simple.in 🐞 Bug fix +1/-1

Fix VMEC file reference in classification example

• Fix VMEC file reference from QA to QH to match Makefile download target
• Ensures correct equilibrium file is used for classification example

examples/classification/simple.in


8. test/golden_record/classifier_combined/simple.in 🧪 Tests +15/-0

Add combined classifier golden record test configuration

• New test configuration file for combined classifier test case
• Enables both fast_class=.True. and tcut=1d-2 to exercise complementary classifiers
• Uses 32 test particles with num_surf=0 for volume sampling

test/golden_record/classifier_combined/simple.in


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented Mar 19, 2026

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (0) 📎 Requirement gaps (0) 📐 Spec deviations (0)

Grey Divider


Action required

1. Plot binning normalization bug 🐞 Bug ✓ Correctness
Description
bin_classification() normalizes perp_inv by its sample max, but doplot_inner() overlays bmin/bmax
boundary curves in a different normalization, so the heatmap bins and boundary curves are on
inconsistent y-scales and the plot can be physically misleading. If all perp_inv values are
identical (e.g., all 0), hp becomes 0 and the script divides by zero when computing bin indices.
Code

examples/classification/plot_classification.py[R10-22]

+    hs = 1.0 / ns
+    pmin = 0.0
+    pmax = np.max(perp_inv)
+    hp = (pmax - pmin) / nperp
+
+    prompt = np.zeros((nperp, ns))
+    regular = np.zeros((nperp, ns))
+    stochastic = np.zeros((nperp, ns))
+
+    for ipart in range(len(s)):
+        i = min(ns, max(1, int(np.ceil(s[ipart] / hs)))) - 1
+        k = min(nperp, max(1, int(np.ceil(perp_inv[ipart] / hp)))) - 1
+
Evidence
The binning scale is defined by pmax=np.max(perp_inv) and hp=(pmax-pmin)/nperp, then k uses
perp_inv/hp, meaning the vertical axis is effectively perp_inv/pmax. However the boundary curves are
plotted as bmin_global/bminmax[:,1|2], which corresponds to (1/bmin_local)/(1/bmin_global) i.e.,
perp_inv*bmin_global, a different normalization unless pmax happens to equal 1/bmin_global;
additionally, when pmax==pmin, hp==0 and perp_inv/hp raises a division-by-zero.

examples/classification/plot_classification.py[5-31]
examples/classification/plot_classification.py[33-56]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`examples/classification/plot_classification.py` bins `perp_inv` using `pmax = np.max(perp_inv)`, but overlays curves computed from `bminmax.dat` that assume normalization by the global minimum B (`bmin_global`). This makes the plotted heatmap and boundary curves inconsistent, and can also crash when `pmax == pmin` (hp=0).

### Issue Context
The boundary curves use `bmin_global / bminmax[:, 1|2]`, which is consistent with plotting `perp_inv * bmin_global` (dimensionless, ideally in [0,1]). The binning should use the same quantity, and must guard against `hp == 0`.

### Fix Focus Areas
- examples/classification/plot_classification.py[5-31]
- examples/classification/plot_classification.py[33-56]

### Suggested approach
- Load `bminmax` first in `main()` and compute `bmin_global`.
- Define `perp_norm = perp_inv * bmin_global` and clip to `[0, 1]` (or set explicit bounds).
- Update `bin_classification()` to bin `perp_norm` in a fixed range `[0,1]` (so `hp = 1.0/nperp`), avoiding `pmax=np.max(...)`.
- Add a defensive guard so `hp` cannot be 0 (even if you keep data-driven pmax).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Classifier skips bminmax for num_surf=0 🐞 Bug ✓ Correctness
Description
compute_pitch_angle_params() now calls get_bminmax when num_surf/=1, but
trace_orbit_with_classifiers() still only calls get_bminmax when num_surf>1. If num_surf remains 0
(e.g., startmode=2 reading a start.dat with varying radii), classification can use stale bmin/bmax
and compute incorrect passing/trap parameters.
Code

src/simple_main.f90[R771-775]

!$omp critical
        bmod = compute_bmod(z(1:3))
-        if (num_surf > 1) then
+        if (num_surf /= 1) then
            call get_bminmax(z(1), bmin, bmax)
        end if
Evidence
After this PR, simple_main uses if (num_surf /= 1) to trigger per-radius bmin/bmax, but
classification.f90 still guards with if(num_surf > 1). This leaves a gap for num_surf=0
workflows where bmin/bmax should vary with radius but won’t be updated inside the classifier’s
passing calculation.

src/simple_main.f90[762-777]
src/classification.f90[148-155]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`src/simple_main.f90` updated the bmin/bmax lookup guard to `num_surf /= 1`, but `src/classification.f90` still uses `num_surf &gt; 1` before calling `get_bminmax`. This can skip per-radius bmin/bmax updates when `num_surf==0`.

### Issue Context
Classifier computations (`passing`, `trap_par`, `perp_inv`) depend on accurate `bmin/bmax`. For `num_surf=0` (volume / mixed-radius starts), bmin/bmax should be obtained via `get_bminmax`.

### Fix Focus Areas
- src/simple_main.f90[762-777]
- src/classification.f90[148-155]

### Suggested approach
- Change the guard in `trace_orbit_with_classifiers` from `if(num_surf &gt; 1)` to `if (num_surf /= 1)` (matching simple_main), or explicitly handle `num_surf==0` as a case that must call `get_bminmax`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

3. Axis region not sampled 🐞 Bug ✓ Correctness
Description
For startmode=1 with num_surf=0, sample_particles() now calls the volume sampler with bounds [0,1],
but the volume sampler clamps the lower bound to s_min=0.01 so the axis region (s<0.01) is never
sampled. This contradicts the PR description’s stated sampling range and can bias diagnostics near
the axis.
Code

src/simple_main.f90[R428-431]

        if (1 == startmode) then
-            if ((0d0 < grid_density) .and. (1d0 > grid_density)) then
+            if (0 == num_surf) then
+                call sample(zstart, 0.0d0, 1.0d0)
+            else if ((0d0 < grid_density) .and. (1d0 > grid_density)) then
Evidence
The generic sample(zstart, 0.0d0, 1.0d0) call matches `sample_volume_single(zstart, s_inner,
s_outer) via the INTERFACE sample. sample_volume_single clamps s_lo = max(s_inner, s_min)`
with s_min=0.01d0, so even when called with s_inner=0.0 it will not generate particles with
s<0.01.

src/simple_main.f90[425-435]
src/samplers.f90[13-21]
src/samplers.f90[87-108]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
`sample_particles()` now routes `startmode=1, num_surf=0` to `sample(zstart, 0.0d0, 1.0d0)`, which dispatches to `sample_volume_single`. That sampler clamps the lower bound to `s_min=0.01d0`, so particles are never started near the axis.

### Issue Context
PR description indicates uniform sampling over s∈[0,1]. Current implementation excludes [0,0.01), which can change classification plots/diagnostics.

### Fix Focus Areas
- src/simple_main.f90[425-435]
- src/samplers.f90[87-108]

### Suggested approach
- If axis singularity avoidance is still needed, reduce clamp to something much smaller (e.g., 1e-8) or make `s_min` configurable.
- Alternatively, only clamp when `s_inner &lt; 0` or when coordinate transforms truly fail at/near the axis, rather than always excluding the region.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Golden tests miss bminmax 🐞 Bug ⚙ Maintainability
Description
write_output now writes bminmax.dat for classifier runs, but the golden-record comparison list for
classifier cases does not include it. Regressions in bmin/bmax table generation will not be detected
by CI golden comparisons.
Code

test/golden_record/compare_golden_results.sh[R126-130]

+        # Check if this is a classifier case with multiple files
+        elif [ "$CASE" = "classifier_fast" ] || [ "$CASE" = "classifier_combined" ]; then
            # List of files to compare for classifier_fast (excluding simple.in and wout.nc)
            # Note: fort.* files are excluded due to non-deterministic ordering in parallel execution
            CLASSIFIER_FILES="avg_inverse_t_lost.dat class_parts.dat confined_fraction.dat healaxis.dat start.dat times_lost.dat"
Evidence
SIMPLE writes bminmax.dat whenever classification is active (ntcut>0 or class_plot), but
compare_golden_results.sh’s CLASSIFIER_FILES omits bminmax.dat for
classifier_fast/classifier_combined, so the file is not compared between reference and current runs.

src/simple_main.f90[861-882]
test/golden_record/compare_golden_results.sh[126-136]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

### Issue description
Golden-record comparisons for classifier cases do not include `bminmax.dat`, even though it is now emitted by `write_output` when classification is active.

### Issue Context
Missing comparisons mean future changes to `get_bminmax` or table formatting won’t be caught.

### Fix Focus Areas
- test/golden_record/compare_golden_results.sh[126-136]

### Suggested approach
- Add `bminmax.dat` to `CLASSIFIER_FILES`.
- (Optional) Extend golden_record_sanity tests to include this file in at least one multi-file compare invocation.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment on lines +10 to +22
hs = 1.0 / ns
pmin = 0.0
pmax = np.max(perp_inv)
hp = (pmax - pmin) / nperp

prompt = np.zeros((nperp, ns))
regular = np.zeros((nperp, ns))
stochastic = np.zeros((nperp, ns))

for ipart in range(len(s)):
i = min(ns, max(1, int(np.ceil(s[ipart] / hs)))) - 1
k = min(nperp, max(1, int(np.ceil(perp_inv[ipart] / hp)))) - 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Plot binning normalization bug 🐞 Bug ✓ Correctness

bin_classification() normalizes perp_inv by its sample max, but doplot_inner() overlays bmin/bmax
boundary curves in a different normalization, so the heatmap bins and boundary curves are on
inconsistent y-scales and the plot can be physically misleading. If all perp_inv values are
identical (e.g., all 0), hp becomes 0 and the script divides by zero when computing bin indices.
Agent Prompt
### Issue description
`examples/classification/plot_classification.py` bins `perp_inv` using `pmax = np.max(perp_inv)`, but overlays curves computed from `bminmax.dat` that assume normalization by the global minimum B (`bmin_global`). This makes the plotted heatmap and boundary curves inconsistent, and can also crash when `pmax == pmin` (hp=0).

### Issue Context
The boundary curves use `bmin_global / bminmax[:, 1|2]`, which is consistent with plotting `perp_inv * bmin_global` (dimensionless, ideally in [0,1]). The binning should use the same quantity, and must guard against `hp == 0`.

### Fix Focus Areas
- examples/classification/plot_classification.py[5-31]
- examples/classification/plot_classification.py[33-56]

### Suggested approach
- Load `bminmax` first in `main()` and compute `bmin_global`.
- Define `perp_norm = perp_inv * bmin_global` and clip to `[0, 1]` (or set explicit bounds).
- Update `bin_classification()` to bin `perp_norm` in a fixed range `[0,1]` (so `hp = 1.0/nperp`), avoiding `pmax=np.max(...)`.
- Add a defensive guard so `hp` cannot be 0 (even if you keep data-driven pmax).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread src/simple_main.f90
Comment on lines 771 to 775
!$omp critical
bmod = compute_bmod(z(1:3))
if (num_surf > 1) then
if (num_surf /= 1) then
call get_bminmax(z(1), bmin, bmax)
end if
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Classifier skips bminmax for num_surf=0 🐞 Bug ✓ Correctness

compute_pitch_angle_params() now calls get_bminmax when num_surf/=1, but
trace_orbit_with_classifiers() still only calls get_bminmax when num_surf>1. If num_surf remains 0
(e.g., startmode=2 reading a start.dat with varying radii), classification can use stale bmin/bmax
and compute incorrect passing/trap parameters.
Agent Prompt
### Issue description
`src/simple_main.f90` updated the bmin/bmax lookup guard to `num_surf /= 1`, but `src/classification.f90` still uses `num_surf > 1` before calling `get_bminmax`. This can skip per-radius bmin/bmax updates when `num_surf==0`.

### Issue Context
Classifier computations (`passing`, `trap_par`, `perp_inv`) depend on accurate `bmin/bmax`. For `num_surf=0` (volume / mixed-radius starts), bmin/bmax should be obtained via `get_bminmax`.

### Fix Focus Areas
- src/simple_main.f90[762-777]
- src/classification.f90[148-155]

### Suggested approach
- Change the guard in `trace_orbit_with_classifiers` from `if(num_surf > 1)` to `if (num_surf /= 1)` (matching simple_main), or explicitly handle `num_surf==0` as a case that must call `get_bminmax`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@krystophny krystophny changed the title Restore volume sampling and bminmax output for classification Fix classification regression and reproduce JPP 2020 paper results Mar 20, 2026
@krystophny krystophny force-pushed the fix/classification-regression branch from a3210dd to cec6270 Compare March 27, 2026 17:44
@krystophny krystophny force-pushed the fix/classification-regression branch from 4105177 to 23db82f Compare May 21, 2026 15:21
krystophny added a commit that referenced this pull request May 21, 2026
The PR #323 volume configs diverged from the published paper recipe in
every knob except the equilibrium file. Bring them back into line with
Albert+ 2023 JPP doi:10.1017/S0022377823000351, page 11:

- ntestpart 100000 -> 200000 (paper used 500000; compromise for runtime)
- trace_time 1.5d-2 -> 1.5d-1 (paper-era poster setting)
- tcut       1d-2   -> 1d-1   (classification cut at 100 ms per paper §3)
- class_plot .True. -> .False. so particles keep their random (s,theta,phi)
  from num_surf=0 volume sampling instead of being pinned to phi=0.
  class_plot=.True. in classification.f90 forces z(3)=cut_in_per*fper for
  every particle, which collapses the sampling onto a single phi plane;
  for QI in particular the per-surface bmin lives at phi != 0, so the
  volume J_perp data tops out well below the deeply-trapped boundary.
- add multharm = 7 (paper value; current default differs)
- remove cut_in_per (no longer used with class_plot=.False.)

plot_paper_results.py: nperp 100 -> 300 to match the paper's 50 x 300
(s, J_perp) grid.
@krystophny krystophny force-pushed the fix/classification-regression branch from 0641818 to 7e7e952 Compare May 22, 2026 06:03
@krystophny krystophny changed the title Fix classification regression and reproduce JPP 2020 paper results fix(classification): fast_class regression + JPP 2023 paper reproduction May 22, 2026
fast_class previously set ierr = ierr_cot, which propagated the
classifier completion status to the integration error flag. Every
classified orbit (including regular ones) was then marked lost, so
confined fractions were wildly wrong (e.g. fc=0.57 instead of 0.94
for QI s=0.3 at 100k particles).

Fix: when fast_class is on and class_plot is off, set regular=.True.
for regular J_parallel orbits instead of stomping ierr. That stops
integration early, counts the orbit in confpart for the remaining
time steps, and records times_lost = trace_time. When class_plot is
on, the old ntcut termination is preserved so classification output
is still written.

Also remove the read_config guard rejecting fast_class + tcut > 0:
the JPP 2023 recipe combines fast classification with a tcut, and
the guard blocks that valid combination. Add a classifier_combined
golden record exercising both flags together.
Add per-configuration input files for the three stellarators studied
in Albert et al., J. Plasma Phys. 89(3), 955890301 (2023):

  - QI (Drevlak 2014, wout_23_1900_fix_bdry.nc)
  - QH (Drevlak 2018, wout_qh_8_7.nc)
  - QA (Henneberg 2019, wout_henneberg_qa.nc)

For each, four cases reproduce the published figures:

  - q?_volume:  Figs 9-11, volume (s, J_perp) classification maps
                (startmode=5, num_surf=0, ntestpart=200000,
                 multharm=7, trace_time=0.15 s, tcut=0.1 s,
                 fast_class=.True.)
  - q?_s03:     Figs 5-7, loss curves at s=0.3
  - q?_s06:     Figs 5-7, loss curves at s=0.6
  - q?_fig8:    Fig 8, classification dashboard at s=0.6

plot_paper_results.py generates fig5-7 losses, fig8 dashboard, and
volume_classification + volume_topology (s, J_perp) maps (J_par and
ideal-topology classifier columns) from class_parts.dat /
times_lost.dat / bminmax.dat / confined_fraction.dat. run_all.sh
downloads the wout files from the proxima-simple-classification repo
on first call and runs all 12 cases under /tmp/simple_classification.

Equilibrium files, plots, and committed plot-essential run outputs
live in the companion repository:
https://github.com/itpplasma/proxima-simple-classification

Remove the legacy in-place runner (plot_classification.py,
postprocess_class.f90, simple.in, README.md) superseded by the new
script set. Makefile, plot_paper_results.py and run_all.sh headers
consistently cite the 2023 paper as the reproduction target.
…vailable

Wrap the numpy/matplotlib/netCDF4 imports in a try/except so the
plot test exits 0 with a 'Skipping plot test: <reason>' message
when the optional Python deps are not installed, instead of
ImportError crashing the test job.
@krystophny krystophny force-pushed the fix/classification-regression branch from 7e7e952 to 52639bd Compare May 22, 2026 06:18
…r_fast

The golden record compares the current branch to a freshly built `main`
binary. When a bug fix on this branch legitimately changes a case's
output, the comparison will always fail until the fix lands on main.

Add GOLDEN_RECORD_SKIP_CASES (space-separated list of case names) to
compare_golden_results.sh and consume it from CMake test registration.
Use it to skip classifier_fast while the fast_class + ierr fix in
classification.f90 is in flight: ref/cur divergence is the intended
outcome (regular orbits now lasting trace_time instead of being marked
as early losses). classifier_combined (new) and classifier_fast both
exercise fast_class with class_plot=.True., so the new code path is
still covered by classifier_combined and the new path agrees between
ref and cur because main's read_config used to reject fast_class+tcut
and is skipped on the reference side.

Once this fix lands on main and main is rebuilt as the reference,
remove classifier_fast from GOLDEN_RECORD_SKIP_CASES.
@krystophny krystophny merged commit e404496 into main May 22, 2026
7 checks passed
@krystophny krystophny deleted the fix/classification-regression branch May 22, 2026 08:22
krystophny added a commit that referenced this pull request May 22, 2026
## Summary

Follow-up to #323. The squash-merged classification fix means main now
matches the output that this branch's `simple.x` produces for
`golden_record_classifier_fast`, so the temporary
`GOLDEN_RECORD_SKIP_CASES=classifier_fast` escape hatch added in #323
can be removed.

Two small changes:

1. `test/tests/CMakeLists.txt`: drop
`GOLDEN_RECORD_SKIP_CASES=classifier_fast`
   from the per-test ENVIRONMENT. The `SKIP_CASES` plumbing in
   `compare_golden_results.sh` stays in place for future
   intentional-divergence bug fixes (just don't set the env var
   unless a fix needs it).
2. `test/golden_record/compare_golden_results.sh`: drop
   `avg_inverse_t_lost.dat` from the classifier file list. That file is
   only written when at least one particle is actually lost; with the
   fast_class fix the small `classifier_fast` test loses zero particles,
   so neither ref nor cur writes it. The comparator's "reference file
   missing" check then spuriously fails even though both runs agree
   byte-for-byte on the files that do exist.

## Verification

Full regression suite, against freshly rebuilt main reference (cached
`runs/run_main` and `simple_main` rebuilt to pick up #323):

```
$ make test-regression
...
13/13 Test #50: golden_record_sanity ....................   Passed    0.57 sec
100% tests passed, 0 tests failed out of 13
Total Test time (real) = 411.09 sec
```

Both classifier cases:

```
1/1 Test #45: golden_record_classifier_fast ...   Passed   16.73 sec
1/1 Test #44: golden_record_classifier_combined ...   Passed   16.18 sec
```

## Test plan

- [x] `make test-regression` passes 13/13 locally
- [x] `golden_record_classifier_fast` (the test #323 had to skip) passes
- [x] `golden_record_classifier_combined` (new in #323) still passes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant