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
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,5 +77,5 @@ repos:
- id: toml-no-deprecated-keys
name: 'TOML conventions: no deprecated config keys'
language: pygrep
entry: '^(mer_flag|force_wv_symmetry|ode_flag|cyl_flag|mat_flag|reform_eq_with_psilim|use_riccati|use_parallel|parallel_threads|populate_dense_xi|gal_flag|nstep|diagnose_ca|diagnose|ksing|power_bp|power_b|power_r|power_rc|use_galgrid|moment|eq_out|theta_out|xlmda_out|eqpsi_out|save_records|gpec_file|fnml_flag|ellip_flag|diag_flag|force_xialpha|fixed_boundary|output_eigenmodes|filter_modes|singular_point_method|compute_response|local_stability_flag)\s*='
entry: '^(mer_flag|force_wv_symmetry|ode_flag|cyl_flag|mat_flag|reform_eq_with_psilim|use_riccati|use_parallel|parallel_threads|populate_dense_xi|gal_flag|nstep|diagnose_ca|diagnose|ksing|power_bp|power_b|power_r|power_rc|use_galgrid|moment|eq_out|theta_out|xlmda_out|eqpsi_out|save_records|gpec_file|fnml_flag|ellip_flag|diag_flag|force_xialpha|fixed_boundary|output_eigenmodes|filter_modes|singular_point_method|compute_response|local_stability_flag|profile_file|profile_group)\s*='
files: ^(examples/.*\.toml|test/test_data/.*\.toml)$
6 changes: 3 additions & 3 deletions benchmarks/benchmark_diiid_ideal_ntv_torque.jl
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ function run_benchmark(fortran_dir::String=default_fortran_dir())
kf_ctrl = KF.KineticForcesControl(;
fgar_flag=true, tgar_flag=tgar_enabled, nn=1, bounce_harmonic_max=4,
ion_mass=2, ion_charge=1, nutype="harmonic", f0type="maxwellian",
collisionality_factor=1, psilims=[0.0, 1.0], kinetic_file=inputs.kin_file,
collisionality_factor=1, psilims=[0.0, 1.0],
verbose=false)

# Initialize KF internal state
Expand All @@ -309,8 +309,8 @@ function run_benchmark(fortran_dir::String=default_fortran_dir())
_p("\n--- Torque integration ---")
t2 = time()

