Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/Equilibrium/KineticProfiles.jl
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,14 @@ function resolve_ntv_species(data::KineticProfileData, ion_species::AbstractVect
return identity.(species) # narrow to a concrete-eltype Vector{ResolvedNTVSpecies{...}}
end

"""
has_ntv_fields(data::KineticProfileData) -> Bool

Whether `data` carries the profiles the NTV view needs (`n_e`, `T_i`, `T_e` and `omega_E`), so that
`kinetic_profile_splines` and `resolve_ntv_species` can be built from it.
"""
has_ntv_fields(data::KineticProfileData) = all(!isnothing, (data.n_e, data.T_i, data.T_e, data.omega_E))

"""
kinetic_profile_splines(data::KineticProfileData;
zi::Int=1, zimp::Int=6, mi::Int=2, mimp::Int=12,
Expand Down
2 changes: 2 additions & 0 deletions src/ForceFreeStates/ForceBalance.jl
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ function _prepare_solve(prob::ForceBalanceProblem, alg::AbstractIntegrator; verb
# A kinetic model (not supplied or synthetic matrices) is evaluated on the equilibrium's kinetic profiles.
ctrl.kinetic === nothing || ctrl.kinetic isa Union{KineticMatrices,TestKineticMatrices} || equil.kinetic !== nothing ||
error("the KineticForces kinetic source needs kinetic profiles on the equilibrium: set kinetic_file or call attach_kinetic_profiles!")
ctrl.kinetic isa Union{Nothing,KineticMatrices,TestKineticMatrices} || Equilibrium.has_ntv_fields(equil.kinetic) ||
error("the KineticForces kinetic source needs n_e, T_i, T_e and omega_E in the kinetic profiles; the kinetic file lacks at least one")
ctrl.kinetic !== nothing && !(alg isa Forward) &&
error("$(nameof(typeof(alg)))() does not support kinetic runs; use Forward().")

Expand Down
3 changes: 3 additions & 0 deletions src/GeneralizedPerturbedEquilibrium.jl
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ function main_from_inputs(
equil = _timed_stage(runtimes, "equilibrium", "Equilibrium") do
Equilibrium.PlasmaEquilibrium(eq_config, additional_input)
end
# The KineticForces stage needs the NTV view; stop before any solve rather than after the perturbed equilibrium.
deck.kinetic_forces !== nothing && equil.kinetic !== nothing && !Equilibrium.has_ntv_fields(equil.kinetic) &&
error("[KineticForces] needs n_e, T_i, T_e and omega_E in the kinetic profiles; the kinetic file lacks at least one")
Comment on lines +170 to +171

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.

Fair, but leaving it for now. The message lists the four required fields, and if the run got further, kinetic_profile_splines would name the missing dataset itself. Changing the wording now would touch src/ and invalidate the harness stamp that is running for the v2.0 deadline. Happy to take it as a follow-up.


if deck.force_balance !== nothing
ffs = _timed_stage(runtimes, "force_free_states", "Force-Free States") do
Expand Down
13 changes: 10 additions & 3 deletions src/KineticForces/CalculatedKineticMatrices.jl
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,18 @@ function compute_calculated_kinetic_matrices(
end

# The force-balance solve's two-pass grid inputs: the kinetic profiles as the KineticForces model views them (its
# control when the source is one, else the default control) and the kinetic-resonance surfaces to pin.
# control when the source is one, else the default control) and the kinetic-resonance surfaces to pin. The model
# needs the NTV view, so a missing profile errors; otherwise profiles read for another consumer (e.g. SLAYER without
# omega_E) are left out and the grid follows the equilibrium alone.
function ForceFreeStates.kinetic_grid_inputs(source, ctrl, equil, intr; rationals)
kf_ctrl = source isa KineticForcesControl ? source : KineticForcesControl()
kinetic_profiles = ntv_profiles(kf_ctrl, equil)
pinned = resonance_grid_nodes(ctrl, kf_ctrl, kinetic_profiles, ntv_species(kf_ctrl, equil), equil, intr; rationals)
data = equil.kinetic
ntv = source isa KineticForcesControl || (data !== nothing && Equilibrium.has_ntv_fields(data))
data !== nothing && !ntv &&
@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.

kinetic_grid_inputs is called once per solve, from maybe_reform_equilibrium, so this logs once per run. No change needed.

kinetic_profiles = ntv ? ntv_profiles(kf_ctrl, equil) : nothing
species = ntv ? ntv_species(kf_ctrl, equil) : nothing
pinned = resonance_grid_nodes(ctrl, kf_ctrl, kinetic_profiles, species, equil, intr; rationals)
return kinetic_profiles, pinned
end

Expand Down
16 changes: 16 additions & 0 deletions test/runtests_solve_api.jl
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,22 @@ using HDF5
ideal = GPEC.maybe_reform_equilibrium(pass1, sl_intr, sl_ctrl)
pass1.kinetic = kinetic
@test refined.rzphi_xs != ideal.rzphi_xs
# Profiles without omega_E (read for SLAYER, say) cannot form the NTV view: the grid follows the
# 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(kinetic)
@test !GPEC.Equilibrium.has_ntv_fields(no_omega)
pass1.kinetic = no_omega
no_omega_grid = GPEC.maybe_reform_equilibrium(pass1, sl_intr, sl_ctrl)
@test no_omega_grid.kinetic === no_omega
@test no_omega_grid.rzphi_xs == ideal.rzphi_xs
calc_deck = merge(sl_deck, Dict("ForceFreeStates" => merge(sl_deck["ForceFreeStates"], Dict("kinetic" => "KineticForces"))))
calc_ctrl = GPEC.force_balance_control(calc_deck, sl_dir)
@test_throws "missing required 'omega_E'" GPEC.maybe_reform_equilibrium(pass1, sl_intr, calc_ctrl)
# The KineticForces source stops up front, before any solve.
@test_throws "omega_E" solve(pass1, FFS.Forward(); nn=1, kinetic=GPEC.KineticForcesControl())
Comment on lines +179 to +181

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.

Not an issue: since Julia 1.8, @test_throws accepts a string and matches it against the error message. These assertions pass (runtests_solve_api 171/171 at 4bba6e7a5), and a wrong message would make them fail.

pass1.kinetic = kinetic
end

@testset "galerkin solve returns a gal result with a unified delta_prime" begin
Expand Down
Loading