Skip to content

Fix int32 overflow in ntcut that hijacks long traces into the classifier#434

Merged
krystophny merged 1 commit into
mainfrom
fix/ntcut-int32-overflow
Jun 27, 2026
Merged

Fix int32 overflow in ntcut that hijacks long traces into the classifier#434
krystophny merged 1 commit into
mainfrom
fix/ntcut-int32-overflow

Conversation

@krystophny

Copy link
Copy Markdown
Member

Problem

params_init computes the classification cut index as

ntcut = ceiling(ntimstep*ntau*tcut/trace_time)

ntimstep*ntau is an int32 * int32 product evaluated before the real
multiply. For second-scale traces ntau reaches ~2.3e6 (npoiper2=16384,
ntau = ceiling(dtau/dtaumin)), so ntimstep*ntau = 1000 * 2.3e6 = 2.3e9
exceeds 2^31, wraps negative, and * tcut(-1) flips ntcut positive.

ntcut > 0 then routes the entire run through trace_orbit_with_classifiers
instead of the normal macrostep path, silently changing loss times and the
confined fraction. Threshold is trace_time ~ 0.94 s at npoiper2=16384;
shorter traces never reach the overflow and are unaffected.

Found while benchmarking the W7-X high-mirror reactor case: a 1 s GC run gave
cf@100ms = 0.756 vs the correct 0.856 from a 100 ms run at the same
physical time, with everything but trace_time identical.

Fix

Extract microstep_cut_index, evaluate the product in real(dp), return
int64, and disable classification (ntcut = -1) whenever tcut <= 0, for any
trace length. ntcut becomes integer(8) (the comparison kt == ntcut already
uses integer(8) kt).

Verification

Exact arithmetic, before (int32, as on main) vs after (int64):

$ # ntimstep=1000, ntau=2297298, tcut=-1, trace_time=1
main  int32 ntcut = 1997669296    (> 0  => spurious classifier path)
fixed int64 ntcut = -2297298000   (< 0  => classification off, correct)

New regression test (would not compile/pass against the int32 path):

$ ctest -L smoke --output-on-failure
4/7 Test #32: test_ntcut_overflow ..............   Passed    0.05 sec
100% tests passed, 0 tests failed out of 7

Full fast suite on this branch: make test-fast -> 55/55 passed, 0 failed.

Single-particle W7-X reactor check (deterministic, GC, npoiper2=16384): particle
loss time at trace_time=0.1 and trace_time=1.0 was 168.348 us vs 246.019 us
before, and 168.348 us in both after; class_parts.dat is no longer written for
the 1 s run.

Golden records: untouched. They use short traces (no overflow), so the cut index
is bitwise-identical; the fix changes results only for trace_time >~ 0.94 s.

ntcut = ceiling(ntimstep*ntau*tcut/trace_time) evaluates ntimstep*ntau as a
32-bit product. For second-scale traces ntau reaches ~2.3e6 (npoiper2=16384,
ntau = ceiling(dtau/dtaumin)), so ntimstep*ntau = 1000*2.3e6 = 2.3e9 exceeds
2^31, wraps negative, and times tcut<0 flips ntcut positive. ntcut>0 then
routes the whole run through trace_orbit_with_classifiers instead of the normal
macrostep path, changing loss times and the confined fraction. Threshold is
trace_time ~ 0.94 s at npoiper2=16384; shorter traces are unaffected.

Evaluate the product in real(dp) and return int64 via microstep_cut_index;
tcut<=0 now yields ntcut=-1 (classification off) for any trace length. Add
test_ntcut_overflow.
@krystophny krystophny added tier/T3 physics or output behavior size/S review size up to 100 changed lines labels Jun 27, 2026
@krystophny krystophny merged commit 99ed6f4 into main Jun 27, 2026
9 checks passed
@krystophny krystophny deleted the fix/ntcut-int32-overflow branch June 27, 2026 07:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/S review size up to 100 changed lines tier/T3 physics or output behavior

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant