diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 02ff18b3f..5efde01b0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -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)$ diff --git a/benchmarks/benchmark_against_fortran_run.jl b/benchmarks/benchmark_against_fortran_run.jl index 6116a8058..841995301 100644 --- a/benchmarks/benchmark_against_fortran_run.jl +++ b/benchmarks/benchmark_against_fortran_run.jl @@ -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\"") @@ -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)") @@ -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\"") @@ -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)") @@ -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) diff --git a/benchmarks/benchmark_coil_ForcingTerms_against_fortran.jl b/benchmarks/benchmark_coil_ForcingTerms_against_fortran.jl index 22e2b1938..e292c24c0 100644 --- a/benchmarks/benchmark_coil_ForcingTerms_against_fortran.jl +++ b/benchmarks/benchmark_coil_ForcingTerms_against_fortran.jl @@ -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) diff --git a/benchmarks/benchmark_diiid_ideal_ntv_torque.jl b/benchmarks/benchmark_diiid_ideal_ntv_torque.jl index 7da279b91..430c353cc 100644 --- a/benchmarks/benchmark_diiid_ideal_ntv_torque.jl +++ b/benchmarks/benchmark_diiid_ideal_ntv_torque.jl @@ -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" @@ -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 @@ -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 @@ -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 @@ -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) diff --git a/benchmarks/benchmark_diiid_kinetic_stability.jl b/benchmarks/benchmark_diiid_kinetic_stability.jl index 9ae37b6b3..1c87d9814 100644 --- a/benchmarks/benchmark_diiid_kinetic_stability.jl +++ b/benchmarks/benchmark_diiid_kinetic_stability.jl @@ -130,7 +130,7 @@ psihigh = 0.993 mpsi = 128 mtheta = 256 newq0 = 0 -etol = 1e-7 +flux_surface_rtol = 1e-7 [Wall] shape = "nowall" @@ -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 @@ -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] diff --git a/benchmarks/benchmark_q_vs_iota_edge.jl b/benchmarks/benchmark_q_vs_iota_edge.jl index 828f1bbb0..4fad6fd28 100644 --- a/benchmarks/benchmark_q_vs_iota_edge.jl +++ b/benchmarks/benchmark_q_vs_iota_edge.jl @@ -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. diff --git a/benchmarks/benchmark_solovev_kinetic_stability.jl b/benchmarks/benchmark_solovev_kinetic_stability.jl index c942a20c6..955f5f2cb 100644 --- a/benchmarks/benchmark_solovev_kinetic_stability.jl +++ b/benchmarks/benchmark_solovev_kinetic_stability.jl @@ -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") @@ -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 diff --git a/docs/src/developer_notes.md b/docs/src/developer_notes.md index 2aefc0482..416cf86a2 100644 --- a/docs/src/developer_notes.md +++ b/docs/src/developer_notes.md @@ -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% @@ -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) | @@ -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 diff --git a/docs/src/equilibrium.md b/docs/src/equilibrium.md index ecff97671..03c2fe898 100644 --- a/docs/src/equilibrium.md +++ b/docs/src/equilibrium.md @@ -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, @@ -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 diff --git a/docs/src/forcing_terms.md b/docs/src/forcing_terms.md index d7fe7bc81..7635c57be 100644 --- a/docs/src/forcing_terms.md +++ b/docs/src/forcing_terms.md @@ -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] @@ -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 diff --git a/docs/src/kinetic_forces.md b/docs/src/kinetic_forces.md index 858bb53a0..c84a76821 100644 --- a/docs/src/kinetic_forces.md +++ b/docs/src/kinetic_forces.md @@ -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 @@ -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//` exactly as in a single-ion @@ -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`) @@ -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 @@ -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 diff --git a/docs/src/perturbed_equilibrium.md b/docs/src/perturbed_equilibrium.md index bcdaa550e..5e079cfee 100644 --- a/docs/src/perturbed_equilibrium.md +++ b/docs/src/perturbed_equilibrium.md @@ -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 diff --git a/docs/src/stability.md b/docs/src/stability.md index c61ca716e..49d5c6dac 100644 --- a/docs/src/stability.md +++ b/docs/src/stability.md @@ -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)``. diff --git a/examples/DIIID-like_SLAYER_example/gpec.toml b/examples/DIIID-like_SLAYER_example/gpec.toml index db8aec602..22a66db03 100644 --- a/examples/DIIID-like_SLAYER_example/gpec.toml +++ b/examples/DIIID-like_SLAYER_example/gpec.toml @@ -15,17 +15,17 @@ mpsi = 0 # Number of radial grid points (0 = auto-compute psi_accuracy = 0.001 # Target absolute error in q for auto-mpsi mtheta = 256 # Number of poloidal grid points newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) -etol = 1e-10 # Error tolerance for equilibrium solver +flux_surface_rtol = 1e-10 # Relative tolerance of the ODE solves that trace each flux surface force_termination = false # Terminate after equilibrium setup (skip stability calculations) [Wall] shape = "nowall" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath) -a = 0.2415 # Distance from plasma (conformal) or shape parameter -aw = 0.05 # Half-thickness parameter for Dee-shaped walls -bw = 1.5 # Elongation parameter for wall shapes -cw = 0 # Offset of wall center from major radius -dw = 0.5 # Triangularity parameter for wall shapes -tw = 0.05 # Sharpness of wall corners (try 0.05 as initial value) +standoff_fraction = 0.2415 # Wall standoff in plasma minor radii (conformal, dee) +half_thickness = 0.05 # Half-thickness parameter for Dee-shaped walls +elongation = 1.5 # Elongation parameter for wall shapes +center_offset = 0 # Offset of wall center from major radius +triangularity = 0.5 # Triangularity parameter for wall shapes +corner_sharpness = 0.05 # Sharpness of wall corners (try 0.05 as initial value) equal_arc_wall = true # Equal arc length distribution of nodes on wall [ForceFreeStates] @@ -33,7 +33,7 @@ force_termination = true # Run FFS + SLAYER, skip PerturbedEquilibrium local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile vac_flag = true # Compute plasma, vacuum, and total energies for free-boundary modes -psiedge = 0.99 # Edge dW(ψ) diagnostic scan band [psiedge, psilim]; set ≥ psilim to disable +dW_edge_scan_start = 0.99 # Edge dW(ψ) diagnostic scan band [dW_edge_scan_start, psilim]; set ≥ psilim to disable qlow = 1.02 # Integration initiated at q determined by min(q0, qlow)... qhigh = 1e3 # Integration terminated at q limit determined by min(qa, qhigh)... sing_start = 0 # Start integration at the sing_start'th rational from the axis (psilow) @@ -42,19 +42,19 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 1 # Largest toroidal mode number to include delta_mlow = 8 # Expands lower bound of Fourier harmonics delta_mhigh = 8 # Expands upper bound of Fourier harmonics -mthvac = 512 # Number of points used in splines over poloidal angle at the plasma-vacuum interface +mtheta_vacuum = 512 # Number of points used in splines over poloidal angle at the plasma-vacuum interface kinetic_source = "fixed" # Kinetic matrix source: "fixed" test matrices, or "calculated" from the kinetic NTV model kinetic_factor = 0.0 # Scaling of kinetic matrices (0 = ideal path; >0 enables kinetic mode) eulerlagrange_tolerance = 1e-10# Relative tolerance for ODE integration of Euler-Lagrange equations save_interval = 3 # Save every Nth ODE step (1=all, 10=every 10th). Always saves near rational surfaces. singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e4 # Maximum fraction of solutions allowed before re-normalized +renorm_threshold = 1e4 # Column-norm threshold that triggers solution renormalization # Integrator selection and domain truncation (see ForceFreeStatesControl docstring for details) integrator = "riccati" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) -set_psilim_via_dmlim = true # TRUE for diverted geqdsks — q → ∞ at separatrix, so dmlim truncation avoids the δW kink instability at negligible domain cost -dmlim = 0.2 # Truncate integration at (last_rational_q + dmlim) / n +truncate_at_rational_offset = true # TRUE for diverted geqdsks — q → ∞ at separatrix, so rational_offset_fraction truncation avoids the δW kink instability at negligible domain cost +rational_offset_fraction = 0.2 # Truncate integration at (last_rational_q + rational_offset_fraction) / n [SLAYER] # SLAYER tearing-mode growth-rate analysis on the DIII-D-like H-mode equilibrium. @@ -70,8 +70,8 @@ enabled = true # Run the SLAYER tearing-mode analysis inner_model = "slayer_fitzpatrick" # Inner-layer Δ(Q) model: "slayer_fitzpatrick", "ggj_shooting", or "ggj_galerkin" scan_mode = "amr" # Q-plane scan strategy: "amr" (adaptive refinement) or "brute_force" coupling_mode = "uncoupled" # "uncoupled" (per-surface) or "coupled" (multi-surface determinant) -dc_type = "none" # Critical-Δ offset selector: "none", "lar", "rfitzp", or "toroidal" -mu_i = 2.0 # Ion mass in proton-mass units (2.0 = deuterium) +D_c_type = "none" # Critical-Δ offset selector: "none", "lar", "fitzpatrick", or "toroidal" +ion_mass = 2.0 # Ion mass in proton-mass units (2.0 = deuterium) zeff = 1.0 # Effective charge chi_perp = 1.0 # fallback only; the kinetic file supplies χ⊥(ψ) chi_tor = 1.0 # fallback only; the kinetic file supplies χ_φ(ψ) diff --git a/examples/DIIID-like_error_field_example/gpec.toml b/examples/DIIID-like_error_field_example/gpec.toml index 0378168f6..2f4f04c3a 100644 --- a/examples/DIIID-like_error_field_example/gpec.toml +++ b/examples/DIIID-like_error_field_example/gpec.toml @@ -19,24 +19,24 @@ mpsi = 0 # Number of radial grid intervals (0 = two-pass a psi_accuracy = 0.001 # Target relative accuracy of splined profile derivatives for the auto grid mtheta = 256 # Number of poloidal grid points newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) -etol = 1e-10 # Error tolerance for equilibrium solver +flux_surface_rtol = 1e-10 # Relative tolerance of the ODE solves that trace each flux surface force_termination = false # Terminate after equilibrium setup (skip stability calculations) [Wall] shape = "nowall" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath) -a = 0.2415 # Distance from plasma (conformal) or shape parameter -aw = 0.05 # Half-thickness parameter for Dee-shaped walls -bw = 1.5 # Elongation parameter for wall shapes -cw = 0 # Offset of wall center from major radius -dw = 0.5 # Triangularity parameter for wall shapes -tw = 0.05 # Sharpness of wall corners (try 0.05 as initial value) +standoff_fraction = 0.2415 # Wall standoff in plasma minor radii (conformal, dee) +half_thickness = 0.05 # Half-thickness parameter for Dee-shaped walls +elongation = 1.5 # Elongation parameter for wall shapes +center_offset = 0 # Offset of wall center from major radius +triangularity = 0.5 # Triangularity parameter for wall shapes +corner_sharpness = 0.05 # Sharpness of wall corners (try 0.05 as initial value) equal_arc_wall = true # Equal arc length distribution of nodes on wall [ForceFreeStates] local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile vac_flag = true # Compute plasma, vacuum, and total energies for free-boundary modes -psiedge = 0.99 # Edge dW(ψ) diagnostic scan band [psiedge, psilim]; set ≥ psilim to disable +dW_edge_scan_start = 0.99 # Edge dW(ψ) diagnostic scan band [dW_edge_scan_start, psilim]; set ≥ psilim to disable qlow = 1.02 # Integration initiated at q determined by min(q0, qlow) qhigh = 1e3 # Integration terminated at q limit determined by min(qa, qhigh) sing_start = 0 # Start integration at the sing_start'th rational from the axis (psilow) @@ -45,19 +45,19 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 1 # Largest toroidal mode number to include delta_mlow = 8 # Expands lower bound of Fourier harmonics delta_mhigh = 8 # Expands upper bound of Fourier harmonics -mthvac = 512 # Number of points used in splines over poloidal angle at the plasma-vacuum interface +mtheta_vacuum = 512 # Number of points used in splines over poloidal angle at the plasma-vacuum interface kinetic_source = "fixed" # Kinetic matrix source: "fixed" test matrices, or "calculated" from the kinetic NTV model kinetic_factor = 0.0 # Scaling of kinetic matrices (0 = ideal path; >0 enables kinetic mode) eulerlagrange_tolerance = 1e-10 # Relative tolerance for ODE integration of Euler-Lagrange equations save_interval = 3 # Save every Nth ODE step (1=all). Always saves near rational surfaces. singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e4 # Column-norm threshold that triggers solution renormalization +renorm_threshold = 1e4 # Column-norm threshold that triggers solution renormalization # Integrator selection and domain truncation (see ForceFreeStatesControl docstring for details) integrator = "forward" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) -set_psilim_via_dmlim = true # Truncate at (last_rational_q + dmlim)/n — TRUE for diverted equilibria (q → ∞ at separatrix) -dmlim = 0.2 # Truncate integration at (last_rational_q + dmlim)/n (used when set_psilim_via_dmlim = true) +truncate_at_rational_offset = true # Truncate at (last_rational_q + rational_offset_fraction)/n — TRUE for diverted equilibria (q → ∞ at separatrix) +rational_offset_fraction = 0.2 # Truncate integration at (last_rational_q + rational_offset_fraction)/n (used when truncate_at_rational_offset = true) [ForcingTerms] forcing_data_format = "coil" # Format: "ascii", "hdf5", or "coil" (Biot-Savart from 3D wires) @@ -202,7 +202,7 @@ compute_response = true # Compute plasma response to forcing compute_singular_coupling = true # Compute singular layer coupling metrics verbose = false # Enable verbose logging write_outputs_to_HDF5 = true # Write perturbed equilibrium outputs to HDF5 -reg_spot = 0.05 # Regularization width for singular surfaces (0 = disabled) +regularization_width = 0.05 # Regularization width for singular surfaces (0 = disabled) [ErrorFields] fd_step_shift_m = 1e-3 # Central-difference step for the rigid shifts [m] @@ -241,16 +241,16 @@ rotation_offset_factor = 2.0 # Rough neoclassical offset as a multipl [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) -nl = 6 # Bounce harmonics retained -zi = 1 # Main-ion charge (deuterium) -mi = 2 # Main-ion mass [proton masses] -zimp = 6 # Impurity charge (carbon) -mimp = 12 # Impurity mass [proton masses] +bounce_harmonic_max = 6 # Bounce harmonics retained +ion_charge = 1 # Main-ion charge (deuterium) +ion_mass = 2 # Main-ion mass [proton masses] +impurity_charge = 6 # Impurity charge (carbon) +impurity_mass = 12 # Impurity mass [proton masses] electron = false # Ion-only NTV nutype = "harmonic" # Collision operator f0type = "maxwellian" # Equilibrium distribution moment = "pressure" # Pressure-moment NTV torque -atol_xlmda = 1e-9 # Absolute tolerance for inner pitch + energy integrations -rtol_xlmda = 1e-5 # Relative tolerance for inner pitch + energy integrations +atol_pitch = 1e-9 # Absolute tolerance for inner pitch + energy integrations +rtol_pitch = 1e-5 # Relative tolerance for inner pitch + energy integrations write_outputs_to_HDF5 = true # Write outputs to the HDF5 file verbose = false # Enable verbose logging diff --git a/examples/DIIID-like_gal_resistive_example/gpec.toml b/examples/DIIID-like_gal_resistive_example/gpec.toml index 51c0c575b..8bced1c04 100644 --- a/examples/DIIID-like_gal_resistive_example/gpec.toml +++ b/examples/DIIID-like_gal_resistive_example/gpec.toml @@ -1,38 +1,38 @@ # DIII-D-like H-mode resistive-MHD example: outer-region singular Galerkin Δ′ solve. # n=1 tearing stability on the bundled geqdsk: the gal solver computes the Δ′ # matching matrix (PEST-3 convention) plus rpec coil-response columns (delta_coil). -# The ldp radial grid packs points toward the rational surfaces; psihigh = 0.993 +# The core_edge_packed radial grid is densest at the axis and edge; psihigh = 0.993 # stays clear of the separatrix. No forcing and no PerturbedEquilibrium stage — # see the sibling DIIID-like_gal_resistive_pe_example for the coil-driven continuation. [Equilibrium] eq_filename = "TkMkr_D3Dlike_Hmode.geqdsk" # Path to equilibrium file eq_type = "efit" # Type of the input 2D equilibrium file jac_type = "hamada" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom) -grid_type = "ldp" # Radial grid packing type (ldp = linear-derivative packing toward rationals) +grid_type = "core_edge_packed" # Radial grid packing type (core_edge_packed = sin² spacing, dense at axis and edge) psilow = 1e-4 # Lower limit of normalized flux coordinate psihigh = 0.993 # Upper limit of normalized flux coordinate (0.993 stays clear of the separatrix; truncating at ≳0.998 is numerically unreasonable here) mpsi = 128 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) psi_accuracy = 0.001 # Target relative accuracy of splined profile derivatives for the auto grid mtheta = 256 # Number of poloidal grid points newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) -etol = 1e-10 # Error tolerance for equilibrium solver +flux_surface_rtol = 1e-10 # Relative tolerance of the ODE solves that trace each flux surface force_termination = false # Terminate after equilibrium setup (skip stability calculations) [Wall] shape = "nowall" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath) -a = 0.2415 # Distance from plasma (conformal) or shape parameter -aw = 0.05 # Half-thickness parameter for Dee-shaped walls -bw = 1.5 # Elongation parameter for wall shapes -cw = 0 # Offset of wall center from major radius -dw = 0.5 # Triangularity parameter for wall shapes -tw = 0.05 # Sharpness of wall corners (try 0.05 as initial value) +standoff_fraction = 0.2415 # Wall standoff in plasma minor radii (conformal, dee) +half_thickness = 0.05 # Half-thickness parameter for Dee-shaped walls +elongation = 1.5 # Elongation parameter for wall shapes +center_offset = 0 # Offset of wall center from major radius +triangularity = 0.5 # Triangularity parameter for wall shapes +corner_sharpness = 0.05 # Sharpness of wall corners (try 0.05 as initial value) equal_arc_wall = true # Equal arc length distribution of nodes on wall [ForceFreeStates] local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile vac_flag = true # Compute plasma, vacuum, and total energies for free-boundary modes -psiedge = 0.99 # Edge dW(ψ) diagnostic scan band [psiedge, psilim]; set ≥ psilim to disable +dW_edge_scan_start = 0.99 # Edge dW(ψ) diagnostic scan band [dW_edge_scan_start, psilim]; set ≥ psilim to disable qlow = 1.02 # Integration initiated at q determined by min(q0, qlow)... qhigh = 1e3 # Integration terminated at q limit determined by min(qa, qhigh)... sing_start = 0 # Start integration at the sing_start'th rational from the axis (psilow) @@ -41,19 +41,19 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 1 # Largest toroidal mode number to include delta_mlow = 8 # Expands lower bound of Fourier harmonics delta_mhigh = 4 # Expands upper bound of Fourier harmonics -mthvac = 512 # Number of points used in splines over poloidal angle at plasma-vacuum interface. +mtheta_vacuum = 512 # Number of points used in splines over poloidal angle at plasma-vacuum interface. kinetic_source = "fixed" # Kinetic matrix source ("fixed" test matrices, or "calculated" kinetic response) kinetic_factor = 0.0 # Scaling of kinetic matrices (0 = ideal path; >0 enables kinetic mode) eulerlagrange_tolerance = 1e-10# Relative tolerance for ODE integration of Euler-Lagrange equations save_interval = 3 # Save every Nth ODE step (1=all, 10=every 10th). Always saves near rational surfaces. singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e4 # Maximum fraction of solutions allowed before re-normalized +renorm_threshold = 1e4 # Column-norm threshold that triggers solution renormalization # Integrator selection and domain truncation (see ForceFreeStatesControl docstring for details) integrator = "galerkin" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) -set_psilim_via_dmlim = false # Keep psilim at psihigh (do not truncate at last_rational_q + dmlim) -dmlim = 0.2 # Truncate integration at (last_rational_q + dmlim) / n (only used when set_psilim_via_dmlim = true) +truncate_at_rational_offset = false # Keep psilim at psihigh (do not truncate at last_rational_q + rational_offset_fraction) +rational_offset_fraction = 0.2 # Truncate integration at (last_rational_q + rational_offset_fraction) / n (only used when truncate_at_rational_offset = true) # Outer-region singular Galerkin Δ′ solver with rpec coil columns. # This case exercises the gal solve AND the rpec coil/edge path (delta_coil) for regression tracking. diff --git a/examples/DIIID-like_gal_resistive_pe_example/gpec.toml b/examples/DIIID-like_gal_resistive_pe_example/gpec.toml index 0f4f237b8..d19fd2187 100644 --- a/examples/DIIID-like_gal_resistive_pe_example/gpec.toml +++ b/examples/DIIID-like_gal_resistive_pe_example/gpec.toml @@ -8,31 +8,31 @@ eq_filename = "TkMkr_D3Dlike_Hmode.geqdsk" # Path to equilibrium file eq_type = "efit" # Type of the input 2D equilibrium file jac_type = "hamada" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom) -grid_type = "ldp" # Radial grid packing type (ldp = linear-derivative packing toward rationals) +grid_type = "core_edge_packed" # Radial grid packing type (core_edge_packed = sin² spacing, dense at axis and edge) psilow = 1e-4 # Lower limit of normalized flux coordinate psihigh = 0.993 # Upper limit of normalized flux coordinate (0.993 stays clear of the separatrix; truncating at ≳0.998 is numerically unreasonable here) mpsi = 128 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) psi_accuracy = 0.001 # Target relative accuracy of splined profile derivatives for the auto grid mtheta = 256 # Number of poloidal grid points newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) -etol = 1e-10 # Error tolerance for equilibrium solver +flux_surface_rtol = 1e-10 # Relative tolerance of the ODE solves that trace each flux surface force_termination = false # Terminate after equilibrium setup (skip stability calculations) [Wall] shape = "nowall" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath) -a = 0.2415 # Distance from plasma (conformal) or shape parameter -aw = 0.05 # Half-thickness parameter for Dee-shaped walls -bw = 1.5 # Elongation parameter for wall shapes -cw = 0 # Offset of wall center from major radius -dw = 0.5 # Triangularity parameter for wall shapes -tw = 0.05 # Sharpness of wall corners (try 0.05 as initial value) +standoff_fraction = 0.2415 # Wall standoff in plasma minor radii (conformal, dee) +half_thickness = 0.05 # Half-thickness parameter for Dee-shaped walls +elongation = 1.5 # Elongation parameter for wall shapes +center_offset = 0 # Offset of wall center from major radius +triangularity = 0.5 # Triangularity parameter for wall shapes +corner_sharpness = 0.05 # Sharpness of wall corners (try 0.05 as initial value) equal_arc_wall = true # Equal arc length distribution of nodes on wall [ForceFreeStates] local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile vac_flag = true # Compute plasma, vacuum, and total energies for free-boundary modes -psiedge = 0.99 # Edge dW(ψ) diagnostic scan band [psiedge, psilim]; set ≥ psilim to disable +dW_edge_scan_start = 0.99 # Edge dW(ψ) diagnostic scan band [dW_edge_scan_start, psilim]; set ≥ psilim to disable qlow = 1.02 # Integration initiated at q determined by min(q0, qlow)... qhigh = 1e3 # Integration terminated at q limit determined by min(qa, qhigh)... sing_start = 0 # Start integration at the sing_start'th rational from the axis (psilow) @@ -41,19 +41,19 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 1 # Largest toroidal mode number to include delta_mlow = 8 # Expands lower bound of Fourier harmonics delta_mhigh = 4 # Expands upper bound of Fourier harmonics -mthvac = 512 # Number of points used in splines over poloidal angle at plasma-vacuum interface. +mtheta_vacuum = 512 # Number of points used in splines over poloidal angle at plasma-vacuum interface. kinetic_source = "fixed" # Kinetic matrix source ("fixed" test matrices, or "calculated" kinetic response) kinetic_factor = 0.0 # Scaling of kinetic matrices (0 = ideal path; >0 enables kinetic mode) eulerlagrange_tolerance = 1e-10# Relative tolerance for ODE integration of Euler-Lagrange equations save_interval = 3 # Save every Nth ODE step (1=all, 10=every 10th). Always saves near rational surfaces. singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e4 # Maximum fraction of solutions allowed before re-normalized +renorm_threshold = 1e4 # Column-norm threshold that triggers solution renormalization # Integrator selection and domain truncation (see ForceFreeStatesControl docstring for details) integrator = "galerkin" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) -set_psilim_via_dmlim = false # Keep psilim at psihigh (do not truncate at last_rational_q + dmlim) -dmlim = 0.2 # Truncate integration at (last_rational_q + dmlim) / n (only used when set_psilim_via_dmlim = true) +truncate_at_rational_offset = false # Keep psilim at psihigh (do not truncate at last_rational_q + rational_offset_fraction) +rational_offset_fraction = 0.2 # Truncate integration at (last_rational_q + rational_offset_fraction) / n (only used when truncate_at_rational_offset = true) # Outer-region singular Galerkin Δ′ solver with rpec coil columns. # This case exercises the gal solve AND the rpec coil/edge path (delta_coil) for regression tracking. @@ -98,4 +98,4 @@ compute_response = true # Compute plasma response to forcing compute_singular_coupling = true # Compute singular layer coupling metrics verbose = true # Enable verbose logging write_outputs_to_HDF5 = true # Write perturbed equilibrium outputs to HDF5 -reg_spot = 0.05 # Regularization width for singular surfaces (0 = disabled) +regularization_width = 0.05 # Regularization width for singular surfaces (0 = disabled) diff --git a/examples/DIIID-like_ideal_example/gpec.toml b/examples/DIIID-like_ideal_example/gpec.toml index a00db6260..a4cc41ea4 100644 --- a/examples/DIIID-like_ideal_example/gpec.toml +++ b/examples/DIIID-like_ideal_example/gpec.toml @@ -14,24 +14,24 @@ mpsi = 0 # Number of radial grid intervals (0 = two-pass a psi_accuracy = 0.001 # Target relative accuracy of splined profile derivatives for the auto grid mtheta = 256 # Number of poloidal grid points newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) -etol = 1e-10 # Error tolerance for equilibrium solver +flux_surface_rtol = 1e-10 # Relative tolerance of the ODE solves that trace each flux surface force_termination = false # Terminate after equilibrium setup (skip stability calculations) [Wall] shape = "nowall" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath) -a = 0.2415 # Distance from plasma (conformal) or shape parameter -aw = 0.05 # Half-thickness parameter for Dee-shaped walls -bw = 1.5 # Elongation parameter for wall shapes -cw = 0 # Offset of wall center from major radius -dw = 0.5 # Triangularity parameter for wall shapes -tw = 0.05 # Sharpness of wall corners (try 0.05 as initial value) +standoff_fraction = 0.2415 # Wall standoff in plasma minor radii (conformal, dee) +half_thickness = 0.05 # Half-thickness parameter for Dee-shaped walls +elongation = 1.5 # Elongation parameter for wall shapes +center_offset = 0 # Offset of wall center from major radius +triangularity = 0.5 # Triangularity parameter for wall shapes +corner_sharpness = 0.05 # Sharpness of wall corners (try 0.05 as initial value) equal_arc_wall = true # Equal arc length distribution of nodes on wall [ForceFreeStates] local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile vac_flag = true # Compute plasma, vacuum, and total energies for free-boundary modes -psiedge = 0.99 # Edge dW(ψ) diagnostic scan band [psiedge, psilim]; set ≥ psilim to disable +dW_edge_scan_start = 0.99 # Edge dW(ψ) diagnostic scan band [dW_edge_scan_start, psilim]; set ≥ psilim to disable qlow = 1.02 # Integration initiated at q determined by min(q0, qlow) qhigh = 1e3 # Integration terminated at q limit determined by min(qa, qhigh) sing_start = 0 # Start integration at the sing_start'th rational from the axis (psilow) @@ -40,19 +40,19 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 1 # Largest toroidal mode number to include delta_mlow = 8 # Expands lower bound of Fourier harmonics delta_mhigh = 8 # Expands upper bound of Fourier harmonics -mthvac = 512 # Number of points used in splines over poloidal angle at the plasma-vacuum interface +mtheta_vacuum = 512 # Number of points used in splines over poloidal angle at the plasma-vacuum interface kinetic_source = "fixed" # Kinetic matrix source: "fixed" test matrices, or "calculated" from the kinetic NTV model kinetic_factor = 0.0 # Scaling of kinetic matrices (0 = ideal path; >0 enables kinetic mode) eulerlagrange_tolerance = 1e-10 # Relative tolerance for ODE integration of Euler-Lagrange equations save_interval = 3 # Save every Nth ODE step (1=all). Always saves near rational surfaces. singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e4 # Column-norm threshold that triggers solution renormalization +renorm_threshold = 1e4 # Column-norm threshold that triggers solution renormalization # Integrator selection and domain truncation (see ForceFreeStatesControl docstring for details) integrator = "forward" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) -set_psilim_via_dmlim = true # Truncate at (last_rational_q + dmlim)/n — TRUE for diverted equilibria (q → ∞ at separatrix) -dmlim = 0.2 # Truncate integration at (last_rational_q + dmlim)/n (used when set_psilim_via_dmlim = true) +truncate_at_rational_offset = true # Truncate at (last_rational_q + rational_offset_fraction)/n — TRUE for diverted equilibria (q → ∞ at separatrix) +rational_offset_fraction = 0.2 # Truncate integration at (last_rational_q + rational_offset_fraction)/n (used when truncate_at_rational_offset = true) [ForcingTerms] forcing_data_format = "coil" # Format: "ascii", "hdf5", or "coil" (Biot-Savart from 3D wires) @@ -71,7 +71,7 @@ compute_response = true # Compute plasma response to forcing compute_singular_coupling = true # Compute singular layer coupling metrics verbose = true # Enable verbose logging write_outputs_to_HDF5 = true # Write perturbed equilibrium outputs to HDF5 -reg_spot = 0.05 # Regularization width for singular surfaces (0 = disabled) +regularization_width = 0.05 # Regularization width for singular surfaces (0 = disabled) [ErrorFields] fd_step_shift_m = 1e-3 # Central-difference step for the rigid shifts [m] @@ -83,16 +83,16 @@ verbose = true # Log per-coil-set progress and linearit [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_phi). Legacy .gpeckf/.kin still readable. nn = 1 # Toroidal mode number (matches nn_low/nn_high) -nl = 6 # Bounce harmonics retained -zi = 1 # Main-ion charge (deuterium) -mi = 2 # Main-ion mass [proton masses] -zimp = 6 # Impurity charge (carbon) -mimp = 12 # Impurity mass [proton masses] +bounce_harmonic_max = 6 # Bounce harmonics retained +ion_charge = 1 # Main-ion charge (deuterium) +ion_mass = 2 # Main-ion mass [proton masses] +impurity_charge = 6 # Impurity charge (carbon) +impurity_mass = 12 # Impurity mass [proton masses] electron = false # Ion-only NTV nutype = "harmonic" # Collision operator f0type = "maxwellian" # Equilibrium distribution moment = "pressure" # Pressure-moment NTV torque -atol_xlmda = 1e-9 # Absolute tolerance for inner pitch + energy integrations -rtol_xlmda = 1e-5 # Relative tolerance for inner pitch + energy integrations +atol_pitch = 1e-9 # Absolute tolerance for inner pitch + energy integrations +rtol_pitch = 1e-5 # Relative tolerance for inner pitch + energy integrations write_outputs_to_HDF5 = true # Write outputs to the HDF5 file verbose = true # Enable verbose logging diff --git a/examples/DIIID-like_ideal_multi_n_example/gpec.toml b/examples/DIIID-like_ideal_multi_n_example/gpec.toml index 507e733ad..aa5a5ca62 100644 --- a/examples/DIIID-like_ideal_multi_n_example/gpec.toml +++ b/examples/DIIID-like_ideal_multi_n_example/gpec.toml @@ -12,30 +12,30 @@ eq_filename = "../DIIID-like_ideal_example/TkMkr_D3Dlike_Hmode.geqdsk" # Path to equilibrium file eq_type = "efit" # Type of the input 2D equilibrium file jac_type = "hamada" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom) -grid_type = "ldp" # Radial grid packing type (ldp = linear-derivative packing toward rationals) +grid_type = "core_edge_packed" # Radial grid packing type (core_edge_packed = sin² spacing, dense at axis and edge) psilow = 1e-4 # Lower limit of normalized poloidal flux psihigh = 0.9 # Upper limit of normalized poloidal flux mpsi = 128 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) mtheta = 256 # Number of poloidal grid points newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) -etol = 1e-10 # Error tolerance for equilibrium solver +flux_surface_rtol = 1e-10 # Relative tolerance of the ODE solves that trace each flux surface force_termination = false # Terminate after equilibrium setup (skip stability calculations) [Wall] shape = "nowall" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath) -a = 0.2415 # Distance from plasma (conformal) or shape parameter -aw = 0.05 # Half-thickness parameter for Dee-shaped walls -bw = 1.5 # Elongation parameter for wall shapes -cw = 0 # Offset of wall center from major radius -dw = 0.5 # Triangularity parameter for wall shapes -tw = 0.05 # Sharpness of wall corners (try 0.05 as initial value) +standoff_fraction = 0.2415 # Wall standoff in plasma minor radii (conformal, dee) +half_thickness = 0.05 # Half-thickness parameter for Dee-shaped walls +elongation = 1.5 # Elongation parameter for wall shapes +center_offset = 0 # Offset of wall center from major radius +triangularity = 0.5 # Triangularity parameter for wall shapes +corner_sharpness = 0.05 # Sharpness of wall corners (try 0.05 as initial value) equal_arc_wall = true # Equal arc length distribution of nodes on wall [ForceFreeStates] local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile vac_flag = true # Compute plasma, vacuum, and total energies for free-boundary modes -psiedge = 0.99 # Edge dW(ψ) diagnostic scan band [psiedge, psilim]; set ≥ psilim to disable +dW_edge_scan_start = 0.99 # Edge dW(ψ) diagnostic scan band [dW_edge_scan_start, psilim]; set ≥ psilim to disable qlow = 1.02 # Integration initiated at q determined by min(q0, qlow) qhigh = 3.83 # Integration terminated at q limit determined by min(qa, qhigh) sing_start = 0 # Start integration at the sing_start'th rational from the axis (psilow) @@ -45,19 +45,19 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 2 # Largest toroidal mode number to include delta_mlow = 4 # Expands lower bound of Fourier harmonics delta_mhigh = 6 # Expands upper bound of Fourier harmonics -mthvac = 512 # Number of points used in splines over poloidal angle at the plasma-vacuum interface +mtheta_vacuum = 512 # Number of points used in splines over poloidal angle at the plasma-vacuum interface kinetic_source = "fixed" # Kinetic matrix source: "fixed" test matrices, or "calculated" from the kinetic NTV model kinetic_factor = 0.0 # Scaling of kinetic matrices (0 = ideal path; >0 enables kinetic mode) eulerlagrange_tolerance = 1e-10 # Relative tolerance for ODE integration of Euler-Lagrange equations save_interval = 3 # Save every Nth ODE step (1=all). Always saves near rational surfaces. singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e4 # Column-norm threshold that triggers solution renormalization +renorm_threshold = 1e4 # Column-norm threshold that triggers solution renormalization # Integrator selection and domain truncation (see ForceFreeStatesControl docstring for details) integrator = "forward" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) -set_psilim_via_dmlim = false # FALSE for multi-n — dmlim truncation is ambiguous when n varies; qhigh sets the edge instead -dmlim = 0.2 # Truncate integration at (last_rational_q + dmlim)/n (used when set_psilim_via_dmlim = true) +truncate_at_rational_offset = false # FALSE for multi-n — rational_offset_fraction truncation is ambiguous when n varies; qhigh sets the edge instead +rational_offset_fraction = 0.2 # Truncate integration at (last_rational_q + rational_offset_fraction)/n (used when truncate_at_rational_offset = true) [ForcingTerms] forcing_data_format = "coil" # Format: "ascii", "hdf5", or "coil" (Biot-Savart from 3D wires) @@ -76,4 +76,4 @@ compute_response = true # Compute plasma response to forcing compute_singular_coupling = true # Compute singular layer coupling metrics verbose = false # Enable verbose logging write_outputs_to_HDF5 = true # Write perturbed equilibrium outputs to HDF5 -reg_spot = 0.05 # Regularization width for singular surfaces (0 = disabled) +regularization_width = 0.05 # Regularization width for singular surfaces (0 = disabled) diff --git a/examples/DIIID-like_riccati_deltaprime_example/gpec.toml b/examples/DIIID-like_riccati_deltaprime_example/gpec.toml index f046f380b..30acce8be 100644 --- a/examples/DIIID-like_riccati_deltaprime_example/gpec.toml +++ b/examples/DIIID-like_riccati_deltaprime_example/gpec.toml @@ -15,17 +15,17 @@ mpsi = 0 # Number of radial grid intervals (0 = two-pass a psi_accuracy = 0.001 # Target relative accuracy of splined profile derivatives for the auto grid mtheta = 256 # Number of poloidal grid points newq0 = 0 # Override for on-axis safety factor (0 = use input value) -etol = 1e-10 # Error tolerance for equilibrium solver +flux_surface_rtol = 1e-10 # Relative tolerance of the ODE solves that trace each flux surface force_termination = false # Terminate after equilibrium setup (skip stability calculations) [Wall] shape = "nowall" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath) -a = 0.2415 # Distance from plasma (conformal) or shape parameter -aw = 0.05 # Half-thickness parameter for Dee-shaped walls -bw = 1.5 # Elongation parameter for wall shapes -cw = 0 # Offset of wall center from major radius -dw = 0.5 # Triangularity parameter for wall shapes -tw = 0.05 # Sharpness of wall corners (try 0.05 as initial value) +standoff_fraction = 0.2415 # Wall standoff in plasma minor radii (conformal, dee) +half_thickness = 0.05 # Half-thickness parameter for Dee-shaped walls +elongation = 1.5 # Elongation parameter for wall shapes +center_offset = 0 # Offset of wall center from major radius +triangularity = 0.5 # Triangularity parameter for wall shapes +corner_sharpness = 0.05 # Sharpness of wall corners (try 0.05 as initial value) equal_arc_wall = true # Equal arc length distribution of nodes on wall [ForceFreeStates] @@ -33,7 +33,7 @@ local_stability_flag = true # Perform local stability analysis (Mercier and b vac_flag = true # Compute plasma, vacuum, and total energies for free-boundary modes force_termination = true # Terminate after force-free states (skip perturbed equilibrium calculations) -psiedge = 0.99 # Edge dW(ψ) diagnostic scan band [psiedge, psilim]; set ≥ psilim to disable +dW_edge_scan_start = 0.99 # Edge dW(ψ) diagnostic scan band [dW_edge_scan_start, psilim]; set ≥ psilim to disable qlow = 1.02 # Integration initiated at q determined by min(q0, qlow) qhigh = 1e3 # Integration terminated at q limit determined by min(qa, qhigh) sing_start = 0 # Start integration at the sing_start'th rational from the axis (psilow) @@ -42,16 +42,16 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 1 # Largest toroidal mode number to include delta_mlow = 8 # Expands lower bound of Fourier harmonics delta_mhigh = 8 # Expands upper bound of Fourier harmonics -mthvac = 512 # Number of points used in splines over poloidal angle at the plasma-vacuum interface +mtheta_vacuum = 512 # Number of points used in splines over poloidal angle at the plasma-vacuum interface kinetic_source = "fixed" # Kinetic matrix source: "fixed" test matrices, or "calculated" from the kinetic NTV model kinetic_factor = 0.0 # Scaling of kinetic matrices (0 = ideal path; >0 enables kinetic mode) eulerlagrange_tolerance = 1e-10 # Relative tolerance for ODE integration of Euler-Lagrange equations save_interval = 3 # Save every Nth ODE step (1=all). Always saves near rational surfaces. singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e4 # Column-norm threshold that triggers solution renormalization +renorm_threshold = 1e4 # Column-norm threshold that triggers solution renormalization # Integrator selection and domain truncation (see ForceFreeStatesControl docstring for details) integrator = "riccati" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) -set_psilim_via_dmlim = true # Truncate at (last_rational_q + dmlim)/n — TRUE for diverted equilibria (q → ∞ at separatrix) -dmlim = 0.2 # Truncate integration at (last_rational_q + dmlim)/n (used when set_psilim_via_dmlim = true) +truncate_at_rational_offset = true # Truncate at (last_rational_q + rational_offset_fraction)/n — TRUE for diverted equilibria (q → ∞ at separatrix) +rational_offset_fraction = 0.2 # Truncate integration at (last_rational_q + rational_offset_fraction)/n (used when truncate_at_rational_offset = true) diff --git a/examples/LAR_beta_scan/gpec.toml b/examples/LAR_beta_scan/gpec.toml index cf26ed99c..6360f8b8e 100644 --- a/examples/LAR_beta_scan/gpec.toml +++ b/examples/LAR_beta_scan/gpec.toml @@ -10,7 +10,7 @@ [Equilibrium] eq_type = "tj_analytic" # TJ-analytic model (inverse pipeline; Fitzpatrick https://github.com/rfitzp/TJ) jac_type = "hamada" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom) -grid_type = "ldp" # Radial grid packing type +grid_type = "core_edge_packed" # Radial grid packing type psilow = 0.01 # Lower limit of normalized poloidal flux psihigh = 0.995 # Upper limit of normalized poloidal flux mpsi = 128 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) @@ -19,19 +19,19 @@ mtheta = 512 # Number of poloidal grid points # TJ-analytic equilibrium parameters (cf. R. Fitzpatrick, TJ code, https://github.com/rfitzp/TJ). # Geometry is FIXED at ε = a / R₀ = 0.4 / 2.0 = 0.2; run_scan.jl varies only `pc` per point. [TJ_ANALYTIC_INPUT] -lar_r0 = 2.0 # Major radius R₀ [m] (centerline radius of the magnetic axis) -lar_a = 0.4 # Minor radius a [m] (plasma half-width at the midplane; here ε = 0.2) -qc = 1.5 # On-axis safety factor q(r=0) -qa = 3.6 # Edge safety factor q(r=a) -pc = 0.001 # Normalized on-axis pressure (β-scan parameter; OVERRIDDEN per run by run_scan.jl) -mu = 2.0 # Pressure-profile peaking exponent in p₂(r) = pc·(1−r²)^μ -B0 = 12.0 # On-axis toroidal field [T] -ma = 128 # TJ-analytic internal radial grid resolution (shape-ODE nodes) -mtau = 128 # TJ-analytic internal poloidal grid resolution (θ-spline nodes) +r0 = 2.0 # Major radius R₀ [m] (centerline radius of the magnetic axis) +a = 0.4 # Minor radius a [m] (plasma half-width at the midplane; here ε = 0.2) +q0 = 1.5 # On-axis safety factor q(r=0) +qa = 3.6 # Edge safety factor q(r=a) +p0 = 0.001 # Normalized on-axis pressure (β-scan parameter; OVERRIDDEN per run by run_scan.jl) +pressure_peaking = 2.0 # Pressure-profile peaking exponent in p₂(r) = p0·(1−r²)^μ +B0 = 12.0 # On-axis toroidal field [T] +ma = 128 # TJ-analytic internal radial grid resolution (shape-ODE nodes) +mtheta = 128 # TJ-analytic internal poloidal grid resolution (θ-spline nodes) [Wall] shape = "conformal" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath) -a = 20 # Distance from plasma (conformal) or shape parameter (20× → effectively no wall) +standoff_fraction = 20 # Wall standoff in plasma minor radii (conformal, dee) (20× → effectively no wall) [ForceFreeStates] local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile @@ -45,16 +45,16 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 1 # Largest toroidal mode number to include delta_mlow = 8 # Expands lower bound of Fourier harmonics delta_mhigh = 8 # Expands upper bound of Fourier harmonics -mthvac = 960 # Number of points used in splines over poloidal angle at the plasma-vacuum interface +mtheta_vacuum = 960 # Number of points used in splines over poloidal angle at the plasma-vacuum interface eulerlagrange_tolerance = 1e-12 # Relative tolerance for ODE integration of Euler-Lagrange equations singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e4 # Column-norm threshold that triggers solution renormalization -sing_order = 6 # Order of the singular-surface (Frobenius) series expansion +renorm_threshold = 1e4 # Column-norm threshold that triggers solution renormalization +frobenius_order = 6 # Order of the singular-surface (Frobenius) series expansion integrator = "riccati" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) -set_psilim_via_dmlim = false # FALSE for limited/analytical equilibria — rationals sparse, dmlim would chop too much edge -dmlim = 0.2 # Truncate integration at (last_rational_q + dmlim)/n (used when set_psilim_via_dmlim = true) +truncate_at_rational_offset = false # FALSE for limited/analytical equilibria — rationals sparse, rational_offset_fraction would chop too much edge +rational_offset_fraction = 0.2 # Truncate integration at (last_rational_q + rational_offset_fraction)/n (used when truncate_at_rational_offset = true) force_termination = true # Stop after force-free states (skip perturbed equilibrium) write_outputs_to_HDF5 = true # Write outputs to the HDF5 file HDF5_filename = "gpec.h5" # Output HDF5 filename (run_scan.jl overrides this per scan point) diff --git a/examples/LAR_beta_scan/run_scan.jl b/examples/LAR_beta_scan/run_scan.jl index 6249ff30d..79f251059 100644 --- a/examples/LAR_beta_scan/run_scan.jl +++ b/examples/LAR_beta_scan/run_scan.jl @@ -43,7 +43,7 @@ const OUTPUT_H5 = joinpath(SCAN_DIR, "beta_scan.h5") # All baseline parameters (Equilibrium, TJ_ANALYTIC_INPUT, Wall, ForceFreeStates) # live in gpec.toml next to this script — there is no side-car TOML file. -# The scan below reads gpec.toml once and overrides ONLY `TJ_ANALYTIC_INPUT.pc` +# The scan below reads gpec.toml once and overrides ONLY `TJ_ANALYTIC_INPUT.p0` # per scan point before writing the per-point gpec.toml into a tempdir. const GPEC_BASE = TOML.parsefile(joinpath(SCAN_DIR, "gpec.toml")) @@ -54,9 +54,9 @@ const GPEC_BASE = TOML.parsefile(joinpath(SCAN_DIR, "gpec.toml")) function run_single(pc::Float64) run_dir = mktempdir(; prefix="gpec_tj_analytic_beta_") try - # Per-point gpec.toml = baseline gpec.toml with TJ_ANALYTIC_INPUT.pc overridden. + # Per-point gpec.toml = baseline gpec.toml with TJ_ANALYTIC_INPUT.p0 overridden. config = deepcopy(GPEC_BASE) - config["TJ_ANALYTIC_INPUT"]["pc"] = pc + config["TJ_ANALYTIC_INPUT"]["p0"] = pc config["ForceFreeStates"]["HDF5_filename"] = joinpath(run_dir, "gpec.h5") open(joinpath(run_dir, "gpec.toml"), "w") do io; TOML.print(io, config); end @@ -103,7 +103,7 @@ function main() pcs = test_mode ? PC_TEST : PC_FULL tj = GPEC_BASE["TJ_ANALYTIC_INPUT"] - @info "TJ-analytic β scan: $(length(pcs)) points, ε=$(tj["lar_a"]/tj["lar_r0"]), B0=$(tj["B0"])T, qc=$(tj["qc"]), qa=$(tj["qa"])" * + @info "TJ-analytic β scan: $(length(pcs)) points, ε=$(tj["a"]/tj["r0"]), B0=$(tj["B0"])T, q0=$(tj["q0"]), qa=$(tj["qa"])" * (test_mode ? " (test mode)" : "") isfile(OUTPUT_H5) && rm(OUTPUT_H5) diff --git a/examples/LAR_epsilon_scan/gpec.toml b/examples/LAR_epsilon_scan/gpec.toml index b1d006b46..b5dfd4e1b 100644 --- a/examples/LAR_epsilon_scan/gpec.toml +++ b/examples/LAR_epsilon_scan/gpec.toml @@ -10,7 +10,7 @@ [Equilibrium] eq_type = "tj_analytic" # TJ-analytic model (inverse pipeline; overridden to "tj_analytic_direct" by run_scan.jl) jac_type = "hamada" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom) -grid_type = "ldp" # Radial grid packing type +grid_type = "core_edge_packed" # Radial grid packing type psilow = 0.01 # Lower limit of normalized poloidal flux psihigh = 0.995 # Upper limit of normalized poloidal flux mpsi = 128 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) @@ -20,19 +20,19 @@ mtheta = 512 # Number of poloidal grid points # All parameters held FIXED except lar_r0, which run_scan.jl overrides as lar_r0 = lar_a / ε; # lar_a stays fixed at 1 m so each scan point is a self-similar rescaling of the geometry. [TJ_ANALYTIC_INPUT] -lar_r0 = 5.0 # Major radius R₀ [m] (baseline ε = 0.2; OVERRIDDEN per scan point by run_scan.jl) -lar_a = 1.0 # Minor radius a [m] (plasma half-width at the midplane; fixed across the scan) -qc = 1.5 # On-axis safety factor q(r=0) -qa = 3.6 # Edge safety factor q(r=a) -pc = 0.001 # Normalized on-axis pressure (kept low for the ε scan to isolate geometry effects) -mu = 2.0 # Pressure-profile peaking exponent in p₂(r) = pc·(1−r²)^μ -B0 = 12.0 # On-axis toroidal field [T] -ma = 128 # TJ-analytic internal radial grid resolution (shape-ODE nodes) -mtau = 128 # TJ-analytic internal poloidal grid resolution (θ-spline nodes) +r0 = 5.0 # Major radius R₀ [m] (baseline ε = 0.2; OVERRIDDEN per scan point by run_scan.jl) +a = 1.0 # Minor radius a [m] (plasma half-width at the midplane; fixed across the scan) +q0 = 1.5 # On-axis safety factor q(r=0) +qa = 3.6 # Edge safety factor q(r=a) +p0 = 0.001 # Normalized on-axis pressure (kept low for the ε scan to isolate geometry effects) +pressure_peaking = 2.0 # Pressure-profile peaking exponent in p₂(r) = p0·(1−r²)^μ +B0 = 12.0 # On-axis toroidal field [T] +ma = 128 # TJ-analytic internal radial grid resolution (shape-ODE nodes) +mtheta = 128 # TJ-analytic internal poloidal grid resolution (θ-spline nodes) [Wall] shape = "conformal" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath) -a = 20 # Distance from plasma (conformal) or shape parameter (20× → effectively no wall) +standoff_fraction = 20 # Wall standoff in plasma minor radii (conformal, dee) (20× → effectively no wall) [ForceFreeStates] local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile @@ -46,16 +46,16 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 1 # Largest toroidal mode number to include delta_mlow = 8 # Expands lower bound of Fourier harmonics delta_mhigh = 8 # Expands upper bound of Fourier harmonics -mthvac = 960 # Number of points used in splines over poloidal angle at the plasma-vacuum interface +mtheta_vacuum = 960 # Number of points used in splines over poloidal angle at the plasma-vacuum interface eulerlagrange_tolerance = 1e-12 # Relative tolerance for ODE integration of Euler-Lagrange equations singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e4 # Column-norm threshold that triggers solution renormalization -sing_order = 6 # Order of the singular-surface (Frobenius) series expansion +renorm_threshold = 1e4 # Column-norm threshold that triggers solution renormalization +frobenius_order = 6 # Order of the singular-surface (Frobenius) series expansion integrator = "riccati" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) -set_psilim_via_dmlim = false # FALSE for limited/analytical equilibria — rationals sparse, dmlim would chop too much edge -dmlim = 0.2 # Truncate integration at (last_rational_q + dmlim)/n (used when set_psilim_via_dmlim = true) +truncate_at_rational_offset = false # FALSE for limited/analytical equilibria — rationals sparse, rational_offset_fraction would chop too much edge +rational_offset_fraction = 0.2 # Truncate integration at (last_rational_q + rational_offset_fraction)/n (used when truncate_at_rational_offset = true) force_termination = true # Stop after force-free states (skip perturbed equilibrium) write_outputs_to_HDF5 = true # Write outputs to the HDF5 file HDF5_filename = "gpec.h5" # Output HDF5 filename (run_scan.jl overrides this per scan point) diff --git a/examples/LAR_epsilon_scan/run_scan.jl b/examples/LAR_epsilon_scan/run_scan.jl index e3327ab95..2e6a156ea 100644 --- a/examples/LAR_epsilon_scan/run_scan.jl +++ b/examples/LAR_epsilon_scan/run_scan.jl @@ -44,7 +44,7 @@ const OUTPUT_H5 = joinpath(SCAN_DIR, "epsilon_scan.h5") # All baseline parameters (Equilibrium, TJ_ANALYTIC_INPUT, Wall, ForceFreeStates) # live in gpec.toml next to this script — there is no side-car TOML file. # The scan below reads gpec.toml once and overrides ONLY -# `TJ_ANALYTIC_INPUT.lar_r0` per scan point as `lar_r0 = lar_a / ε` before +# `TJ_ANALYTIC_INPUT.r0` per scan point as `r0 = a / ε` before # writing the per-point gpec.toml into a tempdir. const GPEC_BASE = TOML.parsefile(joinpath(SCAN_DIR, "gpec.toml")) @@ -55,14 +55,14 @@ const GPEC_BASE = TOML.parsefile(joinpath(SCAN_DIR, "gpec.toml")) function run_single(epsilon::Float64) run_dir = mktempdir(; prefix="gpec_tj_analytic_") try - # Per-point gpec.toml = baseline gpec.toml with TJ_ANALYTIC_INPUT.lar_r0 + # Per-point gpec.toml = baseline gpec.toml with TJ_ANALYTIC_INPUT.r0 # overridden. Switch eq_type to "tj_analytic_direct" so ψ(R, Z) is built # from the TJ-analytic model and processed by the direct-GS # pipeline. Required to capture the ideal external-kink pole (δW_t → # 0 as ε → ε_crit); the inverse path bypasses the line-integrated q # and shows no such pole. config = deepcopy(GPEC_BASE) - config["TJ_ANALYTIC_INPUT"]["lar_r0"] = GPEC_BASE["TJ_ANALYTIC_INPUT"]["lar_a"] / epsilon + config["TJ_ANALYTIC_INPUT"]["r0"] = GPEC_BASE["TJ_ANALYTIC_INPUT"]["a"] / epsilon config["Equilibrium"]["eq_type"] = "tj_analytic_direct" config["ForceFreeStates"]["HDF5_filename"] = joinpath(run_dir, "gpec.h5") open(joinpath(run_dir, "gpec.toml"), "w") do io; TOML.print(io, config); end @@ -110,12 +110,12 @@ function main() epsilons = test_mode ? EPSILONS_TEST : EPSILONS_FULL tj = GPEC_BASE["TJ_ANALYTIC_INPUT"] - @info "TJ-analytic ε scan: $(length(epsilons)) points, B0=$(tj["B0"])T, qc=$(tj["qc"]), qa=$(tj["qa"]), pc=$(tj["pc"])" * + @info "TJ-analytic ε scan: $(length(epsilons)) points, B0=$(tj["B0"])T, q0=$(tj["q0"]), qa=$(tj["qa"]), p0=$(tj["p0"])" * (test_mode ? " (test mode)" : "") isfile(OUTPUT_H5) && rm(OUTPUT_H5) - lar_a = GPEC_BASE["TJ_ANALYTIC_INPUT"]["lar_a"] + lar_a = GPEC_BASE["TJ_ANALYTIC_INPUT"]["a"] for (i, eps) in enumerate(epsilons) @info "[$(i)/$(length(epsilons))] ε=$eps (R0=$(@sprintf("%.3f", lar_a/eps)))" result = run_single(eps) diff --git a/examples/LAR_ideal_match_test/gpec.toml b/examples/LAR_ideal_match_test/gpec.toml index 2bcc0a211..ee2d65369 100644 --- a/examples/LAR_ideal_match_test/gpec.toml +++ b/examples/LAR_ideal_match_test/gpec.toml @@ -9,26 +9,26 @@ [Equilibrium] eq_type = "tj_analytic" # Type of the input 2D equilibrium (analytic large-aspect-ratio model) jac_type = "hamada" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom) -grid_type = "ldp" # Radial grid packing type (ldp = linear-derivative packing toward rationals) +grid_type = "core_edge_packed" # Radial grid packing type (core_edge_packed = sin² spacing, dense at axis and edge) psilow = 0.01 # Lower limit of normalized flux coordinate psihigh = 0.995 # Upper limit of normalized flux coordinate mpsi = 128 # Number of radial grid points (0 = auto-compute from psi_accuracy) mtheta = 512 # Number of poloidal grid points [TJ_ANALYTIC_INPUT] -lar_r0 = 2.0 # Major radius R0 [m] -lar_a = 0.4 # Minor radius a [m] (epsilon = a/R0 = 0.2) -qc = 1.5 # On-axis safety factor -qa = 3.6 # Edge safety factor -pc = 0.01 # Normalized on-axis pressure -mu = 2.0 # Pressure peaking exponent: p2 = pc*(1-r^2)^mu -B0 = 12.0 # On-axis toroidal field [T] -ma = 128 # Radial grid points -mtau = 128 # Poloidal grid points +r0 = 2.0 # Major radius R0 [m] +a = 0.4 # Minor radius a [m] (epsilon = a/R0 = 0.2) +q0 = 1.5 # On-axis safety factor +qa = 3.6 # Edge safety factor +p0 = 0.01 # Normalized on-axis pressure +pressure_peaking = 2.0 # Pressure peaking exponent: p2 = p0*(1-r^2)^pressure_peaking +B0 = 12.0 # On-axis toroidal field [T] +ma = 128 # Radial grid points +mtheta = 128 # Poloidal grid points [Wall] shape = "conformal" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath) -a = 20 # Distance from plasma (conformal) or shape parameter +standoff_fraction = 20 # Wall standoff in plasma minor radii (conformal, dee) [ForceFreeStates] HDF5_filename = "gpec_lar_ideal.h5" # Output filename for the stability-stage results @@ -42,17 +42,17 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 1 # Largest toroidal mode number to include delta_mlow = 8 # Expands lower bound of Fourier harmonics delta_mhigh = 8 # Expands upper bound of Fourier harmonics -mthvac = 960 # Number of points used in splines over poloidal angle at plasma-vacuum interface +mtheta_vacuum = 960 # Number of points used in splines over poloidal angle at plasma-vacuum interface eulerlagrange_tolerance = 1e-12 # Relative tolerance for ODE integration of Euler-Lagrange equations singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e4 # Maximum fraction of solutions allowed before re-normalized -sing_order = 6 # Power-series order for the singular-surface asymptotics +renorm_threshold = 1e4 # Column-norm threshold that triggers solution renormalization +frobenius_order = 6 # Power-series order for the singular-surface asymptotics save_interval = 3 # Save every Nth ODE step (1=all, 10=every 10th). Always saves near rational surfaces. integrator = "galerkin" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) -set_psilim_via_dmlim = false # Keep psilim at psihigh (do not truncate at last_rational_q + dmlim) -dmlim = 0.2 # Truncate integration at (last_rational_q + dmlim) / n (only used when set_psilim_via_dmlim = true) +truncate_at_rational_offset = false # Keep psilim at psihigh (do not truncate at last_rational_q + rational_offset_fraction) +rational_offset_fraction = 0.2 # Truncate integration at (last_rational_q + rational_offset_fraction) / n (only used when truncate_at_rational_offset = true) force_termination = true # Terminate after the stability stage (no perturbed-equilibrium section here) write_outputs_to_HDF5 = true # Write stability outputs to HDF5 diff --git a/examples/LAR_resistive_match_test/gpec.toml b/examples/LAR_resistive_match_test/gpec.toml index 635dae335..25b763acb 100644 --- a/examples/LAR_resistive_match_test/gpec.toml +++ b/examples/LAR_resistive_match_test/gpec.toml @@ -10,26 +10,26 @@ [Equilibrium] eq_type = "tj_analytic" # Type of the input 2D equilibrium (analytic large-aspect-ratio model) jac_type = "hamada" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom) -grid_type = "ldp" # Radial grid packing type (ldp = linear-derivative packing toward rationals) +grid_type = "core_edge_packed" # Radial grid packing type (core_edge_packed = sin² spacing, dense at axis and edge) psilow = 0.01 # Lower limit of normalized flux coordinate psihigh = 0.995 # Upper limit of normalized flux coordinate mpsi = 128 # Number of radial grid points (0 = auto-compute from psi_accuracy) mtheta = 512 # Number of poloidal grid points [TJ_ANALYTIC_INPUT] -lar_r0 = 2.0 # Major radius R0 [m] -lar_a = 0.4 # Minor radius a [m] (epsilon = a/R0 = 0.2) -qc = 1.5 # On-axis safety factor -qa = 3.6 # Edge safety factor -pc = 0.01 # Normalized on-axis pressure -mu = 2.0 # Pressure peaking exponent: p2 = pc*(1-r^2)^mu -B0 = 12.0 # On-axis toroidal field [T] -ma = 128 # Radial grid points -mtau = 128 # Poloidal grid points +r0 = 2.0 # Major radius R0 [m] +a = 0.4 # Minor radius a [m] (epsilon = a/R0 = 0.2) +q0 = 1.5 # On-axis safety factor +qa = 3.6 # Edge safety factor +p0 = 0.01 # Normalized on-axis pressure +pressure_peaking = 2.0 # Pressure peaking exponent: p2 = p0*(1-r^2)^pressure_peaking +B0 = 12.0 # On-axis toroidal field [T] +ma = 128 # Radial grid points +mtheta = 128 # Poloidal grid points [Wall] shape = "conformal" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath) -a = 20 # Distance from plasma (conformal) or shape parameter +standoff_fraction = 20 # Wall standoff in plasma minor radii (conformal, dee) [ForceFreeStates] HDF5_filename = "gpec_lar_resistive.h5" # Output filename for the stability-stage results @@ -43,17 +43,17 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 1 # Largest toroidal mode number to include delta_mlow = 8 # Expands lower bound of Fourier harmonics delta_mhigh = 8 # Expands upper bound of Fourier harmonics -mthvac = 960 # Number of points used in splines over poloidal angle at plasma-vacuum interface +mtheta_vacuum = 960 # Number of points used in splines over poloidal angle at plasma-vacuum interface eulerlagrange_tolerance = 1e-12 # Relative tolerance for ODE integration of Euler-Lagrange equations singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e4 # Maximum fraction of solutions allowed before re-normalized -sing_order = 6 # Power-series order for the singular-surface asymptotics +renorm_threshold = 1e4 # Column-norm threshold that triggers solution renormalization +frobenius_order = 6 # Power-series order for the singular-surface asymptotics save_interval = 3 # Save every Nth ODE step (1=all, 10=every 10th). Always saves near rational surfaces. integrator = "galerkin" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) -set_psilim_via_dmlim = false # Keep psilim at psihigh (do not truncate at last_rational_q + dmlim) -dmlim = 0.2 # Truncate integration at (last_rational_q + dmlim) / n (only used when set_psilim_via_dmlim = true) +truncate_at_rational_offset = false # Keep psilim at psihigh (do not truncate at last_rational_q + rational_offset_fraction) +rational_offset_fraction = 0.2 # Truncate integration at (last_rational_q + rational_offset_fraction) / n (only used when truncate_at_rational_offset = true) force_termination = true # Terminate after the stability stage (no perturbed-equilibrium section here) write_outputs_to_HDF5 = true # Write stability outputs to HDF5 diff --git a/examples/Solovev_ideal_example/gpec.toml b/examples/Solovev_ideal_example/gpec.toml index 4060b3f74..99014c1b7 100644 --- a/examples/Solovev_ideal_example/gpec.toml +++ b/examples/Solovev_ideal_example/gpec.toml @@ -6,13 +6,13 @@ [Equilibrium] eq_type = "sol" # Type of the input 2D equilibrium file jac_type = "pest" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom) -grid_type = "ldp" # Radial grid packing type +grid_type = "core_edge_packed" # Radial grid packing type psilow = 1e-4 # Lower limit of normalized poloidal flux psihigh = 0.9995 # Upper limit of normalized poloidal flux mpsi = 128 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) mtheta = 256 # Number of poloidal grid points newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) -etol = 1e-7 # Error tolerance for equilibrium solver +flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface force_termination = false # Terminate after equilibrium setup (skip stability calculations) [Wall] @@ -23,12 +23,12 @@ force_termination = false # Terminate after equilibrium setup (ski # smoke testing + et[1] regression, NOT BVP Δ' regression — DIIID-like is the canonical # Δ'-matrix fixture (stable et[1] = +1.6, clean BVP Δ'). shape = "conformal" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath) -a = 0.2415 # Distance from plasma (conformal) or shape parameter -aw = 0.05 # Half-thickness parameter for Dee-shaped walls -bw = 1.5 # Elongation parameter for wall shapes -cw = 0 # Offset of wall center from major radius -dw = 0.5 # Triangularity parameter for wall shapes -tw = 0.05 # Sharpness of wall corners (try 0.05 as initial value) +standoff_fraction = 0.2415 # Wall standoff in plasma minor radii (conformal, dee) +half_thickness = 0.05 # Half-thickness parameter for Dee-shaped walls +elongation = 1.5 # Elongation parameter for wall shapes +center_offset = 0 # Offset of wall center from major radius +triangularity = 0.5 # Triangularity parameter for wall shapes +corner_sharpness = 0.05 # Sharpness of wall corners (try 0.05 as initial value) equal_arc_wall = true # Equal arc length distribution of nodes on wall [ForcingTerms] @@ -42,7 +42,7 @@ compute_response = true # Compute plasma response to forcing compute_singular_coupling = true # Compute singular layer coupling metrics verbose = true # Enable verbose logging write_outputs_to_HDF5 = true # Write perturbed equilibrium outputs to HDF5 -reg_spot = 0.05 # Regularization width for singular surfaces (0 = disabled) +regularization_width = 0.05 # Regularization width for singular surfaces (0 = disabled) # Note: SLAYER tearing analysis is not run on the Solovev analytic equilibrium # — its Δ' / inner-layer dispersion does not yield meaningful tearing roots. @@ -53,7 +53,7 @@ reg_spot = 0.05 # Regularization width for singular surf local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile vac_flag = true # Compute plasma, vacuum, and total energies for free-boundary modes -psiedge = 0.99 # Edge dW(ψ) diagnostic scan band [psiedge, psilim]; set ≥ psilim to disable +dW_edge_scan_start = 0.99 # Edge dW(ψ) diagnostic scan band [dW_edge_scan_start, psilim]; set ≥ psilim to disable qlow = 1.02 # Integration initiated at q determined by min(q0, qlow) qhigh = 1e3 # Integration terminated at q limit determined by min(qa, qhigh) sing_start = 0 # Start integration at the sing_start'th rational from the axis (psilow) @@ -62,25 +62,25 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 1 # Largest toroidal mode number to include delta_mlow = 8 # Expands lower bound of Fourier harmonics delta_mhigh = 8 # Expands upper bound of Fourier harmonics -mthvac = 960 # Number of points used in splines over poloidal angle at the plasma-vacuum interface +mtheta_vacuum = 960 # Number of points used in splines over poloidal angle at the plasma-vacuum interface kinetic_source = "fixed" # Kinetic matrix source: "fixed" test matrices, or "calculated" from the kinetic NTV model kinetic_factor = 0.0 # Scaling of kinetic matrices (0 = ideal path; >0 enables kinetic mode) eulerlagrange_tolerance = 1e-7 # Relative tolerance for ODE integration of Euler-Lagrange equations singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e3 # Column-norm threshold that triggers solution renormalization +renorm_threshold = 1e3 # Column-norm threshold that triggers solution renormalization save_interval = 3 # Save every Nth ODE step (1=all). Always saves near rational surfaces. # Integrator selection and domain truncation (see ForceFreeStatesControl docstring for details) integrator = "forward" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) -set_psilim_via_dmlim = false # FALSE for limited/analytical equilibria — rationals sparse, dmlim would chop too much edge -dmlim = 0.2 # Truncate integration at (last_rational_q + dmlim)/n (used when set_psilim_via_dmlim = true) +truncate_at_rational_offset = false # FALSE for limited/analytical equilibria — rationals sparse, rational_offset_fraction would chop too much edge +rational_offset_fraction = 0.2 # Truncate integration at (last_rational_q + rational_offset_fraction)/n (used when truncate_at_rational_offset = true) # Solovev analytic equilibrium parameters (eq_type = "sol"); see SolovevConfig in src/Equilibrium. [SOL_INPUT] mr = 128 # Number of radial grid zones mz = 128 # Number of axial grid zones ma = 128 # Number of flux grid zones -e = 1.6 # Elongation +elongation = 1.6 # Elongation a = 0.33 # Minor radius r0 = 1.0 # Major radius q0 = 1.9 # Safety factor at the magnetic axis (O-point) diff --git a/examples/Solovev_ideal_example_3D/gpec.toml b/examples/Solovev_ideal_example_3D/gpec.toml index 313b4528f..63140372a 100644 --- a/examples/Solovev_ideal_example_3D/gpec.toml +++ b/examples/Solovev_ideal_example_3D/gpec.toml @@ -5,20 +5,20 @@ [Equilibrium] eq_type = "sol" # Type of the input 2D equilibrium file jac_type = "pest" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom) -grid_type = "ldp" # Radial grid packing type +grid_type = "core_edge_packed" # Radial grid packing type psilow = 1e-4 # Lower limit of normalized poloidal flux psihigh = 0.9995 # Upper limit of normalized poloidal flux mpsi = 128 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) mtheta = 256 # Number of poloidal grid points newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) -etol = 1e-7 # Error tolerance for equilibrium solver +flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface force_termination = false # Terminate after equilibrium setup (skip stability calculations) [ForceFreeStates] local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile vac_flag = true # Compute plasma, vacuum, and total energies for free-boundary modes -psiedge = 1.0 # Edge dW(ψ) diagnostic scan band [psiedge, psilim]; set ≥ psilim to disable +dW_edge_scan_start = 1.0 # Edge dW(ψ) diagnostic scan band [dW_edge_scan_start, psilim]; set ≥ psilim to disable qlow = 1.02 # Integration initiated at q determined by min(q0, qlow) qhigh = 1e3 # Integration terminated at q limit determined by min(qa, qhigh) sing_start = 0 # Start integration at the sing_start'th rational from the axis (psilow) @@ -27,30 +27,30 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 1 # Largest toroidal mode number to include delta_mlow = 8 # Expands lower bound of Fourier harmonics delta_mhigh = 8 # Expands upper bound of Fourier harmonics -mthvac = 64 # Number of points used in splines over poloidal angle at the plasma-vacuum interface -nzvac = 96 # Number of points used in splines over toroidal angle at the plasma-vacuum interface +mtheta_vacuum = 64 # Number of points used in splines over poloidal angle at the plasma-vacuum interface +nzeta_vacuum = 96 # Number of points used in splines over toroidal angle at the plasma-vacuum interface kinetic_source = "fixed" # Kinetic matrix source: "fixed" test matrices, or "calculated" from the kinetic NTV model kinetic_factor = 0.0 # Scaling of kinetic matrices (0 = ideal path; >0 enables kinetic mode) eulerlagrange_tolerance = 1e-7 # Relative tolerance for ODE integration of Euler-Lagrange equations singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e3 # Column-norm threshold that triggers solution renormalization +renorm_threshold = 1e3 # Column-norm threshold that triggers solution renormalization save_interval = 3 # Save every Nth ODE step (1=all). Always saves near rational surfaces. # Integrator selection and domain truncation (see ForceFreeStatesControl docstring for details) integrator = "riccati" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) -set_psilim_via_dmlim = false # FALSE for limited/analytical equilibria — rationals sparse, dmlim would chop too much edge -dmlim = 0.2 # Truncate integration at (last_rational_q + dmlim)/n (used when set_psilim_via_dmlim = true) +truncate_at_rational_offset = false # FALSE for limited/analytical equilibria — rationals sparse, rational_offset_fraction would chop too much edge +rational_offset_fraction = 0.2 # Truncate integration at (last_rational_q + rational_offset_fraction)/n (used when truncate_at_rational_offset = true) [Wall] # See examples/Solovev_ideal_example/gpec.toml for the rationale behind the close conformal wall. shape = "conformal" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath) -a = 0.2415 # Distance from plasma (conformal) or shape parameter -aw = 0.05 # Half-thickness parameter for Dee-shaped walls -bw = 1.5 # Elongation parameter for wall shapes -cw = 0 # Offset of wall center from major radius -dw = 0.5 # Triangularity parameter for wall shapes -tw = 0.05 # Sharpness of wall corners (try 0.05 as initial value) +standoff_fraction = 0.2415 # Wall standoff in plasma minor radii (conformal, dee) +half_thickness = 0.05 # Half-thickness parameter for Dee-shaped walls +elongation = 1.5 # Elongation parameter for wall shapes +center_offset = 0 # Offset of wall center from major radius +triangularity = 0.5 # Triangularity parameter for wall shapes +corner_sharpness = 0.05 # Sharpness of wall corners (try 0.05 as initial value) equal_arc_wall = false # Equal arc length distribution of nodes on wall # Uncomment to enable perturbed equilibrium calculations: @@ -68,7 +68,7 @@ equal_arc_wall = false # Equal arc length distribution of nodes mr = 128 # Number of radial grid zones mz = 128 # Number of axial grid zones ma = 128 # Number of flux grid zones -e = 1.6 # Elongation +elongation = 1.6 # Elongation a = 0.33 # Minor radius r0 = 1.0 # Major radius q0 = 1.9 # Safety factor at the magnetic axis (O-point) diff --git a/examples/Solovev_ideal_example_multi_n/gpec.toml b/examples/Solovev_ideal_example_multi_n/gpec.toml index 7670bd5cf..0150e1b73 100644 --- a/examples/Solovev_ideal_example_multi_n/gpec.toml +++ b/examples/Solovev_ideal_example_multi_n/gpec.toml @@ -6,31 +6,31 @@ [Equilibrium] eq_type = "sol" # Type of the input 2D equilibrium file jac_type = "pest" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom) -grid_type = "ldp" # Radial grid packing type +grid_type = "core_edge_packed" # Radial grid packing type psilow = 1e-4 # Lower limit of normalized poloidal flux psihigh = 0.9995 # Upper limit of normalized poloidal flux mpsi = 128 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) mtheta = 256 # Number of poloidal grid points newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) -etol = 1e-7 # Error tolerance for equilibrium solver +flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface force_termination = false # Terminate after equilibrium setup (skip stability calculations) [Wall] # See examples/Solovev_ideal_example/gpec.toml for the rationale behind the close conformal wall. shape = "conformal" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath) -a = 0.2415 # Distance from plasma (conformal) or shape parameter -aw = 0.05 # Half-thickness parameter for Dee-shaped walls -bw = 1.5 # Elongation parameter for wall shapes -cw = 0 # Offset of wall center from major radius -dw = 0.5 # Triangularity parameter for wall shapes -tw = 0.05 # Sharpness of wall corners (try 0.05 as initial value) +standoff_fraction = 0.2415 # Wall standoff in plasma minor radii (conformal, dee) +half_thickness = 0.05 # Half-thickness parameter for Dee-shaped walls +elongation = 1.5 # Elongation parameter for wall shapes +center_offset = 0 # Offset of wall center from major radius +triangularity = 0.5 # Triangularity parameter for wall shapes +corner_sharpness = 0.05 # Sharpness of wall corners (try 0.05 as initial value) equal_arc_wall = true # Equal arc length distribution of nodes on wall [ForceFreeStates] local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile vac_flag = true # Compute plasma, vacuum, and total energies for free-boundary modes -psiedge = 0.99 # Edge dW(ψ) diagnostic scan band [psiedge, psilim]; set ≥ psilim to disable +dW_edge_scan_start = 0.99 # Edge dW(ψ) diagnostic scan band [dW_edge_scan_start, psilim]; set ≥ psilim to disable qlow = 1.02 # Integration initiated at q determined by min(q0, qlow) qhigh = 1e3 # Integration terminated at q limit determined by min(qa, qhigh) sing_start = 0 # Start integration at the sing_start'th rational from the axis (psilow) @@ -39,25 +39,25 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 2 # Largest toroidal mode number to include delta_mlow = 8 # Expands lower bound of Fourier harmonics delta_mhigh = 8 # Expands upper bound of Fourier harmonics -mthvac = 960 # Number of points used in splines over poloidal angle at the plasma-vacuum interface +mtheta_vacuum = 960 # Number of points used in splines over poloidal angle at the plasma-vacuum interface kinetic_source = "fixed" # Kinetic matrix source: "fixed" test matrices, or "calculated" from the kinetic NTV model kinetic_factor = 0.0 # Scaling of kinetic matrices (0 = ideal path; >0 enables kinetic mode) eulerlagrange_tolerance = 1e-7 # Relative tolerance for ODE integration of Euler-Lagrange equations singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e3 # Column-norm threshold that triggers solution renormalization +renorm_threshold = 1e3 # Column-norm threshold that triggers solution renormalization # Integrator selection and domain truncation (see ForceFreeStatesControl docstring for details). # The multi-n Δ' matrix has open issues and is skipped with a warning; the energies stay valid. integrator = "riccati" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) -set_psilim_via_dmlim = false # FALSE for multi-n — dmlim truncation is ambiguous when n varies (sing_lim! skips anyway) -dmlim = 0.2 # Truncate integration at (last_rational_q + dmlim)/n (used when set_psilim_via_dmlim = true) +truncate_at_rational_offset = false # FALSE for multi-n — rational_offset_fraction truncation is ambiguous when n varies (sing_lim! skips anyway) +rational_offset_fraction = 0.2 # Truncate integration at (last_rational_q + rational_offset_fraction)/n (used when truncate_at_rational_offset = true) # Solovev analytic equilibrium parameters (eq_type = "sol"); see SolovevConfig in src/Equilibrium. [SOL_INPUT] mr = 128 # Number of radial grid zones mz = 128 # Number of axial grid zones ma = 128 # Number of flux grid zones -e = 1.6 # Elongation +elongation = 1.6 # Elongation a = 0.33 # Minor radius r0 = 1.0 # Major radius q0 = 1.9 # Safety factor at the magnetic axis (O-point) diff --git a/examples/Solovev_ideal_example_multi_n/single_n_1/gpec.toml b/examples/Solovev_ideal_example_multi_n/single_n_1/gpec.toml index 59760b231..472546634 100644 --- a/examples/Solovev_ideal_example_multi_n/single_n_1/gpec.toml +++ b/examples/Solovev_ideal_example_multi_n/single_n_1/gpec.toml @@ -5,31 +5,31 @@ [Equilibrium] eq_type = "sol" # Type of the input 2D equilibrium file jac_type = "pest" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom) -grid_type = "ldp" # Radial grid packing type +grid_type = "core_edge_packed" # Radial grid packing type psilow = 1e-4 # Lower limit of normalized poloidal flux psihigh = 0.9995 # Upper limit of normalized poloidal flux mpsi = 128 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) mtheta = 256 # Number of poloidal grid points newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) -etol = 1e-7 # Error tolerance for equilibrium solver +flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface force_termination = false # Terminate after equilibrium setup (skip stability calculations) [Wall] # See examples/Solovev_ideal_example/gpec.toml for the rationale behind the close conformal wall. shape = "conformal" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath) -a = 0.2415 # Distance from plasma (conformal) or shape parameter -aw = 0.05 # Half-thickness parameter for Dee-shaped walls -bw = 1.5 # Elongation parameter for wall shapes -cw = 0 # Offset of wall center from major radius -dw = 0.5 # Triangularity parameter for wall shapes -tw = 0.05 # Sharpness of wall corners (try 0.05 as initial value) +standoff_fraction = 0.2415 # Wall standoff in plasma minor radii (conformal, dee) +half_thickness = 0.05 # Half-thickness parameter for Dee-shaped walls +elongation = 1.5 # Elongation parameter for wall shapes +center_offset = 0 # Offset of wall center from major radius +triangularity = 0.5 # Triangularity parameter for wall shapes +corner_sharpness = 0.05 # Sharpness of wall corners (try 0.05 as initial value) equal_arc_wall = true # Equal arc length distribution of nodes on wall [ForceFreeStates] local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile vac_flag = true # Compute plasma, vacuum, and total energies for free-boundary modes -psiedge = 0.99 # Edge dW(ψ) diagnostic scan band [psiedge, psilim]; set ≥ psilim to disable +dW_edge_scan_start = 0.99 # Edge dW(ψ) diagnostic scan band [dW_edge_scan_start, psilim]; set ≥ psilim to disable qlow = 1.02 # Integration initiated at q determined by min(q0, qlow) qhigh = 1e3 # Integration terminated at q limit determined by min(qa, qhigh) sing_start = 0 # Start integration at the sing_start'th rational from the axis (psilow) @@ -38,13 +38,13 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 1 # Largest toroidal mode number to include delta_mlow = 8 # Expands lower bound of Fourier harmonics delta_mhigh = 10 # Expands upper bound of Fourier harmonics -mthvac = 960 # Number of points used in splines over poloidal angle at the plasma-vacuum interface +mtheta_vacuum = 960 # Number of points used in splines over poloidal angle at the plasma-vacuum interface kinetic_source = "fixed" # Kinetic matrix source: "fixed" test matrices, or "calculated" from the kinetic NTV model kinetic_factor = 0.0 # Scaling of kinetic matrices (0 = ideal path; >0 enables kinetic mode) eulerlagrange_tolerance = 1e-7 # Relative tolerance for ODE integration of Euler-Lagrange equations singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e3 # Column-norm threshold that triggers solution renormalization +renorm_threshold = 1e3 # Column-norm threshold that triggers solution renormalization write_outputs_to_HDF5 = true # Write outputs to the HDF5 file HDF5_filename = "euler_n1.h5" # Output HDF5 filename # Solovev analytic equilibrium parameters (eq_type = "sol"); see SolovevConfig in src/Equilibrium. @@ -52,7 +52,7 @@ HDF5_filename = "euler_n1.h5" # Output HDF5 filename mr = 128 # Number of radial grid zones mz = 128 # Number of axial grid zones ma = 128 # Number of flux grid zones -e = 1.6 # Elongation +elongation = 1.6 # Elongation a = 0.33 # Minor radius r0 = 1.0 # Major radius q0 = 1.9 # Safety factor at the magnetic axis (O-point) diff --git a/examples/Solovev_ideal_example_multi_n/single_n_2/gpec.toml b/examples/Solovev_ideal_example_multi_n/single_n_2/gpec.toml index a80ac7741..97f108a5a 100644 --- a/examples/Solovev_ideal_example_multi_n/single_n_2/gpec.toml +++ b/examples/Solovev_ideal_example_multi_n/single_n_2/gpec.toml @@ -5,31 +5,31 @@ [Equilibrium] eq_type = "sol" # Type of the input 2D equilibrium file jac_type = "pest" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom) -grid_type = "ldp" # Radial grid packing type +grid_type = "core_edge_packed" # Radial grid packing type psilow = 1e-4 # Lower limit of normalized poloidal flux psihigh = 0.9995 # Upper limit of normalized poloidal flux mpsi = 128 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) mtheta = 256 # Number of poloidal grid points newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) -etol = 1e-7 # Error tolerance for equilibrium solver +flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface force_termination = false # Terminate after equilibrium setup (skip stability calculations) [Wall] # See examples/Solovev_ideal_example/gpec.toml for the rationale behind the close conformal wall. shape = "conformal" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath) -a = 0.2415 # Distance from plasma (conformal) or shape parameter -aw = 0.05 # Half-thickness parameter for Dee-shaped walls -bw = 1.5 # Elongation parameter for wall shapes -cw = 0 # Offset of wall center from major radius -dw = 0.5 # Triangularity parameter for wall shapes -tw = 0.05 # Sharpness of wall corners (try 0.05 as initial value) +standoff_fraction = 0.2415 # Wall standoff in plasma minor radii (conformal, dee) +half_thickness = 0.05 # Half-thickness parameter for Dee-shaped walls +elongation = 1.5 # Elongation parameter for wall shapes +center_offset = 0 # Offset of wall center from major radius +triangularity = 0.5 # Triangularity parameter for wall shapes +corner_sharpness = 0.05 # Sharpness of wall corners (try 0.05 as initial value) equal_arc_wall = true # Equal arc length distribution of nodes on wall [ForceFreeStates] local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile vac_flag = true # Compute plasma, vacuum, and total energies for free-boundary modes -psiedge = 0.99 # Edge dW(ψ) diagnostic scan band [psiedge, psilim]; set ≥ psilim to disable +dW_edge_scan_start = 0.99 # Edge dW(ψ) diagnostic scan band [dW_edge_scan_start, psilim]; set ≥ psilim to disable qlow = 1.02 # Integration initiated at q determined by min(q0, qlow) qhigh = 1e3 # Integration terminated at q limit determined by min(qa, qhigh) sing_start = 0 # Start integration at the sing_start'th rational from the axis (psilow) @@ -38,13 +38,13 @@ nn_low = 2 # Smallest toroidal mode number to include nn_high = 2 # Largest toroidal mode number to include delta_mlow = 8 # Expands lower bound of Fourier harmonics delta_mhigh = 8 # Expands upper bound of Fourier harmonics -mthvac = 960 # Number of points used in splines over poloidal angle at the plasma-vacuum interface +mtheta_vacuum = 960 # Number of points used in splines over poloidal angle at the plasma-vacuum interface kinetic_source = "fixed" # Kinetic matrix source: "fixed" test matrices, or "calculated" from the kinetic NTV model kinetic_factor = 0.0 # Scaling of kinetic matrices (0 = ideal path; >0 enables kinetic mode) eulerlagrange_tolerance = 1e-7 # Relative tolerance for ODE integration of Euler-Lagrange equations singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e3 # Column-norm threshold that triggers solution renormalization +renorm_threshold = 1e3 # Column-norm threshold that triggers solution renormalization write_outputs_to_HDF5 = true # Write outputs to the HDF5 file HDF5_filename = "euler_n2.h5" # Output HDF5 filename # Solovev analytic equilibrium parameters (eq_type = "sol"); see SolovevConfig in src/Equilibrium. @@ -52,7 +52,7 @@ HDF5_filename = "euler_n2.h5" # Output HDF5 filename mr = 128 # Number of radial grid zones mz = 128 # Number of axial grid zones ma = 128 # Number of flux grid zones -e = 1.6 # Elongation +elongation = 1.6 # Elongation a = 0.33 # Minor radius r0 = 1.0 # Major radius q0 = 1.9 # Safety factor at the magnetic axis (O-point) diff --git a/examples/Solovev_kinetic_NTV_example/gpec.toml b/examples/Solovev_kinetic_NTV_example/gpec.toml index 39483c808..52dbbe671 100644 --- a/examples/Solovev_kinetic_NTV_example/gpec.toml +++ b/examples/Solovev_kinetic_NTV_example/gpec.toml @@ -8,13 +8,13 @@ [Equilibrium] eq_type = "sol" # Type of the input 2D equilibrium file jac_type = "pest" # Coordinate system (hamada, pest, boozer, equal_arc, park, other) -grid_type = "ldp" # Radial grid packing type +grid_type = "core_edge_packed" # Radial grid packing type psilow = 1e-4 # Lower limit of normalized poloidal flux psihigh = 0.9995 # Upper limit of normalized poloidal flux mpsi = 128 # Number of radial grid points (0 = auto-compute from psi_accuracy) mtheta = 256 # Number of poloidal grid points newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) -etol = 1e-7 # Error tolerance for equilibrium solver +flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface force_termination = false # Terminate after equilibrium setup (skip stability calculations) [Wall] @@ -25,12 +25,12 @@ force_termination = false # Terminate after equilibrium setup (ski # smoke testing + et[1] regression, NOT BVP Δ' regression — DIIID-like is the canonical # Δ'-matrix fixture (stable et[1] = +1.6, clean BVP Δ'). shape = "conformal" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath) -a = 0.2415 # Distance from plasma (conformal) or shape parameter -aw = 0.05 # Half-thickness parameter for Dee-shaped walls -bw = 1.5 # Elongation parameter for wall shapes -cw = 0 # Offset of wall center from major radius -dw = 0.5 # Triangularity parameter for wall shapes -tw = 0.05 # Sharpness of wall corners (try 0.05 as initial value) +standoff_fraction = 0.2415 # Wall standoff in plasma minor radii (conformal, dee) +half_thickness = 0.05 # Half-thickness parameter for Dee-shaped walls +elongation = 1.5 # Elongation parameter for wall shapes +center_offset = 0 # Offset of wall center from major radius +triangularity = 0.5 # Triangularity parameter for wall shapes +corner_sharpness = 0.05 # Sharpness of wall corners (try 0.05 as initial value) equal_arc_wall = true # Equal arc length distribution of nodes on wall [ForcingTerms] @@ -44,13 +44,13 @@ compute_response = true # Compute plasma response to forcing compute_singular_coupling = true # Compute singular layer coupling metrics verbose = true # Enable verbose logging write_outputs_to_HDF5 = true # Write perturbed equilibrium outputs to HDF5 -reg_spot = 0.05 # Regularization width for singular surfaces (0 = disabled) +regularization_width = 0.05 # Regularization width for singular surfaces (0 = disabled) [ForceFreeStates] local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile vac_flag = true # Compute plasma, vacuum, and total energies for free-boundary modes -psiedge = 0.99 # Edge dW(ψ) diagnostic scan band [psiedge, psilim]; set ≥ psilim to disable +dW_edge_scan_start = 0.99 # Edge dW(ψ) diagnostic scan band [dW_edge_scan_start, psilim]; set ≥ psilim to disable qlow = 1.02 # Integration initiated at q determined by min(q0, qlow) qhigh = 1e3 # Integration terminated at q limit determined by min(qa, qhigh) sing_start = 0 # Start integration at the sing_start'th rational from the axis (psilow) @@ -59,25 +59,25 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 1 # Largest toroidal mode number to include delta_mlow = 8 # Expands lower bound of Fourier harmonics delta_mhigh = 8 # Expands upper bound of Fourier harmonics -mthvac = 960 # Number of points used in splines over poloidal angle at the plasma-vacuum interface +mtheta_vacuum = 960 # Number of points used in splines over poloidal angle at the plasma-vacuum interface kinetic_source = "fixed" # Kinetic matrix source: "fixed" test matrices, or "calculated" from the kinetic NTV model kinetic_factor = 0.0 # Scaling of kinetic matrices (0 = ideal path; >0 enables kinetic mode) eulerlagrange_tolerance = 1e-7 # Relative tolerance for ODE integration of Euler-Lagrange equations singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e3 # Column-norm threshold that triggers solution renormalization +renorm_threshold = 1e3 # Column-norm threshold that triggers solution renormalization save_interval = 3 # Save every Nth ODE step (1=all). Always saves near rational surfaces. # Integrator selection and domain truncation (see ForceFreeStatesControl docstring for details) integrator = "forward" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) -set_psilim_via_dmlim = false # FALSE for limited/analytical equilibria — rationals sparse, dmlim would chop too much edge -dmlim = 0.2 # Truncate integration at (last_rational_q + dmlim)/n (used when set_psilim_via_dmlim = true) +truncate_at_rational_offset = false # FALSE for limited/analytical equilibria — rationals sparse, rational_offset_fraction would chop too much edge +rational_offset_fraction = 0.2 # Truncate integration at (last_rational_q + rational_offset_fraction)/n (used when truncate_at_rational_offset = true) # Solovev analytic equilibrium parameters (eq_type = "sol"); see SolovevConfig in src/Equilibrium. [SOL_INPUT] mr = 128 # Number of radial grid zones mz = 128 # Number of axial grid zones ma = 128 # Number of flux grid zones -e = 1.6 # Elongation +elongation = 1.6 # Elongation a = 0.33 # Minor radius r0 = 1.0 # Major radius q0 = 1.9 # Safety factor at the magnetic axis (O-point) diff --git a/examples/Solovev_kinetic_calculated_example/gpec.toml b/examples/Solovev_kinetic_calculated_example/gpec.toml index b325fa6ee..a6bce31b9 100644 --- a/examples/Solovev_kinetic_calculated_example/gpec.toml +++ b/examples/Solovev_kinetic_calculated_example/gpec.toml @@ -9,23 +9,23 @@ [Equilibrium] eq_type = "sol" # Type of the input 2D equilibrium file jac_type = "pest" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom) -grid_type = "ldp" # Radial grid packing type +grid_type = "core_edge_packed" # Radial grid packing type psilow = 1e-4 # Lower limit of normalized flux coordinate psihigh = 0.9995 # Upper limit of normalized flux coordinate mpsi = 16 # Number of radial grid points mtheta = 256 # Number of poloidal grid points newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) -etol = 1e-7 # Error tolerance for equilibrium solver +flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface force_termination = false # Terminate after equilibrium setup (skip stability calculations) [Wall] shape = "conformal" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath) -a = 0.2415 # Distance from plasma (conformal) or shape parameter -aw = 0.05 # Half-thickness parameter for Dee-shaped walls -bw = 1.5 # Elongation parameter for wall shapes -cw = 0 # Offset of wall center from major radius -dw = 0.5 # Triangularity parameter for wall shapes -tw = 0.05 # Sharpness of wall corners (try 0.05 as initial value) +standoff_fraction = 0.2415 # Wall standoff in plasma minor radii (conformal, dee) +half_thickness = 0.05 # Half-thickness parameter for Dee-shaped walls +elongation = 1.5 # Elongation parameter for wall shapes +center_offset = 0 # Offset of wall center from major radius +triangularity = 0.5 # Triangularity parameter for wall shapes +corner_sharpness = 0.05 # Sharpness of wall corners (try 0.05 as initial value) equal_arc_wall = true # Equal arc length distribution of nodes on wall [ForcingTerms] @@ -39,13 +39,13 @@ compute_response = true # Compute plasma response to forcing compute_singular_coupling = true # Compute singular layer coupling metrics verbose = false # Enable verbose logging write_outputs_to_HDF5 = true # Write perturbed equilibrium outputs to HDF5 -reg_spot = 0.05 # Regularization width for singular surfaces (0 = disabled) +regularization_width = 0.05 # Regularization width for singular surfaces (0 = disabled) [ForceFreeStates] local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile vac_flag = true # Compute plasma, vacuum, and total energies for free-boundary modes -psiedge = 0.99 # Edge dW(ψ) diagnostic scan band [psiedge, psilim]; set ≥ psilim to disable +dW_edge_scan_start = 0.99 # Edge dW(ψ) diagnostic scan band [dW_edge_scan_start, psilim]; set ≥ psilim to disable qlow = 1.02 # Integration initiated at q determined by min(q0, qlow)... qhigh = 1e3 # Integration terminated at q limit determined by min(qa, qhigh)... sing_start = 0 # Start integration at the sing_start'th rational from the axis (psilow) @@ -54,14 +54,14 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 1 # Largest toroidal mode number to include delta_mlow = 8 # Expands lower bound of Fourier harmonics delta_mhigh = 8 # Expands upper bound of Fourier harmonics -mthvac = 64 # Number of points used in splines over poloidal angle at plasma-vacuum interface. +mtheta_vacuum = 64 # Number of points used in splines over poloidal angle at plasma-vacuum interface. kinetic_source = "calculated" # Kinetic matrix source — exercises KineticForces.compute_calculated_kinetic_matrices callback with real physics kinetic_factor = 1.0 # Full-strength kinetic matrices (the "calculated" path is the real physics; no perturbation scaling) integrator = "forward" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) eulerlagrange_tolerance = 1e-7 # Relative tolerance for ODE integration of Euler-Lagrange equations singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e3 # Maximum fraction of solutions allowed before re-normalized +renorm_threshold = 1e3 # Column-norm threshold that triggers solution renormalization write_outputs_to_HDF5 = true # Write outputs to the HDF5 file verbose = false # Enable verbose logging @@ -83,7 +83,7 @@ fraction = 0.5 # This species' share of the total main-ion densit mr = 128 # Number of radial grid zones mz = 128 # Number of axial grid zones ma = 128 # Number of flux grid zones -e = 1.6 # Elongation +elongation = 1.6 # Elongation a = 0.33 # Minor radius r0 = 1.0 # Major radius q0 = 1.9 # Safety factor at the magnetic axis (O-point) diff --git a/examples/Solovev_kinetic_multiion_example/gpec.toml b/examples/Solovev_kinetic_multiion_example/gpec.toml index 44acdf65e..69614fb88 100644 --- a/examples/Solovev_kinetic_multiion_example/gpec.toml +++ b/examples/Solovev_kinetic_multiion_example/gpec.toml @@ -8,13 +8,13 @@ [Equilibrium] eq_type = "sol" # Type of the input 2D equilibrium file jac_type = "pest" # Coordinate system (hamada, pest, boozer, equal_arc, park, other) -grid_type = "ldp" # Radial grid packing type +grid_type = "core_edge_packed" # Radial grid packing type psilow = 1e-4 # Lower limit of normalized poloidal flux psihigh = 0.9995 # Upper limit of normalized poloidal flux mpsi = 128 # Number of radial grid points (0 = auto-compute from psi_accuracy) mtheta = 256 # Number of poloidal grid points newq0 = 0 # Override for on-axis safety factor (0 = use input value) -etol = 1e-7 # Error tolerance for equilibrium solver +flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface force_termination = false # Terminate after equilibrium setup (skip stability calculations) [Wall] @@ -25,12 +25,12 @@ force_termination = false # Terminate after equilibrium setup (ski # smoke testing + et[1] regression, NOT BVP Δ' regression — DIIID-like is the canonical # Δ'-matrix fixture (stable et[1] = +1.6, clean BVP Δ'). shape = "conformal" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath) -a = 0.2415 # Distance from plasma (conformal) or shape parameter -aw = 0.05 # Half-thickness parameter for Dee-shaped walls -bw = 1.5 # Elongation parameter for wall shapes -cw = 0 # Offset of wall center from major radius -dw = 0.5 # Triangularity parameter for wall shapes -tw = 0.05 # Sharpness of wall corners (try 0.05 as initial value) +standoff_fraction = 0.2415 # Wall standoff in plasma minor radii (conformal, dee) +half_thickness = 0.05 # Half-thickness parameter for Dee-shaped walls +elongation = 1.5 # Elongation parameter for wall shapes +center_offset = 0 # Offset of wall center from major radius +triangularity = 0.5 # Triangularity parameter for wall shapes +corner_sharpness = 0.05 # Sharpness of wall corners (try 0.05 as initial value) equal_arc_wall = true # Equal arc length distribution of nodes on wall [ForcingTerms] @@ -44,14 +44,14 @@ compute_response = true # Compute plasma response to forcing compute_singular_coupling = true # Compute singular layer coupling metrics verbose = true # Enable verbose logging write_outputs_to_HDF5 = true # Write perturbed equilibrium outputs to HDF5 -reg_spot = 0.05 # Regularization width for singular surfaces (0 = disabled) +regularization_width = 0.05 # Regularization width for singular surfaces (0 = disabled) [ForceFreeStates] integrator = "forward" # Dense ξ profiles required by PerturbedEquilibrium and kinetic torque diagnostics local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile vac_flag = true # Compute plasma, vacuum, and total energies for free-boundary modes -psiedge = 0.99 # Edge dW(ψ) diagnostic scan band [psiedge, psilim]; set ≥ psilim to disable +dW_edge_scan_start = 0.99 # Edge dW(ψ) diagnostic scan band [dW_edge_scan_start, psilim]; set ≥ psilim to disable qlow = 1.02 # Integration initiated at q determined by min(q0, qlow) qhigh = 1e3 # Integration terminated at q limit determined by min(qa, qhigh) sing_start = 0 # Start integration at the sing_start'th rational from the axis (psilow) @@ -60,24 +60,24 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 1 # Largest toroidal mode number to include delta_mlow = 8 # Expands lower bound of Fourier harmonics delta_mhigh = 8 # Expands upper bound of Fourier harmonics -mthvac = 960 # Number of points used in splines over poloidal angle at the plasma-vacuum interface +mtheta_vacuum = 960 # Number of points used in splines over poloidal angle at the plasma-vacuum interface kinetic_source = "fixed" # Kinetic matrix source: "fixed" test matrices, or "calculated" from the kinetic NTV model kinetic_factor = 0.0 # Scaling of kinetic matrices (0 = ideal path; >0 enables kinetic mode) eulerlagrange_tolerance = 1e-7 # Relative tolerance for ODE integration of Euler-Lagrange equations singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e3 # Column-norm threshold that triggers solution renormalization +renorm_threshold = 1e3 # Column-norm threshold that triggers solution renormalization save_interval = 3 # Save every Nth ODE step (1=all). Always saves near rational surfaces. # Integration-range truncation (see ForceFreeStatesControl docstring for details) -set_psilim_via_dmlim = false # FALSE for limited/analytical equilibria — rationals sparse, dmlim would chop too much edge -dmlim = 0.2 # Truncate integration at (last_rational_q + dmlim)/n (used when set_psilim_via_dmlim = true) +truncate_at_rational_offset = false # FALSE for limited/analytical equilibria — rationals sparse, rational_offset_fraction would chop too much edge +rational_offset_fraction = 0.2 # Truncate integration at (last_rational_q + rational_offset_fraction)/n (used when truncate_at_rational_offset = true) # Solovev analytic equilibrium parameters (eq_type = "sol"); see SolovevConfig in src/Equilibrium. [SOL_INPUT] mr = 128 # Number of radial grid zones mz = 128 # Number of axial grid zones ma = 128 # Number of flux grid zones -e = 1.6 # Elongation +elongation = 1.6 # Elongation a = 0.33 # Minor radius r0 = 1.0 # Major radius q0 = 1.9 # Safety factor at the magnetic axis (O-point) @@ -90,8 +90,8 @@ f0fac = 1 # Scale toroidal field at constant pressure (β, q change; Φ, p, # 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 -zimp = 6 # Impurity charge (fundamental units); closes quasineutrality -mimp = 12 # Impurity mass (proton masses) +impurity_charge = 6 # Impurity charge (fundamental units); closes quasineutrality +impurity_mass = 12 # Impurity mass (proton masses) [[KineticForces.ion_species]] z = 1 # Species charge (fundamental units) diff --git a/examples/a10_kinetic_example/gpec.toml b/examples/a10_kinetic_example/gpec.toml index 47e2e603b..7d629ea3c 100644 --- a/examples/a10_kinetic_example/gpec.toml +++ b/examples/a10_kinetic_example/gpec.toml @@ -10,13 +10,13 @@ eq_filename = "fix_a100_k10_q2_bn010_prof1" # Path to equilibrium file eq_type = "efit" # Type of the input 2D equilibrium file jac_type = "hamada" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom) -grid_type = "ldp" # Radial grid packing type +grid_type = "core_edge_packed" # Radial grid packing type psilow = 1e-3 # Lower limit of normalized poloidal flux psihigh = 0.99 # Upper limit of normalized poloidal flux mpsi = 16 # Number of radial grid points (low value for fast iteration) mtheta = 256 # Number of poloidal grid points newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) -etol = 1e-7 # Error tolerance for equilibrium solver +flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface [Wall] shape = "nowall" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath) @@ -25,8 +25,8 @@ shape = "nowall" # Wall shape (nowall, conformal, elliptical, dee, local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile vac_flag = true # Compute plasma, vacuum, and total energies for free-boundary modes -set_psilim_via_dmlim = false # FALSE for limited/analytical equilibria — rationals sparse, dmlim would chop too much edge -psiedge = 1.0 # Edge dW(ψ) diagnostic scan band [psiedge, psilim]; set ≥ psilim to disable +truncate_at_rational_offset = false # FALSE for limited/analytical equilibria — rationals sparse, rational_offset_fraction would chop too much edge +dW_edge_scan_start = 1.0 # Edge dW(ψ) diagnostic scan band [dW_edge_scan_start, psilim]; set ≥ psilim to disable qlow = 1.02 # Integration initiated at q determined by min(q0, qlow) qhigh = 1e3 # Integration terminated at q limit determined by min(qa, qhigh) sing_start = 0 # Start integration at the sing_start'th rational from the axis (psilow) @@ -35,27 +35,27 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 1 # Largest toroidal mode number to include delta_mlow = 6 # Expands lower bound of Fourier harmonics delta_mhigh = 6 # Expands upper bound of Fourier harmonics -mthvac = 512 # Number of points used in splines over poloidal angle at the plasma-vacuum interface +mtheta_vacuum = 512 # Number of points used in splines over poloidal angle at the plasma-vacuum interface kinetic_source = "calculated" # Kinetic matrix source: "fixed" test matrices, or "calculated" from the kinetic NTV model kinetic_factor = 1.0 # Scaling of kinetic matrices (0 = ideal path; >0 enables kinetic mode; 1.0 = full strength) integrator = "forward" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) eulerlagrange_tolerance = 1e-7 # Relative tolerance for ODE integration of Euler-Lagrange equations singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e4 # Column-norm threshold that triggers solution renormalization +renorm_threshold = 1e4 # Column-norm threshold that triggers solution renormalization 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 -nl = 6 # Bounce-harmonic number -zi = 1 # Ion charge (fundamental units) -mi = 2 # Ion mass (proton masses) -zimp = 6 # Impurity charge -mimp = 12 # Impurity mass +bounce_harmonic_max = 6 # Bounce-harmonic number +ion_charge = 1 # Ion charge (fundamental units) +ion_mass = 2 # Ion mass (proton masses) +impurity_charge = 6 # Impurity charge +impurity_mass = 12 # Impurity mass electron = false # Include electron contribution (false = ion-only) nutype = "harmonic" # Collision operator (zero, small, krook, harmonic) -f0type = "maxwellian" # Distribution function (maxwellian, jkp, cgl) -atol_xlmda = 1e-9 # Absolute tolerance for inner pitch + energy integrations -rtol_xlmda = 1e-5 # Relative tolerance for inner pitch + energy integrations +f0type = "maxwellian" # Distribution function (maxwellian, park, cgl) +atol_pitch = 1e-9 # Absolute tolerance for inner pitch + energy integrations +rtol_pitch = 1e-5 # Relative tolerance for inner pitch + energy integrations diff --git a/src/Analysis/ForceFreeStates.jl b/src/Analysis/ForceFreeStates.jl index 23d4db74b..03462531f 100644 --- a/src/Analysis/ForceFreeStates.jl +++ b/src/Analysis/ForceFreeStates.jl @@ -162,7 +162,7 @@ end Plot the edge stability scan energy components (et, ep, ev, evonly) vs ψ_N. The edge scan evaluates `δW_total = δW_plasma + δW_vacuum` at each stored integration step -in the region [psiedge, psilim], with the plasma boundary swept from psiedge to psilim. +in the region [dW_edge_scan_start, psilim], with the plasma boundary swept from dW_edge_scan_start to psilim. A positive et indicates stability; the truncation point is chosen at the peak et. Four subplots are shown: @@ -177,7 +177,7 @@ A horizontal dashed line at zero marks the stability boundary. A vertical dashed ### Arguments - - `h5path`: Path to a GPEC HDF5 output file produced with `psiedge < psilim` + - `h5path`: Path to a GPEC HDF5 output file produced with `dW_edge_scan_start < psilim` ### Keyword arguments @@ -204,7 +204,7 @@ function plot_edge_stability_scan(h5path; save_path=nothing, ylims=(-2, 3), kwar end if !has_scan - @warn "No edge_scan group in $h5path. Run with psiedge < psilim to generate it." + @warn "No edge_scan group in $h5path. Run with dW_edge_scan_start < psilim to generate it." return nothing end diff --git a/src/Equilibrium/AnalyticEquilibrium.jl b/src/Equilibrium/AnalyticEquilibrium.jl index 767f2b718..f60e20456 100644 --- a/src/Equilibrium/AnalyticEquilibrium.jl +++ b/src/Equilibrium/AnalyticEquilibrium.jl @@ -15,8 +15,8 @@ Also evaluates the initial derivative using the analytic model. - `y`: Initial state vector of length 5. """ function lar_init_conditions(rmin::Float64, lar_input::LargeAspectRatioConfig) - lar_a = lar_input.lar_a - lar_r0 = lar_input.lar_r0 + lar_a = lar_input.a + lar_r0 = lar_input.r0 q0 = lar_input.q0 # Ensure rmin is not too small to avoid numerical issues in LAR equations @@ -63,8 +63,8 @@ at a given radius `r`, using the current state vector `y` and equilibrium parame """ function lar_der(dy::Vector{Float64}, r::Float64, y::Vector{Float64}, lar_input::LargeAspectRatioConfig) - lar_a = lar_input.lar_a - lar_r0 = lar_input.lar_r0 + lar_a = lar_input.a + lar_r0 = lar_input.r0 q0 = lar_input.q0 @@ -115,8 +115,8 @@ defined by `lar_input`, and returns the full solution table including derived qu function lar_run(equil_input::EquilibriumConfig, lar_input::LargeAspectRatioConfig) rmin = 1e-4 - lar_a = lar_input.lar_a - lar_r0 = lar_input.lar_r0 + lar_a = lar_input.a + lar_r0 = lar_input.r0 q0 = lar_input.q0 beta0 = lar_input.beta0 sigma_type = lar_input.sigma_type @@ -127,7 +127,7 @@ function lar_run(equil_input::EquilibriumConfig, lar_input::LargeAspectRatioConf sigma0 = 2.0 / (q0 * lar_r0) ma = lar_input.ma - mtau = lar_input.mtau + mtau = lar_input.mtheta function dydr(du, u, p, r) lar_input = p @@ -140,7 +140,7 @@ function lar_run(equil_input::EquilibriumConfig, lar_input::LargeAspectRatioConf prob = ODEProblem(dydr, y0, tspan, p) - sol = solve(prob, Rosenbrock23(; autodiff=false); reltol=equil_input.etol, abstol=1e-8, maxiters=10000, dense=false) + sol = solve(prob, Rosenbrock23(; autodiff=false); reltol=equil_input.flux_surface_rtol, abstol=1e-8, maxiters=10000, dense=false) r_arr = sol.t y_mat = reduce(hcat, sol.u)' @@ -295,14 +295,14 @@ struct TJAnalyticShapeParams end function TJAnalyticShapeParams(tj::TJAnalyticConfig; rmin::Float64=1e-4) - a, R0 = tj.lar_a, tj.lar_r0 - mu = max(tj.mu, 1.001) + a, R0 = tj.a, tj.r0 + mu = max(tj.pressure_peaking, 1.001) return TJAnalyticShapeParams( - a, R0, tj.qc, mu, tj.pc, tj.B0, + a, R0, tj.q0, mu, tj.p0, tj.B0, (a / R0)^2, rmin, rmin, rmin * a, - 1.0 / tj.qc, - -2.0 * mu * tj.pc + 1.0 / tj.q0, + -2.0 * mu * tj.p0 ) end @@ -447,16 +447,16 @@ included; they are zero in the TJ-analytic benchmark scans. Reference: R. Fitzpatrick, TJ code, https://github.com/rfitzp/TJ """ function tj_analytic_run(equil_input::EquilibriumConfig, tj::TJAnalyticConfig) - a, R0 = tj.lar_a, tj.lar_r0 - qc, mu = tj.qc, max(tj.mu, 1.001) - pc, B0 = tj.pc, tj.B0 - ma, mtau = tj.ma, tj.mtau + a, R0 = tj.a, tj.r0 + qc, mu = tj.q0, max(tj.pressure_peaking, 1.001) + pc, B0 = tj.p0, tj.B0 + ma, mtau = tj.ma, tj.mtheta p = TJAnalyticShapeParams(tj) epsa2 = p.epsa2 p00_phys = B0^2 * epsa2 * pc # μ₀P = B₀²·εa²·p₂ at axis - nu = tj_analytic_find_nu(p, tj.qa; reltol=equil_input.etol) - sol = tj_analytic_shape_solve(p, nu; reltol=equil_input.etol) + nu = tj_analytic_find_nu(p, tj.qa; reltol=equil_input.flux_surface_rtol) + sol = tj_analytic_shape_solve(p, nu; reltol=equil_input.flux_surface_rtol) r_arr = sol.t y_mat = reduce(hcat, sol.u)' @@ -584,21 +584,21 @@ EFIT-writer (R, Z) → (r, w) Newton inversion that this routine adapts. """ function tj_analytic_run_direct(equil_input::EquilibriumConfig, tj::TJAnalyticConfig; nrbox::Int=257, nzbox::Int=257, rc::Float64=1.2) - a, R0 = tj.lar_a, tj.lar_r0 - qc, mu = tj.qc, max(tj.mu, 1.001) - pc, B0 = tj.pc, tj.B0 + a, R0 = tj.a, tj.r0 + qc, mu = tj.q0, max(tj.pressure_peaking, 1.001) + pc, B0 = tj.p0, tj.B0 p = TJAnalyticShapeParams(tj) epsa, epsa2 = p.a / p.R0, p.epsa2 p00_phys = B0^2 * epsa2 * pc # ν root-find (cf. Fitzpatrick TJ's Setnu): q₂(1) = qa_target. - nu = tj_analytic_find_nu(p, tj.qa; reltol=equil_input.etol) + nu = tj_analytic_find_nu(p, tj.qa; reltol=equil_input.flux_surface_rtol) # Dense saveat so the downstream splines (H₁, g₂, f₃, ψ) are evaluated on # a fine uniform r grid rather than the ~30 adaptive Vern9 steps — otherwise # the (R, Z) → (r, w) Newton iteration hits spline interpolation artifacts. dense_r = collect(range(p.r0, p.a; length=1024)) - sol = tj_analytic_shape_solve(p, nu; reltol=equil_input.etol, + sol = tj_analytic_shape_solve(p, nu; reltol=equil_input.flux_surface_rtol, abstol=1e-10, saveat=dense_r) r_arr = sol.t y_mat = reduce(hcat, sol.u)' @@ -792,7 +792,7 @@ function sol_run(equil_inputs::EquilibriumConfig, sol_inputs::SolovevConfig) mr = sol_inputs.mr mz = sol_inputs.mz ma = sol_inputs.ma - e = sol_inputs.e + e = sol_inputs.elongation a = sol_inputs.a r0 = sol_inputs.r0 q0 = sol_inputs.q0 diff --git a/src/Equilibrium/DirectEquilibrium.jl b/src/Equilibrium/DirectEquilibrium.jl index 3ade63787..66f4296db 100644 --- a/src/Equilibrium/DirectEquilibrium.jl +++ b/src/Equilibrium/DirectEquilibrium.jl @@ -291,7 +291,7 @@ function direct_fieldline_int(psifac::Float64, raw_profile::DirectRunInput, ro:: callback = DiscreteCallback((u, t, i) -> true, refine_affect!; save_positions=(true, false)) prob = ODEProblem{true}(direct_fieldline_der!, u0, (0.0, 2π), params) - sol = solve(prob, Vern9(); callback=callback, reltol=equil_config.etol, abstol=1e-8, dt=2π / 200, adaptive=true, dense=false) + sol = solve(prob, Vern9(); callback=callback, reltol=equil_config.flux_surface_rtol, abstol=1e-8, dt=2π / 200, adaptive=true, dense=false) # A failed solve returns a truncated solution instead of throwing; check both the retcode and # that the field line reached η = 2π, since a callback can end it early and still report Success. @@ -445,7 +445,7 @@ function _build_psi_grid(equil_params, psilow, psihigh) psi_nodes = if equil_params.grid_type in ("auto", "log_asymptotic") psihigh > 0.98 || @warn "grid_type = \"$(equil_params.grid_type)\" needs psihigh > 0.98 (its grid has a fixed edge region on [0.98, psihigh]); " * - "psihigh = $psihigh gives a non-monotonic ψ grid and the equilibrium will fail. Use grid_type = \"ldp\" instead." + "psihigh = $psihigh gives a non-monotonic ψ grid and the equilibrium will fail. Use grid_type = \"core_edge_packed\" instead." # Distribute mpsi across the three regions by log-weights log_core = log(0.03 / psilow) log_mid = log(0.98 / 0.03) @@ -455,7 +455,7 @@ function _build_psi_grid(equil_params, psilow, psihigh) N_core = round(Int, mpsi * log_core / log_total) N_mid = mpsi - N_edge - N_core make_optimal_psi_grid(psilow, psihigh, N_core, N_mid, N_edge) - elseif equil_params.grid_type == "ldp" + elseif equil_params.grid_type == "core_edge_packed" [psilow + (psihigh - psilow) * sin((ipsi / mpsi) * (π / 2))^2 for ipsi in 0:mpsi] elseif equil_params.grid_type == "pow1" # Fortran powspace(psilow, psihigh, 1, mpsi+1, "upper") — edge-packed grid (equil/grid.f90:92-195) @@ -466,7 +466,7 @@ function _build_psi_grid(equil_params, psilow, psihigh) else error("Unsupported grid_type: $(equil_params.grid_type)") end - # Floor node spacing on the fixed grids: ldp/pow1 pack the edge as ~(π/2·mpsi)⁻² and at high + # Floor node spacing on the fixed grids: core_edge_packed/pow1 pack the edge as ~(π/2·mpsi)⁻² and at high # mpsi drive it below the integration-noise scale (garbage curvature). The auto grid floors its # refined pass-2 grid in refined_psi_grid, so it is left untouched here. return equil_params.grid_type in ("auto", "log_asymptotic") ? psi_nodes : enforce_min_spacing(psi_nodes, MIN_KNOT_SPACING) diff --git a/src/Equilibrium/DirectEquilibriumArcLength.jl b/src/Equilibrium/DirectEquilibriumArcLength.jl index 32b93a0ac..9d28370d9 100644 --- a/src/Equilibrium/DirectEquilibriumArcLength.jl +++ b/src/Equilibrium/DirectEquilibriumArcLength.jl @@ -111,7 +111,7 @@ outboard midplane (Z = zo, R > ro) after a minimum arc-length guard. prob = ODEProblem{true}(arclength_fieldline_der!, u0, (0.0, 1.0e4), params) # Tight tolerances on position (y[1:2]); integrals (y[3:5]) effectively unconstrained near x-points - reltol_vec = [equil_config.etol, equil_config.etol, 1e20, 1e20, 1e20] + reltol_vec = [equil_config.flux_surface_rtol, equil_config.flux_surface_rtol, 1e20, 1e20, 1e20] abstol_vec = [1e-8, 1e-8, 1e20, 1e20, 1e20] sol = solve(prob, BS5(); callback=callback, reltol=reltol_vec, abstol=abstol_vec, dt=2π / 200, adaptive=true, dense=false) diff --git a/src/Equilibrium/EquilibriumTypes.jl b/src/Equilibrium/EquilibriumTypes.jl index ac5a212fc..0e59cb321 100644 --- a/src/Equilibrium/EquilibriumTypes.jl +++ b/src/Equilibrium/EquilibriumTypes.jl @@ -21,10 +21,10 @@ specified in the input. - `jac_custom_power_b::Int` - Total-field exponent for a custom Jacobian; read only when `jac_type = "custom"`, ignored for named types - `jac_custom_power_r::Int` - Major-radius exponent for a custom Jacobian; read only when `jac_type = "custom"`, ignored for named types - `jac_custom_power_rc::Int` - Minor-radius (rfac) exponent for a custom Jacobian; read only when `jac_type = "custom"`, ignored for named types - - `r0exp::Float64` - Major radius normalization for CHEASE/EQDSK [m] - - `b0exp::Float64` - On-axis toroidal field normalization for CHEASE/EQDSK [T] + - `r0_norm::Float64` - Major radius normalization for CHEASE/EQDSK [m] + - `b0_norm::Float64` - On-axis toroidal field normalization for CHEASE/EQDSK [T] - `grid_type::String` - Grid type for flux surface discretization ("auto" — two-pass measured-curvature - refinement when mpsi=0, three-region log layout when mpsi>0; "ldp", "pow1", "uniform"; + refinement when mpsi=0, three-region log layout when mpsi>0; "core_edge_packed" (sin² spacing, dense at axis and edge), "pow1", "uniform"; "log_asymptotic" is a legacy alias for "auto") - `psilow::Float64` - Lower limit of normalized flux coordinate - `psihigh::Float64` - Requested upper limit of normalized flux coordinate; the value the @@ -38,15 +38,14 @@ specified in the input. - `mtheta::Int` - Number of poloidal grid points - `newq0::Float64` - Target on-axis safety factor q(0); the q and F profiles are rescaled to meet it (0 = use input value, -1 = use the axis extrapolation with its sign flipped) - - `etol::Float64` - Error tolerance for equilibrium solver + - `flux_surface_rtol::Float64` - Relative tolerance of the ODE solves that trace each flux surface - `force_termination::Bool` - Terminate after equilibrium setup (skip stability calculations) - - `use_galgrid::Bool` - Use the same grid as galerkin method """ @kwdef struct EquilibriumConfig eq_type::String = "efit" eq_filename::String = "mypath" - r0exp::Float64 = 1.0 - b0exp::Float64 = 1.0 + r0_norm::Float64 = 1.0 + b0_norm::Float64 = 1.0 jac_type::String = "hamada" power_bp::Int = 0 @@ -67,10 +66,9 @@ specified in the input. mtheta::Int = 512 newq0::Float64 = 0.0 - etol::Float64 = 1e-10 + flux_surface_rtol::Float64 = 1e-10 force_termination::Bool = false - use_galgrid::Bool = true # IMAS-specific: expected COCOS convention of the input dd.equilibrium (11=IMAS standard, 2=GPEC internal) imas_cocos::Int = 11 @@ -81,10 +79,10 @@ specified in the input. # The four `_` slots are the `power_bp/power_b/power_r/power_rc` struct fields, which @kwdef # forwards positionally. They are always derived below from `jac_type` (or `jac_custom_power_*`), # so their incoming values are ignored (hence `_`). - function EquilibriumConfig(eq_type, eq_filename, r0exp, b0exp, jac_type, _, _, _, _, + function EquilibriumConfig(eq_type, eq_filename, r0_norm, b0_norm, jac_type, _, _, _, _, jac_custom_power_bp, jac_custom_power_b, jac_custom_power_r, jac_custom_power_rc, - grid_type, psilow, psihigh, mpsi, psi_accuracy, mtheta, newq0, etol, - force_termination, use_galgrid, imas_cocos) + grid_type, psilow, psihigh, mpsi, psi_accuracy, mtheta, newq0, flux_surface_rtol, + force_termination, imas_cocos) if jac_type == "hamada" @info "Forcing hamada coordinate jacobian exponents: power_*" power_b = 0 @@ -147,10 +145,10 @@ specified in the input. @warn "psihigh = $psihigh exceeds 1.0 (separatrix); clamping to 1.0" end psihigh = min(psihigh, 1.0) - return new(eq_type, eq_filename, r0exp, b0exp, jac_type, power_bp, power_b, power_r, power_rc, + return new(eq_type, eq_filename, r0_norm, b0_norm, jac_type, power_bp, power_b, power_r, power_rc, jac_custom_power_bp, jac_custom_power_b, jac_custom_power_r, jac_custom_power_rc, - grid_type, psilow, psihigh, mpsi, psi_accuracy, mtheta, newq0, etol, - force_termination, use_galgrid, imas_cocos) + grid_type, psilow, psihigh, mpsi, psi_accuracy, mtheta, newq0, flux_surface_rtol, + force_termination, imas_cocos) end end @@ -219,20 +217,20 @@ A mutable struct holding parameters for the Large Aspect Ratio (LAR) plasma equi ## Fields: - - `lar_r0`: The major radius of the plasma [m]. - - `lar_a`: The minor radius of the plasma [m]. + - `r0`: The major radius of the plasma [m]. + - `a`: The minor radius of the plasma [m]. - `beta0`: The beta value on axis (normalized pressure). - `q0`: The safety factor on axis. - `p_pres`: The exponent for the pressure profile, defined as `p00 * (1 - (r / a)^2)^p_pres`. - `p_sig`: The exponent that determines the shape of the current-related function profile. - `sigma_type`: The type of sigma profile, can be "default" or "wesson". If "wesson", the sigma profile is defined as `sigma0 * (1 - (r / a)^2)^p_sig`. - - `mtau`: The number of grid points in the poloidal direction. + - `mtheta`: The number of grid points in the poloidal direction. - `ma`: The number of grid points in the radial direction. - `zeroth`: If set to true, it neglects the Shafranov shift """ @kwdef mutable struct LargeAspectRatioConfig - lar_r0::Float64 = 10.0 - lar_a::Float64 = 1.0 + r0::Float64 = 10.0 + a::Float64 = 1.0 beta0::Float64 = 1e-3 q0::Float64 = 1.5 qa::Float64 = 3.6 # Edge safety factor (legacy field; not consumed by current sigma_type options) @@ -240,7 +238,7 @@ A mutable struct holding parameters for the Large Aspect Ratio (LAR) plasma equi p_pres::Float64 = 2.0 p_sig::Float64 = 1.0 sigma_type::String = "default" - mtau::Int = 128 + mtheta::Int = 128 ma::Int = 128 zeroth::Bool = false end @@ -265,25 +263,25 @@ to GPEC's direct-GS pipeline; this is NOT a re-implementation of TJ. The model uses analytic profiles with exact control of both the on-axis and edge safety factors. The q profile is determined by: - f1(r) = [1 - (1-r²)^ν] / (ν·qc) + f1(r) = [1 - (1-r²)^ν] / (ν·q0) q(r) = r² / f1(r) -where ν = qa/qc is the current peaking parameter, qc is the axis q, and qa +where ν = qa/q0 is the current peaking parameter, q0 is the axis q, and qa is the edge q. All lengths are normalized to R₀, fields to B₀. The pressure -profile is p₂(r) = pc·(1-r²)^μ. +profile is p₂(r) = p0·(1-r²)^μ with μ = `pressure_peaking`. Reference: R. Fitzpatrick, TJ code, https://github.com/rfitzp/TJ """ @kwdef mutable struct TJAnalyticConfig - lar_r0::Float64 = 10.0 # Major radius R₀ [m] - lar_a::Float64 = 1.0 # Minor radius a [m] (ε = a/R₀) - qc::Float64 = 1.5 # On-axis safety factor + r0::Float64 = 10.0 # Major radius R₀ [m] + a::Float64 = 1.0 # Minor radius a [m] (ε = a/R₀) + q0::Float64 = 1.5 # On-axis safety factor qa::Float64 = 3.6 # Edge safety factor - pc::Float64 = 0.001 # Normalized on-axis pressure - mu::Float64 = 2.0 # Pressure peaking exponent: p₂ = pc·(1-r²)^μ + p0::Float64 = 0.001 # Normalized on-axis pressure + pressure_peaking::Float64 = 2.0 # Pressure peaking exponent μ: p₂ = p0·(1-r²)^μ B0::Float64 = 12.0 # On-axis toroidal field [T] ma::Int = 128 # Radial grid points - mtau::Int = 128 # Poloidal grid points + mtheta::Int = 128 # Poloidal grid points zeroth::Bool = false # If true, suppress Shafranov shift end @@ -304,7 +302,7 @@ A mutable struct holding parameters for the Solev'ev (SOL) plasma equilibrium mo - `mr`: number of radial grid zones - `mz`: number of axial grid zones - `ma`: number of flux grid zones - - `e`: elongation + - `elongation`: elongation - `a`: minor radius - `r0`: major radius - `q0`: safety factor at the o-point @@ -316,7 +314,7 @@ A mutable struct holding parameters for the Solev'ev (SOL) plasma equilibrium mo mr::Int = 128 # number of radial grid zones mz::Int = 128 # number of axial grid zones ma::Int = 128 # number of flux grid zones - e::Float64 = 1.6 # elongation + elongation::Float64 = 1.6 # elongation a::Float64 = 0.33 # minor radius r0::Float64 = 1.0 # major radius q0::Float64 = 1.9 # safety factor at the o-point diff --git a/src/Equilibrium/GridRefinement.jl b/src/Equilibrium/GridRefinement.jl index e58eb81dc..b7ce665bd 100644 --- a/src/Equilibrium/GridRefinement.jl +++ b/src/Equilibrium/GridRefinement.jl @@ -67,9 +67,9 @@ const THETA_STRIDE = 8 # for the pass-1→pass-2 shift of ψ_s. const BRACKET_COEF = 4.0 # Global minimum knot spacing. No grid — auto refinement, the near-surface bracket, or a fixed -# ldp/pow1 grid packing its edge at high mpsi — may place nodes closer than this. Below it, cubic +# core_edge_packed/pow1 grid packing its edge at high mpsi — may place nodes closer than this. Below it, cubic # high derivatives are dominated by field-line integration noise rather than curvature (e.g. an -# ldp grid at mpsi=2048 packs the edge to ~6e-7, deep in noise). Chosen well under the spacing of a +# core_edge_packed grid at mpsi=2048 packs the edge to ~6e-7, deep in noise). Chosen well under the spacing of a # converged uniform reference (~5e-4 at mpsi=2048) so it clips only pathological clustering. const MIN_KNOT_SPACING = 1.0e-4 # Near-rational resolution patch. The Δ′ extraction samples the equilibrium splines' 3rd @@ -80,7 +80,7 @@ const MIN_KNOT_SPACING = 1.0e-4 # `bracket_mandatory_nodes` then centers the surface within. The spacing is **fixed** (τ-independent): # Δ′ is a property of the rational surface, so its accuracy must not depend on the global accuracy # target τ (that only sizes the pedestal/edge grid) — a τ-dependent patch would leave Δ′ far from -# converged at the default τ. RATIONAL_RES_SPACING is set where the q‴ estimate converges (the ldp +# converged at the default τ. RATIONAL_RES_SPACING is set where the q‴ estimate converges (the core_edge_packed # mpsi ladder converges the q=2 Δ′ by h ≈ 5e-4). const RATIONAL_RES_SPACING = 5.0e-4 const RATIONAL_RES_RADIUS = 5.0e-3 @@ -345,7 +345,7 @@ end Drop interior nodes so no two kept nodes are closer than `hmin`, preserving both endpoints. Guards against noise-level packing from any generator — the auto-grid near-surface bracket, or a -fixed `ldp`/`pow1` grid whose edge spacing collapses (`~(π/2·mpsi)⁻²` for `ldp`) at high mpsi, +fixed `core_edge_packed`/`pow1` grid whose edge spacing collapses (`~(π/2·mpsi)⁻²` for `core_edge_packed`) at high mpsi, below which cubic high derivatives are integration noise, not curvature. A no-op when the grid is already coarser than `hmin` everywhere (e.g. a converged uniform reference). """ diff --git a/src/Equilibrium/InverseEquilibrium.jl b/src/Equilibrium/InverseEquilibrium.jl index 4a8d48bb6..c4d610b85 100644 --- a/src/Equilibrium/InverseEquilibrium.jl +++ b/src/Equilibrium/InverseEquilibrium.jl @@ -155,7 +155,7 @@ function equilibrium_solver(input::InverseRunInput; override_psi_nodes::Union{No N_core = round(Int, mpsi * log_core / log_total) N_mid = mpsi - N_edge - N_core sq_xs = make_optimal_psi_grid(psilow, psihigh, N_core, N_mid, N_edge) - elseif grid_type == "ldp" + elseif grid_type == "core_edge_packed" if mpsi == 0 mpsi = 128 end @@ -173,7 +173,7 @@ function equilibrium_solver(input::InverseRunInput; override_psi_nodes::Union{No else error("Unsupported grid_type: $grid_type") end - # Floor node spacing on the fixed grids (ldp/pow1 pack the edge below the integration-noise + # Floor node spacing on the fixed grids (core_edge_packed/pow1 pack the edge below the integration-noise # scale at high mpsi); the auto grid floors its refined grid in refined_psi_grid, and an # override grid arrives already floored. if override_psi_nodes === nothing && !(grid_type in ("auto", "log_asymptotic")) diff --git a/src/Equilibrium/ReadEquilibrium.jl b/src/Equilibrium/ReadEquilibrium.jl index 755a91aca..d6d0096fd 100644 --- a/src/Equilibrium/ReadEquilibrium.jl +++ b/src/Equilibrium/ReadEquilibrium.jl @@ -137,8 +137,8 @@ normalization (R0, B0 scaling), and bundles them into a `InverseRunInput` object function read_chease_binary(config::EquilibriumConfig) @info "Reading CHEASE file (Binary): $(config.eq_filename)" - R0EXP = config.r0exp - B0EXP = config.b0exp + R0EXP = config.r0_norm + B0EXP = config.b0_norm open(config.eq_filename, "r") do io seekstart(io) @@ -244,8 +244,8 @@ them into a `InverseRunInput` object. function read_chease_ascii(config::EquilibriumConfig) @info "Reading CHEASE file (ASCII): $(config.eq_filename)" lines = readlines(config.eq_filename) - R0EXP = config.r0exp - B0EXP = config.b0exp + R0EXP = config.r0_norm + B0EXP = config.b0_norm # --- Parse Header (FORMAT 10: 3I5) --- header_parts = split(lines[1]) diff --git a/src/ErrorFields/Sensitivity.jl b/src/ErrorFields/Sensitivity.jl index 24722a0f4..61ec1a404 100644 --- a/src/ErrorFields/Sensitivity.jl +++ b/src/ErrorFields/Sensitivity.jl @@ -118,7 +118,7 @@ function _rigidly_perturbed(cs::CoilSet, kind::Symbol, axis::Int, h::Real, pivot end nom(i) = pivot === nothing ? Float64[] : fill(pivot[i], cs.ncoil) cfg = CoilSetConfig(; tiltx=component(1), tilty=component(2), tiltz=component(3), - xnom=nom(1), ynom=nom(2), znom=nom(3), tilt_in_meters=false) + rotation_center_x=nom(1), rotation_center_y=nom(2), rotation_center_z=nom(3), tilt_in_meters=false) return apply_transforms(cs, cfg; n_tilt=1) end diff --git a/src/ForceFreeStates/CoreTypes.jl b/src/ForceFreeStates/CoreTypes.jl index 8ef08581d..55fca1f3f 100644 --- a/src/ForceFreeStates/CoreTypes.jl +++ b/src/ForceFreeStates/CoreTypes.jl @@ -120,32 +120,28 @@ gpec.toml. - `verbose::Bool` - Enable verbose output - `local_stability_flag::Bool` - Enable local stability analysis (`D_I` and ballooning) - `vac_flag::Bool` - Enable vacuum region calculation - - `mthvac::Int` - Number of vacuum poloidal grid points (corresponds to `mtheta` in VacuumInput) - - `nzvac::Int` - Number of vacuum toroidal grid points (corresponds to `nzeta` in VacuumInput3D) + - `mtheta_vacuum::Int` - Number of vacuum poloidal grid points (corresponds to `mtheta` in VacuumInput) + - `nzeta_vacuum::Int` - Number of vacuum toroidal grid points (corresponds to `nzeta` in VacuumInput3D) - `sing_start::Int` - Start integration at the `sing_start`-th singular surface - `frobenius_psi_max::Float64` - Largest starting ψ_N at which the Frobenius start is used. The Frobenius start [Glasser 2016 Eq. 51] is a power series about the magnetic axis, so an integration beginning above this value (an interior start from a raised `psilow`, or `qlow` above q₀) instead starts every solution with zero displacement and unit "momentum" (U₁ = 0, U₂ = I), the initialization Fortran DCON always uses, and warns that it did. `0` selects the fixed start everywhere, silently. Default 0.01, which keeps every axis-started case on the Frobenius start. - `nn_low::Int` - Lower bound for toroidal modes - `nn_high::Int` - Upper bound for toroidal modes - `delta_mlow::Int` - Expands lower bound of Fourier harmonics by delta_mlow - `delta_mhigh::Int` - Expands upper bound of Fourier harmonics by delta_mhigh - - `nstep::Int` - Maximum number of integration steps (not yet implemented) - - `ksing::Int` - Singular surface handling parameter - `eulerlagrange_tolerance::Float64` - Relative tolerance for ODE integration of Euler-Lagrange equations - - `ucrit::Float64` - Critical value of unorm ratio to trigger solution normalization. In the standard path it triggers Gaussian reduction; in the Riccati path it triggers `renormalize_riccati_inplace!`. Default `1e4` empirically keeps max(|U₁|, |U₂|) in O(1)–O(10⁴) over the integration domain on DIII-D / Solovev sweeps; lower triggers excess renorms without accuracy gain, higher risks overflow before the next renorm. + - `renorm_threshold::Float64` - Critical value of unorm ratio to trigger solution normalization. In the standard path it triggers Gaussian reduction; in the Riccati path it triggers `renormalize_riccati_inplace!`. Default `1e4` empirically keeps max(|U₁|, |U₂|) in O(1)–O(10⁴) over the integration domain on DIII-D / Solovev sweeps; lower triggers excess renorms without accuracy gain, higher risks overflow before the next renorm. - `numsteps_init::Int` - Initial array size for ODE data storage - `numunorms_init::Int` - Initial array size for solution normalization data - `singfac_min::Float64` - Fractional distance from rational q at which ideal jump condition is enforced - - `set_psilim_via_dmlim::Bool` - Truncate the integration domain at `(last_rational_q + dmlim) / n` rather than at `qhigh` / `psihigh`. Fortran STRIDE found that truncating ~20 % above the outermost rational (`dmlim = 0.2`) avoids a numerical kink instability in δW that appears when the integration ends too close to or just below a rational surface. **For diverted equilibria where q → ∞ at the separatrix** (e.g. DIII-D geqdsks, the bulk of production use) this costs negligible physical domain because rationals get arbitrarily dense near the LCFS — `set_psilim_via_dmlim = true` is the safe and recommended default. **For limited circular / analytical equilibria with finite q at the edge** (Solovev, LAR scans), rationals are sparse and 20 % above the last rational chops off too much edge, so set `set_psilim_via_dmlim = false` and let `qhigh` / `psihigh` control the truncation. Multi-`n` runs are not supported by this truncation (the "outermost rational + dmlim / n" depends on which `n`); when `set_psilim_via_dmlim = true` with `nn_low != nn_high`, `sing_lim!` warns and falls back to `qhigh` / `psihigh`. Default `true`. - - `dmlim::Float64` - Distance beyond last rational surface (normalised ∈ [0,1) in units of 1/n). Only used when `set_psilim_via_dmlim` is true. Fortran STRIDE convention is 0.2 (truncate 20 % of one rational-surface spacing above the last surface), retained here. - - `sing_order::Int` - Order of singular layer (Frobenius) expansion at rational surfaces. Default 6 (Fortran STRIDE convention for Δ' calculations; lower values trade accuracy for speed). + - `truncate_at_rational_offset::Bool` - Truncate the integration domain at `(last_rational_q + rational_offset_fraction) / n` rather than at `qhigh` / `psihigh`. Fortran STRIDE found that truncating ~20 % above the outermost rational (`rational_offset_fraction = 0.2`) avoids a numerical kink instability in δW that appears when the integration ends too close to or just below a rational surface. **For diverted equilibria where q → ∞ at the separatrix** (e.g. DIII-D geqdsks, the bulk of production use) this costs negligible physical domain because rationals get arbitrarily dense near the LCFS — `truncate_at_rational_offset = true` is the safe and recommended default. **For limited circular / analytical equilibria with finite q at the edge** (Solovev, LAR scans), rationals are sparse and 20 % above the last rational chops off too much edge, so set `truncate_at_rational_offset = false` and let `qhigh` / `psihigh` control the truncation. Multi-`n` runs are not supported by this truncation (the "outermost rational + rational_offset_fraction / n" depends on which `n`); when `truncate_at_rational_offset = true` with `nn_low != nn_high`, `sing_lim!` warns and falls back to `qhigh` / `psihigh`. Default `true`. + - `rational_offset_fraction::Float64` - Distance beyond last rational surface (normalised ∈ [0,1) in units of 1/n). Only used when `truncate_at_rational_offset` is true. Fortran STRIDE convention is 0.2 (truncate 20 % of one rational-surface spacing above the last surface), retained here. + - `frobenius_order::Int` - Order of singular layer (Frobenius) expansion at rational surfaces. Default 6 (Fortran STRIDE convention for Δ' calculations; lower values trade accuracy for speed). - `qhigh::Float64` - Integration terminated at q limit determined by minimum of qhigh and qa from equil - `kinetic_source::String` - Kinetic matrix source: "fixed" (X-shaped test matrices scaled by kinetic_factor relative to ideal matrix Frobenius norms; Ak, Dk, Hk Hermitian, Bk, Ck, Ek non-Hermitian), "calculated" (PENTRC — not yet implemented) - `kinetic_factor::Float64` - Dimensionless scaling factor for kinetic matrices. Zero (the default) disables the kinetic path; any positive value enables it and scales the kinetic matrices: when kinetic_source="fixed", scales X-shaped test matrices relative to ideal matrix norms; when kinetic_source="calculated", applied as uniform post-hoc multiplier to W and T components. - `qlow::Float64` - Integration terminated at q limit determined by minimum of qlow and q0 from equil - - `psiedge::Float64` - If less than psilim, records a dW(ψ) diagnostic scan over [psiedge, psilim] on odet.edge_scan. The integration domain (psilim) is always controlled by qhigh / psihigh and is not modified by this scan (unless `truncate_at_dW_peak=true`, see caveats below). - - `truncate_at_dW_peak::Bool` - When `true` and `psiedge < psilim`, the edge-dW scan's peak is adopted as the physical plasma edge, reproducing the ode_record_edge heuristic of Fortran STRIDE. `intr.psilim`/`intr.qlim`/`odet.u` are pulled back to the peak, fixups recorded beyond it are discarded, `q1lim` and the rational-surface list are re-derived at the new edge, and on the Riccati path the Δ' chunks are cut back to it. The result is the same physics as setting `psihigh` to the peak by hand. Δ' still depends on where in the edge band the peak falls, so set this deliberately when you mean to measure against the peak-defined edge (e.g. edge-mode regimes); leave at `false` (default) for the full domain at `qhigh` / `psihigh` / `dmlim`. - - `diagnose::Bool` - Enable diagnostic output (not yet implemented) - - `diagnose_ca::Bool` - Enable asymptotic coefficient diagnostics (not yet implemented) + - `dW_edge_scan_start::Float64` - If less than psilim, records a dW(ψ) diagnostic scan over [dW_edge_scan_start, psilim] on odet.edge_scan. The integration domain (psilim) is always controlled by qhigh / psihigh and is not modified by this scan (unless `truncate_at_dW_peak=true`, see caveats below). + - `truncate_at_dW_peak::Bool` - When `true` and `dW_edge_scan_start < psilim`, the edge-dW scan's peak is adopted as the physical plasma edge, reproducing the ode_record_edge heuristic of Fortran STRIDE. `intr.psilim`/`intr.qlim`/`odet.u` are pulled back to the peak, fixups recorded beyond it are discarded, `q1lim` and the rational-surface list are re-derived at the new edge, and on the Riccati path the Δ' chunks are cut back to it. The result is the same physics as setting `psihigh` to the peak by hand. Δ' still depends on where in the edge band the peak falls, so set this deliberately when you mean to measure against the peak-defined edge (e.g. edge-mode regimes); leave at `false` (default) for the full domain at `qhigh` / `psihigh` / `rational_offset_fraction`. - `write_outputs_to_HDF5::Bool` - Write results to HDF5 format - `HDF5_filename::String` - Name of HDF5 output file - `save_interval::Int` - Save every Nth ODE step (1=all, 10=every 10th). Always saves near rational surfaces. (Same as `euler_step` in the Fortran) @@ -158,31 +154,27 @@ gpec.toml. verbose::Bool = true local_stability_flag::Bool = false vac_flag::Bool = false - mthvac::Int = 480 - nzvac::Int = 1 + mtheta_vacuum::Int = 480 + nzeta_vacuum::Int = 1 sing_start::Int = 0 nn_low::Int = 0 nn_high::Int = 0 delta_mlow::Int = 0 delta_mhigh::Int = 0 - nstep::Int = typemax(Int) - ksing::Int = -1 eulerlagrange_tolerance::Float64 = 1e-8 - ucrit::Float64 = 1e4 + renorm_threshold::Float64 = 1e4 numsteps_init::Int = 4000 numunorms_init::Int = 100 singfac_min::Float64 = 1e-4 # Matches Fortran STRIDE; required nonzero for the Riccati path. - set_psilim_via_dmlim::Bool = true # Safe default for diverted equilibria (most production use); set false for limited/analytical (LAR, Solovev). Auto-skipped for multi-n. See docstring. - dmlim::Float64 = 0.2 - sing_order::Int = 6 + truncate_at_rational_offset::Bool = true # Safe default for diverted equilibria (most production use); set false for limited/analytical (LAR, Solovev). Auto-skipped for multi-n. See docstring. + rational_offset_fraction::Float64 = 0.2 + frobenius_order::Int = 6 qhigh::Float64 = 1e3 kinetic_source::String = "fixed" kinetic_factor::Float64 = 0.0 qlow::Float64 = 0.0 - psiedge::Float64 = 0.99 + dW_edge_scan_start::Float64 = 0.99 truncate_at_dW_peak::Bool = false # Edge-dW peak becomes new physical edge; Δ' BVP made self-consistent. See docstring. - diagnose::Bool = false - diagnose_ca::Bool = false write_outputs_to_HDF5::Bool = true HDF5_filename::String = "gpec.h5" save_interval::Int = 3 diff --git a/src/ForceFreeStates/EulerLagrange.jl b/src/ForceFreeStates/EulerLagrange.jl index 345495fc6..c28997646 100644 --- a/src/ForceFreeStates/EulerLagrange.jl +++ b/src/ForceFreeStates/EulerLagrange.jl @@ -1,7 +1,7 @@ """ EdgeScanState -Holds the state and results for the edge dW stability scan over ψ ∈ [psiedge, psilim]. +Holds the state and results for the edge dW stability scan over ψ ∈ [dW_edge_scan_start, psilim]. Initialized and populated by `findmax_dW_edge!`; results written to HDF5 under `EdgeScan/`. The energies are generalized (W, N) pencil values: power-normalized and invariant to the working (Jacobian) coordinate (see `power_norm_matrix!`). @@ -94,7 +94,7 @@ and a small set of temporary matrices and factors used to compute singular-layer - `index::Array{Int,2}` - Index matrix used for sorting solution norms with shape `(numpert_total, numunorms_init)`. - - `sing_flag::Vector{Bool}` - Boolean flags indicating which stored normalizations correspond to singular solutions # Edge dW scan state and results (disabled sentinel when psiedge >= psilim, i.e. no edge scan) + - `sing_flag::Vector{Bool}` - Boolean flags indicating which stored normalizations correspond to singular solutions # Edge dW scan state and results (disabled sentinel when dW_edge_scan_start >= psilim, i.e. no edge scan) (length `numunorms_init`). - `zeroed_idx::Vector{Vector{Int}}` - For each ideal rational surface jump, a vector of indices of solutions that were zeroed. # Data for integrator - `fixfac::Array{ComplexF64,3}` - Fix-up factors for Gaussian reduction with shape `(numpert_total, numpert_total, numunorms_init)`. @@ -122,7 +122,7 @@ and a small set of temporary matrices and factors used to compute singular-layer ca_l::Array{ComplexF64,4} = zeros(ComplexF64, numpert_total, numpert_total, 2, msing) ca_populated::Bool = false - # Edge dW scan state and results (disabled sentinel when psiedge >= psilim, i.e. no edge scan) + # Edge dW scan state and results (disabled sentinel when dW_edge_scan_start >= psilim, i.e. no edge scan) edge_scan::EdgeScanState = EdgeScanState(numpert_total, 0) # Data for integrator @@ -337,7 +337,7 @@ end forward_eulerlagrange_integration(ctrl, equil, mats, intr; verbose=ctrl.verbose) -> (odet, nothing, nothing, nothing) Forward branch of [`eulerlagrange_integration`](@ref): integrates chunk by chunk from the axis, -applying Gaussian reduction whenever a solution norm ratio exceeds `ctrl.ucrit` and undoing it +applying Gaussian reduction whenever a solution norm ratio exceeds `ctrl.renorm_threshold` and undoing it via `transform_u!` at the end, so `odet.u_store` comes back dense in the axis basis. Call directly to force this branch regardless of `ctrl.integrator`; `verbose` overrides `ctrl.verbose` for progress logging. @@ -904,7 +904,7 @@ function integrate_el_region!( near_start = abs(odet.q - q_start) < near_q_frac * q_range || steps_in_segment[] == 1 near_end = abs(odet.q - q_end) < near_q_frac * q_range # Always save in the edge scan region so findmax_dW_edge! has dense q coverage. - in_edge_scan = ctrl.psiedge < intr.psilim && integrator.t >= ctrl.psiedge + in_edge_scan = ctrl.dW_edge_scan_start < intr.psilim && integrator.t >= ctrl.dW_edge_scan_start if near_start || near_end || (odet.total_steps % ctrl.save_interval == 0) || in_edge_scan # q at the accepted point, not the last internal Runge-Kutta stage @@ -936,7 +936,7 @@ Computes norms of the solution vectors of the array `u` and normalizes them if this is not the first call after a fixup. Formerly `ode_unorm!`. Throws an error if any vector norm is zero. It then compares the variation in norms relative to initial values after a fixup, and applies the Gaussian reduction via -`apply_gaussian_reduction!` if the variation exceeds `ctrl.ucrit` or if `sing_flag` is true. +`apply_gaussian_reduction!` if the variation exceeds `ctrl.renorm_threshold` or if `sing_flag` is true. Performs the same function as `ode_unorm` in the Fortran code, with minor differences in indexing and array handling. @@ -970,7 +970,7 @@ function compute_solution_norms!(u::Array{ComplexF64,3}, odet::OdeState, ctrl::F else odet.unorm ./= odet.unorm0 uratio = maximum(odet.unorm) / minimum(odet.unorm) - if uratio > ctrl.ucrit || sing_flag + if uratio > ctrl.renorm_threshold || sing_flag # TODO: add resizing logic here as well if odet.ifix < ctrl.numunorms_init odet.ifix += 1 @@ -1033,7 +1033,7 @@ end """ findmax_dW_edge!(odet::OdeState, ctrl::ForceFreeStatesControl, equil::Equilibrium.PlasmaEquilibrium, mats::MatrixSplines, intr::ForceFreeStatesInternal) -Records the total dW in the integration region between `ctrl.psiedge` and +Records the total dW in the integration region between `ctrl.dW_edge_scan_start` and `ctrl.psilim`. This performs the same function as `ode_record_edge` in the Fortran, but everything is now done post-integration which cleans up the logic, i.e. no "_edge" arrays. @@ -1049,8 +1049,8 @@ for clarity. We create the wv matrix spline once prior to the loop. """ function findmax_dW_edge!(odet::OdeState, ctrl::ForceFreeStatesControl, equil::Equilibrium.PlasmaEquilibrium, mats::MatrixSplines, intr::ForceFreeStatesInternal) - # Find the first ODE step at or past psiedge; all subsequent steps are contiguous edge steps - edge_start = findfirst(i -> odet.psi_store[i] >= ctrl.psiedge, 1:odet.step) + # Find the first ODE step at or past dW_edge_scan_start; all subsequent steps are contiguous edge steps + edge_start = findfirst(i -> odet.psi_store[i] >= ctrl.dW_edge_scan_start, 1:odet.step) N_edge = odet.step - edge_start + 1 # Initialize EdgeScanState sized exactly to the number of edge steps @@ -1060,7 +1060,7 @@ function findmax_dW_edge!(odet::OdeState, ctrl::ForceFreeStatesControl, equil::E es.psi .= odet.psi_store[edge_start:odet.step] es.q .= odet.q_store[edge_start:odet.step] - # Create a rough spline for wv matrix between psiedge -> psilim so we can approximate dW + # Create a rough spline for wv matrix between dW_edge_scan_start -> psilim so we can approximate dW es.wvmat = free_compute_wv_spline(ctrl, equil, intr) # Loop with compact index j into EdgeScanState; ODE index is edge_start + j - 1. @@ -1088,13 +1088,13 @@ end """ scan_edge_dW!(odet, ctrl, equil, mats, intr; verbose=ctrl.verbose) -> Bool -Record the edge-dW scan over [psiedge, psilim] on `odet.edge_scan`. With `truncate_at_dW_peak`, the +Record the edge-dW scan over [dW_edge_scan_start, psilim] on `odet.edge_scan`. With `truncate_at_dW_peak`, the scan's peak becomes the plasma edge via `truncate_integration!`; otherwise the integration is left untouched. Returns whether it truncated. """ function scan_edge_dW!(odet::OdeState, ctrl::ForceFreeStatesControl, equil::Equilibrium.PlasmaEquilibrium, mats::MatrixSplines, intr::ForceFreeStatesInternal; verbose::Bool=ctrl.verbose) - ctrl.psiedge < intr.psilim || return false + ctrl.dW_edge_scan_start < intr.psilim || return false saved_psifac, saved_u = odet.psifac, copy(odet.u) peak_step = findmax_dW_edge!(odet, ctrl, equil, mats, intr) psi_peak, q_peak = odet.psi_store[peak_step], odet.q_store[peak_step] diff --git a/src/ForceFreeStates/Free.jl b/src/ForceFreeStates/Free.jl index e3bb3fdd8..316d48644 100644 --- a/src/ForceFreeStates/Free.jl +++ b/src/ForceFreeStates/Free.jl @@ -102,7 +102,7 @@ scaled in place. """ function compute_scaled_wv(ctrl::ForceFreeStatesControl, equil::Equilibrium.PlasmaEquilibrium, intr::ForceFreeStatesInternal) (; mlow, mhigh, nlow, nhigh, psilim, qlim, wall_settings) = intr - vac_inputs = Vacuum.VacuumInput(equil, psilim, ctrl.mthvac, ctrl.nzvac, mlow:mhigh, nlow:nhigh) + vac_inputs = Vacuum.VacuumInput(equil, psilim, ctrl.mtheta_vacuum, ctrl.nzeta_vacuum, mlow:mhigh, nlow:nhigh) vac = Vacuum.compute_vacuum_response(vac_inputs, wall_settings) wv = vac.wv singfac = vec((mlow:mhigh) .- qlim .* (nlow:nhigh)') @@ -219,7 +219,7 @@ q-window minimum. # Number of psi grid points for the spline: 4 per q-window minimum # TODO: 4 spline points is arbitrary - is there a better way? - qedge = profiles.q_spline(ctrl.psiedge) + qedge = profiles.q_spline(ctrl.dW_edge_scan_start) npsi = max(4, ceil(Int, (intr.qlim - qedge) * intr.nhigh * 4)) psi_array = zeros!(pool, Float64, npsi + 1) wv_array = zeros!(pool, ComplexF64, npsi + 1, intr.numpert_total, intr.numpert_total) @@ -228,7 +228,7 @@ q-window minimum. # Space points evenly in q over [qedge, qlim] (i=1 → qedge, i=npsi+1 → qlim) qi = qedge + (intr.qlim - qedge) * ((i - 1) / npsi) - psii = ctrl.psiedge + (intr.psilim - ctrl.psiedge) * ((i - 1) / npsi) + psii = ctrl.dW_edge_scan_start + (intr.psilim - ctrl.dW_edge_scan_start) * ((i - 1) / npsi) psi_array[i] = find_zero( (psi -> profiles.q_spline(psi) - qi, psi -> profiles.q_deriv(psi)), @@ -236,7 +236,7 @@ q-window minimum. ) # Compute raw vacuum matrix at the actual scan psi (singfac NOT applied; free_compute_total applies it analytically) - vac_inputs = Vacuum.VacuumInput(equil, psi_array[i], ctrl.mthvac, ctrl.nzvac, intr.mlow:intr.mhigh, intr.nlow:intr.nhigh) + vac_inputs = Vacuum.VacuumInput(equil, psi_array[i], ctrl.mtheta_vacuum, ctrl.nzeta_vacuum, intr.mlow:intr.mhigh, intr.nlow:intr.nhigh) vac = Vacuum.compute_vacuum_response(vac_inputs, intr.wall_settings) @views wv_array[i, :, :] .= vac.wv end @@ -256,7 +256,7 @@ end Compute total complex energy eigenvalue (total1). This is a trimmed down version of `free_run` that only computes the total energy eigenvalue for the mode unstable mode, used in `findmax_dW_edge!` -which calls this function at each step in the psiedge -> psilim region of integration. This performs +which calls this function at each step in the dW_edge_scan_start -> psilim region of integration. This performs the same function as `free_test` in the Fortran code, except we have moved the creation of the wv matrix spline to `free_compute_wv_spline` and pass it in `odet.edge_scan.wvmat` (a complex-valued spline). """ diff --git a/src/ForceFreeStates/Riccati/Driver.jl b/src/ForceFreeStates/Riccati/Driver.jl index a6255d16c..b5defda2f 100644 --- a/src/ForceFreeStates/Riccati/Driver.jl +++ b/src/ForceFreeStates/Riccati/Driver.jl @@ -48,7 +48,7 @@ recover S = U₁·U₂⁻¹ by renormalization. This achieves the same Riccati t This works because the EL ODE is **linear** in (U₁, U₂): the RHS does not grow with |S|, so relative error control is faithful even when S is large. Renormalization triggered by -`renormalize_riccati_inplace!` in the callback (when max(|U₁|) or max(|U₂|) > ucrit) keeps +`renormalize_riccati_inplace!` in the callback (when max(|U₁|) or max(|U₂|) > renorm_threshold) keeps both matrices bounded, preventing overflow and maintaining a well-conditioned state for the solver — exactly analogous to Gaussian reduction in the standard ODE. @@ -69,7 +69,7 @@ During chunk integration (with sing_der! as ODE RHS): u[:,:,1] = U₁ (starts as S_prev, evolves toward new S) u[:,:,2] = U₂ (starts as I, evolves with EL dynamics) -After renormalization (at crossing or when norms exceed ucrit): +After renormalization (at crossing or when norms exceed renorm_threshold): u[:,:,1] = S = U₁ · U₂⁻¹ u[:,:,2] = I diff --git a/src/ForceFreeStates/Riccati/Propagators.jl b/src/ForceFreeStates/Riccati/Propagators.jl index 285695c5b..852cfd99c 100644 --- a/src/ForceFreeStates/Riccati/Propagators.jl +++ b/src/ForceFreeStates/Riccati/Propagators.jl @@ -201,7 +201,7 @@ Callback function for the Riccati ODE integrator. Handles tolerance updates, renormalization, and storage at each step. Uses `sing_der!` as the ODE RHS: u[:,:,1] = U₁ (starts as S), u[:,:,2] = U₂ (starts as I). -When max(|U₁|) or max(|U₂|) exceeds `ctrl.ucrit`, applies `renormalize_riccati_inplace!` +When max(|U₁|) or max(|U₂|) exceeds `ctrl.renorm_threshold`, applies `renormalize_riccati_inplace!` to compute S = U₁·U₂⁻¹ and reset U₂ = I. This is the Riccati analogue of Gaussian reduction in the standard `integrator_callback!`, and keeps the ODE inputs bounded. """ @@ -214,11 +214,11 @@ function riccati_integrator_callback!(integrator) # Use unified tolerance (matches integrate_el_region! on develop) integrator.opts.reltol = ctrl.eulerlagrange_tolerance - # Renormalize when norms exceed ucrit (analogous to Gaussian reduction in integrator_callback!) + # Renormalize when norms exceed renorm_threshold (analogous to Gaussian reduction in integrator_callback!) # During sing_der! integration: u[:,:,1]=U₁ (grows), u[:,:,2]=U₂ (grows). # Renorm computes S = U₁·U₂⁻¹ and resets U₂ = I, keeping inputs bounded. - if maximum(abs, @view(integrator.u[:, :, 1])) > ctrl.ucrit || - maximum(abs, @view(integrator.u[:, :, 2])) > ctrl.ucrit + if maximum(abs, @view(integrator.u[:, :, 1])) > ctrl.renorm_threshold || + maximum(abs, @view(integrator.u[:, :, 2])) > ctrl.renorm_threshold renormalize_riccati_inplace!(integrator.u, intr.numpert_total) end @@ -244,7 +244,7 @@ end Integrate the dual Riccati ODE from `chunk.psi_start` to `chunk.psi_end`. Uses `sing_der!` as the ODE RHS with `riccati_integrator_callback!`, which applies -`renormalize_riccati_inplace!` (instead of Gaussian reduction) when norms exceed ucrit. +`renormalize_riccati_inplace!` (instead of Gaussian reduction) when norms exceed renorm_threshold. Starting state: u[:,:,1] = S_prev, u[:,:,2] = I (set by initialization or previous renorm). Ending state: u[:,:,1] = U₁, u[:,:,2] = U₂ (ratio S = U₁·U₂⁻¹ is the updated Riccati matrix). """ @@ -263,7 +263,7 @@ function riccati_integrate_chunk!( # When a crossing follows (needs_crossing=true), skip renorm so that ca_l is computed # from the bounded (U₁, U₂) state in riccati_cross_ideal_singular_surf!: this gives # consistent normalization with ca_r (also from pre-renorm state), enabling correct Δ'. - # The callback guarantees max(|U₁|), max(|U₂|) ≤ ucrit, so the state is bounded. + # The callback guarantees max(|U₁|), max(|U₂|) ≤ renorm_threshold, so the state is bounded. if !chunk.needs_crossing renormalize_riccati_inplace!(odet.u, intr.numpert_total) end @@ -302,7 +302,7 @@ In-place Riccati renormalization on an arbitrary N×N×2 array: u[:,:,2] = I Used in `riccati_integrator_callback!` to renormalize the integrator's live state -when column norms grow beyond `ctrl.ucrit`, analogous to Gaussian reduction in the +when column norms grow beyond `ctrl.renorm_threshold`, analogous to Gaussian reduction in the standard ODE. This keeps the inputs to `sing_der!` bounded, preventing the same exponential growth that occurs in the standard (non-Riccati) ODE without Gaussian reduction. """ diff --git a/src/ForceFreeStates/Surfaces/Asymptotics.jl b/src/ForceFreeStates/Surfaces/Asymptotics.jl index 26746c0ca..de09720b4 100644 --- a/src/ForceFreeStates/Surfaces/Asymptotics.jl +++ b/src/ForceFreeStates/Surfaces/Asymptotics.jl @@ -13,7 +13,7 @@ See equations 41-48 in the Glasser Phys. Plasmas 2016 112506 for the mathematica ### Arguments - `singp::SingType`: Singular surface parameters - - `sing_order`: Expansion order, defaulting to `ctrl.sing_order`. The Galerkin path overrides it + - `sing_order`: Expansion order, defaulting to `ctrl.frobenius_order`. The Galerkin path overrides it per surface (`gal_sing_order`, raised for high-Mercier-index surfaces). ### Returns @@ -28,7 +28,7 @@ function compute_sing_asymptotics( intr::ForceFreeStatesInternal; sig::Float64=1.0, alpha_override::Union{Nothing,Vector{ComplexF64}}=nothing, - sing_order::Int=ctrl.sing_order + sing_order::Int=ctrl.frobenius_order ) # Allocations @@ -169,7 +169,7 @@ Add a spline for F directly instead of the lower triangular factorization to avo mats::MatrixSplines, intr::ForceFreeStatesInternal; sig::Float64=1.0, - sing_order::Int=ctrl.sing_order + sing_order::Int=ctrl.frobenius_order ) q_spline = profiles.q_spline diff --git a/src/ForceFreeStates/Surfaces/Finding.jl b/src/ForceFreeStates/Surfaces/Finding.jl index 18c45def7..8d8b5d872 100644 --- a/src/ForceFreeStates/Surfaces/Finding.jl +++ b/src/ForceFreeStates/Surfaces/Finding.jl @@ -97,17 +97,17 @@ end Compute and set integration ψ, q, and q' limits by handling cases where user truncates before the last singular surface. Performs a similar function to `sing_lim` -in the Fortran code. Main differences include renaming of sas_flag -> set_psilim_via_dmlim, +in the Fortran code. Main differences include renaming of sas_flag -> truncate_at_rational_offset, removing dW edge storage variables since we now store all integration terms in memory, and simplification of the logic. The target value `qlim` is first determined from user-specified control parameters -(`ctrl.qhigh` or `ctrl.dmlim`), subject to the constraint that it does not exceed -`equil.params.qmax`. If `set_psilim_via_dmlim` is true, `qlim` is adjusted to the largest -rational surface such that `nq + dmlim < qmax`. If `qlim < qmax`, a Newton iteration is +(`ctrl.qhigh` or `ctrl.rational_offset_fraction`), subject to the constraint that it does not exceed +`equil.params.qmax`. If `truncate_at_rational_offset` is true, `qlim` is adjusted to the largest +rational surface such that `nq + rational_offset_fraction < qmax`. If `qlim < qmax`, a Newton iteration is performed to find the corresponding `psilim` to integrate to. -Note that the Newton iteration will be triggered if either `set_psilim_via_dmlim` is true +Note that the Newton iteration will be triggered if either `truncate_at_rational_offset` is true or `ctrl.qhigh < equil.params.qmax`. Otherwise, the equilibrium edge values are used. """ function sing_lim!(intr::ForceFreeStatesInternal, ctrl::ForceFreeStatesControl, equil::Equilibrium.PlasmaEquilibrium) @@ -119,22 +119,22 @@ function sing_lim!(intr::ForceFreeStatesInternal, ctrl::ForceFreeStatesControl, intr.q1lim = profiles.q_deriv(profiles.xs[end]; hint=Ref(profiles.npts_minus_1)) intr.psilim = equil.params.psihigh_resolved - # Optionally override qlim based on dmlim (Fortran sas_flag=t equivalent). The cutoff reads + # Optionally override qlim based on rational_offset_fraction (Fortran sas_flag=t equivalent). The cutoff reads # the *resolved* toroidal range on `intr`, so callers must assign intr.nlow / intr.nhigh # before calling; an unresolved range is an error rather than a silent change of truncation - # strategy. Multi-n runs are not supported — the "outermost rational + dmlim/n" cutoff depends + # strategy. Multi-n runs are not supported — the "outermost rational + rational_offset_fraction/n" cutoff depends # on which n is used — and fall back to qhigh / psihigh truncation with a warning. - if ctrl.set_psilim_via_dmlim && intr.nlow <= 0 - error("sing_lim!: set_psilim_via_dmlim = true requires a resolved toroidal range, but got intr.nlow=$(intr.nlow). " * + if ctrl.truncate_at_rational_offset && intr.nlow <= 0 + error("sing_lim!: truncate_at_rational_offset = true requires a resolved toroidal range, but got intr.nlow=$(intr.nlow). " * "Assign intr.nlow / intr.nhigh (from ctrl.nn_low / ctrl.nn_high) before calling sing_lim!, " * - "or set set_psilim_via_dmlim = false to truncate via qhigh / psihigh instead.") - elseif ctrl.set_psilim_via_dmlim && intr.nlow != intr.nhigh - @warn "set_psilim_via_dmlim = true is ignored for multi-n runs (nn_low=$(intr.nlow), nn_high=$(intr.nhigh)); falling back to qhigh / psihigh truncation." - elseif ctrl.set_psilim_via_dmlim - @info "Setting psilim via dmlim: initial qlim = $(@sprintf("%.3f", intr.qlim)), dmlim = $(@sprintf("%.3f", ctrl.dmlim))" - # Normalize dmlim ∈ [0,1) - dmlim = mod(ctrl.dmlim, 1.0) - intr.qlim = (trunc(Int, intr.nlow * intr.qlim) + dmlim) / intr.nlow + "or set truncate_at_rational_offset = false to truncate via qhigh / psihigh instead.") + elseif ctrl.truncate_at_rational_offset && intr.nlow != intr.nhigh + @warn "truncate_at_rational_offset = true is ignored for multi-n runs (nn_low=$(intr.nlow), nn_high=$(intr.nhigh)); falling back to qhigh / psihigh truncation." + elseif ctrl.truncate_at_rational_offset + @info "Setting psilim via rational_offset_fraction: initial qlim = $(@sprintf("%.3f", intr.qlim)), rational_offset_fraction = $(@sprintf("%.3f", ctrl.rational_offset_fraction))" + # Normalize rational_offset_fraction ∈ [0,1) + rational_offset_fraction = mod(ctrl.rational_offset_fraction, 1.0) + intr.qlim = (trunc(Int, intr.nlow * intr.qlim) + rational_offset_fraction) / intr.nlow # Reduce qlim if above qmax while intr.qlim > equil.params.qmax @@ -142,7 +142,7 @@ function sing_lim!(intr::ForceFreeStatesInternal, ctrl::ForceFreeStatesControl, end end - # If set_psilim_via_dmlim decreased qlim or qhigh < qmax, we need to find the precise psilim via newton iteration + # If truncate_at_rational_offset decreased qlim or qhigh < qmax, we need to find the precise psilim via newton iteration if intr.qlim < equil.params.qmax # Find nearest ψ index where q ≈ qlim _, jpsi = findmin(abs.(profiles.q_spline.y .- intr.qlim)) diff --git a/src/ForcingTerms/CoilFourier.jl b/src/ForcingTerms/CoilFourier.jl index 7cef3b70e..75b967640 100644 --- a/src/ForcingTerms/CoilFourier.jl +++ b/src/ForcingTerms/CoilFourier.jl @@ -64,7 +64,7 @@ Defaults to the **outermost computed surface** `equil.rzphi_xs[end]` (i.e. `psih square root: `rfac = SQRT(crzphi_f(1))` (`coil/field.F:170`, where `crzphi_f(1)` is `rzphi_rsquared` and `crzphi_f(2)` is `rzphi_offset`); `coil/field.F:133` calls that mesh the "control surface mesh". `psilim = psihigh` (`dcon/sing.f:170`) and is only ever moved *inward* -by `sas_flag`/`qhigh`/`psiedge` truncation. Since `psihigh` is the last knot of the radial +by `sas_flag`/`qhigh`/`dW_edge_scan_start` truncation. Since `psihigh` is the last knot of the radial grid these splines are built on (`equil/inverse.f:142`), Fortran evaluates exactly ON the last knot and never extrapolates. The docs state it directly: the external field is specified "on the surface of the GPEC plasma boundary defined by the psihigh variable in equil.in" @@ -85,7 +85,7 @@ auto grid was refined. NOTE ON THE DEFAULT: the physically correct control surface is `psilim`, the *integration* limit, not `psihigh`, the *equilibrium spline* limit. They are equal unless -`dmlim`/`qhigh`/`psiedge` truncation fires, in which case `psilim < psihigh`. PPPL shipped a fix +`rational_offset_fraction`/`qhigh`/`dW_edge_scan_start` truncation fires, in which case `psilim < psihigh`. PPPL shipped a fix for exactly this confusion (`docs/releases.rst:281`: "Fixes inappropriate uses of psihigh, which may not be the end of integration psilim if sas_flag, qhigh, or peak_flag are used"). diff --git a/src/ForcingTerms/CoilGeometry.jl b/src/ForcingTerms/CoilGeometry.jl index a5c06fdb9..6b286e22a 100644 --- a/src/ForcingTerms/CoilGeometry.jl +++ b/src/ForcingTerms/CoilGeometry.jl @@ -53,7 +53,7 @@ A coil set comes from one of several sources, selected by `source`: - `"window_pane"`: an analytic rectangular picture-frame array, placed EITHER by explicit corners (`rz_corners`) OR by physical standoff from the plasma surface (`standoff`, `poloidal_angle`, `poloidal_length`, `poloidal_tilt`); shared toroidal layout via - `ncoil_gen`, `gap_fraction`, `phi0`. + `n_coils`, `gap_fraction`, `phi0`. - `"helical"`: an analytic helical array on a circular cross-section torus (`R0`, `a`, `m_hel`, `n_hel`, `theta_lo`, `theta_hi`, `n_coils`, `gap_fraction`, `phi0`, `offset`). @@ -69,23 +69,22 @@ Shifts/tilts apply to every source (the analytic geometry is built first, then t - `currents`: current [A] per conductor `[ncoil]`; shorter arrays pad with zeros - `shiftx`, `shifty`, `shiftz`: per-conductor translation [m] `[ncoil]` - `tiltx`, `tilty`, `tiltz`: per-conductor tilt in degrees (or meters if `tilt_in_meters`) - - `xnom`, `ynom`, `znom`: explicit rotation center [m]; defaults to arc-length-weighted center of mass + - `rotation_center_x`, `rotation_center_y`, `rotation_center_z`: explicit rotation center [m]; defaults to arc-length-weighted center of mass - `n_tilt`: toroidal mode number for tilt/shift modulation; -1 means inherit run's n - `tilt_in_meters`: interpret tilt as displacement [m] instead of angle [degrees] Analytic-source parameters (used only for the matching `source`): - `radius`, `height`: PF hoop circle radius and Z height [m] - - `ncoil_gen`: number of generated coils (window-pane / helical arrays) - `rz_corners`: window-pane cross-section as two opposite `[R, Z]` corners in m (corner mode) - `gap_fraction`: toroidal gap between adjacent coils as a fraction of their spacing - `phi0`: toroidal angle offset of the first generated coil [rad] - `R0`, `a`: helical torus major radius and minor (circle) radius [m] - `m_hel`, `n_hel`: helical poloidal/toroidal mode numbers setting the pitch - `theta_lo`, `theta_hi`: helical poloidal extent in degrees; a full turn when `theta_hi-theta_lo == 360` - - `n_coils`: number of helical coils in the array + - `n_coils`: number of coils in a generated window-pane or helical array - `offset`: helical coil radial offset from the torus surface [m] - - `nsec_gen`: optional override of the generated point count per coil + - `points_per_coil`: optional override of the generated point count per coil Window-pane standoff placement (alternative to `rz_corners`; needs an equilibrium): @@ -106,16 +105,15 @@ Base.@kwdef struct CoilSetConfig tiltx::Vector{Float64} = Float64[] tilty::Vector{Float64} = Float64[] tiltz::Vector{Float64} = Float64[] - xnom::Vector{Float64} = Float64[] - ynom::Vector{Float64} = Float64[] - znom::Vector{Float64} = Float64[] + rotation_center_x::Vector{Float64} = Float64[] + rotation_center_y::Vector{Float64} = Float64[] + rotation_center_z::Vector{Float64} = Float64[] n_tilt::Int = -1 tilt_in_meters::Bool = false # Analytic-source parameters radius::Float64 = 0.0 height::Float64 = 0.0 - ncoil_gen::Int = 0 rz_corners::Vector{Vector{Float64}} = Vector{Float64}[] gap_fraction::Float64 = 0.0 phi0::Float64 = 0.0 @@ -127,7 +125,7 @@ Base.@kwdef struct CoilSetConfig theta_hi::Float64 = 360.0 n_coils::Int = 0 offset::Float64 = 0.0 - nsec_gen::Int = 0 + points_per_coil::Int = 0 # Window-pane standoff placement (alternative to rz_corners) standoff::Float64 = NaN @@ -180,6 +178,9 @@ function CoilConfig(ft_ctrl::ForcingTermsControl) end function _parse_coil_set_config(d::Dict{String,Any}) + known = Set(String.(fieldnames(CoilSetConfig))) + unknown = sort([k for k in keys(d) if !(k in known)]) + isempty(unknown) || throw(ArgumentError("unknown keys $(unknown) in [[ForcingTerms.coil_set]]. Known: $(sort(collect(known))).")) fvec(key) = Float64.(get(d, key, Float64[])) # rz_corners arrives as a Vector of [R, Z] pairs (TOML array of arrays) corners = [Float64.(c) for c in get(d, "rz_corners", Vector{Float64}[])] @@ -195,14 +196,13 @@ function _parse_coil_set_config(d::Dict{String,Any}) tiltx=fvec("tiltx"), tilty=fvec("tilty"), tiltz=fvec("tiltz"), - xnom=fvec("xnom"), - ynom=fvec("ynom"), - znom=fvec("znom"), + rotation_center_x=fvec("rotation_center_x"), + rotation_center_y=fvec("rotation_center_y"), + rotation_center_z=fvec("rotation_center_z"), n_tilt=get(d, "n_tilt", -1), tilt_in_meters=get(d, "tilt_in_meters", false), radius=Float64(get(d, "radius", 0.0)), height=Float64(get(d, "height", 0.0)), - ncoil_gen=get(d, "ncoil_gen", 0), rz_corners=corners, gap_fraction=Float64(get(d, "gap_fraction", 0.0)), phi0=Float64(get(d, "phi0", 0.0)), @@ -214,7 +214,7 @@ function _parse_coil_set_config(d::Dict{String,Any}) theta_hi=Float64(get(d, "theta_hi", 360.0)), n_coils=get(d, "n_coils", 0), offset=Float64(get(d, "offset", 0.0)), - nsec_gen=get(d, "nsec_gen", 0), + points_per_coil=get(d, "points_per_coil", 0), standoff=Float64(get(d, "standoff", NaN)), poloidal_angle=Float64(get(d, "poloidal_angle", NaN)), poloidal_length=Float64(get(d, "poloidal_length", 0.0)), @@ -834,7 +834,7 @@ Apply per-conductor shifts and tilts to a coil set, returning a modified copy. Replicates the Fortran `coil_read` shift/tilt logic (coil.F lines 240–340): - - Tilts are rotations around the arc-length-weighted center of mass (unless `xnom/ynom/znom` specified) + - Tilts are rotations around the arc-length-weighted center of mass (unless `rotation_center_x/_y/_z` specified) - `n_tilt` controls the toroidal periodicity of tilt/shift modulation - n_tilt = 0: rigid shift only (no tilts applied) - n_tilt ≥ 1: n-fold modulated perturbations @@ -854,9 +854,9 @@ function apply_transforms(cs::CoilSet, cfg::CoilSetConfig; n_tilt::Int=1) tilty_cfg = _pad(cfg.tilty, ncoil) tiltz_cfg = _pad(cfg.tiltz, ncoil) - xnom_cfg = _pad(isempty(cfg.xnom) ? fill(_NOM_UNSET_SENTINEL, ncoil) : cfg.xnom, ncoil) - ynom_cfg = _pad(isempty(cfg.ynom) ? fill(_NOM_UNSET_SENTINEL, ncoil) : cfg.ynom, ncoil) - znom_cfg = _pad(isempty(cfg.znom) ? fill(_NOM_UNSET_SENTINEL, ncoil) : cfg.znom, ncoil) + xnom_cfg = _pad(isempty(cfg.rotation_center_x) ? fill(_NOM_UNSET_SENTINEL, ncoil) : cfg.rotation_center_x, ncoil) + ynom_cfg = _pad(isempty(cfg.rotation_center_y) ? fill(_NOM_UNSET_SENTINEL, ncoil) : cfg.rotation_center_y, ncoil) + znom_cfg = _pad(isempty(cfg.rotation_center_z) ? fill(_NOM_UNSET_SENTINEL, ncoil) : cfg.rotation_center_z, ncoil) # Check if n_tilt = 0 suppresses tilts (Fortran: "no n=0 component") apply_tilt = n_tilt != 0 @@ -988,7 +988,7 @@ shifts/tilts are applied by the caller, so all sources share the same downstream """ function _build_raw_coil_set(cfg::CoilConfig, sc::CoilSetConfig; equil=nothing) if sc.source == "pf_hoop" - kw = sc.nsec_gen > 0 ? (; nsec=sc.nsec_gen) : (;) + kw = sc.points_per_coil > 0 ? (; nsec=sc.points_per_coil) : (;) return make_pf_hoop(; radius=sc.radius, height=sc.height, name=isempty(sc.name) ? "pf_hoop" : sc.name, kw...) elseif sc.source == "window_pane" @@ -1003,11 +1003,11 @@ function _build_raw_coil_set(cfg::CoilConfig, sc::CoilSetConfig; equil=nothing) "pass `equil` to load_coil_sets, or use rz_corners for corner mode") return make_window_pane_standoff(equil; standoff=sc.standoff, poloidal_angle=sc.poloidal_angle, poloidal_length=sc.poloidal_length, - poloidal_tilt=sc.poloidal_tilt, ncoil=sc.ncoil_gen, + poloidal_tilt=sc.poloidal_tilt, ncoil=sc.n_coils, gap_fraction=sc.gap_fraction, phi0=sc.phi0, name=isempty(sc.name) ? "window_pane" : sc.name) elseif has_corners - return make_window_pane(; ncoil=sc.ncoil_gen, rz_corners=sc.rz_corners, + return make_window_pane(; ncoil=sc.n_coils, rz_corners=sc.rz_corners, gap_fraction=sc.gap_fraction, phi0=sc.phi0, name=isempty(sc.name) ? "window_pane" : sc.name) else @@ -1015,7 +1015,7 @@ function _build_raw_coil_set(cfg::CoilConfig, sc::CoilSetConfig; equil=nothing) "standoff+poloidal_angle (standoff mode)") end elseif sc.source == "helical" - kw = sc.nsec_gen > 0 ? (; npts=sc.nsec_gen) : (;) + kw = sc.points_per_coil > 0 ? (; npts=sc.points_per_coil) : (;) return make_helical(; R0=sc.R0, a=sc.a, m=sc.m_hel, n=sc.n_hel, theta_lo=sc.theta_lo, theta_hi=sc.theta_hi, n_coils=sc.n_coils, gap_fraction=sc.gap_fraction, phi0=sc.phi0, offset=sc.offset, diff --git a/src/GeneralizedPerturbedEquilibrium.jl b/src/GeneralizedPerturbedEquilibrium.jl index 27e29ed54..a4015a79c 100755 --- a/src/GeneralizedPerturbedEquilibrium.jl +++ b/src/GeneralizedPerturbedEquilibrium.jl @@ -91,8 +91,8 @@ using .ForcingTerms: RMPField const _DEPRECATED_FFS_KEYS = ("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") -const _DEPRECATED_EQUIL_KEYS = ("power_bp", "power_b", "power_r", "power_rc") + "gal_flag", "nstep", "diagnose_ca", "diagnose", "ksing") +const _DEPRECATED_EQUIL_KEYS = ("power_bp", "power_b", "power_r", "power_rc", "use_galgrid") # Drop deprecated keys from a parsed gpec.toml section so legacy files keep parsing # instead of throwing an unknown-keyword error; warn so the removal is not silent. @@ -421,16 +421,16 @@ function load_kinetic_context( kinetic_file = joinpath(intr.dir_path, kf_ctrl.kinetic_file) kinetic_profiles = Equilibrium.load_kinetic_profiles( 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, density_factor=kf_ctrl.density_factor, temperature_factor=kf_ctrl.temperature_factor, ExB_rotation_factor=kf_ctrl.ExB_rotation_factor, toroidal_rotation_factor=kf_ctrl.toroidal_rotation_factor, chi1=2π * equil.psio) if !isempty(kf_ctrl.ion_species) || kf_ctrl.electron speclist = isempty(kf_ctrl.ion_species) ? - [KineticForces.IonSpecies(; z=kf_ctrl.zi, m=kf_ctrl.mi, fraction=1.0)] : kf_ctrl.ion_species + [KineticForces.IonSpecies(; z=kf_ctrl.ion_charge, m=kf_ctrl.ion_mass, fraction=1.0)] : kf_ctrl.ion_species species = Equilibrium.resolve_ntv_species(kinetic_file, speclist; - electron=kf_ctrl.electron, zimp=kf_ctrl.zimp, mimp=kf_ctrl.mimp, + electron=kf_ctrl.electron, zimp=kf_ctrl.impurity_charge, mimp=kf_ctrl.impurity_mass, density_factor=kf_ctrl.density_factor, temperature_factor=kf_ctrl.temperature_factor, ExB_rotation_factor=kf_ctrl.ExB_rotation_factor, toroidal_rotation_factor=kf_ctrl.toroidal_rotation_factor) @@ -691,7 +691,7 @@ function run_force_free_states( end # Compute free boundary energies. - if ctrl.vac_flag && !(ctrl.ksing > 0 && ctrl.ksing <= intr.msing + 1) + if ctrl.vac_flag if ctrl.verbose wall_desc = intr.wall_settings.shape == "nowall" ? "no wall" : intr.wall_settings.shape @info "Computing free boundary energies ($wall_desc)" @@ -1002,7 +1002,7 @@ function run_kinetic_forces( # built from the run's control with only its own identity overridden. sctrl = KineticForces.KineticForcesControl(; (f => getfield(kf_ctrl, f) for f in fieldnames(KineticForces.KineticForcesControl))..., - zi=sp.z, mi=sp.m, electron=sp.electron, ion_species=KineticForces.IonSpecies[]) + ion_charge=sp.z, ion_mass=sp.m, electron=sp.electron, ion_species=KineticForces.IonSpecies[]) st = KineticForces.KineticForcesState() KineticForces.compute_torque_all_methods!(st, kf_intr, sctrl, result.equil, sp.profiles) push!(states, st) @@ -1207,7 +1207,7 @@ function efc_couplings( # diamagnetic frequencies (Logan & Park 2013 Eq. 7): ω_φ = ω_E + ω_*n + ω_*T. kp = kinetic_profiles chi1 = 2π * equil.psio - chrg = kf_ctrl.zi * Utilities.E_CHG + chrg = kf_ctrl.ion_charge * Utilities.E_CHG ψk = kp.xs ω_E = [kp.omegaE_spline(ψ) for ψ in ψk] ω_star_T = [-2π * kp.Ti_deriv(ψ) / (chrg * chi1) for ψ in ψk] @@ -1518,7 +1518,7 @@ function write_outputs_to_HDF5( out_h5["$fwd/xi_s"] = xi_solution !== nothing ? xi_solution.xi_s_store : ComplexF64[] out_h5["$fwd/crit"] = diag !== nothing ? diag.crit_store : Float64[] - # Write edge stability scan data (only present when psiedge < psilim). + # Write edge stability scan data (only present when dW_edge_scan_start < psilim). # Generalized (W, N) pencil energies — power-normalized, Jacobian-invariant; these are # the values findmax_dW_edge! uses to choose the truncation point. if diag !== nothing && !isempty(diag.edge_scan.psi) diff --git a/src/InnerLayer/SLAYER/LayerInputs.jl b/src/InnerLayer/SLAYER/LayerInputs.jl index 252e81ae9..4136df75a 100644 --- a/src/InnerLayer/SLAYER/LayerInputs.jl +++ b/src/InnerLayer/SLAYER/LayerInputs.jl @@ -176,7 +176,7 @@ profiles, without an intermediate file round-trip. - `bt` -- toroidal field [T]. Scalar, callable of `psi`, or `nothing` (default). When `nothing`, the physical `B_T = F(ψ) / (2π·R₀)` is computed per surface from the equilibrium's F-spline. Note: - `equil.config.b0exp` is a *normalization* (often just `1.0`), not the + `equil.config.b0_norm` is a *normalization* (often just `1.0`), not the physical field, so passing it as a scalar is almost always wrong. - `mu_i` -- ion mass in proton-mass units (default `2.0` for D). @@ -187,7 +187,7 @@ profiles, without an intermediate file round-trip. of `psi` (default `1.0`). - `dr_val` -- resistive interchange index `D_R = E + F + H²` (Glasser-Greene-Johnson 1975) feeding the critical-Δ formulas - (`:lar`, `:rfitzp`, `:toroidal`). When `nothing` (default), Julia + (`:lar`, `:fitzpatrick`, `:toroidal`). When `nothing` (default), Julia derives it per-surface from the equilibrium as `dr_val_k = D_R(ψ_k) = E_k + F_k + H_k²`, consistent with Connor-Hastie-Helander 2015 (PPCF 57 065001) Eq. 59 @@ -204,9 +204,9 @@ profiles, without an intermediate file round-trip. raised if `dc_type=:toroidal` is also requested — the auto-derived formula additionally needs ⟨|∇ψ|²⟩ FSA which `ResistGeometry` doesn't currently expose. Pass a scalar / vector / callable to use - a prescribed value. (For `dc_type=:rfitzp` and `:lar`, dgeo_val is + a prescribed value. (For `dc_type=:fitzpatrick` and `:lar`, dgeo_val is not consulted.) - - `dc_type` -- `:none` (default), `:lar`, `:rfitzp`, or `:toroidal`. + - `dc_type` -- `:none` (default), `:lar`, `:fitzpatrick`, or `:toroidal`. - `rs_method` -- radial label defining `r_s` for the whole layer stack: `:midplane` (default), `:halfwidth`, `:fsa`, `:volume`, or `:flux`. See [`radial_label`](@ref) for the definitions. S, the r-based shear, W_d, diff --git a/src/InnerLayer/SLAYER/LayerParameters.jl b/src/InnerLayer/SLAYER/LayerParameters.jl index ae875dea4..d64b86ee1 100644 --- a/src/InnerLayer/SLAYER/LayerParameters.jl +++ b/src/InnerLayer/SLAYER/LayerParameters.jl @@ -102,7 +102,7 @@ end # Allowed dc_type values for the critical-Δ offset. `:none` is the default # `dc_tmp = 0` branch. -const ALLOWED_DC_TYPES = (:none, :lar, :rfitzp, :toroidal) +const ALLOWED_DC_TYPES = (:none, :lar, :fitzpatrick, :toroidal) """ r_based_shear(rs, q, dq_dpsi, da_dpsi) -> Float64 @@ -167,7 +167,7 @@ function _solve_dc_tmp(; dc_type::Symbol, dr_val::Real, dgeo_val::Real, return 0.5 * (-dr_val) * π^1.5 * (chi_par / chi_perp)^0.25 * sqrt((n_tor * abs(sval_r)) / (R0 * rs)) - elseif dc_type === :rfitzp + elseif dc_type === :fitzpatrick return -(sqrt(2.0) * π^1.5 * dr_val) / Wd elseif dc_type === :toroidal return 0.5 * (-dr_val) * π^1.5 * @@ -213,7 +213,7 @@ parametrization (P_perp/P_tor/D_norm; the older magnetic/electron Prandtl - `chi_perp`, `chi_tor` -- perpendicular / toroidal heat diffusivity [m²/s] - `m`, `n` -- poloidal / toroidal mode numbers at the surface - `dr_val`, `dgeo_val` -- inputs for the critical-Δ formula - - `dc_type` -- one of `:none`, `:lar`, `:rfitzp`, `:toroidal` + - `dc_type` -- one of `:none`, `:lar`, `:fitzpatrick`, `:toroidal` - `ising` -- singular-surface index for traceability - `k_ref` -- reference-length ratio K = r_s·(dψ_N/dr) at the surface, used by the Tearing runner to convert the ψ_N-referenced outer Δ' to diff --git a/src/KineticForces/CalculatedKineticMatrices.jl b/src/KineticForces/CalculatedKineticMatrices.jl index bb61135e4..1c134182f 100644 --- a/src/KineticForces/CalculatedKineticMatrices.jl +++ b/src/KineticForces/CalculatedKineticMatrices.jl @@ -95,7 +95,7 @@ function compute_calculated_kinetic_matrices( # ipsi row of kw_flat/kt_flat. Per-thread copies of kf_intr provide isolated # tpsi_* θ-grid buffers and interpolant hint refs; geometric/profile splines # are read-only and safely shared through deepcopy semantics. - nl = kf_ctrl.nl + nl = kf_ctrl.bounce_harmonic_max nthreads = Threads.maxthreadid() thread_intrs = [deepcopy(kf_intr) for _ in 1:nthreads] thread_full_w = [zeros(ComplexF64, mpert, mpert, 6) for _ in 1:nthreads] @@ -108,7 +108,7 @@ function compute_calculated_kinetic_matrices( # from kf_ctrl. The kinetic W/torque matrices are additive over species, so we # accumulate (+=) each species' block-diagonal contribution. splist = species === nothing ? - [Equilibrium.ResolvedNTVSpecies(kf_ctrl.zi, kf_ctrl.mi, kf_ctrl.electron, "single", kinetic_profiles)] : species + [Equilibrium.ResolvedNTVSpecies(kf_ctrl.ion_charge, kf_ctrl.ion_mass, kf_ctrl.electron, "single", kinetic_profiles)] : species for sp in splist # Hoisted per-species scalars: the closure then captures concrete values, not a @@ -133,10 +133,10 @@ function compute_calculated_kinetic_matrices( fill!(block_t, 0) compute_kinetic_matrices_at_psi!( block_w, block_t, psi, n, ell, - z_s, m_s, kf_ctrl.wdfac, kf_ctrl.divxfac, + z_s, m_s, kf_ctrl.magnetic_drift_factor, kf_ctrl.divxi_factor, el_s, equil, intr_t, prof_s; - nutype=kf_ctrl.nutype, f0type=kf_ctrl.f0type, nufac=kf_ctrl.nufac, - atol_xlmda=kf_ctrl.atol_xlmda, rtol_xlmda=kf_ctrl.rtol_xlmda, + nutype=kf_ctrl.nutype, f0type=kf_ctrl.f0type, nufac=kf_ctrl.collisionality_factor, + atol_xlmda=kf_ctrl.atol_pitch, rtol_xlmda=kf_ctrl.rtol_pitch, atol_x=kf_ctrl.atol_x, rtol_x=kf_ctrl.rtol_x, nested_tolerance_margin=kf_ctrl.nested_tolerance_margin ) diff --git a/src/KineticForces/Compute.jl b/src/KineticForces/Compute.jl index 62c55d7f1..8f2fd519e 100644 --- a/src/KineticForces/Compute.jl +++ b/src/KineticForces/Compute.jl @@ -23,7 +23,7 @@ Build the ψ-quadrature node list `[x0, interior points strictly inside (x0, xou resonances); this function owns the ordering: sort, drop near-duplicates (closer than `PANEL_MERGE_ATOL`, e.g. a kinetic resonance coinciding with a rational), and drop points within `PANEL_MERGE_ATOL` of a bound to avoid degenerate panels. Paneling the integral at -these surfaces puts the resonant torque-density peaks (reg_spot/collisionally broadened, but +these surfaces puts the resonant torque-density peaks (regularization_width/collisionally broadened, but narrow in ψ) on Gauss-Kronrod interval endpoints, which the rule handles natively instead of hunting them by adaptive bisection. """ @@ -137,7 +137,7 @@ function integrate_psi_quadgk( tpsi!(thread_tpsi[tid], psi, n, l, zi, mi, wdfac, divxfac, electron, method, equil, thread_intrs[tid], kinetic_profiles; op_wmats=w, - atol_xlmda=ctrl.atol_xlmda, rtol_xlmda=ctrl.rtol_xlmda, + atol_xlmda=ctrl.atol_pitch, rtol_xlmda=ctrl.rtol_pitch, atol_x=ctrl.atol_x, rtol_x=ctrl.rtol_x, nested_tolerance_margin=ctrl.nested_tolerance_margin) harm_vals[ell_idx] = thread_tpsi[tid][] @@ -334,8 +334,8 @@ function compute_torque_all_methods!(state::KineticForcesState, intr::KineticFor # Adaptive QuadGK integration over ψ (serial BatchIntegrand) result = integrate_psi_quadgk( - n, ctrl.nl, ctrl.zi, ctrl.mi, - ctrl.wdfac, ctrl.divxfac, ctrl.electron, + n, ctrl.bounce_harmonic_max, ctrl.ion_charge, ctrl.ion_mass, + ctrl.magnetic_drift_factor, ctrl.divxi_factor, ctrl.electron, method, equil, intr, ctrl, kinetic_profiles; psi_min=ctrl.psilims[1], psi_max=ctrl.psilims[2]) diff --git a/src/KineticForces/EnergyIntegration.jl b/src/KineticForces/EnergyIntegration.jl index 36cc1ecef..191eb4bfb 100644 --- a/src/KineticForces/EnergyIntegration.jl +++ b/src/KineticForces/EnergyIntegration.jl @@ -73,12 +73,12 @@ For CGL there is no resonance denominator: N_cgl = x^2.5 / (i·n). x25 = x * x * sqrt(x) # x^2.5 fx = if p.f0type == "maxwellian" ComplexF64((p.we + p.wn + p.wt * (x - 1.5)) * x25) - elseif p.f0type == "jkp" + elseif p.f0type == "park" ComplexF64((p.we + p.wn + p.wt * 2) * x25) elseif p.f0type == "cgl" complex(0.0, -x25 / p.n) # x^2.5 / (i*n) else - error("f0type must be maxwellian, jkp, or cgl") + error("f0type must be maxwellian, park, or cgl") end if p.qt fx *= (x - 2.5) @@ -101,12 +101,12 @@ never carries a CGL numerator (CGL has no pole). a = p.we + p.wn + p.wt * (x - 1.5) nn = ComplexF64(a * x25) dn = ComplexF64(p.wt * x25 + a * 2.5 * x15) # d/dx[(…)·x^2.5] - elseif p.f0type == "jkp" + elseif p.f0type == "park" a = p.we + p.wn + p.wt * 2 nn = ComplexF64(a * x25) dn = ComplexF64(a * 2.5 * x15) else - error("_energy_numerator_deriv supports maxwellian and jkp") + error("_energy_numerator_deriv supports maxwellian and park") end return p.qt ? dn * (x - 2.5) + nn : dn # d/dx[N·(x-2.5)] = N′·(x-2.5) + N end @@ -302,7 +302,7 @@ all collisionalities: the collisionless case (ν ≡ 0) is the exact ν→0 limi its real-axis pole resolved analytically (see `_integrate_energy_resonant`). Collision operator types (`nutype`): `"zero"`, `"small"`, `"krook"`, `"harmonic"`. -Distribution function types (`f0type`): `"maxwellian"`, `"jkp"`, `"cgl"`. +Distribution function types (`f0type`): `"maxwellian"`, `"park"`, `"cgl"`. `ximag` is accepted for backward compatibility but no longer used — resonance poles are now handled analytically rather than by contour deformation. diff --git a/src/KineticForces/KineticForcesStructs.jl b/src/KineticForces/KineticForcesStructs.jl index ed9ef14a6..ccc299bff 100644 --- a/src/KineticForces/KineticForcesStructs.jl +++ b/src/KineticForces/KineticForcesStructs.jl @@ -110,26 +110,26 @@ builds a second control for its differing tolerance). prmm_flag::Bool = false # Passing MXM E-L torque matrix norm # Plasma species parameters - zi::Int = 1 # Ion charge (fundamental units) - mi::Int = 2 # Ion mass (proton masses) - zimp::Int = 6 # Impurity charge - mimp::Int = 12 # Impurity mass + ion_charge::Int = 1 # Ion charge (fundamental units) + ion_mass::Int = 2 # Ion mass (proton masses) + impurity_charge::Int = 6 # Impurity charge + impurity_mass::Int = 12 # Impurity mass electron::Bool = false # Add electron NTV in addition to the ion species ion_species::Vector{IonSpecies} = IonSpecies[] # multi-main-ion set; empty ⇒ single ion # Mode numbers nn::Int = 1 # Toroidal mode number - nl::Int = 1 # Bounce harmonic number + bounce_harmonic_max::Int = 1 # Bounce harmonics summed over ℓ = -max:max # Tolerances, outermost to innermost: ψ quadrature ⊃ λ (pitch) ⊃ x (energy). # Each level must be resolved more tightly than the one enclosing it, or the outer # integrator chases its integrand's own quadrature noise instead of converging. - # *_xlmda: tolerances for the λ (pitch) integration + # *_pitch: tolerances for the λ (pitch) integration # *_x: tolerances for the x (energy) integration nested inside it; NaN ⇒ derive as # nested_tolerance_margin × the pitch tolerances # *_psi: tolerances for the outer ψ quadrature - atol_xlmda::Float64 = 1e-8 # Absolute tolerance for the inner pitch integration - rtol_xlmda::Float64 = 1e-5 # Relative tolerance for the inner pitch integration + atol_pitch::Float64 = 1e-8 # Absolute tolerance for the inner pitch integration + rtol_pitch::Float64 = 1e-5 # Relative tolerance for the inner pitch integration atol_x::Float64 = NaN # Absolute tolerance for the energy integration (NaN ⇒ derived) rtol_x::Float64 = NaN # Relative tolerance for the energy integration (NaN ⇒ derived) # The pitch integrand IS the energy integral, so the energy level is resolved this much @@ -137,7 +137,7 @@ builds a second control for its differing tolerance). nested_tolerance_margin::Float64 = 1e-2 # Factor relating derived energy tolerances to the pitch ones # rtol_psi is the primary convergence knob: ~2 significant figures matches the validity # of the NTV model approximations. Do not set it tighter than the noise floor of the - # inner integrals (keep rtol_psi ≳ 10 × rtol_xlmda). + # inner integrals (keep rtol_psi ≳ 10 × rtol_pitch). rtol_psi::Float64 = 1e-2 # Relative tolerance for outer ψ quadrature # atol_psi is in N·m and therefore amplitude-sensitive: NTV scales as δB², so a 10× # weaker applied field gives a 100× smaller torque and any fixed absolute tolerance can @@ -153,15 +153,15 @@ builds a second control for its differing tolerance). density_factor::Float64 = 1.0 # Density scaling (ni, ne) temperature_factor::Float64 = 1.0 # Temperature scaling (Ti, Te) ExB_rotation_factor::Float64 = 1.0 # ExB rotation scaling (omegaE) - wdfac::Float64 = 1.0 # Magnetic drift scaling + magnetic_drift_factor::Float64 = 1.0 # Magnetic drift scaling toroidal_rotation_factor::Float64 = 1.0 # Total toroidal rotation scaling (wphi) - nufac::Float64 = 1.0 # Collisionality scaling - divxfac::Float64 = 1.0 # div(xi_perp) scaling + collisionality_factor::Float64 = 1.0 # Collisionality scaling + divxi_factor::Float64 = 1.0 # div(xi_perp) scaling # Energy integration parameters nutype::String = "harmonic" # Collision operator: "zero", "small", "krook", "harmonic" - f0type::String = "maxwellian" # Distribution function: "maxwellian", "jkp", "cgl" + f0type::String = "maxwellian" # Distribution function: "maxwellian", "park", "cgl" # Diagnostic parameters psilims::Vector{Float64} = [0.0, 1.0] # Integration limits in psi diff --git a/src/PerturbedEquilibrium/FieldReconstruction.jl b/src/PerturbedEquilibrium/FieldReconstruction.jl index 56d74b012..c768e3473 100644 --- a/src/PerturbedEquilibrium/FieldReconstruction.jl +++ b/src/PerturbedEquilibrium/FieldReconstruction.jl @@ -337,7 +337,7 @@ Matches Fortran gpeq_sol regularization + gpout_xclebsch output convention: - `clebsch_psi1` = xmp1 = ∂ξ^ψ/∂ψ × singfac²/(singfac² + reg_spot²) - `clebsch_alpha` = xms/χ₁ (regularized ξ^α divided by χ₁ per gpout_xclebsch convention) -When reg_spot=0, clebsch_psi1 = xi_psi1 and clebsch_alpha = xi_s/χ₁ (no regularization). +When regularization_width = 0, clebsch_psi1 = xi_psi1 and clebsch_alpha = xi_s/χ₁ (no regularization). Ideal runs re-solve the regularized xms = -A⁻¹(B·xmp1 + C·xsp) from `mats.ideal`. Kinetic runs scale the stored ξ_s by the same singfac factor (Fortran gpeq.f), since the kinetic A is non-Hermitian. @@ -360,8 +360,8 @@ function compute_clebsch_displacements( clebsch_psi1 = copy(xi_psi1_modes) # will be regularized below clebsch_alpha = xi_s_modes ./ chi1 # ξ^α/χ₁ (will be regularized below) - reg_spot = ctrl.reg_spot - @assert reg_spot >= 0 "reg_spot must be non-negative (got $reg_spot)" + reg_spot = ctrl.regularization_width # Fortran gpec reg_spot + @assert reg_spot >= 0 "regularization_width must be non-negative (got $reg_spot)" if reg_spot == 0 return clebsch_psi, clebsch_psi1, clebsch_alpha @@ -511,7 +511,7 @@ function compute_contra_displacements( npsi, numpert_total = size(xi_psi_modes) (; mpert, mlow, nlow, npert) = ffs chi1 = 2π * equil.psio - reg_spot = ctrl.reg_spot + reg_spot = ctrl.regularization_width # Fortran gpec reg_spot fc = metric.fourier_coeffs xwp_modes = zeros(ComplexF64, npsi, numpert_total) diff --git a/src/PerturbedEquilibrium/PerturbedEquilibrium.jl b/src/PerturbedEquilibrium/PerturbedEquilibrium.jl index 1151e5425..fa84db745 100644 --- a/src/PerturbedEquilibrium/PerturbedEquilibrium.jl +++ b/src/PerturbedEquilibrium/PerturbedEquilibrium.jl @@ -74,7 +74,7 @@ function compute_perturbed_equilibrium( state = PerturbedEquilibriumState() equil = ffs.equil mats = ffs.mats - mthvac = ffs.control.mthvac + mtheta_vacuum = ffs.control.mtheta_vacuum # Step 0: Initialize mode arrays for convenient indexing initialize_mode_arrays!(intr, ffs) @@ -99,14 +99,14 @@ function compute_perturbed_equilibrium( if ctrl.compute_response && ForceFreeStates.require(ffs, :free_boundary, "plasma response calculation") && ForceFreeStates.require_solution(ffs, "plasma response calculation") - compute_plasma_response!(state, equil, solution, ffs.free_boundary.wt0, mthvac, ffs, intr, ctrl, ffs.metric, mats) + compute_plasma_response!(state, equil, solution, ffs.free_boundary.wt0, mtheta_vacuum, ffs, intr, ctrl, ffs.metric, mats) end # Step 3: Compute singular coupling metrics if ctrl.compute_singular_coupling && ForceFreeStates.require(ffs, :free_boundary, "singular coupling calculation") && ForceFreeStates.require_solution(ffs, "singular coupling calculation") - compute_singular_coupling_metrics!(state, equil, solution, mthvac, ffs, intr, ctrl, mats) + compute_singular_coupling_metrics!(state, equil, solution, mtheta_vacuum, ffs, intr, ctrl, mats) compute_dominant_coupling!(state, ctrl) end @@ -163,7 +163,7 @@ function materialize_forcing_modes( # Same control surface as the coil branch above: psilim, the integration # limit (Fortran: gpec/gpec.f:431 `field_bs_psi(psilim, ...)`). Without it # the normalization was taken on the equilibrium-spline limit, which differs - # whenever dmlim/qhigh/psiedge truncation moves psilim inward. + # whenever rational_offset_fraction/qhigh/dW_edge_scan_start truncation moves psilim inward. convert_forcing_normalization!(modes_n, norm_tag, equil, n, minimum(m_vals), maximum(m_vals); psi=ffs.psilim) end diff --git a/src/PerturbedEquilibrium/PerturbedEquilibriumStructs.jl b/src/PerturbedEquilibrium/PerturbedEquilibriumStructs.jl index 0cb0b7e37..c433733db 100644 --- a/src/PerturbedEquilibrium/PerturbedEquilibriumStructs.jl +++ b/src/PerturbedEquilibrium/PerturbedEquilibriumStructs.jl @@ -29,7 +29,7 @@ Regularization: # High Priority (MWE) - - `reg_spot::Float64` - Regularization width for singular surface smoothing (default: 0.05). Set to 0 to disable. Must be ≥ 0. + - `regularization_width::Float64` - Regularization width for singular surface smoothing (default: 0.05). Set to 0 to disable. Must be ≥ 0. """ @kwdef struct PerturbedEquilibriumControl # High Priority (MWE) @@ -49,7 +49,7 @@ Regularization: # Regularization width for singular surface smoothing (matches Fortran gpec.f reg_spot). # Set to 0 to disable regularization. Must be non-negative. - reg_spot::Float64 = 5e-2 + regularization_width::Float64 = 5e-2 end """ diff --git a/src/PerturbedEquilibrium/Response.jl b/src/PerturbedEquilibrium/Response.jl index a3934560f..2100e52c1 100644 --- a/src/PerturbedEquilibrium/Response.jl +++ b/src/PerturbedEquilibrium/Response.jl @@ -1,6 +1,6 @@ """ compute_plasma_response!( - state, equil, solution, wt0, mthvac, ffs, + state, equil, solution, wt0, mtheta_vacuum, ffs, intr, ctrl, metric, mats ) @@ -19,7 +19,7 @@ function compute_plasma_response!( equil::Equilibrium.PlasmaEquilibrium, solution::SolutionProfiles, wt0::Matrix{ComplexF64}, - mthvac::Int, + mtheta_vacuum::Int, ffs::ForceFreeStatesResult, intr::PerturbedEquilibriumInternal, ctrl::PerturbedEquilibriumControl, @@ -37,7 +37,7 @@ function compute_plasma_response!( plasma_inductance = calc_plasma_inductance(wt0, ffs, equil.psio) # Surface inductance L from vacuum surface-current matrix at psilim, block-diagonal in n. - surface_inductance = calc_surface_inductance(equil, ffs.psilim, mthvac, ffs.mlow:ffs.mhigh, ffs.nlow:ffs.nhigh) + surface_inductance = calc_surface_inductance(equil, ffs.psilim, mtheta_vacuum, ffs.mlow:ffs.mhigh, ffs.nlow:ffs.nhigh) permeability = calc_permeability(plasma_inductance, surface_inductance) # Reluctance ϱ = L⁻¹·(Λ† − L)·L⁻¹ (Fortran gpresp_reluct: diff_indmats = CONJG(TRANSPOSE(plas_indmats)) − surf_indmats). diff --git a/src/PerturbedEquilibrium/SingularCoupling.jl b/src/PerturbedEquilibrium/SingularCoupling.jl index 9d447552c..e8c5c9e09 100644 --- a/src/PerturbedEquilibrium/SingularCoupling.jl +++ b/src/PerturbedEquilibrium/SingularCoupling.jl @@ -222,7 +222,7 @@ end state::PerturbedEquilibriumState, equil::Equilibrium.PlasmaEquilibrium, solution::SolutionProfiles, - mthvac::Int, + mtheta_vacuum::Int, ffs::ForceFreeStatesResult, intr::PerturbedEquilibriumInternal, ctrl::PerturbedEquilibriumControl, @@ -254,7 +254,7 @@ function compute_singular_coupling_metrics!( state::PerturbedEquilibriumState, equil::Equilibrium.PlasmaEquilibrium, solution::SolutionProfiles, - mthvac::Int, + mtheta_vacuum::Int, ffs::ForceFreeStatesResult, intr::PerturbedEquilibriumInternal, ctrl::PerturbedEquilibriumControl, @@ -277,7 +277,7 @@ function compute_singular_coupling_metrics!( chi1 = 2π * equil.psio twopi = 2π - mtheta = mthvac + mtheta = mtheta_vacuum # Phase 1: Collect all resonant (surface, n) pairs in psi order resonant_pairs = Tuple{Int,Int}[] diff --git a/src/PerturbedEquilibrium/Utils.jl b/src/PerturbedEquilibrium/Utils.jl index f49ecdfed..d2064aee4 100644 --- a/src/PerturbedEquilibrium/Utils.jl +++ b/src/PerturbedEquilibrium/Utils.jl @@ -288,7 +288,7 @@ const PE_H5_ANNOTATIONS = [ "Response/xi_clebsch_psi" => (; long_name="Clebsch displacement component ξ^ψ (PENTRC input, gpout_xclebsch convention)", dims=("psi", "mode"), attach=(1 => "Response/psi",)), "Response/dxi_clebsch_psidpsi" => - (; long_name="regularized ψ_N derivative of ξ^ψ (× singfac²/(singfac²+reg_spot²))", dims=("psi", "mode"), attach=(1 => "Response/psi",)), + (; long_name="regularized ψ_N derivative of ξ^ψ (× singfac²/(singfac²+regularization_width²))", dims=("psi", "mode"), attach=(1 => "Response/psi",)), "Response/xi_clebsch_alpha" => (; long_name="Clebsch displacement component ξ^α/χ₁ (PENTRC input, gpout_xclebsch convention)", dims=("psi", "mode"), attach=(1 => "Response/psi",)), "Response/xi_n" => (; long_name="physical normal displacement ξ_n", units="m", dims=("psi", "mode"), attach=(1 => "Response/psi",)), diff --git a/src/Tearing/Runner/Control.jl b/src/Tearing/Runner/Control.jl index 1a12ff7e6..2ab4565ab 100644 --- a/src/Tearing/Runner/Control.jl +++ b/src/Tearing/Runner/Control.jl @@ -21,8 +21,8 @@ constructor. - `scan_mode` -- `:amr` (default) or `:brute_force` - `coupling_mode` -- `:uncoupled` (default, per-surface) or `:coupled` (multi-surface determinant) - - `dc_type` -- critical-Δ offset selector, one of `:none`, `:lar`, - `:rfitzp`, `:toroidal` (χ_‖-matching critical-Δ formulas, + - `D_c_type` -- critical-Δ offset selector, one of `:none`, `:lar`, + `:fitzpatrick`, `:toroidal` (χ_‖-matching critical-Δ formulas, Connor-Hastie-Helander 2015) - `msing_max` -- number of surfaces to include in the coupled determinant (default 3; capped at `length(sings)` at runtime) @@ -32,16 +32,16 @@ constructor. - `bt` -- toroidal field `[T]`. `nothing` (default) resolves the physical `B_T = F(ψ)/(2π·R₀)` per surface from the equilibrium's F-spline; a scalar or a callable of `psi` overrides it - - `mu_i` -- ion mass in proton-mass units (default 2.0 for D) + - `ion_mass` -- ion mass in proton-mass units (default 2.0 for D) - `zeff` -- effective charge - `chi_perp`, `chi_tor` -- fallback perpendicular / toroidal heat diffusivity [m²/s], used only when the kinetic file carries no usable `chi_e`/`chi_phi` profile (dataset absent or all-zero); otherwise the file's χ⊥(ψ)/χ_φ(ψ) take precedence - - `dr_val`, `dgeo_val` -- critical-Δ formula inputs. `nothing` (default) - auto-derives them from the equilibrium: `dr_val` from the resistive - interchange index `D_R = E + F + H²` at each surface, `dgeo_val` from the - toroidal geometric factor (required only by `dc_type=:toroidal`). Supply a + - `D_R`, `D_geo` -- critical-Δ formula inputs. `nothing` (default) + auto-derives them from the equilibrium: `D_R` from the resistive + interchange index `D_R = E + F + H²` at each surface, `D_geo` from the + toroidal geometric factor (required only by `D_c_type=:toroidal`). Supply a scalar only to override the auto-derivation; an explicit `0.0` disables the critical-Δ offset (Δ_crit ≡ 0) - `theta_sample` -- poloidal angle at which to sample minor radius @@ -101,16 +101,16 @@ there is one consistent interface for resistive and kinetic profiles. inner_model::Symbol = :slayer_fitzpatrick scan_mode::Symbol = :amr coupling_mode::Symbol = :uncoupled - dc_type::Symbol = :none + D_c_type::Symbol = :none msing_max::Int = 3 bt::Union{Float64,Nothing} = nothing - mu_i::Float64 = 2.0 + ion_mass::Float64 = 2.0 zeff::Float64 = 1.0 chi_perp::Float64 = 1.0 chi_tor::Float64 = 1.0 - dr_val::Union{Float64,Nothing} = nothing - dgeo_val::Union{Float64,Nothing} = nothing + D_R::Union{Float64,Nothing} = nothing + D_geo::Union{Float64,Nothing} = nothing theta_sample::Float64 = 0.0 resistivity_model::Symbol = :sauter lnLambda_form::Symbol = :nrl @@ -160,7 +160,7 @@ end const _VALID_INNER_MODELS = (:slayer_fitzpatrick, :ggj_shooting, :ggj_galerkin) const _VALID_SCAN_MODES = (:amr, :brute_force) const _VALID_COUPLING_MODES = (:uncoupled, :coupled) -const _VALID_DC_TYPES = (:none, :lar, :rfitzp, :toroidal) +const _VALID_D_C_TYPES = (:none, :lar, :fitzpatrick, :toroidal) const _VALID_RESISTIVITY_MODELS = (:sauter, :redl, :spitzer, :spitzer_harm) const _VALID_LNLAMBDA_FORMS = (:nrl, :sauter, :wesson) @@ -174,9 +174,9 @@ function validate(ctrl::SLAYERControl) ctrl.coupling_mode in _VALID_COUPLING_MODES || throw(ArgumentError("SLAYERControl: coupling_mode=$(ctrl.coupling_mode) " * "not in $(_VALID_COUPLING_MODES)")) - ctrl.dc_type in _VALID_DC_TYPES || - throw(ArgumentError("SLAYERControl: dc_type=$(ctrl.dc_type) " * - "not in $(_VALID_DC_TYPES)")) + ctrl.D_c_type in _VALID_D_C_TYPES || + throw(ArgumentError("SLAYERControl: D_c_type=$(ctrl.D_c_type) " * + "not in $(_VALID_D_C_TYPES)")) ctrl.resistivity_model in _VALID_RESISTIVITY_MODELS || throw(ArgumentError("SLAYERControl: resistivity_model=$(ctrl.resistivity_model) " * "not in $(_VALID_RESISTIVITY_MODELS)")) @@ -241,12 +241,12 @@ function slayer_control_from_toml(section::AbstractDict) kwargs = Dict{Symbol,Any}() for (k, v) in flat sym = Symbol(k) - if sym in (:inner_model, :scan_mode, :coupling_mode, :dc_type, + if sym in (:inner_model, :scan_mode, :coupling_mode, :D_c_type, :resistivity_model, :lnLambda_form) kwargs[sym] = v isa Symbol ? v : Symbol(String(v)) elseif sym in (:Q_re_range, :Q_im_range) kwargs[sym] = _as_range(v) - elseif sym in (:bt, :dr_val, :dgeo_val) + elseif sym in (:bt, :D_R, :D_geo) # Allow explicit nothing (auto-derive) or a number (override) kwargs[sym] = v === nothing ? nothing : Float64(v) elseif sym === :boxes diff --git a/src/Tearing/Runner/run_slayer.jl b/src/Tearing/Runner/run_slayer.jl index 2459087cf..f231b3cbf 100644 --- a/src/Tearing/Runner/run_slayer.jl +++ b/src/Tearing/Runner/run_slayer.jl @@ -442,12 +442,12 @@ function run_slayer(equil, surfaces::AbstractVector, delta_prime_matrix::Abstrac # GGJ γ-extraction is future work; `run_slayer_from_inputs` emits the # warning once the model is built (so direct callers see it too). params = build_ggj_inputs(equil, surfaces, profiles; - mu_i=control.mu_i, + mu_i=control.ion_mass, zeff=control.zeff, resistivity_model=_build_resistivity_model(control.resistivity_model), lnLambda_form=control.lnLambda_form) else - # `equil.config.b0exp` is a NORMALIZATION (commonly exactly 1.0), not the toroidal + # `equil.config.b0_norm` is a NORMALIZATION (commonly exactly 1.0), not the toroidal # field, so substituting it here silently ran the layer physics at B_T = 1 T. Pass the # control value through instead: `nothing` makes build_slayer_inputs compute the # physical B_T = F(psi)/(2*pi*R_0) per surface from the equilibrium's F-spline, which is @@ -462,13 +462,13 @@ function run_slayer(equil, surfaces::AbstractVector, delta_prime_matrix::Abstrac "control.chi_perp/chi_tor fallback for the missing one(s).") params = build_slayer_inputs(equil, surfaces, profiles; bt=bt, - mu_i=control.mu_i, + mu_i=control.ion_mass, zeff=control.zeff, chi_perp=chi_perp, chi_tor=chi_tor, - dr_val=control.dr_val, - dgeo_val=control.dgeo_val, - dc_type=control.dc_type, + dr_val=control.D_R, + dgeo_val=control.D_geo, + dc_type=control.D_c_type, theta=control.theta_sample, resistivity_model=_build_resistivity_model(control.resistivity_model), lnLambda_form=control.lnLambda_form) diff --git a/src/Vacuum/DataTypes.jl b/src/Vacuum/DataTypes.jl index bab0c50f9..afdd2a2a3 100644 --- a/src/Vacuum/DataTypes.jl +++ b/src/Vacuum/DataTypes.jl @@ -193,7 +193,7 @@ Struct containing input settings for vacuum wall geometry. - `shape::String`: String selecting wall shape. Options are: + `"nowall"`: No wall - + `"conformal"`: Wall conformal to plasma surface at distance `a` + + `"conformal"`: Wall conformal to plasma surface at `standoff_fraction` minor radii + `"elliptical"`: Elliptical wall + `"dee"`: Dee-shaped wall + `"mod_dee"`: Modified Dee-shaped wall @@ -202,20 +202,21 @@ Struct containing input settings for vacuum wall geometry. A non-axisymmetric boundary (`nzeta_in > 1`) supports `"nowall"` and `"conformal"`; the others are poloidal contours that get revolved and so need `nzeta_in == 1`. - - `a::Float64`: Distance of wall from plasma in units of the minor radius `0.5(max R - min R)` - (conformal), or shape parameter (others). On a non-axisymmetric boundary the extrema are taken - over the whole torus, so `a` scales with half the global R-extent rather than a cross-section - minor radius; it reduces to the axisymmetric definition when the boundary is axisymmetric. + - `standoff_fraction::Float64`: Wall standoff in units of the plasma minor radius `0.5(max R - min R)` + ("conformal" and "dee"). On a non-axisymmetric boundary the extrema are taken over the whole torus, + so it scales with half the global R-extent; this reduces to the axisymmetric definition. - - `aw::Float64`: Half-thickness parameter for Dee-shaped walls + - `minor_radius::Float64`: Wall minor radius [m] ("elliptical" and "mod_dee") - - `bw::Float64`: Elongation parameter for wall shapes + - `half_thickness::Float64`: Half-thickness parameter for Dee-shaped walls - - `cw::Float64`: Offset of the center of the wall from the major radius + - `elongation::Float64`: Elongation parameter for wall shapes - - `dw::Float64`: Triangularity parameter for wall shapes + - `center_offset::Float64`: Offset of the wall center from the major radius ("dee", in minor radii); wall center R [m] ("mod_dee") - - `tw::Float64`: Sharpness of the corners of the wall (try 0.05 as initial value) + - `triangularity::Float64`: Triangularity parameter for wall shapes + + - `corner_sharpness::Float64`: Sharpness of the corners of the wall (try 0.05 as initial value) # Core shape selection @@ -230,12 +231,13 @@ Struct containing input settings for vacuum wall geometry. shape::String = "nowall" # Standard geometric parameters for Dee/Mod-Dee - a::Float64 = 0.3 - aw::Float64 = 0.05 - bw::Float64 = 1.5 - cw::Float64 = 0.0 - dw::Float64 = 0.5 - tw::Float64 = 0.05 + standoff_fraction::Float64 = 0.3 + minor_radius::Float64 = 0.3 + half_thickness::Float64 = 0.05 + elongation::Float64 = 1.5 + center_offset::Float64 = 0.0 + triangularity::Float64 = 0.5 + corner_sharpness::Float64 = 0.05 # Algorithmic options equal_arc_wall::Bool = true @@ -513,7 +515,10 @@ function WallGeometry(inputs::VacuumInput, plasma_surf::PlasmaGeometry, wall_set r_major = 0.5 * (xmax + xmin) # Destructuring settings for readability - (; aw, bw, cw, dw, tw, a) = wall_settings + # Short Fortran vac.in names for the shape formulas; `a` is the standoff or wall radius per shape + aw, bw, cw, dw, tw = wall_settings.half_thickness, wall_settings.elongation, wall_settings.center_offset, + wall_settings.triangularity, wall_settings.corner_sharpness + a = wall_settings.shape in ("elliptical", "mod_dee") ? wall_settings.minor_radius : wall_settings.standoff_fraction wcentr = 0.0 # Initialize if wall_settings.shape == "conformal" @@ -531,7 +536,7 @@ function WallGeometry(inputs::VacuumInput, plasma_surf::PlasmaGeometry, wall_set end if any(x_wall .<= centerstack_min + eps(Float64)) - @warn "Conformal wall with a=$a would cross R=0 axis; forcing minimum wall R to $(@sprintf "%.2e" centerstack_min) m to avoid unphysical geometry." + @warn "Conformal wall with standoff_fraction=$a would cross R=0 axis; forcing minimum wall R to $(@sprintf "%.2e" centerstack_min) m to avoid unphysical geometry." end elseif wall_settings.shape == "elliptical" @@ -743,9 +748,9 @@ function WallGeometry3D(inputs::VacuumInput, plasma_surf::PlasmaGeometry3D, wall wall_settings.equal_arc_wall && @info "equal_arc_wall is ignored for non-axisymmetric (nzeta_in > 1) walls: it re-parameterizes a 2D contour and would break the plasma/wall index alignment the near-field patch relies on." - # Same logic as 2D, with the extrema taken over the whole torus (see WallShapeSettings.a) + # Same logic as 2D, with the extrema taken over the whole torus (see WallShapeSettings.standoff_fraction) R_plasma = [hypot(plasma_surf.r[idx, 1], plasma_surf.r[idx, 2]) for idx in axes(plasma_surf.r, 1)] - offset_gap = wall_settings.a * 0.5 * (maximum(R_plasma) - minimum(R_plasma)) + offset_gap = wall_settings.standoff_fraction * 0.5 * (maximum(R_plasma) - minimum(R_plasma)) @info "Calculating conformal wall shape $((@sprintf "%.2e" offset_gap)) m from plasma surface." # Plasma normal points into the plasma, so the displacement is along -normal diff --git a/test/runtests_coils.jl b/test/runtests_coils.jl index be6b98556..b6f0c75c2 100644 --- a/test/runtests_coils.jl +++ b/test/runtests_coils.jl @@ -568,6 +568,9 @@ end # A 10° X-tilt should move the (originally flat) hoop out of the z=0.2 plane @test !all(isapprox.(out[1].z, 0.2; atol=1e-6)) + # A misspelled or retired coil_set key is an error, not silently ignored + @test_throws ArgumentError ForcingTerms._parse_coil_set_config(Dict{String,Any}("source" => "pf_hoop", "xnom" => [1.0])) + # File source dispatches on extension: .h5 -> read_coil_h5, .dat -> read_coil_dat mktempdir() do dir h5 = joinpath(dir, "il.h5") @@ -590,7 +593,7 @@ end # window_pane standoff mode requires an equilibrium cfg_standoff = ForcingTerms.CoilConfig(; coil_sets=[ - ForcingTerms.CoilSetConfig(; source="window_pane", ncoil_gen=2, + ForcingTerms.CoilSetConfig(; source="window_pane", n_coils=2, standoff=0.2, poloidal_angle=0.0, poloidal_length=0.6, currents=[1.0, -1.0]) ] ) @@ -599,7 +602,7 @@ end # Supplying both rz_corners and standoff is ambiguous cfg_both = ForcingTerms.CoilConfig(; coil_sets=[ - ForcingTerms.CoilSetConfig(; source="window_pane", ncoil_gen=2, + ForcingTerms.CoilSetConfig(; source="window_pane", n_coils=2, rz_corners=[[2.2, -0.5], [2.2, 0.5]], standoff=0.2, poloidal_angle=0.0) ] ) @@ -645,7 +648,7 @@ end println(io, l) end end - cfg(path) = Equilibrium.EquilibriumConfig(; eq_filename=path, eq_type="efit", jac_type="hamada", grid_type="ldp", psilow=0.01, psihigh=0.99, mpsi=32, mtheta=64) + cfg(path) = Equilibrium.EquilibriumConfig(; eq_filename=path, eq_type="efit", jac_type="hamada", grid_type="core_edge_packed", psilow=0.01, psihigh=0.99, mpsi=32, mtheta=64) eq_pos = Equilibrium.setup_equilibrium(cfg(gfile)) eq_neg = Equilibrium.setup_equilibrium(cfg(flipped)) @test eq_pos.params.ip_sign == 1 && eq_neg.params.ip_sign == -1 diff --git a/test/runtests_equil.jl b/test/runtests_equil.jl index b4a257956..7705a476a 100644 --- a/test/runtests_equil.jl +++ b/test/runtests_equil.jl @@ -10,7 +10,7 @@ eq_filename=joinpath(data_dir, "EQDSK_COCOS_02"), eq_type="efit", jac_type="boozer", - grid_type="ldp", + grid_type="core_edge_packed", psilow=0.01, psihigh=0.994 ) @@ -25,7 +25,7 @@ eq_filename=joinpath(data_dir, "EQDSK_COCOS_02"), eq_type="efit_arclength", jac_type="boozer", - grid_type="ldp", + grid_type="core_edge_packed", psilow=0.01, psihigh=0.994 ) @@ -49,7 +49,7 @@ eq_filename=joinpath(data_dir, "EQDSK_COCOS_02"), eq_type="efit_by_inversion", jac_type="boozer", - grid_type="ldp", + grid_type="core_edge_packed", psilow=0.01, psihigh=0.994 ) @@ -90,7 +90,7 @@ eq_filename=joinpath(data_dir, "EQDSK_COCOS_02"), eq_type="efit", jac_type="boozer", - grid_type="ldp", + grid_type="core_edge_packed", psilow=0.01, psihigh=1.0 ) @@ -107,7 +107,7 @@ eq_filename=joinpath(data_dir, "EQDSK_COCOS_02"), eq_type="efit", jac_type="boozer", - grid_type="ldp", + grid_type="core_edge_packed", mpsi=32, psilow=0.01, psihigh=0.994, @@ -169,11 +169,11 @@ eq_filename=joinpath(data_dir, "INP1_binary"), eq_type="chease_binary", jac_type="boozer", - grid_type="ldp", + grid_type="core_edge_packed", psilow=0.01, psihigh=0.994, - r0exp=6.8, - b0exp=7.4 + r0_norm=6.8, + b0_norm=7.4 ) global plasma_eq_binary = GeneralizedPerturbedEquilibrium.Equilibrium.setup_equilibrium(binary_config) @@ -185,11 +185,11 @@ eq_filename=joinpath(data_dir, "INP1_ascii"), eq_type="chease_ascii", jac_type="boozer", - grid_type="ldp", + grid_type="core_edge_packed", psilow=0.01, psihigh=0.994, - r0exp=6.8, - b0exp=7.4 + r0_norm=6.8, + b0_norm=7.4 ) global plasma_eq_ascii = GeneralizedPerturbedEquilibrium.Equilibrium.setup_equilibrium(ascii_config) @@ -246,7 +246,7 @@ # values up to 2π from values in [0,0.5]), causing physically impossible B fields # and wildly non-constant r² on what should be closed flux surfaces. @testset "CHEASE Physical Validation" begin - b0exp = 7.4 # CHEASE normalization field [T] + b0_norm = 7.4 # CHEASE normalization field [T] B_nodes_binary = plasma_eq_binary.eqfun_B.nodal_derivs.partials[1, :, :] B_nodes_ascii = plasma_eq_ascii.eqfun_B.nodal_derivs.partials[1, :, :] @@ -258,10 +258,10 @@ @test all(>(0), B_nodes_ascii) # B field must be within a factor of 3 of the normalization field - @test all(B_nodes_binary .> b0exp / 3) - @test all(B_nodes_binary .< b0exp * 3) - @test all(B_nodes_ascii .> b0exp / 3) - @test all(B_nodes_ascii .< b0exp * 3) + @test all(B_nodes_binary .> b0_norm / 3) + @test all(B_nodes_binary .< b0_norm * 3) + @test all(B_nodes_ascii .> b0_norm / 3) + @test all(B_nodes_ascii .< b0_norm * 3) # q must be finite, positive, and in a physically reasonable range q_binary = plasma_eq_binary.profiles.q_spline.y @@ -280,13 +280,13 @@ lar_config = GeneralizedPerturbedEquilibrium.Equilibrium.EquilibriumConfig(; eq_type="lar", jac_type="boozer", - grid_type="ldp", + grid_type="core_edge_packed", psilow=0.01, psihigh=0.99 ) lar_input = GeneralizedPerturbedEquilibrium.Equilibrium.LargeAspectRatioConfig(; - lar_r0=10.0, lar_a=1.0, q0=1.5, beta0=1e-4, - mtau=64, ma=64, zeroth=true + r0=10.0, a=1.0, q0=1.5, beta0=1e-4, + mtheta=64, ma=64, zeroth=true ) plasma_eq_lar = GeneralizedPerturbedEquilibrium.Equilibrium.setup_equilibrium(lar_config, lar_input) @@ -346,7 +346,7 @@ @testset "sol_run scalar relationships" begin equil_inputs, sol_inputs = make_inputs() mr, mz, ma = sol_inputs.mr, sol_inputs.mz, sol_inputs.ma - e, a, r0, q0 = sol_inputs.e, sol_inputs.a, sol_inputs.r0, sol_inputs.q0 + e, a, r0, q0 = sol_inputs.elongation, sol_inputs.a, sol_inputs.r0, sol_inputs.q0 p0fac, b0fac, f0fac = sol_inputs.p0fac, sol_inputs.b0fac, sol_inputs.f0fac dri = GeneralizedPerturbedEquilibrium.Equilibrium.sol_run(equil_inputs, sol_inputs) @@ -408,7 +408,7 @@ dri2 = GeneralizedPerturbedEquilibrium.Equilibrium.sol_run( equil_inputs, GeneralizedPerturbedEquilibrium.Equilibrium.SolovevConfig(sol_inputs.mr, sol_inputs.mz, sol_inputs.ma, - sol_inputs.e * 1.1, sol_inputs.a, sol_inputs.r0, + sol_inputs.elongation * 1.1, sol_inputs.a, sol_inputs.r0, sol_inputs.q0, sol_inputs.p0fac, sol_inputs.b0fac, sol_inputs.f0fac) ) @@ -446,7 +446,7 @@ mpsi=64, mtheta=128) eq_config = Eq.EquilibriumConfig(; eq_type="sol", eq_filename="unused", - jac_type="pest", grid_type="ldp", + jac_type="pest", grid_type="core_edge_packed", psilow=1e-4, psihigh=0.99999, mpsi=mpsi, mtheta=mtheta) sol_config = Eq.SolovevConfig(64, 64, 64, e, a, r0, q0, 1.0, 1.0, 1.0) dri = Eq.sol_run(eq_config, sol_config) diff --git a/test/runtests_eulerlagrange.jl b/test/runtests_eulerlagrange.jl index 02fa2f07e..2cc8f0300 100644 --- a/test/runtests_eulerlagrange.jl +++ b/test/runtests_eulerlagrange.jl @@ -307,7 +307,7 @@ end mpert = 2 odet = GeneralizedPerturbedEquilibrium.ForceFreeStates.OdeState(mpert, 10, 10, 10) intr = GeneralizedPerturbedEquilibrium.ForceFreeStates.ForceFreeStatesInternal(; mpert=mpert) - ctrl = GeneralizedPerturbedEquilibrium.ForceFreeStates.ForceFreeStatesControl(; ucrit=10.0) + ctrl = GeneralizedPerturbedEquilibrium.ForceFreeStates.ForceFreeStatesControl(; renorm_threshold=10.0) # Case 1: Basic norm computation odet.u = zeros(ComplexF64, 2, 2, 2) @@ -333,7 +333,7 @@ end GeneralizedPerturbedEquilibrium.ForceFreeStates.compute_solution_norms!(odet.u, odet, ctrl, intr, false) @test odet.unorm[1:intr.mpert] ≈ [1, 1] - # Case 4: Trigger fixup via ucrit + # Case 4: Trigger fixup via renorm_threshold odet.unorm0 = ones(intr.mpert) odet.u[:, 1, 1] .= [1000, 0] # large norm odet.u[:, 2, 1] .= [1, 0] # small norm diff --git a/test/runtests_kinetic.jl b/test/runtests_kinetic.jl index d502385f7..a9f7ae71a 100644 --- a/test/runtests_kinetic.jl +++ b/test/runtests_kinetic.jl @@ -479,9 +479,9 @@ @test ctrl.fgar_flag == true @test ctrl.tgar_flag == false @test ctrl.nn == 1 - @test ctrl.nl == 1 - @test ctrl.zi == 1 - @test ctrl.mi == 2 + @test ctrl.bounce_harmonic_max == 1 + @test ctrl.ion_charge == 1 + @test ctrl.ion_mass == 2 @test ctrl.nutype == "harmonic" @test ctrl.f0type == "maxwellian" @test ctrl.psilims == [0.0, 1.0] diff --git a/test/runtests_rerun_from_h5.jl b/test/runtests_rerun_from_h5.jl index f52e76986..13172216b 100644 --- a/test/runtests_rerun_from_h5.jl +++ b/test/runtests_rerun_from_h5.jl @@ -268,8 +268,8 @@ end Equil = GeneralizedPerturbedEquilibrium.Equilibrium config = Equil.EquilibriumConfig(; eq_filename=joinpath(@__DIR__, "test_data", "CHEASE_test_data", "INP1_ascii"), - eq_type="chease_ascii", jac_type="boozer", grid_type="ldp", - psilow=0.01, psihigh=0.994, r0exp=6.8, b0exp=7.4 + eq_type="chease_ascii", jac_type="boozer", grid_type="core_edge_packed", + psilow=0.01, psihigh=0.994, r0_norm=6.8, b0_norm=7.4 ) src = Equil.read_chease_ascii(config) @test src isa Equil.InverseRunInput diff --git a/test/runtests_sing.jl b/test/runtests_sing.jl index d58d11c2b..b77309c9d 100644 --- a/test/runtests_sing.jl +++ b/test/runtests_sing.jl @@ -177,19 +177,19 @@ using FastInterpolations: cubic_interp, CubicFit, LinearBinarySearch, Series, Ex # --------------------------------- @testset "sing_lim" begin equil = load_equilibrium_from_gpec(joinpath(@__DIR__, "test_data", "regression_solovev_ideal_example", "gpec.toml")) - ctrl = GeneralizedPerturbedEquilibrium.ForceFreeStates.ForceFreeStatesControl(; qhigh=equil.params.qmax, set_psilim_via_dmlim=false) + ctrl = GeneralizedPerturbedEquilibrium.ForceFreeStates.ForceFreeStatesControl(; qhigh=equil.params.qmax, truncate_at_rational_offset=false) intr = GeneralizedPerturbedEquilibrium.ForceFreeStates.ForceFreeStatesInternal() GeneralizedPerturbedEquilibrium.ForceFreeStates.sing_lim!(intr, ctrl, equil) @test isapprox(intr.qlim, equil.params.qmax; atol=1e-12) @test isapprox(intr.psilim, equil.params.psihigh_resolved; atol=1e-12) - ctrl = GeneralizedPerturbedEquilibrium.ForceFreeStates.ForceFreeStatesControl(; qhigh=max(equil.params.qmin + 0.1, equil.params.qmax - 0.5), set_psilim_via_dmlim=false) + ctrl = GeneralizedPerturbedEquilibrium.ForceFreeStates.ForceFreeStatesControl(; qhigh=max(equil.params.qmin + 0.1, equil.params.qmax - 0.5), truncate_at_rational_offset=false) GeneralizedPerturbedEquilibrium.ForceFreeStates.sing_lim!(intr, ctrl, equil) @test intr.qlim < equil.params.qmax + 1e-12 @test intr.psilim <= equil.params.psihigh_resolved q_at_psilim = equil.profiles.q_spline(intr.psilim) @test isapprox(q_at_psilim, intr.qlim; atol=1e-6) - ctrl_dmlim = GeneralizedPerturbedEquilibrium.ForceFreeStates.ForceFreeStatesControl(; qhigh=equil.params.qmax, set_psilim_via_dmlim=true) + ctrl_dmlim = GeneralizedPerturbedEquilibrium.ForceFreeStates.ForceFreeStatesControl(; qhigh=equil.params.qmax, truncate_at_rational_offset=true) intr_unresolved = GeneralizedPerturbedEquilibrium.ForceFreeStates.ForceFreeStatesInternal() @test_throws ErrorException GeneralizedPerturbedEquilibrium.ForceFreeStates.sing_lim!(intr_unresolved, ctrl_dmlim, equil) end diff --git a/test/runtests_slayer_inputs.jl b/test/runtests_slayer_inputs.jl index fce001036..ccb881ecb 100644 --- a/test/runtests_slayer_inputs.jl +++ b/test/runtests_slayer_inputs.jl @@ -115,16 +115,16 @@ end @testset "build_slayer_inputs: bt defaults to the physical B_T, not a normalization" begin - # `b0exp` is a normalization, not a field; passing it as `bt` ran the layer physics at the + # `b0_norm` is a normalization, not a field; passing it as `bt` ran the layer physics at the # wrong toroidal field. Asserted behaviourally rather than by grepping the source, so a # refactor cannot silently break the check and a reintroduction anywhere in the chain still # fails: tau_h = R0*sqrt(mu0*rho)/(n*sval_r*bt), so lu is linear in the field actually used # and the resolved bt is recoverable from the returned parameters. # # Note this fixture cannot exhibit the original bug: Solovev is normalized so that - # b0exp == 1.0 and F(psi)/(2*pi*R0) == 1.0 to roundoff, which is exactly why the defect + # b0_norm == 1.0 and F(psi)/(2*pi*R0) == 1.0 to roundoff, which is exactly why the defect # survived. It shows up on a deck whose normalization differs from its field -- the - # DIII-D-like EFIT deck has b0exp = 1.0 against a physical ~1.95 T. What is pinned here is + # DIII-D-like EFIT deck has b0_norm = 1.0 against a physical ~1.95 T. What is pinned here is # therefore the resolution rule itself, which is deck-independent. sings = [_mk_sing(psi=0.3, q=2.0, q1=1.5, m=2, n=1)] bt_phys = Float64(equil.profiles.F_spline(0.3)) / (2π * equil.ro) @@ -193,14 +193,14 @@ bt=2.0, dc_type=:none, dr_val=0.0) @test sl_none[1].dc_tmp == 0.0 - # dc_type=:rfitzp with dr_val = 0 still gives zero + # dc_type=:fitzpatrick with dr_val = 0 still gives zero sl_rf0 = build_slayer_inputs(equil, sings, profiles; - bt=2.0, dc_type=:rfitzp, dr_val=0.0) + bt=2.0, dc_type=:fitzpatrick, dr_val=0.0) @test sl_rf0[1].dc_tmp == 0.0 - # dc_type=:rfitzp with dr_val > 0 → nonzero negative offset + # dc_type=:fitzpatrick with dr_val > 0 → nonzero negative offset sl_rf = build_slayer_inputs(equil, sings, profiles; - bt=2.0, dc_type=:rfitzp, dr_val=0.01) + bt=2.0, dc_type=:fitzpatrick, dr_val=0.01) @test sl_rf[1].dc_tmp < 0 @test isfinite(sl_rf[1].dc_tmp) end diff --git a/test/runtests_slayer_params.jl b/test/runtests_slayer_params.jl index 1f665fb1d..ac7c15c8a 100644 --- a/test/runtests_slayer_params.jl +++ b/test/runtests_slayer_params.jl @@ -100,14 +100,14 @@ @test p_none.dc_tmp == 0.0 # :none ignores dr_val p_lar = slayer_parameters(; _ref_kwargs(; dr_val=0.01, dc_type=:lar)...) - p_rf = slayer_parameters(; _ref_kwargs(; dr_val=0.01, dc_type=:rfitzp)...) + p_rf = slayer_parameters(; _ref_kwargs(; dr_val=0.01, dc_type=:fitzpatrick)...) p_tor = slayer_parameters(; _ref_kwargs(; dr_val=0.01, dc_type=:toroidal)...) @test isfinite(p_lar.dc_tmp) @test isfinite(p_rf.dc_tmp) @test isfinite(p_tor.dc_tmp) # dr_val > 0 with the (-dr_val) prefactor ⇒ negative dc_tmp for - # :lar, :rfitzp, :toroidal branches. + # :lar, :fitzpatrick, :toroidal branches. @test p_lar.dc_tmp < 0 @test p_rf.dc_tmp < 0 @test p_tor.dc_tmp < 0 diff --git a/test/runtests_slayer_runner.jl b/test/runtests_slayer_runner.jl index ee114e37f..c6d5e7788 100644 --- a/test/runtests_slayer_runner.jl +++ b/test/runtests_slayer_runner.jl @@ -39,7 +39,7 @@ @test_throws ArgumentError Runner.validate( SLAYERControl(; coupling_mode=:bogus)) @test_throws ArgumentError Runner.validate( - SLAYERControl(; dc_type=:bogus)) + SLAYERControl(; D_c_type=:bogus)) @test_throws ArgumentError Runner.validate( SLAYERControl(; msing_max=0)) @test_throws ArgumentError Runner.validate( @@ -52,11 +52,11 @@ "inner_model" => "slayer_fitzpatrick", "scan_mode" => "brute_force", "coupling_mode" => "coupled", - "dc_type" => "rfitzp", + "D_c_type" => "fitzpatrick", "msing_max" => 2, "bt" => 1.8, - "mu_i" => 2.0, - "dr_val" => 0.01, + "ion_mass" => 2.0, + "D_R" => 0.01, "scan_grid" => Dict{String,Any}( "Q_re_range" => [-5.0, 5.0], "Q_im_range" => [-1.0, 3.0], @@ -75,10 +75,10 @@ @test c.inner_model === :slayer_fitzpatrick @test c.scan_mode === :brute_force @test c.coupling_mode === :coupled - @test c.dc_type === :rfitzp + @test c.D_c_type === :fitzpatrick @test c.msing_max == 2 @test c.bt === 1.8 - @test c.dr_val == 0.01 + @test c.D_R == 0.01 @test c.Q_re_range == (-5.0, 5.0) @test c.Q_im_range == (-1.0, 3.0) @test c.nre == 50 diff --git a/test/runtests_solve_api.jl b/test/runtests_solve_api.jl index d04eb9065..77ed51b44 100644 --- a/test/runtests_solve_api.jl +++ b/test/runtests_solve_api.jl @@ -2,7 +2,7 @@ using TOML # The scripting API: `PlasmaEquilibrium` / `solve(eq, alg)` / `perturbed_equilibrium` must run the # same pipeline the TOML driver does. Every assertion is anchored on the coarse Solovev fixture -# deck (mpsi=16, mthvac=64, delta_m=0), so an API run is compared against `main` on the same deck. +# deck (mpsi=16, mtheta_vacuum=64, delta_m=0), so an API run is compared against `main` on the same deck. @testset "solve API" begin GPEC = GeneralizedPerturbedEquilibrium FFS = GPEC.ForceFreeStates diff --git a/test/runtests_tj_analytic.jl b/test/runtests_tj_analytic.jl index 5bbcb25d2..da080440c 100644 --- a/test/runtests_tj_analytic.jl +++ b/test/runtests_tj_analytic.jl @@ -19,12 +19,12 @@ using GeneralizedPerturbedEquilibrium.Equilibrium: TJAnalyticConfig, Equilibrium @testset "TJ-analytic model" begin @testset "tj_analytic_run (inverse) — basic invariants at ε = 0.25" begin # Keep ε, mpsi, mtheta modest so the whole block runs in ~1 s. - tj = TJAnalyticConfig(lar_r0 = 1.0 / 0.25, lar_a = 1.0, - qc = 1.5, qa = 3.6, pc = 0.001, mu = 2.0, B0 = 12.0, - ma = 64, mtau = 64) + tj = TJAnalyticConfig(r0 = 1.0 / 0.25, a = 1.0, + q0 = 1.5, qa = 3.6, p0 = 0.001, pressure_peaking = 2.0, B0 = 12.0, + ma = 64, mtheta = 64) eq = EquilibriumConfig(eq_type = "tj_analytic", psilow = 0.01, psihigh = 0.995, - mpsi = 64, mtheta = 128, etol = 1e-7) + mpsi = 64, mtheta = 128, flux_surface_rtol = 1e-7) pe = setup_equilibrium(eq, tj) # psio is a physical-scale ψ; regressions in the a→a² normalization @@ -46,12 +46,12 @@ using GeneralizedPerturbedEquilibrium.Equilibrium: TJAnalyticConfig, Equilibrium # ε = 0.60 sits on the stable side of the ideal-external-kink pole at # ε ≈ 0.665 for this (qc, qa, pc, μ) combination. Pole-approach shape # (δW_t small, Δ' > 0 and growing) is the Option B success criterion. - tj = TJAnalyticConfig(lar_r0 = 1.0 / 0.60, lar_a = 1.0, - qc = 1.5, qa = 3.6, pc = 0.001, mu = 2.0, B0 = 12.0, - ma = 64, mtau = 64) + tj = TJAnalyticConfig(r0 = 1.0 / 0.60, a = 1.0, + q0 = 1.5, qa = 3.6, p0 = 0.001, pressure_peaking = 2.0, B0 = 12.0, + ma = 64, mtheta = 64) eq = EquilibriumConfig(eq_type = "tj_analytic_direct", psilow = 0.01, psihigh = 0.995, - mpsi = 64, mtheta = 128, etol = 1e-7) + mpsi = 64, mtheta = 128, flux_surface_rtol = 1e-7) pe = setup_equilibrium(eq, tj) @test pe.psio > 0 @@ -73,12 +73,12 @@ using GeneralizedPerturbedEquilibrium.Equilibrium: TJAnalyticConfig, Equilibrium # At the magnetic axis ψ_in should equal psio (axis convention: ψ # positive at axis, zero at LCFS); sampling well outside the LCFS should # give a negative value (the vacuum branch of psi_rz). - tj = TJAnalyticConfig(lar_r0 = 1.0 / 0.25, lar_a = 1.0, - qc = 1.5, qa = 3.6, pc = 0.001, mu = 2.0, B0 = 12.0, - ma = 64, mtau = 64) + tj = TJAnalyticConfig(r0 = 1.0 / 0.25, a = 1.0, + q0 = 1.5, qa = 3.6, p0 = 0.001, pressure_peaking = 2.0, B0 = 12.0, + ma = 64, mtheta = 64) eq = EquilibriumConfig(eq_type = "tj_analytic_direct", psilow = 0.01, psihigh = 0.995, - mpsi = 64, mtheta = 128, etol = 1e-7) + mpsi = 64, mtheta = 128, flux_surface_rtol = 1e-7) inp = tj_analytic_run_direct(eq, tj) # ψ at the geometric axis matches psio (see DirectRunInput docstring for diff --git a/test/runtests_vacuum.jl b/test/runtests_vacuum.jl index 1445a3818..9c56a1218 100644 --- a/test/runtests_vacuum.jl +++ b/test/runtests_vacuum.jl @@ -32,14 +32,14 @@ @testset "default constructor" begin w = WallShapeSettings() @test w.shape == "nowall" - @test w.a == 0.3 + @test w.standoff_fraction == 0.3 @test w.equal_arc_wall == true end @testset "keyword constructor" begin - w = WallShapeSettings(shape="conformal", a=0.2, equal_arc_wall=false) + w = WallShapeSettings(shape="conformal", standoff_fraction=0.2, equal_arc_wall=false) @test w.shape == "conformal" - @test w.a == 0.2 + @test w.standoff_fraction == 0.2 @test w.equal_arc_wall == false end end @@ -110,7 +110,7 @@ @testset "conformal" begin inputs = _circle_inputs(16) plasma_surf = GeneralizedPerturbedEquilibrium.Vacuum.PlasmaGeometry(inputs) - wall_settings = WallShapeSettings(shape="conformal", a=0.2) + wall_settings = WallShapeSettings(shape="conformal", standoff_fraction=0.2) wall = GeneralizedPerturbedEquilibrium.Vacuum.WallGeometry(inputs, plasma_surf, wall_settings) @test wall.nowall == false @test length(wall.x) == 16 @@ -125,7 +125,7 @@ @testset "elliptical" begin inputs = _circle_inputs(16) plasma_surf = GeneralizedPerturbedEquilibrium.Vacuum.PlasmaGeometry(inputs) - wall_settings = WallShapeSettings(shape="elliptical", a=0.5) + wall_settings = WallShapeSettings(shape="elliptical", minor_radius=0.5) wall = GeneralizedPerturbedEquilibrium.Vacuum.WallGeometry(inputs, plasma_surf, wall_settings) @test wall.nowall == false @test length(wall.x) == 16 @@ -136,7 +136,7 @@ @testset "dee" begin inputs = _circle_inputs(16) plasma_surf = GeneralizedPerturbedEquilibrium.Vacuum.PlasmaGeometry(inputs) - wall_settings = WallShapeSettings(shape="dee", a=0.1, cw=0.0) + wall_settings = WallShapeSettings(shape="dee", standoff_fraction=0.1, center_offset=0.0) wall = GeneralizedPerturbedEquilibrium.Vacuum.WallGeometry(inputs, plasma_surf, wall_settings) @test wall.nowall == false @test length(wall.x) == 16 @@ -159,7 +159,7 @@ # Use a "dee" wall shape with parameters that will produce R < 0 # Setting cw (offset) to a large negative value will shift the wall left past R=0 - wall_settings = WallShapeSettings(shape="dee", cw=-1.5, a=0.1) + wall_settings = WallShapeSettings(shape="dee", center_offset=-1.5, standoff_fraction=0.1) @test_throws ErrorException GeneralizedPerturbedEquilibrium.Vacuum.WallGeometry(inputs, plasma_surf_near_zero, wall_settings) end @@ -179,7 +179,7 @@ nzeta=1 ) ) - wall_settings = WallShapeSettings(shape="conformal", a=10.0, equal_arc_wall=false) + wall_settings = WallShapeSettings(shape="conformal", standoff_fraction=10.0, equal_arc_wall=false) wall = GeneralizedPerturbedEquilibrium.Vacuum.WallGeometry(inputs, plasma_surf_near_zero, wall_settings) expected_min = min(0.1, 0.1 * minimum(plasma_surf_near_zero.x)) @test all(wall.x .>= expected_min) @@ -397,7 +397,7 @@ @testset "conformal wall" begin inputs = _make_inputs() - wall_settings = WallShapeSettings(shape="conformal", a=0.5) + wall_settings = WallShapeSettings(shape="conformal", standoff_fraction=0.5) (; wv, I_v, plasma_pts, wall_pts) = compute_vacuum_response(inputs, wall_settings) num_modes = length(inputs.m_modes) * length(inputs.n_modes) @@ -433,7 +433,7 @@ @testset "in-place compute_vacuum_response! matches wrapper" begin # The allocating wrapper is a thin caller of the in-place routine; verify the # in-place entry populates caller-owned storage identically. - for wall_settings in (WallShapeSettings(shape="nowall"), WallShapeSettings(shape="conformal", a=0.5)) + for wall_settings in (WallShapeSettings(shape="nowall"), WallShapeSettings(shape="conformal", standoff_fraction=0.5)) inputs = _make_inputs() ref = compute_vacuum_response(inputs, wall_settings; compute_Iv=true) @@ -453,7 +453,7 @@ inputs = _make_inputs() vac = VacuumResponse(inputs) - compute_vacuum_response!(vac, inputs, WallShapeSettings(; shape="conformal", a=0.5); compute_Iv=true) + compute_vacuum_response!(vac, inputs, WallShapeSettings(; shape="conformal", standoff_fraction=0.5); compute_Iv=true) @test !all(iszero, vac.I_v) compute_vacuum_response!(vac, inputs, WallShapeSettings(; shape="nowall")) @@ -466,7 +466,8 @@ @testset "extract_plasma_surface_at_psi" begin # Self-contained analytic Solovev equilibrium (same recipe as runtests_equil.jl). - eq_config = Equilibrium.EquilibriumConfig(; eq_type="sol", eq_filename="unused", jac_type="pest", grid_type="ldp", psilow=1e-4, psihigh=0.99999, mpsi=64, mtheta=128) + eq_config = Equilibrium.EquilibriumConfig(; eq_type="sol", eq_filename="unused", jac_type="pest", grid_type="core_edge_packed", + psilow=1e-4, psihigh=0.99999, mpsi=64, mtheta=128) sol_config = Equilibrium.SolovevConfig(64, 64, 64, 1.6, 0.33, 1.0, 1.9, 1.0, 1.0, 1.0) pe = Equilibrium.equilibrium_solver(Equilibrium.sol_run(eq_config, sol_config)) @@ -485,7 +486,8 @@ @testset "calc_surface_inductance" begin # Same Solovev recipe as above; exercises the PerturbedEquilibrium helper end-to-end - eq_config = Equilibrium.EquilibriumConfig(; eq_type="sol", eq_filename="unused", jac_type="pest", grid_type="ldp", psilow=1e-4, psihigh=0.99999, mpsi=64, mtheta=128) + eq_config = Equilibrium.EquilibriumConfig(; eq_type="sol", eq_filename="unused", jac_type="pest", grid_type="core_edge_packed", + psilow=1e-4, psihigh=0.99999, mpsi=64, mtheta=128) sol_config = Equilibrium.SolovevConfig(64, 64, 64, 1.6, 0.33, 1.0, 1.9, 1.0, 1.0, 1.0) pe = Equilibrium.equilibrium_solver(Equilibrium.sol_run(eq_config, sol_config)) @@ -621,7 +623,7 @@ wall = GeneralizedPerturbedEquilibrium.Vacuum.WallGeometry3D( inputs, GeneralizedPerturbedEquilibrium.Vacuum.PlasmaGeometry3D(inputs), - WallShapeSettings(shape="conformal", a=0.2) + WallShapeSettings(shape="conformal", standoff_fraction=0.2) ) @test wall.nowall == false @test wall.mtheta == 32 @@ -653,7 +655,7 @@ @testset "WallGeometry3D conformal, non-axisymmetric input" begin inputs = _make_3d_periodic_inputs(mtheta=24, nzeta=24) - settings = WallShapeSettings(shape="conformal", a=0.2, equal_arc_wall=false) + settings = WallShapeSettings(shape="conformal", standoff_fraction=0.2, equal_arc_wall=false) plasma = GeneralizedPerturbedEquilibrium.Vacuum.PlasmaGeometry3D(inputs) wall = GeneralizedPerturbedEquilibrium.Vacuum.WallGeometry3D(inputs, plasma, settings) @@ -689,7 +691,7 @@ inputs = _make_3d_periodic_inputs(mtheta=24, nzeta=24, mtheta_in=32, nzeta_in=16, eps=0.0) plasma = GeneralizedPerturbedEquilibrium.Vacuum.PlasmaGeometry3D(inputs) for a in (0.2, 0.5) - wall = GeneralizedPerturbedEquilibrium.Vacuum.WallGeometry3D(inputs, plasma, WallShapeSettings(shape="conformal", a=a, equal_arc_wall=false)) + wall = GeneralizedPerturbedEquilibrium.Vacuum.WallGeometry3D(inputs, plasma, WallShapeSettings(shape="conformal", standoff_fraction=a, equal_arc_wall=false)) radii = [hypot(hypot(wall.r[i, 1], wall.r[i, 2]) - R0, wall.r[i, 3]) for i in axes(wall.r, 1)] # Residual is the 32 -> 24 poloidal resampling of the boundary spline, not the offset @test all(isapprox(rad, r_minor * (1 + a); atol=5e-4) for rad in radii) @@ -707,12 +709,12 @@ @test_logs (:info, r"equal_arc_wall is ignored") match_mode=:any GeneralizedPerturbedEquilibrium.Vacuum.WallGeometry3D( inputs, plasma, - WallShapeSettings(shape="conformal", a=0.2, equal_arc_wall=true) + WallShapeSettings(shape="conformal", standoff_fraction=0.2, equal_arc_wall=true) ) # nfp > 1 can only reach the constructor from an axisymmetric boundary, and is rejected there per_period = VacuumInput(x=inputs.x, y=inputs.y, z=inputs.z, mtheta_in=16, nzeta_in=16, m_modes=inputs.m_modes, n_modes=inputs.n_modes, mtheta=24, nzeta=24, nfp=3) - @test_throws ErrorException GeneralizedPerturbedEquilibrium.Vacuum.WallGeometry3D(per_period, plasma, WallShapeSettings(shape="conformal", a=0.2)) + @test_throws ErrorException GeneralizedPerturbedEquilibrium.Vacuum.WallGeometry3D(per_period, plasma, WallShapeSettings(shape="conformal", standoff_fraction=0.2)) # nowall does not skip that guard @test_throws ErrorException GeneralizedPerturbedEquilibrium.Vacuum.WallGeometry3D(per_period, plasma, WallShapeSettings(shape="nowall")) # a plasma surface from a different grid is rejected rather than indexed off the end @@ -720,14 +722,14 @@ @test_throws ErrorException GeneralizedPerturbedEquilibrium.Vacuum.WallGeometry3D( inputs, GeneralizedPerturbedEquilibrium.Vacuum.PlasmaGeometry3D(other_grid), - WallShapeSettings(shape="conformal", a=0.2) + WallShapeSettings(shape="conformal", standoff_fraction=0.2) ) # an offset that folds the surface is rejected rather than silently returned folded = _make_3d_nonaxis_inputs(mtheta=24, nzeta=24, mtheta_in=12, nzeta_in=12) @test_throws ErrorException GeneralizedPerturbedEquilibrium.Vacuum.WallGeometry3D( folded, GeneralizedPerturbedEquilibrium.Vacuum.PlasmaGeometry3D(folded), - WallShapeSettings(shape="conformal", a=0.2, equal_arc_wall=false) + WallShapeSettings(shape="conformal", standoff_fraction=0.2, equal_arc_wall=false) ) end @@ -738,9 +740,9 @@ axi = VacuumInput(mtheta_in=32, nzeta_in=1, x=collect(1.7 .+ 0.3 .* cos.(θ_eq)), z=collect(0.45 .* sin.(θ_eq)), ν=zeros(32), m_modes=[1, 2], n_modes=[0, 1], mtheta=32, nzeta=32) - for (shape, settings) in (("elliptical", WallShapeSettings(shape="elliptical", a=0.5)), - ("dee", WallShapeSettings(shape="dee", a=0.3)), - ("mod_dee", WallShapeSettings(shape="mod_dee", a=0.5, cw=1.7))) + for (shape, settings) in (("elliptical", WallShapeSettings(shape="elliptical", minor_radius=0.5)), + ("dee", WallShapeSettings(shape="dee", standoff_fraction=0.3)), + ("mod_dee", WallShapeSettings(shape="mod_dee", minor_radius=0.5, center_offset=1.7))) wall = GeneralizedPerturbedEquilibrium.Vacuum.WallGeometry3D(axi, GeneralizedPerturbedEquilibrium.Vacuum.PlasmaGeometry3D(axi), settings) @test size(wall.r) == (32 * 32, 3) @test all(isfinite, wall.normal) @@ -751,7 +753,7 @@ # Gap warning uses the measured same-index separation, not a conformal-only offset that # is 0 for these shapes. a = 1 m sits well outside one coarser cell, so no sub-cell warning. @test_logs min_level=Base.CoreLogging.Warn GeneralizedPerturbedEquilibrium.Vacuum.WallGeometry3D( - axi, GeneralizedPerturbedEquilibrium.Vacuum.PlasmaGeometry3D(axi), WallShapeSettings(shape="elliptical", a=1.0) + axi, GeneralizedPerturbedEquilibrium.Vacuum.PlasmaGeometry3D(axi), WallShapeSettings(shape="elliptical", minor_radius=1.0) ) # an unreadable wall file is still reported by the 2D reader @test_throws ErrorException GeneralizedPerturbedEquilibrium.Vacuum.WallGeometry3D( @@ -803,7 +805,7 @@ @testset "compute_vacuum_response 3D conformal wall" begin inputs = _make_3d_inputs(mtheta=32, nzeta=32, mtheta_eq=17) - wall_settings = WallShapeSettings(shape="conformal", a=0.3) + wall_settings = WallShapeSettings(shape="conformal", standoff_fraction=0.3) (; wv, I_v, plasma_pts, wall_pts) = compute_vacuum_response(inputs, wall_settings) num_modes = length(inputs.m_modes) * length(inputs.n_modes) @@ -819,7 +821,7 @@ @testset "compute_vacuum_response 3D compute_Iv=true" begin inputs = _make_3d_inputs(mtheta=32, nzeta=32, mtheta_eq=17) # Both wall branches: I_v was left zeroed in 3D before, and the wall adds a second source block - for wall_settings in (WallShapeSettings(shape="nowall"), WallShapeSettings(shape="conformal", a=0.3)) + for wall_settings in (WallShapeSettings(shape="nowall"), WallShapeSettings(shape="conformal", standoff_fraction=0.3)) (; I_v) = compute_vacuum_response(inputs, wall_settings; compute_Iv=true) @test all(isfinite, I_v) @test !all(iszero, I_v) @@ -863,7 +865,7 @@ # the near-field patch keeps its full PATCH_RAD. On a 24 x 24 grid those two demands cannot be met # by a close wall: h_ζ ~ 2π R0 / nzeta is 0.45 m here. inputs = _make_3d_periodic_inputs(mtheta=24, nzeta=24) - settings = WallShapeSettings(shape="conformal", a=2.5, equal_arc_wall=false) + settings = WallShapeSettings(shape="conformal", standoff_fraction=2.5, equal_arc_wall=false) wv = compute_vacuum_response(inputs, settings).wv @test all(isfinite, wv) @@ -962,7 +964,7 @@ # A wall adds a second source block to the operator, so repeat the check with one present: # the field-period fold has to land the wall columns in the right block for both to agree. - walled = WallShapeSettings(shape="conformal", a=0.2, equal_arc_wall=false) + walled = WallShapeSettings(shape="conformal", standoff_fraction=0.2, equal_arc_wall=false) vac_red_wall = compute_vacuum_response(inputs_red, walled; compute_Iv=true) vac_full_wall = compute_vacuum_response(inputs_full, walled; compute_Iv=true) @test isapprox(vac_red_wall.wv, vac_full_wall.wv; rtol=1e-6, atol=1e-7) @@ -1000,7 +1002,7 @@ end mtheta, nzeta_p = 24, 8 - walled = WallShapeSettings(shape="conformal", a=0.2, equal_arc_wall=false) + walled = WallShapeSettings(shape="conformal", standoff_fraction=0.2, equal_arc_wall=false) # The whole item rests on the operator inheriting the reflection symmetry, so assert it directly. inputs = _stell_inputs(; mtheta=mtheta, nzeta_p=nzeta_p, nfp=3, n_modes=[1]) diff --git a/test/test_data/regression_solovev_ideal_example/gpec.toml b/test/test_data/regression_solovev_ideal_example/gpec.toml index 9b3d346ac..2e1607f7a 100644 --- a/test/test_data/regression_solovev_ideal_example/gpec.toml +++ b/test/test_data/regression_solovev_ideal_example/gpec.toml @@ -1,34 +1,34 @@ # Regression fixture: Solovev n=1 ideal stability (mirrors examples/Solovev_ideal_example). -# Coarsened for speed (mpsi=16, mthvac=64, delta_m=0) and run quietly with HDF5 output off; +# Coarsened for speed (mpsi=16, mtheta_vacuum=64, delta_m=0) and run quietly with HDF5 output off; # consumed by the test suite in test/runtests_*.jl. [Equilibrium] eq_type = "sol" # Type of the input 2D equilibrium file jac_type = "pest" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom) -grid_type = "ldp" # Radial grid packing type +grid_type = "core_edge_packed" # Radial grid packing type psilow = 1e-4 # Lower limit of normalized poloidal flux psihigh = 0.9995 # Upper limit of normalized poloidal flux mpsi = 16 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) mtheta = 256 # Number of poloidal grid points newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) -etol = 1e-7 # Error tolerance for equilibrium solver +flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface force_termination = false # Terminate after equilibrium setup (skip stability calculations) [Wall] shape = "conformal" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath); close conformal wall stabilizes the Solovev n=1 kink (see examples/Solovev_ideal_example/gpec.toml) -a = 0.2415 # Distance from plasma (conformal) or shape parameter -aw = 0.05 # Half-thickness parameter for Dee-shaped walls -bw = 1.5 # Elongation parameter for wall shapes -cw = 0 # Offset of wall center from major radius -dw = 0.5 # Triangularity parameter for wall shapes -tw = 0.05 # Sharpness of wall corners (try 0.05 as initial value) +standoff_fraction = 0.2415 # Wall standoff in plasma minor radii (conformal, dee) +half_thickness = 0.05 # Half-thickness parameter for Dee-shaped walls +elongation = 1.5 # Elongation parameter for wall shapes +center_offset = 0 # Offset of wall center from major radius +triangularity = 0.5 # Triangularity parameter for wall shapes +corner_sharpness = 0.05 # Sharpness of wall corners (try 0.05 as initial value) equal_arc_wall = true # Equal arc length distribution of nodes on wall [ForceFreeStates] local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile vac_flag = true # Compute plasma, vacuum, and total energies for free-boundary modes -psiedge = 0.99 # Edge dW(ψ) diagnostic scan band [psiedge, psilim]; set ≥ psilim to disable +dW_edge_scan_start = 0.99 # Edge dW(ψ) diagnostic scan band [dW_edge_scan_start, psilim]; set ≥ psilim to disable qlow = 1.02 # Integration initiated at q determined by min(q0, qlow) qhigh = 1e3 # Integration terminated at q limit determined by min(qa, qhigh) sing_start = 0 # Start integration at the sing_start'th rational from the axis (psilow) @@ -37,18 +37,18 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 1 # Largest toroidal mode number to include delta_mlow = 0 # Expands lower bound of Fourier harmonics delta_mhigh = 0 # Expands upper bound of Fourier harmonics -mthvac = 64 # Number of points used in splines over poloidal angle at the plasma-vacuum interface +mtheta_vacuum = 64 # Number of points used in splines over poloidal angle at the plasma-vacuum interface kinetic_source = "fixed" # Kinetic matrix source: "fixed" test matrices, or "calculated" from the kinetic NTV model kinetic_factor = 0.0 # Scaling of kinetic matrices (0 = ideal path; >0 enables kinetic mode) eulerlagrange_tolerance = 1e-7 # Relative tolerance for ODE integration of Euler-Lagrange equations singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e3 # Column-norm threshold that triggers solution renormalization +renorm_threshold = 1e3 # Column-norm threshold that triggers solution renormalization # Integrator selection and domain truncation (see ForceFreeStatesControl docstring for details) integrator = "forward" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) -set_psilim_via_dmlim = false # FALSE for limited/analytical equilibria — rationals sparse, dmlim would chop too much edge -dmlim = 0.2 # Truncate integration at (last_rational_q + dmlim)/n (used when set_psilim_via_dmlim = true) +truncate_at_rational_offset = false # FALSE for limited/analytical equilibria — rationals sparse, rational_offset_fraction would chop too much edge +rational_offset_fraction = 0.2 # Truncate integration at (last_rational_q + rational_offset_fraction)/n (used when truncate_at_rational_offset = true) write_outputs_to_HDF5 = false # Write outputs to the HDF5 file verbose = false # Enable verbose logging @@ -57,7 +57,7 @@ verbose = false # Enable verbose logging mr = 128 # Number of radial grid zones mz = 128 # Number of axial grid zones ma = 128 # Number of flux grid zones -e = 1.6 # Elongation +elongation = 1.6 # Elongation a = 0.33 # Minor radius r0 = 1.0 # Major radius q0 = 1.9 # Safety factor at the magnetic axis (O-point) diff --git a/test/test_data/regression_solovev_ideal_example_multi_n/gpec.toml b/test/test_data/regression_solovev_ideal_example_multi_n/gpec.toml index 58c4a6cd1..9ba608c55 100644 --- a/test/test_data/regression_solovev_ideal_example_multi_n/gpec.toml +++ b/test/test_data/regression_solovev_ideal_example_multi_n/gpec.toml @@ -1,34 +1,34 @@ # Regression fixture: Solovev multi-n ideal stability (n = 1 and 2; mirrors -# examples/Solovev_ideal_example_multi_n). Coarsened for speed (mpsi=16, mthvac=64, +# examples/Solovev_ideal_example_multi_n). Coarsened for speed (mpsi=16, mtheta_vacuum=64, # delta_m=0) and run quietly with HDF5 output off; consumed by test/runtests_*.jl. [Equilibrium] eq_type = "sol" # Type of the input 2D equilibrium file jac_type = "pest" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom) -grid_type = "ldp" # Radial grid packing type +grid_type = "core_edge_packed" # Radial grid packing type psilow = 1e-4 # Lower limit of normalized poloidal flux psihigh = 0.9995 # Upper limit of normalized poloidal flux mpsi = 16 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) mtheta = 256 # Number of poloidal grid points newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) -etol = 1e-7 # Error tolerance for equilibrium solver +flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface force_termination = false # Terminate after equilibrium setup (skip stability calculations) [Wall] shape = "conformal" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath); close conformal wall stabilizes the Solovev n=1 kink (see examples/Solovev_ideal_example/gpec.toml) -a = 0.2415 # Distance from plasma (conformal) or shape parameter -aw = 0.05 # Half-thickness parameter for Dee-shaped walls -bw = 1.5 # Elongation parameter for wall shapes -cw = 0 # Offset of wall center from major radius -dw = 0.5 # Triangularity parameter for wall shapes -tw = 0.05 # Sharpness of wall corners (try 0.05 as initial value) +standoff_fraction = 0.2415 # Wall standoff in plasma minor radii (conformal, dee) +half_thickness = 0.05 # Half-thickness parameter for Dee-shaped walls +elongation = 1.5 # Elongation parameter for wall shapes +center_offset = 0 # Offset of wall center from major radius +triangularity = 0.5 # Triangularity parameter for wall shapes +corner_sharpness = 0.05 # Sharpness of wall corners (try 0.05 as initial value) equal_arc_wall = true # Equal arc length distribution of nodes on wall [ForceFreeStates] local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile vac_flag = true # Compute plasma, vacuum, and total energies for free-boundary modes -psiedge = 0.99 # Edge dW(ψ) diagnostic scan band [psiedge, psilim]; set ≥ psilim to disable +dW_edge_scan_start = 0.99 # Edge dW(ψ) diagnostic scan band [dW_edge_scan_start, psilim]; set ≥ psilim to disable qlow = 1.02 # Integration initiated at q determined by min(q0, qlow) qhigh = 1e3 # Integration terminated at q limit determined by min(qa, qhigh) sing_start = 0 # Start integration at the sing_start'th rational from the axis (psilow) @@ -37,18 +37,18 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 2 # Largest toroidal mode number to include delta_mlow = 0 # Expands lower bound of Fourier harmonics delta_mhigh = 0 # Expands upper bound of Fourier harmonics -mthvac = 64 # Number of points used in splines over poloidal angle at the plasma-vacuum interface +mtheta_vacuum = 64 # Number of points used in splines over poloidal angle at the plasma-vacuum interface kinetic_source = "fixed" # Kinetic matrix source: "fixed" test matrices, or "calculated" from the kinetic NTV model kinetic_factor = 0.0 # Scaling of kinetic matrices (0 = ideal path; >0 enables kinetic mode) eulerlagrange_tolerance = 1e-7 # Relative tolerance for ODE integration of Euler-Lagrange equations singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e3 # Column-norm threshold that triggers solution renormalization +renorm_threshold = 1e3 # Column-norm threshold that triggers solution renormalization # Integrator selection and domain truncation (see ForceFreeStatesControl docstring for details) integrator = "forward" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) -set_psilim_via_dmlim = false # FALSE for multi-n — dmlim truncation is ambiguous when n varies (sing_lim! skips anyway) -dmlim = 0.2 # Truncate integration at (last_rational_q + dmlim)/n (used when set_psilim_via_dmlim = true) +truncate_at_rational_offset = false # FALSE for multi-n — rational_offset_fraction truncation is ambiguous when n varies (sing_lim! skips anyway) +rational_offset_fraction = 0.2 # Truncate integration at (last_rational_q + rational_offset_fraction)/n (used when truncate_at_rational_offset = true) write_outputs_to_HDF5 = false # Write outputs to the HDF5 file verbose = false # Enable verbose logging @@ -57,7 +57,7 @@ verbose = false # Enable verbose logging mr = 128 # Number of radial grid zones mz = 128 # Number of axial grid zones ma = 128 # Number of flux grid zones -e = 1.6 # Elongation +elongation = 1.6 # Elongation a = 0.33 # Minor radius r0 = 1.0 # Major radius q0 = 1.9 # Safety factor at the magnetic axis (O-point) diff --git a/test/test_data/regression_solovev_kinetic_calculated/gpec.toml b/test/test_data/regression_solovev_kinetic_calculated/gpec.toml index 1a6bb6f13..a05caa035 100644 --- a/test/test_data/regression_solovev_kinetic_calculated/gpec.toml +++ b/test/test_data/regression_solovev_kinetic_calculated/gpec.toml @@ -5,31 +5,31 @@ [Equilibrium] eq_type = "sol" # Type of the input 2D equilibrium file jac_type = "pest" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom) -grid_type = "ldp" # Radial grid packing type +grid_type = "core_edge_packed" # Radial grid packing type psilow = 1e-4 # Lower limit of normalized poloidal flux psihigh = 0.9995 # Upper limit of normalized poloidal flux mpsi = 16 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) mtheta = 256 # Number of poloidal grid points newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) -etol = 1e-7 # Error tolerance for equilibrium solver +flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface force_termination = false # Terminate after equilibrium setup (skip stability calculations) [Wall] # A close conformal wall stabilizes the Solovev n=1 kink for this fixture. shape = "conformal" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath) -a = 0.2415 # Distance from plasma (conformal) or shape parameter -aw = 0.05 # Half-thickness parameter for Dee-shaped walls -bw = 1.5 # Elongation parameter for wall shapes -cw = 0 # Offset of wall center from major radius -dw = 0.5 # Triangularity parameter for wall shapes -tw = 0.05 # Sharpness of wall corners (try 0.05 as initial value) +standoff_fraction = 0.2415 # Wall standoff in plasma minor radii (conformal, dee) +half_thickness = 0.05 # Half-thickness parameter for Dee-shaped walls +elongation = 1.5 # Elongation parameter for wall shapes +center_offset = 0 # Offset of wall center from major radius +triangularity = 0.5 # Triangularity parameter for wall shapes +corner_sharpness = 0.05 # Sharpness of wall corners (try 0.05 as initial value) equal_arc_wall = true # Equal arc length distribution of nodes on wall [ForceFreeStates] local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile vac_flag = true # Compute plasma, vacuum, and total energies for free-boundary modes -psiedge = 0.99 # Edge dW(ψ) diagnostic scan band [psiedge, psilim]; set ≥ psilim to disable +dW_edge_scan_start = 0.99 # Edge dW(ψ) diagnostic scan band [dW_edge_scan_start, psilim]; set ≥ psilim to disable qlow = 1.02 # Integration initiated at q determined by min(q0, qlow) qhigh = 1e3 # Integration terminated at q limit determined by min(qa, qhigh) sing_start = 0 # Start integration at the sing_start'th rational from the axis (psilow) @@ -38,14 +38,14 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 1 # Largest toroidal mode number to include delta_mlow = 0 # Expands lower bound of Fourier harmonics delta_mhigh = 0 # Expands upper bound of Fourier harmonics -mthvac = 64 # Number of points used in splines over poloidal angle at the plasma-vacuum interface +mtheta_vacuum = 64 # Number of points used in splines over poloidal angle at the plasma-vacuum interface kinetic_source = "calculated" # Kinetic matrix source: "fixed" test matrices, or "calculated" from the kinetic NTV model kinetic_factor = 1.0 # Scaling of kinetic matrices (0 = ideal path; >0 enables kinetic mode; 1.0 = full strength) integrator = "forward" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) eulerlagrange_tolerance = 1e-7 # Relative tolerance for ODE integration of Euler-Lagrange equations singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e3 # Column-norm threshold that triggers solution renormalization +renorm_threshold = 1e3 # Column-norm threshold that triggers solution renormalization write_outputs_to_HDF5 = true # Write outputs to the HDF5 file verbose = false # Enable verbose logging # Solovev analytic equilibrium parameters (eq_type = "sol"); see SolovevConfig in src/Equilibrium. @@ -53,7 +53,7 @@ verbose = false # Enable verbose logging mr = 128 # Number of radial grid zones mz = 128 # Number of axial grid zones ma = 128 # Number of flux grid zones -e = 1.6 # Elongation +elongation = 1.6 # Elongation a = 0.33 # Minor radius r0 = 1.0 # Major radius q0 = 1.9 # Safety factor at the magnetic axis (O-point) diff --git a/test/test_data/regression_solovev_kinetic_example/gpec.toml b/test/test_data/regression_solovev_kinetic_example/gpec.toml index 67692a315..92684ed25 100644 --- a/test/test_data/regression_solovev_kinetic_example/gpec.toml +++ b/test/test_data/regression_solovev_kinetic_example/gpec.toml @@ -5,30 +5,30 @@ [Equilibrium] eq_type = "sol" # Type of the input 2D equilibrium file jac_type = "pest" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom) -grid_type = "ldp" # Radial grid packing type +grid_type = "core_edge_packed" # Radial grid packing type psilow = 1e-4 # Lower limit of normalized poloidal flux psihigh = 0.9995 # Upper limit of normalized poloidal flux mpsi = 16 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) mtheta = 256 # Number of poloidal grid points newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) -etol = 1e-7 # Error tolerance for equilibrium solver +flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface force_termination = false # Terminate after equilibrium setup (skip stability calculations) [Wall] shape = "conformal" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath); close conformal wall stabilizes the Solovev n=1 kink (see examples/Solovev_ideal_example/gpec.toml) -a = 0.2415 # Distance from plasma (conformal) or shape parameter -aw = 0.05 # Half-thickness parameter for Dee-shaped walls -bw = 1.5 # Elongation parameter for wall shapes -cw = 0 # Offset of wall center from major radius -dw = 0.5 # Triangularity parameter for wall shapes -tw = 0.05 # Sharpness of wall corners (try 0.05 as initial value) +standoff_fraction = 0.2415 # Wall standoff in plasma minor radii (conformal, dee) +half_thickness = 0.05 # Half-thickness parameter for Dee-shaped walls +elongation = 1.5 # Elongation parameter for wall shapes +center_offset = 0 # Offset of wall center from major radius +triangularity = 0.5 # Triangularity parameter for wall shapes +corner_sharpness = 0.05 # Sharpness of wall corners (try 0.05 as initial value) equal_arc_wall = true # Equal arc length distribution of nodes on wall [ForceFreeStates] local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile vac_flag = true # Compute plasma, vacuum, and total energies for free-boundary modes -psiedge = 0.99 # Edge dW(ψ) diagnostic scan band [psiedge, psilim]; set ≥ psilim to disable +dW_edge_scan_start = 0.99 # Edge dW(ψ) diagnostic scan band [dW_edge_scan_start, psilim]; set ≥ psilim to disable qlow = 1.02 # Integration initiated at q determined by min(q0, qlow) qhigh = 1e3 # Integration terminated at q limit determined by min(qa, qhigh) sing_start = 0 # Start integration at the sing_start'th rational from the axis (psilow) @@ -37,18 +37,18 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 1 # Largest toroidal mode number to include delta_mlow = 0 # Expands lower bound of Fourier harmonics delta_mhigh = 0 # Expands upper bound of Fourier harmonics -mthvac = 64 # Number of points used in splines over poloidal angle at the plasma-vacuum interface +mtheta_vacuum = 64 # Number of points used in splines over poloidal angle at the plasma-vacuum interface kinetic_source = "fixed" # Kinetic matrix source: "fixed" test matrices, or "calculated" from the kinetic NTV model kinetic_factor = 1e-9 # Scaling of kinetic matrices (0 = ideal path; >0 enables kinetic mode; tiny value just exercises the path) eulerlagrange_tolerance = 1e-7 # Relative tolerance for ODE integration of Euler-Lagrange equations singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e3 # Column-norm threshold that triggers solution renormalization +renorm_threshold = 1e3 # Column-norm threshold that triggers solution renormalization # Integrator selection and domain truncation (see ForceFreeStatesControl docstring for details) integrator = "forward" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) -set_psilim_via_dmlim = false # FALSE for limited/analytical equilibria — rationals sparse, dmlim would chop too much edge -dmlim = 0.2 # Truncate integration at (last_rational_q + dmlim)/n (used when set_psilim_via_dmlim = true) +truncate_at_rational_offset = false # FALSE for limited/analytical equilibria — rationals sparse, rational_offset_fraction would chop too much edge +rational_offset_fraction = 0.2 # Truncate integration at (last_rational_q + rational_offset_fraction)/n (used when truncate_at_rational_offset = true) write_outputs_to_HDF5 = true # Write outputs to the HDF5 file verbose = false # Enable verbose logging @@ -57,7 +57,7 @@ verbose = false # Enable verbose logging mr = 128 # Number of radial grid zones mz = 128 # Number of axial grid zones ma = 128 # Number of flux grid zones -e = 1.6 # Elongation +elongation = 1.6 # Elongation a = 0.33 # Minor radius r0 = 1.0 # Major radius q0 = 1.9 # Safety factor at the magnetic axis (O-point) diff --git a/test/test_data/regression_solovev_kinetic_multi_n/gpec.toml b/test/test_data/regression_solovev_kinetic_multi_n/gpec.toml index b2bdfb2b1..e70b6cccc 100644 --- a/test/test_data/regression_solovev_kinetic_multi_n/gpec.toml +++ b/test/test_data/regression_solovev_kinetic_multi_n/gpec.toml @@ -5,30 +5,30 @@ [Equilibrium] eq_type = "sol" # Type of the input 2D equilibrium file jac_type = "pest" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom) -grid_type = "ldp" # Radial grid packing type +grid_type = "core_edge_packed" # Radial grid packing type psilow = 1e-4 # Lower limit of normalized poloidal flux psihigh = 0.9995 # Upper limit of normalized poloidal flux mpsi = 16 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) mtheta = 256 # Number of poloidal grid points newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) -etol = 1e-7 # Error tolerance for equilibrium solver +flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface force_termination = false # Terminate after equilibrium setup (skip stability calculations) [Wall] shape = "conformal" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath); close conformal wall stabilizes the Solovev n=1 kink (see examples/Solovev_ideal_example/gpec.toml) -a = 0.2415 # Distance from plasma (conformal) or shape parameter -aw = 0.05 # Half-thickness parameter for Dee-shaped walls -bw = 1.5 # Elongation parameter for wall shapes -cw = 0 # Offset of wall center from major radius -dw = 0.5 # Triangularity parameter for wall shapes -tw = 0.05 # Sharpness of wall corners (try 0.05 as initial value) +standoff_fraction = 0.2415 # Wall standoff in plasma minor radii (conformal, dee) +half_thickness = 0.05 # Half-thickness parameter for Dee-shaped walls +elongation = 1.5 # Elongation parameter for wall shapes +center_offset = 0 # Offset of wall center from major radius +triangularity = 0.5 # Triangularity parameter for wall shapes +corner_sharpness = 0.05 # Sharpness of wall corners (try 0.05 as initial value) equal_arc_wall = true # Equal arc length distribution of nodes on wall [ForceFreeStates] local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile vac_flag = true # Compute plasma, vacuum, and total energies for free-boundary modes -psiedge = 0.99 # Edge dW(ψ) diagnostic scan band [psiedge, psilim]; set ≥ psilim to disable +dW_edge_scan_start = 0.99 # Edge dW(ψ) diagnostic scan band [dW_edge_scan_start, psilim]; set ≥ psilim to disable qlow = 1.02 # Integration initiated at q determined by min(q0, qlow) qhigh = 1e3 # Integration terminated at q limit determined by min(qa, qhigh) sing_start = 0 # Start integration at the sing_start'th rational from the axis (psilow) @@ -37,18 +37,18 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 2 # Largest toroidal mode number to include delta_mlow = 0 # Expands lower bound of Fourier harmonics delta_mhigh = 0 # Expands upper bound of Fourier harmonics -mthvac = 64 # Number of points used in splines over poloidal angle at the plasma-vacuum interface +mtheta_vacuum = 64 # Number of points used in splines over poloidal angle at the plasma-vacuum interface kinetic_source = "fixed" # Kinetic matrix source: "fixed" test matrices, or "calculated" from the kinetic NTV model kinetic_factor = 1e-9 # Scaling of kinetic matrices (0 = ideal path; >0 enables kinetic mode; tiny value just exercises the path) eulerlagrange_tolerance = 1e-7 # Relative tolerance for ODE integration of Euler-Lagrange equations singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e3 # Column-norm threshold that triggers solution renormalization +renorm_threshold = 1e3 # Column-norm threshold that triggers solution renormalization # Integrator selection and domain truncation (see ForceFreeStatesControl docstring for details) integrator = "forward" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) -set_psilim_via_dmlim = false # FALSE for multi-n — dmlim truncation is ambiguous when n varies (sing_lim! skips anyway) -dmlim = 0.2 # Truncate integration at (last_rational_q + dmlim)/n (used when set_psilim_via_dmlim = true) +truncate_at_rational_offset = false # FALSE for multi-n — rational_offset_fraction truncation is ambiguous when n varies (sing_lim! skips anyway) +rational_offset_fraction = 0.2 # Truncate integration at (last_rational_q + rational_offset_fraction)/n (used when truncate_at_rational_offset = true) write_outputs_to_HDF5 = true # Write outputs to the HDF5 file verbose = false # Enable verbose logging @@ -57,7 +57,7 @@ verbose = false # Enable verbose logging mr = 128 # Number of radial grid zones mz = 128 # Number of axial grid zones ma = 128 # Number of flux grid zones -e = 1.6 # Elongation +elongation = 1.6 # Elongation a = 0.33 # Minor radius r0 = 1.0 # Major radius q0 = 1.9 # Safety factor at the magnetic axis (O-point) diff --git a/test/test_data/regression_solovev_kinetic_nuzero/gpec.toml b/test/test_data/regression_solovev_kinetic_nuzero/gpec.toml index 5f5c9104a..d8576efc6 100644 --- a/test/test_data/regression_solovev_kinetic_nuzero/gpec.toml +++ b/test/test_data/regression_solovev_kinetic_nuzero/gpec.toml @@ -6,30 +6,30 @@ [Equilibrium] eq_type = "sol" # Type of the input 2D equilibrium file jac_type = "pest" # Coordinate system (hamada, pest, boozer, equal_arc, park, custom) -grid_type = "ldp" # Radial grid packing type +grid_type = "core_edge_packed" # Radial grid packing type psilow = 1e-4 # Lower limit of normalized flux coordinate psihigh = 0.9995 # Upper limit of normalized flux coordinate mpsi = 16 # Number of radial grid points mtheta = 256 # Number of poloidal grid points newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) -etol = 1e-7 # Error tolerance for equilibrium solver +flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface force_termination = false # Terminate after equilibrium setup (skip stability calculations) [Wall] shape = "conformal" # Wall shape (nowall, conformal, elliptical, dee, mod_dee, filepath) -a = 0.2415 # Distance from plasma (conformal) or shape parameter -aw = 0.05 # Half-thickness parameter for Dee-shaped walls -bw = 1.5 # Elongation parameter for wall shapes -cw = 0 # Offset of wall center from major radius -dw = 0.5 # Triangularity parameter for wall shapes -tw = 0.05 # Sharpness of wall corners (try 0.05 as initial value) +standoff_fraction = 0.2415 # Wall standoff in plasma minor radii (conformal, dee) +half_thickness = 0.05 # Half-thickness parameter for Dee-shaped walls +elongation = 1.5 # Elongation parameter for wall shapes +center_offset = 0 # Offset of wall center from major radius +triangularity = 0.5 # Triangularity parameter for wall shapes +corner_sharpness = 0.05 # Sharpness of wall corners (try 0.05 as initial value) equal_arc_wall = true # Equal arc length distribution of nodes on wall [ForceFreeStates] local_stability_flag = true # Perform local stability analysis (Mercier and ballooning) across the ψ profile vac_flag = true # Compute plasma, vacuum, and total energies for free-boundary modes -psiedge = 0.99 # Edge dW(ψ) diagnostic scan band [psiedge, psilim]; set ≥ psilim to disable +dW_edge_scan_start = 0.99 # Edge dW(ψ) diagnostic scan band [dW_edge_scan_start, psilim]; set ≥ psilim to disable qlow = 1.02 # Integration initiated at q determined by min(q0, qlow)... qhigh = 1e3 # Integration terminated at q limit determined by min(qa, qhigh)... sing_start = 0 # Start integration at the sing_start'th rational from the axis (psilow) @@ -38,14 +38,14 @@ nn_low = 1 # Smallest toroidal mode number to include nn_high = 1 # Largest toroidal mode number to include delta_mlow = 0 # Expands lower bound of Fourier harmonics delta_mhigh = 0 # Expands upper bound of Fourier harmonics -mthvac = 64 # Number of points used in splines over poloidal angle at plasma-vacuum interface. +mtheta_vacuum = 64 # Number of points used in splines over poloidal angle at plasma-vacuum interface. kinetic_source = "calculated" # Kinetic matrix source — exercises KineticForces.compute_calculated_kinetic_matrices callback with real physics kinetic_factor = 1.0 # Full-strength kinetic matrices (the "calculated" path is the real physics; no perturbation scaling) integrator = "forward" # Integration formalism: "forward" (dense ξ, needed for [PerturbedEquilibrium]/kinetic), "riccati" (chunked propagator BVP, unlocks the singular-surface Δ′ matrix), or "galerkin" (RDCON outer-region singular Galerkin Δ′, with the optional RPEC inner-layer match) eulerlagrange_tolerance = 1e-7 # Relative tolerance for ODE integration of Euler-Lagrange equations singfac_min = 1e-4 # Fractional distance from rational q at which ideal jump enforced -ucrit = 1e3 # Maximum fraction of solutions allowed before re-normalized +renorm_threshold = 1e3 # Column-norm threshold that triggers solution renormalization write_outputs_to_HDF5 = true # Write outputs to the HDF5 file verbose = false # Enable verbose logging @@ -57,7 +57,7 @@ nutype = "zero" # Collisionless operator — isolates the real-x-s mr = 128 # Number of radial grid zones mz = 128 # Number of axial grid zones ma = 128 # Number of flux grid zones -e = 1.6 # Elongation +elongation = 1.6 # Elongation a = 0.33 # Minor radius r0 = 1.0 # Major radius q0 = 1.9 # Safety factor at the magnetic axis (O-point)