Skip to content

Equilibrium - BUGFIX - Build the two-pass ψ grid from kinetic profiles that have no omega_E - #617

Open
d-burg wants to merge 3 commits into
developfrom
bugfix/two-pass-grid-without-omega-e
Open

d-burg wants to merge 3 commits into
developfrom
bugfix/two-pass-grid-without-omega-e

Conversation

@d-burg

@d-burg d-burg commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Release note

  • Audience: users
  • Numerical impact: none expected on the shipped decks, whose kinetic files all carry omega_E. A deck whose kinetic profiles lack omega_E now runs: its two-pass grid follows the equilibrium alone, where before this it stopped in the equilibrium stage (harness @ fc9d9bb)
  • Migration: none

A run whose kinetic file has no omega_E no longer stops in the equilibrium stage. SLAYER growth rates can again be computed from such files, assuming Ω_E = 0 with a warning, as before kinetic profiles moved onto the equilibrium. Runs with [KineticForces] or kinetic_source = "calculated" still need omega_E, and now stop up front, before any solve, saying so.

Closes #614

Regression report

regress --refs 08a6975ef,fc9d9bb74 --force (all cases) on feynman (SLURM, 8 threads, Julia 1.11.9, manifest pinned): every case is unchanged, all 24 including the kinetic, SLAYER and lar_reversed_shear_n1 cases; gal_resistive_pe is N/A on both refs, as on develop. runtests_solve_api.jl passes 152/152 at fc9d9bb74.

An earlier run, regress --refs 4f33fcf60,c05b2babc --force, was also unchanged. On feynman at c05b2babc, runtests_solve_api.jl (148), runtests_grid_refinement.jl (84), runtests_kinetic_profiles.jl (7), runtests_kinetic.jl (5143) and runtests_slayer_runner.jl (164) pass.

On the Mac (Julia 1.11.6) at c05b2babc:

  • runtests_solve_api.jl passes 148/148, including the 4 new checks.
  • The Equilibrium - BUGFIX - Build the two-pass ψ grid from kinetic profiles that have no omega_E #614 reproducer, examples/DIIID-like_SLAYER_example with its kinetic file copied without omega_E, now runs to completion with the [SLAYER.CriticalResonantField] block disabled.
    • It logs "the kinetic profiles lack an NTV field …, so the grid follows the equilibrium alone".
    • SLAYER warns and takes Ω_E = 0.
    • With that block enabled, the run stops at the critical resonant field instead, with its own explicit error: it genuinely needs the E×B rotation.

Notes for reviewers

Cause. The two-pass grid (maybe_reform_equilibrium) and the EL kinetic stage (prepare_force_free_states!) built the NTV view of the attached profiles (ntv_profiles, ntv_species) whenever any profiles were attached. That view requires n_e, T_i, T_e and omega_E. A file read for SLAYER need not carry omega_E, and the HDF5 kinetic schema treats it as optional.

Change.

  • Equilibrium.has_ntv_fields(data) names the NTV view's required fields once.
  • _ntv_views(kf_ctrl, equil, ctrl) is the one place both call sites now get the view and species. It builds them when a calculated kinetic source needs them, where a missing field still errors from kinetic_profile_splines. Otherwise it builds them only when the profiles carry the NTV fields.
  • When the profiles lack those fields, the grid follows the equilibrium alone and logs that once. For a SLAYER-only deck that is the grid it had before kinetic profiles moved onto the equilibrium.
  • kinetic_forces_control stops a run with [KineticForces] or kinetic_source = "calculated" up front when the profiles lack an NTV field, before any solve.
  • The KineticForces stage, the Validity output and the error-field couplings still build the NTV view unconditionally. They are NTV consumers, so a missing omega_E should, and does, stop them.
  • When the grid has no NTV view and axis_validity_suppression is on, the EL stage logs that the near-axis suppression is off.

Unchanged. When the attached profiles carry every NTV field, which covers every shipped deck, both call sites behave exactly as before. That includes a fixed kinetic source with attached profiles, which keeps its axis-validity boundary.

Tests (extending "attached kinetic profiles refine the two-pass grid"):

  • has_ntv_fields separates the two profile sets.
  • omega_E-less profiles re-form without error, stay attached, and give exactly the grid of an equilibrium with no profiles attached.
  • A calculated kinetic source with the same profiles still fails, with "missing required 'omega_E'".
  • kinetic_forces_control stops on those profiles with [KineticForces] or a calculated source, and returns normally for a deck with neither.

🤖 Generated with Claude Code

…s that have no omega_E