kinetic_profiles = Eq.load_kinetic_profiles(
kf_ctrl.kinetic_file;
kinetic_profiles = Eq.kinetic_profile_splines(
Eq.read_kinetic_file(inputs.kin_file);
zi=kf_ctrl.ion_charge, zimp=kf_ctrl.impurity_charge, mi=kf_ctrl.ion_mass, mimp=kf_ctrl.impurity_mass)

kf_state = KF.KineticForcesState()
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/benchmark_diiid_kinetic_stability.jl
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ function build_benchmark_tomldir(eq_file::String, kin_file::String)

[Equilibrium]
eq_filename = "$eq_name"
kinetic_file = "$kin_file"
eq_type = "efit"
jac_type = "hamada"
grid_type = "pow1" # Matches Fortran DIIID_kinetic_example (equil/grid.f90 powspace)
Expand Down Expand Up @@ -161,7 +162,6 @@ renorm_threshold = 1e4
write_outputs_to_HDF5 = true

[KineticForces]
kinetic_file = "$kin_file"
nn = 1
nl = 4
zi = 1
Expand Down
21 changes: 17 additions & 4 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ The objects map one-to-one onto the pipeline stages:
`ForceFreeStatesResult` once, and `perturbed_equilibrium(resp, rmp)` drives them with one field —
together the `[PerturbedEquilibrium]` section.

## Kinetic profiles

The equilibrium carries the kinetic profiles. Name the file with the `kinetic_file` (and,
for HDF5, `kinetic_group`) keyword, the same keys as the `[Equilibrium]` deck section, and it
is read once into `equil.kinetic`; every kinetic stage derives its own view from it.
[`Equilibrium.attach_kinetic_profiles!`](@ref) attaches a file or in-memory `KineticProfileData` to an
equilibrium built without one, such as an analytic equilibrium.

```julia
eq = PlasmaEquilibrium("input.geqdsk"; jac_type="hamada", kinetic_file="kinetic.h5")
```

## Combining forcing sources

`RMPField`s form a vector space: `+`, `-` and scalar `*` build lazy linear combinations that
Expand Down Expand Up @@ -125,16 +137,17 @@ extraction is not implemented yet, so `GGJModel()` errors here.

```julia
ffs = solve(eq, Riccati(); nn=1, vac_flag=true) # Δ′ matrix for the dispersion
tear = solve(TearingProblem(ffs; profile_file="kin.h5"), SLAYERModel()) # coupling_mode=:coupled returns one global root instead
tear = solve(TearingProblem(ffs), SLAYERModel()) # coupling_mode=:coupled returns one global root instead
tear.gamma_Hz, tear.rational_q # root-found rate at each rational surface
```

Keyword arguments of `TearingProblem` are the `[SLAYER]` deck section's procedure knobs
(scan mode and Q-domain, coupling mode, critical-Δ convention, extraction filters);
kinetic profiles come from `profile_file`, relative to the run directory.
kinetic profiles come from the equilibrium the solve was posed on (see Kinetic profiles above).

Kinetic runs (`kinetic_factor > 0`) take `Forward()` and the `"fixed"` kinetic source;
the `"calculated"` source needs the `[KineticForces]` profiles and remains TOML-driven.
Kinetic runs (`kinetic_factor > 0`) take `Forward()`. The `"calculated"` source reads the
equilibrium's kinetic profiles, with the default `KineticForcesControl` species and collision
settings.

## Local stability

Expand Down
8 changes: 5 additions & 3 deletions docs/src/kinetic_forces.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,10 @@ paths: the post-PE ψ-quadrature diagnostic and the self-consistent
`kinetic_source = "calculated"` matrices.

```toml
[KineticForces]
[Equilibrium]
kinetic_file = "kinetic.h5" # n_i column/dataset = TOTAL main-ion density

[KineticForces]
electron = true # add electron NTV in addition to the ion species
impurity_charge = 6 # impurity charge (closes quasineutrality)
impurity_mass = 12 # impurity mass
Expand Down Expand Up @@ -95,8 +97,8 @@ profiles and physics parameters for sensitivity studies:

### Profile-loader knobs (`density_factor`, `temperature_factor`, `ExB_rotation_factor`, `toroidal_rotation_factor`)

These four knobs are applied during kinetic profile loading in
`load_kinetic_profiles`, before any physics evaluation. The physical model is:
These four knobs are applied when the NTV view of the kinetic profiles is built by
`kinetic_profile_splines`, before any physics evaluation. The physical model is:

```math
\omega_\phi = \omega_E + \omega_{*n,i} + \omega_{*T,i}
Expand Down
12 changes: 6 additions & 6 deletions docs/src/stability.md
Original file line number Diff line number Diff line change
Expand Up @@ -323,12 +323,12 @@ TokaMaker's `save_eqdsk` writes such files when `lcfs_pad` (``p``) is set with t
``\psi = 1/(1 - p)``. Writing with `truncate_eq=False` keeps the whole plasma; EFIT-written files
place their boundary on the X-point flux.

The scan takes its kinetic profiles from the `[SLAYER]` `profile_file`, through the same control
and loader the SLAYER analysis uses, so both see the same profiles, the same HDF5 group and the
same χ⊥(ψ) — the layer widths cannot disagree between them. A deck without a `[SLAYER]`
`profile_file` does not get the scan. Because the file is resolved from the run's TOML, the scan
only runs on the `gpec.toml` entry point: the programmatic `solve(prob, alg)` path has no inputs to
resolve it from, so the cap cannot be applied there and setting the flag warns instead. Its result is always written to
The scan takes its kinetic profiles from the equilibrium (`[Equilibrium] kinetic_file`) through the
same view and `[SLAYER]` control the SLAYER analysis uses, so both see the same profiles and the
same χ⊥(ψ) — the layer widths cannot disagree between them. A deck without a `[SLAYER]` section or
kinetic profiles does not get the scan. Because the layer settings come from the run's `[SLAYER]`
section, the scan only runs on the `gpec.toml` entry point: the programmatic `solve(prob, alg)` path
has no such section, so the cap cannot be applied there and setting the flag warns instead. Its result is always written to
`ForceFreeStates/LayerOverlap/`, but it only constrains the domain when
`psilim_from_layer_overlap = true`. It then caps `qlim` before the `rational_offset_fraction` step, so `rational_offset_fraction`
still selects the final surface from inside the cap. A cap beyond `psihigh` has no effect.
Expand Down
6 changes: 3 additions & 3 deletions examples/DIIID-like_SLAYER_example/gpec.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

[Equilibrium]
eq_filename = "../DIIID-like_ideal_example/TkMkr_D3Dlike_Hmode.geqdsk" # Path to equilibrium file
kinetic_file = "../DIIID-like_ideal_example/TkMkr_D3Dlike_Hmode_kinetic.h5" # Kinetic-profile file (HDF5 GPEC kinetic schema or 6-column ASCII), read once into the equilibrium
eq_type = "efit" # Type of the input 2D equilibrium file
jac_type = "hamada" # Coordinate system (hamada, pest, boozer, equal_arc)
grid_type = "log_asymptotic" # Radial grid packing type
Expand Down Expand Up @@ -93,9 +94,8 @@ filter_above_poles = true # Discard roots above the highest pole γ
filter_outside_re = true # Condition the above-pole filter on the +γ step exiting the Re(Δ)=0 contour loop
polish_roots = true # refine each root to the true zero; enables the validity gate
store_scan = false # per-surface scans omitted from HDF5 (uncoupled has one per surface)
# Standardized kinetic-profile file (GPEC HDF5 kinetic schema), shared with the
# sibling ideal example; carries n_e, T_e, T_i, omega_E, chi_e (χ⊥,e), chi_i (χ⊥,i), chi_phi (χ_φ).
profile_file = "../DIIID-like_ideal_example/TkMkr_D3Dlike_Hmode_kinetic.h5" # Kinetic-profile file (relative to the run dir), required when SLAYER is enabled
# Kinetic profiles come from the [Equilibrium] kinetic_file (GPEC HDF5 kinetic schema), shared with the
# sibling ideal example; it carries n_e, T_e, T_i, omega_E, chi_e (χ⊥,e), chi_i (χ⊥,i), chi_phi (χ_φ).

[SLAYER.scan_grid]
Q_re_range = [-2.0, 2.0] # Scan box in the normalized Q plane, Re(Q) axis
Expand Down
2 changes: 1 addition & 1 deletion examples/DIIID-like_error_field_example/gpec.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

[Equilibrium]
eq_filename = "../DIIID-like_ideal_example/TkMkr_D3Dlike_Hmode.geqdsk" # Path to equilibrium file
kinetic_file = "../DIIID-like_ideal_example/TkMkr_D3Dlike_Hmode_kinetic.h5" # Kinetic-profile file (HDF5 GPEC kinetic schema or 6-column ASCII), read once into the equilibrium
eq_type = "efit" # Type of the input 2D equilibrium file
jac_type = "hamada" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom)
grid_type = "auto" # Radial grid packing type ("auto" = two-pass measured-curvature grid)
Expand Down Expand Up @@ -247,7 +248,6 @@ rotation_span_factor = 1.0 # The scan covers ±span_factor × max(|
rotation_offset_factor = 2.0 # Rough neoclassical offset as a multiple of the ion temperature-gradient diamagnetic frequency ω_*T

