Skip to content

Initialize interpolation index in interp_profile (fixes nondeterministic solve)#170

Merged
krystophny merged 1 commit into
mainfrom
fix/wave-code-uninitialized-interp-index
Jul 1, 2026
Merged

Initialize interpolation index in interp_profile (fixes nondeterministic solve)#170
krystophny merged 1 commit into
mainfrom
fix/wave-code-uninitialized-interp-index

Conversation

@krystophny

@krystophny krystophny commented Jul 1, 2026

Copy link
Copy Markdown
Member

Problem

interp_profile (in both QL-Balance/src/base/wave_code_data_64bit.f90 and KiLCA/interface/wave_code_data_64bit.f90) declares a local integer ind and passes it to eval_neville_polynom without initializing it:

integer :: deg = 9, Dmin = 0, Dmax = 0, l, ind;   ! ind never set
do l = 1, dim_new
    call eval_neville_polynom(dim_old, r_old, q_old, deg, r_new(l), Dmin, Dmax, ind, q_new(l));
end do

ind is the in/out warm-start window index for the interpolation. On the first call it is read uninitialized. In eval_neville_polynom (KiLCA/interp/interp.h) the guard if (*ind < 0 || *ind > dim-1) *ind = dim/2; only catches out-of-range garbage; in-range garbage flows through find_index_for_interp, and out-of-range garbage triggers an out-of-bounds read of the grid array xg[*ind].

Because interp_profile interpolates the initial background profiles (q, n, Ti, Te, ...) onto the balance grid, this seeds a stack-garbage-dependent perturbation that propagates through the entire linear solve. The result is a solve that is not reproducible run to run for identical inputs, even single-threaded.

This is a pre-existing bug on main, independent of the fortnum migration. It is a major contributor to the ql-balance / KiLCA golden-record instability seen in the fortnum PR stack (#144).

Fix

Initialize ind = 0 before the loop in both copies of interp_profile. 0 is a valid warm-start window index; the interpolation is otherwise unchanged.

Verification

Case: ql-balance golden f_6_2 (m=6, n=2). Comparison quantity: flre/linear-data/m_6_n_2_flab_[1,0]/EB.dat.

Fails on main

valgrind --tool=memcheck --track-origins=yes on the unfixed binary:

ERROR SUMMARY: 40 errors from 23 contexts
  12 Conditional jump or move depends on uninitialised value(s)   at eval_neville_polynom_
  10 Use of uninitialised value of size 8                         at eval_neville_polynom_
   ... Uninitialised value was created by a stack allocation
       at interp_background_profiles_ (wave_code_data_64bit.f90:712)   [interp_profile ind]

Run-to-run (same binary, same inputs, OMP_NUM_THREADS=1): EB.dat differs at the first data value; max ~7e-4 relative.

Passes after fix

valgrind on the fixed binary: the 22 uninitialised-value reads in eval_neville_polynom are gone (remaining 1 error is an unrelated strcpy over-read, fixed separately).

5 fresh single-thread runs, EB.dat md5:

      5 8b5d3ba0f8a32aee

All byte-identical.

Notes

  • A smaller, separate run-to-run nondeterminism (~1e-9, valgrind-clean) remains and is tracked separately.

interp_profile passed the local integer ind to eval_neville_polynom
without initializing it. ind is the in/out warm-start window index; on
the first call it was read uninitialized (and could drive an
out-of-bounds read of the grid array), making the interpolated
background profiles depend on stack garbage and the whole solve
non-deterministic run to run.

Set ind = 0 before the loop in both copies of interp_profile.

Verified on the ql-balance f_6_2 case:
- valgrind memcheck: 22 "uninitialised value" reads in
  eval_neville_polynom (origin: interp_profile ind) -> 0.
- 5/5 fresh single-thread runs now produce byte-identical EB.dat;
  before, run-to-run EB differed by ~7e-4 relative.
@krystophny krystophny requested a review from marjohma July 1, 2026 17:00
@krystophny krystophny merged commit 6557919 into main Jul 1, 2026
2 checks passed
@krystophny krystophny deleted the fix/wave-code-uninitialized-interp-index branch July 1, 2026 18:45
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