Skip to content

ql-balance/KiLCA golden record: run-to-run nondeterminism in the linear solve (root cause + fixes) #172

Description

@krystophny

Summary

The ql-balance / KiLCA golden-record comparison is unstable because the KiLCA linear solve is not reproducible run to run for identical inputs — even single-threaded. Two independent memory bugs in code shared by all backends are the cause; both are pre-existing on main and unrelated to the fortnum migration. This issue records the root-cause analysis and links the fixes.

Case used throughout: golden f_6_2 (m=6, n=2). Primary observable: flre/linear-data/m_6_n_2_flab_[1,0]/zone_0_poy_test_err.dat and EB.dat.

Root causes

1. Uninitialized interpolation index (dominant) — fixed in #170

interp_profile (in QL-Balance/src/base/wave_code_data_64bit.f90 and KiLCA/interface/wave_code_data_64bit.f90) passes a local integer ind to eval_neville_polynom without initializing it. ind is the in/out warm-start window index; on the first call it is read uninitialized (guard in KiLCA/interp/interp.h only catches out-of-range garbage; in-range garbage flows through, out-of-range garbage triggers an OOB read of the grid array). Because this interpolates the initial background profiles onto the balance grid, it seeds a stack-garbage-dependent perturbation that propagates through the whole solve.

  • valgrind memcheck: 22 "uninitialised value" reads in eval_neville_polynom, origin interp_profile's ind.
  • After fixing: those reads -> 0; run-to-run EB.dat goes from ~7e-4 relative spread to byte-identical across runs.

2. Buffer over-read on the run path — fixed in #171

calc_wave_code_data_, calc_wave_code_data_for_mode_, calc_conductivity_matrices_for_mode_ in KiLCA/interface/wave_code_interface.cpp strcpy a non-NUL-terminated Fortran character buffer of length *pathlength. strcpy over-reads past the buffer (and can overflow the fixed 1024-byte destination). Fixed by memcpy of the known length.

  • valgrind: "Invalid read of size 1" in calc_wave_code_data_for_mode_ -> 0.

3. Residual nondeterminism (open)

After #170 and #171, a much smaller run-to-run effect remains: the solve settles into one of two discrete states differing by ~1e-9 in EB.dat (~1 ULP in the resonant radius r_res). It is valgrind-clean (not uninitialized memory), single-threaded, and appears in both GSL and fortnum builds. Cause not yet identified (candidate: address/ordering-dependent branch). This is the last blocker to a byte-exact golden.

poy_test_err is a diagnostic, not a physics output

zone_0_poy_test_err.dat is the Poynting energy-balance consistency residual |div(PoyntingFlux) - (absorbed_power + j.E)| — the solution's own numerical error (max ~1e-3), a difference of nearly-equal terms. It amplifies last-ULP input differences by ~1e8 (a ~1e-9 field perturbation becomes ~50% per-node). It should not be compared byte/rtol-exact across runs or backends. Recommend excluding it from the golden or flooring it at its own scale (atol ~1e-3), documented as a diagnostic.

The fortnum migration is transparent

Relevant to #144 and #164: with the solve made deterministic (threads pinned), GSL (main) and fortnum builds produce byte-identical output across the whole linear-data tree, including r_res. The apparent GSL-vs-fortnum differences reported earlier (sort tie order; r_res 1-ULP) are the run-to-run bimodality of item 3, present in the unmodified GSL code too — not a fortnum defect. #164's own control experiment (perturbing GSL's own root reproduces the failure) is consistent with this. Once items 1-3 land, the ql-balance step exclusions added for #164 should be revisited and the fortnum-regression framing corrected.

Reproduction

Build ql-balance.x, run it twice on the same f_6_2 runfolder with OMP_NUM_THREADS=1 OPENBLAS_NUM_THREADS=1, and diff flre/linear-data/m_6_n_2_flab_[1,0]/EB.dat. On main the two runs differ; with #170 applied they are byte-identical (modulo item 3). valgrind --tool=memcheck --track-origins=yes on the same run surfaces both memory errors.

Status

@marjohma for visibility — this reframes the #144 golden failures as pre-existing KiLCA nondeterminism, not a fortnum regression.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions