Skip to content

QL: source-only in_file_pert='none' feeds a zero source to the Arnoldi eigensolver (NaN, endless zhseqr failures) #122

Description

@krystophny

Subsystem: NEO-2-QL non-axisymmetric (isw_qflux_NA=1) solve
Branch to fix: feat/ql-qflux-mode-input (PR #121, the PR that adds none mode); also present in the stacked feat/ql-misalignment-drive (PR #119).
Related: #118, #121, #119

Summary

A source-only non-axisymmetric run (in_file_pert='none', isw_m_phi_input=1) with no source actually configured (no perturbation field, helical drive off) assembles an identically zero source vector. That vector is the Arnoldi seed, so its normalization divides by zero (0/0 = NaN). The NaN propagates into the Hessenberg matrix and zhseqr fails to converge at every Arnoldi step, printing try_eigvecvals: zhseqr failed to compute all eigenvalues in a loop for ~8 minutes, never producing qflux output and never aborting cleanly.

Reproduction

Minimal source-only NA &ntv_input (single electron species, HELCORE-type Boozer geometry), drive off:

&ntv_input
  isw_ntv_mode    = 0
  isw_qflux_NA    = 1
  in_file_pert    = 'none'
  isw_m_phi_input = 1
  m_phi_input     = -1
  isw_hel_drive   = 0
  m_theta_hel     = 1
  hel_phim_re     = 0.0d0
  hel_brad_re     = 0.0d0
/

Run neo_2_ql.x on a single surface. Actual: endless try_eigvecvals: zhseqr failed to compute all eigenvalues; no qflux_ntv.dat / neo2_multispecies_out.h5.

Expected: either a single clear diagnostic (source-only NA run has no source) or the exact zero NA response, in O(1) time.

Root cause (file:line, branch feat/ql-qflux-mode-input)

  • NEO-2-QL/neo_magfie_perturbation.f90:100-108 (added by QL: allow prescribed perturbation mode without field file #121): in_file_pert='none' short-circuits with mnmax_pert = 0; RETURN, so the perturbation field contribution bnoverb0 == 0. The only guard added is that none requires isw_m_phi_input=1; nothing requires a non-zero source.
  • With the drive off, no other NA source is assembled, so source_vector == 0, and NEO-2-QL/ripple_solver_ArnoldiOrder2_test.f90:3587 sets f_init_arnoldi = source_vector_all(:,1,ispec) = 0.
  • NEO-2-QL/arnoldi_mod.f90:307-309: q_spec = SUM(CONJG(fnew)*fnew) = 0, then qvecs_prev(:,1) = fnew / SQRT(SUM(q_spec)) = 0/0 = NaN. No zero-norm guard.
  • NaN contaminates hmat; zhseqr returns info>0 (arnoldi_mod.f90:451,455) at every m up to mmax = n_arnoldi = 500, for each of the 3 source columns. arnoldi never tests ierr inside its DO m=2,mmax loop (arnoldi_mod.f90:339), so it re-fails instead of aborting. That is the spin.

Not reachable on main

On main, neo_read_pert opens in_file_pert with status='old' unconditionally (NEO-2-QL/neo_magfie_perturbation.f90:97), so 'none' is a file-not-found error; has_perturbation_file, isw_m_phi_input, m_phi_input, and the mnmax_pert=0 short-circuit do not exist there. The zero-source NA path is introduced by none mode, and PR #121 alone reproduces the failure using its own feature. This is therefore a #121 gap, not a pre-existing arnoldi_mod bug.

Proposed fix (scoped to #121, arnoldi_mod.f90 untouched)

Guard the degenerate zero-source NA run in the code #121 owns. Where the source-only NA source is assembled/known (the none-mode path in neo_magfie_perturbation.f90 plus the source-only entry in NEO-2-QL/neo2.f90), detect an identically zero assembled source and either:

  • preferred: STOP with one clear message, e.g. "source-only NA run (in_file_pert='none') has no source: set a drive (isw_hel_drive/=0 with non-zero hel_*) or provide a perturbation file"; or
  • short-circuit to the exact zero NA response without invoking arnoldi.

Test the assembled source, not the mode flag: on the stacked feat/ql-misalignment-drive (PR #119) a valid none + drive-on run has a non-zero source and must pass unchanged.

Files to edit

  • NEO-2-QL/neo_magfie_perturbation.f90 and/or NEO-2-QL/neo2.f90: the guard.
  • TEST/: new behavioral test (below).

Test (behavioral, red-then-green)

Add a TEST/ case in the style of TEST/test_ntv_perturbation_input.f90 (#121): a source-only none config with no drive yields the guard outcome (clean error or exact zero) with no NaN and no repeated zhseqr failure; a none + drive-on config still assembles a non-zero source. The test fails before the guard and passes after. Assert the user-visible outcome (no NaN / clean result), not the guard helper itself.

Success criteria

  • The new NTV test is green in the repo test suite (make test), the rest unchanged.
  • The reproduction namelist returns in O(1) with a clear result or diagnostic, not a spin.
  • Golden records unchanged.

Non-goals

  • Do not edit arnoldi_mod.f90 / ripple_solver_* core numerics here.
  • Do not attempt the near-resonant driven case (lambda = m*iota + n = -0.01); convergence of a driven (non-zero-source) near-resonant run is a separate question.
  • Do not touch or regenerate golden records.

Optional follow-up (separate, low priority, solver owner)

Defensive hardening in arnoldi_mod.f90: guard the zero-norm seed at :309 (zero source => exact f=0, skip Arnoldi), the Gram-Schmidt breakdown at :335, and abort the m-loop on ierr/=0 at :339 instead of re-failing 500x. Belt-and-suspenders; not required to clear this issue.

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