[KineticForces]
kinetic_file = "../DIIID-like_ideal_example/TkMkr_D3Dlike_Hmode_kinetic.h5" # GPEC HDF5 kinetic schema (psi,n_i,n_e,T_i,T_e,omega_E,+chi_e,chi_phi)
nn = 1 # Toroidal mode number (matches nn_low/nn_high)
bounce_harmonic_max = 6 # Bounce harmonics retained
ion_charge = 1 # Main-ion charge (deuterium)
Expand Down
2 changes: 1 addition & 1 deletion examples/DIIID-like_ideal_example/gpec.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

[Equilibrium]
eq_filename = "TkMkr_D3Dlike_Hmode.geqdsk" # Path to equilibrium file
kinetic_file = "TkMkr_D3Dlike_Hmode_kinetic.h5" # Kinetic-profile file (HDF5 GPEC kinetic schema or 6-column ASCII), read once into the equilibrium
eq_type = "efit" # Type of the input 2D equilibrium file
jac_type = "hamada" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom)
grid_type = "auto" # Radial grid packing type ("auto" = two-pass measured-curvature grid)
Expand Down Expand Up @@ -88,7 +89,6 @@ write_outputs_to_HDF5 = true # Write ErrorFields/CoilSensitivities/ t
verbose = true # Log per-coil-set progress and linearity diagnostics

