Skip to content
Open
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|power_bp|power_b|power_r|power_rc)\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)\s*='
files: ^(examples/.*\.toml|test/test_data/.*\.toml)$
14 changes: 7 additions & 7 deletions benchmarks/benchmark_against_fortran_run.jl
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ function write_gpec_toml_coil(
@printf(io, "psihigh = %.6f\n", p.psihigh)
println(io, "mpsi = $(p.mpsi)")
println(io, "mtheta = $(p.mtheta_equil)")
println(io, "etol = 1e-7")
println(io, "flux_surface_rtol = 1e-7")
println(io)
println(io, "[Wall]")
println(io, "shape = \"nowall\"")
Expand All @@ -416,12 +416,12 @@ function write_gpec_toml_coil(
# Match the Fortran sas_flag truncation: integration stops at q = qhigh
# (= outermost rational q + dmlim). psiedge from dcon.in (=1.0 → no edge dW scan).
@printf(io, "qhigh = %.4f\n", qhigh)
@printf(io, "psiedge = %.4f\n", p.psiedge)
@printf(io, "dW_edge_scan_start = %.4f\n", p.psiedge)
println(io, "nn_low = $(p.nn)")
println(io, "nn_high = $(p.nn)")
println(io, "delta_mlow = $(p.delta_mlow)")
println(io, "delta_mhigh = $(p.delta_mhigh)")
println(io, "mthvac = 512")
println(io, "mtheta_vacuum = 512")
println(io, "eulerlagrange_tolerance = 1e-7")
println(io, "singfac_min = 1e-4")
println(io, "save_interval = $(p.euler_stride)")
Expand Down Expand Up @@ -467,7 +467,7 @@ function write_gpec_toml_file(
@printf(io, "psihigh = %.6f\n", p.psihigh)
println(io, "mpsi = $(p.mpsi)")
println(io, "mtheta = $(p.mtheta_equil)")
println(io, "etol = 1e-7")
println(io, "flux_surface_rtol = 1e-7")
println(io)
println(io, "[Wall]")
println(io, "shape = \"nowall\"")
Expand All @@ -476,12 +476,12 @@ function write_gpec_toml_file(
println(io, "local_stability_flag = true")
println(io, "vac_flag = true")
@printf(io, "qlow = %.4f\n", p.qlow)
@printf(io, "psiedge = %.4f\n", p.psiedge)
@printf(io, "dW_edge_scan_start = %.4f\n", p.psiedge)
println(io, "nn_low = $(p.nn)")
println(io, "nn_high = $(p.nn)")
println(io, "delta_mlow = $(p.delta_mlow)")
println(io, "delta_mhigh = $(p.delta_mhigh)")
println(io, "mthvac = 512")
println(io, "mtheta_vacuum = 512")
println(io, "eulerlagrange_tolerance = 1e-7")
println(io, "singfac_min = 1e-4")
println(io, "save_interval = $(p.euler_stride)")
Expand Down Expand Up @@ -511,7 +511,7 @@ function setup_equil(p::FortranRunParams, fortran_dir::String)
"mpsi" => p.mpsi,
"mtheta" => p.mtheta_equil,
"grid_type" => p.grid_type,
"etol" => 1e-7
"flux_surface_rtol" => 1e-7
)
eq_config = Equilibrium.EquilibriumConfig(eq_dict, fortran_dir)
return Equilibrium.setup_equilibrium(eq_config)
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/benchmark_coil_ForcingTerms_against_fortran.jl
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ t_equil = @elapsed begin
"mtheta" => p.mtheta_equil,
"grid_type" => p.grid_type,
"psi_accuracy" => 0.001,
"etol" => 1e-7,
"flux_surface_rtol" => 1e-7,
)
eq_config = Equilibrium.EquilibriumConfig(eq_dict, run_dir)
equil = Equilibrium.setup_equilibrium(eq_config)
Expand Down
18 changes: 9 additions & 9 deletions benchmarks/benchmark_diiid_ideal_ntv_torque.jl
Original file line number Diff line number Diff line change
Expand Up @@ -117,13 +117,13 @@ function build_benchmark_tomldir(eq_file::String)
eq_filename = "$eq_name"
eq_type = "efit"
jac_type = "hamada"
grid_type = "ldp"
grid_type = "core_edge_packed"
psilow = 1e-4
psihigh = 0.993
mpsi = 128
mtheta = 256
newq0 = 0
etol = 1e-7
flux_surface_rtol = 1e-7

[Wall]
shape = "nowall"
Expand All @@ -133,7 +133,7 @@ local_stability_flag = true
vac_flag = true
force_termination = false

psiedge = 1.00 # No edge-scan truncation (dmlim mechanism removed in develop)
dW_edge_scan_start = 1.0 # No edge-scan truncation (rational_offset_fraction truncation off)
qlow = 1.02
qhigh = 1e3
sing_start = 0
Expand All @@ -142,14 +142,14 @@ nn_low = 1
nn_high = 1
delta_mlow = 8
delta_mhigh = 8
mthvac = 512
mtheta_vacuum = 512

kinetic_source = "fixed"
kinetic_factor = 0.0
eulerlagrange_tolerance = 1e-7
save_interval = 3
singfac_min = 1e-4
ucrit = 1e4
renorm_threshold = 1e4
"""
)
end
Expand Down Expand Up @@ -278,9 +278,9 @@ function run_benchmark(fortran_dir::String=default_fortran_dir())
# for the final full-validation run.
tgar_enabled = get(ENV, "BENCHMARK_TGAR", "0") == "1"
kf_ctrl = KF.KineticForcesControl(;
fgar_flag=true, tgar_flag=tgar_enabled, nn=1, nl=4,
mi=2, zi=1, nutype="harmonic", f0type="maxwellian",
nufac=1, psilims=[0.0, 1.0], kinetic_file=inputs.kin_file,
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,
verbose=false)

# Initialize KF internal state
Expand Down Expand Up @@ -309,7 +309,7 @@ function run_benchmark(fortran_dir::String=default_fortran_dir())

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

kf_state = KF.KineticForcesState()
KF.compute_torque_all_methods!(kf_state, kf_intr, kf_ctrl, equil, kinetic_profiles)
Expand Down
8 changes: 4 additions & 4 deletions benchmarks/benchmark_diiid_kinetic_stability.jl
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ psihigh = 0.993
mpsi = 128
mtheta = 256
newq0 = 0
etol = 1e-7
flux_surface_rtol = 1e-7

[Wall]
shape = "nowall"
Expand All @@ -140,7 +140,7 @@ local_stability_flag = true
vac_flag = true
force_termination = true # Skip PE+KF post-processing — we only need FFS eigenvalues

psiedge = 1.0 # No edge-scan truncation (dmlim mechanism removed in develop)
dW_edge_scan_start = 1.0 # No edge-scan truncation (rational_offset_fraction truncation off)
qlow = 1.02
qhigh = 1e3
sing_start = 0
Expand All @@ -149,14 +149,14 @@ nn_low = 1
nn_high = 1
delta_mlow = 8
delta_mhigh = 8
mthvac = 512
mtheta_vacuum = 512
thmax0 = 1

kinetic_source = "calculated"
kinetic_factor = 1.0
eulerlagrange_tolerance = 1e-7
singfac_min = 1e-4
ucrit = 1e4
renorm_threshold = 1e4
write_outputs_to_HDF5 = true

[KineticForces]
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/benchmark_q_vs_iota_edge.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ using Plots
const GPE = GeneralizedPerturbedEquilibrium
const EXAMPLE_DIR = joinpath(@__DIR__, "..", "examples", "DIIID-like_ideal_example")

# Dense ldp reference equilibrium: treat its q(ψ) as ground truth
# Dense core_edge_packed reference equilibrium: treat its q(ψ) as ground truth
function reference_q()
inputs, _, additional_input = GPE.build_inputs_from_toml(EXAMPLE_DIR)
equil_dict = merge(inputs["Equilibrium"], Dict{String,Any}("grid_type" => "ldp", "mpsi" => 1024))
equil_dict = merge(inputs["Equilibrium"], Dict{String,Any}("grid_type" => "core_edge_packed", "mpsi" => 1024))
eq_config = GPE.Equilibrium.EquilibriumConfig(equil_dict, EXAMPLE_DIR)
equil = GPE.Equilibrium.setup_equilibrium(eq_config, additional_input)
# The dict rides along so the mpsi scan can rebuild configs; EquilibriumConfig is immutable.
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/benchmark_solovev_kinetic_stability.jl
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ function build_matched_fortran_deck(workdir::String)
replace_namelist_value!(dconin, "qlow", string(ffs["qlow"]))
replace_namelist_value!(dconin, "qhigh", string(ffs["qhigh"]))
replace_namelist_value!(dconin, "singfac_min", string(ffs["singfac_min"]))
# Julia set_psilim_via_dmlim defaults true (sas_flag) with dmlim 0.2.
# Julia truncate_at_rational_offset defaults true (sas_flag) with rational_offset_fraction 0.2.
replace_namelist_value!(dconin, "sas_flag", "t")
replace_namelist_value!(dconin, "dmlim", "0.2")

Expand All @@ -118,7 +118,7 @@ function build_matched_fortran_deck(workdir::String)
replace_namelist_value!(pentin, "nl", string(get(kf, "nl", 1)))
replace_namelist_value!(pentin, "nutype", "\"" * string(get(kf, "nutype", "harmonic")) * "\"")
replace_namelist_value!(pentin, "f0type", "\"" * string(get(kf, "f0type", "maxwellian")) * "\"")
replace_namelist_value!(pentin, "nufac", string(get(kf, "nufac", 1)))
replace_namelist_value!(pentin, "nufac", string(get(kf, "collisionality_factor", 1)))
replace_namelist_value!(pentin, "kinetic_file", "\"kinetic.txt\"")

# kinetic.txt — Fortran readtable wants a title line then numeric rows; the
Expand Down
10 changes: 5 additions & 5 deletions docs/src/developer_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Three things follow. First, convergence is per-surface: the outer surface
to about 1% — while `dpm[2,2]` is marginal and `dpm[1,1]` (q=2) never settles,
still moving ~7% between the two tightest grids. A plateau detector must
therefore report per-surface rather than pass/fail for the whole diagonal.
(As the `ldp` scan below shows, q=2 is not inherently unconvergeable — it is the
(As the `core_edge_packed` scan below shows, q=2 is not inherently unconvergeable — it is the
auto grid that prevents it from settling.)

Second, the growth rate is linear in Δ′: `gamma/dpm[1,1]` is 24.1 to within 0.5%
Expand All @@ -86,7 +86,7 @@ after pass 2, so tightening `psi_accuracy` moves it *further* from
self-consistency rather than closer, and the warning's advice to "consider
tightening psi_accuracy" is counterproductive in this regime.

The same deck on a deterministic `ldp` grid, which skips the measure-and-re-form
The same deck on a deterministic `core_edge_packed` grid, which skips the measure-and-re-form
step entirely, converges:

| mpsi | dpm[1,1] | dpm[2,2] | dpm[3,3] | gamma 2/1 (Hz) |
Expand All @@ -105,18 +105,18 @@ value should do. The non-convergence under the auto grid is an artifact of the
generator, not of the Δ′ extraction.

Two consequences. A plateau criterion is implementable today against a fixed
`ldp` grid, without waiting on the auto-grid work. And the auto grid's answers
`core_edge_packed` grid, without waiting on the auto-grid work. And the auto grid's answers
are biased in both directions relative to the converged value: at its default
`psi_accuracy` it gave 6.39 (28% low), at its tightest 9.51 (7% high). Anything
pinned on the auto grid should be read with that in mind.

This is not implemented. Doing it properly needs:

- either a fixed `ldp` grid (which already converges, see above) or knot
- either a fixed `core_edge_packed` grid (which already converges, see above) or knot
refinement iterated to a fixed point (repeat the measure-and-re-form
step until `implied_knot_count` stops exceeding the grid in use), since
without it the scan target keeps moving;
- a scan driver over `psi_accuracy` × truncation (`psihigh`, `dmlim`, `qhigh`)
- a scan driver over `psi_accuracy` × truncation (`psihigh`, `rational_offset_fraction`, `qhigh`)
that records the Δ′ diagonal per configuration;
- a plateau/mode detector with an explicit stationarity tolerance, plus a
defined failure mode for surfaces where no plateau exists (the
Expand Down
10 changes: 5 additions & 5 deletions docs/src/equilibrium.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,18 +96,18 @@ built by a **two-pass measured-curvature refinement** driven by the single accur
(no file re-read).

Every region's knot count scales as τ^(-1/3), so tightening `psi_accuracy` refines
the core, pedestal, and edge proportionally. The legacy `grid_type = "ldp"`
the core, pedestal, and edge proportionally. The fixed `grid_type = "core_edge_packed"`
(sin²-spaced), `"pow1"`, `"uniform"`, and explicit `mpsi > 0` (single-pass, fixed
layout) are still supported. Library users calling `setup_equilibrium` directly with
`mpsi = 0` receive the coarse pass-1 grid; use `refined_psi_grid` and the
`override_psi_nodes` keyword to apply the refinement manually.

The packing on the DIII-D-like example (n=1) compared to fixed `ldp` grids — note the
The packing on the DIII-D-like example (n=1) compared to fixed `core_edge_packed` grids — note the
coarse spacing across the smooth mid-radius, the spacing dips at each rational surface,
and the core/pedestal/edge packing (`benchmarks/plot_grid_knot_placement.jl` regenerates
this figure):

![Radial knot packing: auto two-pass vs ldp](assets/grid_knot_placement.png)
![Radial knot packing: auto two-pass vs core_edge_packed](assets/grid_knot_placement.png)

Decomposing the density by source on the same example shows the pedestal band
(ψ_N ≈ 0.85–0.98) is driven by *measured* curvature, not the edge floor: the pressure,
Expand Down Expand Up @@ -176,10 +176,10 @@ Analytic / testing example: construct a large-aspect-ratio model
using GeneralizedPerturbedEquilibrium

# Create a LAR config from a small TOML fragment or file
larcfg = GeneralizedPerturbedEquilibrium.Equilibrium.LargeAspectRatioConfig(lar_r0=10.0, lar_a=1.0, beta0=1e-3)
larcfg = GeneralizedPerturbedEquilibrium.Equilibrium.LargeAspectRatioConfig(r0=10.0, a=1.0, beta0=1e-3)
pe = GeneralizedPerturbedEquilibrium.Equilibrium.setup_equilibrium(GeneralizedPerturbedEquilibrium.Equilibrium.EquilibriumConfig(control=Dict("eq_filename"=>"unused","eq_type"=>"lar")), larcfg)

println("Built LAR equilibrium with a = ", larcfg.lar_a)
println("Built LAR equilibrium with a = ", larcfg.a)
```

## Notes and Caveats
Expand Down
4 changes: 2 additions & 2 deletions docs/src/forcing_terms.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ tiltx = [2.0]
# Analytic window-pane (picture-frame) array — corner mode: two opposite [R, Z] corners
[[ForcingTerms.coil_set]]
source = "window_pane"
ncoil_gen = 8
n_coils = 8
rz_corners = [[2.2, -0.6], [2.2, 0.6]]
gap_fraction = 0.15
currents = [1e3, -1e3, 1e3, -1e3, 1e3, -1e3, 1e3, -1e3]
Expand All @@ -151,7 +151,7 @@ currents = [1e3, -1e3, 1e3, -1e3, 1e3, -1e3, 1e3, -1e3]
# (degrees, 0 = tangential). Requires an equilibrium (the main forcing pipeline supplies it).
[[ForcingTerms.coil_set]]
source = "window_pane"
ncoil_gen = 2
n_coils = 2
standoff = 0.25
poloidal_angle = 0.0
poloidal_length = 1.2
Expand Down
18 changes: 9 additions & 9 deletions docs/src/kinetic_forces.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ paths: the post-PE ψ-quadrature diagnostic and the self-consistent
[KineticForces]
kinetic_file = "kinetic.h5" # n_i column/dataset = TOTAL main-ion density
electron = true # add electron NTV in addition to the ion species
zimp = 6 # impurity charge (closes quasineutrality)
mimp = 12 # impurity mass
impurity_charge = 6 # impurity charge (closes quasineutrality)
impurity_mass = 12 # impurity mass

[[KineticForces.ion_species]]
z = 1
Expand All @@ -58,8 +58,8 @@ Each species sets exactly one of `fraction` (share of the file's total `n_i`)
or `density` (a named `n_*` dataset). An all-fraction list must sum to 1; in a
mixed list the impurity content is set by the file's `n_i`/`n_e` deficit, not
by a fraction shortfall, and fractions may sum below (never above) 1. Every
main-ion charge must satisfy `z < zimp`. An empty `ion_species` list runs the
single main ion from `zi`/`mi` (with `electron = true` still adding the
main-ion charge must satisfy `z < impurity_charge`. An empty `ion_species` list runs the
single main ion from `ion_charge`/`ion_mass` (with `electron = true` still adding the
electron species — the electron flag always means *in addition to* the ions).

The summed total is written to `KineticForces/<method>/` exactly as in a single-ion
Expand All @@ -85,9 +85,9 @@ profiles and physics parameters for sensitivity studies:
| `temperature_factor` | 1.0 | Profile loader | Temperature scaling (Ti, Te) |
| `ExB_rotation_factor` | 1.0 | Profile loader | ExB rotation scaling (omegaE) |
| `toroidal_rotation_factor` | 1.0 | Profile loader | Total toroidal rotation scaling (wphi) |
| `wdfac` | 1.0 | Evaluation time | Magnetic drift frequency scaling |
| `nufac` | 1.0 | Evaluation time | Collisionality scaling |
| `divxfac` | 1.0 | Evaluation time | ``\nabla \cdot \xi_\perp`` scaling |
| `magnetic_drift_factor` | 1.0 | Evaluation time | Magnetic drift frequency scaling |
| `collisionality_factor` | 1.0 | Evaluation time | Collisionality scaling |
| `divxi_factor` | 1.0 | Evaluation time | ``\nabla \cdot \xi_\perp`` scaling |

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

Expand Down Expand Up @@ -119,7 +119,7 @@ The scaling sequence is:
6. Recompute collisionality from scaled density and temperature
7. Build final splines from scaled arrays

### Evaluation-time knobs (`wdfac`, `nufac`, `divxfac`)
### Evaluation-time knobs (`magnetic_drift_factor`, `collisionality_factor`, `divxi_factor`)

These three knobs are applied during the bounce-averaged kinetic matrix and
torque calculations in `KineticForces/Torque.jl` and related modules. They
Expand All @@ -131,7 +131,7 @@ terms respectively, and do not modify the stored kinetic profile splines.
(``\nu_i``, ``\nu_e``) from the scaled density and temperature arrays.
Fortran PENTRC (`inputs.f90:237-246`) computes collisionality from
**unscaled** profiles. If you need independent collisionality scaling
without changing the density/temperature profiles, use `nufac`.
without changing the density/temperature profiles, use `collisionality_factor`.

2. **Consistent derivative ordering:** Fortran's `inputs.f90:269-272`
mixes pre-scaling spline derivatives with post-scaling array values
Expand Down
2 changes: 1 addition & 1 deletion docs/src/perturbed_equilibrium.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ different n never couple:
An integer-q surface appears once for each n that resonates there. Those rows are not
neighbors of each other when the Chirikov parameter is computed.

`set_psilim_via_dmlim` is ignored for multi-n runs, so set the edge with `qhigh` or `psihigh`,
`truncate_at_rational_offset` is ignored for multi-n runs, so set the edge with `qhigh` or `psihigh`,
away from any rational m/n in the range. KineticForces still takes a single n.

## Dominant resonant-coupling mode
Expand Down
2 changes: 1 addition & 1 deletion docs/src/stability.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ than integrating the quadratic Riccati ODE directly (which blows up when ``|S|``
the code integrates the linear EL system with `sing_der!` as the RHS and recovers
``S = U_1 U_2^{-1}`` via periodic renormalization — an approach that is mathematically
equivalent to O(Δψ) but uses `Vern9`'s full 9th-order accuracy. Renormalization is
triggered whenever ``\max(|U_1|)`` or ``\max(|U_2|)`` exceeds the threshold `ucrit`, and is
triggered whenever ``\max(|U_1|)`` or ``\max(|U_2|)`` exceeds the threshold `renorm_threshold`, and is
forced at the end of each chunk. At singular surface crossings,
`riccati_cross_ideal_singular_surf!` applies the small-asymptotic matching directly in column
`ipert_res` — without Gaussian reduction — and renormalizes to ``(S, I)``.
Expand Down
Loading
Loading