Port remaining KiLCA C++ to Fortran (fortnum-backed, single PR)#161
Open
krystophny wants to merge 53 commits into
Open
Port remaining KiLCA C++ to Fortran (fortnum-backed, single PR)#161krystophny wants to merge 53 commits into
krystophny wants to merge 53 commits into
Conversation
ad79c4f to
d0cf53e
Compare
This was referenced Jul 1, 2026
marjohma
added a commit
that referenced
this pull request
Jul 1, 2026
A ported code that never terminates (surfaced by the fortnum port on #161's golden run, which hung >30 min) would otherwise tie up a runner until GitHub's 6h cap. Add a job-level timeout-minutes: 30 backstop, plus a portable per-case 'timeout' in gr_run_case around both the executable and the QL-Balance prepare hook (which runs KiLCA internally). GR_CASE_TIMEOUT (default 600s) is configurable and degrades to unguarded when no timeout/gtimeout is present.
This was referenced Jul 2, 2026
29211aa to
b15b4ff
Compare
7f59071 to
c0fdc99
Compare
There was a problem hiding this comment.
Review verdict: Request changes
Review complete. Approve with follow-up. The core ports (ddeabm ODE, ZEAL root-finding, vel_integral, 1F1m/hypergeometric) are numerically verified by golden-record tests. 5 non-blocking items filed for follow-up: stale -lslatec/libgsl-dev in legacy scripts/CI, and undocumented WKB_verbose default change. No blockers.
Reimplement the arbitrary-odd-degree data splines (alloc/calc/eval/eval_d/ free) in Fortran behind the unchanged spline_alloc_/spline_calc_/spline_eval_/ spline_eval_d_/spline_free_ C ABI, so all KiLCA C++ callers link without change. The opaque handle stays the C address of the spline state; caller-owned x and coefficient arrays are referenced through stored C pointers; boundary and band systems use LAPACK dgesv/dgbsv as before. Index arithmetic mirrors the C++ via 0-based pointer remapping. Add test_spline: an odd-degree natural spline reproduces linear data exactly (value and derivative), honors the node interpolation condition, and handles the two-function (dimy=2) strides through spline_eval_d. 26/26 fast tests pass.
Reimplement the confluent hypergeometric 1F1(1,b,z) family (Kummer series and continued-fraction variants) in Fortran. Each routine keeps its single-trailing -underscore C symbol via bind(C, name=...), so the external Fortran callers in KiLCA conductivity and QL-Balance W2_arr link unchanged; arguments stay real(c_double) by reference. The quadrature variant defers to fortnum's clean-room hyperg_1f1_a1 instead of the former fortnum C ABI shim. Verified byte-faithful against the original C++ to ~2e-15 across both the Kummer (|z/b|<0.1) and continued-fraction (|z/b|>=0.1) branches; test_hyper1f1 locks in those reference values. 27/27 fast tests pass.
Reimplement the velocity-space response integral in Fortran behind the unchanged calc_velocity_integral_ C symbol. The integrand is a bind(C) function and the driver calls the same fortnum semi-infinite adaptive quadrature (fortnum_integrate_qagiu) via its C ABI, so results are bit-for-bit identical to the C++ version (verified 0 difference across all three index branches). test_vel_integral locks in the reference values. 28/28 fast tests pass.
Move the Fortran-facing eval_neville_polynom_ and eval_neville_polynom_ready_ out of interp.cpp into kilca_neville_m, preserving the single-underscore C symbols and by-reference int*/double* argument convention so the KiLCA and QL-Balance wave_code_data callers link unchanged. The C++ inline algorithm in interp.h is left in place for the C++ call sites. The grid wrapper now caps the moving-window degree to the available points (edeg = min(deg, dim-1)). With enough points (dim > deg, the production case) this is identical to the C++; for a short grid the original dimx-deg-1 went negative and read out of bounds, which -fcheck=all traps. test_neville checks exact reproduction of a degree-4 polynomial (value and derivatives) through both wrappers; 29/29 fast tests pass, including the restored test_kim_adapter.
Turn on BUILD_FORTRAN_MODULE_INTERFACE so SUNDIALS builds fcvode_mod, fnvector_serial_mod, fsundials_core_mod, etc. This lets the KiLCA solver drive CVODE directly from Fortran during the C++ -> Fortran translation, instead of hand-writing bind(C) interfaces for the CVODE/N_Vector/SUNMatrix/SUNLinearSolver C API. No behavior change; 29/29 tests pass.
Remove the C++ antenna class. The Fortran antenna_data module now parses
antenna.in and modes.in directly (read_antenna_settings_, replacing
antenna::read_settings) and owns ra/wa/I0/flab/dma/modes/flag_debug/flag_eigmode.
The remaining C++ that read sd->as->{dma,flab,modes,ra,wa,flag_eigmode}
(core.cpp mode loops, main_linear/main_eig_param, calc_flre_quants) now call
bind(C) getters get_antenna_*; settings no longer holds an antenna* and
settings::read_settings calls the Fortran reader.
Fortran list-directed reads parse the 'value #comment' lines (the (re,im)
complex frequency parses natively). test_antenna_settings writes a known
antenna.in/modes.in and checks every getter. 30/30 fast tests pass.
Remove the C++ output_sett class. The Fortran output_data module parses output.in (read_output_settings_, replacing output_sett::read_settings) and owns flag_background/emfield/additional/dispersion, flag_debug, num_quants and the flag_quants array. The ~33 C++ read sites across mode/flre/background that used sd->os->flag_* now call bind(C) getters get_output_*; settings no longer holds an output_sett*. output_sett.h keeps its constants.h include to preserve the transitive include the tree relied on. test_output_settings locks the output.in layout and flag_quants indexing. 31/31 fast tests pass.
Remove the C++ back_sett class. The Fortran background module now parses background.in directly (read_background_settings_, replacing back_sett::read_settings) and owns rtor/rp/B0/path2profiles/calc_back/ flag_back/N/V_gal_sys/V_scale/zele/zion/flag_debug, plus the derived mass/charge/huge_factor values. ~80 C++ read sites across 17 in-build files (background, flre, imhd, hom_medium, mode, interface, core) now call bind(C) getters get_background_*; settings no longer holds a back_sett*. flag_back and path2profiles needed extra care: flag_back is a single character threaded into several Fortran routines as a 1-char buffer (get_background_flag_back_ returns the char by value; call sites build a local 2-byte null-terminated buffer), and path2profiles is copied into a caller-supplied buffer (get_background_path2profiles_). mass/charge pointer aliases became local 2-element arrays filled from the indexed getters. cond_profs.cpp and calc_back_slatec.cpp are dead (not in the active ADAPTIVE_GRID_GENERATOR=POLYNOMIAL build) and were left untouched; they'll be deleted with the rest of the C++ bridge in the final Fortran-only cleanup. test_back_sett locks the background.in layout, the derived particle mass/charge/huge_factor values, and the flag_back/path2profiles string handling. 32/32 fast tests pass.
Remove the C++ eigmode_sett class. The Fortran eigmode_sett_data module parses eigmode.in (read_eigmode_settings_, replacing eigmode_sett::read_settings) and owns fname/search_flag/rdim/rfmin/rfmax/ idim/ifmin/ifmax/stop_flag/eps_res/eps_abs/eps_rel/delta/test_roots/ flag_debug/Nguess/kmin/kmax/fstart/n_zeros/use_winding. The ~25 C++ read sites in find_eigmodes.cpp, calc_eigmode.cpp and core.cpp now call bind(C) getters; settings no longer holds an eigmode_sett*. fstart (complex<double>*) becomes indexed scalar getters get_eigmode_fstart_re_/im_; zersol's set_start_array stores the pointer rather than copying, so find_eigmodes builds a local array that lives through the synchronous FindZeros call, matching the original lifetime. fname is filled into a caller-supplied buffer like path2profiles. transf_quants.cpp is dead code (not in the active build) and was left untouched. test_eigmode_sett locks the eigmode.in layout, all scalar fields, the fname string, and the fstart complex array. 33/33 fast tests pass. All four KiLCA settings classes (antenna, output_sett, back_sett, eigmode_sett) are now Fortran.
Remove the C++ maxwell_eqs_data class. Since multiple flre_zone instances can be alive at once (each holding its own per-zone snapshot of the transient maxwell_equations Fortran module state), this uses the same opaque-handle pattern as kilca_spline_m rather than a singleton: maxwell_eqs_data_create_ allocates a Fortran derived-type instance and snapshots num_vars/num_eqs/ der_order/dim_Ersp_state/iErsp_state/dim_Ersp_sys/iErsp_sys/dim_Brsp_sys/ iBrsp_sys/sys_ind via the SAME existing C-ABI calls (copy_module_data_to_maxwell_eqs_data_struct_f_ etc.) the old C++ constructor used, so the values are byte-identical. flre_zone.h's "maxwell_eqs_data *me" member becomes an "intptr_t" handle; ~50 me-> access sites across flre_zone.cpp and calc_flre_quants.cpp now call bind(C) getters. Dropped the entirely-unused struct fields (max_der_order_Ersp, names_sys/state/comp, iEr/iEs/.../iddBp) that were never set or read anywhere. transf_quants.cpp is dead code (not in the active build) and was left untouched. test_maxwell_eqs_data isolates the trickiest part -- der_order's flat C row-major [3][3] layout vs Fortran 2D indexing -- with fake C-ABI providers supplying known, all-distinct values, confirming every getter's index mapping exactly (including the Fortran-1-based-to-C-0-based shifts). 34/34 fast tests pass, including the end-to-end EM solve in test_kim_solver_em.
Remove the C++ disp_profiles class. Per-instance handle (same pattern as kilca_spline_m/kilca_maxwell_eqs_data_m): each flre_zone owns its own instance, allocated via disp_profiles_create_ and freed via disp_profiles_destroy_. calculate_dispersion_profiles now calls the existing Fortran calc_dispersion subroutine natively (Fortran-to-Fortran, so the gfortran hidden-string-length convention for the flagback argument is handled automatically by the compiler, unlike the old C ABI path which passed it manually). The C++ sort_dispersion_profiles method is not translated: SORT_DISPERSION_PROFILES is hard-coded to 0 in CMakeLists.txt, so the #if SORT_DISPERSION_PROFILES==1 branch calling it was dead code in the active build, matching how cond_profs.cpp and calc_back_slatec.cpp were left untouched earlier. save_dispersion_profiles calls the existing C++ save_cmplx_matrix_to_one_file (io/inout.cpp), now given extern "C" linkage (a minimal, additive change; it has no overloads and no other declaration) so the new Fortran code can call it directly. flre_zone.h's disp_profiles* dp member becomes an intptr_t handle; the 3 call sites in flre_zone.cpp now use disp_profiles_create_/calculate_/save_. test_disp_profiles verifies the create/calculate/save/destroy lifecycle and, via a fake calc_dispersion that encodes the grid index into its output plus a fake save_cmplx_matrix_to_one_file that checks every grid point landed at the expected flat offset, empirically confirms the k+dimk*i / p+dimp*i C pointer arithmetic translated correctly to Fortran array slicing. 35/35 fast tests pass.
Remove the C++ sysmat_profiles class. Per-instance handle (same pattern as
kilca_spline_m/kilca_maxwell_eqs_data_m/kilca_disp_profiles_m): each
flre_zone owns its own instance. The existing mode_data Fortran module's
sp_ptr bridge (set_sysmat_profiles_in_mode_data_module_) needed no change --
it was already an opaque integer(pp) handle, agnostic to whether it points at
a C++ object or a Fortran one.
calc_and_spline_sysmatrix_profiles becomes sysmat_profiles_create_. Since
flre_zone stays a C++ class, the caller (flre_zone.cpp) now reads
zone->cp->{flag_back,path2linear,NC} and zone->{Nwaves,max_dim_c,eps_out,
flag_debug,r1,r2,wd->r_res} itself (only C++ can do that member access) and
passes them in directly. The adaptive-grid callback
(calc_adaptive_1D_grid_4vector_, an existing C++ function taking a C function
pointer) is registered via c_funloc on a bind(C) Fortran sample_sysmat_func,
exactly mirroring the original callback. Sorting uses fortnum's argsort
directly (native Fortran, 1-based) instead of the former sort_index_doubles
C++ wrapper. calc_diff_sys_matrix_, spline_alloc_/spline_calc_, and
save_cmplx_matrix (now given extern "C" linkage, like
save_cmplx_matrix_to_one_file earlier) are called via their existing C ABI
entry points, exactly as the former C++ methods did.
The dead branch (eval_diff_sys_matrix_f_, sort_dispersion_profiles-style
SORT_DISPERSION_PROFILES-equivalent USE_SPLINES_IN_RHS_EVALUATION==1 path,
alloc_sysmatrix_profiles_) is dropped: USE_SPLINES_IN_RHS_EVALUATION is
hard-coded to 0, so rhs_func.cpp's only live path is the "exact"
calc_diff_sys_matrix_ branch, and eval_diff_sys_matrix_f_/
alloc_sysmatrix_profiles_ have zero callers.
flre_zone.h's sysmat_profiles* sp member becomes an intptr_t handle;
rhs_func.h's rhs_func_params.sp follows suit (4 call sites in rhs_func.cpp
updated). eval_diff_sys_matrix_ (used by rhs_func.cpp) now takes the handle
directly and reads sidM/dimM via getters instead of struct member access.
test_sysmat_profiles verifies the argsort-based grid-point rearrangement
(translated from sort_index_doubles + the i/j flat-index M-rearrangement
loop): the fake adaptive-grid step adds two points deliberately out of
sorted order via the real c_funloc callback roundtrip, and the fake
spline_calc_ (which receives the fully rearranged M array) confirms every
grid point's row landed at the expected post-sort offset. 36/36 fast tests
pass, including the end-to-end EM solve in test_kim_solver_em.
Remove the C++ cond_profiles class. Per-instance handle (same pattern as kilca_maxwell_eqs_data_m/kilca_sysmat_profiles_m): each flre_zone owns its own instance. calc_and_spline_main_conductivity_profiles becomes cond_profiles_create_. flre_zone stays a C++ class, so the caller (flre_zone.cpp) computes the adaptive-grid bounds from its (still-C++) background instance (a = max(r1-1, bp->x[0]), b = min(r2+1, bp->x[dimx-1])), calls eval_path_to_linear_data itself, and passes wd->m/n/olab/r_res/omov in directly, since wave_data is not yet Fortran-resident either. Live functions translated: sample_cond_func_polynom (a c_funloc callback into adaptive_grid_polynom_res, mirroring the sysmat_profiles pattern), calc_splines_for_K_polynom/set_arrays_for_K_polynom/calc_splines_for_C/ calc_C_matrices (complex binomial-coefficient sum into the conductivity matrix, with the Galilean correction call preserved), eval_K_matrices/ eval_C_matrices (internal spline evaluation), eval_all_K_matrices/ eval_all_C_matrices (bind(C), used by flre_quants.cpp), and the iKs/iKa/ iCs/iCa index-arithmetic helpers exposed via get_cond_iks_. eval_c_matrices_f_/eval_k_matrices_f_ and calc_and_spline_conductivity_for_ point_/delete_conductivity_profiles_f_ also needed full translation: even though the conductivity.f90 branch that calls the "exact" per-point path is provably unreachable at runtime (background's flag_back is read once at startup and never reassigned, so ctensor/kmatrices' `backflag == flag_back` guard always holds), the symbols are still statically referenced by that live Fortran code and must resolve at link time. The per-point path reads fields of a *different*, dynamically-supplied background/wave_data C++ instance (not the Fortran-resident background module), so two small additive extern "C" accessors were added (get_background_obj_dimx_/x0_/ xlast_ in background.h/.cpp, get_wave_data_obj_omov_re_/im_ in the new wave_data.cpp) instead of duplicating per-instance field access in Fortran. A second adaptive-grid forwarding wrapper, adaptive_grid_polynom_err, was added to adaptive_grid_pol.h/.cpp (extern "C", forwards to the existing dim_err/ind_err C++ overload) without touching the overloaded function's own linkage. Caught and fixed two bugs before any test ran: an assumed-size array-section slice (f(off:)) passed to an assumed-size dummy, illegal in Fortran -- fixed to sequence-associated scalar element references (f(off+1)); and eps in the adaptive_grid_polynom_res interface incorrectly declared VALUE when the oracle signature takes double *eps (now intent(inout)). Dropped as genuinely dead (zero callers anywhere, confirmed via grep): alloc_conductivity_profiles_ (folded into the two live constructors that need it), the non-_polynom siblings (sample_cond_func, calc_splines_for_K, set_arrays_for_K, smooth_arrays_for_K -- only reachable via the inactive ADAPTIVE_GRID_GENERATOR=USUAL cond_profs.cpp variant), and the *_fine debug dumpers (commented-out call sites only). flre_zone.h's cond_profiles *cp becomes an intptr_t handle; ~10 call sites in flre_zone.cpp/flre_quants.cpp/wave_code_interface.cpp rewired to the new getters (get_cond_nk_/dimk_/nc_/dimc_/dimx_/x_ptr_/k_ptr_/iks_/flag_back_/ path2linear_). zone->cp->wd->m (flre_zone.cpp, 2 sites) is simplified to zone->wd->m, the same underlying wave_data instance via zone's own member. 36/36 fast tests pass, including the end-to-end EM solve in test_kim_solver_em, which exercises the real conductivity path through ctensor/diff_sys.f90 -> eval_c_matrices_f_ -> calc_C_matrices -> spline evaluation.
Remove the C++ flre_quants class. Per-instance handle (same pattern as kilca_cond_profiles_m/kilca_sysmat_profiles_m): each flre_zone owns its own instance. flre_zone stays a C++ class, so the caller (flre_zone.cpp) extracts everything the constructor needs (flre_order, dim, r, Ncomps, EB_mov, wd->omov, bc1/bc2, index, the cp/me handles, and a freshly computed path2linear) and passes it directly, mirroring the cond_profiles_create_ call site. The C++ class held a FIXED set of 8 quantities (num_tot=8; "add more quants if needed" was never exercised) dispatched through calc_quant[]/save_quant[] function-pointer arrays populated once in the constructor. Since the set is fixed and known at translation time, the dispatch is rendered as direct conditional calls instead of literal Fortran procedure-pointer arrays - behaviorally identical, far less error-prone to hand-translate. For the same reason, qloc[i]/qint[i] (a C++ array of per-quantity heap buffers, sized dimq[i]*dimx and allocated only for requested quantities) becomes 8 separately-named, always-allocated flat arrays: the oracle's own get_output_flag_quants_/flag[] gating already guaranteed an unallocated buffer was never read, so always-allocating changes memory footprint only in the (apparently never exercised) all-quantities-off corner case, never observable output. All `if (DEBUG_FLAG) fprintf(...)` blocks are dropped: DEBUG_FLAG is a compile-time macro hard-coded 0 in code_settings.h, so these blocks provably never execute (matching the SORT_DISPERSION_PROFILES precedent). save_total_absorbed_power (declared but never defined or called) and the file-local `binary_search` (declared, defined, never called) are dropped as genuinely dead. calc_quant/save_quant member functions are called only from within this class's own dispatch methods, so they became plain private module subroutines - only the handful of true external entry points (create/destroy/calculate_local_profiles/calculate_integrated_profiles/ save_profiles/calculate_JaE/transform_quants_to_lab_cyl_frame/ interp_diss_power_density/interp_current_density) need bind(C) names. All array-reinterpretation typedefs the oracle used (e.g. `typedef double curr_dens[3][2][3][2][dimx]; curr_dens &CD = *((curr_dens *)qp->qloc[...]);`) were replaced with explicit flat-index arithmetic functions, derived and cross-checked against the underlying spline-channel layouts already verified in kilca_cond_profiles_m (iKs/iCs) and against shared.cpp's binomial_coefficients (`BC[n + k*(N+1)]`) - not re-guessed from the C array-dimension order alone. eval_neville_polynom (interp_diss_power_density/interp_current_density) and the maxwell_eqs_data getters (get_me_iersp_sys/get_me_ibrsp_sys/ get_me_der_order) are called natively via `use kilca_neville_m`/ `use kilca_maxwell_eqs_data_m`, reusing their real Fortran interfaces instead of re-declaring bind(C) shims. eval_all_k_matrices/eval_all_c_matrices/ get_cond_nc are reused the same way from kilca_cond_profiles_m. save_real_array (io/inout.h) gained extern "C" linkage (single declaration, no overloads, same minimal-additive technique as save_cmplx_matrix earlier) since it had none and is now called from Fortran. Two real Fortran bugs caught only by the gfortran build itself: eval_hthz_'s bind(C) interface had both a scalar dummy `r` and an array dummy `R` - "Duplicate symbol" case-insensitivity collision, same class of bug as the hyper1F1 An/an and sysmat_profiles r/R collisions earlier this port; renamed to rval/hout. And two output arguments (divpfd, dpd) were declared scalar but passed to assumed-size array dummies (spline_eval_d_/eval_neville_ polynom) - illegal beyond simple sequence association since a bare scalar variable (unlike an array element reference) has no array nature; fixed by declaring them as 1-element arrays. VERIFICATION CAVEAT: calc_all_quants/save_all_quants (flre_zone's overrides of zone's pure virtual entry points into this whole subsystem) have zero callers anywhere in KiLCA, QL-Balance, or KIM - confirmed by an exhaustive grep. This was already true of the C++ oracle; nothing in this port made it reachable. The 36/36 fast suite stays green (proving the build links and nothing else broke), but it does not exercise this module's actual physics formulas, since the entry points are unreachable dead code in the live tree today. Follow-up: add a dedicated fake-based unit test (same technique as test_sysmat_profiles_fakes.f90) before this subsystem is ever wired back up to a live caller.
eval_diff_sys_matrix_ had zero live callers anywhere in KiLCA or QL-Balance - the only other mention is a commented-out call (under a different name, eval_diff_sys_matrix_f) in dispersion.f90. Matches this port's established dead-code precedent (cond_profs.cpp, interp.cpp's make_adaptive_grid/eval_lagrange_polynom) rather than being translated. 36/36 tests pass.
New module kilca_shared_m (core/shared_m.f90), replacing the live part
of core/shared.{h,cpp}. Liveness verified by grep across all .cpp/.h/
.f90 files (the established discipline for this port): xmalloc,
xrealloc, compare_doubles, Ckn, calc_interp_polynom (the oracle's own
comment already flagged this as "the code is not checked!!!"),
eval_interp_polynom (likewise "not implemented!" - a dead stub, not the
unrelated same-named functions in interp_m.f90/adaptive_grid_pol.h),
localizator and localizator_4_derivs had zero callers anywhere and were
dropped.
signum and sort_index_doubles keep their bind(C) name (no trailing
underscore) since each has exactly one remaining caller, both still
C++ and not yet ported (progs/main_eig_param.cpp, math/adapt_grid/
adaptive_grid.cpp - S8 scope): shared.h shrinks to just these two
extern "C" declarations so neither call site needs to change.
binomial_coefficients is the odd one out: its only callers are
PRE-EXISTING legacy Fortran (kmatrices*.f90, conduct_arrays*.f90) via
implicit interface (`call binomial_coefficients (%val(N), bico)`,
gfortran-mangled to external symbol "binomial_coefficients_") -
translated as a free (non-module-contained) subprogram per this port's
established free-subprogram-vs-module-contained-procedure rule, taking
the destination array as a native 2D Fortran array (bico's own
declared shape (0:N,0:N) already matches the oracle's flat-array
layout exactly, confirmed by the oracle's own "fortran: C(n,k)"
comment - no flat-index arithmetic needed).
sort_index_doubles itself uses fortnum_multiroot's native argsort
(heapsort, 1-based) converted to 0-based for the C++ caller, then
reproduces the oracle's own tie-break fixup verbatim (heapsort is not
stable; the conductivity grid shares zone-boundary nodes with
duplicate x values, so a deterministic secondary ascending-index sort
within each tie run is load-bearing for spline well-definedness, per
the oracle's own comment).
Also: adaptive_grid.cpp gained an explicit `#include <cstdio>` - it
was relying on the old shared.h's transitive `#include <stdio.h>` for
fprintf/stderr, which the trimmed header no longer provides.
36/36 tests pass.
New modules adaptive_grid_m (math/adapt_grid/adaptive_grid_m.f90) and
adaptive_grid_pol_m (adaptive_grid_pol_m.f90), replacing the live parts
of math/adapt_grid/adaptive_grid.{h,cpp} and adaptive_grid_pol.{h,cpp}.
Both keep their callers' bind(C) symbol names exactly, so the still-C++
or already-Fortran call sites need no change. Liveness verified by grep
across all C++, header and Fortran sources, the established discipline
for this port.
Scalar variant (adaptive_grid_m): only calc_adaptive_1D_grid_4vector_
is live (sole caller sysmat_profiles_create in kilca_sysmat_profiles_m,
via a bind(C, name="calc_adaptive_1D_grid_4vector_") interface block).
Its helpers set_interval, add_new_interval_to_the_array,
update_max_err_interval and eval_error survive as private module
procedures. The companion scalar entry calc_adaptive_1D_grid_ and its
only helper check_and_remove_grid_condensations_ had zero callers
anywhere and were dropped, per this port's dead-code precedent.
Polynomial variant (adaptive_grid_pol_m): adaptive_grid_polynom_res and
adaptive_grid_polynom_err are live (both called by the conductivity
grid generator kilca_cond_profiles_m via bind(C) interface blocks). The
extern "C" adaptive_grid_polynom_err was a one-line forwarder to the
dim_err/ind_err overload of adaptive_grid_polynom, whose only other
caller was that forwarder; its algorithm body is folded directly into
adaptive_grid_polynom_err here. find_index_for_interp, search_array,
binary_search and this file's own yshift-strided eval_neville_polynom
(distinct from kilca_neville_m's Dmin/Dmax variant) are private helpers.
Dropped as dead: the dimy/no-ind_err overload of adaptive_grid_polynom
(reached only from the orphaned demo main_agp.cpp, in no CMake target);
sparse_grid_polynom and its helpers eval_interp_polynom/func_interp
(already ported as kilca_interp_m, no caller of this file's copies);
sign (never called). The DEBUG_FLAG (compile-time 0) and local debug
(always 0) warning prints never executed, so their dead branches and
the xmerr/ymerr/jmerr trace variables that only fed them are omitted.
Behavior preserved verbatim: the resonance-zone threshold keeps the
oracle's two sequential divisions (/D/D, not /(D*D)) for bit-identical
rounding; the C++ pointer-swap between (xold,yold) and (xnew,ynew) plus
its "if (x1 != xold)" skip-copy is a memory-reuse detail with no
numerical effect, done here with move_alloc and an unconditional final
copy into the caller's x1/y1 (matching the kilca_interp_m precedent).
36/36 tests pass.
….cpp The .h headers stay - post_processing/main_post_proc.cpp (a live CMake target) still includes them for the bind(C) declarations now provided by adaptive_grid_m.f90/adaptive_grid_pol_m.f90, confirmed by a clean post_proc relink against the new Fortran symbols. 36/36 tests pass.
New module kilca_eigmode_solve_m (eigmode/eigmode_solve_m.f90),
replacing eigmode/calc_eigmode.{cpp,h} and eigmode/find_eigmodes.cpp.
The three runtime-selected search strategies (dispatched by
get_eigmode_search_flag_ from core_data_m) keep their exact bind(C)
entry-point names find_det_zeros, loop_over_frequences and
find_eigmodes, so core_data_m's dispatch interface needs no change
(only its two now-stale "still C++" doc comments are refreshed).
find_det_zeros drives fortnum_multiroot_hybrid (the fortnum C ABI, the
same hybrid Newton solve the oracle already used) with the eval_det
residual on the fortnum_vector_fn ABI, plus the optional winding-number
sanity check (calc_circle_integral/inv_det). loop_over_frequences is the
plain 2D determinant-table scan.
find_eigmodes drives the vendored ZerSol C++ library through its
already-shipped extern "C" wrapper (math/zersol-0.0.0/src/zersolc.h,
specialized to _Real = double), wrapped here via bind(C) interfaces.
zersolc.cpp was not previously compiled by any target; it is added to
kilca_lib_cpp_sources, while the templated zerosolver.hpp and the rest
of zersol-0.0.0 stay untouched C++ (matching this port's SUNDIALS-as-C-
library treatment). Every oracle S.set_*/F.set_* call maps one-for-one
to a C-API set_* call, in the same order, with the same values:
nd_method(1), nd_step(delta) when delta>0, min_rec_lev(8), n_target,
start_array, n_split_x(4), n_split_y(4), max_iter_num(24),
eps_for_arg(eps_abs,eps_rel), eps_for_func(0,eps_res), use_winding,
debug_level(1), print_level(1). The commented-out oracle settings
(max_rec_lev, interp_err, jump_err, max_part_level, multiplicity) are
correspondingly not set. solver.print_status() -> print_status(solver,
""); the search.dump operator<< -> print_dump(solver,"search.dump");
the on-failure clog<<solver -> print_dump(solver,"") (stdout, the
nearest C-API equivalent). free_solver is added explicitly to release
the heap handle the C++ stack object freed implicitly via RAII.
DEBUG_FLAG is 0, so the guarded cout<<S is dead and dropped.
The oracle's eval_det and templated determinant<T> have near-identical
bodies; both become thin bind(C) shims (eval_det_residual on the
fortnum_vector_fn ABI, determinant_cb on ZerSol's complex_function ABI)
over one shared compute_det/eval_determinant_core, with a det_params_t
bind(C) type mirroring the C det_params struct field-for-field. The
mode_data_get_wd_ intptr_t handle is bridged to the wave_data accessors'
c_ptr via transfer.
test_func dropped: zero callers across all .cpp/.h/.f90 in KiLCA,
QL-Balance and KIM (grep-confirmed). calc_det translated and kept as a
public entry point matching the oracle header, though it too has no
caller (the oracle exported it uncalled). calc_circle_integral's
function-pointer argument had inv_det as its only possible value, so
inv_det is called directly.
Build green (cmake --build, 1107 targets), ctest 36/36.
Complete the translation of io/inout.cpp into kilca_inout_m (io/inout_m.f90)
and delete inout.cpp; io/inout.h shrinks to the live set, all now exported
with C linkage.
FILE*-based readers (read_line_2get_double/_complex/_int/_string,
read_line_2skip_it) keep the C "FILE *" handle opaque as type(c_ptr) and read
it through libc getline via a bind(C) interface, so their still-C++ callers
(progs/main_eig_param, post_processing/main_post_proc) pass their fopen'd
handle through unchanged. Each reader takes the text before '#' and parses it
with a Fortran list-directed read: the (re,im) complex frequency parses
natively, and read_line_2get_string extracts the first whitespace/tab token
(reproducing trim) and writes it null-terminated into the caller's char*
buffer via c_f_pointer. The filename-based count_lines_in_file_with_comments
and load_data_file_with_comments use native Fortran I/O with comment-line
filtering ('%', '#', '!'); the latter preserves the oracle's quirk of looping
over the raw line count while checking the comment-aware count.
Dropped as dead: save_real_matrix_to_one_file, save_complex_array, trim,
load_profile, load_and_alloc_profile, load_complex_profile. A fresh grep over
all .cpp/.h/.f90 in the built targets found zero callers; the only load_profile
hits are a separate, uncompiled subroutine in interface/wave_code_data_64bit.f90
(not in kilca_lib), and trim is used only internally by read_line_2get_string,
now inlined.
inout.h keeps the FILE* readers plus the previously translated savers/loaders
as extern "C" so the C++ entry points link against the bind(C) symbols.
36/36 tests pass.
Translate the three remaining C++ executable drivers to Fortran program units, completing the KiLCA main-library translation (only the vendored math/zersol-0.0.0/src/zersolc.cpp stays C++, wrapped via bind(C)). - progs/main_linear.f90: the KiLCA_Normal driver. Builds the project path (getcwd via a bind(C) wrapper, or argv[1]) and drives the now-Fortran core_data pipeline, using kilca_core_data_m natively and bind(C) interface blocks only for the non-module symbols set_core_data_in_core_module_ and get_antenna_flag_eigmode_. - progs/main_eig_param.f90: the KiLCA_EigParam eigenmode parameter scan. Reads search.in through the library read_line_2get_* routines (opened with a bind(C) fopen), issues its mkdir/cp/mv/rm shell steps through execute_command_line (same /bin/sh -c semantics as the oracle system() calls, exact command strings preserved), and keeps the clean_run opendir/readdir directory walk via bind(C), replacing fnmatch with the equivalent literal substring / exact-name tests its five known patterns reduce to. roots.dat parsing and the background.in/eigmode.in line patching are done with native Fortran I/O. - post_processing/main_post_proc.f90: the post_proc form-factor evaluator. Reuses the library file I/O (read_line_2get_*, count_lines_in_file, load_data_file*) and interpolation (find_index_for_interp, eval_neville_polynom, now made public in adaptive_grid_pol_m), and hands the packed complex form-factors to the existing F77 save_form_facs_fortran writer. The C static-local index seeds in the evaluate_* helpers are reproduced with save variables initialised on first call. - progs/progs_common_m.f90: shared helpers compiled into kilca_lib. get_project_path reproduces the drivers' path bootstrap; fmt_g and fmt_e reproduce C printf %g / %.15g / %.20e byte for byte (validated against glibc printf) so the directory names, glob patterns and data-file lines the drivers write stay identical. Delete torque_facs.cpp (re-verified against the current CMakeLists: not referenced by any add_executable/add_library target, header does not exist) and the old .cpp/.h entry points. Six pre-existing orphan .cpp files under KiLCA (fill_array, main_agp, test_four, four_transf, transf_quants, calc_I_array_drift) remain: none are in any build target, but they are unrelated to the entry-point translation and still reference the shared extern "C" headers, so the "zero C++" precondition for deleting those headers does not hold; they are left untouched. Verification: cmake --build build (green), ctest 36/36 passed before and after; kilca_normal_exe, kilca_eigparam_exe and post_proc all link, and each runs its entry logic (path setup, kilca_version write, input-file error paths, core_data project-type check) in a smoke test.
Every KiLCA .cpp file is gone (only math/zersol-0.0.0/ stays C++, the
intentionally-vendored ISO_C_BINDING-wrapped root-finder library). This
commit removes what's left:
Six orphaned .cpp/.f90 files with zero build-target references anywhere
(confirmed by grep across every CMakeLists.txt and the whole tree):
interface/fill_array.cpp, math/adapt_grid/main_agp.cpp,
math/fourier/test_four.{cpp,f90}, math/fourier/four_transf.cpp,
flre/quants/transf_quants.{cpp,h}, flre/conductivity/tests/
calc_I_array_drift.cpp.
29 "extern C" bridge headers that existed solely to let C++ translation
units see the bind(C) declarations of the now-Fortran modules they used
to call into - with zero .cpp files left to read them, they are dead
weight (core.h, mode.h, background.h, eval_back.h, settings.h, shared.h,
output_sett.h, slatec.h, antenna.h, eigmode_sett.h, back_sett.h,
stitching.h, zone_dispatch.h, wave_data_dispatch.h,
transforms_dispatch.h, flre_zone_dispatch.h, inout.h, interp.h,
spline.h, solver.h, rhs_func.h, lapack.h, adaptive_grid.h,
adaptive_grid_pol.h, sysmat_profs.h, maxwell_eqs_data.h, disp_profs.h,
cond_profs.h, conduct_parameters.h, flre_quants.h). Verified
math/zersol-0.0.0/src/zersolc.cpp - the sole remaining C++ TU - only
includes its own vendored headers before deleting any of these.
Also dropped constants.h/typedefs.h/code_settings.h (the checked-in,
stale copy of the CMake-templated code_settings.h.in, which stays):
zero references anywhere once the above are gone.
36/36 tests pass.
…nd run Now that all entry points are Fortran, ran kilca_normal_exe against test/golden/kilca/cases/flre_m6n2/ for the first time in this port's history (no Fortran driver existed before this session to run the whole pipeline end to end). Verified every fix below against the pristine pre-port C++ oracle (built from commit 3c364ab, this branch's merge-base) before changing anything, per the "translate exactly, no behavior changes" mandate - none of these are "improvements", they all make the Fortran translation match behavior the oracle already has, which earlier translations (done before any full end-to-end run was possible) missed. 1. core/shared_m.f90: new strtol_int, reproducing C's strtol(s, NULL, 10) exactly (skip whitespace, optional sign, consume a digit run, stop at the first non-digit, 0 if none found). Confirmed via the pristine oracle (forced its print_settings to run unconditionally) that flre_zone's max_dim field, written in zone_1_flre.in as "1.00e+05" but declared an int and read via read_line_2get_int, silently becomes 1 in the real C++ build (strtol stops at the decimal point) - not 100000. A plain Fortran list-directed integer read rejects "1.00e+05" outright instead. 2. eigmode/eigmode_sett_m.f90, mode/zone_m.f90: read_int_before_hash (both files) now uses strtol_int instead of a strict list-directed read. mode/zone_m.f90's read_real_before_hash/read_token_before_hash/ read_complex_before_hash/skip_line, and eigmode_sett_m.f90's own value parsing, gained iostat guards defaulting to 0/0.0/(0,0)/'' on a failed line read or unparseable value, mirroring read_line_2get_double_/_int_/_string_/2skip_it_'s oracle behavior of warning to stderr and continuing rather than aborting. Confirmed via the pristine oracle that: - eigmode.in files in this repo (both the golden fixture and python/KiLCA_interface/blueprints/eigmode.in) are missing the "ZerSol parameters" section eigmode_sett::read_settings expects, shifting every later field by two lines - the oracle silently ends up with n_zeros=4, use_winding=0, Nguess=0 (so the fstart-reading loop never executes) instead of crashing. - zone_2_vacuum.in has no trailing blank line after its last field, so hmedium_zone::read_settings's final skip-line call reads past EOF - the oracle's getline-based reader just warns and returns. A strict Fortran read (error stop / uncaught runtime error) on either input aborts the whole program; the oracle does not. 3. flre/flre_zone_m.f90: fixed a genuine Fortran-only bug (not an oracle-fidelity issue) in 3 places - `ht => htz(0:ho)` is a SIMPLE pointer assignment to an array section, which per the Fortran standard resets the lower bound to 1 regardless of the section's own indices (`ht(0)` was therefore out of bounds). Fixed with explicit bounds-remapping syntax, `ht(0:ho) => htz(0:ho)`, which preserves the 0-based indexing the surrounding code (calc_deriv_ product and friends) assumes throughout. 4. solver/solver_m.f90: fixed a second Fortran-only bug in renorm_basis_vecs. The previous translation passed a SECTION (Smat((dim-1)*Neq:)) as the actual argument for the assumed-size udata(*) dummy, then walked udata_i NEGATIVE relative to that section's own start to reach earlier blocks - mirroring the oracle's raw `udata -= Neq` pointer arithmetic, which is legal C (the pointer stays within the same underlying Smat allocation) but forms an index below udata's declared lower bound of 1, which this project's -fcheck=all build flag (present in every configuration, not just Debug) flags as an error regardless of whether the actual memory access is in-bounds. Fixed by passing the FULL Smat array plus a new udata_i0 parameter (the starting absolute 0-based offset, (dim-1)*Neq) and walking udata_i down from udata_i0 to 0 (never negative) instead - reaches the exact same memory locations without ever forming an out-of-range index. Together these let kilca_normal_exe progress from immediate settings- parse failure through zone reading and the entire basis-vector solver (previously the port's own "hardest" unit, now exercised end-to-end for the first time) into flre_quants' calc_all_quants path - which memory notes had already flagged as "zero callers anywhere, so 36/36 green proves compile/link correctness only, not numerical correctness" for this exact reason. A further bug there (qp%jae_arr array-bounds overrun, flre_quants_m.f90:1259) is scoped but not yet fixed - left for a follow-up session. 36/36 tests pass.
… formats
Continuing the golden-record debugging from the previous commit -
kilca_normal_exe now completes the flre_m6n2 golden case end to end
with exit code 0.
flre/quants/flre_quants_m.f90: fixed a systemic off-by-one across 9
allocatable arrays (current_dens, abs_pow_dens/abs_pow_int, diss_pow_
dens/diss_pow_int, kin_flux, poy_flux, tot_flux, number_dens, lor_
torque_dens/lor_torque_int, kmat, yarr). Every access site in this
file uses a uniform idx_2/idx_3/idx_cd/idx_nd/idx_kmat(...) + 1 shift
(the idx_* helpers return a 0-based flat index in [0, N-1]; the +1
converts that into 1-based Fortran indexing) - but these arrays were
ALL allocated 0-based (0:N-1) instead of 1-based (1:N), putting the
valid access range one element past each array's declared upper
bound. Confirmed the +1 convention is correct and intentional by
checking cmat/bico, which are accessed via idx_cmat/idx_bico WITH NO
+1 and correctly remain 0-based - the mismatch was specific to the 9
arrays above. Verified each fix against the oracle's own field usage
(e.g. idx_cd's analytically-computed max value across every call
site's actual spec/type_/i/part/node range exactly equals each array's
intended element count).
mode/mode_data_m.f90: three path/filename/data-file-content format
strings used Fortran's native g0.15/g0.16/es24.16e3 edit descriptors,
which do not reproduce C's %.15lg/%.16lg/%.16le formatting (trailing-
zero stripping, 2-digit-minimum exponent, no leading sign space,
lowercase 'e'). Switched all three to the fmt_g/fmt_e helpers already
built this session (kilca_progs_common_m, for the same reason in the
entry-point translation) via a new `use kilca_progs_common_m` import:
- eval_path_to_linear_data/_dispersion_data/_poincare_data (%.15lg)
- save_mode_det_data's mode_M_N_[olab].dat filename and content
(%.16le, plus its exact " "/tab/" " field separator, not a uniform
tab)
- set_and_make_mode_data_directories' mode_data.dat content (%.16lg)
Verified against the pristine oracle build: the linear-data mode
subdirectory name now matches byte for byte
("m_6_n_2_flab_[1,0]", not "[1.00000000000000,0.00000000000000]"),
the mode_M_N_[...].dat filename matches byte for byte, and mode_data.
dat's content is now BYTE-IDENTICAL to the oracle's. The determinant
values inside mode_6_2_[...].dat differ at the 5th significant digit
(~1e-41 magnitude, a near-zero determinant near a resonance where
catastrophic cancellation dominates) - well within test/golden/bin/
gr_numcompare.py's default tolerance (rtol=1e-7, atol=1e-12; atol
dominates for values this close to zero, giving an effective relative
error around 1e-33).
This float-formatting mismatch (Fortran g0.d/esW.dEe vs C's %g/%e) is
likely present in MANY other already-translated modules that write
.dat files or construct filenames from real values - this session
only fixed the specific call sites blocking file/directory matching
for THIS golden case. A broader audit (grep for g0./esNN.NNeN edit
descriptors across every translated module, cross-check each against
its oracle fprintf format string) remains a valuable follow-up.
36/36 tests pass.
The C++ oracle carried two speed-of-light constants: constants.h's `const double c = 29979245800.0` (full double precision, used by C++ code) and the Fortran `constants` module's `c = 29979245800.0` (a bare literal that gfortran parses at single precision, storing 29979246592.0, used by the legacy Fortran physics). The golden record encodes both: C++-computed quantities use the exact value, legacy-Fortran-computed quantities (conduct_parameters -> Vth_m, j0th_m) use the truncated one. A prior blanket `_dp` fix on the module constant made the legacy path diverge from the golden record. Restore the split instead: - constants module keeps the legacy single-precision c (now written explicitly as real(29979245800.0_sp, dp), bit-identical to the bare literal, so it is no longer a silent trap), matching every unported Fortran consumer with zero source change. - The three ported-from-C++ modules (background_data_m, flre_zone_m, transforms_m) carry a local double-precision c mirroring constants.h. Golden record: dPhi0/j0th (C++ path) and Vth_m/j0th_m (Fortran path) now all match; 56/58 flre_m6n2 files pass. ctest 36/36.
…-compiler-irreducible)
…erge, full oracle unit needs removed GSL
…1 regime The flre_m6n2 FLRE zone-0 solution crosses its resonant layer, where the conductivity uses a confluent-hypergeometric 1F1m with b ~ z. The subtraction 1F1 - 1 - z/b cancels to ~1e-11 of its operands, so the result's low bits are compiler-codegen-dependent cancellation garbage: the gfortran port and the pre-port C++ oracle land ~2e-12 apart, which amplifies through the stiff resonant FLRE integration to O(1) in the fields near the resonant surface and changes the adaptive output grid (EB.dat, zone_0_poy_test_err.dat). Control experiment on the unmodified C++ oracle: multiplying its own 1F1m by (1 + 1e-12) and comparing to the unperturbed oracle reproduces the identical failure (EB.dat max_rel 1.6e-6, zone_0_poy_test_err O(1)), proving this is a pre-existing property of this ill-conditioned test case, not a port regression. Same situation and remedy as #164 (QL-Balance f_6_2). Add a documented GR_EXCLUDE mechanism to gr_numcompare.py and exclude exactly these two proven-affected FLRE zone-0 outputs in the KiLCA case config. The bar (rtol 1e-7, atol 1e-12) is unchanged; the other 56 files stay strict and the gate still fails on any real regression (exit 1 without the exclusion). Full analysis in docs/flre-golden-record-investigation.md. ctest 36/36.
…loosening impossible, exclusion is the only bar-preserving option
…ort_index_doubles) sysmat_profs_m sorted the adaptive sysmat grid with fortnum's argsort (an unstable heapsort) directly, while the C++ oracle sorts the same grid with sort_index_doubles, which breaks exact-key ties by ascending original index. The sysmat grid shares zone-boundary nodes (duplicated x), so the unstable sort left their order heap-dependent and not guaranteed to match the oracle. Reproduce the oracle's tie-break so the sysmat permutation (and its spline) is deterministic and oracle-matching. Verified against the golden case (no output change here since the RHS recomputes the matrix rather than using the sysmat spline, but the determinism now matches the oracle at this site). ctest 36/36.
… on EB/poy across a 1e-8 root-tolerance change (matchable by nothing)
The strided E1c(k,:)/B1c(k,:) actuals packed a temporary on every call under -fcheck=all, emitting 15188 runtime warnings (3.1 MB of stderr) per flre solve. Assumed-shape 0-based dummies take the sections directly; outputs are byte-identical.
Popen(stdout=PIPE, stderr=PIPE) followed by wait() deadlocks once the child fills a 64 KiB pipe buffer; the ported KiLCA's stderr warnings did exactly that, hanging the QL-Balance golden prepare until its 600 s timeout.
ql-balance re-creates the background every wave-code invocation; the second create died on allocate of already-allocated module arrays (fresh-object semantics of the oracle's 'new background'). ql-balance.x now runs the golden case to completion (39 s, exit 0), bit-identical to the C++ baseline on the compared quantities.
…ettings constants.h says m_e = 9.10938185917485e-28; the Fortran constants module computes me = mp/1836.1526675, which lands 2 ULP away. The golden record encodes the C++ value on every ported electron-parameter path (omc_, vT_), and the 2-ULP seed amplifies through the stiff resonant FLRE solve. Same split-constant treatment as the speed-of-light fix.
gfortran (no math-errno) auto-vectorizes the Chebyshev-node loop to libmvec _ZGVbN2v_cos at -O3; the C++ original evaluated scalar cos (C/C++ errno semantics block libmvec). Vector and scalar cos differ in last bits for some arguments, shifting adaptive-grid nodes 2 ULP off the golden record. Verified: nodes bit-identical after the change, and the conductivity sampling pipeline (cti/cte, K matrices, W2 inputs across 4187 probed evaluations) is bit-identical to the C++ build.
Mirrors the C++-side swap of KAMEL#146 so oracle and port share one compiled kernel: fortnum returns F11m = 1F1(1;b+2;z) - 1 directly with no cancellation, replacing the |z/b|-dispatch reconstruction whose subtraction cancels to ~1e-11 of its operands and carries codegen-dependent low bits that no reimplementation can reproduce. ctest 36/36.
…tries renorm_basis_vecs must receive the final orthonormalization's r, R factor, and taus (the oracle passes rdata-1, ydata-Neq, taudata-2*Nfs); the port passed the post-advance indices, so with any renormalization active the first segment crossing inverted the Q slot instead of R and every segment lookup shifted by one. This corrupted the superposed fields locally around renorm radii: the flre_m6n2 Poynting self-consistency residual spiked to 1.01 near r=5.03 (healthy C++ level 2.9e-2) and the adaptive output grid kept 34 extra rows (2682 vs 2648). With this fix the full-Fortran port matches the fortnum-wired C++ build (main + #145 Bessel + #146 1F1m) on the flre_m6n2 golden at 58/58 files (EB.dat max_rel 0.0, poy residual byte-equal 2.867e-2) and on the QL-Balance f_6_2 golden at 33/33 quantities (rtol 1e-8, incl. Br and D^ql LinearProfiles). ctest 36/36.
The investigation log concluded the divergence was cross-compiler-irreducible 1F1 cancellation and excluded EB.dat/poy from the golden. Wrong: with the 1F1 unified through fortnum the gap persisted until three more fixes landed (electron-mass literal, scalar-cos pinning, renorm-reconstruction off-by-one). Status section now lists all four with verification numbers; the exclusion section is retracted and the GR_EXCLUDE references removed.
c0fdc99 to
2b45ded
Compare
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.
Goal
Single integration PR porting the remaining first-party non-Fortran KiLCA /
QL-Balance code to Fortran, using fortnum for numerics. The KiLCA tree is now
100% Fortran: class hierarchy, zone stack, solver, adaptive grids, eigmode
root search, executables. Left vendored:
umf4_f77*wrapper.c, libcerf,SuperLU/SuiteSparse. AMOS/SLATEC Bessel is replaced by the fortnum shim
(
common/math/fortnum_amos_compat.f90, shared content with #145); the 1F1mhot path routes through fortnum
hyperg_1f1m_a1(pairs with the C++-sideswap in #146).
Golden-record parity
Compared against an oracle built as current
main+ #145 + #146 (all-C++,fortnum-wired kernels), local A/B with
test/golden:flre_m6n2: 58/58 files pass (EB.dat max_rel 0.0, poyself-consistency residual equal at 2.867e-2).
f_6_2: 33/33 quantities at rtol 1e-8 / atol 1e-15,including the
LinearProfilesBr and D^ql series.electromagnetic: 109/109 vs the current baseline.ctest36/36.Four port defects found by bit-level bisection and fixed here
(
docs/flre-golden-record-investigation.mdhas the full log):Popen(PIPE,PIPE)+wait()in KAMELpy whilethe ported FLRE loop emitted 15k array-temporary warnings into the
never-drained pipe; plus a background-singleton re-init crash.
m_eliteral(same treatment as the split speed-of-light constant).
_ZGVbN2v_cosin the ported adaptive-grid unit shiftedChebyshev nodes 2 ULP off the scalar-cos C++ baseline; pinned to scalar.
solver_m:renorm_basis_vecsmuststart at the last orthonormalization's stored entries (
rdata-1,ydata-Neq,taudata-2*Nfs); the port passed post-advance indices,corrupting fields locally at renorm radii (Poynting residual 1.01 at
r=5.03, 34 extra output-grid rows).
No golden exclusions: the branch uses main's harness unmodified (the earlier
GR_EXCLUDEscoping is reverted;compare.pyis main's strict list).Gate status and merge order
The remaining CI golden delta vs the current
golden-baselinetag is thedeliberate #145/#146 kernel-value change (KiLCA EB.dat max_rel 9.7e-6;
QL-Balance
LinearProfiles~5e-7 rel). Plan: merge #145, then #146, re-blessgolden-baselineto that main commit (both kernels change values withinsolution accuracy; the amplification analysis is in the docs and #164/#172),
then this PR's golden gate is strict-green with no exclusions.
Verification
cmake -B build -G Ninja -DCMAKE_BUILD_TYPE=Release && cmake --build build --parallelthen
ctest --test-dir build-> 36/36 pass. Golden A/B numbers above fromtest/golden/run_golden.shmachinery run locally against baseline, currentmain, and main+#145+#146 oracle builds.