[KineticForces]
kinetic_file = "TkMkr_D3Dlike_Hmode_kinetic.h5" # GPEC HDF5 kinetic schema (psi,n_i,n_e,T_i,T_e,omega_E,+chi_e,chi_i,chi_phi). Legacy .gpeckf/.kin still readable.
nn = 1 # Toroidal mode number (matches nn_low/nn_high)
bounce_harmonic_max = 6 # Bounce harmonics retained
ion_charge = 1 # Main-ion charge (deuterium)
Expand Down
4 changes: 2 additions & 2 deletions examples/DIIID-like_truncation_example/gpec.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

[Equilibrium]
eq_filename = "../DIIID-like_ideal_example/TkMkr_D3Dlike_Hmode.geqdsk" # Path to equilibrium file
kinetic_file = "../DIIID-like_ideal_example/TkMkr_D3Dlike_Hmode_kinetic.h5" # Kinetic-profile file (HDF5 GPEC kinetic schema or 6-column ASCII), read once into the equilibrium
eq_type = "efit" # Type of the input 2D equilibrium file
jac_type = "hamada" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom)
grid_type = "auto" # Radial grid packing type ("auto" = two-pass measured-curvature grid)
Expand Down Expand Up @@ -44,8 +45,7 @@ psilim_from_layer_overlap = true # Cap psilim where adjacent resistive layers

[SLAYER]
# The growth-rate analysis is not run here; the section is present because the layer-overlap scan
# takes its kinetic profiles, and its chi_perp_e(psi), from the SLAYER profile_file. P_perp is pinned to the
# builds its layer widths, and its chi_perp_e(psi), from the SLAYER settings and the equilibrium's kinetic profiles. P_perp is pinned to the
# electron channel so the cap binds: with the default D_perp the layers are ~30% narrower and do not overlap inside psi = 1.
enabled = false # Run the SLAYER tearing-mode analysis
P_perp_model = "chi_perp_e" # Perpendicular Prandtl number model (pinned; see above)
profile_file = "../DIIID-like_ideal_example/TkMkr_D3Dlike_Hmode_kinetic.h5" # Kinetic-profile file (relative to the run dir), required when SLAYER is enabled
2 changes: 1 addition & 1 deletion examples/Solovev_kinetic_NTV_example/gpec.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

[Equilibrium]
eq_type = "sol" # Type of the input 2D equilibrium file
kinetic_file = "kinetic.dat" # Kinetic-profile file (HDF5 GPEC kinetic schema or 6-column ASCII), read once into the equilibrium
jac_type = "pest" # Coordinate system (hamada, pest, boozer, equal_arc, park, other)
grid_type = "core_edge_packed" # Radial grid packing type
psilow = 1e-4 # Lower limit of normalized poloidal flux
Expand Down Expand Up @@ -93,6 +94,5 @@ b0fac = 1 # Scale toroidal field at constant β (Bt changes; shape, β fixe
f0fac = 1 # Scale toroidal field at constant pressure (β, q change; Φ, p, Bp fixed)

[KineticForces]
kinetic_file = "kinetic.dat" # Kinetic profile file: psi_n, n_i, n_e, T_i, T_e, omega_E columns
fcgl_flag = true # Fluid Chew–Goldberger–Low energy (l = 0 fluid limit) alongside the default fgar method
axis_validity_suppression = true # Suppress kinetic terms inside the near-axis orbit-width validity limit
2 changes: 1 addition & 1 deletion examples/Solovev_kinetic_calculated_example/gpec.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