The grid refinement and the EL kinetic stage built the NTV view of the attached profiles whenever any were attached, and that view requires omega_E, so a deck whose kinetic file has no omega_E (read for SLAYER) stopped in the equilibrium stage. The view is now built only when a calculated kinetic source needs it, where a missing field still errors, or when the profiles carry the NTV fields; otherwise the grid follows the equilibrium alone, as before the profiles moved onto the equilibrium.

Closes #614

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@d-burg d-burg added this to the GPEC v2.0.0 milestone Oct 30, 2026
@d-burg d-burg self-assigned this Oct 30, 2026
@github-actions github-actions Bot added the bugfix Something was wrong and now is not label Oct 30, 2026
@d-burg
d-burg marked this pull request as ready for review October 30, 2026 08:06

@jhalpern30 jhalpern30 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix looks right for the SLAYER-only case; the grid behavior matches the description. One accuracy concern (fail-late for [KineticForces] decks) and a few clarity nits inline.

Also: the maybe_reform_equilibrium docstring still says the grid measures "the NTV view of its attached kinetic profiles"; that is now conditional ("when they carry the NTV fields"). And the branch is behind develop.

Comment thread src/GeneralizedPerturbedEquilibrium.jl Outdated
# kinetic source needs them, so a missing profile errors; otherwise they are built only when the profiles
# carry the NTV fields, and profiles read for another consumer (e.g. SLAYER without omega_E) are left alone.
function _ntv_views(kf_ctrl::KineticForces.KineticForcesControl, equil::Equilibrium.PlasmaEquilibrium, ctrl::ForceFreeStatesControl)
needed = ctrl.kinetic_factor > 0 && ctrl.kinetic_source == "calculated"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fail-late regression for [KineticForces] decks. needed covers only the calculated source. A deck with a [KineticForces] section and profiles lacking omega_E used to error here in the equilibrium stage; now it runs the full FFS solve and writes HDF5 before erroring in write_validity! / the KineticForces stage.

Suggest adding the check to kinetic_forces_control, next to the existing "needs kinetic profiles" error: error when _needs_kinetic_profiles(inputs, ctrl) && !Equilibrium.has_ntv_fields(equil.kinetic). That keeps every NTV consumer failing up front. (needed here would then be redundant on the run_gpec path, but the solve-API path skips kinetic_forces_control, so keep it unless that path gets the same check.)

Minor: this is now the third copy of kinetic_factor > 0 && kinetic_source == "calculated" (with _needs_kinetic_profiles and resonance_grid_nodes).

kinetic_profiles = KineticForces.ntv_profiles(kf_ctrl, equil)
kinetic_profiles, species = _ntv_views(kf_ctrl, equil, ctrl)
(equil.kinetic === nothing || kinetic_profiles !== nothing) ||
@info "Two-pass psi grid: the kinetic profiles lack an NTV field (n_e, T_i, T_e, omega_E), so the grid follows the equilibrium alone"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: the (a || b) || @info guard is a double negative; equil.kinetic !== nothing && kinetic_profiles === nothing && @info ... reads directly.

# importing KineticForces (which would invert the load order).
kinetic_profiles = KineticForces.ntv_profiles(kf_ctrl, equil)
species = KineticForces.ntv_species(kf_ctrl, equil)
kinetic_profiles, species = _ntv_views(kf_ctrl, equil, ctrl)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With kinetic_source = "fixed" and profiles lacking omega_E, this now gives axis_psi_c = 0, so near-axis suppression is silently off, while the same deck with omega_E gets a nonzero boundary. Not a regression (it errored before), but worth an @info here or a line in the PR description so it isn't silent.

Comment thread test/runtests_solve_api.jl Outdated
# equilibrium alone, unless the run computes calculated kinetic matrices, which need it.
no_omega = GPEC.Equilibrium.KineticProfileData(; psi=kinetic.psi, n_i=kinetic.n_i, n_e=kinetic.n_e,
T_i=kinetic.T_i, T_e=kinetic.T_e, chi_e=kinetic.chi_e, chi_i=kinetic.chi_i, chi_phi=kinetic.chi_phi)
@test !GPEC.Equilibrium.has_ntv_fields(no_omega) && GPEC.Equilibrium.has_ntv_fields(kinetic)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: split into two @tests so a failure says which half broke.

@jhalpern30 jhalpern30 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, up to you what you want to include from the review

…ga_E and report the suppression they lose

A [KineticForces] deck with omega_E-less profiles now errors in kinetic_forces_control rather than after the solve, a fixed kinetic source logs that the near-axis suppression is off, and the calculated-source test lives in one helper. Review changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Something was wrong and now is not

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Equilibrium - BUGFIX - Build the two-pass ψ grid from kinetic profiles that have no omega_E

2 participants