[Equilibrium]
eq_type = "sol" # Type of the input 2D equilibrium file
kinetic_file = "kinetic.dat" # Kinetic-profile file (HDF5 GPEC kinetic schema or 6-column ASCII), read once into the equilibrium
jac_type = "pest" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom)
grid_type = "core_edge_packed" # Radial grid packing type
psilow = 1e-4 # Lower limit of normalized flux coordinate
Expand Down Expand Up @@ -74,7 +75,6 @@ write_outputs_to_HDF5 = true # Write outputs to the HDF5 file
verbose = false # Enable verbose logging

[KineticForces]
kinetic_file = "kinetic.dat" # Kinetic profile file: psi_n, n_i, n_e, T_i, T_e, omega_E columns

[[KineticForces.ion_species]]
z = 1 # Species charge (fundamental units)
Expand Down
2 changes: 1 addition & 1 deletion examples/Solovev_kinetic_multiion_example/gpec.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

[Equilibrium]
eq_type = "sol" # Type of the input 2D equilibrium file
kinetic_file = "kinetic.dat" # Kinetic-profile file (HDF5 GPEC kinetic schema or 6-column ASCII), read once into the equilibrium
jac_type = "pest" # Coordinate system (hamada, pest, boozer, equal_arc, park, other)
grid_type = "core_edge_packed" # Radial grid packing type
psilow = 1e-4 # Lower limit of normalized poloidal flux
Expand Down Expand Up @@ -95,7 +96,6 @@ f0fac = 1 # Scale toroidal field at constant pressure (β, q change; Φ, p,
[KineticForces]
# Multi-ion NTV: the analytic Solovev main ion is modelled as a 50/50 D-T mix (n_i = total),
# so the summed NTV runs over D + T + the neutrality-closing impurity + electrons.
kinetic_file = "kinetic.dat" # Kinetic profile file: psi_n, n_i, n_e, T_i, T_e, omega_E columns
electron = true # Add electron NTV in addition to the ion species
impurity_charge = 6 # Impurity charge (fundamental units); closes quasineutrality
impurity_mass = 12 # Impurity mass (proton masses)
Expand Down
2 changes: 1 addition & 1 deletion examples/a10_kinetic_example/gpec.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

[Equilibrium]
eq_filename = "fix_a100_k10_q2_bn010_prof1" # Path to equilibrium file
kinetic_file = "a10_prof1.txt" # Kinetic-profile file (HDF5 GPEC kinetic schema or 6-column ASCII), read once into the equilibrium
eq_type = "efit" # Type of the input 2D equilibrium file
jac_type = "hamada" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom)
grid_type = "core_edge_packed" # Radial grid packing type
Expand Down Expand Up @@ -57,7 +58,6 @@ write_outputs_to_HDF5 = true # Write outputs to the HDF5 file
force_termination = true # Stop after force-free states — the kinetic stability eigenvalues are the result of interest

[KineticForces]
kinetic_file = "a10_prof1.txt" # Path to the kinetic profile file (bundled)
nn = 1 # Toroidal mode number
bounce_harmonic_max = 6 # Bounce-harmonic number
ion_charge = 1 # Ion charge (fundamental units)
Expand Down
2 changes: 1 addition & 1 deletion regression-harness/src/runner.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ With no `overrides`, the example deck runs in place (returns it untouched). With
the deck is copied to a uniquely named throwaway sibling of the example dir and the named gpec.toml keys are
patched there, so one shared example can serve several cases (e.g. a collisionless variant via
`"KineticForces.nutype" => "zero"`). The copy sits beside the original so relative file
references in the deck (e.g. `SLAYER.profile_file = "../<other_example>/..."`) still resolve.
references in the deck (e.g. `Equilibrium.kinetic_file = "../<other_example>/..."`) still resolve.
Override keys are dotted paths into the TOML; missing intermediate tables are created.
Returns `(rundir, is_temp)`; the caller removes `rundir` when `is_temp`.
"""
Expand Down
Loading
Loading