From a12d936b47ea28c30fb2235928bd3476fa4196fc Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sun, 1 Nov 2026 18:50:16 -0500 Subject: [PATCH 01/14] Equilibrium - FEATURE - Accept supplied Coulomb logarithm and collision rates in kinetic profiles Kinetic files may carry lnLambda, nu_i and nu_e (HDF5 datasets, or a 9-column ASCII table as Fortran PENTRC rates-v1). Each replaces the internal collision model, interpolated in log space; a supplied lnLambda also replaces the resistivity closure's, so KineticForces and the tearing layers see one value. A supplied nu_i is the first main ion's rate, and other species keep the model's (z_s/z_1)^2 sqrt(m_1/m_s) ratio to it. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VdRsGGg4YRG6E9fgLGbpdF --- docs/src/kinetic_forces.md | 16 +++++-- src/Equilibrium/KineticProfiles.jl | 57 +++++++++++++++--------- src/Equilibrium/PlasmaEquilibrium.jl | 17 ++++++- src/InnerLayer/LayerPlasma.jl | 3 +- src/InnerLayer/SLAYER/KineticView.jl | 9 +++- src/InnerLayer/SLAYER/LayerInputs.jl | 26 ++++++----- src/InnerLayer/SLAYER/LayerParameters.jl | 6 ++- src/Tearing/LayerOverlap.jl | 10 +++-- src/Tearing/TearingProblem.jl | 3 +- test/runtests_multiion.jl | 48 ++++++++++++++++++++ test/runtests_solve_api.jl | 5 +++ 11 files changed, 154 insertions(+), 46 deletions(-) diff --git a/docs/src/kinetic_forces.md b/docs/src/kinetic_forces.md index 091492b543..157198a09a 100644 --- a/docs/src/kinetic_forces.md +++ b/docs/src/kinetic_forces.md @@ -14,8 +14,9 @@ extension: `T_e`, `omega_E` (and `n_i`, defaulting to `n_e` if omitted); optional `omega_tor`, `chi_e` and `chi_i` (electron and ion perpendicular heat diffusivities ``\chi_{\perp,e}``, ``\chi_{\perp,i}``), `chi_phi` (toroidal momentum diffusivity - ``\chi_\phi``), and `zeff` - (effective charge ``Z_\mathrm{eff}``, dimensionless). Each dataset + ``\chi_\phi``), `zeff` + (effective charge ``Z_\mathrm{eff}``, dimensionless), and the supplied collision + rates `lnLambda` (dimensionless), `nu_i` and `nu_e` (1/s). Each dataset carries a `units` attribute; the root carries `schema_version` and `provenance`. Densities are m⁻³, temperatures eV, frequencies rad/s, diffusivities m²/s. Additional datasets named `n_*` (e.g. `n_D`, `n_T`) @@ -24,7 +25,16 @@ extension: `write_kinetic_h5` (which round-trips the per-species densities). - **ASCII (`.gpeckf`/`.kin`/`.dat`)** — legacy six-column whitespace table `psi_n n_i n_e T_i[eV] T_e[eV] omega_E`, retained for backward - compatibility. Header rows are skipped. + compatibility, or nine columns that append `lnLambda nu_i[1/s] nu_e[1/s]` + (the Fortran PENTRC `kinetic_format = "rates-v1"` table). Header rows are skipped. + +Supplied collision rates replace the internal collision model, which otherwise computes them from +``n`` and ``T`` (Logan & Park 2013 Eq. 6). Each is optional and must be positive; it is interpolated +in log space, so it stays positive between table points. A supplied `lnLambda` also replaces the +`lnLambda_form` Coulomb logarithm of the resistivity closure, so the tearing layers see the same +value. A supplied `nu_i` is the first main ion's rate; the other ion species, the impurity among +them, keep the internal model's ``(Z_s/Z_1)^2\sqrt{m_1/m_s}`` ratio to it. The profile scaling +factors of `[KineticForces]` do not rescale supplied rates. The NTV calculation consumes `n_i, n_e, T_i, T_e, omega_E`; `chi_e`/`chi_i`/`chi_phi`/`zeff`, when present, are carried for the resistive-layer (SLAYER) analysis and ignored diff --git a/src/Equilibrium/KineticProfiles.jl b/src/Equilibrium/KineticProfiles.jl index 98f6db0847..93e2dc8156 100644 --- a/src/Equilibrium/KineticProfiles.jl +++ b/src/Equilibrium/KineticProfiles.jl @@ -40,6 +40,10 @@ end # Names kinetic profiles in error messages by their provenance, when they carry one. _describe(data) = isempty(data.provenance) ? "kinetic profiles" : "kinetic profiles ($(data.provenance))" +# A supplied rate profile on the kinetic grid: pchip in log space, as Fortran PENTRC `rates-v1`, so it interpolates +# geometrically across decades. +_supplied_rate(psi_in, v, psi_reg) = v === nothing ? nothing : exp.(_pchip_resample(psi_in, log.(v), psi_reg)) + # Hard stop on an unphysical negative density or temperature (bad input or an extrapolation past the table). function _assert_nonneg_profile(source, label, arr, psi_reg) i = findfirst(<(0), arr) @@ -77,15 +81,17 @@ const _KINETIC_H5_EXTS = (".h5", ".hdf5", ".he5") # The optional profile datasets of the HDF5 kinetic schema and their units, one row per `KineticProfileData` # field of the same name; the reader and writer both iterate it, so a new profile is a row here plus its field. const _KINETIC_H5_PROFILES = ("n_i" => "m^-3", "n_e" => "m^-3", "T_i" => "eV", "T_e" => "eV", "omega_E" => "rad/s", - "omega_tor" => "rad/s", "chi_e" => "m^2/s", "chi_i" => "m^2/s", "chi_phi" => "m^2/s", "zeff" => "dimensionless") + "omega_tor" => "rad/s", "chi_e" => "m^2/s", "chi_i" => "m^2/s", "chi_phi" => "m^2/s", "zeff" => "dimensionless", + "lnLambda" => "dimensionless", "nu_i" => "1/s", "nu_e" => "1/s") """ read_kinetic_file(path; group="/") -> KineticProfileData Read a kinetic-profile file, dispatching on extension: HDF5 (`.h5`/`.hdf5`) via the GPEC kinetic schema, otherwise a 6-column ASCII table (`.gpeckf`/`.kin`/`.dat`: -`psi_n n_i n_e T_i[eV] T_e[eV] omega_E`). Returns the raw columns; downstream -consumers validate the fields they require. +`psi_n n_i n_e T_i[eV] T_e[eV] omega_E`), or a 9-column one that appends `lnLambda nu_i[1/s] nu_e[1/s]` +as Fortran PENTRC `kinetic_format = "rates-v1"`. Returns the raw columns; downstream consumers +validate the fields they require. """ function read_kinetic_file(path::AbstractString; group::AbstractString="/") isfile(path) || error("Kinetic profile file not found: $path") @@ -94,12 +100,13 @@ function read_kinetic_file(path::AbstractString; group::AbstractString="/") end """ -Read a legacy 6-column ASCII kinetic table into a `KineticProfileData`. +Read a 6-column ASCII kinetic table, or a 9-column one with supplied collision rates, into a `KineticProfileData`. """ function _read_kinetic_ascii(path::AbstractString) - psi, ni, ne, Ti, Te, omegaE = _read_kinetic_table(path) - return KineticProfileData(; psi=psi, n_i=ni, n_e=ne, T_i=Ti, T_e=Te, omega_E=omegaE, - provenance="ASCII 6-column: $(basename(path))") + cols = _read_kinetic_table(path) + rates = length(cols) == 9 ? (; lnLambda=cols[7], nu_i=cols[8], nu_e=cols[9]) : (;) + return KineticProfileData(; psi=cols[1], n_i=cols[2], n_e=cols[3], T_i=cols[4], T_e=cols[5], omega_E=cols[6], rates..., + provenance="ASCII $(length(cols))-column: $(basename(path))") end """ @@ -313,15 +320,17 @@ function resolve_ntv_species(data::KineticProfileData, ion_species::AbstractVect n_main = zeros(npts) n_imp = zeros(npts) nue = zeros(npts) + lnL_sup, nui_sup, nue_sup = (_supplied_rate(psi_in, getfield(data, f), psi_reg) for f in (:lnLambda, :nu_i, :nu_e)) for i in 1:npts z, zp, nm, nimp = multi_ion_composition(zs, [ns[si][i] for si in eachindex(ion_species)], ne[i]; zimp=zimp, mimp=mimp) zeff[i] = z zpitch[i] = zp n_main[i] = nm n_imp[i] = nimp - loglam[i] = _coulomb_log(ne[i], Te[i]) + loglam[i] = lnL_sup === nothing ? _coulomb_log(ne[i], Te[i]) : lnL_sup[i] nue[i] = (zp / _NU_PREFAC) * ne[i] * loglam[i] / (sqrt(_ME / _MP) * _collision_tkev(Te[i])^1.5) end + nue_sup === nothing || (nue .= nue_sup) # A negative impurity density means the main ions over-neutralize (Σ z_s n_s > n_e) — unphysical. _assert_nonneg_profile(source, "impurity density (n_e < Σ z_s n_s)", n_imp, psi_reg) # Approaching the zpitch pole at Zeff = zimp signals a dilute main-ion mix taken up by impurity. @@ -330,7 +339,12 @@ function resolve_ntv_species(data::KineticProfileData, ion_species::AbstractVect # Per-species Krook ν (Logan & Park 2013 Eq. 6) with a test-particle z_s²; `zpitch` is a main-ion closure, # applied approximately to the impurity/electron test species. - _nu(zsp, msp) = [(zpitch[i] / _NU_PREFAC) * zsp^2 * n_main[i] * loglam[i] / (sqrt(Float64(msp)) * _collision_tkev(Ti[i])^1.5) for i in 1:npts] + # A supplied ν_i is the first main ion's rate; the others keep the model's (z_s/z₁)²·√(m₁/m_s) ratio to it. + z1, m1 = ion_species[1].z, ion_species[1].m + _nu(zsp, msp) = + nui_sup === nothing ? + [(zpitch[i] / _NU_PREFAC) * zsp^2 * n_main[i] * loglam[i] / (sqrt(Float64(msp)) * _collision_tkev(Ti[i])^1.5) for i in 1:npts] : + nui_sup .* ((zsp / z1)^2 * sqrt(m1 / msp)) _view(dens, nu) = KineticProfileSplines(psi_reg, dens, ne, Ti, Te, omegaE, loglam, nu, nue, zeff) # Full NTV species set: main ions, the neutrality-closing impurity, and (optionally) electrons. @@ -467,6 +481,7 @@ function kinetic_profile_splines(data::KineticProfileData; nui = zeros(Float64, nkin + 1) nue = zeros(Float64, nkin + 1) zeff = zeros(Float64, nkin + 1) + lnL_sup, nui_sup, nue_sup = (_supplied_rate(psi_input, getfield(data, f), psi_reg) for f in (:lnLambda, :nu_i, :nu_e)) for i in 1:(nkin+1) n_i = ni[i] @@ -477,7 +492,7 @@ function kinetic_profile_splines(data::KineticProfileData; z = zimp - (n_i / n_e) * zi * (zimp - zi) zpitch = 1.0 + (1.0 + mimp) / (2.0 * mimp) * zimp * (z - 1.0) / (zimp - z) - ll = _coulomb_log(n_e, T_e) + ll = lnL_sup === nothing ? _coulomb_log(n_e, T_e) : lnL_sup[i] loglam[i] = ll # Krook ν (Logan & Park 2013 Eq. 6) with a test-particle z²; the single-species limit of resolve_ntv_species. @@ -485,13 +500,16 @@ function kinetic_profile_splines(data::KineticProfileData; nue[i] = (zpitch / _NU_PREFAC) * n_e * ll / (sqrt(_ME / _MP) * _collision_tkev(T_e)^1.5) zeff[i] = z end + # Supplied rates replace the model's, as Fortran PENTRC `rates-v1`; the profile scaling factors do not rescale them. + nui_sup === nothing || (nui .= nui_sup) + nue_sup === nothing || (nue .= nue_sup) return KineticProfileSplines(psi_reg, ni, ne, Ti, Te, omegaE, loglam, nui, nue, zeff) end """ -Parse a 6-column kinetic profile table, dropping `#` comments and non-numeric header rows. -Returns the six columns. +Parse a 6- or 9-column kinetic profile table, dropping `#` comments and non-numeric header rows. +Returns the columns. """ function _read_kinetic_table(kinetic_file::AbstractString) table = DelimitedFiles.readdlm(kinetic_file; comments=true) @@ -501,16 +519,11 @@ function _read_kinetic_table(kinetic_file::AbstractString) if isempty(numeric_rows) error("No numeric data rows found in kinetic file: $kinetic_file") end - table = reduce(vcat, (reshape(Float64.(r), 1, 6) for r in numeric_rows)) - - psi_input = collect(table[:, 1]) - n_i_input = collect(table[:, 2]) - n_e_input = collect(table[:, 3]) - T_i_input = collect(table[:, 4]) - T_e_input = collect(table[:, 5]) - omega_e_input = collect(table[:, 6]) - - return psi_input, n_i_input, n_e_input, T_i_input, T_e_input, omega_e_input + ncol = length(numeric_rows[1]) + (ncol in (6, 9) && all(r -> length(r) == ncol, numeric_rows)) || + error("kinetic file $kinetic_file: every data row needs 6 columns, or 9 with lnLambda nu_i nu_e") + table = reduce(vcat, (reshape(Float64.(r), 1, ncol) for r in numeric_rows)) + return [collect(table[:, j]) for j in 1:ncol] end """ diff --git a/src/Equilibrium/PlasmaEquilibrium.jl b/src/Equilibrium/PlasmaEquilibrium.jl index dd1737bf66..e802b0c351 100644 --- a/src/Equilibrium/PlasmaEquilibrium.jl +++ b/src/Equilibrium/PlasmaEquilibrium.jl @@ -146,6 +146,8 @@ layer-overlap scan) reads the composition and closure from here, so they all see | `chi_i` | ion perpendicular heat diffusivity χ⊥,i | | `chi_phi` | toroidal momentum diffusivity χ_φ | | `zeff` | effective charge Z_eff(ψ) supplied directly (optional); the resistive-layer analysis prefers it to its closure | +| `lnLambda` | Coulomb logarithm supplied directly (optional); replaces the internal one in the collision model and resistivity | +| `nu_i`/`nu_e` | main-ion / electron collision rates in 1/s supplied directly (optional); replace the internal collision model | | `species_densities` | named per-species densities (e.g. `"n_D"`, `"n_T"`) for explicit multi-ion input; `nothing` for ASCII / no extras | | `provenance` | short string recording the source file/format | | `ion_species` | main-ion species, a `Vector{IonSpecies}`; default one deuterium ion carrying all of `n_i` | @@ -174,6 +176,9 @@ struct KineticProfileData chi_i::Union{Nothing,Vector{Float64}} chi_phi::Union{Nothing,Vector{Float64}} zeff::Union{Nothing,Vector{Float64}} + lnLambda::Union{Nothing,Vector{Float64}} + nu_i::Union{Nothing,Vector{Float64}} + nu_e::Union{Nothing,Vector{Float64}} species_densities::Union{Nothing,Dict{String,Vector{Float64}}} provenance::String ion_species::Vector{IonSpecies} @@ -193,7 +198,7 @@ const _LNLAMBDA_FORMS = (:nrl, :sauter, :wesson) function KineticProfileData(; psi, n_i=nothing, n_e=nothing, T_i=nothing, T_e=nothing, omega_E=nothing, omega_tor=nothing, chi_e=nothing, chi_i=nothing, chi_phi=nothing, zeff=nothing, - species_densities=nothing, provenance="", + lnLambda=nothing, nu_i=nothing, nu_e=nothing, species_densities=nothing, provenance="", ion_species=[IonSpecies(; z=1, m=2, fraction=1.0)], impurity_charge=6, impurity_mass=12, zeff_default=1.0, chi_e_default=1.0, chi_i_default=nothing, chi_phi_default=1.0, tau_E=nothing, resistivity_model=:sauter, lnLambda_form=:nrl) @@ -209,8 +214,16 @@ function KineticProfileData(; psi, n_i=nothing, n_e=nothing, T_i=nothing, T_e=no model, form = Symbol(resistivity_model), Symbol(lnLambda_form) model in _RESISTIVITY_MODELS || throw(ArgumentError("KineticProfileData: resistivity_model = $model not in $_RESISTIVITY_MODELS")) form in _LNLAMBDA_FORMS || throw(ArgumentError("KineticProfileData: lnLambda_form = $form not in $_LNLAMBDA_FORMS")) + # An all-zero rate dataset counts as not supplied, like the other optional profiles. + _rate(x) = x === nothing || all(iszero, x) ? nothing : x + lnLambda, nu_i, nu_e = _rate(lnLambda), _rate(nu_i), _rate(nu_e) + for (name, v) in (("lnLambda", lnLambda), ("nu_i", nu_i), ("nu_e", nu_e)) + v === nothing || all(x -> isfinite(x) && x > 0, v) || + throw(ArgumentError("KineticProfileData: supplied $name must be positive and finite everywhere")) + end return KineticProfileData(Float64.(collect(psi)), _v(n_i), _v(n_e), _v(T_i), _v(T_e), - _v(omega_E), _v(omega_tor), _v(chi_e), _v(chi_i), _v(chi_phi), _v(zeff), species_densities, String(provenance), + _v(omega_E), _v(omega_tor), _v(chi_e), _v(chi_i), _v(chi_phi), _v(zeff), _v(lnLambda), _v(nu_i), _v(nu_e), + species_densities, String(provenance), species, Int(impurity_charge), Int(impurity_mass), Float64(zeff_default), _s(chi_e_default), _s(chi_i_default), _s(chi_phi_default), _s(tau_E), model, form) end diff --git a/src/InnerLayer/LayerPlasma.jl b/src/InnerLayer/LayerPlasma.jl index f8dd77105d..28d86db489 100644 --- a/src/InnerLayer/LayerPlasma.jl +++ b/src/InnerLayer/LayerPlasma.jl @@ -25,7 +25,8 @@ function layer_eta(equil, surfaces) data = _layer_kinetic_data(equil, "resistivity η") loaded = SLAYER._slayer_profiles(data, nothing) return SLAYER.layer_resistivity(equil, surfaces, loaded.profiles; zeff=SLAYER._layer_zeff(loaded, data), - resistivity_model=SLAYER._build_resistivity_model(data.resistivity_model), lnLambda_form=data.lnLambda_form) + resistivity_model=SLAYER._build_resistivity_model(data.resistivity_model), lnLambda_form=data.lnLambda_form, + lnLambda=loaded.lnLambda) end """ diff --git a/src/InnerLayer/SLAYER/KineticView.jl b/src/InnerLayer/SLAYER/KineticView.jl index b8b646baa7..6e3652c860 100644 --- a/src/InnerLayer/SLAYER/KineticView.jl +++ b/src/InnerLayer/SLAYER/KineticView.jl @@ -48,8 +48,15 @@ function _slayer_profiles(data::KineticProfileData, control) cubic_interp(psi_xs, z) end zeff_source = _usable(data.zeff) ? :file : zeff === nothing ? :scalar : :closure + # A supplied Coulomb logarithm replaces the closure's lnLambda_form one; interpolated in log space, so it stays positive. + lnLambda = if data.lnLambda === nothing + nothing + else + log_spl = pchip_interp(psi_xs, log.(data.lnLambda); extrap=ExtendExtrap()) + ψ -> exp(log_spl(ψ)) + end return (profiles=profiles, chi_perp_e=chi_perp_e, chi_perp_i=chi_perp_i, chi_tor=chi_tor, zeff=zeff, zeff_source=zeff_source, - has_omega_E=(data.omega_E !== nothing)) + has_omega_E=(data.omega_E !== nothing), lnLambda=lnLambda) end # Map the TOML resistivity_model symbol to a NeoResistivityModel instance. diff --git a/src/InnerLayer/SLAYER/LayerInputs.jl b/src/InnerLayer/SLAYER/LayerInputs.jl index 2cfef836b9..d17cb06a59 100644 --- a/src/InnerLayer/SLAYER/LayerInputs.jl +++ b/src/InnerLayer/SLAYER/LayerInputs.jl @@ -6,7 +6,7 @@ using ..Utilities: KineticProfiles using ...Utilities.NeoclassicalResistivity: NeoResistivityModel, SpitzerModel, coulomb_log_e, nu_star_e -using FastInterpolations: DerivOp, integrate, cubic_interp, cumulative_integrate, ExtendExtrap +using FastInterpolations: DerivOp, integrate, cubic_interp, pchip_interp, cumulative_integrate, ExtendExtrap """ surface_minor_radius(equil, psi; theta=0.0) -> Float64 @@ -256,6 +256,7 @@ Keywords documented as scalar-or-callable are evaluated per surface at ψ. - `resistivity_model` -- `SauterNeoModel()` (default), `RedlNeoModel()`, `SpitzerModel()` or `SpitzerHarmModel()`; the neoclassical `f_trap` and ν*_e come from `sing.restype` when populated, else the ε-only forms - `lnLambda_form` -- Coulomb-log form (default `:nrl`; `:wesson` with `SpitzerHarmModel()` reproduces the legacy SLAYER) + - `lnLambda` -- supplied Coulomb logarithm, a scalar or a callable of ψ; when given it replaces the `lnLambda_form` one """ function build_slayer_inputs(equil, sings, profiles::KineticProfiles; bt=nothing, @@ -277,7 +278,8 @@ function build_slayer_inputs(equil, sings, profiles::KineticProfiles; dc_type::Symbol=:none, theta::Real=0.0, resistivity_model::NeoResistivityModel=SauterNeoModel(), - lnLambda_form::Symbol=:nrl) + lnLambda_form::Symbol=:nrl, + lnLambda=nothing) R0_use = R0 === nothing ? equil.ro : Float64(R0) _eval(x, ψ) = x === nothing ? nothing : x isa Real ? Float64(x) : Float64(x(ψ)) @@ -328,8 +330,9 @@ function build_slayer_inputs(equil, sings, profiles::KineticProfiles; # Trapped-fraction inputs from ResistGeometry when populated; else slayer_parameters uses the ε-only forms. rg = sing.restype + lnL = _eval(lnLambda, psi) (; f_trap, nu_e_star, R_major_eff) = _resistivity_geometry(rg, prof.n_e, prof.T_e, q, _eval(zeff, psi); - resistivity_model=resistivity_model, lnLambda_form=lnLambda_form) + resistivity_model=resistivity_model, lnLambda_form=lnLambda_form, lnLamb=lnL) # Resistive interchange index D_R = E + F + H² (Glasser-Greene-Johnson 1975), the (−D_R) of Connor et al. 2015 Eq. 59; # not the Mercier index D_I = E + F + H − 1/4. @@ -423,6 +426,7 @@ function build_slayer_inputs(equil, sings, profiles::KineticProfiles; nu_e_star=nu_e_star, R_major_eff=R_major_eff, lnLambda_form=lnLambda_form, + lnLamb=lnL, k_ref=k_ref_k, alpha_mercier=alpha_k ) @@ -433,12 +437,12 @@ end # Trapped fraction, electron collisionality and effective major radius of the neoclassical resistivity # closure at one surface, from its `ResistGeometry` when populated; `nothing` lets `surface_resistivity` # fall back to the ε = r_s/R form. -function _resistivity_geometry(rg, n_e, T_e, q, zeff; resistivity_model::NeoResistivityModel, lnLambda_form::Symbol) +function _resistivity_geometry(rg, n_e, T_e, q, zeff; resistivity_model::NeoResistivityModel, lnLambda_form::Symbol, lnLamb=nothing) rg === nothing && return (f_trap=nothing, nu_e_star=nothing, R_major_eff=nothing) nu_e_star = if resistivity_model isa Union{SpitzerModel,SpitzerHarmModel} nothing else - lnL = coulomb_log_e(n_e, T_e; form=lnLambda_form) + lnL = lnLamb === nothing ? coulomb_log_e(n_e, T_e; form=lnLambda_form) : lnLamb nu_star_e(n_e, T_e, rg.R_major, rg.eps_local, q, zeff; lnLamb=lnL) end return (f_trap=rg.f_trap, nu_e_star=nu_e_star, R_major_eff=rg.R_major) @@ -446,24 +450,26 @@ end """ layer_resistivity(equil, sings, profiles::KineticProfiles; zeff=1.0, R0=nothing, rs_method=:midplane, - theta=0.0, q_beyond=nothing, resistivity_model=SauterNeoModel(), lnLambda_form=:nrl) -> Vector{Float64} + theta=0.0, q_beyond=nothing, resistivity_model=SauterNeoModel(), lnLambda_form=:nrl, + lnLambda=nothing) -> Vector{Float64} Parallel resistivity η in Ω·m at each surface of `sings`, with the same closure and per-surface inputs `build_slayer_inputs` gives the SLAYER layer, so every resistive inner layer of a run sees one η. -`zeff` is a scalar or a callable of ψ. +`zeff` and a supplied Coulomb logarithm `lnLambda` are scalars or callables of ψ. """ function layer_resistivity(equil, sings, profiles::KineticProfiles; zeff=1.0, R0=nothing, rs_method::Symbol=:midplane, theta::Real=0.0, q_beyond=nothing, - resistivity_model::NeoResistivityModel=SauterNeoModel(), lnLambda_form::Symbol=:nrl) + resistivity_model::NeoResistivityModel=SauterNeoModel(), lnLambda_form::Symbol=:nrl, lnLambda=nothing) R0_use = R0 === nothing ? equil.ro : Float64(R0) rs_at, _ = radial_label(equil; rs_method=rs_method, theta=theta, q_beyond=q_beyond) return map(sings) do sing psi = sing.psifac prof = profiles(psi) z = zeff isa Real ? Float64(zeff) : Float64(zeff(psi)) + lnL = lnLambda === nothing ? nothing : lnLambda isa Real ? Float64(lnLambda) : Float64(lnLambda(psi)) geometry = _resistivity_geometry(sing.restype, prof.n_e, prof.T_e, sing.q, z; - resistivity_model=resistivity_model, lnLambda_form=lnLambda_form) + resistivity_model=resistivity_model, lnLambda_form=lnLambda_form, lnLamb=lnL) surface_resistivity(; n_e=prof.n_e, t_e=prof.T_e, zeff=z, qval=sing.q, rs=rs_at(psi), R0=R0_use, - resistivity_model=resistivity_model, lnLambda_form=lnLambda_form, geometry...) + resistivity_model=resistivity_model, lnLambda_form=lnLambda_form, lnLamb=lnL, geometry...) end end diff --git a/src/InnerLayer/SLAYER/LayerParameters.jl b/src/InnerLayer/SLAYER/LayerParameters.jl index a48b88b192..7f84e1bf5b 100644 --- a/src/InnerLayer/SLAYER/LayerParameters.jl +++ b/src/InnerLayer/SLAYER/LayerParameters.jl @@ -253,6 +253,7 @@ model does not use may be left `nothing`; one it uses must be given, positive an and Sauter 1999 Eq. 18b with ε = `rs / R_major_eff` - `R_major_eff` -- ⟨R⟩ at the surface for the ν*_e formula (default `R0`) - `lnLambda_form` -- Coulomb-log form: `:nrl` (default), `:sauter` or `:wesson` + - `lnLamb` -- supplied Coulomb logarithm; when given it replaces the `lnLambda_form` one - `k_ref` -- K = r_s·(dψ_N/dr), converting the ψ_N-referenced outer Δ' to the r_s reference (default 1, no conversion) - `alpha_mercier` -- Mercier exponent α = √(−D_I) of the same conversion (default 0.5, the slab value) """ @@ -275,11 +276,12 @@ function slayer_parameters(; nu_e_star::Union{Real,Nothing}=nothing, R_major_eff::Union{Real,Nothing}=nothing, lnLambda_form::Symbol=:nrl, + lnLamb::Union{Real,Nothing}=nothing, k_ref::Real=1.0, alpha_mercier::Real=0.5) - # Coulomb logarithm shared by the resistivity closure and τ_ee. - lnLamb = coulomb_log_e(n_e, t_e; form=lnLambda_form) + # Coulomb logarithm shared by the resistivity closure and τ_ee: the supplied one, else `lnLambda_form`'s. + lnLamb = lnLamb === nothing ? coulomb_log_e(n_e, t_e; form=lnLambda_form) : Float64(lnLamb) eta = surface_resistivity(; n_e=n_e, t_e=t_e, zeff=zeff, qval=qval, rs=rs, R0=R0, resistivity_model=resistivity_model, lnLambda_form=lnLambda_form, f_trap=f_trap, nu_e_star=nu_e_star, diff --git a/src/Tearing/LayerOverlap.jl b/src/Tearing/LayerOverlap.jl index 3375731a4a..e77a80dbf3 100644 --- a/src/Tearing/LayerOverlap.jl +++ b/src/Tearing/LayerOverlap.jl @@ -124,7 +124,7 @@ comparison only. - `theta` -- poloidal angle for the minor-radius chord, used only by `rs_method=:midplane` (default `0.0`) - `rs_method` -- radial label of the widths and their ψ Jacobian (default `:flux`, the label Fitzpatrick Eq. (100) is anchored to; see `radial_label`) - - `bt`, `mu_i`, `mu_rho`, `zeff`, `chi_perp_e`, `chi_perp_i`, `chi_tor`, `tau_E`, `P_perp_model`, `resistivity_model`, `lnLambda_form` + - `bt`, `mu_i`, `mu_rho`, `zeff`, `chi_perp_e`, `chi_perp_i`, `chi_tor`, `tau_E`, `P_perp_model`, `resistivity_model`, `lnLambda_form`, `lnLambda` -- passed through to `build_slayer_inputs`. The defaults are placeholders: pass the run's own values Surface `k` overlaps its inner neighbour when `ψ_k − w_k/2 < ψ_{k-1} + w_{k-1}/2`. Both surfaces are @@ -149,7 +149,8 @@ function resistive_layer_overlap(equil, profiles::KineticProfiles; tau_E::Union{Real,Nothing}=nothing, P_perp_model::Symbol=:D_perp, resistivity_model::NeoResistivityModel=SauterNeoModel(), - lnLambda_form::Symbol=:nrl) + lnLambda_form::Symbol=:nrl, + lnLambda=nothing) n_tor > 0 || throw(ArgumentError("resistive_layer_overlap: n_tor must be positive, got $n_tor")) psihigh_safe = Float64(psihigh_safe) @@ -279,7 +280,7 @@ function resistive_layer_overlap(equil, profiles::KineticProfiles; bt=bt, mu_i=mu_i, mu_rho=mu_rho, zeff=zeff, chi_perp_e=chi_perp_e, chi_perp_i=chi_perp_i, chi_tor=chi_tor, tau_E=tau_E, P_perp_model=P_perp_model, dr_val=0.0, dc_type=:none, theta=theta, - resistivity_model=resistivity_model, lnLambda_form=lnLambda_form) + resistivity_model=resistivity_model, lnLambda_form=lnLambda_form, lnLambda=lnLambda) catch err # Typically a DomainError: profiles that stop short of the surface, or negative shear under a sqrt. err isa Union{ArgumentError,DomainError} || rethrow() @@ -410,7 +411,8 @@ function layer_overlap_scan(equil, model::SLAYERModel, ns) resistive_layer_overlap(equil, loaded.profiles; n_tor=n, rs_method=:flux, chi..., tau_E=data.tau_E, P_perp_model=model.P_perp_model, mu_i=_layer_ion_mass(data), mu_rho=_layer_mass_per_electron(data, zeff), zeff=zeff, theta=model.theta_sample, - resistivity_model=_build_resistivity_model(data.resistivity_model), lnLambda_form=data.lnLambda_form) + resistivity_model=_build_resistivity_model(data.resistivity_model), lnLambda_form=data.lnLambda_form, + lnLambda=loaded.lnLambda) for n in ns ] for (n, scan) in zip(ns, scans) diff --git a/src/Tearing/TearingProblem.jl b/src/Tearing/TearingProblem.jl index 1f54504473..323dcaada2 100644 --- a/src/Tearing/TearingProblem.jl +++ b/src/Tearing/TearingProblem.jl @@ -60,7 +60,8 @@ function _tearing_params(model::SLAYERModel, equil, surfaces, loaded) dc_type=model.D_c_type, theta=model.theta_sample, resistivity_model=_build_resistivity_model(data.resistivity_model), - lnLambda_form=data.lnLambda_form) + lnLambda_form=data.lnLambda_form, + lnLambda=loaded.lnLambda) end """ diff --git a/test/runtests_multiion.jl b/test/runtests_multiion.jl index 96172a3247..dc520a55e4 100644 --- a/test/runtests_multiion.jl +++ b/test/runtests_multiion.jl @@ -198,6 +198,54 @@ using HDF5 @test_throws r"densities must be positive" EQ.kinetic_profile_splines(EQ.KineticProfileData(data; n_e=[fill(1.1e19, length(psi) - 1); 0.0])) end + @testset "supplied collision rates replace the internal model" begin + psi = collect(0.0:0.05:1.0) + prof(a) = @. a * (1 - 0.6psi) + data = EQ.KineticProfileData(; psi=psi, n_i=prof(1.0e20), n_e=prof(1.1e20), T_i=prof(2000.0), T_e=prof(2500.0), + omega_E=prof(1.0e4)) + model = EQ.kinetic_profile_splines(data) + xs = model.xs + # The model's own rates, supplied at the table points, come back to interpolation accuracy. + own = EQ.KineticProfileData(data; lnLambda=model.loglam_spline.(psi), nu_i=model.nui_spline.(psi), nu_e=model.nue_spline.(psi)) + sup = EQ.kinetic_profile_splines(own) + for f in (:loglam_spline, :nui_spline, :nue_spline) + @test getfield(sup, f).(xs) ≈ getfield(model, f).(xs) rtol = 1e-3 + end + # Interpolation is in log space, so scaling the supplied rates scales the profiles exactly. + double = EQ.kinetic_profile_splines(EQ.KineticProfileData(own; nu_i=2 .* own.nu_i, nu_e=2 .* own.nu_e)) + @test double.nui_spline.(xs) ≈ 2 .* sup.nui_spline.(xs) rtol = 1e-12 + @test double.nue_spline.(xs) ≈ 2 .* sup.nue_spline.(xs) rtol = 1e-12 + # A supplied lnΛ alone feeds the model's ν, which is linear in it. + lnl = EQ.kinetic_profile_splines(EQ.KineticProfileData(data; lnLambda=fill(15.0, length(psi)))) + @test all(≈(15.0), lnl.loglam_spline.(xs)) + @test lnl.nui_spline.(xs) ≈ model.nui_spline.(xs) .* 15.0 ./ model.loglam_spline.(xs) rtol = 1e-12 + # Multi-ion: the first main ion takes the supplied ν_i; the others keep the model's (z_s/z₁)²·√(m₁/m_s) ratio. + sp = EQ.resolve_ntv_species(own, [KF.IonSpecies(; z=1, m=2, fraction=0.5), KF.IonSpecies(; z=1, m=3, fraction=0.5)]; zimp=6, mimp=12) + nu(z, m) = only(s for s in sp if s.z == z && s.m == m).profiles.nui_spline.(xs) + @test nu(1, 2) ≈ sup.nui_spline.(xs) rtol = 1e-12 + @test nu(1, 3) ≈ sqrt(2 / 3) .* nu(1, 2) rtol = 1e-12 + @test nu(6, 12) ≈ 36 * sqrt(2 / 12) .* nu(1, 2) rtol = 1e-12 + # Rates must be positive; an all-zero dataset counts as absent. + @test_throws ArgumentError EQ.KineticProfileData(data; nu_i=-own.nu_i) + @test EQ.KineticProfileData(data; nu_e=zeros(length(psi))).nu_e === nothing + # Both file formats carry the rates: HDF5 round-trips them and a 9-column ASCII table reads them. + h5 = tempname() * ".h5" + EQ.write_kinetic_h5(h5, own) + back = EQ.read_kinetic_file(h5) + @test back.lnLambda ≈ own.lnLambda && back.nu_i ≈ own.nu_i && back.nu_e ≈ own.nu_e + rm(h5) + ascii = tempname() * ".kin" + open(ascii, "w") do io + println(io, "# psi n_i n_e T_i T_e omega_E lnLambda nu_i nu_e") + for k in eachindex(psi) + println(io, join((psi[k], own.n_i[k], own.n_e[k], own.T_i[k], own.T_e[k], own.omega_E[k], own.lnLambda[k], own.nu_i[k], own.nu_e[k]), " ")) + end + end + table = EQ.read_kinetic_file(ascii) + @test table.nu_i ≈ own.nu_i && table.lnLambda ≈ own.lnLambda + rm(ascii) + end + @testset "combine_species_states" begin # Combined profile = Σ species dT/dψ interpolated onto the union grid; total = Σ total. mk(psi, dt) = ( diff --git a/test/runtests_solve_api.jl b/test/runtests_solve_api.jl index 7b56441b13..48933a75ee 100644 --- a/test/runtests_solve_api.jl +++ b/test/runtests_solve_api.jl @@ -365,6 +365,11 @@ using HDF5 @test prob.eta == GPEC.InnerLayer.layer_eta(kgal.equil, kgal.surfaces) && all(>(0), prob.eta) @test prob.rho ≈ fill(2 * GPEC.Utilities.M_P * 5e19, n) @test prob.rotation ≈ fill(1e3, n) + # A supplied Coulomb logarithm replaces the closure's in η: the Spitzer η is linear in lnΛ. + keq = deepcopy(kgal.equil) + spitzer_eta(lnL) = (attach_kinetic_profiles!(keq, KineticProfileData(; psi, n_e=flat(5e19), n_i=flat(5e19), T_e=flat(1e3), + T_i=flat(1e3), omega_E=flat(2π * 1e3), resistivity_model=:spitzer, lnLambda=flat(lnL))); GPEC.InnerLayer.layer_eta(keq, kgal.surfaces)) + @test spitzer_eta(30.0) ≈ 2 .* spitzer_eta(15.0) rtol = 1e-12 # Explicit values override each default on its own. given = MatchProblem(kgal; eta=1e-7, rotation=fill(3.0, n)) @test given.eta == fill(1e-7, n) && given.rho == prob.rho && given.rotation == fill(3.0, n) From 3883dba769575fe3f2480059f43ccb10b26bc261 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sun, 1 Nov 2026 18:50:39 -0500 Subject: [PATCH 02/14] Docs - DOCS - State the laboratory direction of positive omega_E from the toroidal map and B_t Mirrors the corrected Fortran GPEC sign-convention statement: positive omega_E is co-current exactly when B_t is clockwise, not according to helicity alone. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VdRsGGg4YRG6E9fgLGbpdF --- docs/src/conventions.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/src/conventions.md b/docs/src/conventions.md index 154afed892..f599fef855 100644 --- a/docs/src/conventions.md +++ b/docs/src/conventions.md @@ -343,9 +343,13 @@ The `KineticForces` module (the Julia port of PENTRC) conventions below are used - ``\omega_E`` is the input E × B toroidal-rotation profile, evaluated as `welec = omegaE_spline(psi)` (`Torque.jl`). Positive ``\omega_E`` means rotation in the direction of the toroidal coordinate ``\zeta``. -- The mapping to current direction follows from the ``\zeta``/``\phi`` convention fixed by helicity - ([Helicity and Handedness](@ref)); it is not re-derived inside the kinetic module, which only - reads the supplied profile. +- In the laboratory frame, where ``\phi`` increases counter-clockwise viewed from above, the toroidal + coordinate maps as ``\phi = -h\,(2\pi\zeta + \nu)`` with ``h`` the helicity + ([Helicity and Handedness](@ref)), so positive ``\omega_E`` has counter-clockwise sign ``-h``. + Relative to the plasma current its sign is ``-h\,s_I = -s_B``, where ``s_I`` and ``s_B`` are the + signs of ``I_p`` and ``B_t`` (`ip_sign`, `bt_sign`): positive ``\omega_E`` is co-current exactly when + ``B_t`` is clockwise (``s_B = -1``), whatever the helicity. For ``s_I = s_B = +1`` it is clockwise and + counter-current. ### Diamagnetic Frequencies From a84e9731d30a42999339bca9e0bc41d49ffb0f5b Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sun, 1 Nov 2026 19:24:10 -0500 Subject: [PATCH 03/14] Equilibrium - BUGFIX - Reject a ragged kinetic table and document how supplied rates meet the closure and scaling factors Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VdRsGGg4YRG6E9fgLGbpdF --- docs/src/kinetic_forces.md | 18 +++++++++++------- src/Equilibrium/KineticProfiles.jl | 11 ++++++++--- src/InnerLayer/SLAYER/KineticView.jl | 2 +- test/runtests_multiion.jl | 11 +++++++---- test/runtests_solve_api.jl | 4 ++++ 5 files changed, 31 insertions(+), 15 deletions(-) diff --git a/docs/src/kinetic_forces.md b/docs/src/kinetic_forces.md index 157198a09a..b6272e4710 100644 --- a/docs/src/kinetic_forces.md +++ b/docs/src/kinetic_forces.md @@ -28,13 +28,17 @@ extension: compatibility, or nine columns that append `lnLambda nu_i[1/s] nu_e[1/s]` (the Fortran PENTRC `kinetic_format = "rates-v1"` table). Header rows are skipped. -Supplied collision rates replace the internal collision model, which otherwise computes them from -``n`` and ``T`` (Logan & Park 2013 Eq. 6). Each is optional and must be positive; it is interpolated -in log space, so it stays positive between table points. A supplied `lnLambda` also replaces the -`lnLambda_form` Coulomb logarithm of the resistivity closure, so the tearing layers see the same -value. A supplied `nu_i` is the first main ion's rate; the other ion species, the impurity among -them, keep the internal model's ``(Z_s/Z_1)^2\sqrt{m_1/m_s}`` ratio to it. The profile scaling -factors of `[KineticForces]` do not rescale supplied rates. +Supplied collision rates replace the internal Krook collision model of the NTV calculation, which +otherwise computes them from ``n`` and ``T`` (Logan & Park 2013 Eq. 6). Each is optional and must be +positive. Like Fortran PENTRC, they are interpolated in log space, geometrically between table +points; outside the table's ψ range the end interval's curve is extended in log space, so supply +the rates over the whole ψ range. A supplied `lnLambda` also replaces the `lnLambda_form` Coulomb +logarithm of the resistivity closure, so the tearing layers see the same value; the closure's +electron collisionality ``\nu_{*e}`` is still built from ``n_e`` and ``T_e`` with that ``\ln\Lambda``, +not from a supplied `nu_e`. A supplied `nu_i` is the first main ion's rate; the other ion species, +the impurity among them, keep the internal model's ``(Z_s/Z_1)^2\sqrt{m_1/m_s}`` ratio to it. The +density and temperature scaling factors of `[KineticForces]` do not rescale supplied rates, while +`collisionality_factor` still multiplies them. The NTV calculation consumes `n_i, n_e, T_i, T_e, omega_E`; `chi_e`/`chi_i`/`chi_phi`/`zeff`, when present, are carried for the resistive-layer (SLAYER) analysis and ignored diff --git a/src/Equilibrium/KineticProfiles.jl b/src/Equilibrium/KineticProfiles.jl index 93e2dc8156..739dc4ac3a 100644 --- a/src/Equilibrium/KineticProfiles.jl +++ b/src/Equilibrium/KineticProfiles.jl @@ -271,7 +271,7 @@ function resolve_ntv_species(data::KineticProfileData, ion_species::AbstractVect _assert_positive_density(source, "n_i (total)", ni_total, psi_reg) _assert_nonneg_profile(source, "T_i", Ti, psi_reg) _assert_nonneg_profile(source, "T_e", Te, psi_reg) - _warn_collision_floors(source, ne, Te, Ti) + all(f -> getfield(data, f) !== nothing, (:lnLambda, :nu_i, :nu_e)) || _warn_collision_floors(source, ne, Te, Ti) # Fractions are shares of the total n_i: never above 1, and exactly 1 when every species is fraction-specified. fracs = [s.fraction for s in ion_species if !isnan(s.fraction)] @@ -434,7 +434,7 @@ function kinetic_profile_splines(data::KineticProfileData; _assert_positive_density(_describe(data), "n_i", ni, psi_reg) _assert_nonneg_profile(_describe(data), "T_i", Ti, psi_reg) _assert_nonneg_profile(_describe(data), "T_e", Te, psi_reg) - _warn_collision_floors(_describe(data), ne, Te, Ti) + all(f -> getfield(data, f) !== nothing, (:lnLambda, :nu_i, :nu_e)) || _warn_collision_floors(_describe(data), ne, Te, Ti) needs_rotation_reform = density_factor != 1.0 || temperature_factor != 1.0 || toroidal_rotation_factor != 1.0 any_scaling = needs_rotation_reform || ExB_rotation_factor != 1.0 @@ -514,13 +514,18 @@ Returns the columns. function _read_kinetic_table(kinetic_file::AbstractString) table = DelimitedFiles.readdlm(kinetic_file; comments=true) + # readdlm pads a short row with empty strings, so a data row (numeric first entry) that is not all numeric is ragged. + ragged = findfirst(row -> row[1] isa Number && !all(x -> isa(x, Number), row), collect(eachrow(table))) + ragged === nothing || + error("kinetic file $kinetic_file: data row $ragged has fewer columns than the others; every data row needs 6 columns, or 9 with lnLambda nu_i nu_e") + # Keep numeric rows and stack them row-wise (reshape(:, 6) is column-major and would scramble columns). numeric_rows = [collect(row) for row in eachrow(table) if all(x -> isa(x, Number), row)] if isempty(numeric_rows) error("No numeric data rows found in kinetic file: $kinetic_file") end ncol = length(numeric_rows[1]) - (ncol in (6, 9) && all(r -> length(r) == ncol, numeric_rows)) || + ncol in (6, 9) || error("kinetic file $kinetic_file: every data row needs 6 columns, or 9 with lnLambda nu_i nu_e") table = reduce(vcat, (reshape(Float64.(r), 1, ncol) for r in numeric_rows)) return [collect(table[:, j]) for j in 1:ncol] diff --git a/src/InnerLayer/SLAYER/KineticView.jl b/src/InnerLayer/SLAYER/KineticView.jl index 6e3652c860..7ff3898bc9 100644 --- a/src/InnerLayer/SLAYER/KineticView.jl +++ b/src/InnerLayer/SLAYER/KineticView.jl @@ -48,7 +48,7 @@ function _slayer_profiles(data::KineticProfileData, control) cubic_interp(psi_xs, z) end zeff_source = _usable(data.zeff) ? :file : zeff === nothing ? :scalar : :closure - # A supplied Coulomb logarithm replaces the closure's lnLambda_form one; interpolated in log space, so it stays positive. + # A supplied Coulomb logarithm replaces the closure's lnLambda_form one; interpolated in log space as the collision model's. lnLambda = if data.lnLambda === nothing nothing else diff --git a/test/runtests_multiion.jl b/test/runtests_multiion.jl index dc520a55e4..ce7c273a41 100644 --- a/test/runtests_multiion.jl +++ b/test/runtests_multiion.jl @@ -211,10 +211,10 @@ using HDF5 for f in (:loglam_spline, :nui_spline, :nue_spline) @test getfield(sup, f).(xs) ≈ getfield(model, f).(xs) rtol = 1e-3 end - # Interpolation is in log space, so scaling the supplied rates scales the profiles exactly. - double = EQ.kinetic_profile_splines(EQ.KineticProfileData(own; nu_i=2 .* own.nu_i, nu_e=2 .* own.nu_e)) - @test double.nui_spline.(xs) ≈ 2 .* sup.nui_spline.(xs) rtol = 1e-12 - @test double.nue_spline.(xs) ≈ 2 .* sup.nue_spline.(xs) rtol = 1e-12 + # Interpolation is in log space: an exponential in ψ is reproduced exactly, which a linear-space fit would not do. + expo = EQ.kinetic_profile_splines(EQ.KineticProfileData(own; nu_i=@.(1e3 * exp(4psi)), nu_e=@.(1e5 * exp(-3psi)))) + @test expo.nui_spline.(xs) ≈ @.(1e3 * exp(4xs)) rtol = 1e-12 + @test expo.nue_spline.(xs) ≈ @.(1e5 * exp(-3xs)) rtol = 1e-12 # A supplied lnΛ alone feeds the model's ν, which is linear in it. lnl = EQ.kinetic_profile_splines(EQ.KineticProfileData(data; lnLambda=fill(15.0, length(psi)))) @test all(≈(15.0), lnl.loglam_spline.(xs)) @@ -243,6 +243,9 @@ using HDF5 end table = EQ.read_kinetic_file(ascii) @test table.nu_i ≈ own.nu_i && table.lnLambda ≈ own.lnLambda + # A data row with fewer columns than the others is an error, not a silently dropped row. + open(io -> println(io, join((1.0, own.n_i[end], own.n_e[end], own.T_i[end], own.T_e[end], own.omega_E[end]), " ")), ascii, "a") + @test_throws r"fewer columns" EQ.read_kinetic_file(ascii) rm(ascii) end diff --git a/test/runtests_solve_api.jl b/test/runtests_solve_api.jl index 48933a75ee..201542f767 100644 --- a/test/runtests_solve_api.jl +++ b/test/runtests_solve_api.jl @@ -370,6 +370,10 @@ using HDF5 spitzer_eta(lnL) = (attach_kinetic_profiles!(keq, KineticProfileData(; psi, n_e=flat(5e19), n_i=flat(5e19), T_e=flat(1e3), T_i=flat(1e3), omega_E=flat(2π * 1e3), resistivity_model=:spitzer, lnLambda=flat(lnL))); GPEC.InnerLayer.layer_eta(keq, kgal.surfaces)) @test spitzer_eta(30.0) ≈ 2 .* spitzer_eta(15.0) rtol = 1e-12 + # The neoclassical closure takes it too, through the Spitzer factor and ν*_e: η rises with lnΛ. + sauter_eta(lnL) = (attach_kinetic_profiles!(keq, KineticProfileData(; psi, n_e=flat(5e19), n_i=flat(5e19), T_e=flat(1e3), + T_i=flat(1e3), omega_E=flat(2π * 1e3), lnLambda=flat(lnL))); GPEC.InnerLayer.layer_eta(keq, kgal.surfaces)) + @test all(sauter_eta(30.0) .> sauter_eta(15.0)) # Explicit values override each default on its own. given = MatchProblem(kgal; eta=1e-7, rotation=fill(3.0, n)) @test given.eta == fill(1e-7, n) && given.rho == prob.rho && given.rotation == fill(3.0, n) From a16b428483aff2ec072f4df86e90f118cb168911 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sun, 1 Nov 2026 19:52:47 -0500 Subject: [PATCH 04/14] Repo - MINOR - Format the branch with the pinned pre-commit hooks Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VdRsGGg4YRG6E9fgLGbpdF --- Project.toml | 2 - benchmarks/benchmark_against_fortran_run.jl | 50 +- ...hmark_coil_ForcingTerms_against_fortran.jl | 121 ++-- .../benchmark_diiid_ideal_ntv_torque.jl | 26 +- .../benchmark_diiid_kinetic_stability.jl | 16 +- benchmarks/benchmark_legendre_2007.jl | 2 +- .../benchmark_solovev_kinetic_stability.jl | 46 +- benchmarks/benchmark_threads.jl | 2 +- benchmarks/equil_method_comparison.jl | 36 +- benchmarks/equil_numerical_params.jl | 26 +- benchmarks/equil_psihigh_scan.jl | 37 +- benchmarks/equil_psilow_scan.jl | 12 +- benchmarks/equil_spline_comparison.jl | 366 +++++----- .../analyze_example.jl | 2 +- .../run_imas_example.jl | 8 +- examples/LAR_beta_scan/run_scan.jl | 51 +- examples/LAR_epsilon_scan/run_scan.jl | 51 +- .../Solovev_ideal_example/analyze_example.jl | 9 +- .../analyze_example.jl | 17 +- examples/fourier_transform_usage.jl | 2 +- regression-harness/src/types.jl | 5 +- regression-harness/src/utils.jl | 2 +- src/Analysis/Equilibrium.jl | 19 +- src/Analysis/PerturbedEquilibrium.jl | 56 +- src/Analysis/PerturbedEquilibriumModes.jl | 53 +- src/Deck.jl | 25 +- src/Equilibrium/Axisymmetric/Inverse.jl | 22 +- src/Equilibrium/Derived/GeometryProfiles.jl | 4 +- src/Equilibrium/Grid/GridRefinement.jl | 4 +- src/ForceFreeStates/EulerLagrange.jl | 10 +- src/ForceFreeStates/Galerkin/GalerkinSolve.jl | 8 +- src/ForceFreeStates/Matching/MatchProblem.jl | 2 +- src/ForceFreeStates/Riccati/DeltaPrimeBVP.jl | 10 +- src/ForceFreeStates/Riccati/Propagators.jl | 47 +- src/ForceFreeStates/Surfaces/Asymptotics.jl | 4 +- src/ForceFreeStates/Surfaces/Finding.jl | 8 +- src/ForcingTerms/ForcingTerms.jl | 5 +- .../coil_geometries/jet_ex_mid.dat | 338 ++++----- src/ForcingTerms/coil_geometries/jtext_l.dat | 162 ++--- src/ForcingTerms/coil_geometries/jtext_m.dat | 162 ++--- src/ForcingTerms/coil_geometries/jtext_u.dat | 162 ++--- .../coil_geometries/mastu_efcc.dat | 338 ++++----- .../coil_geometries/mastu_rmpl.dat | 674 +++++++++--------- .../coil_geometries/mastu_rmpu.dat | 338 ++++----- src/InnerLayer/GGJ/Galerkin.jl | 2 +- src/InnerLayer/GGJ/Ray.jl | 6 +- src/InnerLayer/SLAYER/LayerInputs.jl | 2 +- src/InnerLayer/SLAYER/LayerParameters.jl | 18 +- src/KineticForces/KineticForces.jl | 4 +- src/KineticForces/Output.jl | 7 +- src/KineticForces/Torque.jl | 1 - src/KineticForces/Utils.jl | 11 +- src/LocalStability/Ballooning.jl | 54 +- .../PerturbedEquilibrium.jl | 6 +- src/PerturbedEquilibrium/ResonantCoupling.jl | 10 +- src/PerturbedEquilibrium/Response.jl | 2 +- src/PerturbedEquilibrium/SingularCoupling.jl | 4 +- .../CriticalResonantField/TorqueBalance.jl | 2 +- src/Tearing/Dispersion/BruteForceScan.jl | 24 +- src/Tearing/Dispersion/ContourSearchAMR.jl | 10 +- src/Tearing/GGJTearing.jl | 2 +- src/Tearing/HDF5Output.jl | 3 +- src/Tearing/Result.jl | 1 + src/Tearing/SLAYERTearing.jl | 19 +- src/Tearing/TearingProblem.jl | 4 +- src/Utilities/FourierTransforms.jl | 6 +- src/Utilities/HDF5Annotations.jl | 3 +- src/Utilities/HDF5Schema.jl | 12 +- src/Utilities/PhysicalConstants.jl | 8 +- src/Vacuum/DataTypes.jl | 16 +- src/Vacuum/Kernel2D.jl | 28 +- src/Vacuum/Kernel3D.jl | 4 +- src/Vacuum/Utilities.jl | 8 +- test/runtests.jl | 2 +- test/runtests_analysis_plots.jl | 15 +- test/runtests_coils.jl | 2 +- test/runtests_coordinate_invariant.jl | 4 +- test/runtests_core_capped_knots.jl | 2 +- test/runtests_critical_resonant_field.jl | 5 +- test/runtests_dispersion_amr.jl | 3 +- test/runtests_dispersion_ggj.jl | 2 +- test/runtests_dispersion_polish.jl | 4 +- test/runtests_dispersion_residual.jl | 46 +- test/runtests_dispersion_scan.jl | 50 +- test/runtests_equil.jl | 8 +- test/runtests_fouriertransforms.jl | 8 +- test/runtests_galerkin_cholesky.jl | 8 +- test/runtests_imas.jl | 54 +- test/runtests_kinetic.jl | 5 +- test/runtests_layer_overlap_fitzpatrick.jl | 7 +- test/runtests_multiion.jl | 5 +- test/runtests_parallel_integration.jl | 16 +- test/runtests_riccati.jl | 16 +- test/runtests_sfl_sampling.jl | 16 +- test/runtests_sing.jl | 3 +- test/runtests_slayer_inputs.jl | 2 +- test/runtests_slayer_runner.jl | 3 +- test/runtests_solve_api.jl | 29 +- test/runtests_vacuum.jl | 23 +- 99 files changed, 2069 insertions(+), 1886 deletions(-) diff --git a/Project.toml b/Project.toml index f3a49b5c95..f61b337b47 100644 --- a/Project.toml +++ b/Project.toml @@ -73,5 +73,3 @@ Statistics = "1" TOML = "1" Test = "1" julia = "1.11" - - diff --git a/benchmarks/benchmark_against_fortran_run.jl b/benchmarks/benchmark_against_fortran_run.jl index 138fc91fb1..87464dc8b5 100644 --- a/benchmarks/benchmark_against_fortran_run.jl +++ b/benchmarks/benchmark_against_fortran_run.jl @@ -538,8 +538,8 @@ function compare_phix( grid = ForcingTerms.sample_boundary_grid(equil, p.mtheta_coil, p.nzeta_coil; psi=psilim) nobs = p.mtheta_coil * p.nzeta_coil - obs_R = zeros(nobs); - obs_phi = zeros(nobs); + obs_R = zeros(nobs) + obs_phi = zeros(nobs) obs_Z = zeros(nobs) for j in 1:p.nzeta_coil, i in 1:p.mtheta_coil idx = i + (j - 1) * p.mtheta_coil @@ -547,8 +547,8 @@ function compare_phix( obs_phi[idx] = grid.phi_grid[j] + grid.phi_offset[i] obs_Z[idx] = grid.Z[i] end - B_R = zeros(nobs); - B_phi = zeros(nobs); + B_R = zeros(nobs) + B_phi = zeros(nobs) B_Z = zeros(nobs) ForcingTerms.compute_biot_savart_boundary!(B_R, B_phi, B_Z, obs_R, obs_phi, obs_Z, coil_sets) bn = zeros(p.mtheta_coil, p.nzeta_coil) @@ -584,9 +584,9 @@ end function build_comparison_table(fort, julia, fortran_dir, bench_dir, nn) lines = String[] - push!(lines, "=" ^ 72) + push!(lines, "="^72) push!(lines, "GPEC FORTRAN vs JULIA COMPARISON") - push!(lines, "=" ^ 72) + push!(lines, "="^72) push!(lines, "Fortran : $fortran_dir") push!(lines, "Julia : $(joinpath(bench_dir, "gpec.h5"))") push!(lines, "Date : $(Dates.format(Dates.now(), "yyyy-mm-dd HH:MM"))") @@ -629,9 +629,9 @@ function build_comparison_table(fort, julia, fortran_dir, bench_dir, nn) push!(lines, "") push!(lines, "--- Mercier Criterion ---") - fdi = fort["di"]; + fdi = fort["di"] fdr = fort["dr"] - jdi = julia["di"]; + jdi = julia["di"] jdr = julia["dr"] if !isempty(jdi) && !isempty(fdi) jdi_on_fg = [_interp1(jq_psi, jdi, p) for p in fq_psi] @@ -705,7 +705,7 @@ function build_comparison_table(fort, julia, fortran_dir, bench_dir, nn) m = i <= length(m_ctrl) ? m_ctrl[i] : i fl = abs(f_L[i, i]) jl = abs(j_L[i, i]) - ratio = fl > 0 ? jl/fl : NaN + ratio = fl > 0 ? jl / fl : NaN push!(lines, @sprintf("%-5d %14.4e %14.4e %-6s %10.3f", m, fl, jl, rel_pct(fl, jl), ratio)) end else @@ -724,7 +724,7 @@ function build_comparison_table(fort, julia, fortran_dir, bench_dir, nn) m = i <= length(m_ctrl) ? m_ctrl[i] : i fl = abs(f_LS[i, i]) jl = abs(j_LS[i, i]) - ratio = fl > 0 ? jl/fl : NaN + ratio = fl > 0 ? jl / fl : NaN push!(lines, @sprintf("%-5d %14.4e %14.4e %-6s %10.3f", m, fl, jl, rel_pct(fl, jl), ratio)) end else @@ -753,7 +753,7 @@ function build_comparison_table(fort, julia, fortran_dir, bench_dir, nn) jp_r = (row > 0 && !isempty(julia["rational_psi"])) ? julia["rational_psi"][row] : NaN j_phi = (row > 0 && !isempty(julia["resonant_area_weighted_field"])) ? abs(julia["resonant_area_weighted_field"][row]) : NaN - j_wisl = (row > 0 && !isempty(julia["island_half_width"])) ? 2*julia["island_half_width"][row] : NaN + j_wisl = (row > 0 && !isempty(julia["island_half_width"])) ? 2 * julia["island_half_width"][row] : NaN j_kch = (row > 0 && !isempty(julia["chirikov_parameter"])) ? julia["chirikov_parameter"][row] : NaN push!( @@ -842,16 +842,16 @@ function build_comparison_table(fort, julia, fortran_dir, bench_dir, nn) ) end - push!(lines, "=" ^ 72) + push!(lines, "="^72) return lines end function _interp1(x::AbstractVector, y::AbstractVector, xi::Real) - n = length(x); + n = length(x) n < 2 && return y[1] xi <= x[1] && return y[1] xi >= x[end] && return y[end] - i = clamp(searchsortedlast(x, xi), 1, n-1) + i = clamp(searchsortedlast(x, xi), 1, n - 1) t = (xi - x[i]) / (x[i+1] - x[i]) return y[i] + t * (y[i+1] - y[i]) end @@ -924,12 +924,12 @@ function generate_plots(fort, julia, bench_dir, nn) p6 = plot(; xlabel="m", ylabel="|b̄| [T]", title="Phi_x & Phi_tot spectrum (n=$nn)", legend=:topright) if !isempty(j_fvec) && !isempty(f_phi_x) - jfx_amps = [abs(get(Dict(zip(j_mmodes, j_fvec)), m, NaN+0im)) for m in m_ctrl] - jft_amps = [abs(get(Dict(zip(j_mmodes, j_rvec)), m, NaN+0im)) for m in m_ctrl] + jfx_amps = [abs(get(Dict(zip(j_mmodes, j_fvec)), m, NaN + 0im)) for m in m_ctrl] + jft_amps = [abs(get(Dict(zip(j_mmodes, j_rvec)), m, NaN + 0im)) for m in m_ctrl] ffx_amps = abs.(f_phi_x) fft_amps = abs.(f_phi_tot) - me, jfe = step_series(m_ctrl, replace(jfx_amps, NaN=>0.0)) - _, jte = step_series(m_ctrl, replace(jft_amps, NaN=>0.0)) + me, jfe = step_series(m_ctrl, replace(jfx_amps, NaN => 0.0)) + _, jte = step_series(m_ctrl, replace(jft_amps, NaN => 0.0)) _, ffe = step_series(m_ctrl, ffx_amps) _, fte = step_series(m_ctrl, fft_amps) plot!(p6, me, ffe; seriestype=:steppre, lw=2, color=:steelblue, label="Φ_x Fortran") @@ -1200,7 +1200,7 @@ function generate_plots(fort, julia, bench_dir, nn) j_theta_data, _, _ = PerturbedEquilibriumModes.modes_to_theta(h5_path, h5_var; keep_sfl_phi=false) j_fun = j_theta_data[:, :, 1] # (npsi, ntheta) for n=1 catch e - @warn "modes_to_theta failed for $h5_var" exception=e + @warn "modes_to_theta failed for $h5_var" exception = e end end @@ -1349,11 +1349,11 @@ function main(argv=ARGS) coil_dat_dir = joinpath(@__DIR__, "..", "src", "ForcingTerms", "coil_geometries") forcing_mode = use_file_forcing ? "file (Fortran Phi_coil)" : "coil (Julia Biot-Savart)" - println("=" ^ 72) + println("="^72) println("GPEC Fortran vs Julia Benchmark") println(" Fortran dir : $fortran_dir") println(" Forcing mode : $forcing_mode") - println("=" ^ 72) + println("="^72) println("\n[1/5] Parsing Fortran inputs ...") p = parse_fortran_run(fortran_dir) @@ -1441,15 +1441,15 @@ function main(argv=ARGS) println() table_lines = build_comparison_table(fort, julia, fortran_dir, bench_dir, p.nn) for line in table_lines - ; - println(line); + + println(line) end table_path = joinpath(bench_dir, "comparison_table.txt") open(table_path, "w") do io for line in table_lines - ; - println(io, line); + + println(io, line) end end println("\nComparison table saved to: ", abspath(table_path)) diff --git a/benchmarks/benchmark_coil_ForcingTerms_against_fortran.jl b/benchmarks/benchmark_coil_ForcingTerms_against_fortran.jl index ceea29e317..6b5456de71 100644 --- a/benchmarks/benchmark_coil_ForcingTerms_against_fortran.jl +++ b/benchmarks/benchmark_coil_ForcingTerms_against_fortran.jl @@ -25,7 +25,7 @@ using NCDatasets using Plots using Printf -const OUTPUT_DIR = joinpath(@__DIR__, "coil_pipeline") +const OUTPUT_DIR = joinpath(@__DIR__, "coil_pipeline") const COIL_DAT_DIR = joinpath(@__DIR__, "..", "src", "ForcingTerms", "coil_geometries") # --------------------------------------------------------------------------- @@ -89,38 +89,38 @@ end function parse_fortran_run(dir::String)::FortranRunParams equil_file = joinpath(dir, "equil.in") - dcon_file = joinpath(dir, "dcon.in") - coil_file = joinpath(dir, "coil.in") + dcon_file = joinpath(dir, "dcon.in") + coil_file = joinpath(dir, "coil.in") isfile(equil_file) || error("equil.in not found in $dir") - isfile(dcon_file) || error("dcon.in not found in $dir") - isfile(coil_file) || error("coil.in not found in $dir") + isfile(dcon_file) || error("dcon.in not found in $dir") + isfile(coil_file) || error("coil.in not found in $dir") - eq_text = _strip_fortran_comments(read(equil_file, String)) - dcon_text = _strip_fortran_comments(read(dcon_file, String)) - coil_text = _strip_fortran_comments(read(coil_file, String)) + eq_text = _strip_fortran_comments(read(equil_file, String)) + dcon_text = _strip_fortran_comments(read(dcon_file, String)) + coil_text = _strip_fortran_comments(read(coil_file, String)) # Equilibrium parameters - eq_type = _find_string(eq_text, "eq_type"; default="efit") - eq_file = _find_string(eq_text, "eq_filename"; default="") - jac_type = _find_string(eq_text, "jac_type"; default="hamada") - psilow = _find_scalar(eq_text, "psilow"; default=1e-4) - psihigh = _find_scalar(eq_text, "psihigh"; default=0.99) - mtheta_eq = _find_int(eq_text, "mtheta"; default=256) + eq_type = _find_string(eq_text, "eq_type"; default="efit") + eq_file = _find_string(eq_text, "eq_filename"; default="") + jac_type = _find_string(eq_text, "jac_type"; default="hamada") + psilow = _find_scalar(eq_text, "psilow"; default=1e-4) + psihigh = _find_scalar(eq_text, "psihigh"; default=0.99) + mtheta_eq = _find_int(eq_text, "mtheta"; default=256) grid_type = _find_string(eq_text, "grid_type"; default="ldp") # Toroidal mode number and m-range expansion - nn = _find_int(dcon_text, "nn"; default=1) - delta_mlow = _find_int(dcon_text, "delta_mlow"; default=8) + nn = _find_int(dcon_text, "nn"; default=1) + delta_mlow = _find_int(dcon_text, "delta_mlow"; default=8) delta_mhigh = _find_int(dcon_text, "delta_mhigh"; default=8) # Coil grid parameters mtheta_coil = _find_int(coil_text, "cmtheta"; default=480) - nzeta_coil = _find_int(coil_text, "cmzeta"; default=40) - machine = _find_string(coil_text, "machine"; default="d3d") - coil_num = _find_int(coil_text, "coil_num"; default=1) + nzeta_coil = _find_int(coil_text, "cmzeta"; default=40) + machine = _find_string(coil_text, "machine"; default="d3d") + coil_num = _find_int(coil_text, "coil_num"; default=1) - coil_names = String[] + coil_names = String[] coil_currents = Vector{Float64}[] for ci in 1:coil_num name = _find_indexed_string(coil_text, "coil_name", ci; default="coil$ci") @@ -135,9 +135,9 @@ function parse_fortran_run(dir::String)::FortranRunParams end return FortranRunParams(eq_type, eq_file, jac_type, psilow, psihigh, mtheta_eq, - grid_type, nn, delta_mlow, delta_mhigh, - mtheta_coil, nzeta_coil, machine, - coil_names, coil_currents) + grid_type, nn, delta_mlow, delta_mhigh, + mtheta_coil, nzeta_coil, machine, + coil_names, coil_currents) end function find_output_nc(dir::String, nn::Int) @@ -177,16 +177,16 @@ end nc_path = find_output_nc(run_dir, p.nn) # Read Fortran output (psilim and Phi_x spectrum) -fortran_m = Int[] -fortran_Phix = Float64[] +fortran_m = Int[] +fortran_Phix = Float64[] fortran_psilim = NaN NCDatasets.Dataset(nc_path, "r") do ds - global fortran_m = Int.(ds["m"][:]) + global fortran_m = Int.(ds["m"][:]) global fortran_psilim = Float64(ds.attrib["psilim"]) phi_x_raw = ds["Phi_x"][:, :] # (mpert, 2); col 1=cos, col 2=sin - global fortran_Phix = sqrt.(phi_x_raw[:, 1].^2 .+ phi_x_raw[:, 2].^2) + global fortran_Phix = sqrt.(phi_x_raw[:, 1] .^ 2 .+ phi_x_raw[:, 2] .^ 2) end -mlow = minimum(fortran_m) +mlow = minimum(fortran_m) mhigh = maximum(fortran_m) @printf " Fortran psilim=%.6f, m=%d:%d\n" fortran_psilim mlow mhigh @@ -196,15 +196,15 @@ mhigh = maximum(fortran_m) println("\n[2/4] Loading equilibrium ...") t_equil = @elapsed begin eq_dict = Dict{String,Any}( - "eq_type" => p.eq_type, - "eq_filename" => p.eq_filename, - "jac_type" => p.jac_type, - "psilow" => p.psilow, - "psihigh" => p.psihigh, - "mtheta" => p.mtheta_equil, - "grid_type" => p.grid_type, + "eq_type" => p.eq_type, + "eq_filename" => p.eq_filename, + "jac_type" => p.jac_type, + "psilow" => p.psilow, + "psihigh" => p.psihigh, + "mtheta" => p.mtheta_equil, + "grid_type" => p.grid_type, "psi_accuracy" => 0.001, - "flux_surface_rtol" => 1e-7, + "flux_surface_rtol" => 1e-7 ) eq_config = GeneralizedPerturbedEquilibrium.equilibrium_config(Dict{String,Any}("Equilibrium" => eq_dict), run_dir) equil = Equilibrium.PlasmaEquilibrium(eq_config) @@ -223,7 +223,7 @@ for (name, currents) in zip(p.coil_names, p.coil_currents) isfile(dat_path) || error("Coil geometry file not found: $dat_path") raw = read_coil_dat(dat_path) push!(coil_sets, CoilSet(raw.name, raw.ncoil, raw.s, raw.nw, raw.nsec, - raw.x, raw.y, raw.z, Float64.(currents[1:raw.ncoil]))) + raw.x, raw.y, raw.z, Float64.(currents[1:raw.ncoil]))) end for cs in coil_sets @printf " %-10s %d conductors currents: %s A\n" cs.name cs.ncoil string(round.(cs.currents; digits=1)) @@ -231,18 +231,22 @@ end julia_modes = ForcingMode[] bnd_grid = nothing -bn = nothing -t_biot = @elapsed begin +bn = nothing +t_biot = @elapsed begin bnd_grid = sample_boundary_grid(equil, p.mtheta_coil, p.nzeta_coil; psi=fortran_psilim) nobs = p.mtheta_coil * p.nzeta_coil - obs_R = zeros(nobs); obs_phi = zeros(nobs); obs_Z = zeros(nobs) + obs_R = zeros(nobs) + obs_phi = zeros(nobs) + obs_Z = zeros(nobs) for j in 1:p.nzeta_coil, i in 1:p.mtheta_coil idx = i + (j - 1) * p.mtheta_coil - obs_R[idx] = bnd_grid.R[i] + obs_R[idx] = bnd_grid.R[i] obs_phi[idx] = bnd_grid.phi_grid[j] + bnd_grid.phi_offset[i] - obs_Z[idx] = bnd_grid.Z[i] + obs_Z[idx] = bnd_grid.Z[i] end - B_R = zeros(nobs); B_phi = zeros(nobs); B_Z = zeros(nobs) + B_R = zeros(nobs) + B_phi = zeros(nobs) + B_Z = zeros(nobs) compute_biot_savart_boundary!(B_R, B_phi, B_Z, obs_R, obs_phi, obs_Z, coil_sets) bn = zeros(p.mtheta_coil, p.nzeta_coil) project_normal_flux!(bn, B_R, B_Z, bnd_grid) @@ -250,7 +254,7 @@ t_biot = @elapsed begin end @printf " Done in %.1f s, max|Phi_x| = %.3e T·m²\n" t_biot maximum(abs, bn) -julia_m = [md.m for md in sort(julia_modes; by=md -> md.m)] +julia_m = [md.m for md in sort(julia_modes; by=md -> md.m)] julia_amps = abs.([md.amplitude for md in sort(julia_modes; by=md -> md.m)]) # Print comparison table @@ -259,7 +263,7 @@ println(" " * "-"^55) for (jm, ja) in zip(julia_m, julia_amps) k = findfirst(==(jm), fortran_m) if !isnothing(k) - fp = fortran_Phix[k] + fp = fortran_Phix[k] ratio = ja > 1e-40 ? fp / ja : NaN @printf " %3d %12.4e %12.4e %.4f\n" jm ja fp ratio end @@ -272,7 +276,7 @@ println(" Expected ratio ≈ 1.000 for all modes.") println("\n[4/4] Generating figure ...") function step_series(m_vals, amps) - m_ext = [m_vals[1] - 1; m_vals; m_vals[end] + 1] + m_ext = [m_vals[1] - 1; m_vals; m_vals[end] + 1] amp_ext = [0.0; amps; 0.0] return m_ext, amp_ext end @@ -286,19 +290,20 @@ p2 = plot_bn_contour(bn, p.mtheta_coil, p.nzeta_coil; n=p.nn) title!(p2, "Normal flux Φₓ(θ, ζ) at ψ=$(round(fortran_psilim; digits=4))") # Panel 3: Julia vs Fortran Phi_x spectrum -jm_ext, ja_ext = step_series(julia_m, julia_amps) -fm_ext, fa_ext = step_series(fortran_m, fortran_Phix) +jm_ext, ja_ext = step_series(julia_m, julia_amps) +fm_ext, fa_ext = step_series(fortran_m, fortran_Phix) p3 = plot(; xlabel="Poloidal mode m", ylabel="|Phi_x| [T·m²]", - title="Spectrum: Julia vs Fortran (n=$(p.nn))", - legend=:topright) + title="Spectrum: Julia vs Fortran (n=$(p.nn))", + legend=:topright) plot!(p3, jm_ext, ja_ext; seriestype=:steppre, lw=2, color=:blue, - label="Julia ψ=$(round(fortran_psilim; digits=4))") + label="Julia ψ=$(round(fortran_psilim; digits=4))") plot!(p3, fm_ext, fa_ext; seriestype=:steppre, lw=2, color=:orange, - linestyle=:dash, label="Fortran Phi_x") -ylims!(p3, (0, Inf)); xlims!(p3, mlow - 2, mhigh + 2) + linestyle=:dash, label="Fortran Phi_x") +ylims!(p3, (0, Inf)); +xlims!(p3, mlow - 2, mhigh + 2); # Panel 4: Phi_x / Julia ratio -ratio_m = Int[] +ratio_m = Int[] ratio_vals = Float64[] for (jm, ja) in zip(julia_m, julia_amps) k = findfirst(==(jm), fortran_m) @@ -308,8 +313,8 @@ for (jm, ja) in zip(julia_m, julia_amps) end end p4 = plot(; xlabel="Poloidal mode m", ylabel="Phi_x / Julia", - title="Ratio (flat at 1.0 → correct)", - legend=:topright) + title="Ratio (flat at 1.0 → correct)", + legend=:topright) if !isempty(ratio_m) scatter!(p4, ratio_m, ratio_vals; color=:blue, label="ratio", markersize=5) hline!(p4, [1.0]; color=:red, linestyle=:dash, label="expected = 1.0") @@ -318,8 +323,8 @@ xlims!(p4, mlow - 2, mhigh + 2) run_label = basename(run_dir) fig = plot(p1, p2, p3, p4; - layout=(2, 2), size=(1400, 900), - plot_title="$run_label | $(p.machine) n=$(p.nn) psilim=$(round(fortran_psilim; digits=4))") + layout=(2, 2), size=(1400, 900), + plot_title="$run_label | $(p.machine) n=$(p.nn) psilim=$(round(fortran_psilim; digits=4))") out_png = joinpath(OUTPUT_DIR, "coil_pipeline_$(run_label)_n$(p.nn).png") savefig(fig, out_png) diff --git a/benchmarks/benchmark_diiid_ideal_ntv_torque.jl b/benchmarks/benchmark_diiid_ideal_ntv_torque.jl index 5b14c41b52..3b2ae96fe4 100644 --- a/benchmarks/benchmark_diiid_ideal_ntv_torque.jl +++ b/benchmarks/benchmark_diiid_ideal_ntv_torque.jl @@ -35,7 +35,9 @@ const KF = GPE.KineticForces const Eq = GPE.Equilibrium const PE = GPE.PerturbedEquilibrium -"Fortran GPEC DIII-D ideal example run directory, from the environment (no on-disk default)." +""" +Fortran GPEC DIII-D ideal example run directory, from the environment (no on-disk default). +""" default_fortran_dir() = get(() -> error("Set GPEC_FORTRAN_DIIID, or pass the Fortran run directory as the first argument"), ENV, "GPEC_FORTRAN_DIIID") @@ -214,10 +216,10 @@ _p(args...) = (println(stderr, args...); flush(stderr)) _pf(fmt, args...) = (print(stderr, Printf.format(Printf.Format(fmt), args...)); flush(stderr)) function run_benchmark(fortran_dir::String=default_fortran_dir()) - _p("=" ^ 70) + _p("="^70) _p(" DIIID Kinetic Benchmark: Julia KineticForces vs Fortran PENTRC") _p(" Fortran example: $fortran_dir") - _p("=" ^ 70) + _p("="^70) inputs = discover_inputs(fortran_dir) ref = read_pentrc_reference(inputs.pentrc_nc) @@ -318,9 +320,9 @@ function run_benchmark(fortran_dir::String=default_fortran_dir()) end # Compare results - _p("\n" * "=" ^ 70) + _p("\n" * "="^70) _p(" Results Comparison") - _p("=" ^ 70) + _p("="^70) results = Dict{String,NamedTuple}() @@ -346,13 +348,13 @@ function run_benchmark(fortran_dir::String=default_fortran_dir()) end end - _p("\n" * "=" ^ 70) + _p("\n" * "="^70) # Check pass/fail all_pass = true for (method_key, r) in results if r.T_err > 5.0 || r.dW_err > 5.0 - @warn "$(uppercase(method_key)) exceeds 5% threshold" T_err=r.T_err dW_err=r.dW_err + @warn "$(uppercase(method_key)) exceeds 5% threshold" T_err = r.T_err dW_err = r.dW_err all_pass = false end end @@ -366,9 +368,9 @@ function run_benchmark(fortran_dir::String=default_fortran_dir()) end # Plot dT/dψ and T(ψ) overlay vs Fortran PENTRC reference - _p("\n" * "=" ^ 70) + _p("\n" * "="^70) _p(" Plotting dT/dψ and T(ψ) overlay vs Fortran") - _p("=" ^ 70) + _p("="^70) pentrc_nc = inputs.pentrc_nc @@ -383,7 +385,7 @@ function run_benchmark(fortran_dir::String=default_fortran_dir()) dt_var = "dTdpsi_$method_key" T_var = "T_$method_key" if !(haskey(ds, psi_var) && haskey(ds, dt_var) && haskey(ds, T_var)) - @warn "Fortran NetCDF missing variables for method" method=method_key + @warn "Fortran NetCDF missing variables for method" method = method_key continue end psi_f = Array(ds[psi_var][:]) @@ -445,7 +447,7 @@ function run_benchmark(fortran_dir::String=default_fortran_dir()) savefig(p1, path1) _p(" Saved dT/dψ panel → $path1") catch err - @warn "dT/dψ panel savefig failed" method=mkey err + @warn "dT/dψ panel savefig failed" method = mkey err end # Cumulative T panel @@ -461,7 +463,7 @@ function run_benchmark(fortran_dir::String=default_fortran_dir()) savefig(p2, path2) _p(" Saved cumulative T panel → $path2") catch err - @warn "cumulative T panel savefig failed" method=mkey err + @warn "cumulative T panel savefig failed" method = mkey err end end diff --git a/benchmarks/benchmark_diiid_kinetic_stability.jl b/benchmarks/benchmark_diiid_kinetic_stability.jl index d9e77e1e55..46d970b372 100644 --- a/benchmarks/benchmark_diiid_kinetic_stability.jl +++ b/benchmarks/benchmark_diiid_kinetic_stability.jl @@ -36,7 +36,9 @@ using GeneralizedPerturbedEquilibrium const GPE = GeneralizedPerturbedEquilibrium const AnalysisFFS = GPE.Analysis.ForceFreeStates -"Fortran GPEC DIII-D kinetic example run directory, from the environment (no on-disk default)." +""" +Fortran GPEC DIII-D kinetic example run directory, from the environment (no on-disk default). +""" default_fortran_dir() = get(() -> error("Set GPEC_FORTRAN_DIIID_DCON, or pass the Fortran run directory as the first argument"), ENV, "GPEC_FORTRAN_DIIID_DCON") @@ -167,10 +169,10 @@ _p(args...) = (println(stderr, args...); flush(stderr)) _pf(fmt, args...) = (print(stderr, Printf.format(Printf.Format(fmt), args...)); flush(stderr)) function run_benchmark(fortran_dir::String=default_fortran_dir()) - _p("=" ^ 70) + _p("="^70) _p(" DIIID Kinetic-DCON Benchmark: Julia KF→FFS vs Fortran DCON") _p(" Fortran example: $fortran_dir") - _p("=" ^ 70) + _p("="^70) inputs = discover_inputs(fortran_dir) ref = read_dcon_reference(inputs.dcon_nc) @@ -204,9 +206,9 @@ function run_benchmark(fortran_dir::String=default_fortran_dir()) re_err = abs(julia_re - ref.W_t_re) / abs(ref.W_t_re) * 100 im_err = abs(julia_im - ref.W_t_im) / abs(ref.W_t_im) * 100 - _p("\n" * "=" ^ 70) + _p("\n" * "="^70) _p(" Results Comparison (kinetic-DCON least-stable eigenvalue)") - _p("=" ^ 70) + _p("="^70) _pf(" Re(et[1]): Julia = %12.6f Fortran = %12.6f err = %6.2f%%\n", julia_re, ref.W_t_re, re_err) _pf(" Im(et[1]): Julia = %12.6f Fortran = %12.6f err = %6.2f%%\n", @@ -220,7 +222,7 @@ function run_benchmark(fortran_dir::String=default_fortran_dir()) im_pass = im_err <= 20.0 im_stretch = im_err <= 5.0 - _p("\n" * "=" ^ 70) + _p("\n" * "="^70) if re_pass && im_pass _p(" PASS — Re within 5 %, Im within 20 %" * (im_stretch ? " (stretch: Im within 5 %)" : "")) @@ -229,7 +231,7 @@ function run_benchmark(fortran_dir::String=default_fortran_dir()) re_pass || _pf(" Re err %.2f%% > 5%%\n", re_err) im_pass || _pf(" Im err %.2f%% > 20%%\n", im_err) end - _p("=" ^ 70) + _p("="^70) # cond(F̄) vs ψ diagnostic — shows whether kinetic singular surfaces # (det(F̄)=0) were found and why peaks were/weren't accepted. diff --git a/benchmarks/benchmark_legendre_2007.jl b/benchmarks/benchmark_legendre_2007.jl index 6827d1d950..184fbe3548 100644 --- a/benchmarks/benchmark_legendre_2007.jl +++ b/benchmarks/benchmark_legendre_2007.jl @@ -104,7 +104,7 @@ for (s, n) in test_cases speedup = time_1997 / time_2007 println(@sprintf("%-8.2f %-8d %-20.6f %-20.6f %-15.2fx", - s, n, time_1997*1000, time_2007*1000, speedup)) + s, n, time_1997 * 1000, time_2007 * 1000, speedup)) end println("\n3. VISUAL CHECK: rhohat parameter and integration method selection") diff --git a/benchmarks/benchmark_solovev_kinetic_stability.jl b/benchmarks/benchmark_solovev_kinetic_stability.jl index 9d0fa56291..0485951bef 100644 --- a/benchmarks/benchmark_solovev_kinetic_stability.jl +++ b/benchmarks/benchmark_solovev_kinetic_stability.jl @@ -43,10 +43,10 @@ using HDF5 using GeneralizedPerturbedEquilibrium const GPE = GeneralizedPerturbedEquilibrium -const REPO = normpath(joinpath(@__DIR__, "..")) -const JULIA_FIXTURE = joinpath(REPO, "test", "test_data", "regression_solovev_kinetic_calculated") -const FORTRAN_DCON = get(ENV, "GPEC_FORTRAN_DCON", "") -const FORTRAN_SOLDIR = get(ENV, "GPEC_FORTRAN_SOLDIR", "") +const REPO = normpath(joinpath(@__DIR__, "..")) +const JULIA_FIXTURE = joinpath(REPO, "test", "test_data", "regression_solovev_kinetic_calculated") +const FORTRAN_DCON = get(ENV, "GPEC_FORTRAN_DCON", "") +const FORTRAN_SOLDIR = get(ENV, "GPEC_FORTRAN_SOLDIR", "") _p(args...) = (println(stderr, args...); flush(stderr)) @@ -90,35 +90,35 @@ function build_matched_fortran_deck(workdir::String) end gpec = TOML.parsefile(joinpath(JULIA_FIXTURE, "gpec.toml")) - eq = gpec["Equilibrium"] - ffs = gpec["ForceFreeStates"] - kf = get(gpec, "KineticForces", Dict{String,Any}()) + eq = gpec["Equilibrium"] + ffs = gpec["ForceFreeStates"] + kf = get(gpec, "KineticForces", Dict{String,Any}()) # equil.in — grid + coordinate settings to match the Julia [Equilibrium] block. eqin = joinpath(workdir, "equil.in") - replace_namelist_value!(eqin, "psilow", string(eq["psilow"])) + replace_namelist_value!(eqin, "psilow", string(eq["psilow"])) replace_namelist_value!(eqin, "psihigh", string(eq["psihigh"])) - replace_namelist_value!(eqin, "mpsi", string(eq["mpsi"])) - replace_namelist_value!(eqin, "mtheta", string(eq["mtheta"])) + replace_namelist_value!(eqin, "mpsi", string(eq["mpsi"])) + replace_namelist_value!(eqin, "mtheta", string(eq["mtheta"])) # dcon.in — kinetic flags already match; align mode band, edge truncation, n. dconin = joinpath(workdir, "dcon.in") - replace_namelist_value!(dconin, "nn", string(first(ffs["nn"]))) - replace_namelist_value!(dconin, "delta_mlow", string(get(ffs, "delta_mlow", 0))) + replace_namelist_value!(dconin, "nn", string(first(ffs["nn"]))) + replace_namelist_value!(dconin, "delta_mlow", string(get(ffs, "delta_mlow", 0))) replace_namelist_value!(dconin, "delta_mhigh", string(get(ffs, "delta_mhigh", 0))) - replace_namelist_value!(dconin, "qlow", string(ffs["qlow"])) - replace_namelist_value!(dconin, "qhigh", string(ffs["qhigh"])) + 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 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") + replace_namelist_value!(dconin, "dmlim", "0.2") # pentrc.in — bounce harmonics + species; defaults mirror KineticForcesControl. pentin = joinpath(workdir, "pentrc.in") - replace_namelist_value!(pentin, "nl", string(get(kf, "nl", 1))) + 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, "collisionality_factor", 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 @@ -161,9 +161,9 @@ function run_julia_reference() end function run_benchmark() - _p("=" ^ 70) + _p("="^70) _p(" Solovev Kinetic-DCON Benchmark: Julia KF→FFS vs Fortran DCON") - _p("=" ^ 70) + _p("="^70) workdir = mktempdir(; prefix="solovev_kinetic_fortran_") build_matched_fortran_deck(workdir) @@ -178,15 +178,15 @@ function run_benchmark() re_err = abs(jul_re - fort_re) / abs(fort_re) * 100 im_err = abs(jul_im - fort_im) / abs(fort_im) * 100 - _p("\n" * "=" ^ 70) + _p("\n" * "="^70) _p(" Least-stable total-energy eigenvalue (mpert matched)") - _p("=" ^ 70) + _p("="^70) _p(@sprintf(" Re(et[1]): Julia = %12.6f Fortran = %12.6f err = %6.2f%%", jul_re, fort_re, re_err)) _p(@sprintf(" Im(et[1]): Julia = %12.6f Fortran = %12.6f err = %6.2f%%", jul_im, fort_im, im_err)) re_pass = re_err <= 5.0 im_pass = im_err <= 20.0 - _p("\n" * "=" ^ 70) + _p("\n" * "="^70) if re_pass && im_pass _p(" PASS — Re within 5 %, Im within 20 %") else @@ -194,7 +194,7 @@ function run_benchmark() re_pass || _p(@sprintf(" Re err %.2f%% > 5%%", re_err)) im_pass || _p(@sprintf(" Im err %.2f%% > 20%%", im_err)) end - _p("=" ^ 70) + _p("="^70) return (; jul_re, jul_im, fort_re, fort_im, re_err, im_err, wall) end diff --git a/benchmarks/benchmark_threads.jl b/benchmarks/benchmark_threads.jl index a757a85467..2dc54c30d6 100644 --- a/benchmarks/benchmark_threads.jl +++ b/benchmarks/benchmark_threads.jl @@ -49,6 +49,6 @@ for (label, ex) in [("Solovev", sol_ex), ("DIIID-like", diiid_ex)] println("$label (N=$N, nthreads=$nthreads)") @printf(" forward et[1]=%.5f t=%.2fs speedup=1.00×\n", et_fwd, t_fwd) @printf(" riccati et[1]=%.5f t=%.2fs speedup=%.2f× err=%.4f%%\n", - et_ric, t_ric, t_fwd/t_ric, err_ric) + et_ric, t_ric, t_fwd / t_ric, err_ric) println() end diff --git a/benchmarks/equil_method_comparison.jl b/benchmarks/equil_method_comparison.jl index 5f83146009..bd6faab8dd 100644 --- a/benchmarks/equil_method_comparison.jl +++ b/benchmarks/equil_method_comparison.jl @@ -21,13 +21,13 @@ using GeneralizedPerturbedEquilibrium.Equilibrium using TOML, Printf, Statistics example_path = length(ARGS) > 0 ? ARGS[1] : joinpath(@__DIR__, "../examples/DIIID-like_ideal_example") -config_path = joinpath(example_path, "gpec.toml") +config_path = joinpath(example_path, "gpec.toml") psihigh_override = length(ARGS) > 1 ? parse(Float64, ARGS[2]) : nothing -println("=" ^ 65) +println("="^65) println("Equilibrium Method Comparison") println("Example: $example_path") -println("=" ^ 65) +println("="^65) methods = ["efit", "efit_arclength", "efit_by_inversion"] results = Dict{String,Any}() @@ -58,18 +58,18 @@ for method in methods success = false end results[method] = Dict( - "success" => success, - "pe" => pe, - "runtime" => (t1 + t2) / 2.0, - "config" => cfg + "success" => success, + "pe" => pe, + "runtime" => (t1 + t2) / 2.0, + "config" => cfg ) success && @printf(" Runtime (avg 2 warm): %.3f s\n", (t1 + t2) / 2.0) end # ─── Profile comparison ─────────────────────────────────────────────────────── -println("\n" * "=" ^ 65) +println("\n" * "="^65) println("q(ψ) cross-method comparison") -println("=" ^ 65) +println("="^65) # Reference from the first successful method at shared psi_nodes ref_method = first(m for m in methods if results[m]["success"]) @@ -105,13 +105,13 @@ for method in methods results[method]["success"] || continue Δq = abs.([q_vals[method][i] - efit_q_interp(psi_nodes[i]) for i in 1:length(psi_nodes)]) @printf(" |q_%s - q_efit| (ψ<0.95): max=%.2e rms=%.2e\n", - method, maximum(Δq[mask_mid]), sqrt(mean(Δq[mask_mid].^2))) + method, maximum(Δq[mask_mid]), sqrt(mean(Δq[mask_mid] .^ 2))) end # ─── Roundtrip error ───────────────────────────────────────────────────────── -println("\n" * "=" ^ 65) +println("\n" * "="^65) println("Roundtrip error: (ψ,θ)→(R,Z)→ψ_spline(R,Z)−ψ_target") -println("=" ^ 65) +println("="^65) for method in methods results[method]["success"] || continue @@ -123,7 +123,7 @@ for method in methods psio = pe.psio psi_xs = pe.rzphi_xs psi_ys = pe.rzphi_ys - mpsi = length(psi_xs) - 1 + mpsi = length(psi_xs) - 1 mtheta = length(psi_ys) - 1 errors = Float64[] @@ -131,7 +131,7 @@ for method in methods ψ_target = psi_xs[ipsi] for itheta in 1:8:mtheta+1 θ = psi_ys[itheta] - r2 = pe.rzphi_rsquared((ψ_target, θ)) + r2 = pe.rzphi_rsquared((ψ_target, θ)) off = pe.rzphi_offset((ψ_target, θ)) rfac = sqrt(max(r2, 0.0)) η = 2π * (θ + off) @@ -148,10 +148,10 @@ for method in methods end # ─── GSE residual summary ───────────────────────────────────────────────────── -println("\n" * "=" ^ 65) +println("\n" * "="^65) println("Grad-Shafranov residual (from equilibrium_gse! output — see gsec.h5 if diagnose_src=true)") println("(Showing q-monotonicity near edge as proxy since GSE requires diagnose_src=true)") -println("=" ^ 65) +println("="^65) for method in methods results[method]["success"] || continue @@ -167,9 +167,9 @@ for method in methods end # ─── Runtime summary ───────────────────────────────────────────────────────── -println("\n" * "=" ^ 65) +println("\n" * "="^65) println("Runtime summary") -println("=" ^ 65) +println("="^65) for method in methods if results[method]["success"] @printf(" %-22s %.3f s\n", method, results[method]["runtime"]) diff --git a/benchmarks/equil_numerical_params.jl b/benchmarks/equil_numerical_params.jl index 42c2e45fd3..93cff33b32 100644 --- a/benchmarks/equil_numerical_params.jl +++ b/benchmarks/equil_numerical_params.jl @@ -21,26 +21,26 @@ using GeneralizedPerturbedEquilibrium.Equilibrium using TOML, Printf, Statistics example_path = length(ARGS) > 0 ? ARGS[1] : joinpath(@__DIR__, "../examples/DIIID-like_ideal_example") -config_path = joinpath(example_path, "gpec.toml") +config_path = joinpath(example_path, "gpec.toml") function make_config(path, eq_type, mpsi, mtheta) raw = TOML.parsefile(path) raw["Equilibrium"]["eq_type"] = eq_type - raw["Equilibrium"]["mpsi"] = mpsi - raw["Equilibrium"]["mtheta"] = mtheta + raw["Equilibrium"]["mpsi"] = mpsi + raw["Equilibrium"]["mtheta"] = mtheta return GeneralizedPerturbedEquilibrium.equilibrium_config(raw, dirname(path)) end function roundtrip_error(pe, raw_profile) psi_xs = pe.rzphi_xs - psio = pe.psio + psio = pe.psio mtheta = length(pe.rzphi_ys) - 1 errors = Float64[] for ipsi in 1:4:length(psi_xs) ψ = psi_xs[ipsi] for itheta in 1:8:mtheta+1 θ = pe.rzphi_ys[itheta] - r2 = pe.rzphi_rsquared((ψ, θ)) + r2 = pe.rzphi_rsquared((ψ, θ)) off = pe.rzphi_offset((ψ, θ)) rfac = sqrt(max(r2, 0.0)) η = 2π * (θ + off) @@ -61,9 +61,9 @@ PlasmaEquilibrium(warmup_cfg) rows = [] # ── mpsi scan ────────────────────────────────────────────────────────────────── -println("\n" * "=" ^ 65) +println("\n" * "="^65) println("mpsi scan (mtheta=256, default reltol)") -println("=" ^ 65) +println("="^65) for method in ["efit", "efit_arclength", "efit_by_inversion"] for mpsi in [64, 128, 256] cfg = make_config(config_path, method, mpsi, 256) @@ -87,9 +87,9 @@ for method in ["efit", "efit_arclength", "efit_by_inversion"] end # ── mtheta scan ──────────────────────────────────────────────────────────────── -println("\n" * "=" ^ 65) +println("\n" * "="^65) println("mtheta scan (mpsi=128, default reltol)") -println("=" ^ 65) +println("="^65) for method in ["efit", "efit_arclength", "efit_by_inversion"] for mtheta in [128, 256, 512] cfg = make_config(config_path, method, 128, mtheta) @@ -116,17 +116,17 @@ end # Note: reltol is not exposed through EquilibriumConfig yet. # This scan patches it by temporarily modifying the solver call. # TODO: expose etol/reltol as a config parameter when needed. -println("\n" * "=" ^ 65) +println("\n" * "="^65) println("ODE reltol scan — NOTE: not yet exposed via config; using default reltol") println("(Add reltol to EquilibriumConfig and threading to enable this scan)") -println("=" ^ 65) +println("="^65) # ── Contour grid resolution scan (efit_by_inversion only) ───────────────────── # resolution_factor scales the adaptive contour grid of equilibrium_solver_by_inversion. # We call it directly here, bypassing the PlasmaEquilibrium constructor. -println("\n" * "=" ^ 65) +println("\n" * "="^65) println("Contour grid resolution scan (efit_by_inversion only, mpsi=128, mtheta=256)") -println("=" ^ 65) +println("="^65) for resolution_factor in [2.0, 4.0, 8.0] cfg = make_config(config_path, "efit_by_inversion", 128, 256) raw_input = Equilibrium.read_efit(cfg) diff --git a/benchmarks/equil_psihigh_scan.jl b/benchmarks/equil_psihigh_scan.jl index 9a8c9dc78a..2df1356d3a 100644 --- a/benchmarks/equil_psihigh_scan.jl +++ b/benchmarks/equil_psihigh_scan.jl @@ -29,7 +29,7 @@ using GeneralizedPerturbedEquilibrium.Equilibrium using TOML, Printf, Statistics, HDF5 example_path = length(ARGS) > 0 ? ARGS[1] : joinpath(@__DIR__, "../examples/DIIID-like_ideal_example") -config_path = joinpath(example_path, "gpec.toml") +config_path = joinpath(example_path, "gpec.toml") psihigh_values = [0.980, 0.985, 0.990, 0.993, 0.995, 0.996, 0.997, 0.998, 0.999, 0.9995, 0.9999, 1.0] methods = ["efit", "efit_arclength", "efit_by_inversion"] @@ -81,9 +81,9 @@ end println("JIT warmup (equilibrium + FFS)...") run_ffs_et1(config_path, "efit", 0.990) -println("=" ^ 70) +println("="^70) println("psihigh robustness scan (equilibrium + stability)") -println("=" ^ 70) +println("="^70) rows = [] @@ -122,7 +122,7 @@ for method in methods ψ = psi_xs[ipsi] for itheta in 1:8:mtheta+1 θ = pe.rzphi_ys[itheta] - r2 = pe.rzphi_rsquared((ψ, θ)) + r2 = pe.rzphi_rsquared((ψ, θ)) off = pe.rzphi_offset((ψ, θ)) rfac = sqrt(max(r2, 0.0)) η = 2π * (θ + off) @@ -160,13 +160,16 @@ for method in methods isnan(q_at_psihigh) ? -1.0 : q_at_psihigh, et1_str) - push!(rows, ( - method=method, psihigh=psihigh, success=success, runtime=runtime, - roundtrip_max_edge=roundtrip_max_edge, - q_mono_violations_edge=q_mono_violations_edge, - q_at_psihigh=q_at_psihigh, q_edge_slope=q_edge_slope, - et1=et1, error_msg=err_msg - )) + push!( + rows, + ( + method=method, psihigh=psihigh, success=success, runtime=runtime, + roundtrip_max_edge=roundtrip_max_edge, + q_mono_violations_edge=q_mono_violations_edge, + q_at_psihigh=q_at_psihigh, q_edge_slope=q_edge_slope, + et1=et1, error_msg=err_msg + ) + ) end end @@ -180,7 +183,7 @@ end println("\nResults saved to: $output_csv") # Summary -println("\n" * "=" ^ 70) +println("\n" * "="^70) println("Last successful psihigh per method:") for method in methods method_rows = filter(r -> r.method == method && r.success, rows) @@ -192,14 +195,16 @@ for method in methods end end -println("\n" * "=" ^ 70) +println("\n" * "="^70) println("et[1] vs psihigh (free-boundary stability eigenvalue):") println(" (positive = stable, negative = unstable)") @printf(" %-22s %s\n", "psihigh", join([@sprintf("%-22s", m) for m in methods])) for psihigh in psihigh_values - vals = [let r = findfirst(x -> x.method == m && x.psihigh == psihigh, rows) - r === nothing || isnan(rows[r].et1) ? " -" : @sprintf("%+.4f", rows[r].et1) - end for m in methods] + vals = [ + let r = findfirst(x -> x.method == m && x.psihigh == psihigh, rows) + r === nothing || isnan(rows[r].et1) ? " -" : @sprintf("%+.4f", rows[r].et1) + end for m in methods + ] @printf(" %-22.4f %s\n", psihigh, join([@sprintf("%-22s", v) for v in vals])) end println() diff --git a/benchmarks/equil_psilow_scan.jl b/benchmarks/equil_psilow_scan.jl index c7288949ce..f93476c680 100644 --- a/benchmarks/equil_psilow_scan.jl +++ b/benchmarks/equil_psilow_scan.jl @@ -19,7 +19,7 @@ using GeneralizedPerturbedEquilibrium.Equilibrium using TOML, Printf, Statistics example_path = length(ARGS) > 0 ? ARGS[1] : joinpath(@__DIR__, "../examples/DIIID-like_ideal_example") -config_path = joinpath(example_path, "gpec.toml") +config_path = joinpath(example_path, "gpec.toml") psilow_values = [1e-1, 5e-2, 1e-2, 5e-3, 1e-3, 5e-4, 1e-4] methods = ["efit", "efit_arclength", "efit_by_inversion"] @@ -42,7 +42,7 @@ function read_efit_header_qaxis(eq_filename) lines_per_block = cld(nw, 5) # q-profile starts after 4 header lines + 4 data blocks (fpol,pres,ffprime,pprime) + psi 2D block nh = parse(Int, header_parts[end]) - q_start = 6 + (4 + cld(nw*nh, 5)) * lines_per_block # rough; parse all blocks + q_start = 6 + (4 + cld(nw * nh, 5)) * lines_per_block # rough; parse all blocks # Simpler: just extrapolate from the first two q-values returned by our config return nothing # will use profile extrapolation below end @@ -52,9 +52,9 @@ println("JIT warmup...") warmup_cfg = make_config(config_path, "efit", 1e-2) PlasmaEquilibrium(warmup_cfg) -println("=" ^ 70) +println("="^70) println("psilow near-axis accuracy scan") -println("=" ^ 70) +println("="^70) rows = [] @@ -82,7 +82,7 @@ for method in methods raw_profile = Equilibrium.read_efit(cfg) psi_xs = pe.rzphi_xs mtheta = length(pe.rzphi_ys) - 1 - psio = pe.psio + psio = pe.psio # q0 extrapolated to axis (linear from innermost two grid points) q1 = pe.profiles.q_spline.y[1] @@ -96,7 +96,7 @@ for method in methods ψ = psi_xs[ipsi] for itheta in 1:8:mtheta+1 θ = pe.rzphi_ys[itheta] - r2 = pe.rzphi_rsquared((ψ, θ)) + r2 = pe.rzphi_rsquared((ψ, θ)) off = pe.rzphi_offset((ψ, θ)) rfac = sqrt(max(r2, 0.0)) η = 2π * (θ + off) diff --git a/benchmarks/equil_spline_comparison.jl b/benchmarks/equil_spline_comparison.jl index 8dbbb521b5..19f906c551 100644 --- a/benchmarks/equil_spline_comparison.jl +++ b/benchmarks/equil_spline_comparison.jl @@ -27,15 +27,15 @@ using TOML, Printf, Statistics, Plots # ─── CLI arguments ───────────────────────────────────────────────────────────── example_path = length(ARGS) > 0 ? ARGS[1] : - joinpath(@__DIR__, "../examples/DIIID-like_ideal_example") + joinpath(@__DIR__, "../examples/DIIID-like_ideal_example") psihigh_arg = length(ARGS) > 1 ? parse(Float64, ARGS[2]) : 0.997 -config_path = joinpath(example_path, "gpec.toml") +config_path = joinpath(example_path, "gpec.toml") -println("=" ^ 70) +println("="^70) println("Equilibrium Spline Comparison: efit / efit_arclength / efit_by_inversion") println("Example : $example_path") println("psihigh : $psihigh_arg") -println("=" ^ 70) +println("="^70) # ─── Output directory ────────────────────────────────────────────────────────── psihigh_tag = replace(string(psihigh_arg), "." => "p") @@ -46,38 +46,38 @@ println("Output : $outdir\n") # ─── Config factory ──────────────────────────────────────────────────────────── function make_config(path::String, eq_type::String, psihigh::Float64) raw = TOML.parsefile(path) - raw["Equilibrium"]["eq_type"] = eq_type + raw["Equilibrium"]["eq_type"] = eq_type raw["Equilibrium"]["psihigh"] = psihigh return GeneralizedPerturbedEquilibrium.equilibrium_config(raw, dirname(path)) end # ─── Load all three equilibria ───────────────────────────────────────────────── # efit is the reference; efit_arclength and efit_by_inversion are compared to it. -ref_method = "efit" -compare_methods = ["efit_arclength", "efit_by_inversion"] -all_methods = vcat(ref_method, compare_methods) +ref_method = "efit" +compare_methods = ["efit_arclength", "efit_by_inversion"] +all_methods = vcat(ref_method, compare_methods) method_color = Dict( - "efit" => :blue, - "efit_arclength" => :darkorange, - "efit_by_inversion" => :red, + "efit" => :blue, + "efit_arclength" => :darkorange, + "efit_by_inversion" => :red ) method_style = Dict( - "efit" => :solid, - "efit_arclength" => :dash, - "efit_by_inversion" => :dot, + "efit" => :solid, + "efit_arclength" => :dash, + "efit_by_inversion" => :dot ) method_label = Dict( - "efit" => "efit", - "efit_arclength" => "efit_arclength", - "efit_by_inversion" => "efit_by_inv", + "efit" => "efit", + "efit_arclength" => "efit_arclength", + "efit_by_inversion" => "efit_by_inv" ) diff_color = Dict( - "efit_arclength" => :darkorange, - "efit_by_inversion" => :red, + "efit_arclength" => :darkorange, + "efit_by_inversion" => :red ) -pes = Dict{String, Any}() +pes = Dict{String,Any}() for m in all_methods println("--- Running: $m ---") cfg = make_config(config_path, m, psihigh_arg) @@ -94,22 +94,26 @@ isempty(failed) || println("Skipping failed methods in comparisons: $(join(faile haskey(pes, ref_method) || error("Reference method '$ref_method' failed — cannot continue.") pe_ref = pes[ref_method] -all_methods = filter(m -> haskey(pes, m), all_methods) +all_methods = filter(m -> haskey(pes, m), all_methods) compare_methods_active = filter(m -> haskey(pes, m), compare_methods) # ─── Helpers ─────────────────────────────────────────────────────────────────── -"Convert (ψ, θ) straight-field-line coordinates → (R, Z) physical coordinates." +""" +Convert (ψ, θ) straight-field-line coordinates → (R, Z) physical coordinates. +""" function psi_theta_to_RZ(pe, psi, theta) - r2 = pe.rzphi_rsquared((psi, theta)) - off = pe.rzphi_offset((psi, theta)) + r2 = pe.rzphi_rsquared((psi, theta)) + off = pe.rzphi_offset((psi, theta)) rfac = sqrt(max(r2, 0.0)) - η = 2π * (theta + off) - R = pe.ro + rfac * cos(η) - Z = pe.zo + rfac * sin(η) + η = 2π * (theta + off) + R = pe.ro + rfac * cos(η) + Z = pe.zo + rfac * sin(η) return R, Z end -"Compute the full (R, Z) trace of each flux surface in psi_vals." +""" +Compute the full (R, Z) trace of each flux surface in psi_vals. +""" function flux_surface_RZ(pe, psi_vals, theta_range) R_all = [Float64[] for _ in 1:length(psi_vals)] Z_all = [Float64[] for _ in 1:length(psi_vals)] @@ -130,7 +134,7 @@ end psi_lo = pe_ref.rzphi_xs[1] psi_hi = pe_ref.rzphi_xs[end] mpsi_eval = 8 * (length(pe_ref.rzphi_xs) - 1) -psi_full = psi_lo .+ (psi_hi - psi_lo) .* sin.(range(0.0, 1.0; length=mpsi_eval+1) .* (π/2)).^2 +psi_full = psi_lo .+ (psi_hi - psi_lo) .* sin.(range(0.0, 1.0; length=mpsi_eval + 1) .* (π / 2)) .^ 2 mask_core = psi_full .< 0.10 mask_edge = psi_full .> 0.98 @@ -143,21 +147,21 @@ theta_colors = [:blue, :green, :darkorange, :purple] # ═══════════════════════════════════════════════════════════════════════════════ # 1. 1D PROFILE COMPARISONS # ═══════════════════════════════════════════════════════════════════════════════ -println("=" ^ 70) +println("="^70) println("1. 1D Profile Comparisons") -println("=" ^ 70) +println("="^70) profile_specs = [ - ("F (2π·R·Bₜ)", pe -> pe.profiles.F_spline), - ("μ₀·P", pe -> pe.profiles.P_spline), - ("dV/dψ", pe -> pe.profiles.dVdpsi_spline), - ("q", pe -> pe.profiles.q_spline), + ("F (2π·R·Bₜ)", pe -> pe.profiles.F_spline), + ("μ₀·P", pe -> pe.profiles.P_spline), + ("dV/dψ", pe -> pe.profiles.dVdpsi_spline), + ("q", pe -> pe.profiles.q_spline) ] profile_fnames = ["profile_F", "profile_P", "profile_dVdpsi", "profile_q"] for (idx, (pname, spl_getter)) in enumerate(profile_specs) spl_ref = spl_getter(pe_ref) - y_ref = [spl_ref(ψ) for ψ in psi_full] + y_ref = [spl_ref(ψ) for ψ in psi_full] p_full = plot(psi_full, y_ref; lw=2, color=method_color[ref_method], ls=method_style[ref_method], label=method_label[ref_method], @@ -187,8 +191,8 @@ for (idx, (pname, spl_getter)) in enumerate(profile_specs) for m in compare_methods_active spl_m = spl_getter(pes[m]) - y_m = [spl_m(ψ) for ψ in psi_full] - Δy = y_ref .- y_m + y_m = [spl_m(ψ) for ψ in psi_full] + Δy = y_ref .- y_m plot!(p_full, psi_full, y_m; lw=1.5, color=method_color[m], ls=method_style[m], label=method_label[m]) @@ -227,76 +231,76 @@ z_global_grid = Float64[] r_zoom_grid_vis = Float64[] z_zoom_grid_vis = Float64[] if haskey(pes, "efit_by_inversion") -let - global r_global_grid, z_global_grid, r_zoom_grid_vis, z_zoom_grid_vis - cfg_inv = make_config(config_path, "efit_by_inversion", psihigh_arg) - raw_inv = Equilibrium.read_efit(cfg_inv) - ro_g, zo_g = pes["efit_by_inversion"].ro, pes["efit_by_inversion"].zo - psio_g = raw_inv.psio - psilow_g = cfg_inv.psilow - topology_g = Equilibrium.classify_topology(raw_inv, psio_g) - - ψ_RR = abs(raw_inv.psi_in((ro_g, zo_g); deriv=DerivOp(2, 0))) - ψ_ZZ = abs(raw_inv.psi_in((ro_g, zo_g); deriv=DerivOp(0, 2))) - a_low_g = min(sqrt(2 * psilow_g * psio_g / ψ_RR), sqrt(2 * psilow_g * psio_g / ψ_ZZ)) - - nw_g = length(raw_inv.psi_in_xs) - nh_g = length(raw_inv.psi_in_ys) - β_r_g, β_z_g = 2.0, 2.0 - refine_g = 5 - - nr_g = max(4, round(Int, refine_g * nw_g)) - nz_g = max(4, round(Int, refine_g * nh_g)) - r_g = Equilibrium.make_stretched_r_grid(raw_inv.rmin, raw_inv.rmax, ro_g, nr_g, β_r_g) - z_g = Equilibrium.make_stretched_z_grid(raw_inv.zmin, raw_inv.zmax, zo_g, nz_g, topology_g, β_z_g) - - iro_g = clamp(searchsortedfirst(r_g, ro_g), 2, length(r_g)) - izo_g = clamp(searchsortedfirst(z_g, zo_g), 2, length(z_g)) - dR_ax = r_g[iro_g] - r_g[iro_g - 1] - dZ_ax = z_g[izo_g] - z_g[izo_g - 1] - sc_r = max(1, ceil(Int, dR_ax / (0.2 * a_low_g))) - sc_z = max(1, ceil(Int, dZ_ax / (0.2 * a_low_g))) - if sc_r > 1 - nr_g = (nr_g - 1) * sc_r + 1 - r_g = Equilibrium.make_stretched_r_grid(raw_inv.rmin, raw_inv.rmax, ro_g, nr_g, β_r_g) + let + global r_global_grid, z_global_grid, r_zoom_grid_vis, z_zoom_grid_vis + cfg_inv = make_config(config_path, "efit_by_inversion", psihigh_arg) + raw_inv = Equilibrium.read_efit(cfg_inv) + ro_g, zo_g = pes["efit_by_inversion"].ro, pes["efit_by_inversion"].zo + psio_g = raw_inv.psio + psilow_g = cfg_inv.psilow + topology_g = Equilibrium.classify_topology(raw_inv, psio_g) + + ψ_RR = abs(raw_inv.psi_in((ro_g, zo_g); deriv=DerivOp(2, 0))) + ψ_ZZ = abs(raw_inv.psi_in((ro_g, zo_g); deriv=DerivOp(0, 2))) + a_low_g = min(sqrt(2 * psilow_g * psio_g / ψ_RR), sqrt(2 * psilow_g * psio_g / ψ_ZZ)) + + nw_g = length(raw_inv.psi_in_xs) + nh_g = length(raw_inv.psi_in_ys) + β_r_g, β_z_g = 2.0, 2.0 + refine_g = 5 + + nr_g = max(4, round(Int, refine_g * nw_g)) + nz_g = max(4, round(Int, refine_g * nh_g)) + r_g = Equilibrium.make_stretched_r_grid(raw_inv.rmin, raw_inv.rmax, ro_g, nr_g, β_r_g) + z_g = Equilibrium.make_stretched_z_grid(raw_inv.zmin, raw_inv.zmax, zo_g, nz_g, topology_g, β_z_g) + iro_g = clamp(searchsortedfirst(r_g, ro_g), 2, length(r_g)) - dR_ax = r_g[iro_g] - r_g[iro_g - 1] - end - if sc_z > 1 - nz_g = (nz_g - 1) * sc_z + 1 - z_g = Equilibrium.make_stretched_z_grid(raw_inv.zmin, raw_inv.zmax, zo_g, nz_g, topology_g, β_z_g) izo_g = clamp(searchsortedfirst(z_g, zo_g), 2, length(z_g)) - dZ_ax = z_g[izo_g] - z_g[izo_g - 1] + dR_ax = r_g[iro_g] - r_g[iro_g-1] + dZ_ax = z_g[izo_g] - z_g[izo_g-1] + sc_r = max(1, ceil(Int, dR_ax / (0.2 * a_low_g))) + sc_z = max(1, ceil(Int, dZ_ax / (0.2 * a_low_g))) + if sc_r > 1 + nr_g = (nr_g - 1) * sc_r + 1 + r_g = Equilibrium.make_stretched_r_grid(raw_inv.rmin, raw_inv.rmax, ro_g, nr_g, β_r_g) + iro_g = clamp(searchsortedfirst(r_g, ro_g), 2, length(r_g)) + dR_ax = r_g[iro_g] - r_g[iro_g-1] + end + if sc_z > 1 + nz_g = (nz_g - 1) * sc_z + 1 + z_g = Equilibrium.make_stretched_z_grid(raw_inv.zmin, raw_inv.zmax, zo_g, nz_g, topology_g, β_z_g) + izo_g = clamp(searchsortedfirst(z_g, zo_g), 2, length(z_g)) + dZ_ax = z_g[izo_g] - z_g[izo_g-1] + end + r_global_grid = r_g + z_global_grid = z_g + + d_max_g = max(dR_ax, dZ_ax) + threshold_g = (10 * d_max_g)^2 * max(ψ_RR, ψ_ZZ) / (2 * psio_g) + psi_zoom_max = 9 * threshold_g + a_zoom_r = sqrt(2 * psi_zoom_max * psio_g / ψ_RR) * 1.2 + a_zoom_z = sqrt(2 * psi_zoom_max * psio_g / ψ_ZZ) * 1.2 + sinh_β = sinh(β_r_g) + nr_z = max(200, ceil(Int, 8 * β_r_g * a_zoom_r * 10 / (sinh_β * a_low_g))) + nz_z = max(200, ceil(Int, 8 * β_r_g * a_zoom_z * 10 / (sinh_β * a_low_g))) + r_zoom_grid_vis = Equilibrium.make_stretched_r_grid( + max(raw_inv.rmin, ro_g - a_zoom_r), min(raw_inv.rmax, ro_g + a_zoom_r), + ro_g, nr_z, β_r_g) + z_zoom_grid_vis = Equilibrium.make_stretched_r_grid( + max(raw_inv.zmin, zo_g - a_zoom_z), min(raw_inv.zmax, zo_g + a_zoom_z), + zo_g, nz_z, β_r_g) + println(" Grid vis: global $(nr_g)×$(nz_g), zoom $(nr_z)×$(nz_z)") end - r_global_grid = r_g - z_global_grid = z_g - - d_max_g = max(dR_ax, dZ_ax) - threshold_g = (10 * d_max_g)^2 * max(ψ_RR, ψ_ZZ) / (2 * psio_g) - psi_zoom_max = 9 * threshold_g - a_zoom_r = sqrt(2 * psi_zoom_max * psio_g / ψ_RR) * 1.2 - a_zoom_z = sqrt(2 * psi_zoom_max * psio_g / ψ_ZZ) * 1.2 - sinh_β = sinh(β_r_g) - nr_z = max(200, ceil(Int, 8 * β_r_g * a_zoom_r * 10 / (sinh_β * a_low_g))) - nz_z = max(200, ceil(Int, 8 * β_r_g * a_zoom_z * 10 / (sinh_β * a_low_g))) - r_zoom_grid_vis = Equilibrium.make_stretched_r_grid( - max(raw_inv.rmin, ro_g - a_zoom_r), min(raw_inv.rmax, ro_g + a_zoom_r), - ro_g, nr_z, β_r_g) - z_zoom_grid_vis = Equilibrium.make_stretched_r_grid( - max(raw_inv.zmin, zo_g - a_zoom_z), min(raw_inv.zmax, zo_g + a_zoom_z), - zo_g, nz_z, β_r_g) - println(" Grid vis: global $(nr_g)×$(nz_g), zoom $(nr_z)×$(nz_z)") -end end # haskey(pes, "efit_by_inversion") # ═══════════════════════════════════════════════════════════════════════════════ # 2. FLUX SURFACE CONTOUR OVERLAYS # ═══════════════════════════════════════════════════════════════════════════════ -println("\n" * "=" ^ 70) +println("\n" * "="^70) println("2. Flux Surface Contour Overlays") -println("=" ^ 70) +println("="^70) -psi_contours = collect(range(psi_lo, psi_hi; length=20)) +psi_contours = collect(range(psi_lo, psi_hi; length=20)) theta_contour = range(0.0, 1.0; length=256) R_fs = Dict(m => flux_surface_RZ(pes[m], psi_contours, theta_contour) for m in all_methods) @@ -305,7 +309,7 @@ p_ctr_full = plot(; aspect_ratio=:equal, xlabel="R [m]", ylabel="Z [m]", title="Flux surfaces: all methods (psihigh=$psihigh_arg)") p_ctr_core = plot(; aspect_ratio=:equal, xlabel="R [m]", ylabel="Z [m]", title="Deep core zoom (innermost 4 surfaces)") -p_ctr_xpt = plot(; aspect_ratio=:equal, xlabel="R [m]", ylabel="Z [m]", +p_ctr_xpt = plot(; aspect_ratio=:equal, xlabel="R [m]", ylabel="Z [m]", title="Far edge / x-point zoom (outermost 3 surfaces)") for m in all_methods @@ -322,7 +326,7 @@ for m in all_methods ls=method_style[m], lw=1.5, label=lbl) end n_outer = length(psi_contours) - for k in (n_outer - 2):n_outer + for k in (n_outer-2):n_outer lbl = k == n_outer - 2 ? method_label[m] : "" plot!(p_ctr_xpt, Rs[k], Zs[k]; color=method_color[m], ls=method_style[m], lw=1.5, label=lbl) @@ -332,45 +336,47 @@ end # Grid overplots: scatter subsampled grid points on each panel (only if efit_by_inversion ran). # Global grid: every 20th R × 12th Z (grey). Zoom grid: every 3rd R × 3rd Z (darkgrey). if !isempty(r_global_grid) -let - step_rg, step_zg = 20, 12 - Rg = [r_global_grid[ir] for ir in 1:step_rg:length(r_global_grid) - for _ in 1:step_zg:length(z_global_grid)] - Zg = [z_global_grid[iz] for _ in 1:step_rg:length(r_global_grid) - for iz in 1:step_zg:length(z_global_grid)] - scatter!(p_ctr_full, Rg, Zg; ms=0.8, color=:grey, alpha=0.25, - markerstrokewidth=0, label="global grid") - - step_rz, step_zz = 3, 3 - Rz = [r_zoom_grid_vis[ir] for ir in 1:step_rz:length(r_zoom_grid_vis) - for _ in 1:step_zz:length(z_zoom_grid_vis)] - Zz = [z_zoom_grid_vis[iz] for _ in 1:step_rz:length(r_zoom_grid_vis) - for iz in 1:step_zz:length(z_zoom_grid_vis)] - scatter!(p_ctr_full, Rz, Zz; ms=0.8, color=:darkgrey, alpha=0.4, - markerstrokewidth=0, label="zoom grid") - - # Core panel: show both grids at full density within the core zoom region - scatter!(p_ctr_core, Rg, Zg; ms=1.2, color=:grey, alpha=0.3, - markerstrokewidth=0, label="global grid") - scatter!(p_ctr_core, Rz, Zz; ms=1.2, color=:darkgrey, alpha=0.5, - markerstrokewidth=0, label="zoom grid") - - # X-point panel: global grid only (zoom doesn't extend there) - scatter!(p_ctr_xpt, Rg, Zg; ms=1.2, color=:grey, alpha=0.3, - markerstrokewidth=0, label="global grid") -end + let + step_rg, step_zg = 20, 12 + Rg = [r_global_grid[ir] for ir in 1:step_rg:length(r_global_grid) + for _ in 1:step_zg:length(z_global_grid)] + Zg = [z_global_grid[iz] for _ in 1:step_rg:length(r_global_grid) + for iz in 1:step_zg:length(z_global_grid)] + scatter!(p_ctr_full, Rg, Zg; ms=0.8, color=:grey, alpha=0.25, + markerstrokewidth=0, label="global grid") + + step_rz, step_zz = 3, 3 + Rz = [r_zoom_grid_vis[ir] for ir in 1:step_rz:length(r_zoom_grid_vis) + for _ in 1:step_zz:length(z_zoom_grid_vis)] + Zz = [z_zoom_grid_vis[iz] for _ in 1:step_rz:length(r_zoom_grid_vis) + for iz in 1:step_zz:length(z_zoom_grid_vis)] + scatter!(p_ctr_full, Rz, Zz; ms=0.8, color=:darkgrey, alpha=0.4, + markerstrokewidth=0, label="zoom grid") + + # Core panel: show both grids at full density within the core zoom region + scatter!(p_ctr_core, Rg, Zg; ms=1.2, color=:grey, alpha=0.3, + markerstrokewidth=0, label="global grid") + scatter!(p_ctr_core, Rz, Zz; ms=1.2, color=:darkgrey, alpha=0.5, + markerstrokewidth=0, label="zoom grid") + + # X-point panel: global grid only (zoom doesn't extend there) + scatter!(p_ctr_xpt, Rg, Zg; ms=1.2, color=:grey, alpha=0.3, + markerstrokewidth=0, label="global grid") + end end # !isempty(r_global_grid) # Set zoom limits for core and x-point panels using the reference method Rc, Zc = R_fs[ref_method] n_core = 4 -all_R_c = vcat(Rc[1:n_core]...); all_Z_c = vcat(Zc[1:n_core]...) +all_R_c = vcat(Rc[1:n_core]...); +all_Z_c = vcat(Zc[1:n_core]...); pad = 0.03 xlims!(p_ctr_core, minimum(all_R_c) - pad, maximum(all_R_c) + pad) ylims!(p_ctr_core, minimum(all_Z_c) - pad, maximum(all_Z_c) + pad) n_outer = length(psi_contours) -all_R_e = vcat(Rc[(n_outer-2):n_outer]...); all_Z_e = vcat(Zc[(n_outer-2):n_outer]...) +all_R_e = vcat(Rc[(n_outer-2):n_outer]...); +all_Z_e = vcat(Zc[(n_outer-2):n_outer]...); xpt_idx = argmin(all_Z_e) xlims!(p_ctr_xpt, all_R_e[xpt_idx] - 0.20, all_R_e[xpt_idx] + 0.20) ylims!(p_ctr_xpt, all_Z_e[xpt_idx] - 0.05, all_Z_e[xpt_idx] + 0.40) @@ -388,7 +394,7 @@ p_tht_full = plot(; aspect_ratio=:equal, xlabel="R [m]", ylabel="Z [m]", title="θ = const lines: all methods (psihigh=$psihigh_arg)") p_tht_core = plot(; aspect_ratio=:equal, xlabel="R [m]", ylabel="Z [m]", title="Deep core zoom") -p_tht_xpt = plot(; aspect_ratio=:equal, xlabel="R [m]", ylabel="Z [m]", +p_tht_xpt = plot(; aspect_ratio=:equal, xlabel="R [m]", ylabel="Z [m]", title="Far edge / x-point zoom") for m in all_methods @@ -402,14 +408,14 @@ for m in all_methods Zs = [psi_theta_to_RZ(pe, ψ, θ)[2] for ψ in psi_dense] plot!(p_tht_full, Rs, Zs; color=method_color[m], ls=method_style[m], lw=0.9, alpha=0.7, label=lbl) plot!(p_tht_core, Rs, Zs; color=method_color[m], ls=method_style[m], lw=1.5, label=lbl) - plot!(p_tht_xpt, Rs, Zs; color=method_color[m], ls=method_style[m], lw=1.5, label=lbl) + plot!(p_tht_xpt, Rs, Zs; color=method_color[m], ls=method_style[m], lw=1.5, label=lbl) end # Grid dots at every (ψ_i, θ_j) intersection — by construction on the lines above Rg = [psi_theta_to_RZ(pe, ψ, θ)[1] for ψ in pe.rzphi_xs for θ in θ_sub] Zg = [psi_theta_to_RZ(pe, ψ, θ)[2] for ψ in pe.rzphi_xs for θ in θ_sub] scatter!(p_tht_full, Rg, Zg; ms=1.5, color=method_color[m], alpha=0.5, markerstrokewidth=0, label="") scatter!(p_tht_core, Rg, Zg; ms=2.0, color=method_color[m], alpha=0.6, markerstrokewidth=0, label="") - scatter!(p_tht_xpt, Rg, Zg; ms=2.0, color=method_color[m], alpha=0.6, markerstrokewidth=0, label="") + scatter!(p_tht_xpt, Rg, Zg; ms=2.0, color=method_color[m], alpha=0.6, markerstrokewidth=0, label="") end xlims!(p_tht_core, minimum(all_R_c) - pad, maximum(all_R_c) + pad) @@ -425,21 +431,21 @@ println(" Saved flux contour figures.") # ═══════════════════════════════════════════════════════════════════════════════ # 3. 2D RZPHI GEOMETRIC SPLINES AT SELECT THETA SLICES # ═══════════════════════════════════════════════════════════════════════════════ -println("\n" * "=" ^ 70) +println("\n" * "="^70) println("3. 2D rzphi Geometric Spline Profiles at Select θ Values") -println("=" ^ 70) +println("="^70) rzphi_specs = [ - ("r²=(R-R₀)²+(Z-Z₀)²", pe -> ((ψ, θ) -> pe.rzphi_rsquared((ψ, θ)))), + ("r²=(R-R₀)²+(Z-Z₀)²", pe -> ((ψ, θ) -> pe.rzphi_rsquared((ψ, θ)))), ("angle offset η/2π−θ", pe -> ((ψ, θ) -> pe.rzphi_offset((ψ, θ)))), - ("ν (toroidal shift)", pe -> ((ψ, θ) -> pe.rzphi_nu((ψ, θ)))), - ("Jacobian", pe -> ((ψ, θ) -> pe.rzphi_jac((ψ, θ)))), + ("ν (toroidal shift)", pe -> ((ψ, θ) -> pe.rzphi_nu((ψ, θ)))), + ("Jacobian", pe -> ((ψ, θ) -> pe.rzphi_jac((ψ, θ)))) ] rzphi_fnames = ["rzphi_rsquared", "rzphi_angle_offset", "rzphi_nu", "rzphi_jacobian"] for (sidx, (sname, fn_getter)) in enumerate(rzphi_specs) - fn_ref = fn_getter(pe_ref) - yd_all = [[fn_ref(ψ, θ) for ψ in psi_full] for θ in theta_select] + fn_ref = fn_getter(pe_ref) + yd_all = [[fn_ref(ψ, θ) for ψ in psi_full] for θ in theta_select] p_full = plot(; xlabel="ψ", ylabel=sname, legend=:best, xlims=(0, 1), title="$sname — full domain (psihigh=$psihigh_arg)") @@ -454,20 +460,25 @@ for (sidx, (sname, fn_getter)) in enumerate(rzphi_specs) plot!(p_full, psi_full, yd; color=tc, lw=2, label="efit θ=$(θ)") plot!(p_core, psi_full[mask_core], yd[mask_core]; color=tc, lw=2, label="efit θ=$(θ)") plot!(p_edge, t_edge_full, yd[mask_edge]; color=tc, lw=2, label="efit θ=$(θ)") - xs = pe_ref.rzphi_xs; ys = [fn_ref(ψ, θ) for ψ in xs] + xs = pe_ref.rzphi_xs + ys = [fn_ref(ψ, θ) for ψ in xs] scatter!(p_full, xs, ys; ms=3, markerstrokewidth=0, color=tc, label="") - let mask = xs .< 0.10; scatter!(p_core, xs[mask], ys[mask]; ms=3, markerstrokewidth=0, color=tc, label="") end - let mask = xs .> 0.98; scatter!(p_edge, -log.(1.0 .- xs[mask]), ys[mask]; ms=3, markerstrokewidth=0, color=tc, label="") end + let mask = xs .< 0.10 + scatter!(p_core, xs[mask], ys[mask]; ms=3, markerstrokewidth=0, color=tc, label="") + end + let mask = xs .> 0.98 + scatter!(p_edge, -log.(1.0 .- xs[mask]), ys[mask]; ms=3, markerstrokewidth=0, color=tc, label="") + end end println(" $sname:") for m in compare_methods_active - fn_m = fn_getter(pes[m]) + fn_m = fn_getter(pes[m]) yi_all = [[fn_m(ψ, θ) for ψ in psi_full] for θ in theta_select] for (tidx, θ) in enumerate(theta_select) tc = theta_colors[tidx] yi = yi_all[tidx] - Δ = yd_all[tidx] .- yi + Δ = yd_all[tidx] .- yi plot!(p_full, psi_full, yi; color=tc, lw=1.2, ls=method_style[m], label="$(method_label[m]) θ=$(θ)") plot!(p_core, psi_full[mask_core], yi[mask_core]; color=tc, lw=1.2, @@ -476,10 +487,15 @@ for (sidx, (sname, fn_getter)) in enumerate(rzphi_specs) ls=method_style[m], label="$(method_label[m]) θ=$(θ)") plot!(p_diff, psi_full, Δ; color=tc, lw=1.2, ls=method_style[m], label="(efit−$(method_label[m])) θ=$(θ)") - xs = pes[m].rzphi_xs; ys = [fn_m(ψ, θ) for ψ in xs] + xs = pes[m].rzphi_xs + ys = [fn_m(ψ, θ) for ψ in xs] scatter!(p_full, xs, ys; ms=3, markerstrokewidth=0, color=tc, label="") - let mask = xs .< 0.10; scatter!(p_core, xs[mask], ys[mask]; ms=3, markerstrokewidth=0, color=tc, label="") end - let mask = xs .> 0.98; scatter!(p_edge, -log.(1.0 .- xs[mask]), ys[mask]; ms=3, markerstrokewidth=0, color=tc, label="") end + let mask = xs .< 0.10 + scatter!(p_core, xs[mask], ys[mask]; ms=3, markerstrokewidth=0, color=tc, label="") + end + let mask = xs .> 0.98 + scatter!(p_edge, -log.(1.0 .- xs[mask]), ys[mask]; ms=3, markerstrokewidth=0, color=tc, label="") + end Δc = any(mask_core) ? Δ[mask_core] : [0.0] Δe = any(mask_edge) ? Δ[mask_edge] : [0.0] @@ -496,20 +512,20 @@ end # ═══════════════════════════════════════════════════════════════════════════════ # 4. EQFUN PHYSICS SPLINES AT SELECT THETA SLICES # ═══════════════════════════════════════════════════════════════════════════════ -println("\n" * "=" ^ 70) +println("\n" * "="^70) println("4. Physics (eqfun) Spline Profiles at Select θ Values") -println("=" ^ 70) +println("="^70) eqfun_specs = [ - ("|B| (total field [T])", pe -> ((ψ, θ) -> pe.eqfun_B((ψ, θ)))), + ("|B| (total field [T])", pe -> ((ψ, θ) -> pe.eqfun_B((ψ, θ)))), ("metric1 (e₁·e₂+q·e₃·e₁)/JB²", pe -> ((ψ, θ) -> pe.eqfun_metric1((ψ, θ)))), - ("metric2 (e₂·e₃+q·e₃²)/JB²", pe -> ((ψ, θ) -> pe.eqfun_metric2((ψ, θ)))), + ("metric2 (e₂·e₃+q·e₃²)/JB²", pe -> ((ψ, θ) -> pe.eqfun_metric2((ψ, θ)))) ] eqfun_fnames = ["eqfun_Bmag", "eqfun_metric1", "eqfun_metric2"] for (eidx, (ename, fn_getter)) in enumerate(eqfun_specs) - fn_ref = fn_getter(pe_ref) - yd_all = [[fn_ref(ψ, θ) for ψ in psi_full] for θ in theta_select] + fn_ref = fn_getter(pe_ref) + yd_all = [[fn_ref(ψ, θ) for ψ in psi_full] for θ in theta_select] p_full = plot(; xlabel="ψ", ylabel=ename, legend=:best, xlims=(0, 1), title="$ename — full domain (psihigh=$psihigh_arg)") @@ -524,20 +540,25 @@ for (eidx, (ename, fn_getter)) in enumerate(eqfun_specs) plot!(p_full, psi_full, yd; color=tc, lw=2, label="efit θ=$(θ)") plot!(p_core, psi_full[mask_core], yd[mask_core]; color=tc, lw=2, label="efit θ=$(θ)") plot!(p_edge, t_edge_full, yd[mask_edge]; color=tc, lw=2, label="efit θ=$(θ)") - xs = pe_ref.rzphi_xs; ys = [fn_ref(ψ, θ) for ψ in xs] + xs = pe_ref.rzphi_xs + ys = [fn_ref(ψ, θ) for ψ in xs] scatter!(p_full, xs, ys; ms=3, markerstrokewidth=0, color=tc, label="") - let mask = xs .< 0.10; scatter!(p_core, xs[mask], ys[mask]; ms=3, markerstrokewidth=0, color=tc, label="") end - let mask = xs .> 0.98; scatter!(p_edge, -log.(1.0 .- xs[mask]), ys[mask]; ms=3, markerstrokewidth=0, color=tc, label="") end + let mask = xs .< 0.10 + scatter!(p_core, xs[mask], ys[mask]; ms=3, markerstrokewidth=0, color=tc, label="") + end + let mask = xs .> 0.98 + scatter!(p_edge, -log.(1.0 .- xs[mask]), ys[mask]; ms=3, markerstrokewidth=0, color=tc, label="") + end end println(" $ename:") for m in compare_methods_active - fn_m = fn_getter(pes[m]) + fn_m = fn_getter(pes[m]) yi_all = [[fn_m(ψ, θ) for ψ in psi_full] for θ in theta_select] for (tidx, θ) in enumerate(theta_select) tc = theta_colors[tidx] yi = yi_all[tidx] - Δ = yd_all[tidx] .- yi + Δ = yd_all[tidx] .- yi plot!(p_full, psi_full, yi; color=tc, lw=1.2, ls=method_style[m], label="$(method_label[m]) θ=$(θ)") plot!(p_core, psi_full[mask_core], yi[mask_core]; color=tc, lw=1.2, @@ -546,10 +567,15 @@ for (eidx, (ename, fn_getter)) in enumerate(eqfun_specs) ls=method_style[m], label="$(method_label[m]) θ=$(θ)") plot!(p_diff, psi_full, Δ; color=tc, lw=1.2, ls=method_style[m], label="(efit−$(method_label[m])) θ=$(θ)") - xs = pes[m].rzphi_xs; ys = [fn_m(ψ, θ) for ψ in xs] + xs = pes[m].rzphi_xs + ys = [fn_m(ψ, θ) for ψ in xs] scatter!(p_full, xs, ys; ms=3, markerstrokewidth=0, color=tc, label="") - let mask = xs .< 0.10; scatter!(p_core, xs[mask], ys[mask]; ms=3, markerstrokewidth=0, color=tc, label="") end - let mask = xs .> 0.98; scatter!(p_edge, -log.(1.0 .- xs[mask]), ys[mask]; ms=3, markerstrokewidth=0, color=tc, label="") end + let mask = xs .< 0.10 + scatter!(p_core, xs[mask], ys[mask]; ms=3, markerstrokewidth=0, color=tc, label="") + end + let mask = xs .> 0.98 + scatter!(p_edge, -log.(1.0 .- xs[mask]), ys[mask]; ms=3, markerstrokewidth=0, color=tc, label="") + end Δc = any(mask_core) ? Δ[mask_core] : [0.0] Δe = any(mask_edge) ? Δ[mask_edge] : [0.0] @@ -566,9 +592,9 @@ end # ═══════════════════════════════════════════════════════════════════════════════ # 5. SUMMARY # ═══════════════════════════════════════════════════════════════════════════════ -println("\n" * "=" ^ 70) +println("\n" * "="^70) println("Summary: axis / edge parameter comparison") -println("=" ^ 70) +println("="^70) col_labels = [method_label[m] for m in all_methods] @printf(" %-22s %s\n", "Parameter", join([@sprintf("%18s", l) for l in col_labels])) @@ -580,13 +606,13 @@ function row_int(label, vals) @printf(" %-22s %s\n", label, join([@sprintf("%18d", v) for v in vals])) end -row("ro [m]", [pes[m].ro for m in all_methods]) -row("zo [m]", [pes[m].zo for m in all_methods]) -row("psio [Wb/rad]",[pes[m].psio for m in all_methods]) -row("psilow", [pes[m].rzphi_xs[1] for m in all_methods]) -row("psihigh", [pes[m].rzphi_xs[end] for m in all_methods]) -row("q(psilow)", [pes[m].profiles.q_spline(pes[m].rzphi_xs[1]) for m in all_methods]) -row("q(psihigh)", [pes[m].profiles.q_spline(pes[m].rzphi_xs[end]) for m in all_methods]) +row("ro [m]", [pes[m].ro for m in all_methods]) +row("zo [m]", [pes[m].zo for m in all_methods]) +row("psio [Wb/rad]", [pes[m].psio for m in all_methods]) +row("psilow", [pes[m].rzphi_xs[1] for m in all_methods]) +row("psihigh", [pes[m].rzphi_xs[end] for m in all_methods]) +row("q(psilow)", [pes[m].profiles.q_spline(pes[m].rzphi_xs[1]) for m in all_methods]) +row("q(psihigh)", [pes[m].profiles.q_spline(pes[m].rzphi_xs[end]) for m in all_methods]) q_profiles = Dict(m => [pes[m].profiles.q_spline(ψ) for ψ in psi_full] for m in all_methods) row_int("q non-mono (edge)", diff --git a/examples/DIIID-like_ideal_example/analyze_example.jl b/examples/DIIID-like_ideal_example/analyze_example.jl index a3dc74bd29..1a3db2eb16 100644 --- a/examples/DIIID-like_ideal_example/analyze_example.jl +++ b/examples/DIIID-like_ideal_example/analyze_example.jl @@ -41,7 +41,7 @@ function symmetric_clims(z) end geqdsk_cases = [ - "TkMkr_D3Dlike_Hmode.geqdsk", + "TkMkr_D3Dlike_Hmode.geqdsk" ] base_toml = TOML.parsefile(joinpath(@__DIR__, "gpec.toml")) diff --git a/examples/DIIID-like_ideal_example_IMAS/run_imas_example.jl b/examples/DIIID-like_ideal_example_IMAS/run_imas_example.jl index eea4c4bbe1..9671b0bfa9 100644 --- a/examples/DIIID-like_ideal_example_IMAS/run_imas_example.jl +++ b/examples/DIIID-like_ideal_example_IMAS/run_imas_example.jl @@ -52,8 +52,8 @@ try config_imas = merge(SHARED_CONFIG, Dict("Equilibrium" => merge(EQ_COMMON, Dict("eq_type" => "imas", "eq_filename" => "from_dd", "imas_cocos" => 11)))) open(joinpath(tmpdir_imas, "gpec.toml"), "w") do io - ; - TOML.print(io, config_imas); + + TOML.print(io, config_imas) end result_imas = GPEC.main([tmpdir_imas]; dd=dd) global et_imas = real(result_imas.ffs.free_boundary.et[1]) @@ -71,8 +71,8 @@ try config_gfile = merge(SHARED_CONFIG, Dict("Equilibrium" => merge(EQ_COMMON, Dict("eq_type" => "efit", "eq_filename" => GEQDSK)))) open(joinpath(tmpdir_gfile, "gpec.toml"), "w") do io - ; - TOML.print(io, config_gfile); + + TOML.print(io, config_gfile) end result_gfile = GPEC.main([tmpdir_gfile]) global et_gfile = real(result_gfile.ffs.free_boundary.et[1]) diff --git a/examples/LAR_beta_scan/run_scan.jl b/examples/LAR_beta_scan/run_scan.jl index 12eb1c2fd6..f0faf52fd5 100644 --- a/examples/LAR_beta_scan/run_scan.jl +++ b/examples/LAR_beta_scan/run_scan.jl @@ -30,11 +30,11 @@ using Printf # is approximately uniform over most of the range and smoothly tightens as # the pole is approached, giving an even visual cadence without wasting # points on the flat-slope region far from the pole. -function _warped_grid(x_start::Float64, x_end::Float64, N::Int; p::Float64 = 2.0) +function _warped_grid(x_start::Float64, x_end::Float64, N::Int; p::Float64=2.0) return [x_start + (x_end - x_start) * (1 - (1 - i / (N - 1))^p) for i in 0:N-1] end -const PC_FULL = _warped_grid(0.001, 0.1735, 40; p = 2.0) +const PC_FULL = _warped_grid(0.001, 0.1735, 40; p=2.0) const PC_TEST = [0.001, 0.10, 0.17] @@ -57,12 +57,14 @@ function run_single(pc::Float64) # Per-point gpec.toml = baseline gpec.toml with TJAnalytic.p0 overridden. config = deepcopy(GPEC_BASE) config["TJAnalytic"]["p0"] = pc - open(joinpath(run_dir, "gpec.toml"), "w") do io; TOML.print(io, config); end + open(joinpath(run_dir, "gpec.toml"), "w") do io + TOML.print(io, config) + end GeneralizedPerturbedEquilibrium.main([run_dir]) return extract_results(joinpath(run_dir, "gpec.h5")) catch e - @warn "Failed for pc=$pc" exception=(e, catch_backtrace()) + @warn "Failed for pc=$pc" exception = (e, catch_backtrace()) return nothing finally rm(run_dir; force=true, recursive=true) @@ -72,24 +74,32 @@ end function extract_results(h5_path::String) h5open(h5_path, "r") do f fbs = "ForceFreeStates/FreeBoundaryStability" - ep = read(f, "$fbs/eigenmode_plasma_energies"); ev = read(f, "$fbs/eigenmode_vacuum_energies"); et = read(f, "$fbs/eigenmode_energies") + ep = read(f, "$fbs/eigenmode_plasma_energies") + ev = read(f, "$fbs/eigenmode_vacuum_energies") + et = read(f, "$fbs/eigenmode_energies") msing = read(f, "SingularSurfaces/rational_count") m_sing = read(f, "SingularSurfaces/rational_m") dp_mat = haskey(f, "SingularSurfaces/Delta_prime_matrix") ? read(f, "SingularSurfaces/Delta_prime_matrix") : nothing qlim = haskey(f, "Info/qlim") ? read(f, "Info/qlim") : read(f, "Equilibrium/q_max") - q0 = read(f, "Equilibrium/q_axis"); qmax = read(f, "Equilibrium/q_max") + q0 = read(f, "Equilibrium/q_axis") + qmax = read(f, "Equilibrium/q_max") - dp_21 = NaN + NaN*im; dp_31 = NaN + NaN*im + dp_21 = NaN + NaN * im + dp_31 = NaN + NaN * im if dp_mat !== nothing && msing > 0 for s in 1:min(msing, size(dp_mat, 1)) m_val = size(m_sing, 1) == msing ? m_sing[s, 1] : m_sing[1, s] - if m_val == 2; dp_21 = dp_mat[s, s]; end - if m_val == 3; dp_31 = dp_mat[s, s]; end + if m_val == 2 + dp_21 = dp_mat[s, s] + end + if m_val == 3 + dp_31 = dp_mat[s, s] + end end end return (dp_21=dp_21, dp_31=dp_31, - dW_plasma=real(ep[1]), dW_vacuum=real(ev[1]), dW_total=real(et[1]), - q0=q0, qmax=qmax, qlim=qlim, msing=msing, dp_matrix=dp_mat) + dW_plasma=real(ep[1]), dW_vacuum=real(ev[1]), dW_total=real(et[1]), + q0=q0, qmax=qmax, qlim=qlim, msing=msing, dp_matrix=dp_mat) end end @@ -116,11 +126,20 @@ function main() haskey(f, gname) && delete_object(f, gname) g = create_group(f, gname) g["pressure_factor"] = pc - g["dp_21_real"] = real(result.dp_21); g["dp_21_imag"] = imag(result.dp_21) - g["dp_31_real"] = real(result.dp_31); g["dp_31_imag"] = imag(result.dp_31) - g["dW_plasma"] = result.dW_plasma; g["dW_vacuum"] = result.dW_vacuum; g["dW_total"] = result.dW_total - g["q0"] = result.q0; g["qmax"] = result.qmax; g["qlim"] = result.qlim; g["msing"] = result.msing - if result.dp_matrix !== nothing; g["dp_matrix"] = result.dp_matrix; end + g["dp_21_real"] = real(result.dp_21) + g["dp_21_imag"] = imag(result.dp_21) + g["dp_31_real"] = real(result.dp_31) + g["dp_31_imag"] = imag(result.dp_31) + g["dW_plasma"] = result.dW_plasma + g["dW_vacuum"] = result.dW_vacuum + g["dW_total"] = result.dW_total + g["q0"] = result.q0 + g["qmax"] = result.qmax + g["qlim"] = result.qlim + g["msing"] = result.msing + if result.dp_matrix !== nothing + g["dp_matrix"] = result.dp_matrix + end end @printf(" dp21=%+.4f%+.4fi dp31=%+.4f%+.4fi dW_t=%+.6f qa=%.3f\n", real(result.dp_21), imag(result.dp_21), real(result.dp_31), imag(result.dp_31), diff --git a/examples/LAR_epsilon_scan/run_scan.jl b/examples/LAR_epsilon_scan/run_scan.jl index 1f920ba4bb..f02e249ff4 100644 --- a/examples/LAR_epsilon_scan/run_scan.jl +++ b/examples/LAR_epsilon_scan/run_scan.jl @@ -29,11 +29,11 @@ using Printf # spacing tightens smoothly as the pole is approached — the flat low-ε # region is covered with even cadence, and more points land in the final # few percent where Δ' rises by orders of magnitude. -function _warped_grid(x_start::Float64, x_end::Float64, N::Int; p::Float64 = 2.0) +function _warped_grid(x_start::Float64, x_end::Float64, N::Int; p::Float64=2.0) return [x_start + (x_end - x_start) * (1 - (1 - i / (N - 1))^p) for i in 0:N-1] end -const EPSILONS_FULL = _warped_grid(0.125, 0.660, 56; p = 2.0) +const EPSILONS_FULL = _warped_grid(0.125, 0.660, 56; p=2.0) const EPSILONS_TEST = [0.2495, 0.4072, 0.5510] @@ -63,12 +63,14 @@ function run_single(epsilon::Float64) config = deepcopy(GPEC_BASE) config["TJAnalytic"]["r0"] = GPEC_BASE["TJAnalytic"]["a"] / epsilon config["TJAnalytic"]["solver"] = "direct" - open(joinpath(run_dir, "gpec.toml"), "w") do io; TOML.print(io, config); end + open(joinpath(run_dir, "gpec.toml"), "w") do io + TOML.print(io, config) + end GeneralizedPerturbedEquilibrium.main([run_dir]) return extract_results(joinpath(run_dir, "gpec.h5")) catch e - @warn "Failed for ε=$epsilon" exception=(e, catch_backtrace()) + @warn "Failed for ε=$epsilon" exception = (e, catch_backtrace()) return nothing finally rm(run_dir; force=true, recursive=true) @@ -78,24 +80,32 @@ end function extract_results(h5_path::String) h5open(h5_path, "r") do f fbs = "ForceFreeStates/FreeBoundaryStability" - ep = read(f, "$fbs/eigenmode_plasma_energies"); ev = read(f, "$fbs/eigenmode_vacuum_energies"); et = read(f, "$fbs/eigenmode_energies") + ep = read(f, "$fbs/eigenmode_plasma_energies") + ev = read(f, "$fbs/eigenmode_vacuum_energies") + et = read(f, "$fbs/eigenmode_energies") msing = read(f, "SingularSurfaces/rational_count") m_sing = read(f, "SingularSurfaces/rational_m") dp_mat = haskey(f, "SingularSurfaces/Delta_prime_matrix") ? read(f, "SingularSurfaces/Delta_prime_matrix") : nothing qlim = haskey(f, "Info/qlim") ? read(f, "Info/qlim") : read(f, "Equilibrium/q_max") - q0 = read(f, "Equilibrium/q_axis"); qmax = read(f, "Equilibrium/q_max") + q0 = read(f, "Equilibrium/q_axis") + qmax = read(f, "Equilibrium/q_max") - dp_21 = NaN + NaN*im; dp_31 = NaN + NaN*im + dp_21 = NaN + NaN * im + dp_31 = NaN + NaN * im if dp_mat !== nothing && msing > 0 for s in 1:min(msing, size(dp_mat, 1)) m_val = size(m_sing, 1) == msing ? m_sing[s, 1] : m_sing[1, s] - if m_val == 2; dp_21 = dp_mat[s, s]; end - if m_val == 3; dp_31 = dp_mat[s, s]; end + if m_val == 2 + dp_21 = dp_mat[s, s] + end + if m_val == 3 + dp_31 = dp_mat[s, s] + end end end return (dp_21=dp_21, dp_31=dp_31, - dW_plasma=real(ep[1]), dW_vacuum=real(ev[1]), dW_total=real(et[1]), - q0=q0, qmax=qmax, qlim=qlim, msing=msing, dp_matrix=dp_mat) + dW_plasma=real(ep[1]), dW_vacuum=real(ev[1]), dW_total=real(et[1]), + q0=q0, qmax=qmax, qlim=qlim, msing=msing, dp_matrix=dp_mat) end end @@ -123,11 +133,20 @@ function main() haskey(f, gname) && delete_object(f, gname) g = create_group(f, gname) g["epsilon"] = eps - g["dp_21_real"] = real(result.dp_21); g["dp_21_imag"] = imag(result.dp_21) - g["dp_31_real"] = real(result.dp_31); g["dp_31_imag"] = imag(result.dp_31) - g["dW_plasma"] = result.dW_plasma; g["dW_vacuum"] = result.dW_vacuum; g["dW_total"] = result.dW_total - g["q0"] = result.q0; g["qmax"] = result.qmax; g["qlim"] = result.qlim; g["msing"] = result.msing - if result.dp_matrix !== nothing; g["dp_matrix"] = result.dp_matrix; end + g["dp_21_real"] = real(result.dp_21) + g["dp_21_imag"] = imag(result.dp_21) + g["dp_31_real"] = real(result.dp_31) + g["dp_31_imag"] = imag(result.dp_31) + g["dW_plasma"] = result.dW_plasma + g["dW_vacuum"] = result.dW_vacuum + g["dW_total"] = result.dW_total + g["q0"] = result.q0 + g["qmax"] = result.qmax + g["qlim"] = result.qlim + g["msing"] = result.msing + if result.dp_matrix !== nothing + g["dp_matrix"] = result.dp_matrix + end end @printf(" dp21=%+.4f%+.4fi dp31=%+.4f%+.4fi dW_t=%+.6f qa=%.3f\n", real(result.dp_21), imag(result.dp_21), real(result.dp_31), imag(result.dp_31), diff --git a/examples/Solovev_ideal_example/analyze_example.jl b/examples/Solovev_ideal_example/analyze_example.jl index 9f08e1469a..62178b316a 100644 --- a/examples/Solovev_ideal_example/analyze_example.jl +++ b/examples/Solovev_ideal_example/analyze_example.jl @@ -10,6 +10,9 @@ h5path = joinpath(@__DIR__, "gpec.h5") p_eq = Analysis.Equilibrium.plot_equilibrium_summary(h5path) p_ffs = Analysis.ForceFreeStates.plot_ffs_summary(h5path) -display(p_eq); Plots.savefig(p_eq, joinpath(@__DIR__, "equilibrium_summary.png")) -display(p_ffs); Plots.savefig(p_ffs, joinpath(@__DIR__, "ffs_summary.png")) -display(p_pe); Plots.savefig(p_pe, joinpath(@__DIR__, "pe_summary.png")) \ No newline at end of file +display(p_eq); +Plots.savefig(p_eq, joinpath(@__DIR__, "equilibrium_summary.png")); +display(p_ffs); +Plots.savefig(p_ffs, joinpath(@__DIR__, "ffs_summary.png")); +display(p_pe); +Plots.savefig(p_pe, joinpath(@__DIR__, "pe_summary.png")); diff --git a/examples/Solovev_ideal_example_multi_n/analyze_example.jl b/examples/Solovev_ideal_example_multi_n/analyze_example.jl index 3a1e46134f..10edafd6e3 100644 --- a/examples/Solovev_ideal_example_multi_n/analyze_example.jl +++ b/examples/Solovev_ideal_example_multi_n/analyze_example.jl @@ -9,11 +9,14 @@ h5path = joinpath(@__DIR__, "gpec.h5") # Summary plots p_eq = Analysis.Equilibrium.plot_equilibrium_summary(h5path) p_ffs = Analysis.ForceFreeStates.plot_ffs_summary(h5path) -p_pe = Analysis.PerturbedEquilibrium.plot_perturbed_equilibrium_summary(h5path) +p_pe = Analysis.PerturbedEquilibrium.plot_perturbed_equilibrium_summary(h5path) -display(p_eq); Plots.savefig(p_eq, joinpath(@__DIR__, "equilibrium_summary.png")) -display(p_ffs); Plots.savefig(p_ffs, joinpath(@__DIR__, "ffs_summary.png")) -display(p_pe); Plots.savefig(p_pe, joinpath(@__DIR__, "pe_summary.png")) +display(p_eq); +Plots.savefig(p_eq, joinpath(@__DIR__, "equilibrium_summary.png")); +display(p_ffs); +Plots.savefig(p_ffs, joinpath(@__DIR__, "ffs_summary.png")); +display(p_pe); +Plots.savefig(p_pe, joinpath(@__DIR__, "pe_summary.png")); # Analyze the single-n runs h5path_n1 = joinpath(@__DIR__, "single_n_1", "euler_n1.h5") @@ -21,5 +24,7 @@ h5path_n2 = joinpath(@__DIR__, "single_n_2", "euler_n2.h5") p_n1 = Analysis.ForceFreeStates.plot_ffs_summary(h5path_n1) p_n2 = Analysis.ForceFreeStates.plot_ffs_summary(h5path_n2) -display(p_n1); Plots.savefig(p_n1, joinpath(@__DIR__, "single_n_1", "ffs_summary.png")) -display(p_n2); Plots.savefig(p_n2, joinpath(@__DIR__, "single_n_2", "ffs_summary.png")) +display(p_n1); +Plots.savefig(p_n1, joinpath(@__DIR__, "single_n_1", "ffs_summary.png")); +display(p_n2); +Plots.savefig(p_n2, joinpath(@__DIR__, "single_n_2", "ffs_summary.png")); diff --git a/examples/fourier_transform_usage.jl b/examples/fourier_transform_usage.jl index 3c0890734f..19539f7d39 100644 --- a/examples/fourier_transform_usage.jl +++ b/examples/fourier_transform_usage.jl @@ -33,7 +33,7 @@ println(" mlow = $mlow (lowest mode: m ∈ [$mlow, $(mlow+mpert-1)])") println("\nBasis functions: cos(m*θ), sin(m*θ) with m = $mlow:$(mlow+mpert-1)") # Create test data: a simple function in theta space -theta = range(0, 2π; length=mtheta+1)[1:(end-1)] +theta = range(0, 2π; length=mtheta + 1)[1:(end-1)] f_theta = sin.(5 .* theta) .+ 0.5 .* cos.(8 .* theta) # Forward transform: theta → modes diff --git a/regression-harness/src/types.jl b/regression-harness/src/types.jl index a3ee164a78..f31cc55dbf 100644 --- a/regression-harness/src/types.jl +++ b/regression-harness/src/types.jl @@ -19,10 +19,11 @@ end Specification for a test case: what to run and what to extract. `kind` selects the runner backend: + - "gpec_run" (default) — run GPEC end-to-end on `example_dir`, extract from `gpec.h5` - "computed" — run a self-contained Julia computation that writes a small h5 - (no `example_dir` required); used for analytic/reference cases - like the GGJ inner-layer benchmark. + (no `example_dir` required); used for analytic/reference cases + like the GGJ inner-layer benchmark. `precompile_workload` sets GPEC's PrecompileTools workload preference for the run. It defaults to `false` so every case compares code compiled on first use; a case sets it `true` to track the diff --git a/regression-harness/src/utils.jl b/regression-harness/src/utils.jl index 01afbd40a2..d1110f8eff 100644 --- a/regression-harness/src/utils.jl +++ b/regression-harness/src/utils.jl @@ -130,7 +130,7 @@ newest. Without it, two refs are compared across two different package sets and differences surface as physics regressions. """ function create_worktree(commit_hash::String, repo_root::String; - pin_manifest_from::Union{String,Nothing}=nothing)::String + pin_manifest_from::Union{String,Nothing}=nothing)::String short = commit_hash[1:min(8, length(commit_hash))] worktree_path = tempname() * "_gpec_$(short)" try diff --git a/src/Analysis/Equilibrium.jl b/src/Analysis/Equilibrium.jl index 6dbfede40c..f00a406cdd 100644 --- a/src/Analysis/Equilibrium.jl +++ b/src/Analysis/Equilibrium.jl @@ -219,7 +219,7 @@ function plot_flux_surfaces(h5path; n_psi=11, n_theta=18, save_path=nothing) for s in 1:msing idx = argmin(abs.(xs_rz .- psi_sing[s])) q_label = abs(q_sing[s] - round(q_sing[s])) < 0.05 ? - "q=$(round(Int, q_sing[s]))" : "q=$(round(q_sing[s], digits=2))" + "q=$(round(Int, q_sing[s]))" : "q=$(round(q_sing[s], digits=2))" plot!(p, [R_grid[idx, :]; R_grid[idx, 1]], [Z_grid[idx, :]; Z_grid[idx, 1]]; color=:red, linewidth=1.5, label=s == 1 ? "Rational surface" : "") @@ -368,23 +368,26 @@ function plot_equilibrium_summary(h5path; save_path=nothing) title_str = "q0=$(round(q0,digits=2)) q95=$(round(q95,digits=2)) βₜ=$(round(betat,digits=3)) βₙ=$(round(betan,digits=3)) κ=$(round(kappa,digits=2)) li1=$(round(li1,digits=3))" - p_rz = plot_flux_surfaces(h5path) - p_q = plot_qprofile(h5path; show_singular=true) + p_rz = plot_flux_surfaces(h5path) + p_q = plot_qprofile(h5path; show_singular=true) p_pres = plot_pressure_profile(h5path) - p_f = plot_f_profile(h5path) - p_gse = plot_gse_by_theta(h5path) # includes integrated overplot; nothing if absent + p_f = plot_f_profile(h5path) + p_gse = plot_gse_by_theta(h5path) # includes integrated overplot; nothing if absent # Suppress x-axis labels/ticks on all but the bottom profile panel — they share the # same ψ_N axis and labeling every panel wastes vertical space. - hide_xaxis!(p) = plot!(p; xlabel="", xformatter=_->"", bottom_margin=1Plots.mm) + hide_xaxis!(p) = plot!(p; xlabel="", xformatter=_ -> "", bottom_margin=1Plots.mm) if isnothing(p_gse) - hide_xaxis!(p_q); hide_xaxis!(p_pres) + hide_xaxis!(p_q) + hide_xaxis!(p_pres) l = @layout [a{0.38w} [b; c; d]] p = plot(p_rz, p_q, p_pres, p_f; layout=l, size=(1300, 750), plot_title=title_str, plot_titlefontsize=10, top_margin=8Plots.mm) else - hide_xaxis!(p_q); hide_xaxis!(p_pres); hide_xaxis!(p_f) + hide_xaxis!(p_q) + hide_xaxis!(p_pres) + hide_xaxis!(p_f) l = @layout [a{0.38w} [b; c; d; e]] p = plot(p_rz, p_q, p_pres, p_f, p_gse; layout=l, size=(1300, 1000), plot_title=title_str, plot_titlefontsize=10, top_margin=8Plots.mm) diff --git a/src/Analysis/PerturbedEquilibrium.jl b/src/Analysis/PerturbedEquilibrium.jl index 03f2696605..83d5cde7bf 100644 --- a/src/Analysis/PerturbedEquilibrium.jl +++ b/src/Analysis/PerturbedEquilibrium.jl @@ -45,10 +45,10 @@ function plot_resonant_area_weighted_field_amplitude(h5path; save_path=nothing) return plot(; title="No resonant area-weighted field data — run with perturbed equilibrium enabled", legend=false) resonant_area_weighted_field, rational_psi, rational_q, rational_n = h5open(h5path, "r") do fid - read(fid[base * "resonant_area_weighted_field"]), - read(fid[base * "rational_psi"]), - read(fid[base * "rational_q"]), - read(fid[base * "rational_n"]) + read(fid[base*"resonant_area_weighted_field"]), + read(fid[base*"rational_psi"]), + read(fid[base*"rational_q"]), + read(fid[base*"rational_n"]) end p = plot(; xlabel="Norm. Poloidal Flux", ylabel="|b^r| [T]", @@ -96,9 +96,9 @@ function plot_island_widths(h5path; save_path=nothing) return plot(; title="No island width data — run with perturbed equilibrium enabled", legend=false) island_hw, rational_psi, rational_q = h5open(h5path, "r") do fid - read(fid[base * "island_half_width"]), - read(fid[base * "rational_psi"]), - read(fid[base * "rational_q"]) + read(fid[base*"island_half_width"]), + read(fid[base*"rational_psi"]), + read(fid[base*"rational_q"]) end p = scatter( @@ -150,9 +150,9 @@ function plot_chirikov_parameter(h5path; save_path=nothing) return plot(; title="No Chirikov data — run with perturbed equilibrium enabled", legend=false) chirikov, rational_psi, rational_q = h5open(h5path, "r") do fid - read(fid[base * "chirikov_parameter"]), - read(fid[base * "rational_psi"]), - read(fid[base * "rational_q"]) + read(fid[base*"chirikov_parameter"]), + read(fid[base*"rational_psi"]), + read(fid[base*"rational_q"]) end colors = [k > 1.0 ? :red : :steelblue for k in chirikov] @@ -212,10 +212,10 @@ function plot_driven_delta_prime(h5path; save_path=nothing) return plot(; title="No PE Δ' data — run with perturbed equilibrium enabled", legend=false) delta_prime, rational_psi, rational_q, rational_n = h5open(h5path, "r") do fid - read(fid[base * "Delta_prime"]), - read(fid[base * "rational_psi"]), - read(fid[base * "rational_q"]), - read(fid[base * "rational_n"]) + read(fid[base*"Delta_prime"]), + read(fid[base*"rational_psi"]), + read(fid[base*"rational_q"]), + read(fid[base*"rational_n"]) end p = plot(; xlabel="Norm. Poloidal Flux", ylabel="Re(Δ')", @@ -280,7 +280,7 @@ function plot_resonant_area_weighted_field(h5path; save_path=nothing) plot!(p; title="") end - hide_xaxis!(p) = plot!(p; xlabel="", xformatter=_->"", bottom_margin=1Plots.mm) + hide_xaxis!(p) = plot!(p; xlabel="", xformatter=_ -> "", bottom_margin=1Plots.mm) for p in (p1, p2, p3, p4) hide_xaxis!(p) end @@ -298,10 +298,10 @@ function _plot_resonant_current(h5path) return plot(; title="No resonant current data", legend=false) resonant_current, rational_psi, rational_q, rational_n = h5open(h5path, "r") do fid - read(fid[base * "resonant_current"]), - read(fid[base * "rational_psi"]), - read(fid[base * "rational_q"]), - read(fid[base * "rational_n"]) + read(fid[base*"resonant_current"]), + read(fid[base*"rational_psi"]), + read(fid[base*"rational_q"]), + read(fid[base*"rational_n"]) end p = plot(; xlabel="Norm. Poloidal Flux", ylabel="|I_res|", @@ -353,10 +353,10 @@ A `Plots.jl` plot object. """ function plot_mode_spectrogram(h5path; component=:xi_psi, save_path=nothing) comp_map = Dict( - :xi_psi => "xi_psi", - :b_psi => "b_psi_area_weighted", # area-normalized b^ψ + :xi_psi => "xi_psi", + :b_psi => "b_psi_area_weighted", # area-normalized b^ψ :Jb_theta => "Jb_theta", - :Jb_zeta => "Jb_zeta", + :Jb_zeta => "Jb_zeta" ) haskey(comp_map, component) || error("component must be one of :xi_psi, :b_psi, :Jb_theta, :Jb_zeta") @@ -369,7 +369,7 @@ function plot_mode_spectrogram(h5path; component=:xi_psi, save_path=nothing) data, psi_response, mlow, mhigh, nhigh, q95, rational_psi = h5open(h5path, "r") do fid read(fid[dataset_path]), - read(fid[base * "psi"]), + read(fid[base*"psi"]), read(fid["Info/mlow"]), read(fid["Info/mhigh"]), read(fid["Info/nhigh"]), read(fid["Equilibrium/q_95"]), read(fid["PerturbedEquilibrium/SingularCoupling/rational_psi"]) @@ -440,9 +440,9 @@ Three-panel composite summary of perturbed equilibrium results: A `Plots.jl` plot object. """ function plot_perturbed_equilibrium_summary(h5path; save_path=nothing) - p_islands = plot_resonant_area_weighted_field_amplitude(h5path) - p_bpsi = _plot_bpsi_edge_spectrum(h5path) - p_spectro = plot_mode_spectrogram(h5path; component=:xi_psi) + p_islands = plot_resonant_area_weighted_field_amplitude(h5path) + p_bpsi = _plot_bpsi_edge_spectrum(h5path) + p_spectro = plot_mode_spectrogram(h5path; component=:xi_psi) l = @layout [grid(1, 2){0.35h}; b] p = plot(p_islands, p_bpsi, p_spectro; layout=l, size=(1100, 1100)) @@ -458,11 +458,11 @@ function _plot_bpsi_edge_spectrum(h5path) return plot(; title="No b_psi data — run with perturbed equilibrium enabled", legend=false) data, mlow, mhigh = h5open(h5path, "r") do fid - read(fid[base * "b_psi_area_weighted"]), + read(fid[base*"b_psi_area_weighted"]), read(fid["Info/mlow"]), read(fid["Info/mhigh"]) end - mpert = mhigh - mlow + 1 + mpert = mhigh - mlow + 1 m_vals = mlow:mhigh bpsi_edge = abs.(data[end, 1:mpert]) diff --git a/src/Analysis/PerturbedEquilibriumModes.jl b/src/Analysis/PerturbedEquilibriumModes.jl index 58aacdaf98..a4dc52174f 100644 --- a/src/Analysis/PerturbedEquilibriumModes.jl +++ b/src/Analysis/PerturbedEquilibriumModes.jl @@ -20,33 +20,37 @@ Reads all required metadata (mlow, nlow, mpert, npert) and spline data (ν) from the HDF5 file. # Arguments -- `h5_file::String`: Path to gpec.h5 output file -- `variable::String`: HDF5 dataset path, e.g. `"PerturbedEquilibrium/Response/xi_R"` + + - `h5_file::String`: Path to gpec.h5 output file + - `variable::String`: HDF5 dataset path, e.g. `"PerturbedEquilibrium/Response/xi_R"` # Keyword arguments -- `mtheta::Int`: theta grid resolution (default: `max(2*(|mlow|+mpert), 512)`) -- `keep_sfl_phi::Bool`: if `true` (default), output in SFL toroidal angle; - if `false`, apply `exp(i*n*ν(ψ,θ))` to convert to machine toroidal angle - and conjugate if `helicity > 0` (matches Fortran `gpout_xbrzphifun`) + + - `mtheta::Int`: theta grid resolution (default: `max(2*(|mlow|+mpert), 512)`) + - `keep_sfl_phi::Bool`: if `true` (default), output in SFL toroidal angle; + if `false`, apply `exp(i*n*ν(ψ,θ))` to convert to machine toroidal angle + and conjugate if `helicity > 0` (matches Fortran `gpout_xbrzphifun`) # Returns -- `theta_data::Array{ComplexF64,3}`: `[npsi × mtheta × npert]` -- `theta_grid::Vector{Float64}`: `[mtheta]` SFL theta ∈ [0, 1) -- `n_vals::Vector{Int}`: `[npert]` toroidal mode numbers + + - `theta_data::Array{ComplexF64,3}`: `[npsi × mtheta × npert]` + - `theta_grid::Vector{Float64}`: `[mtheta]` SFL theta ∈ [0, 1) + - `n_vals::Vector{Int}`: `[npert]` toroidal mode numbers !!! note + When used on the cylindrical components `xi_R`, `xi_Z`, `xi_phi`, `b_R`, `b_Z`, `b_phi` these are currently in beta and show up to ~20% discrepancies vs Fortran. """ function modes_to_theta(h5_file::String, variable::String; - mtheta::Union{Int,Nothing}=nothing, - keep_sfl_phi::Bool=true) + mtheta::Union{Int,Nothing}=nothing, + keep_sfl_phi::Bool=true) h5open(h5_file, "r") do f modes = read(f, variable) # (npsi, numpert_total) npsi, numpert_total = size(modes) - mlow = read(f, "Info/mlow") - nlow = read(f, "Info/nlow") + mlow = read(f, "Info/mlow") + nlow = read(f, "Info/nlow") mpert = read(f, "Info/mpert") npert = read(f, "Info/npert") @assert numpert_total == mpert * npert "Expected numpert_total=$(mpert*npert), got $numpert_total" @@ -58,7 +62,7 @@ function modes_to_theta(h5_file::String, variable::String; theta_data = zeros(ComplexF64, npsi, mth, npert) for k in 1:npert - col_range = (k-1)*mpert+1 : k*mpert + col_range = (k-1)*mpert+1:k*mpert for ipsi in 1:npsi theta_data[ipsi, :, k] .= inverse(ft, view(modes, ipsi, col_range)) end @@ -68,7 +72,7 @@ function modes_to_theta(h5_file::String, variable::String; # Reconstruct ν spline from stored grid + nodal values (FastInterpolations v0.4 API) rzphi_xs = read(f, "Equilibrium/Geometry/psi") rzphi_ys = read(f, "Equilibrium/Geometry/theta") - nu_vals = read(f, "Equilibrium/Geometry/nu") + nu_vals = read(f, "Equilibrium/Geometry/nu") nu_spline = cubic_interp( (rzphi_xs, rzphi_ys), nu_vals; bc=(CubicFit(), PeriodicBC()), @@ -77,8 +81,8 @@ function modes_to_theta(h5_file::String, variable::String; psi_grid = read(f, "ForceFreeStates/Solutions/ForwardIntegration/psi") - bt_sign = haskey(f, "Equilibrium/B_T_sign") ? read(f, "Equilibrium/B_T_sign") : 1 - crnt = haskey(f, "Equilibrium/I_p") ? read(f, "Equilibrium/I_p") : 1.0 + bt_sign = haskey(f, "Equilibrium/B_T_sign") ? read(f, "Equilibrium/B_T_sign") : 1 + crnt = haskey(f, "Equilibrium/I_p") ? read(f, "Equilibrium/I_p") : 1.0 helicity = bt_sign * Int(sign(crnt)) hint = (Ref(1), Ref(1)) @@ -110,18 +114,21 @@ Extend theta-space data to a `(θ, φ)` grid via toroidal inverse DFT. f(θ, φ) = Σₙ fₙ(θ) exp(i·n·φ) # Arguments -- `theta_data::Array{ComplexF64,3}`: `[npsi × mtheta × npert]` from `modes_to_theta` -- `n_vals::Vector{Int}`: toroidal mode numbers + + - `theta_data::Array{ComplexF64,3}`: `[npsi × mtheta × npert]` from `modes_to_theta` + - `n_vals::Vector{Int}`: toroidal mode numbers # Keyword arguments -- `nphi::Int`: toroidal grid points (default: `max(4*maximum(abs.(n_vals)), 64)`) + + - `nphi::Int`: toroidal grid points (default: `max(4*maximum(abs.(n_vals)), 64)`) # Returns -- `full_data::Array{ComplexF64,3}`: `[npsi × mtheta × nphi]` -- `phi_grid::Vector{Float64}`: `[nphi]` in radians ∈ [0, 2π) + + - `full_data::Array{ComplexF64,3}`: `[npsi × mtheta × nphi]` + - `phi_grid::Vector{Float64}`: `[nphi]` in radians ∈ [0, 2π) """ function theta_to_thetaphi(theta_data::Array{ComplexF64,3}, n_vals::Vector{Int}; - nphi::Union{Int,Nothing}=nothing) + nphi::Union{Int,Nothing}=nothing) npsi, mth, npert = size(theta_data) np = isnothing(nphi) ? max(4 * maximum(abs.(n_vals)), 64) : nphi phi_grid = [(j - 1) * 2π / np for j in 1:np] diff --git a/src/Deck.jl b/src/Deck.jl index 4f88de793a..ea0f4b17ce 100644 --- a/src/Deck.jl +++ b/src/Deck.jl @@ -38,7 +38,7 @@ const _MOVED_KEYS = Dict{String,Dict{String,String}}( ("gal_$k" => "moved to [Match] as $k" for k in ("eta", "rho", "rotation", "gamma"))..., ("gal_inner_$k" => "moved to [GGJ] as $k" for k in ("xfac", "nx", "nq", "cutoff", "kmax"))..., ("gal_$k" => "moved to [Galerkin] as $k" for k in ("nx", "nq", "pfac", "dx0", "dx1", "dx2", "cutoff", "tol", "gnstep", - "dx1dx2_flag", "rpec_flag", "edge_onesided"))..., + "dx1dx2_flag", "rpec_flag", "edge_onesided"))..., "gal_sing_order_ceiling" => "moved to [Galerkin] as frobenius_order_ceiling"), "LocalStability" => Dict{String,String}( k => "moved to [Ballooning]" for k in ("ode_tol", "n_scan", "max_alpha_scale", "alpha_tol", "psi_window")), @@ -217,8 +217,10 @@ function equilibrium_config(inputs::AbstractDict, path::AbstractString) elseif eq_type === nothing error("Missing required key in [Equilibrium]: eq_type") elseif haskey(Equilibrium.ANALYTIC_EQ, eq_type) - error("[Equilibrium] eq_type = \"$eq_type\" is not a deck value: state the analytic equilibrium as a " * - "[$(Equilibrium.ANALYTIC_EQ[eq_type].section)] section and remove eq_type") + error( + "[Equilibrium] eq_type = \"$eq_type\" is not a deck value: state the analytic equilibrium as a " * + "[$(Equilibrium.ANALYTIC_EQ[eq_type].section)] section and remove eq_type" + ) end fields = _struct_fields(Equilibrium.EquilibriumConfig, (:power_bp, :power_b, :power_r, :power_rc)) kwargs = section_keywords(_deck_paths(table, path, ("eq_filename", "kinetic_file")), "Equilibrium", fields) @@ -367,8 +369,10 @@ function tearing_from_deck(inputs::AbstractDict) inner_model = _selector(table, "Tearing", "inner_model", ("SLAYER", "GGJ")) delete!(problem, :inner_model) crf = get(table, "CriticalResonantField", nothing) - crf === nothing || (problem[:critical_resonant_field] = CriticalResonantFieldControl(; - section_keywords(crf, "Tearing.CriticalResonantField", _struct_fields(CriticalResonantFieldControl))...)) + crf === nothing || ( + problem[:critical_resonant_field] = CriticalResonantFieldControl(; + section_keywords(crf, "Tearing.CriticalResonantField", _struct_fields(CriticalResonantFieldControl))...) + ) config = TearingConfig(; section_keywords(get(table, "search", Dict{String,Any}()), "Tearing.search", _struct_fields(TearingConfig))...) return (; problem, model=inner_model == "SLAYER" ? slayer_model(inputs) : ggj_model(inputs), config) end @@ -415,8 +419,10 @@ function error_fields_from_deck(inputs::AbstractDict, path::AbstractString) haskey(inputs, "ErrorFields") || return nothing table = _deck_paths(inputs["ErrorFields"], path, ("tolerance_file",)) sub(name, T) = T(; section_keywords(get(table, name, Dict{String,Any}()), "ErrorFields.$name", _struct_fields(T))...) - ctrl = ErrorFields.ErrorFieldsControl(; section_keywords(_without(table, "MonteCarlo", "Risk", "scenario", "NTV", "tolerance_file"), - "ErrorFields", _struct_fields(ErrorFields.ErrorFieldsControl))...) + ctrl = ErrorFields.ErrorFieldsControl(; + section_keywords(_without(table, "MonteCarlo", "Risk", "scenario", "NTV", "tolerance_file"), + "ErrorFields", _struct_fields(ErrorFields.ErrorFieldsControl))... + ) file = get(table, "tolerance_file", "") isempty(file) || isfile(file) || error("[ErrorFields] tolerance_file not found: $file") scenario = haskey(table, "scenario") ? section_keywords(table["scenario"], "ErrorFields.scenario", _struct_fields(ErrorFields.ScenarioParameters)) : nothing @@ -639,7 +645,8 @@ function deck_sections(pe::PerturbedEquilibrium.PerturbedEquilibriumState) "compute_torque_response" => resp.compute_torque_response, "regularization_width" => pe.regularization_width) sections = Dict{String,Any}("PerturbedEquilibrium" => table) forcing === nothing && return sections - sections["ForcingTerms"] = forcing isa ForcingTerms.RMPSource && forcing.scale == 1 ? _forcing_table(forcing.ctrl) : - Dict{String,Any}("forcing_data_format" => "hdf5", "forcing_data_file" => _RECORDED_FORCING) + sections["ForcingTerms"] = + forcing isa ForcingTerms.RMPSource && forcing.scale == 1 ? _forcing_table(forcing.ctrl) : + Dict{String,Any}("forcing_data_format" => "hdf5", "forcing_data_file" => _RECORDED_FORCING) return sections end diff --git a/src/Equilibrium/Axisymmetric/Inverse.jl b/src/Equilibrium/Axisymmetric/Inverse.jl index 02adab7eeb..3500c48581 100644 --- a/src/Equilibrium/Axisymmetric/Inverse.jl +++ b/src/Equilibrium/Axisymmetric/Inverse.jl @@ -83,7 +83,7 @@ function equilibrium_solver(input::InverseRunInput; override_psi_nodes::Union{No twopi = 2 * π - deta = zeros(Float64, mx+1, my+1) + deta = zeros(Float64, mx + 1, my + 1) for ipsi in 0:mx, itheta in 0:my if r2[ipsi+1, itheta+1] == 0.0 deta[ipsi+1, itheta+1] = 0.0 @@ -149,7 +149,7 @@ function equilibrium_solver(input::InverseRunInput; override_psi_nodes::Union{No if mpsi == 0 mpsi = 128 end - sq_xs = psilow .+ (psihigh - psilow) .* (sin.(range(0.0, 1.0; length=mpsi+1) .* (π/2))) .^ 2 + sq_xs = psilow .+ (psihigh - psilow) .* (sin.(range(0.0, 1.0; length=mpsi + 1) .* (π / 2))) .^ 2 elseif grid_type == "pow1" if mpsi == 0 mpsi = 128 @@ -180,7 +180,7 @@ function equilibrium_solver(input::InverseRunInput; override_psi_nodes::Union{No sq_xs = enforce_min_spacing(sq_xs, MIN_KNOT_SPACING) mpsi = length(sq_xs) - 1 end - sq_fs = zeros(Float64, mpsi+1, 4) + sq_fs = zeros(Float64, mpsi + 1, 4) sq = cubic_interp(sq_xs, Series(sq_fs); extrap=ExtendExtrap()) # Coordinate grids @@ -188,21 +188,21 @@ function equilibrium_solver(input::InverseRunInput; override_psi_nodes::Union{No mtheta = my end - theta_range = range(0.0, 1.0; length=mtheta+1) + theta_range = range(0.0, 1.0; length=mtheta + 1) # rzphi components: r², deta, dphi, jac - rzphi_fs = zeros(Float64, mpsi+1, mtheta+1, 4) + rzphi_fs = zeros(Float64, mpsi + 1, mtheta + 1, 4) rzphi_xs = copy(sq_xs) # Vector copy stored in PlasmaEquilibrium; interpolants use the faster Range theta_range (equal to machine precision). rzphi_ys = collect(theta_range) # eqfun components: |B|, metric1, metric2 - eqfun_fs = zeros(Float64, mpsi+1, mtheta+1, 3) + eqfun_fs = zeros(Float64, mpsi + 1, mtheta + 1, 3) eqfun_xs = copy(sq_xs) # Preallocate arrays for periodic spline fitting in the loop - spl_xs = zeros(Float64, mtheta+1) - spl_fs = zeros(Float64, mtheta+1, 5) + spl_xs = zeros(Float64, mtheta + 1) + spl_fs = zeros(Float64, mtheta + 1, 5) spl_post_buf = Vector{Float64}(undef, 5) # scratch for post-SFL spline evaluation # q: i-files use the field-line integral, as Fortran `inverse_run`; a tabulated q splined in ψ is @@ -234,11 +234,11 @@ function equilibrium_solver(input::InverseRunInput; override_psi_nodes::Union{No rfac = sqrt(f_rsq) r = ro + rfac * cos(twopi * (theta + f_deta)) jacfac = fx_rsq * (1 + fy_deta) - fy_rsq * fx_deta - w11 = (1 + fy_deta) * twopi ^ 2 * rfac / jacfac + w11 = (1 + fy_deta) * twopi^2 * rfac / jacfac w12 = -fy_rsq * pi / (rfac * jacfac) - bp = psio * sqrt(w11*w11 + w12*w12) / r + bp = psio * sqrt(w11 * w11 + w12 * w12) / r bt = f_sq_in_buf[1] / r - b = sqrt(bp*bp + bt*bt) + b = sqrt(bp * bp + bt * bt) spl_fs[itheta+1, 1] = f_rsq spl_fs[itheta+1, 2] = f_deta diff --git a/src/Equilibrium/Derived/GeometryProfiles.jl b/src/Equilibrium/Derived/GeometryProfiles.jl index e62c1977a2..171a4ad1c3 100644 --- a/src/Equilibrium/Derived/GeometryProfiles.jl +++ b/src/Equilibrium/Derived/GeometryProfiles.jl @@ -19,7 +19,7 @@ function compute_geometry_profiles( rzphi_rsquared::FastInterpolations.CubicInterpolantND, rzphi_offset::FastInterpolations.CubicInterpolantND, rzphi_jac::FastInterpolations.CubicInterpolantND, - ro::Float64, + ro::Float64 ) npsi = length(rzphi_xs) mthsurf = length(rzphi_ys) - 1 # skip the periodic duplicate at θ = 1 @@ -60,6 +60,6 @@ function compute_geometry_profiles(pe::PlasmaEquilibrium) return compute_geometry_profiles( pe.rzphi_xs, pe.rzphi_ys, pe.rzphi_rsquared, pe.rzphi_offset, pe.rzphi_jac, - pe.ro, + pe.ro ) end diff --git a/src/Equilibrium/Grid/GridRefinement.jl b/src/Equilibrium/Grid/GridRefinement.jl index 40ff91d9ab..772635b8c4 100644 --- a/src/Equilibrium/Grid/GridRefinement.jl +++ b/src/Equilibrium/Grid/GridRefinement.jl @@ -408,8 +408,8 @@ function bracket_mandatory_nodes(grid::Vector{Float64}, centers::Vector{Float64} right < hi && push!(kept, (right, true)) end isempty(capped) || @warn "$(length(capped)) rational surface(s) have grid bracket half-widths capped below their Δ′ stencil " * - "(neighboring surface or domain edge too close, or a near-tangent surface with q′ ≈ 0); Δ′ there is less accurate. " * - "(ψ, half-width, stencil): $(join(capped, ", "))" + "(neighboring surface or domain edge too close, or a near-tangent surface with q′ ≈ 0); Δ′ there is less accurate. " * + "(ψ, half-width, stencil): $(join(capped, ", "))" sort!(kept; by=first) merged = Float64[] for (v, _) in kept diff --git a/src/ForceFreeStates/EulerLagrange.jl b/src/ForceFreeStates/EulerLagrange.jl index 6e3b8be637..0c259db9eb 100644 --- a/src/ForceFreeStates/EulerLagrange.jl +++ b/src/ForceFreeStates/EulerLagrange.jl @@ -706,8 +706,10 @@ Errors when the solver stops short of the interval's end (e.g. the step size und function solve_el(prob, rtol; callback=COLUMN_ABSTOL_CALLBACK) sol = solve(prob, Vern9(); reltol=rtol, abstol=column_abstol(prob.u0, rtol), callback, save_everystep=false, save_end=true) SciMLBase.successful_retcode(sol) || - error("Euler-Lagrange integration aborted at ψ = $(@sprintf("%.6f", sol.t[end])) before ψ = $(@sprintf("%.6f", prob.tspan[2])) " * - "(retcode $(sol.retcode)). Loosen eulerlagrange_tolerance or refine the ψ grid; for kinetic runs, lower kinetic_factor.") + error( + "Euler-Lagrange integration aborted at ψ = $(@sprintf("%.6f", sol.t[end])) before ψ = $(@sprintf("%.6f", prob.tspan[2])) " * + "(retcode $(sol.retcode)). Loosen eulerlagrange_tolerance or refine the ψ grid; for kinetic runs, lower kinetic_factor." + ) return sol end @@ -962,7 +964,7 @@ function transform_u!(odet::OdeState, intr::ForceFreeStatesInternal) temp[ksol, jsol] = odet.fixfac[ksol, jsol, ifix] end end - mul!(gauss_buffer, view(gauss,:,:,ifix), temp) + mul!(gauss_buffer, view(gauss, :, :, ifix), temp) gauss[:, :, ifix] .= gauss_buffer end # Drop the resonant solutions zeroed at an ideal crossing @@ -976,7 +978,7 @@ function transform_u!(odet::OdeState, intr::ForceFreeStatesInternal) # Region i lies between fixups i-1 and i; its transform is the product of all later reductions transforms[:, :, end] .= identity for ifix in odet.ifix:-1:1 - mul!(view(transforms,:,:,ifix), view(gauss,:,:,ifix), view(transforms,:,:,(ifix+1))) + mul!(view(transforms, :, :, ifix), view(gauss, :, :, ifix), view(transforms, :, :, (ifix + 1))) end jfix = 1 diff --git a/src/ForceFreeStates/Galerkin/GalerkinSolve.jl b/src/ForceFreeStates/Galerkin/GalerkinSolve.jl index ca367e91f5..5d4f6d2df5 100644 --- a/src/ForceFreeStates/Galerkin/GalerkinSolve.jl +++ b/src/ForceFreeStates/Galerkin/GalerkinSolve.jl @@ -194,7 +194,7 @@ function gal_scaled_lu_solve!(mat::Matrix{ComplexF64}, sol::AbstractVecOrMat{Com n = size(mat, 2) off = kl + ku + 1 # band row holding the diagonal d = [iszero(mat[off, j]) ? 1.0 : 1 / sqrt(abs(mat[off, j])) for j in 1:n] - for j in 1:n, i in max(1, j-ku):min(n, j+kl) + for j in 1:n, i in max(1, j - ku):min(n, j + kl) mat[off+i-j, j] *= d[i] * d[j] end sol .*= d @@ -362,18 +362,18 @@ function gal_cholesky_solve!(mat::Matrix{ComplexF64}, sol::AbstractVecOrMat{Comp n = size(mat, 2) off = 2kl + 1 a(i, j) = mat[off+i-j, j] - band(i) = max(1, i-kl):min(n, i+kl) + band(i) = max(1, i - kl):min(n, i + kl) fixed = [all(j == i ? a(i, j) == 1 : iszero(a(i, j)) for j in band(i)) for i in 1:n] for j in 1:n isfinite(a(j, j)) || return false - for i in (j+1):min(n, j+kl) + for i in (j+1):min(n, j + kl) isfinite(a(i, j)) && isfinite(a(j, i)) || return false fixed[i] || fixed[j] || abs(a(i, j) - conj(a(j, i))) <= 1e-10 * sqrt(abs(a(i, i))) * sqrt(abs(a(j, j))) || return false end end ab = zeros(ComplexF64, kl + 1, n) - for j in 1:n, i in j:min(n, j+kl) + for j in 1:n, i in j:min(n, j + kl) ab[1+i-j, j] = fixed[i] || fixed[j] ? (i == j ? one(ComplexF64) : zero(ComplexF64)) : a(i, j) end gal_zpbtrf!(ab, kl) || return false diff --git a/src/ForceFreeStates/Matching/MatchProblem.jl b/src/ForceFreeStates/Matching/MatchProblem.jl index 4945399267..f80f52eb46 100644 --- a/src/ForceFreeStates/Matching/MatchProblem.jl +++ b/src/ForceFreeStates/Matching/MatchProblem.jl @@ -222,7 +222,7 @@ function _compute_match(prob::MatchProblem, model::InnerLayer.GGJModel) sols_cut = gal_sol.xi_cut if isempty(sols_cut) @warn "MatchProblem: the solve retained no cut solution (set cut_solution=true on Galerkin), " * - "so the composite inner-region profiles are skipped; bpen and the matched outer ξ are unaffected" maxlog=1 + "so the composite inner-region profiles are skipped; bpen and the matched outer ξ are unaffected" maxlog = 1 empty!(inner_psi) empty!(inner_xi) empty!(inner_b) diff --git a/src/ForceFreeStates/Riccati/DeltaPrimeBVP.jl b/src/ForceFreeStates/Riccati/DeltaPrimeBVP.jl index a0ac6743ba..fcac7b1151 100644 --- a/src/ForceFreeStates/Riccati/DeltaPrimeBVP.jl +++ b/src/ForceFreeStates/Riccati/DeltaPrimeBVP.jl @@ -367,7 +367,7 @@ function _assemble_bvp_S_axis(uShootR::Vector{Matrix{ComplexF64}}, if j < msing # Midpoint matching between consecutive surfaces M[junc_rows, _col_right(j, N)] .= -uShootR[j] - M[junc_rows, _col_left(j+1, N)] .= uShootL[j+1] + M[junc_rows, _col_left(j + 1, N)] .= uShootL[j+1] else # Edge junction M[junc_rows, _col_right(msing, N)] .= uShootR[msing] @@ -431,12 +431,12 @@ function _assemble_bvp_FM_axis(Phi_L_mats::Vector{Matrix{ComplexF64}}, M[1:2N, (N+1):(N+2N)] .= Phi_L_mats[1] M[1:2N, col_axis] .= -view(Phi_R_mats[1], :, (N+1):2N) - row_drive_base = 2N + (4N-2)*msing + row_drive_base = 2N + (4N - 2) * msing for j in 1:msing ipert_j = ipert_all[j] cl = _col_left(j, N) cr = _col_right(j, N) - row_cont = 2N + (4N-2)*(j-1) + row_cont = 2N + (4N - 2) * (j - 1) for i in 1:2N if i != ipert_j && i != ipert_j + N row_cont += 1 @@ -447,7 +447,7 @@ function _assemble_bvp_FM_axis(Phi_L_mats::Vector{Matrix{ComplexF64}}, junc_rows = (row_cont+1):(2N+(4N-2)*j) if j < msing M[junc_rows, cr] .= Phi_R_mats[j+1] - M[junc_rows, _col_left(j+1, N)] .= -Phi_L_mats[j+1] + M[junc_rows, _col_left(j + 1, N)] .= -Phi_L_mats[j+1] else M[junc_rows, cr] .= Phi_R_mats[msing+1] if wv !== nothing @@ -487,7 +487,7 @@ function _solve_bvp_and_combine_pest3(M::Matrix{ComplexF64}, msing::Int, N::Int, for jsing in 1:msing, side in 1:2 dRow = 2jsing - (2 - side) fill!(b, 0) - drive_row = use_S_axis ? (nMat - s2 + dRow) : (2N + (4N-2)*msing + dRow) + drive_row = use_S_axis ? (nMat - s2 + dRow) : (2N + (4N - 2) * msing + dRow) b[drive_row] = 1 x = use_lu ? (M_lu \ b) : (M_pinv * b) diff --git a/src/ForceFreeStates/Riccati/Propagators.jl b/src/ForceFreeStates/Riccati/Propagators.jl index 3c0ce25d49..81bd3d831c 100644 --- a/src/ForceFreeStates/Riccati/Propagators.jl +++ b/src/ForceFreeStates/Riccati/Propagators.jl @@ -3,7 +3,7 @@ # `riccati_integrator_callback!` saves every step within this fraction of the span (or absolute ψ # floor, for short chunks) of a segment's right end; elsewhere every `save_interval`-th step. const SAVE_NEAR_END_FRAC = 0.05 -const SAVE_NEAR_END_PSI = 1e-4 +const SAVE_NEAR_END_PSI = 1e-4 """ assemble_fm_matrix(propagators, idx_range; condition=false, T_init=nothing) -> Matrix{ComplexF64} @@ -13,6 +13,7 @@ mapping the IC at the start of `idx_range[1]` to the state at the end of `idx_ra (`T_init` defaults to the identity). Each `ChunkPropagator` stores the 2N columns of Φ split into two N×N×2 blocks: + ``` block_upper_ic[:,:,1:2] ↔ Φ[:,1:N] (result from IC=(I,0)) block_lower_ic[:,:,1:2] ↔ Φ[:,N+1:2N] (result from IC=(0,I)) @@ -23,8 +24,8 @@ the N U₂ columns survive. Use it only for the axis→first-surface segment, wh needs only U₂ ICs; never for inter-surface segments, where both halves carry information. """ function assemble_fm_matrix(propagators::Vector{ChunkPropagator}, idx_range; - condition::Bool=false, - T_init::Union{Nothing,Matrix{ComplexF64}}=nothing) + condition::Bool=false, + T_init::Union{Nothing,Matrix{ComplexF64}}=nothing) # Take N from T_init when given, so an empty idx_range (or propagator list) still works. N = if T_init !== nothing size(T_init, 1) ÷ 2 @@ -110,7 +111,7 @@ end riccati_der!(du, u, params, psieval) Evaluate the explicit dual Riccati ODE right-hand side: - dS/dψ = w†·F̄⁻¹·w - S·Ḡ·S, w = Q - K̄·S +dS/dψ = w†·F̄⁻¹·w - S·Ḡ·S, w = Q - K̄·S where Q = diag(1/(m - n·q)) is the diagonal singular factor matrix. The identity slice u[:,:,2] = I does not evolve (du[:,:,2] = 0). @@ -132,7 +133,7 @@ See: Glasser (2018) Phys. Plasmas 25, 032507 — Eq. 19 (dual Riccati form) _, equil, mats, intr, odet, _ = params Npert = intr.numpert_total - S = @view u[:, :, 1] + S = @view u[:, :, 1] dS = @view du[:, :, 1] @view(du[:, :, 2]) .= 0 # identity does not evolve @@ -146,9 +147,9 @@ See: Glasser (2018) Phys. Plasmas 25, 032507 — Eq. 19 (dual Riccati form) fmat_lower = acquire!(pool, ComplexF64, Npert, Npert) kmat = similar!(pool, fmat_lower) gmat = similar!(pool, fmat_lower) - w = similar!(pool, fmat_lower) # w = Q - K̄·S - v = similar!(pool, fmat_lower) # v = F̄⁻¹·w (then reused for S·Ḡ·S) - tmp = similar!(pool, fmat_lower) # scratch + w = similar!(pool, fmat_lower) # w = Q - K̄·S + v = similar!(pool, fmat_lower) # v = F̄⁻¹·w (then reused for S·Ḡ·S) + tmp = similar!(pool, fmat_lower) # scratch # Evaluate F̄ (Cholesky factor), K̄, Ḡ splines at current ψ mats.ideal.F_spline_lower(vec(fmat_lower), psieval; hint=mats._hint) @@ -225,7 +226,7 @@ function riccati_integrate_chunk!( cb = DiscreteCallback((u, t, integrator) -> true, riccati_integrator_callback!) rtol = ctrl.algorithm.eulerlagrange_tolerance prob = ODEProblem(sing_der!, odet.u, (chunk.psi_start, chunk.psi_end), - (ctrl, equil, mats, intr, odet, chunk)) + (ctrl, equil, mats, intr, odet, chunk)) sol = solve_el(prob, rtol; callback=cb) odet.u .= sol.u[end] odet.psifac = sol.t[end] @@ -239,8 +240,8 @@ end renormalize_riccati_inplace!(u, N) In-place Riccati renormalization on an arbitrary N×N×2 array: - u[:,:,1] = U₁ · U₂⁻¹ (new S) - u[:,:,2] = I +u[:,:,1] = U₁ · U₂⁻¹ (new S) +u[:,:,2] = I The Riccati analogue of Gaussian reduction: keeps the inputs to `sing_der!` bounded. """ @@ -282,8 +283,8 @@ function integrate_propagator_chunk!( N = intr.numpert_total # Backward chunks integrate psi_end → psi_start, where forward-growing solutions decay. tspan = chunk.direction == 1 ? - (chunk.psi_start, chunk.psi_end) : - (chunk.psi_end, chunk.psi_start) + (chunk.psi_start, chunk.psi_end) : + (chunk.psi_end, chunk.psi_start) rtol = ctrl.algorithm.eulerlagrange_tolerance params = (ctrl, equil, mats, intr, odet_proxy, chunk) @@ -331,12 +332,12 @@ function integrate_fm_with_ua_ic( equil::Equilibrium.PlasmaEquilibrium, mats::MatrixSplines, intr::ForceFreeStatesInternal; - backward::Bool = false, - psi_ua::Float64 = NaN + backward::Bool=false, + psi_ua::Float64=NaN ) N = intr.numpert_total psi_start = chunks[first(chunk_range)].psi_start - psi_end = chunks[last(chunk_range)].psi_end + psi_end = chunks[last(chunk_range)].psi_end # ua lives at the inner-layer boundary psi_ua, which may differ slightly from the chunk boundary. if backward && !isnan(psi_ua) psi_end = psi_ua @@ -359,8 +360,8 @@ function integrate_fm_with_ua_ic( odet_proxy.mats_hint[] = 1 prob = ODEProblem(sing_der!, u0, tspan, params) sol = solve_el(prob, rtol) - result[1:N, 1:N] .= sol.u[end][:, :, 1] - result[N+1:2N, 1:N] .= sol.u[end][:, :, 2] + result[1:N, 1:N] .= sol.u[end][:, :, 1] + result[N+1:2N, 1:N] .= sol.u[end][:, :, 2] # Batch 2: columns N+1:2N of T (small solutions) u0[:, :, 1] .= ua[:, N+1:2N, 1] @@ -369,8 +370,8 @@ function integrate_fm_with_ua_ic( odet_proxy.mats_hint[] = 1 prob = ODEProblem(sing_der!, u0, tspan, params) sol = solve_el(prob, rtol) - result[1:N, N+1:2N] .= sol.u[end][:, :, 1] - result[N+1:2N, N+1:2N] .= sol.u[end][:, :, 2] + result[1:N, N+1:2N] .= sol.u[end][:, :, 1] + result[N+1:2N, N+1:2N] .= sol.u[end][:, :, 2] return result end @@ -422,8 +423,8 @@ function apply_propagator_inverse!(odet::OdeState, prop::ChunkPropagator) prop.block_upper_ic[:,:,2] prop.block_lower_ic[:,:,2]] #! format: on # x = Φ_bwd⁻¹ · [U₁_old; U₂_old] advances psi_start → psi_end. - u_old = [odet.u[:,:,1]; odet.u[:,:,2]] # 2N × N + u_old = [odet.u[:, :, 1]; odet.u[:, :, 2]] # 2N × N u_new = Φ \ u_old # LU solve, 2N × N - odet.u[:,:,1] .= u_new[1:N, :] - odet.u[:,:,2] .= u_new[N+1:2N, :] + odet.u[:, :, 1] .= u_new[1:N, :] + odet.u[:, :, 2] .= u_new[N+1:2N, :] end diff --git a/src/ForceFreeStates/Surfaces/Asymptotics.jl b/src/ForceFreeStates/Surfaces/Asymptotics.jl index 6f7345c799..0d3f3af248 100644 --- a/src/ForceFreeStates/Surfaces/Asymptotics.jl +++ b/src/ForceFreeStates/Surfaces/Asymptotics.jl @@ -97,7 +97,7 @@ function compute_sing_asymptotics( trace_floor = 64 * Base.eps(Float64) * opnorm(m0mat_block, Inf) sig > 0 && abs(block_tr) / 2 > max(1e-3 * abs(alpha_i), trace_floor) && @warn @sprintf("Resonant M₀ block at ψ=%.6f, m=%d (sig=%+.0f): |tr|/2 = %.3e is not small against |α| = %.3e", - singp.psifac, singp.m[i], sig, abs(block_tr) / 2, abs(alpha_i)) _id=(:resonant_trace, objectid(intr), singp.psifac, singp.m[i]) maxlog=1 + singp.psifac, singp.m[i], sig, abs(block_tr) / 2, abs(alpha_i)) _id = (:resonant_trace, objectid(intr), singp.psifac, singp.m[i]) maxlog = 1 r1_i = r1[i] power[r1_i] = p_big power[r1_i+intr.numpert_total] = p_small @@ -124,7 +124,7 @@ function compute_sing_asymptotics( msg *= @sprintf(" m0mat(1,2)= %+.12e %+.12ei\n", real(m0mat[1, 2]), imag(m0mat[1, 2])) msg *= @sprintf(" m0mat(2,1)= %+.12e %+.12ei\n", real(m0mat[2, 1]), imag(m0mat[2, 1])) msg *= @sprintf(" m0mat(2,2)= %+.12e %+.12ei\n", real(m0mat[2, 2]), imag(m0mat[2, 2])) - block_det = m0mat[1, 1]*m0mat[2, 2] - m0mat[2, 1]*m0mat[1, 2] + block_det = m0mat[1, 1] * m0mat[2, 2] - m0mat[2, 1] * m0mat[1, 2] block_tr = m0mat[1, 1] + m0mat[2, 2] msg *= @sprintf(" det= %+.12e, tr= %+.12e %+.12ei\n", real(block_det), real(block_tr), imag(block_tr)) msg *= @sprintf(" alpha= %+.12e %+.12ei\n", real(alpha[1]), imag(alpha[1])) diff --git a/src/ForceFreeStates/Surfaces/Finding.jl b/src/ForceFreeStates/Surfaces/Finding.jl index 61e8ec11b9..389e7c5431 100644 --- a/src/ForceFreeStates/Surfaces/Finding.jl +++ b/src/ForceFreeStates/Surfaces/Finding.jl @@ -131,9 +131,11 @@ function sing_lim!(intr::ForceFreeStatesInternal, ctrl::ForceFreeStatesControl, # Rational-offset cutoff needs the resolved single-n range on `intr`; multi-n falls back to qhigh / psihigh. 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 (resolve_mode_space!) before calling sing_lim!, " * - "or set truncate_at_rational_offset = false to truncate via qhigh / psihigh instead.") + error( + "sing_lim!: truncate_at_rational_offset = true requires a resolved toroidal range, but got intr.nlow=$(intr.nlow). " * + "Assign intr.nlow / intr.nhigh (resolve_mode_space!) before calling sing_lim!, " * + "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 (n = $(intr.nlow):$(intr.nhigh)); falling back to qhigh / psihigh truncation." elseif ctrl.truncate_at_rational_offset diff --git a/src/ForcingTerms/ForcingTerms.jl b/src/ForcingTerms/ForcingTerms.jl index eae14b0388..98738ad6ae 100644 --- a/src/ForcingTerms/ForcingTerms.jl +++ b/src/ForcingTerms/ForcingTerms.jl @@ -53,7 +53,6 @@ Data structure for a single forcing mode. - `n::Int` - Toroidal mode number - `m::Int` - Poloidal mode number - - `amplitude::ComplexF64` - Complex amplitude in unit-norm convention (= Fortran Phi_x, T·m² per unit-norm cell) after loading. File inputs are tagged by their input convention: @@ -367,7 +366,9 @@ end RMPField(modes::Vector{ForcingMode}; scale::Number=1.0) = RMPSpectrum([ForcingMode(; n=mode.n, m=mode.m, amplitude=mode.amplitude) for mode in modes], ComplexF64(scale)) -"Flattened weighted-leaf list of any [`RMPField`](@ref)." +""" +Flattened weighted-leaf list of any [`RMPField`](@ref). +""" _rmp_terms(f::Union{RMPSource,RMPSpectrum}) = Union{RMPSource,RMPSpectrum}[f] _rmp_terms(f::RMPFieldSum) = f.terms diff --git a/src/ForcingTerms/coil_geometries/jet_ex_mid.dat b/src/ForcingTerms/coil_geometries/jet_ex_mid.dat index ea2549ad3c..639e8ca414 100644 --- a/src/ForcingTerms/coil_geometries/jet_ex_mid.dat +++ b/src/ForcingTerms/coil_geometries/jet_ex_mid.dat @@ -1,169 +1,169 @@ - 4 1 42 1.00 - 5.5000e+00 0.0000e+00 3.0900e+00 - 5.4795e+00 4.7404e-01 3.0900e+00 - 5.4183e+00 9.4456e-01 3.0900e+00 - 5.3167e+00 1.4080e+00 3.0900e+00 - 5.1756e+00 1.8611e+00 3.0900e+00 - 4.9959e+00 2.3002e+00 3.0900e+00 - 4.7791e+00 2.7222e+00 3.0900e+00 - 4.5266e+00 3.1240e+00 3.0900e+00 - 4.2405e+00 3.5025e+00 3.0900e+00 - 3.9229e+00 3.8550e+00 3.0900e+00 - 3.5760e+00 4.1788e+00 3.0900e+00 - 3.2025e+00 4.4714e+00 3.0900e+00 - 2.8052e+00 4.7308e+00 3.0900e+00 - 2.3870e+00 4.9550e+00 3.0900e+00 - 1.9511e+00 5.1423e+00 3.0900e+00 - 1.5006e+00 5.2913e+00 3.0900e+00 - 1.0390e+00 5.4010e+00 3.0900e+00 - 5.6960e-01 5.4704e+00 3.0900e+00 - 9.5988e-02 5.4992e+00 3.0900e+00 - 9.5988e-02 5.4992e+00 3.0900e+00 - 9.5988e-02 5.4992e+00 1.5700e+00 - 9.5988e-02 5.4992e+00 5.0000e-02 - 9.5988e-02 5.4992e+00 -1.4700e+00 - 9.5988e-02 5.4992e+00 -2.9900e+00 - 7.0430e-01 5.4547e+00 -2.9900e+00 - 1.3039e+00 5.3432e+00 -2.9900e+00 - 1.8876e+00 5.1660e+00 -2.9900e+00 - 2.4479e+00 4.9252e+00 -2.9900e+00 - 2.9782e+00 4.6239e+00 -2.9900e+00 - 3.4719e+00 4.2657e+00 -2.9900e+00 - 3.9229e+00 3.8550e+00 -2.9900e+00 - 4.3256e+00 3.3969e+00 -2.9900e+00 - 4.6751e+00 2.8971e+00 -2.9900e+00 - 4.9672e+00 2.3616e+00 -2.9900e+00 - 5.1981e+00 1.7971e+00 -2.9900e+00 - 5.3651e+00 1.2105e+00 -2.9900e+00 - 5.4662e+00 6.0899e-01 -2.9900e+00 - 5.5000e+00 0.0000e+00 -2.9900e+00 - 5.5000e+00 0.0000e+00 -1.4700e+00 - 5.5000e+00 0.0000e+00 5.0000e-02 - 5.5000e+00 0.0000e+00 1.5700e+00 - 5.5000e+00 0.0000e+00 3.0900e+00 - 3.3678e-16 5.5000e+00 3.0900e+00 - -4.7404e-01 5.4795e+00 3.0900e+00 - -9.4456e-01 5.4183e+00 3.0900e+00 - -1.4080e+00 5.3167e+00 3.0900e+00 - -1.8611e+00 5.1756e+00 3.0900e+00 - -2.3002e+00 4.9959e+00 3.0900e+00 - -2.7222e+00 4.7791e+00 3.0900e+00 - -3.1240e+00 4.5266e+00 3.0900e+00 - -3.5025e+00 4.2405e+00 3.0900e+00 - -3.8550e+00 3.9229e+00 3.0900e+00 - -4.1788e+00 3.5760e+00 3.0900e+00 - -4.4714e+00 3.2025e+00 3.0900e+00 - -4.7308e+00 2.8052e+00 3.0900e+00 - -4.9550e+00 2.3870e+00 3.0900e+00 - -5.1423e+00 1.9511e+00 3.0900e+00 - -5.2913e+00 1.5006e+00 3.0900e+00 - -5.4010e+00 1.0390e+00 3.0900e+00 - -5.4704e+00 5.6960e-01 3.0900e+00 - -5.4992e+00 9.5988e-02 3.0900e+00 - -5.4992e+00 9.5988e-02 3.0900e+00 - -5.4992e+00 9.5988e-02 1.5700e+00 - -5.4992e+00 9.5988e-02 5.0000e-02 - -5.4992e+00 9.5988e-02 -1.4700e+00 - -5.4992e+00 9.5988e-02 -2.9900e+00 - -5.4547e+00 7.0430e-01 -2.9900e+00 - -5.3432e+00 1.3039e+00 -2.9900e+00 - -5.1660e+00 1.8876e+00 -2.9900e+00 - -4.9252e+00 2.4479e+00 -2.9900e+00 - -4.6239e+00 2.9782e+00 -2.9900e+00 - -4.2657e+00 3.4719e+00 -2.9900e+00 - -3.8550e+00 3.9229e+00 -2.9900e+00 - -3.3969e+00 4.3256e+00 -2.9900e+00 - -2.8971e+00 4.6751e+00 -2.9900e+00 - -2.3616e+00 4.9672e+00 -2.9900e+00 - -1.7971e+00 5.1981e+00 -2.9900e+00 - -1.2105e+00 5.3651e+00 -2.9900e+00 - -6.0899e-01 5.4662e+00 -2.9900e+00 - 3.3678e-16 5.5000e+00 -2.9900e+00 - 3.3678e-16 5.5000e+00 -1.4700e+00 - 3.3678e-16 5.5000e+00 5.0000e-02 - 3.3678e-16 5.5000e+00 1.5700e+00 - 3.3678e-16 5.5000e+00 3.0900e+00 - -5.5000e+00 6.7356e-16 3.0900e+00 - -5.4795e+00 -4.7404e-01 3.0900e+00 - -5.4183e+00 -9.4456e-01 3.0900e+00 - -5.3167e+00 -1.4080e+00 3.0900e+00 - -5.1756e+00 -1.8611e+00 3.0900e+00 - -4.9959e+00 -2.3002e+00 3.0900e+00 - -4.7791e+00 -2.7222e+00 3.0900e+00 - -4.5266e+00 -3.1240e+00 3.0900e+00 - -4.2405e+00 -3.5025e+00 3.0900e+00 - -3.9229e+00 -3.8550e+00 3.0900e+00 - -3.5760e+00 -4.1788e+00 3.0900e+00 - -3.2025e+00 -4.4714e+00 3.0900e+00 - -2.8052e+00 -4.7308e+00 3.0900e+00 - -2.3870e+00 -4.9550e+00 3.0900e+00 - -1.9511e+00 -5.1423e+00 3.0900e+00 - -1.5006e+00 -5.2913e+00 3.0900e+00 - -1.0390e+00 -5.4010e+00 3.0900e+00 - -5.6960e-01 -5.4704e+00 3.0900e+00 - -9.5988e-02 -5.4992e+00 3.0900e+00 - -9.5988e-02 -5.4992e+00 3.0900e+00 - -9.5988e-02 -5.4992e+00 1.5700e+00 - -9.5988e-02 -5.4992e+00 5.0000e-02 - -9.5988e-02 -5.4992e+00 -1.4700e+00 - -9.5988e-02 -5.4992e+00 -2.9900e+00 - -7.0430e-01 -5.4547e+00 -2.9900e+00 - -1.3039e+00 -5.3432e+00 -2.9900e+00 - -1.8876e+00 -5.1660e+00 -2.9900e+00 - -2.4479e+00 -4.9252e+00 -2.9900e+00 - -2.9782e+00 -4.6239e+00 -2.9900e+00 - -3.4719e+00 -4.2657e+00 -2.9900e+00 - -3.9229e+00 -3.8550e+00 -2.9900e+00 - -4.3256e+00 -3.3969e+00 -2.9900e+00 - -4.6751e+00 -2.8971e+00 -2.9900e+00 - -4.9672e+00 -2.3616e+00 -2.9900e+00 - -5.1981e+00 -1.7971e+00 -2.9900e+00 - -5.3651e+00 -1.2105e+00 -2.9900e+00 - -5.4662e+00 -6.0899e-01 -2.9900e+00 - -5.5000e+00 6.7356e-16 -2.9900e+00 - -5.5000e+00 6.7356e-16 -1.4700e+00 - -5.5000e+00 6.7356e-16 5.0000e-02 - -5.5000e+00 6.7356e-16 1.5700e+00 - -5.5000e+00 6.7356e-16 3.0900e+00 - -1.0103e-15 -5.5000e+00 3.0900e+00 - 4.7404e-01 -5.4795e+00 3.0900e+00 - 9.4456e-01 -5.4183e+00 3.0900e+00 - 1.4080e+00 -5.3167e+00 3.0900e+00 - 1.8611e+00 -5.1756e+00 3.0900e+00 - 2.3002e+00 -4.9959e+00 3.0900e+00 - 2.7222e+00 -4.7791e+00 3.0900e+00 - 3.1240e+00 -4.5266e+00 3.0900e+00 - 3.5025e+00 -4.2405e+00 3.0900e+00 - 3.8550e+00 -3.9229e+00 3.0900e+00 - 4.1788e+00 -3.5760e+00 3.0900e+00 - 4.4714e+00 -3.2025e+00 3.0900e+00 - 4.7308e+00 -2.8052e+00 3.0900e+00 - 4.9550e+00 -2.3870e+00 3.0900e+00 - 5.1423e+00 -1.9511e+00 3.0900e+00 - 5.2913e+00 -1.5006e+00 3.0900e+00 - 5.4010e+00 -1.0390e+00 3.0900e+00 - 5.4704e+00 -5.6960e-01 3.0900e+00 - 5.4992e+00 -9.5988e-02 3.0900e+00 - 5.4992e+00 -9.5988e-02 3.0900e+00 - 5.4992e+00 -9.5988e-02 1.5700e+00 - 5.4992e+00 -9.5988e-02 5.0000e-02 - 5.4992e+00 -9.5988e-02 -1.4700e+00 - 5.4992e+00 -9.5988e-02 -2.9900e+00 - 5.4547e+00 -7.0430e-01 -2.9900e+00 - 5.3432e+00 -1.3039e+00 -2.9900e+00 - 5.1660e+00 -1.8876e+00 -2.9900e+00 - 4.9252e+00 -2.4479e+00 -2.9900e+00 - 4.6239e+00 -2.9782e+00 -2.9900e+00 - 4.2657e+00 -3.4719e+00 -2.9900e+00 - 3.8550e+00 -3.9229e+00 -2.9900e+00 - 3.3969e+00 -4.3256e+00 -2.9900e+00 - 2.8971e+00 -4.6751e+00 -2.9900e+00 - 2.3616e+00 -4.9672e+00 -2.9900e+00 - 1.7971e+00 -5.1981e+00 -2.9900e+00 - 1.2105e+00 -5.3651e+00 -2.9900e+00 - 6.0899e-01 -5.4662e+00 -2.9900e+00 - -1.0103e-15 -5.5000e+00 -2.9900e+00 - -1.0103e-15 -5.5000e+00 -1.4700e+00 - -1.0103e-15 -5.5000e+00 5.0000e-02 - -1.0103e-15 -5.5000e+00 1.5700e+00 - -1.0103e-15 -5.5000e+00 3.0900e+00 + 4 1 42 1.00 + 5.5000e+00 0.0000e+00 3.0900e+00 + 5.4795e+00 4.7404e-01 3.0900e+00 + 5.4183e+00 9.4456e-01 3.0900e+00 + 5.3167e+00 1.4080e+00 3.0900e+00 + 5.1756e+00 1.8611e+00 3.0900e+00 + 4.9959e+00 2.3002e+00 3.0900e+00 + 4.7791e+00 2.7222e+00 3.0900e+00 + 4.5266e+00 3.1240e+00 3.0900e+00 + 4.2405e+00 3.5025e+00 3.0900e+00 + 3.9229e+00 3.8550e+00 3.0900e+00 + 3.5760e+00 4.1788e+00 3.0900e+00 + 3.2025e+00 4.4714e+00 3.0900e+00 + 2.8052e+00 4.7308e+00 3.0900e+00 + 2.3870e+00 4.9550e+00 3.0900e+00 + 1.9511e+00 5.1423e+00 3.0900e+00 + 1.5006e+00 5.2913e+00 3.0900e+00 + 1.0390e+00 5.4010e+00 3.0900e+00 + 5.6960e-01 5.4704e+00 3.0900e+00 + 9.5988e-02 5.4992e+00 3.0900e+00 + 9.5988e-02 5.4992e+00 3.0900e+00 + 9.5988e-02 5.4992e+00 1.5700e+00 + 9.5988e-02 5.4992e+00 5.0000e-02 + 9.5988e-02 5.4992e+00 -1.4700e+00 + 9.5988e-02 5.4992e+00 -2.9900e+00 + 7.0430e-01 5.4547e+00 -2.9900e+00 + 1.3039e+00 5.3432e+00 -2.9900e+00 + 1.8876e+00 5.1660e+00 -2.9900e+00 + 2.4479e+00 4.9252e+00 -2.9900e+00 + 2.9782e+00 4.6239e+00 -2.9900e+00 + 3.4719e+00 4.2657e+00 -2.9900e+00 + 3.9229e+00 3.8550e+00 -2.9900e+00 + 4.3256e+00 3.3969e+00 -2.9900e+00 + 4.6751e+00 2.8971e+00 -2.9900e+00 + 4.9672e+00 2.3616e+00 -2.9900e+00 + 5.1981e+00 1.7971e+00 -2.9900e+00 + 5.3651e+00 1.2105e+00 -2.9900e+00 + 5.4662e+00 6.0899e-01 -2.9900e+00 + 5.5000e+00 0.0000e+00 -2.9900e+00 + 5.5000e+00 0.0000e+00 -1.4700e+00 + 5.5000e+00 0.0000e+00 5.0000e-02 + 5.5000e+00 0.0000e+00 1.5700e+00 + 5.5000e+00 0.0000e+00 3.0900e+00 + 3.3678e-16 5.5000e+00 3.0900e+00 + -4.7404e-01 5.4795e+00 3.0900e+00 + -9.4456e-01 5.4183e+00 3.0900e+00 + -1.4080e+00 5.3167e+00 3.0900e+00 + -1.8611e+00 5.1756e+00 3.0900e+00 + -2.3002e+00 4.9959e+00 3.0900e+00 + -2.7222e+00 4.7791e+00 3.0900e+00 + -3.1240e+00 4.5266e+00 3.0900e+00 + -3.5025e+00 4.2405e+00 3.0900e+00 + -3.8550e+00 3.9229e+00 3.0900e+00 + -4.1788e+00 3.5760e+00 3.0900e+00 + -4.4714e+00 3.2025e+00 3.0900e+00 + -4.7308e+00 2.8052e+00 3.0900e+00 + -4.9550e+00 2.3870e+00 3.0900e+00 + -5.1423e+00 1.9511e+00 3.0900e+00 + -5.2913e+00 1.5006e+00 3.0900e+00 + -5.4010e+00 1.0390e+00 3.0900e+00 + -5.4704e+00 5.6960e-01 3.0900e+00 + -5.4992e+00 9.5988e-02 3.0900e+00 + -5.4992e+00 9.5988e-02 3.0900e+00 + -5.4992e+00 9.5988e-02 1.5700e+00 + -5.4992e+00 9.5988e-02 5.0000e-02 + -5.4992e+00 9.5988e-02 -1.4700e+00 + -5.4992e+00 9.5988e-02 -2.9900e+00 + -5.4547e+00 7.0430e-01 -2.9900e+00 + -5.3432e+00 1.3039e+00 -2.9900e+00 + -5.1660e+00 1.8876e+00 -2.9900e+00 + -4.9252e+00 2.4479e+00 -2.9900e+00 + -4.6239e+00 2.9782e+00 -2.9900e+00 + -4.2657e+00 3.4719e+00 -2.9900e+00 + -3.8550e+00 3.9229e+00 -2.9900e+00 + -3.3969e+00 4.3256e+00 -2.9900e+00 + -2.8971e+00 4.6751e+00 -2.9900e+00 + -2.3616e+00 4.9672e+00 -2.9900e+00 + -1.7971e+00 5.1981e+00 -2.9900e+00 + -1.2105e+00 5.3651e+00 -2.9900e+00 + -6.0899e-01 5.4662e+00 -2.9900e+00 + 3.3678e-16 5.5000e+00 -2.9900e+00 + 3.3678e-16 5.5000e+00 -1.4700e+00 + 3.3678e-16 5.5000e+00 5.0000e-02 + 3.3678e-16 5.5000e+00 1.5700e+00 + 3.3678e-16 5.5000e+00 3.0900e+00 + -5.5000e+00 6.7356e-16 3.0900e+00 + -5.4795e+00 -4.7404e-01 3.0900e+00 + -5.4183e+00 -9.4456e-01 3.0900e+00 + -5.3167e+00 -1.4080e+00 3.0900e+00 + -5.1756e+00 -1.8611e+00 3.0900e+00 + -4.9959e+00 -2.3002e+00 3.0900e+00 + -4.7791e+00 -2.7222e+00 3.0900e+00 + -4.5266e+00 -3.1240e+00 3.0900e+00 + -4.2405e+00 -3.5025e+00 3.0900e+00 + -3.9229e+00 -3.8550e+00 3.0900e+00 + -3.5760e+00 -4.1788e+00 3.0900e+00 + -3.2025e+00 -4.4714e+00 3.0900e+00 + -2.8052e+00 -4.7308e+00 3.0900e+00 + -2.3870e+00 -4.9550e+00 3.0900e+00 + -1.9511e+00 -5.1423e+00 3.0900e+00 + -1.5006e+00 -5.2913e+00 3.0900e+00 + -1.0390e+00 -5.4010e+00 3.0900e+00 + -5.6960e-01 -5.4704e+00 3.0900e+00 + -9.5988e-02 -5.4992e+00 3.0900e+00 + -9.5988e-02 -5.4992e+00 3.0900e+00 + -9.5988e-02 -5.4992e+00 1.5700e+00 + -9.5988e-02 -5.4992e+00 5.0000e-02 + -9.5988e-02 -5.4992e+00 -1.4700e+00 + -9.5988e-02 -5.4992e+00 -2.9900e+00 + -7.0430e-01 -5.4547e+00 -2.9900e+00 + -1.3039e+00 -5.3432e+00 -2.9900e+00 + -1.8876e+00 -5.1660e+00 -2.9900e+00 + -2.4479e+00 -4.9252e+00 -2.9900e+00 + -2.9782e+00 -4.6239e+00 -2.9900e+00 + -3.4719e+00 -4.2657e+00 -2.9900e+00 + -3.9229e+00 -3.8550e+00 -2.9900e+00 + -4.3256e+00 -3.3969e+00 -2.9900e+00 + -4.6751e+00 -2.8971e+00 -2.9900e+00 + -4.9672e+00 -2.3616e+00 -2.9900e+00 + -5.1981e+00 -1.7971e+00 -2.9900e+00 + -5.3651e+00 -1.2105e+00 -2.9900e+00 + -5.4662e+00 -6.0899e-01 -2.9900e+00 + -5.5000e+00 6.7356e-16 -2.9900e+00 + -5.5000e+00 6.7356e-16 -1.4700e+00 + -5.5000e+00 6.7356e-16 5.0000e-02 + -5.5000e+00 6.7356e-16 1.5700e+00 + -5.5000e+00 6.7356e-16 3.0900e+00 + -1.0103e-15 -5.5000e+00 3.0900e+00 + 4.7404e-01 -5.4795e+00 3.0900e+00 + 9.4456e-01 -5.4183e+00 3.0900e+00 + 1.4080e+00 -5.3167e+00 3.0900e+00 + 1.8611e+00 -5.1756e+00 3.0900e+00 + 2.3002e+00 -4.9959e+00 3.0900e+00 + 2.7222e+00 -4.7791e+00 3.0900e+00 + 3.1240e+00 -4.5266e+00 3.0900e+00 + 3.5025e+00 -4.2405e+00 3.0900e+00 + 3.8550e+00 -3.9229e+00 3.0900e+00 + 4.1788e+00 -3.5760e+00 3.0900e+00 + 4.4714e+00 -3.2025e+00 3.0900e+00 + 4.7308e+00 -2.8052e+00 3.0900e+00 + 4.9550e+00 -2.3870e+00 3.0900e+00 + 5.1423e+00 -1.9511e+00 3.0900e+00 + 5.2913e+00 -1.5006e+00 3.0900e+00 + 5.4010e+00 -1.0390e+00 3.0900e+00 + 5.4704e+00 -5.6960e-01 3.0900e+00 + 5.4992e+00 -9.5988e-02 3.0900e+00 + 5.4992e+00 -9.5988e-02 3.0900e+00 + 5.4992e+00 -9.5988e-02 1.5700e+00 + 5.4992e+00 -9.5988e-02 5.0000e-02 + 5.4992e+00 -9.5988e-02 -1.4700e+00 + 5.4992e+00 -9.5988e-02 -2.9900e+00 + 5.4547e+00 -7.0430e-01 -2.9900e+00 + 5.3432e+00 -1.3039e+00 -2.9900e+00 + 5.1660e+00 -1.8876e+00 -2.9900e+00 + 4.9252e+00 -2.4479e+00 -2.9900e+00 + 4.6239e+00 -2.9782e+00 -2.9900e+00 + 4.2657e+00 -3.4719e+00 -2.9900e+00 + 3.8550e+00 -3.9229e+00 -2.9900e+00 + 3.3969e+00 -4.3256e+00 -2.9900e+00 + 2.8971e+00 -4.6751e+00 -2.9900e+00 + 2.3616e+00 -4.9672e+00 -2.9900e+00 + 1.7971e+00 -5.1981e+00 -2.9900e+00 + 1.2105e+00 -5.3651e+00 -2.9900e+00 + 6.0899e-01 -5.4662e+00 -2.9900e+00 + -1.0103e-15 -5.5000e+00 -2.9900e+00 + -1.0103e-15 -5.5000e+00 -1.4700e+00 + -1.0103e-15 -5.5000e+00 5.0000e-02 + -1.0103e-15 -5.5000e+00 1.5700e+00 + -1.0103e-15 -5.5000e+00 3.0900e+00 diff --git a/src/ForcingTerms/coil_geometries/jtext_l.dat b/src/ForcingTerms/coil_geometries/jtext_l.dat index b744bedd8d..476b554510 100644 --- a/src/ForcingTerms/coil_geometries/jtext_l.dat +++ b/src/ForcingTerms/coil_geometries/jtext_l.dat @@ -1,81 +1,81 @@ - 4 1 20 1.00 - 1.2000e+00 1.6000e-02 -3.9800e-01 - 1.2000e+00 1.6000e-02 -3.3300e-01 - 1.2000e+00 2.5370e-02 -3.1000e-01 - 1.2000e+00 4.8000e-02 -3.0100e-01 - 1.2000e+00 1.3200e-01 -3.0100e-01 - 1.1880e+00 1.5800e-01 -3.0100e-01 - 1.1620e+00 1.6300e-01 -3.0100e-01 - 7.9400e-01 9.0000e-02 -3.0100e-01 - 7.7500e-01 7.9000e-02 -3.0100e-01 - 7.6800e-01 5.8000e-02 -3.0100e-01 - 7.6800e-01 -6.2000e-02 -3.0100e-01 - 7.7500e-01 -8.3000e-02 -3.0100e-01 - 7.9500e-01 -9.4000e-02 -3.0100e-01 - 1.1620e+00 -1.6700e-01 -3.0100e-01 - 1.1880e+00 -1.6000e-01 -3.0100e-01 - 1.2000e+00 -1.3500e-01 -3.0100e-01 - 1.2000e+00 -5.0000e-02 -3.0100e-01 - 1.2000e+00 -2.5370e-02 -3.1000e-01 - 1.2000e+00 -1.6000e-02 -3.3300e-01 - 1.2000e+00 -1.6000e-02 -3.9800e-01 - 1.6000e-02 1.2000e+00 -3.9800e-01 - 1.6000e-02 1.2000e+00 -3.3300e-01 - 2.5370e-02 1.2000e+00 -3.1000e-01 - 4.8000e-02 1.2000e+00 -3.0100e-01 - 1.3200e-01 1.2000e+00 -3.0100e-01 - 1.5800e-01 1.1880e+00 -3.0100e-01 - 1.6300e-01 1.1620e+00 -3.0100e-01 - 9.0000e-02 7.9400e-01 -3.0100e-01 - 7.9000e-02 7.7500e-01 -3.0100e-01 - 5.8000e-02 7.6800e-01 -3.0100e-01 - -6.2000e-02 7.6800e-01 -3.0100e-01 - -8.3000e-02 7.7500e-01 -3.0100e-01 - -9.4000e-02 7.9500e-01 -3.0100e-01 - -1.6700e-01 1.1620e+00 -3.0100e-01 - -1.6000e-01 1.1880e+00 -3.0100e-01 - -1.3500e-01 1.2000e+00 -3.0100e-01 - -5.0000e-02 1.2000e+00 -3.0100e-01 - -2.5370e-02 1.2000e+00 -3.1000e-01 - -1.6000e-02 1.2000e+00 -3.3300e-01 - -1.6000e-02 1.2000e+00 -3.9800e-01 - -7.7650e-01 1.6000e-02 -3.9800e-01 - -7.7650e-01 1.6000e-02 -3.3300e-01 - -7.7650e-01 2.5370e-02 -3.1000e-01 - -7.7650e-01 4.8000e-02 -3.0100e-01 - -7.7650e-01 6.1000e-02 -3.0100e-01 - -7.8350e-01 8.2000e-02 -3.0100e-01 - -8.0250e-01 9.3000e-02 -3.0100e-01 - -1.2015e+00 1.7100e-01 -3.0100e-01 - -1.2275e+00 1.6500e-01 -3.0100e-01 - -1.2395e+00 1.4100e-01 -3.0100e-01 - -1.2395e+00 -1.4100e-01 -3.0100e-01 - -1.2275e+00 -1.6600e-01 -3.0100e-01 - -1.2015e+00 -1.7200e-01 -3.0100e-01 - -8.0250e-01 -9.3000e-02 -3.0100e-01 - -7.8350e-01 -8.2000e-02 -3.0100e-01 - -7.7650e-01 -6.2000e-02 -3.0100e-01 - -7.7650e-01 -4.9000e-02 -3.0100e-01 - -7.7650e-01 -2.5370e-02 -3.1000e-01 - -7.7650e-01 -1.6000e-02 -3.3300e-01 - -7.7650e-01 -1.6000e-02 -3.9800e-01 - 1.6000e-02 -7.7650e-01 -3.9800e-01 - 1.6000e-02 -7.7650e-01 -3.3300e-01 - 2.5370e-02 -7.7650e-01 -3.1000e-01 - 4.8000e-02 -7.7650e-01 -3.0100e-01 - 6.1000e-02 -7.7650e-01 -3.0100e-01 - 8.2000e-02 -7.8350e-01 -3.0100e-01 - 9.3000e-02 -8.0250e-01 -3.0100e-01 - 1.7100e-01 -1.2015e+00 -3.0100e-01 - 1.6500e-01 -1.2275e+00 -3.0100e-01 - 1.4100e-01 -1.2395e+00 -3.0100e-01 - -1.4100e-01 -1.2395e+00 -3.0100e-01 - -1.6600e-01 -1.2275e+00 -3.0100e-01 - -1.7200e-01 -1.2015e+00 -3.0100e-01 - -9.3000e-02 -8.0250e-01 -3.0100e-01 - -8.2000e-02 -7.8350e-01 -3.0100e-01 - -6.2000e-02 -7.7650e-01 -3.0100e-01 - -4.9000e-02 -7.7650e-01 -3.0100e-01 - -2.5370e-02 -7.7650e-01 -3.1000e-01 - -1.6000e-02 -7.7650e-01 -3.3300e-01 - -1.6000e-02 -7.7650e-01 -3.9800e-01 + 4 1 20 1.00 + 1.2000e+00 1.6000e-02 -3.9800e-01 + 1.2000e+00 1.6000e-02 -3.3300e-01 + 1.2000e+00 2.5370e-02 -3.1000e-01 + 1.2000e+00 4.8000e-02 -3.0100e-01 + 1.2000e+00 1.3200e-01 -3.0100e-01 + 1.1880e+00 1.5800e-01 -3.0100e-01 + 1.1620e+00 1.6300e-01 -3.0100e-01 + 7.9400e-01 9.0000e-02 -3.0100e-01 + 7.7500e-01 7.9000e-02 -3.0100e-01 + 7.6800e-01 5.8000e-02 -3.0100e-01 + 7.6800e-01 -6.2000e-02 -3.0100e-01 + 7.7500e-01 -8.3000e-02 -3.0100e-01 + 7.9500e-01 -9.4000e-02 -3.0100e-01 + 1.1620e+00 -1.6700e-01 -3.0100e-01 + 1.1880e+00 -1.6000e-01 -3.0100e-01 + 1.2000e+00 -1.3500e-01 -3.0100e-01 + 1.2000e+00 -5.0000e-02 -3.0100e-01 + 1.2000e+00 -2.5370e-02 -3.1000e-01 + 1.2000e+00 -1.6000e-02 -3.3300e-01 + 1.2000e+00 -1.6000e-02 -3.9800e-01 + 1.6000e-02 1.2000e+00 -3.9800e-01 + 1.6000e-02 1.2000e+00 -3.3300e-01 + 2.5370e-02 1.2000e+00 -3.1000e-01 + 4.8000e-02 1.2000e+00 -3.0100e-01 + 1.3200e-01 1.2000e+00 -3.0100e-01 + 1.5800e-01 1.1880e+00 -3.0100e-01 + 1.6300e-01 1.1620e+00 -3.0100e-01 + 9.0000e-02 7.9400e-01 -3.0100e-01 + 7.9000e-02 7.7500e-01 -3.0100e-01 + 5.8000e-02 7.6800e-01 -3.0100e-01 + -6.2000e-02 7.6800e-01 -3.0100e-01 + -8.3000e-02 7.7500e-01 -3.0100e-01 + -9.4000e-02 7.9500e-01 -3.0100e-01 + -1.6700e-01 1.1620e+00 -3.0100e-01 + -1.6000e-01 1.1880e+00 -3.0100e-01 + -1.3500e-01 1.2000e+00 -3.0100e-01 + -5.0000e-02 1.2000e+00 -3.0100e-01 + -2.5370e-02 1.2000e+00 -3.1000e-01 + -1.6000e-02 1.2000e+00 -3.3300e-01 + -1.6000e-02 1.2000e+00 -3.9800e-01 + -7.7650e-01 1.6000e-02 -3.9800e-01 + -7.7650e-01 1.6000e-02 -3.3300e-01 + -7.7650e-01 2.5370e-02 -3.1000e-01 + -7.7650e-01 4.8000e-02 -3.0100e-01 + -7.7650e-01 6.1000e-02 -3.0100e-01 + -7.8350e-01 8.2000e-02 -3.0100e-01 + -8.0250e-01 9.3000e-02 -3.0100e-01 + -1.2015e+00 1.7100e-01 -3.0100e-01 + -1.2275e+00 1.6500e-01 -3.0100e-01 + -1.2395e+00 1.4100e-01 -3.0100e-01 + -1.2395e+00 -1.4100e-01 -3.0100e-01 + -1.2275e+00 -1.6600e-01 -3.0100e-01 + -1.2015e+00 -1.7200e-01 -3.0100e-01 + -8.0250e-01 -9.3000e-02 -3.0100e-01 + -7.8350e-01 -8.2000e-02 -3.0100e-01 + -7.7650e-01 -6.2000e-02 -3.0100e-01 + -7.7650e-01 -4.9000e-02 -3.0100e-01 + -7.7650e-01 -2.5370e-02 -3.1000e-01 + -7.7650e-01 -1.6000e-02 -3.3300e-01 + -7.7650e-01 -1.6000e-02 -3.9800e-01 + 1.6000e-02 -7.7650e-01 -3.9800e-01 + 1.6000e-02 -7.7650e-01 -3.3300e-01 + 2.5370e-02 -7.7650e-01 -3.1000e-01 + 4.8000e-02 -7.7650e-01 -3.0100e-01 + 6.1000e-02 -7.7650e-01 -3.0100e-01 + 8.2000e-02 -7.8350e-01 -3.0100e-01 + 9.3000e-02 -8.0250e-01 -3.0100e-01 + 1.7100e-01 -1.2015e+00 -3.0100e-01 + 1.6500e-01 -1.2275e+00 -3.0100e-01 + 1.4100e-01 -1.2395e+00 -3.0100e-01 + -1.4100e-01 -1.2395e+00 -3.0100e-01 + -1.6600e-01 -1.2275e+00 -3.0100e-01 + -1.7200e-01 -1.2015e+00 -3.0100e-01 + -9.3000e-02 -8.0250e-01 -3.0100e-01 + -8.2000e-02 -7.8350e-01 -3.0100e-01 + -6.2000e-02 -7.7650e-01 -3.0100e-01 + -4.9000e-02 -7.7650e-01 -3.0100e-01 + -2.5370e-02 -7.7650e-01 -3.1000e-01 + -1.6000e-02 -7.7650e-01 -3.3300e-01 + -1.6000e-02 -7.7650e-01 -3.9800e-01 diff --git a/src/ForcingTerms/coil_geometries/jtext_m.dat b/src/ForcingTerms/coil_geometries/jtext_m.dat index f211a2d1e5..9defc821f6 100644 --- a/src/ForcingTerms/coil_geometries/jtext_m.dat +++ b/src/ForcingTerms/coil_geometries/jtext_m.dat @@ -1,81 +1,81 @@ - 4 1 20 1.00 - 1.3300e+00 1.6000e-02 3.5600e-01 - 1.2830e+00 1.6000e-02 2.6500e-01 - 1.3065e+00 2.5370e-02 2.4300e-01 - 1.3300e+00 4.8000e-02 2.1100e-01 - 1.3300e+00 1.3200e-01 2.1100e-01 - 1.3300e+00 1.5800e-01 2.0200e-01 - 1.3300e+00 1.6300e-01 1.7900e-01 - 1.3300e+00 9.0000e-02 -2.0600e-01 - 1.3300e+00 7.9000e-02 -2.2900e-01 - 1.3300e+00 5.8000e-02 -2.3800e-01 - 1.3300e+00 -6.2000e-02 -2.3800e-01 - 1.3300e+00 -8.3000e-02 -2.2900e-01 - 1.3300e+00 -9.4000e-02 -2.0600e-01 - 1.3300e+00 -1.6700e-01 1.7900e-01 - 1.3300e+00 -1.6000e-01 2.0200e-01 - 1.3300e+00 -1.3500e-01 2.1100e-01 - 1.3300e+00 -5.0000e-02 2.1100e-01 - 1.3065e+00 -2.5370e-02 2.4300e-01 - 1.2830e+00 -1.6000e-02 2.6500e-01 - 1.3300e+00 -1.6000e-02 3.5600e-01 - -8.7000e-02 1.2000e+00 -3.5600e-01 - -8.7000e-02 1.2000e+00 -2.6500e-01 - -7.1000e-02 1.2000e+00 -2.4300e-01 - -5.5000e-02 1.2000e+00 -2.1100e-01 - 1.6900e-01 1.2000e+00 -2.1100e-01 - 1.9100e-01 1.1880e+00 -2.0200e-01 - 2.0100e-01 1.1620e+00 -1.7900e-01 - 2.0100e-01 7.9400e-01 2.0600e-01 - 1.9100e-01 7.7500e-01 2.2900e-01 - 1.6900e-01 7.6800e-01 2.3800e-01 - -1.6800e-01 7.6800e-01 2.3800e-01 - -1.9100e-01 7.7500e-01 2.2900e-01 - -2.0000e-01 7.9500e-01 2.0600e-01 - -2.0000e-01 1.1620e+00 -1.7900e-01 - -1.9100e-01 1.1880e+00 -2.0200e-01 - -1.6800e-01 1.2000e+00 -2.1100e-01 - -1.5100e-01 1.2000e+00 -2.1100e-01 - -1.3500e-01 1.2000e+00 -2.4300e-01 - -1.1900e-01 1.2000e+00 -2.6500e-01 - -1.1900e-01 1.2000e+00 -3.5600e-01 - -1.3300e+00 1.6000e-02 3.5600e-01 - -1.2830e+00 1.6000e-02 2.6500e-01 - -1.3065e+00 2.5370e-02 2.4300e-01 - -1.3300e+00 4.8000e-02 2.1100e-01 - -1.3300e+00 6.1000e-02 2.1100e-01 - -1.3300e+00 8.2000e-02 2.0200e-01 - -1.3300e+00 9.3000e-02 1.7900e-01 - -1.3300e+00 1.7100e-01 -2.0600e-01 - -1.3300e+00 1.6500e-01 -2.2900e-01 - -1.3300e+00 1.4100e-01 -2.3800e-01 - -1.3300e+00 -1.4100e-01 -2.3800e-01 - -1.3300e+00 -1.6600e-01 -2.2900e-01 - -1.3300e+00 -1.7200e-01 -2.0600e-01 - -1.3300e+00 -9.3000e-02 1.7900e-01 - -1.3300e+00 -8.2000e-02 2.0200e-01 - -1.3300e+00 -6.2000e-02 2.1100e-01 - -1.3300e+00 -4.9000e-02 2.1100e-01 - -1.3065e+00 -2.5370e-02 2.4300e-01 - -1.2830e+00 -1.6000e-02 2.6500e-01 - -1.3300e+00 -1.6000e-02 3.5600e-01 - 8.7000e-02 -7.7650e-01 -3.5600e-01 - 8.7000e-02 -7.7650e-01 -2.6500e-01 - 7.1000e-02 -7.7650e-01 -2.4300e-01 - 5.5000e-02 -7.7650e-01 -2.1100e-01 - 5.5000e-02 -7.7650e-01 -2.1100e-01 - 3.2600e-02 -7.8350e-01 -2.0200e-01 - 2.2600e-02 -8.0250e-01 -1.7900e-01 - 2.2600e-02 -1.2015e+00 2.0600e-01 - 3.2600e-02 -1.2275e+00 2.2900e-01 - 5.4600e-02 -1.2395e+00 2.3800e-01 - 1.6800e-01 -1.2395e+00 2.3800e-01 - 1.9100e-01 -1.2275e+00 2.2900e-01 - 2.0000e-01 -1.2015e+00 2.0600e-01 - 2.0000e-01 -8.0250e-01 -1.7900e-01 - 1.9100e-01 -7.8350e-01 -2.0200e-01 - 1.6800e-01 -7.7650e-01 -2.1100e-01 - 1.5100e-01 -7.7650e-01 -2.1100e-01 - 1.3500e-01 -7.7650e-01 -2.4300e-01 - 1.1900e-01 -7.7650e-01 -2.6500e-01 - 1.1900e-01 -7.7650e-01 -3.5600e-01 + 4 1 20 1.00 + 1.3300e+00 1.6000e-02 3.5600e-01 + 1.2830e+00 1.6000e-02 2.6500e-01 + 1.3065e+00 2.5370e-02 2.4300e-01 + 1.3300e+00 4.8000e-02 2.1100e-01 + 1.3300e+00 1.3200e-01 2.1100e-01 + 1.3300e+00 1.5800e-01 2.0200e-01 + 1.3300e+00 1.6300e-01 1.7900e-01 + 1.3300e+00 9.0000e-02 -2.0600e-01 + 1.3300e+00 7.9000e-02 -2.2900e-01 + 1.3300e+00 5.8000e-02 -2.3800e-01 + 1.3300e+00 -6.2000e-02 -2.3800e-01 + 1.3300e+00 -8.3000e-02 -2.2900e-01 + 1.3300e+00 -9.4000e-02 -2.0600e-01 + 1.3300e+00 -1.6700e-01 1.7900e-01 + 1.3300e+00 -1.6000e-01 2.0200e-01 + 1.3300e+00 -1.3500e-01 2.1100e-01 + 1.3300e+00 -5.0000e-02 2.1100e-01 + 1.3065e+00 -2.5370e-02 2.4300e-01 + 1.2830e+00 -1.6000e-02 2.6500e-01 + 1.3300e+00 -1.6000e-02 3.5600e-01 + -8.7000e-02 1.2000e+00 -3.5600e-01 + -8.7000e-02 1.2000e+00 -2.6500e-01 + -7.1000e-02 1.2000e+00 -2.4300e-01 + -5.5000e-02 1.2000e+00 -2.1100e-01 + 1.6900e-01 1.2000e+00 -2.1100e-01 + 1.9100e-01 1.1880e+00 -2.0200e-01 + 2.0100e-01 1.1620e+00 -1.7900e-01 + 2.0100e-01 7.9400e-01 2.0600e-01 + 1.9100e-01 7.7500e-01 2.2900e-01 + 1.6900e-01 7.6800e-01 2.3800e-01 + -1.6800e-01 7.6800e-01 2.3800e-01 + -1.9100e-01 7.7500e-01 2.2900e-01 + -2.0000e-01 7.9500e-01 2.0600e-01 + -2.0000e-01 1.1620e+00 -1.7900e-01 + -1.9100e-01 1.1880e+00 -2.0200e-01 + -1.6800e-01 1.2000e+00 -2.1100e-01 + -1.5100e-01 1.2000e+00 -2.1100e-01 + -1.3500e-01 1.2000e+00 -2.4300e-01 + -1.1900e-01 1.2000e+00 -2.6500e-01 + -1.1900e-01 1.2000e+00 -3.5600e-01 + -1.3300e+00 1.6000e-02 3.5600e-01 + -1.2830e+00 1.6000e-02 2.6500e-01 + -1.3065e+00 2.5370e-02 2.4300e-01 + -1.3300e+00 4.8000e-02 2.1100e-01 + -1.3300e+00 6.1000e-02 2.1100e-01 + -1.3300e+00 8.2000e-02 2.0200e-01 + -1.3300e+00 9.3000e-02 1.7900e-01 + -1.3300e+00 1.7100e-01 -2.0600e-01 + -1.3300e+00 1.6500e-01 -2.2900e-01 + -1.3300e+00 1.4100e-01 -2.3800e-01 + -1.3300e+00 -1.4100e-01 -2.3800e-01 + -1.3300e+00 -1.6600e-01 -2.2900e-01 + -1.3300e+00 -1.7200e-01 -2.0600e-01 + -1.3300e+00 -9.3000e-02 1.7900e-01 + -1.3300e+00 -8.2000e-02 2.0200e-01 + -1.3300e+00 -6.2000e-02 2.1100e-01 + -1.3300e+00 -4.9000e-02 2.1100e-01 + -1.3065e+00 -2.5370e-02 2.4300e-01 + -1.2830e+00 -1.6000e-02 2.6500e-01 + -1.3300e+00 -1.6000e-02 3.5600e-01 + 8.7000e-02 -7.7650e-01 -3.5600e-01 + 8.7000e-02 -7.7650e-01 -2.6500e-01 + 7.1000e-02 -7.7650e-01 -2.4300e-01 + 5.5000e-02 -7.7650e-01 -2.1100e-01 + 5.5000e-02 -7.7650e-01 -2.1100e-01 + 3.2600e-02 -7.8350e-01 -2.0200e-01 + 2.2600e-02 -8.0250e-01 -1.7900e-01 + 2.2600e-02 -1.2015e+00 2.0600e-01 + 3.2600e-02 -1.2275e+00 2.2900e-01 + 5.4600e-02 -1.2395e+00 2.3800e-01 + 1.6800e-01 -1.2395e+00 2.3800e-01 + 1.9100e-01 -1.2275e+00 2.2900e-01 + 2.0000e-01 -1.2015e+00 2.0600e-01 + 2.0000e-01 -8.0250e-01 -1.7900e-01 + 1.9100e-01 -7.8350e-01 -2.0200e-01 + 1.6800e-01 -7.7650e-01 -2.1100e-01 + 1.5100e-01 -7.7650e-01 -2.1100e-01 + 1.3500e-01 -7.7650e-01 -2.4300e-01 + 1.1900e-01 -7.7650e-01 -2.6500e-01 + 1.1900e-01 -7.7650e-01 -3.5600e-01 diff --git a/src/ForcingTerms/coil_geometries/jtext_u.dat b/src/ForcingTerms/coil_geometries/jtext_u.dat index 45404b00f0..b70ef435f6 100644 --- a/src/ForcingTerms/coil_geometries/jtext_u.dat +++ b/src/ForcingTerms/coil_geometries/jtext_u.dat @@ -1,81 +1,81 @@ - 4 1 20 1.00 - 1.2000e+00 1.6000e-02 3.9800e-01 - 1.2000e+00 1.6000e-02 3.3300e-01 - 1.2000e+00 2.5370e-02 3.1000e-01 - 1.2000e+00 4.8000e-02 3.0100e-01 - 1.2000e+00 1.3200e-01 3.0100e-01 - 1.1880e+00 1.5800e-01 3.0100e-01 - 1.1620e+00 1.6300e-01 3.0100e-01 - 7.9400e-01 9.0000e-02 3.0100e-01 - 7.7500e-01 7.9000e-02 3.0100e-01 - 7.6800e-01 5.8000e-02 3.0100e-01 - 7.6800e-01 -6.2000e-02 3.0100e-01 - 7.7500e-01 -8.3000e-02 3.0100e-01 - 7.9500e-01 -9.4000e-02 3.0100e-01 - 1.1620e+00 -1.6700e-01 3.0100e-01 - 1.1880e+00 -1.6000e-01 3.0100e-01 - 1.2000e+00 -1.3500e-01 3.0100e-01 - 1.2000e+00 -5.0000e-02 3.0100e-01 - 1.2000e+00 -2.5370e-02 3.1000e-01 - 1.2000e+00 -1.6000e-02 3.3300e-01 - 1.2000e+00 -1.6000e-02 3.9800e-01 - 1.6000e-02 1.2000e+00 3.9800e-01 - 1.6000e-02 1.2000e+00 3.3300e-01 - 2.5370e-02 1.2000e+00 3.1000e-01 - 4.8000e-02 1.2000e+00 3.0100e-01 - 1.3200e-01 1.2000e+00 3.0100e-01 - 1.5800e-01 1.1880e+00 3.0100e-01 - 1.6300e-01 1.1620e+00 3.0100e-01 - 9.0000e-02 7.9400e-01 3.0100e-01 - 7.9000e-02 7.7500e-01 3.0100e-01 - 5.8000e-02 7.6800e-01 3.0100e-01 - -6.2000e-02 7.6800e-01 3.0100e-01 - -8.3000e-02 7.7500e-01 3.0100e-01 - -9.4000e-02 7.9500e-01 3.0100e-01 - -1.6700e-01 1.1620e+00 3.0100e-01 - -1.6000e-01 1.1880e+00 3.0100e-01 - -1.3500e-01 1.2000e+00 3.0100e-01 - -5.0000e-02 1.2000e+00 3.0100e-01 - -2.5370e-02 1.2000e+00 3.1000e-01 - -1.6000e-02 1.2000e+00 3.3300e-01 - -1.6000e-02 1.2000e+00 3.9800e-01 - -7.7650e-01 1.6000e-02 3.9800e-01 - -7.7650e-01 1.6000e-02 3.3300e-01 - -7.7650e-01 2.5370e-02 3.1000e-01 - -7.7650e-01 4.8000e-02 3.0100e-01 - -7.7650e-01 6.1000e-02 3.0100e-01 - -7.8350e-01 8.2000e-02 3.0100e-01 - -8.0250e-01 9.3000e-02 3.0100e-01 - -1.2015e+00 1.7100e-01 3.0100e-01 - -1.2275e+00 1.6500e-01 3.0100e-01 - -1.2395e+00 1.4100e-01 3.0100e-01 - -1.2395e+00 -1.4100e-01 3.0100e-01 - -1.2275e+00 -1.6600e-01 3.0100e-01 - -1.2015e+00 -1.7200e-01 3.0100e-01 - -8.0250e-01 -9.3000e-02 3.0100e-01 - -7.8350e-01 -8.2000e-02 3.0100e-01 - -7.7650e-01 -6.2000e-02 3.0100e-01 - -7.7650e-01 -4.9000e-02 3.0100e-01 - -7.7650e-01 -2.5370e-02 3.1000e-01 - -7.7650e-01 -1.6000e-02 3.3300e-01 - -7.7650e-01 -1.6000e-02 3.9800e-01 - 1.6000e-02 -7.7650e-01 3.9800e-01 - 1.6000e-02 -7.7650e-01 3.3300e-01 - 2.5370e-02 -7.7650e-01 3.1000e-01 - 4.8000e-02 -7.7650e-01 3.0100e-01 - 6.1000e-02 -7.7650e-01 3.0100e-01 - 8.2000e-02 -7.8350e-01 3.0100e-01 - 9.3000e-02 -8.0250e-01 3.0100e-01 - 1.7100e-01 -1.2015e+00 3.0100e-01 - 1.6500e-01 -1.2275e+00 3.0100e-01 - 1.4100e-01 -1.2395e+00 3.0100e-01 - -1.4100e-01 -1.2395e+00 3.0100e-01 - -1.6600e-01 -1.2275e+00 3.0100e-01 - -1.7200e-01 -1.2015e+00 3.0100e-01 - -9.3000e-02 -8.0250e-01 3.0100e-01 - -8.2000e-02 -7.8350e-01 3.0100e-01 - -6.2000e-02 -7.7650e-01 3.0100e-01 - -4.9000e-02 -7.7650e-01 3.0100e-01 - -2.5370e-02 -7.7650e-01 3.1000e-01 - -1.6000e-02 -7.7650e-01 3.3300e-01 - -1.6000e-02 -7.7650e-01 3.9800e-01 + 4 1 20 1.00 + 1.2000e+00 1.6000e-02 3.9800e-01 + 1.2000e+00 1.6000e-02 3.3300e-01 + 1.2000e+00 2.5370e-02 3.1000e-01 + 1.2000e+00 4.8000e-02 3.0100e-01 + 1.2000e+00 1.3200e-01 3.0100e-01 + 1.1880e+00 1.5800e-01 3.0100e-01 + 1.1620e+00 1.6300e-01 3.0100e-01 + 7.9400e-01 9.0000e-02 3.0100e-01 + 7.7500e-01 7.9000e-02 3.0100e-01 + 7.6800e-01 5.8000e-02 3.0100e-01 + 7.6800e-01 -6.2000e-02 3.0100e-01 + 7.7500e-01 -8.3000e-02 3.0100e-01 + 7.9500e-01 -9.4000e-02 3.0100e-01 + 1.1620e+00 -1.6700e-01 3.0100e-01 + 1.1880e+00 -1.6000e-01 3.0100e-01 + 1.2000e+00 -1.3500e-01 3.0100e-01 + 1.2000e+00 -5.0000e-02 3.0100e-01 + 1.2000e+00 -2.5370e-02 3.1000e-01 + 1.2000e+00 -1.6000e-02 3.3300e-01 + 1.2000e+00 -1.6000e-02 3.9800e-01 + 1.6000e-02 1.2000e+00 3.9800e-01 + 1.6000e-02 1.2000e+00 3.3300e-01 + 2.5370e-02 1.2000e+00 3.1000e-01 + 4.8000e-02 1.2000e+00 3.0100e-01 + 1.3200e-01 1.2000e+00 3.0100e-01 + 1.5800e-01 1.1880e+00 3.0100e-01 + 1.6300e-01 1.1620e+00 3.0100e-01 + 9.0000e-02 7.9400e-01 3.0100e-01 + 7.9000e-02 7.7500e-01 3.0100e-01 + 5.8000e-02 7.6800e-01 3.0100e-01 + -6.2000e-02 7.6800e-01 3.0100e-01 + -8.3000e-02 7.7500e-01 3.0100e-01 + -9.4000e-02 7.9500e-01 3.0100e-01 + -1.6700e-01 1.1620e+00 3.0100e-01 + -1.6000e-01 1.1880e+00 3.0100e-01 + -1.3500e-01 1.2000e+00 3.0100e-01 + -5.0000e-02 1.2000e+00 3.0100e-01 + -2.5370e-02 1.2000e+00 3.1000e-01 + -1.6000e-02 1.2000e+00 3.3300e-01 + -1.6000e-02 1.2000e+00 3.9800e-01 + -7.7650e-01 1.6000e-02 3.9800e-01 + -7.7650e-01 1.6000e-02 3.3300e-01 + -7.7650e-01 2.5370e-02 3.1000e-01 + -7.7650e-01 4.8000e-02 3.0100e-01 + -7.7650e-01 6.1000e-02 3.0100e-01 + -7.8350e-01 8.2000e-02 3.0100e-01 + -8.0250e-01 9.3000e-02 3.0100e-01 + -1.2015e+00 1.7100e-01 3.0100e-01 + -1.2275e+00 1.6500e-01 3.0100e-01 + -1.2395e+00 1.4100e-01 3.0100e-01 + -1.2395e+00 -1.4100e-01 3.0100e-01 + -1.2275e+00 -1.6600e-01 3.0100e-01 + -1.2015e+00 -1.7200e-01 3.0100e-01 + -8.0250e-01 -9.3000e-02 3.0100e-01 + -7.8350e-01 -8.2000e-02 3.0100e-01 + -7.7650e-01 -6.2000e-02 3.0100e-01 + -7.7650e-01 -4.9000e-02 3.0100e-01 + -7.7650e-01 -2.5370e-02 3.1000e-01 + -7.7650e-01 -1.6000e-02 3.3300e-01 + -7.7650e-01 -1.6000e-02 3.9800e-01 + 1.6000e-02 -7.7650e-01 3.9800e-01 + 1.6000e-02 -7.7650e-01 3.3300e-01 + 2.5370e-02 -7.7650e-01 3.1000e-01 + 4.8000e-02 -7.7650e-01 3.0100e-01 + 6.1000e-02 -7.7650e-01 3.0100e-01 + 8.2000e-02 -7.8350e-01 3.0100e-01 + 9.3000e-02 -8.0250e-01 3.0100e-01 + 1.7100e-01 -1.2015e+00 3.0100e-01 + 1.6500e-01 -1.2275e+00 3.0100e-01 + 1.4100e-01 -1.2395e+00 3.0100e-01 + -1.4100e-01 -1.2395e+00 3.0100e-01 + -1.6600e-01 -1.2275e+00 3.0100e-01 + -1.7200e-01 -1.2015e+00 3.0100e-01 + -9.3000e-02 -8.0250e-01 3.0100e-01 + -8.2000e-02 -7.8350e-01 3.0100e-01 + -6.2000e-02 -7.7650e-01 3.0100e-01 + -4.9000e-02 -7.7650e-01 3.0100e-01 + -2.5370e-02 -7.7650e-01 3.1000e-01 + -1.6000e-02 -7.7650e-01 3.3300e-01 + -1.6000e-02 -7.7650e-01 3.9800e-01 diff --git a/src/ForcingTerms/coil_geometries/mastu_efcc.dat b/src/ForcingTerms/coil_geometries/mastu_efcc.dat index e94da889f5..5259e11ccb 100644 --- a/src/ForcingTerms/coil_geometries/mastu_efcc.dat +++ b/src/ForcingTerms/coil_geometries/mastu_efcc.dat @@ -1,169 +1,169 @@ - 4 1 42 1.00 - 2.9319e+00 0.0000e+00 1.6550e+00 - 2.9213e+00 2.4953e-01 1.6550e+00 - 2.8894e+00 4.9726e-01 1.6550e+00 - 2.8366e+00 7.4137e-01 1.6550e+00 - 2.7632e+00 9.8011e-01 1.6550e+00 - 2.6698e+00 1.2117e+00 1.6550e+00 - 2.5570e+00 1.4346e+00 1.6550e+00 - 2.4256e+00 1.6470e+00 1.6550e+00 - 2.2766e+00 1.8474e+00 1.6550e+00 - 2.1111e+00 2.0345e+00 1.6550e+00 - 1.9303e+00 2.2068e+00 1.6550e+00 - 1.7355e+00 2.3631e+00 1.6550e+00 - 1.5281e+00 2.5022e+00 1.6550e+00 - 1.3096e+00 2.6232e+00 1.6550e+00 - 1.0815e+00 2.7251e+00 1.6550e+00 - 8.4568e-01 2.8073e+00 1.6550e+00 - 6.0369e-01 2.8691e+00 1.6550e+00 - 3.5731e-01 2.9100e+00 1.6550e+00 - 3.5731e-01 2.9100e+00 1.6550e+00 - 3.5729e-01 2.9099e+00 9.1800e-01 - 3.5727e-01 2.9097e+00 1.8100e-01 - 3.5725e-01 2.9096e+00 -5.5600e-01 - 3.5723e-01 2.9094e+00 -1.2930e+00 - 3.5721e-01 2.9093e+00 -2.0300e+00 - 6.7851e-01 2.8515e+00 -2.0300e+00 - 9.9139e-01 2.7583e+00 -2.0300e+00 - 1.2920e+00 2.6310e+00 -2.0300e+00 - 1.5765e+00 2.4710e+00 -2.0300e+00 - 1.8415e+00 2.2804e+00 -2.0300e+00 - 2.0837e+00 2.0614e+00 -2.0300e+00 - 2.3000e+00 1.8169e+00 -2.0300e+00 - 2.4878e+00 1.5499e+00 -2.0300e+00 - 2.6447e+00 1.2636e+00 -2.0300e+00 - 2.7688e+00 9.6171e-01 -2.0300e+00 - 2.8586e+00 6.4785e-01 -2.0300e+00 - 2.9129e+00 3.2594e-01 -2.0300e+00 - 2.9311e+00 0.0000e+00 -2.0300e+00 - 2.9313e+00 0.0000e+00 -1.2930e+00 - 2.9314e+00 0.0000e+00 -5.5600e-01 - 2.9316e+00 0.0000e+00 1.8100e-01 - 2.9317e+00 0.0000e+00 9.1800e-01 - 2.9319e+00 0.0000e+00 1.6550e+00 - 1.7953e-16 2.9319e+00 1.6550e+00 - -2.4953e-01 2.9213e+00 1.6550e+00 - -4.9726e-01 2.8894e+00 1.6550e+00 - -7.4137e-01 2.8366e+00 1.6550e+00 - -9.8011e-01 2.7632e+00 1.6550e+00 - -1.2117e+00 2.6698e+00 1.6550e+00 - -1.4346e+00 2.5570e+00 1.6550e+00 - -1.6470e+00 2.4256e+00 1.6550e+00 - -1.8474e+00 2.2766e+00 1.6550e+00 - -2.0345e+00 2.1111e+00 1.6550e+00 - -2.2068e+00 1.9303e+00 1.6550e+00 - -2.3631e+00 1.7355e+00 1.6550e+00 - -2.5022e+00 1.5281e+00 1.6550e+00 - -2.6232e+00 1.3096e+00 1.6550e+00 - -2.7251e+00 1.0815e+00 1.6550e+00 - -2.8073e+00 8.4568e-01 1.6550e+00 - -2.8691e+00 6.0369e-01 1.6550e+00 - -2.9100e+00 3.5731e-01 1.6550e+00 - -2.9100e+00 3.5731e-01 1.6550e+00 - -2.9099e+00 3.5729e-01 9.1800e-01 - -2.9097e+00 3.5727e-01 1.8100e-01 - -2.9096e+00 3.5725e-01 -5.5600e-01 - -2.9094e+00 3.5723e-01 -1.2930e+00 - -2.9093e+00 3.5721e-01 -2.0300e+00 - -2.8515e+00 6.7851e-01 -2.0300e+00 - -2.7583e+00 9.9139e-01 -2.0300e+00 - -2.6310e+00 1.2920e+00 -2.0300e+00 - -2.4710e+00 1.5765e+00 -2.0300e+00 - -2.2804e+00 1.8415e+00 -2.0300e+00 - -2.0614e+00 2.0837e+00 -2.0300e+00 - -1.8169e+00 2.3000e+00 -2.0300e+00 - -1.5499e+00 2.4878e+00 -2.0300e+00 - -1.2636e+00 2.6447e+00 -2.0300e+00 - -9.6171e-01 2.7688e+00 -2.0300e+00 - -6.4785e-01 2.8586e+00 -2.0300e+00 - -3.2594e-01 2.9129e+00 -2.0300e+00 - 1.7948e-16 2.9311e+00 -2.0300e+00 - 1.7949e-16 2.9313e+00 -1.2930e+00 - 1.7950e-16 2.9314e+00 -5.5600e-01 - 1.7951e-16 2.9316e+00 1.8100e-01 - 1.7952e-16 2.9317e+00 9.1800e-01 - 1.7953e-16 2.9319e+00 1.6550e+00 - -2.9319e+00 3.5905e-16 1.6550e+00 - -2.9213e+00 -2.4953e-01 1.6550e+00 - -2.8894e+00 -4.9726e-01 1.6550e+00 - -2.8366e+00 -7.4137e-01 1.6550e+00 - -2.7632e+00 -9.8011e-01 1.6550e+00 - -2.6698e+00 -1.2117e+00 1.6550e+00 - -2.5570e+00 -1.4346e+00 1.6550e+00 - -2.4256e+00 -1.6470e+00 1.6550e+00 - -2.2766e+00 -1.8474e+00 1.6550e+00 - -2.1111e+00 -2.0345e+00 1.6550e+00 - -1.9303e+00 -2.2068e+00 1.6550e+00 - -1.7355e+00 -2.3631e+00 1.6550e+00 - -1.5281e+00 -2.5022e+00 1.6550e+00 - -1.3096e+00 -2.6232e+00 1.6550e+00 - -1.0815e+00 -2.7251e+00 1.6550e+00 - -8.4568e-01 -2.8073e+00 1.6550e+00 - -6.0369e-01 -2.8691e+00 1.6550e+00 - -3.5731e-01 -2.9100e+00 1.6550e+00 - -3.5731e-01 -2.9100e+00 1.6550e+00 - -3.5729e-01 -2.9099e+00 9.1800e-01 - -3.5727e-01 -2.9097e+00 1.8100e-01 - -3.5725e-01 -2.9096e+00 -5.5600e-01 - -3.5723e-01 -2.9094e+00 -1.2930e+00 - -3.5721e-01 -2.9093e+00 -2.0300e+00 - -6.7851e-01 -2.8515e+00 -2.0300e+00 - -9.9139e-01 -2.7583e+00 -2.0300e+00 - -1.2920e+00 -2.6310e+00 -2.0300e+00 - -1.5765e+00 -2.4710e+00 -2.0300e+00 - -1.8415e+00 -2.2804e+00 -2.0300e+00 - -2.0837e+00 -2.0614e+00 -2.0300e+00 - -2.3000e+00 -1.8169e+00 -2.0300e+00 - -2.4878e+00 -1.5499e+00 -2.0300e+00 - -2.6447e+00 -1.2636e+00 -2.0300e+00 - -2.7688e+00 -9.6171e-01 -2.0300e+00 - -2.8586e+00 -6.4785e-01 -2.0300e+00 - -2.9129e+00 -3.2594e-01 -2.0300e+00 - -2.9311e+00 3.5896e-16 -2.0300e+00 - -2.9313e+00 3.5898e-16 -1.2930e+00 - -2.9314e+00 3.5900e-16 -5.5600e-01 - -2.9316e+00 3.5902e-16 1.8100e-01 - -2.9317e+00 3.5903e-16 9.1800e-01 - -2.9319e+00 3.5905e-16 1.6550e+00 - -5.3858e-16 -2.9319e+00 1.6550e+00 - 2.4953e-01 -2.9213e+00 1.6550e+00 - 4.9726e-01 -2.8894e+00 1.6550e+00 - 7.4137e-01 -2.8366e+00 1.6550e+00 - 9.8011e-01 -2.7632e+00 1.6550e+00 - 1.2117e+00 -2.6698e+00 1.6550e+00 - 1.4346e+00 -2.5570e+00 1.6550e+00 - 1.6470e+00 -2.4256e+00 1.6550e+00 - 1.8474e+00 -2.2766e+00 1.6550e+00 - 2.0345e+00 -2.1111e+00 1.6550e+00 - 2.2068e+00 -1.9303e+00 1.6550e+00 - 2.3631e+00 -1.7355e+00 1.6550e+00 - 2.5022e+00 -1.5281e+00 1.6550e+00 - 2.6232e+00 -1.3096e+00 1.6550e+00 - 2.7251e+00 -1.0815e+00 1.6550e+00 - 2.8073e+00 -8.4568e-01 1.6550e+00 - 2.8691e+00 -6.0369e-01 1.6550e+00 - 2.9100e+00 -3.5731e-01 1.6550e+00 - 2.9100e+00 -3.5731e-01 1.6550e+00 - 2.9099e+00 -3.5729e-01 9.1800e-01 - 2.9097e+00 -3.5727e-01 1.8100e-01 - 2.9096e+00 -3.5725e-01 -5.5600e-01 - 2.9094e+00 -3.5723e-01 -1.2930e+00 - 2.9093e+00 -3.5721e-01 -2.0300e+00 - 2.8515e+00 -6.7851e-01 -2.0300e+00 - 2.7583e+00 -9.9139e-01 -2.0300e+00 - 2.6310e+00 -1.2920e+00 -2.0300e+00 - 2.4710e+00 -1.5765e+00 -2.0300e+00 - 2.2804e+00 -1.8415e+00 -2.0300e+00 - 2.0614e+00 -2.0837e+00 -2.0300e+00 - 1.8169e+00 -2.3000e+00 -2.0300e+00 - 1.5499e+00 -2.4878e+00 -2.0300e+00 - 1.2636e+00 -2.6447e+00 -2.0300e+00 - 9.6171e-01 -2.7688e+00 -2.0300e+00 - 6.4785e-01 -2.8586e+00 -2.0300e+00 - 3.2594e-01 -2.9129e+00 -2.0300e+00 - -5.3843e-16 -2.9311e+00 -2.0300e+00 - -5.3846e-16 -2.9313e+00 -1.2930e+00 - -5.3849e-16 -2.9314e+00 -5.5600e-01 - -5.3852e-16 -2.9316e+00 1.8100e-01 - -5.3855e-16 -2.9317e+00 9.1800e-01 - -5.3858e-16 -2.9319e+00 1.6550e+00 + 4 1 42 1.00 + 2.9319e+00 0.0000e+00 1.6550e+00 + 2.9213e+00 2.4953e-01 1.6550e+00 + 2.8894e+00 4.9726e-01 1.6550e+00 + 2.8366e+00 7.4137e-01 1.6550e+00 + 2.7632e+00 9.8011e-01 1.6550e+00 + 2.6698e+00 1.2117e+00 1.6550e+00 + 2.5570e+00 1.4346e+00 1.6550e+00 + 2.4256e+00 1.6470e+00 1.6550e+00 + 2.2766e+00 1.8474e+00 1.6550e+00 + 2.1111e+00 2.0345e+00 1.6550e+00 + 1.9303e+00 2.2068e+00 1.6550e+00 + 1.7355e+00 2.3631e+00 1.6550e+00 + 1.5281e+00 2.5022e+00 1.6550e+00 + 1.3096e+00 2.6232e+00 1.6550e+00 + 1.0815e+00 2.7251e+00 1.6550e+00 + 8.4568e-01 2.8073e+00 1.6550e+00 + 6.0369e-01 2.8691e+00 1.6550e+00 + 3.5731e-01 2.9100e+00 1.6550e+00 + 3.5731e-01 2.9100e+00 1.6550e+00 + 3.5729e-01 2.9099e+00 9.1800e-01 + 3.5727e-01 2.9097e+00 1.8100e-01 + 3.5725e-01 2.9096e+00 -5.5600e-01 + 3.5723e-01 2.9094e+00 -1.2930e+00 + 3.5721e-01 2.9093e+00 -2.0300e+00 + 6.7851e-01 2.8515e+00 -2.0300e+00 + 9.9139e-01 2.7583e+00 -2.0300e+00 + 1.2920e+00 2.6310e+00 -2.0300e+00 + 1.5765e+00 2.4710e+00 -2.0300e+00 + 1.8415e+00 2.2804e+00 -2.0300e+00 + 2.0837e+00 2.0614e+00 -2.0300e+00 + 2.3000e+00 1.8169e+00 -2.0300e+00 + 2.4878e+00 1.5499e+00 -2.0300e+00 + 2.6447e+00 1.2636e+00 -2.0300e+00 + 2.7688e+00 9.6171e-01 -2.0300e+00 + 2.8586e+00 6.4785e-01 -2.0300e+00 + 2.9129e+00 3.2594e-01 -2.0300e+00 + 2.9311e+00 0.0000e+00 -2.0300e+00 + 2.9313e+00 0.0000e+00 -1.2930e+00 + 2.9314e+00 0.0000e+00 -5.5600e-01 + 2.9316e+00 0.0000e+00 1.8100e-01 + 2.9317e+00 0.0000e+00 9.1800e-01 + 2.9319e+00 0.0000e+00 1.6550e+00 + 1.7953e-16 2.9319e+00 1.6550e+00 + -2.4953e-01 2.9213e+00 1.6550e+00 + -4.9726e-01 2.8894e+00 1.6550e+00 + -7.4137e-01 2.8366e+00 1.6550e+00 + -9.8011e-01 2.7632e+00 1.6550e+00 + -1.2117e+00 2.6698e+00 1.6550e+00 + -1.4346e+00 2.5570e+00 1.6550e+00 + -1.6470e+00 2.4256e+00 1.6550e+00 + -1.8474e+00 2.2766e+00 1.6550e+00 + -2.0345e+00 2.1111e+00 1.6550e+00 + -2.2068e+00 1.9303e+00 1.6550e+00 + -2.3631e+00 1.7355e+00 1.6550e+00 + -2.5022e+00 1.5281e+00 1.6550e+00 + -2.6232e+00 1.3096e+00 1.6550e+00 + -2.7251e+00 1.0815e+00 1.6550e+00 + -2.8073e+00 8.4568e-01 1.6550e+00 + -2.8691e+00 6.0369e-01 1.6550e+00 + -2.9100e+00 3.5731e-01 1.6550e+00 + -2.9100e+00 3.5731e-01 1.6550e+00 + -2.9099e+00 3.5729e-01 9.1800e-01 + -2.9097e+00 3.5727e-01 1.8100e-01 + -2.9096e+00 3.5725e-01 -5.5600e-01 + -2.9094e+00 3.5723e-01 -1.2930e+00 + -2.9093e+00 3.5721e-01 -2.0300e+00 + -2.8515e+00 6.7851e-01 -2.0300e+00 + -2.7583e+00 9.9139e-01 -2.0300e+00 + -2.6310e+00 1.2920e+00 -2.0300e+00 + -2.4710e+00 1.5765e+00 -2.0300e+00 + -2.2804e+00 1.8415e+00 -2.0300e+00 + -2.0614e+00 2.0837e+00 -2.0300e+00 + -1.8169e+00 2.3000e+00 -2.0300e+00 + -1.5499e+00 2.4878e+00 -2.0300e+00 + -1.2636e+00 2.6447e+00 -2.0300e+00 + -9.6171e-01 2.7688e+00 -2.0300e+00 + -6.4785e-01 2.8586e+00 -2.0300e+00 + -3.2594e-01 2.9129e+00 -2.0300e+00 + 1.7948e-16 2.9311e+00 -2.0300e+00 + 1.7949e-16 2.9313e+00 -1.2930e+00 + 1.7950e-16 2.9314e+00 -5.5600e-01 + 1.7951e-16 2.9316e+00 1.8100e-01 + 1.7952e-16 2.9317e+00 9.1800e-01 + 1.7953e-16 2.9319e+00 1.6550e+00 + -2.9319e+00 3.5905e-16 1.6550e+00 + -2.9213e+00 -2.4953e-01 1.6550e+00 + -2.8894e+00 -4.9726e-01 1.6550e+00 + -2.8366e+00 -7.4137e-01 1.6550e+00 + -2.7632e+00 -9.8011e-01 1.6550e+00 + -2.6698e+00 -1.2117e+00 1.6550e+00 + -2.5570e+00 -1.4346e+00 1.6550e+00 + -2.4256e+00 -1.6470e+00 1.6550e+00 + -2.2766e+00 -1.8474e+00 1.6550e+00 + -2.1111e+00 -2.0345e+00 1.6550e+00 + -1.9303e+00 -2.2068e+00 1.6550e+00 + -1.7355e+00 -2.3631e+00 1.6550e+00 + -1.5281e+00 -2.5022e+00 1.6550e+00 + -1.3096e+00 -2.6232e+00 1.6550e+00 + -1.0815e+00 -2.7251e+00 1.6550e+00 + -8.4568e-01 -2.8073e+00 1.6550e+00 + -6.0369e-01 -2.8691e+00 1.6550e+00 + -3.5731e-01 -2.9100e+00 1.6550e+00 + -3.5731e-01 -2.9100e+00 1.6550e+00 + -3.5729e-01 -2.9099e+00 9.1800e-01 + -3.5727e-01 -2.9097e+00 1.8100e-01 + -3.5725e-01 -2.9096e+00 -5.5600e-01 + -3.5723e-01 -2.9094e+00 -1.2930e+00 + -3.5721e-01 -2.9093e+00 -2.0300e+00 + -6.7851e-01 -2.8515e+00 -2.0300e+00 + -9.9139e-01 -2.7583e+00 -2.0300e+00 + -1.2920e+00 -2.6310e+00 -2.0300e+00 + -1.5765e+00 -2.4710e+00 -2.0300e+00 + -1.8415e+00 -2.2804e+00 -2.0300e+00 + -2.0837e+00 -2.0614e+00 -2.0300e+00 + -2.3000e+00 -1.8169e+00 -2.0300e+00 + -2.4878e+00 -1.5499e+00 -2.0300e+00 + -2.6447e+00 -1.2636e+00 -2.0300e+00 + -2.7688e+00 -9.6171e-01 -2.0300e+00 + -2.8586e+00 -6.4785e-01 -2.0300e+00 + -2.9129e+00 -3.2594e-01 -2.0300e+00 + -2.9311e+00 3.5896e-16 -2.0300e+00 + -2.9313e+00 3.5898e-16 -1.2930e+00 + -2.9314e+00 3.5900e-16 -5.5600e-01 + -2.9316e+00 3.5902e-16 1.8100e-01 + -2.9317e+00 3.5903e-16 9.1800e-01 + -2.9319e+00 3.5905e-16 1.6550e+00 + -5.3858e-16 -2.9319e+00 1.6550e+00 + 2.4953e-01 -2.9213e+00 1.6550e+00 + 4.9726e-01 -2.8894e+00 1.6550e+00 + 7.4137e-01 -2.8366e+00 1.6550e+00 + 9.8011e-01 -2.7632e+00 1.6550e+00 + 1.2117e+00 -2.6698e+00 1.6550e+00 + 1.4346e+00 -2.5570e+00 1.6550e+00 + 1.6470e+00 -2.4256e+00 1.6550e+00 + 1.8474e+00 -2.2766e+00 1.6550e+00 + 2.0345e+00 -2.1111e+00 1.6550e+00 + 2.2068e+00 -1.9303e+00 1.6550e+00 + 2.3631e+00 -1.7355e+00 1.6550e+00 + 2.5022e+00 -1.5281e+00 1.6550e+00 + 2.6232e+00 -1.3096e+00 1.6550e+00 + 2.7251e+00 -1.0815e+00 1.6550e+00 + 2.8073e+00 -8.4568e-01 1.6550e+00 + 2.8691e+00 -6.0369e-01 1.6550e+00 + 2.9100e+00 -3.5731e-01 1.6550e+00 + 2.9100e+00 -3.5731e-01 1.6550e+00 + 2.9099e+00 -3.5729e-01 9.1800e-01 + 2.9097e+00 -3.5727e-01 1.8100e-01 + 2.9096e+00 -3.5725e-01 -5.5600e-01 + 2.9094e+00 -3.5723e-01 -1.2930e+00 + 2.9093e+00 -3.5721e-01 -2.0300e+00 + 2.8515e+00 -6.7851e-01 -2.0300e+00 + 2.7583e+00 -9.9139e-01 -2.0300e+00 + 2.6310e+00 -1.2920e+00 -2.0300e+00 + 2.4710e+00 -1.5765e+00 -2.0300e+00 + 2.2804e+00 -1.8415e+00 -2.0300e+00 + 2.0614e+00 -2.0837e+00 -2.0300e+00 + 1.8169e+00 -2.3000e+00 -2.0300e+00 + 1.5499e+00 -2.4878e+00 -2.0300e+00 + 1.2636e+00 -2.6447e+00 -2.0300e+00 + 9.6171e-01 -2.7688e+00 -2.0300e+00 + 6.4785e-01 -2.8586e+00 -2.0300e+00 + 3.2594e-01 -2.9129e+00 -2.0300e+00 + -5.3843e-16 -2.9311e+00 -2.0300e+00 + -5.3846e-16 -2.9313e+00 -1.2930e+00 + -5.3849e-16 -2.9314e+00 -5.5600e-01 + -5.3852e-16 -2.9316e+00 1.8100e-01 + -5.3855e-16 -2.9317e+00 9.1800e-01 + -5.3858e-16 -2.9319e+00 1.6550e+00 diff --git a/src/ForcingTerms/coil_geometries/mastu_rmpl.dat b/src/ForcingTerms/coil_geometries/mastu_rmpl.dat index 7d3a798d3e..1bb9ca7855 100644 --- a/src/ForcingTerms/coil_geometries/mastu_rmpl.dat +++ b/src/ForcingTerms/coil_geometries/mastu_rmpl.dat @@ -1,337 +1,337 @@ - 8 1 42 1.00 - 1.3110e+00 0.0000e+00 -7.9100e-01 - 1.3107e+00 2.6300e-02 -7.9100e-01 - 1.3099e+00 5.2589e-02 -7.9100e-01 - 1.3086e+00 7.8857e-02 -7.9100e-01 - 1.3068e+00 1.0509e-01 -7.9100e-01 - 1.3044e+00 1.3129e-01 -7.9100e-01 - 1.3015e+00 1.5743e-01 -7.9100e-01 - 1.2981e+00 1.8351e-01 -7.9100e-01 - 1.2942e+00 2.0951e-01 -7.9100e-01 - 1.2897e+00 2.3543e-01 -7.9100e-01 - 1.2847e+00 2.6125e-01 -7.9100e-01 - 1.2792e+00 2.8697e-01 -7.9100e-01 - 1.2732e+00 3.1258e-01 -7.9100e-01 - 1.2667e+00 3.3806e-01 -7.9100e-01 - 1.2596e+00 3.6340e-01 -7.9100e-01 - 1.2521e+00 3.8859e-01 -7.9100e-01 - 1.2440e+00 4.1363e-01 -7.9100e-01 - 1.2355e+00 4.3851e-01 -7.9100e-01 - 1.2264e+00 4.6320e-01 -7.9100e-01 - 1.2169e+00 4.8771e-01 -7.9100e-01 - 1.2169e+00 4.8771e-01 -7.9100e-01 - 1.2703e+00 5.0910e-01 -6.9100e-01 - 1.3237e+00 5.3049e-01 -5.9100e-01 - 1.3352e+00 5.0068e-01 -5.9100e-01 - 1.3461e+00 4.7062e-01 -5.9100e-01 - 1.3563e+00 4.4032e-01 -5.9100e-01 - 1.3658e+00 4.0980e-01 -5.9100e-01 - 1.3747e+00 3.7908e-01 -5.9100e-01 - 1.3828e+00 3.4816e-01 -5.9100e-01 - 1.3903e+00 3.1707e-01 -5.9100e-01 - 1.3971e+00 2.8582e-01 -5.9100e-01 - 1.4031e+00 2.5442e-01 -5.9100e-01 - 1.4085e+00 2.2290e-01 -5.9100e-01 - 1.4131e+00 1.9127e-01 -5.9100e-01 - 1.4170e+00 1.5954e-01 -5.9100e-01 - 1.4203e+00 1.2773e-01 -5.9100e-01 - 1.4228e+00 9.5850e-02 -5.9100e-01 - 1.4246e+00 6.3927e-02 -5.9100e-01 - 1.4256e+00 3.1972e-02 -5.9100e-01 - 1.4260e+00 0.0000e+00 -5.9100e-01 - 1.3685e+00 0.0000e+00 -6.9100e-01 - 1.3110e+00 0.0000e+00 -7.9100e-01 - 9.2702e-01 9.2702e-01 -7.9100e-01 - 9.0823e-01 9.4543e-01 -7.9100e-01 - 8.8909e-01 9.6346e-01 -7.9100e-01 - 8.6958e-01 9.8110e-01 -7.9100e-01 - 8.4972e-01 9.9835e-01 -7.9100e-01 - 8.2952e-01 1.0152e+00 -7.9100e-01 - 8.0899e-01 1.0316e+00 -7.9100e-01 - 7.8813e-01 1.0476e+00 -7.9100e-01 - 7.6696e-01 1.0632e+00 -7.9100e-01 - 7.4547e-01 1.0784e+00 -7.9100e-01 - 7.2369e-01 1.0932e+00 -7.9100e-01 - 7.0161e-01 1.1075e+00 -7.9100e-01 - 6.7926e-01 1.1213e+00 -7.9100e-01 - 6.5663e-01 1.1347e+00 -7.9100e-01 - 6.3373e-01 1.1477e+00 -7.9100e-01 - 6.1058e-01 1.1601e+00 -7.9100e-01 - 5.8718e-01 1.1721e+00 -7.9100e-01 - 5.6355e-01 1.1837e+00 -7.9100e-01 - 5.3969e-01 1.1948e+00 -7.9100e-01 - 5.1562e-01 1.2053e+00 -7.9100e-01 - 5.1562e-01 1.2053e+00 -7.9100e-01 - 5.3823e-01 1.2582e+00 -6.9100e-01 - 5.6085e-01 1.3111e+00 -5.9100e-01 - 5.9010e-01 1.2982e+00 -5.9100e-01 - 6.1906e-01 1.2846e+00 -5.9100e-01 - 6.4770e-01 1.2704e+00 -5.9100e-01 - 6.7602e-01 1.2556e+00 -5.9100e-01 - 7.0400e-01 1.2401e+00 -5.9100e-01 - 7.3163e-01 1.2240e+00 -5.9100e-01 - 7.5889e-01 1.2073e+00 -5.9100e-01 - 7.8577e-01 1.1900e+00 -5.9100e-01 - 8.1225e-01 1.1721e+00 -5.9100e-01 - 8.3832e-01 1.1536e+00 -5.9100e-01 - 8.6398e-01 1.1345e+00 -5.9100e-01 - 8.8919e-01 1.1148e+00 -5.9100e-01 - 9.1397e-01 1.0946e+00 -5.9100e-01 - 9.3828e-01 1.0738e+00 -5.9100e-01 - 9.6212e-01 1.0525e+00 -5.9100e-01 - 9.8547e-01 1.0307e+00 -5.9100e-01 - 1.0083e+00 1.0083e+00 -5.9100e-01 - 9.6768e-01 9.6768e-01 -6.9100e-01 - 9.2702e-01 9.2702e-01 -7.9100e-01 - 8.0276e-17 1.3110e+00 -7.9100e-01 - -2.6300e-02 1.3107e+00 -7.9100e-01 - -5.2589e-02 1.3099e+00 -7.9100e-01 - -7.8857e-02 1.3086e+00 -7.9100e-01 - -1.0509e-01 1.3068e+00 -7.9100e-01 - -1.3129e-01 1.3044e+00 -7.9100e-01 - -1.5743e-01 1.3015e+00 -7.9100e-01 - -1.8351e-01 1.2981e+00 -7.9100e-01 - -2.0951e-01 1.2942e+00 -7.9100e-01 - -2.3543e-01 1.2897e+00 -7.9100e-01 - -2.6125e-01 1.2847e+00 -7.9100e-01 - -2.8697e-01 1.2792e+00 -7.9100e-01 - -3.1258e-01 1.2732e+00 -7.9100e-01 - -3.3806e-01 1.2667e+00 -7.9100e-01 - -3.6340e-01 1.2596e+00 -7.9100e-01 - -3.8859e-01 1.2521e+00 -7.9100e-01 - -4.1363e-01 1.2440e+00 -7.9100e-01 - -4.3851e-01 1.2355e+00 -7.9100e-01 - -4.6320e-01 1.2264e+00 -7.9100e-01 - -4.8771e-01 1.2169e+00 -7.9100e-01 - -4.8771e-01 1.2169e+00 -7.9100e-01 - -5.0910e-01 1.2703e+00 -6.9100e-01 - -5.3049e-01 1.3237e+00 -5.9100e-01 - -5.0068e-01 1.3352e+00 -5.9100e-01 - -4.7062e-01 1.3461e+00 -5.9100e-01 - -4.4032e-01 1.3563e+00 -5.9100e-01 - -4.0980e-01 1.3658e+00 -5.9100e-01 - -3.7908e-01 1.3747e+00 -5.9100e-01 - -3.4816e-01 1.3828e+00 -5.9100e-01 - -3.1707e-01 1.3903e+00 -5.9100e-01 - -2.8582e-01 1.3971e+00 -5.9100e-01 - -2.5442e-01 1.4031e+00 -5.9100e-01 - -2.2290e-01 1.4085e+00 -5.9100e-01 - -1.9127e-01 1.4131e+00 -5.9100e-01 - -1.5954e-01 1.4170e+00 -5.9100e-01 - -1.2773e-01 1.4203e+00 -5.9100e-01 - -9.5850e-02 1.4228e+00 -5.9100e-01 - -6.3927e-02 1.4246e+00 -5.9100e-01 - -3.1972e-02 1.4256e+00 -5.9100e-01 - 8.7317e-17 1.4260e+00 -5.9100e-01 - 8.3796e-17 1.3685e+00 -6.9100e-01 - 8.0276e-17 1.3110e+00 -7.9100e-01 - -9.2702e-01 9.2702e-01 -7.9100e-01 - -9.4543e-01 9.0823e-01 -7.9100e-01 - -9.6346e-01 8.8909e-01 -7.9100e-01 - -9.8110e-01 8.6958e-01 -7.9100e-01 - -9.9835e-01 8.4972e-01 -7.9100e-01 - -1.0152e+00 8.2952e-01 -7.9100e-01 - -1.0316e+00 8.0899e-01 -7.9100e-01 - -1.0476e+00 7.8813e-01 -7.9100e-01 - -1.0632e+00 7.6696e-01 -7.9100e-01 - -1.0784e+00 7.4547e-01 -7.9100e-01 - -1.0932e+00 7.2369e-01 -7.9100e-01 - -1.1075e+00 7.0161e-01 -7.9100e-01 - -1.1213e+00 6.7926e-01 -7.9100e-01 - -1.1347e+00 6.5663e-01 -7.9100e-01 - -1.1477e+00 6.3373e-01 -7.9100e-01 - -1.1601e+00 6.1058e-01 -7.9100e-01 - -1.1721e+00 5.8718e-01 -7.9100e-01 - -1.1837e+00 5.6355e-01 -7.9100e-01 - -1.1948e+00 5.3969e-01 -7.9100e-01 - -1.2053e+00 5.1562e-01 -7.9100e-01 - -1.2053e+00 5.1562e-01 -7.9100e-01 - -1.2582e+00 5.3823e-01 -6.9100e-01 - -1.3111e+00 5.6085e-01 -5.9100e-01 - -1.2982e+00 5.9010e-01 -5.9100e-01 - -1.2846e+00 6.1906e-01 -5.9100e-01 - -1.2704e+00 6.4770e-01 -5.9100e-01 - -1.2556e+00 6.7602e-01 -5.9100e-01 - -1.2401e+00 7.0400e-01 -5.9100e-01 - -1.2240e+00 7.3163e-01 -5.9100e-01 - -1.2073e+00 7.5889e-01 -5.9100e-01 - -1.1900e+00 7.8577e-01 -5.9100e-01 - -1.1721e+00 8.1225e-01 -5.9100e-01 - -1.1536e+00 8.3832e-01 -5.9100e-01 - -1.1345e+00 8.6398e-01 -5.9100e-01 - -1.1148e+00 8.8919e-01 -5.9100e-01 - -1.0946e+00 9.1397e-01 -5.9100e-01 - -1.0738e+00 9.3828e-01 -5.9100e-01 - -1.0525e+00 9.6212e-01 -5.9100e-01 - -1.0307e+00 9.8547e-01 -5.9100e-01 - -1.0083e+00 1.0083e+00 -5.9100e-01 - -9.6768e-01 9.6768e-01 -6.9100e-01 - -9.2702e-01 9.2702e-01 -7.9100e-01 - -1.3110e+00 1.6055e-16 -7.9100e-01 - -1.3107e+00 -2.6300e-02 -7.9100e-01 - -1.3099e+00 -5.2589e-02 -7.9100e-01 - -1.3086e+00 -7.8857e-02 -7.9100e-01 - -1.3068e+00 -1.0509e-01 -7.9100e-01 - -1.3044e+00 -1.3129e-01 -7.9100e-01 - -1.3015e+00 -1.5743e-01 -7.9100e-01 - -1.2981e+00 -1.8351e-01 -7.9100e-01 - -1.2942e+00 -2.0951e-01 -7.9100e-01 - -1.2897e+00 -2.3543e-01 -7.9100e-01 - -1.2847e+00 -2.6125e-01 -7.9100e-01 - -1.2792e+00 -2.8697e-01 -7.9100e-01 - -1.2732e+00 -3.1258e-01 -7.9100e-01 - -1.2667e+00 -3.3806e-01 -7.9100e-01 - -1.2596e+00 -3.6340e-01 -7.9100e-01 - -1.2521e+00 -3.8859e-01 -7.9100e-01 - -1.2440e+00 -4.1363e-01 -7.9100e-01 - -1.2355e+00 -4.3851e-01 -7.9100e-01 - -1.2264e+00 -4.6320e-01 -7.9100e-01 - -1.2169e+00 -4.8771e-01 -7.9100e-01 - -1.2169e+00 -4.8771e-01 -7.9100e-01 - -1.2703e+00 -5.0910e-01 -6.9100e-01 - -1.3237e+00 -5.3049e-01 -5.9100e-01 - -1.3352e+00 -5.0068e-01 -5.9100e-01 - -1.3461e+00 -4.7062e-01 -5.9100e-01 - -1.3563e+00 -4.4032e-01 -5.9100e-01 - -1.3658e+00 -4.0980e-01 -5.9100e-01 - -1.3747e+00 -3.7908e-01 -5.9100e-01 - -1.3828e+00 -3.4816e-01 -5.9100e-01 - -1.3903e+00 -3.1707e-01 -5.9100e-01 - -1.3971e+00 -2.8582e-01 -5.9100e-01 - -1.4031e+00 -2.5442e-01 -5.9100e-01 - -1.4085e+00 -2.2290e-01 -5.9100e-01 - -1.4131e+00 -1.9127e-01 -5.9100e-01 - -1.4170e+00 -1.5954e-01 -5.9100e-01 - -1.4203e+00 -1.2773e-01 -5.9100e-01 - -1.4228e+00 -9.5850e-02 -5.9100e-01 - -1.4246e+00 -6.3927e-02 -5.9100e-01 - -1.4256e+00 -3.1972e-02 -5.9100e-01 - -1.4260e+00 1.7463e-16 -5.9100e-01 - -1.3685e+00 1.6759e-16 -6.9100e-01 - -1.3110e+00 1.6055e-16 -7.9100e-01 - -9.2702e-01 -9.2702e-01 -7.9100e-01 - -9.0823e-01 -9.4543e-01 -7.9100e-01 - -8.8909e-01 -9.6346e-01 -7.9100e-01 - -8.6958e-01 -9.8110e-01 -7.9100e-01 - -8.4972e-01 -9.9835e-01 -7.9100e-01 - -8.2952e-01 -1.0152e+00 -7.9100e-01 - -8.0899e-01 -1.0316e+00 -7.9100e-01 - -7.8813e-01 -1.0476e+00 -7.9100e-01 - -7.6696e-01 -1.0632e+00 -7.9100e-01 - -7.4547e-01 -1.0784e+00 -7.9100e-01 - -7.2369e-01 -1.0932e+00 -7.9100e-01 - -7.0161e-01 -1.1075e+00 -7.9100e-01 - -6.7926e-01 -1.1213e+00 -7.9100e-01 - -6.5663e-01 -1.1347e+00 -7.9100e-01 - -6.3373e-01 -1.1477e+00 -7.9100e-01 - -6.1058e-01 -1.1601e+00 -7.9100e-01 - -5.8718e-01 -1.1721e+00 -7.9100e-01 - -5.6355e-01 -1.1837e+00 -7.9100e-01 - -5.3969e-01 -1.1948e+00 -7.9100e-01 - -5.1562e-01 -1.2053e+00 -7.9100e-01 - -5.1562e-01 -1.2053e+00 -7.9100e-01 - -5.3823e-01 -1.2582e+00 -6.9100e-01 - -5.6085e-01 -1.3111e+00 -5.9100e-01 - -5.9010e-01 -1.2982e+00 -5.9100e-01 - -6.1906e-01 -1.2846e+00 -5.9100e-01 - -6.4770e-01 -1.2704e+00 -5.9100e-01 - -6.7602e-01 -1.2556e+00 -5.9100e-01 - -7.0400e-01 -1.2401e+00 -5.9100e-01 - -7.3163e-01 -1.2240e+00 -5.9100e-01 - -7.5889e-01 -1.2073e+00 -5.9100e-01 - -7.8577e-01 -1.1900e+00 -5.9100e-01 - -8.1225e-01 -1.1721e+00 -5.9100e-01 - -8.3832e-01 -1.1536e+00 -5.9100e-01 - -8.6398e-01 -1.1345e+00 -5.9100e-01 - -8.8919e-01 -1.1148e+00 -5.9100e-01 - -9.1397e-01 -1.0946e+00 -5.9100e-01 - -9.3828e-01 -1.0738e+00 -5.9100e-01 - -9.6212e-01 -1.0525e+00 -5.9100e-01 - -9.8547e-01 -1.0307e+00 -5.9100e-01 - -1.0083e+00 -1.0083e+00 -5.9100e-01 - -9.6768e-01 -9.6768e-01 -6.9100e-01 - -9.2702e-01 -9.2702e-01 -7.9100e-01 - -2.4083e-16 -1.3110e+00 -7.9100e-01 - 2.6300e-02 -1.3107e+00 -7.9100e-01 - 5.2589e-02 -1.3099e+00 -7.9100e-01 - 7.8857e-02 -1.3086e+00 -7.9100e-01 - 1.0509e-01 -1.3068e+00 -7.9100e-01 - 1.3129e-01 -1.3044e+00 -7.9100e-01 - 1.5743e-01 -1.3015e+00 -7.9100e-01 - 1.8351e-01 -1.2981e+00 -7.9100e-01 - 2.0951e-01 -1.2942e+00 -7.9100e-01 - 2.3543e-01 -1.2897e+00 -7.9100e-01 - 2.6125e-01 -1.2847e+00 -7.9100e-01 - 2.8697e-01 -1.2792e+00 -7.9100e-01 - 3.1258e-01 -1.2732e+00 -7.9100e-01 - 3.3806e-01 -1.2667e+00 -7.9100e-01 - 3.6340e-01 -1.2596e+00 -7.9100e-01 - 3.8859e-01 -1.2521e+00 -7.9100e-01 - 4.1363e-01 -1.2440e+00 -7.9100e-01 - 4.3851e-01 -1.2355e+00 -7.9100e-01 - 4.6320e-01 -1.2264e+00 -7.9100e-01 - 4.8771e-01 -1.2169e+00 -7.9100e-01 - 4.8771e-01 -1.2169e+00 -7.9100e-01 - 5.0910e-01 -1.2703e+00 -6.9100e-01 - 5.3049e-01 -1.3237e+00 -5.9100e-01 - 5.0068e-01 -1.3352e+00 -5.9100e-01 - 4.7062e-01 -1.3461e+00 -5.9100e-01 - 4.4032e-01 -1.3563e+00 -5.9100e-01 - 4.0980e-01 -1.3658e+00 -5.9100e-01 - 3.7908e-01 -1.3747e+00 -5.9100e-01 - 3.4816e-01 -1.3828e+00 -5.9100e-01 - 3.1707e-01 -1.3903e+00 -5.9100e-01 - 2.8582e-01 -1.3971e+00 -5.9100e-01 - 2.5442e-01 -1.4031e+00 -5.9100e-01 - 2.2290e-01 -1.4085e+00 -5.9100e-01 - 1.9127e-01 -1.4131e+00 -5.9100e-01 - 1.5954e-01 -1.4170e+00 -5.9100e-01 - 1.2773e-01 -1.4203e+00 -5.9100e-01 - 9.5850e-02 -1.4228e+00 -5.9100e-01 - 6.3927e-02 -1.4246e+00 -5.9100e-01 - 3.1972e-02 -1.4256e+00 -5.9100e-01 - -2.6195e-16 -1.4260e+00 -5.9100e-01 - -2.5139e-16 -1.3685e+00 -6.9100e-01 - -2.4083e-16 -1.3110e+00 -7.9100e-01 - 9.2702e-01 -9.2702e-01 -7.9100e-01 - 9.4543e-01 -9.0823e-01 -7.9100e-01 - 9.6346e-01 -8.8909e-01 -7.9100e-01 - 9.8110e-01 -8.6958e-01 -7.9100e-01 - 9.9835e-01 -8.4972e-01 -7.9100e-01 - 1.0152e+00 -8.2952e-01 -7.9100e-01 - 1.0316e+00 -8.0899e-01 -7.9100e-01 - 1.0476e+00 -7.8813e-01 -7.9100e-01 - 1.0632e+00 -7.6696e-01 -7.9100e-01 - 1.0784e+00 -7.4547e-01 -7.9100e-01 - 1.0932e+00 -7.2369e-01 -7.9100e-01 - 1.1075e+00 -7.0161e-01 -7.9100e-01 - 1.1213e+00 -6.7926e-01 -7.9100e-01 - 1.1347e+00 -6.5663e-01 -7.9100e-01 - 1.1477e+00 -6.3373e-01 -7.9100e-01 - 1.1601e+00 -6.1058e-01 -7.9100e-01 - 1.1721e+00 -5.8718e-01 -7.9100e-01 - 1.1837e+00 -5.6355e-01 -7.9100e-01 - 1.1948e+00 -5.3969e-01 -7.9100e-01 - 1.2053e+00 -5.1562e-01 -7.9100e-01 - 1.2053e+00 -5.1562e-01 -7.9100e-01 - 1.2582e+00 -5.3823e-01 -6.9100e-01 - 1.3111e+00 -5.6085e-01 -5.9100e-01 - 1.2982e+00 -5.9010e-01 -5.9100e-01 - 1.2846e+00 -6.1906e-01 -5.9100e-01 - 1.2704e+00 -6.4770e-01 -5.9100e-01 - 1.2556e+00 -6.7602e-01 -5.9100e-01 - 1.2401e+00 -7.0400e-01 -5.9100e-01 - 1.2240e+00 -7.3163e-01 -5.9100e-01 - 1.2073e+00 -7.5889e-01 -5.9100e-01 - 1.1900e+00 -7.8577e-01 -5.9100e-01 - 1.1721e+00 -8.1225e-01 -5.9100e-01 - 1.1536e+00 -8.3832e-01 -5.9100e-01 - 1.1345e+00 -8.6398e-01 -5.9100e-01 - 1.1148e+00 -8.8919e-01 -5.9100e-01 - 1.0946e+00 -9.1397e-01 -5.9100e-01 - 1.0738e+00 -9.3828e-01 -5.9100e-01 - 1.0525e+00 -9.6212e-01 -5.9100e-01 - 1.0307e+00 -9.8547e-01 -5.9100e-01 - 1.0083e+00 -1.0083e+00 -5.9100e-01 - 9.6768e-01 -9.6768e-01 -6.9100e-01 - 9.2702e-01 -9.2702e-01 -7.9100e-01 + 8 1 42 1.00 + 1.3110e+00 0.0000e+00 -7.9100e-01 + 1.3107e+00 2.6300e-02 -7.9100e-01 + 1.3099e+00 5.2589e-02 -7.9100e-01 + 1.3086e+00 7.8857e-02 -7.9100e-01 + 1.3068e+00 1.0509e-01 -7.9100e-01 + 1.3044e+00 1.3129e-01 -7.9100e-01 + 1.3015e+00 1.5743e-01 -7.9100e-01 + 1.2981e+00 1.8351e-01 -7.9100e-01 + 1.2942e+00 2.0951e-01 -7.9100e-01 + 1.2897e+00 2.3543e-01 -7.9100e-01 + 1.2847e+00 2.6125e-01 -7.9100e-01 + 1.2792e+00 2.8697e-01 -7.9100e-01 + 1.2732e+00 3.1258e-01 -7.9100e-01 + 1.2667e+00 3.3806e-01 -7.9100e-01 + 1.2596e+00 3.6340e-01 -7.9100e-01 + 1.2521e+00 3.8859e-01 -7.9100e-01 + 1.2440e+00 4.1363e-01 -7.9100e-01 + 1.2355e+00 4.3851e-01 -7.9100e-01 + 1.2264e+00 4.6320e-01 -7.9100e-01 + 1.2169e+00 4.8771e-01 -7.9100e-01 + 1.2169e+00 4.8771e-01 -7.9100e-01 + 1.2703e+00 5.0910e-01 -6.9100e-01 + 1.3237e+00 5.3049e-01 -5.9100e-01 + 1.3352e+00 5.0068e-01 -5.9100e-01 + 1.3461e+00 4.7062e-01 -5.9100e-01 + 1.3563e+00 4.4032e-01 -5.9100e-01 + 1.3658e+00 4.0980e-01 -5.9100e-01 + 1.3747e+00 3.7908e-01 -5.9100e-01 + 1.3828e+00 3.4816e-01 -5.9100e-01 + 1.3903e+00 3.1707e-01 -5.9100e-01 + 1.3971e+00 2.8582e-01 -5.9100e-01 + 1.4031e+00 2.5442e-01 -5.9100e-01 + 1.4085e+00 2.2290e-01 -5.9100e-01 + 1.4131e+00 1.9127e-01 -5.9100e-01 + 1.4170e+00 1.5954e-01 -5.9100e-01 + 1.4203e+00 1.2773e-01 -5.9100e-01 + 1.4228e+00 9.5850e-02 -5.9100e-01 + 1.4246e+00 6.3927e-02 -5.9100e-01 + 1.4256e+00 3.1972e-02 -5.9100e-01 + 1.4260e+00 0.0000e+00 -5.9100e-01 + 1.3685e+00 0.0000e+00 -6.9100e-01 + 1.3110e+00 0.0000e+00 -7.9100e-01 + 9.2702e-01 9.2702e-01 -7.9100e-01 + 9.0823e-01 9.4543e-01 -7.9100e-01 + 8.8909e-01 9.6346e-01 -7.9100e-01 + 8.6958e-01 9.8110e-01 -7.9100e-01 + 8.4972e-01 9.9835e-01 -7.9100e-01 + 8.2952e-01 1.0152e+00 -7.9100e-01 + 8.0899e-01 1.0316e+00 -7.9100e-01 + 7.8813e-01 1.0476e+00 -7.9100e-01 + 7.6696e-01 1.0632e+00 -7.9100e-01 + 7.4547e-01 1.0784e+00 -7.9100e-01 + 7.2369e-01 1.0932e+00 -7.9100e-01 + 7.0161e-01 1.1075e+00 -7.9100e-01 + 6.7926e-01 1.1213e+00 -7.9100e-01 + 6.5663e-01 1.1347e+00 -7.9100e-01 + 6.3373e-01 1.1477e+00 -7.9100e-01 + 6.1058e-01 1.1601e+00 -7.9100e-01 + 5.8718e-01 1.1721e+00 -7.9100e-01 + 5.6355e-01 1.1837e+00 -7.9100e-01 + 5.3969e-01 1.1948e+00 -7.9100e-01 + 5.1562e-01 1.2053e+00 -7.9100e-01 + 5.1562e-01 1.2053e+00 -7.9100e-01 + 5.3823e-01 1.2582e+00 -6.9100e-01 + 5.6085e-01 1.3111e+00 -5.9100e-01 + 5.9010e-01 1.2982e+00 -5.9100e-01 + 6.1906e-01 1.2846e+00 -5.9100e-01 + 6.4770e-01 1.2704e+00 -5.9100e-01 + 6.7602e-01 1.2556e+00 -5.9100e-01 + 7.0400e-01 1.2401e+00 -5.9100e-01 + 7.3163e-01 1.2240e+00 -5.9100e-01 + 7.5889e-01 1.2073e+00 -5.9100e-01 + 7.8577e-01 1.1900e+00 -5.9100e-01 + 8.1225e-01 1.1721e+00 -5.9100e-01 + 8.3832e-01 1.1536e+00 -5.9100e-01 + 8.6398e-01 1.1345e+00 -5.9100e-01 + 8.8919e-01 1.1148e+00 -5.9100e-01 + 9.1397e-01 1.0946e+00 -5.9100e-01 + 9.3828e-01 1.0738e+00 -5.9100e-01 + 9.6212e-01 1.0525e+00 -5.9100e-01 + 9.8547e-01 1.0307e+00 -5.9100e-01 + 1.0083e+00 1.0083e+00 -5.9100e-01 + 9.6768e-01 9.6768e-01 -6.9100e-01 + 9.2702e-01 9.2702e-01 -7.9100e-01 + 8.0276e-17 1.3110e+00 -7.9100e-01 + -2.6300e-02 1.3107e+00 -7.9100e-01 + -5.2589e-02 1.3099e+00 -7.9100e-01 + -7.8857e-02 1.3086e+00 -7.9100e-01 + -1.0509e-01 1.3068e+00 -7.9100e-01 + -1.3129e-01 1.3044e+00 -7.9100e-01 + -1.5743e-01 1.3015e+00 -7.9100e-01 + -1.8351e-01 1.2981e+00 -7.9100e-01 + -2.0951e-01 1.2942e+00 -7.9100e-01 + -2.3543e-01 1.2897e+00 -7.9100e-01 + -2.6125e-01 1.2847e+00 -7.9100e-01 + -2.8697e-01 1.2792e+00 -7.9100e-01 + -3.1258e-01 1.2732e+00 -7.9100e-01 + -3.3806e-01 1.2667e+00 -7.9100e-01 + -3.6340e-01 1.2596e+00 -7.9100e-01 + -3.8859e-01 1.2521e+00 -7.9100e-01 + -4.1363e-01 1.2440e+00 -7.9100e-01 + -4.3851e-01 1.2355e+00 -7.9100e-01 + -4.6320e-01 1.2264e+00 -7.9100e-01 + -4.8771e-01 1.2169e+00 -7.9100e-01 + -4.8771e-01 1.2169e+00 -7.9100e-01 + -5.0910e-01 1.2703e+00 -6.9100e-01 + -5.3049e-01 1.3237e+00 -5.9100e-01 + -5.0068e-01 1.3352e+00 -5.9100e-01 + -4.7062e-01 1.3461e+00 -5.9100e-01 + -4.4032e-01 1.3563e+00 -5.9100e-01 + -4.0980e-01 1.3658e+00 -5.9100e-01 + -3.7908e-01 1.3747e+00 -5.9100e-01 + -3.4816e-01 1.3828e+00 -5.9100e-01 + -3.1707e-01 1.3903e+00 -5.9100e-01 + -2.8582e-01 1.3971e+00 -5.9100e-01 + -2.5442e-01 1.4031e+00 -5.9100e-01 + -2.2290e-01 1.4085e+00 -5.9100e-01 + -1.9127e-01 1.4131e+00 -5.9100e-01 + -1.5954e-01 1.4170e+00 -5.9100e-01 + -1.2773e-01 1.4203e+00 -5.9100e-01 + -9.5850e-02 1.4228e+00 -5.9100e-01 + -6.3927e-02 1.4246e+00 -5.9100e-01 + -3.1972e-02 1.4256e+00 -5.9100e-01 + 8.7317e-17 1.4260e+00 -5.9100e-01 + 8.3796e-17 1.3685e+00 -6.9100e-01 + 8.0276e-17 1.3110e+00 -7.9100e-01 + -9.2702e-01 9.2702e-01 -7.9100e-01 + -9.4543e-01 9.0823e-01 -7.9100e-01 + -9.6346e-01 8.8909e-01 -7.9100e-01 + -9.8110e-01 8.6958e-01 -7.9100e-01 + -9.9835e-01 8.4972e-01 -7.9100e-01 + -1.0152e+00 8.2952e-01 -7.9100e-01 + -1.0316e+00 8.0899e-01 -7.9100e-01 + -1.0476e+00 7.8813e-01 -7.9100e-01 + -1.0632e+00 7.6696e-01 -7.9100e-01 + -1.0784e+00 7.4547e-01 -7.9100e-01 + -1.0932e+00 7.2369e-01 -7.9100e-01 + -1.1075e+00 7.0161e-01 -7.9100e-01 + -1.1213e+00 6.7926e-01 -7.9100e-01 + -1.1347e+00 6.5663e-01 -7.9100e-01 + -1.1477e+00 6.3373e-01 -7.9100e-01 + -1.1601e+00 6.1058e-01 -7.9100e-01 + -1.1721e+00 5.8718e-01 -7.9100e-01 + -1.1837e+00 5.6355e-01 -7.9100e-01 + -1.1948e+00 5.3969e-01 -7.9100e-01 + -1.2053e+00 5.1562e-01 -7.9100e-01 + -1.2053e+00 5.1562e-01 -7.9100e-01 + -1.2582e+00 5.3823e-01 -6.9100e-01 + -1.3111e+00 5.6085e-01 -5.9100e-01 + -1.2982e+00 5.9010e-01 -5.9100e-01 + -1.2846e+00 6.1906e-01 -5.9100e-01 + -1.2704e+00 6.4770e-01 -5.9100e-01 + -1.2556e+00 6.7602e-01 -5.9100e-01 + -1.2401e+00 7.0400e-01 -5.9100e-01 + -1.2240e+00 7.3163e-01 -5.9100e-01 + -1.2073e+00 7.5889e-01 -5.9100e-01 + -1.1900e+00 7.8577e-01 -5.9100e-01 + -1.1721e+00 8.1225e-01 -5.9100e-01 + -1.1536e+00 8.3832e-01 -5.9100e-01 + -1.1345e+00 8.6398e-01 -5.9100e-01 + -1.1148e+00 8.8919e-01 -5.9100e-01 + -1.0946e+00 9.1397e-01 -5.9100e-01 + -1.0738e+00 9.3828e-01 -5.9100e-01 + -1.0525e+00 9.6212e-01 -5.9100e-01 + -1.0307e+00 9.8547e-01 -5.9100e-01 + -1.0083e+00 1.0083e+00 -5.9100e-01 + -9.6768e-01 9.6768e-01 -6.9100e-01 + -9.2702e-01 9.2702e-01 -7.9100e-01 + -1.3110e+00 1.6055e-16 -7.9100e-01 + -1.3107e+00 -2.6300e-02 -7.9100e-01 + -1.3099e+00 -5.2589e-02 -7.9100e-01 + -1.3086e+00 -7.8857e-02 -7.9100e-01 + -1.3068e+00 -1.0509e-01 -7.9100e-01 + -1.3044e+00 -1.3129e-01 -7.9100e-01 + -1.3015e+00 -1.5743e-01 -7.9100e-01 + -1.2981e+00 -1.8351e-01 -7.9100e-01 + -1.2942e+00 -2.0951e-01 -7.9100e-01 + -1.2897e+00 -2.3543e-01 -7.9100e-01 + -1.2847e+00 -2.6125e-01 -7.9100e-01 + -1.2792e+00 -2.8697e-01 -7.9100e-01 + -1.2732e+00 -3.1258e-01 -7.9100e-01 + -1.2667e+00 -3.3806e-01 -7.9100e-01 + -1.2596e+00 -3.6340e-01 -7.9100e-01 + -1.2521e+00 -3.8859e-01 -7.9100e-01 + -1.2440e+00 -4.1363e-01 -7.9100e-01 + -1.2355e+00 -4.3851e-01 -7.9100e-01 + -1.2264e+00 -4.6320e-01 -7.9100e-01 + -1.2169e+00 -4.8771e-01 -7.9100e-01 + -1.2169e+00 -4.8771e-01 -7.9100e-01 + -1.2703e+00 -5.0910e-01 -6.9100e-01 + -1.3237e+00 -5.3049e-01 -5.9100e-01 + -1.3352e+00 -5.0068e-01 -5.9100e-01 + -1.3461e+00 -4.7062e-01 -5.9100e-01 + -1.3563e+00 -4.4032e-01 -5.9100e-01 + -1.3658e+00 -4.0980e-01 -5.9100e-01 + -1.3747e+00 -3.7908e-01 -5.9100e-01 + -1.3828e+00 -3.4816e-01 -5.9100e-01 + -1.3903e+00 -3.1707e-01 -5.9100e-01 + -1.3971e+00 -2.8582e-01 -5.9100e-01 + -1.4031e+00 -2.5442e-01 -5.9100e-01 + -1.4085e+00 -2.2290e-01 -5.9100e-01 + -1.4131e+00 -1.9127e-01 -5.9100e-01 + -1.4170e+00 -1.5954e-01 -5.9100e-01 + -1.4203e+00 -1.2773e-01 -5.9100e-01 + -1.4228e+00 -9.5850e-02 -5.9100e-01 + -1.4246e+00 -6.3927e-02 -5.9100e-01 + -1.4256e+00 -3.1972e-02 -5.9100e-01 + -1.4260e+00 1.7463e-16 -5.9100e-01 + -1.3685e+00 1.6759e-16 -6.9100e-01 + -1.3110e+00 1.6055e-16 -7.9100e-01 + -9.2702e-01 -9.2702e-01 -7.9100e-01 + -9.0823e-01 -9.4543e-01 -7.9100e-01 + -8.8909e-01 -9.6346e-01 -7.9100e-01 + -8.6958e-01 -9.8110e-01 -7.9100e-01 + -8.4972e-01 -9.9835e-01 -7.9100e-01 + -8.2952e-01 -1.0152e+00 -7.9100e-01 + -8.0899e-01 -1.0316e+00 -7.9100e-01 + -7.8813e-01 -1.0476e+00 -7.9100e-01 + -7.6696e-01 -1.0632e+00 -7.9100e-01 + -7.4547e-01 -1.0784e+00 -7.9100e-01 + -7.2369e-01 -1.0932e+00 -7.9100e-01 + -7.0161e-01 -1.1075e+00 -7.9100e-01 + -6.7926e-01 -1.1213e+00 -7.9100e-01 + -6.5663e-01 -1.1347e+00 -7.9100e-01 + -6.3373e-01 -1.1477e+00 -7.9100e-01 + -6.1058e-01 -1.1601e+00 -7.9100e-01 + -5.8718e-01 -1.1721e+00 -7.9100e-01 + -5.6355e-01 -1.1837e+00 -7.9100e-01 + -5.3969e-01 -1.1948e+00 -7.9100e-01 + -5.1562e-01 -1.2053e+00 -7.9100e-01 + -5.1562e-01 -1.2053e+00 -7.9100e-01 + -5.3823e-01 -1.2582e+00 -6.9100e-01 + -5.6085e-01 -1.3111e+00 -5.9100e-01 + -5.9010e-01 -1.2982e+00 -5.9100e-01 + -6.1906e-01 -1.2846e+00 -5.9100e-01 + -6.4770e-01 -1.2704e+00 -5.9100e-01 + -6.7602e-01 -1.2556e+00 -5.9100e-01 + -7.0400e-01 -1.2401e+00 -5.9100e-01 + -7.3163e-01 -1.2240e+00 -5.9100e-01 + -7.5889e-01 -1.2073e+00 -5.9100e-01 + -7.8577e-01 -1.1900e+00 -5.9100e-01 + -8.1225e-01 -1.1721e+00 -5.9100e-01 + -8.3832e-01 -1.1536e+00 -5.9100e-01 + -8.6398e-01 -1.1345e+00 -5.9100e-01 + -8.8919e-01 -1.1148e+00 -5.9100e-01 + -9.1397e-01 -1.0946e+00 -5.9100e-01 + -9.3828e-01 -1.0738e+00 -5.9100e-01 + -9.6212e-01 -1.0525e+00 -5.9100e-01 + -9.8547e-01 -1.0307e+00 -5.9100e-01 + -1.0083e+00 -1.0083e+00 -5.9100e-01 + -9.6768e-01 -9.6768e-01 -6.9100e-01 + -9.2702e-01 -9.2702e-01 -7.9100e-01 + -2.4083e-16 -1.3110e+00 -7.9100e-01 + 2.6300e-02 -1.3107e+00 -7.9100e-01 + 5.2589e-02 -1.3099e+00 -7.9100e-01 + 7.8857e-02 -1.3086e+00 -7.9100e-01 + 1.0509e-01 -1.3068e+00 -7.9100e-01 + 1.3129e-01 -1.3044e+00 -7.9100e-01 + 1.5743e-01 -1.3015e+00 -7.9100e-01 + 1.8351e-01 -1.2981e+00 -7.9100e-01 + 2.0951e-01 -1.2942e+00 -7.9100e-01 + 2.3543e-01 -1.2897e+00 -7.9100e-01 + 2.6125e-01 -1.2847e+00 -7.9100e-01 + 2.8697e-01 -1.2792e+00 -7.9100e-01 + 3.1258e-01 -1.2732e+00 -7.9100e-01 + 3.3806e-01 -1.2667e+00 -7.9100e-01 + 3.6340e-01 -1.2596e+00 -7.9100e-01 + 3.8859e-01 -1.2521e+00 -7.9100e-01 + 4.1363e-01 -1.2440e+00 -7.9100e-01 + 4.3851e-01 -1.2355e+00 -7.9100e-01 + 4.6320e-01 -1.2264e+00 -7.9100e-01 + 4.8771e-01 -1.2169e+00 -7.9100e-01 + 4.8771e-01 -1.2169e+00 -7.9100e-01 + 5.0910e-01 -1.2703e+00 -6.9100e-01 + 5.3049e-01 -1.3237e+00 -5.9100e-01 + 5.0068e-01 -1.3352e+00 -5.9100e-01 + 4.7062e-01 -1.3461e+00 -5.9100e-01 + 4.4032e-01 -1.3563e+00 -5.9100e-01 + 4.0980e-01 -1.3658e+00 -5.9100e-01 + 3.7908e-01 -1.3747e+00 -5.9100e-01 + 3.4816e-01 -1.3828e+00 -5.9100e-01 + 3.1707e-01 -1.3903e+00 -5.9100e-01 + 2.8582e-01 -1.3971e+00 -5.9100e-01 + 2.5442e-01 -1.4031e+00 -5.9100e-01 + 2.2290e-01 -1.4085e+00 -5.9100e-01 + 1.9127e-01 -1.4131e+00 -5.9100e-01 + 1.5954e-01 -1.4170e+00 -5.9100e-01 + 1.2773e-01 -1.4203e+00 -5.9100e-01 + 9.5850e-02 -1.4228e+00 -5.9100e-01 + 6.3927e-02 -1.4246e+00 -5.9100e-01 + 3.1972e-02 -1.4256e+00 -5.9100e-01 + -2.6195e-16 -1.4260e+00 -5.9100e-01 + -2.5139e-16 -1.3685e+00 -6.9100e-01 + -2.4083e-16 -1.3110e+00 -7.9100e-01 + 9.2702e-01 -9.2702e-01 -7.9100e-01 + 9.4543e-01 -9.0823e-01 -7.9100e-01 + 9.6346e-01 -8.8909e-01 -7.9100e-01 + 9.8110e-01 -8.6958e-01 -7.9100e-01 + 9.9835e-01 -8.4972e-01 -7.9100e-01 + 1.0152e+00 -8.2952e-01 -7.9100e-01 + 1.0316e+00 -8.0899e-01 -7.9100e-01 + 1.0476e+00 -7.8813e-01 -7.9100e-01 + 1.0632e+00 -7.6696e-01 -7.9100e-01 + 1.0784e+00 -7.4547e-01 -7.9100e-01 + 1.0932e+00 -7.2369e-01 -7.9100e-01 + 1.1075e+00 -7.0161e-01 -7.9100e-01 + 1.1213e+00 -6.7926e-01 -7.9100e-01 + 1.1347e+00 -6.5663e-01 -7.9100e-01 + 1.1477e+00 -6.3373e-01 -7.9100e-01 + 1.1601e+00 -6.1058e-01 -7.9100e-01 + 1.1721e+00 -5.8718e-01 -7.9100e-01 + 1.1837e+00 -5.6355e-01 -7.9100e-01 + 1.1948e+00 -5.3969e-01 -7.9100e-01 + 1.2053e+00 -5.1562e-01 -7.9100e-01 + 1.2053e+00 -5.1562e-01 -7.9100e-01 + 1.2582e+00 -5.3823e-01 -6.9100e-01 + 1.3111e+00 -5.6085e-01 -5.9100e-01 + 1.2982e+00 -5.9010e-01 -5.9100e-01 + 1.2846e+00 -6.1906e-01 -5.9100e-01 + 1.2704e+00 -6.4770e-01 -5.9100e-01 + 1.2556e+00 -6.7602e-01 -5.9100e-01 + 1.2401e+00 -7.0400e-01 -5.9100e-01 + 1.2240e+00 -7.3163e-01 -5.9100e-01 + 1.2073e+00 -7.5889e-01 -5.9100e-01 + 1.1900e+00 -7.8577e-01 -5.9100e-01 + 1.1721e+00 -8.1225e-01 -5.9100e-01 + 1.1536e+00 -8.3832e-01 -5.9100e-01 + 1.1345e+00 -8.6398e-01 -5.9100e-01 + 1.1148e+00 -8.8919e-01 -5.9100e-01 + 1.0946e+00 -9.1397e-01 -5.9100e-01 + 1.0738e+00 -9.3828e-01 -5.9100e-01 + 1.0525e+00 -9.6212e-01 -5.9100e-01 + 1.0307e+00 -9.8547e-01 -5.9100e-01 + 1.0083e+00 -1.0083e+00 -5.9100e-01 + 9.6768e-01 -9.6768e-01 -6.9100e-01 + 9.2702e-01 -9.2702e-01 -7.9100e-01 diff --git a/src/ForcingTerms/coil_geometries/mastu_rmpu.dat b/src/ForcingTerms/coil_geometries/mastu_rmpu.dat index 59d20c96db..ba2a4e14f8 100644 --- a/src/ForcingTerms/coil_geometries/mastu_rmpu.dat +++ b/src/ForcingTerms/coil_geometries/mastu_rmpu.dat @@ -1,169 +1,169 @@ - 4 1 42 1.00 - 1.4180e+00 0.0000e+00 7.6000e-01 - 1.4177e+00 3.0026e-02 7.6000e-01 - 1.4167e+00 6.0039e-02 7.6000e-01 - 1.4151e+00 9.0025e-02 7.6000e-01 - 1.4129e+00 1.1997e-01 7.6000e-01 - 1.4101e+00 1.4986e-01 7.6000e-01 - 1.4066e+00 1.7969e-01 7.6000e-01 - 1.4024e+00 2.0943e-01 7.6000e-01 - 1.3977e+00 2.3908e-01 7.6000e-01 - 1.3923e+00 2.6862e-01 7.6000e-01 - 1.3863e+00 2.9805e-01 7.6000e-01 - 1.3797e+00 3.2733e-01 7.6000e-01 - 1.3725e+00 3.5648e-01 7.6000e-01 - 1.3646e+00 3.8546e-01 7.6000e-01 - 1.3561e+00 4.1427e-01 7.6000e-01 - 1.3471e+00 4.4289e-01 7.6000e-01 - 1.3374e+00 4.7132e-01 7.6000e-01 - 1.3271e+00 4.9953e-01 7.6000e-01 - 1.3162e+00 5.2752e-01 7.6000e-01 - 1.3162e+00 5.2752e-01 7.6000e-01 - 1.3487e+00 5.4054e-01 6.2950e-01 - 1.3812e+00 5.5356e-01 4.9900e-01 - 1.3926e+00 5.2419e-01 4.9900e-01 - 1.4034e+00 4.9458e-01 4.9900e-01 - 1.4136e+00 4.6475e-01 4.9900e-01 - 1.4231e+00 4.3472e-01 4.9900e-01 - 1.4320e+00 4.0449e-01 4.9900e-01 - 1.4402e+00 3.7407e-01 4.9900e-01 - 1.4478e+00 3.4349e-01 4.9900e-01 - 1.4548e+00 3.1276e-01 4.9900e-01 - 1.4611e+00 2.8188e-01 4.9900e-01 - 1.4667e+00 2.5088e-01 4.9900e-01 - 1.4717e+00 2.1977e-01 4.9900e-01 - 1.4760e+00 1.8856e-01 4.9900e-01 - 1.4797e+00 1.5726e-01 4.9900e-01 - 1.4827e+00 1.2589e-01 4.9900e-01 - 1.4850e+00 9.4469e-02 4.9900e-01 - 1.4867e+00 6.3003e-02 4.9900e-01 - 1.4877e+00 3.1509e-02 4.9900e-01 - 1.4880e+00 0.0000e+00 4.9900e-01 - 1.4530e+00 0.0000e+00 6.2950e-01 - 1.4180e+00 0.0000e+00 7.6000e-01 - 8.6827e-17 1.4180e+00 7.6000e-01 - -3.0026e-02 1.4177e+00 7.6000e-01 - -6.0039e-02 1.4167e+00 7.6000e-01 - -9.0025e-02 1.4151e+00 7.6000e-01 - -1.1997e-01 1.4129e+00 7.6000e-01 - -1.4986e-01 1.4101e+00 7.6000e-01 - -1.7969e-01 1.4066e+00 7.6000e-01 - -2.0943e-01 1.4024e+00 7.6000e-01 - -2.3908e-01 1.3977e+00 7.6000e-01 - -2.6862e-01 1.3923e+00 7.6000e-01 - -2.9805e-01 1.3863e+00 7.6000e-01 - -3.2733e-01 1.3797e+00 7.6000e-01 - -3.5648e-01 1.3725e+00 7.6000e-01 - -3.8546e-01 1.3646e+00 7.6000e-01 - -4.1427e-01 1.3561e+00 7.6000e-01 - -4.4289e-01 1.3471e+00 7.6000e-01 - -4.7132e-01 1.3374e+00 7.6000e-01 - -4.9953e-01 1.3271e+00 7.6000e-01 - -5.2752e-01 1.3162e+00 7.6000e-01 - -5.2752e-01 1.3162e+00 7.6000e-01 - -5.4054e-01 1.3487e+00 6.2950e-01 - -5.5356e-01 1.3812e+00 4.9900e-01 - -5.2419e-01 1.3926e+00 4.9900e-01 - -4.9458e-01 1.4034e+00 4.9900e-01 - -4.6475e-01 1.4136e+00 4.9900e-01 - -4.3472e-01 1.4231e+00 4.9900e-01 - -4.0449e-01 1.4320e+00 4.9900e-01 - -3.7407e-01 1.4402e+00 4.9900e-01 - -3.4349e-01 1.4478e+00 4.9900e-01 - -3.1276e-01 1.4548e+00 4.9900e-01 - -2.8188e-01 1.4611e+00 4.9900e-01 - -2.5088e-01 1.4667e+00 4.9900e-01 - -2.1977e-01 1.4717e+00 4.9900e-01 - -1.8856e-01 1.4760e+00 4.9900e-01 - -1.5726e-01 1.4797e+00 4.9900e-01 - -1.2589e-01 1.4827e+00 4.9900e-01 - -9.4469e-02 1.4850e+00 4.9900e-01 - -6.3003e-02 1.4867e+00 4.9900e-01 - -3.1509e-02 1.4877e+00 4.9900e-01 - 9.1114e-17 1.4880e+00 4.9900e-01 - 8.8971e-17 1.4530e+00 6.2950e-01 - 8.6827e-17 1.4180e+00 7.6000e-01 - -1.4180e+00 1.7365e-16 7.6000e-01 - -1.4177e+00 -3.0026e-02 7.6000e-01 - -1.4167e+00 -6.0039e-02 7.6000e-01 - -1.4151e+00 -9.0025e-02 7.6000e-01 - -1.4129e+00 -1.1997e-01 7.6000e-01 - -1.4101e+00 -1.4986e-01 7.6000e-01 - -1.4066e+00 -1.7969e-01 7.6000e-01 - -1.4024e+00 -2.0943e-01 7.6000e-01 - -1.3977e+00 -2.3908e-01 7.6000e-01 - -1.3923e+00 -2.6862e-01 7.6000e-01 - -1.3863e+00 -2.9805e-01 7.6000e-01 - -1.3797e+00 -3.2733e-01 7.6000e-01 - -1.3725e+00 -3.5648e-01 7.6000e-01 - -1.3646e+00 -3.8546e-01 7.6000e-01 - -1.3561e+00 -4.1427e-01 7.6000e-01 - -1.3471e+00 -4.4289e-01 7.6000e-01 - -1.3374e+00 -4.7132e-01 7.6000e-01 - -1.3271e+00 -4.9953e-01 7.6000e-01 - -1.3162e+00 -5.2752e-01 7.6000e-01 - -1.3162e+00 -5.2752e-01 7.6000e-01 - -1.3487e+00 -5.4054e-01 6.2950e-01 - -1.3812e+00 -5.5356e-01 4.9900e-01 - -1.3926e+00 -5.2419e-01 4.9900e-01 - -1.4034e+00 -4.9458e-01 4.9900e-01 - -1.4136e+00 -4.6475e-01 4.9900e-01 - -1.4231e+00 -4.3472e-01 4.9900e-01 - -1.4320e+00 -4.0449e-01 4.9900e-01 - -1.4402e+00 -3.7407e-01 4.9900e-01 - -1.4478e+00 -3.4349e-01 4.9900e-01 - -1.4548e+00 -3.1276e-01 4.9900e-01 - -1.4611e+00 -2.8188e-01 4.9900e-01 - -1.4667e+00 -2.5088e-01 4.9900e-01 - -1.4717e+00 -2.1977e-01 4.9900e-01 - -1.4760e+00 -1.8856e-01 4.9900e-01 - -1.4797e+00 -1.5726e-01 4.9900e-01 - -1.4827e+00 -1.2589e-01 4.9900e-01 - -1.4850e+00 -9.4469e-02 4.9900e-01 - -1.4867e+00 -6.3003e-02 4.9900e-01 - -1.4877e+00 -3.1509e-02 4.9900e-01 - -1.4880e+00 1.8223e-16 4.9900e-01 - -1.4530e+00 1.7794e-16 6.2950e-01 - -1.4180e+00 1.7365e-16 7.6000e-01 - -2.6048e-16 -1.4180e+00 7.6000e-01 - 3.0026e-02 -1.4177e+00 7.6000e-01 - 6.0039e-02 -1.4167e+00 7.6000e-01 - 9.0025e-02 -1.4151e+00 7.6000e-01 - 1.1997e-01 -1.4129e+00 7.6000e-01 - 1.4986e-01 -1.4101e+00 7.6000e-01 - 1.7969e-01 -1.4066e+00 7.6000e-01 - 2.0943e-01 -1.4024e+00 7.6000e-01 - 2.3908e-01 -1.3977e+00 7.6000e-01 - 2.6862e-01 -1.3923e+00 7.6000e-01 - 2.9805e-01 -1.3863e+00 7.6000e-01 - 3.2733e-01 -1.3797e+00 7.6000e-01 - 3.5648e-01 -1.3725e+00 7.6000e-01 - 3.8546e-01 -1.3646e+00 7.6000e-01 - 4.1427e-01 -1.3561e+00 7.6000e-01 - 4.4289e-01 -1.3471e+00 7.6000e-01 - 4.7132e-01 -1.3374e+00 7.6000e-01 - 4.9953e-01 -1.3271e+00 7.6000e-01 - 5.2752e-01 -1.3162e+00 7.6000e-01 - 5.2752e-01 -1.3162e+00 7.6000e-01 - 5.4054e-01 -1.3487e+00 6.2950e-01 - 5.5356e-01 -1.3812e+00 4.9900e-01 - 5.2419e-01 -1.3926e+00 4.9900e-01 - 4.9458e-01 -1.4034e+00 4.9900e-01 - 4.6475e-01 -1.4136e+00 4.9900e-01 - 4.3472e-01 -1.4231e+00 4.9900e-01 - 4.0449e-01 -1.4320e+00 4.9900e-01 - 3.7407e-01 -1.4402e+00 4.9900e-01 - 3.4349e-01 -1.4478e+00 4.9900e-01 - 3.1276e-01 -1.4548e+00 4.9900e-01 - 2.8188e-01 -1.4611e+00 4.9900e-01 - 2.5088e-01 -1.4667e+00 4.9900e-01 - 2.1977e-01 -1.4717e+00 4.9900e-01 - 1.8856e-01 -1.4760e+00 4.9900e-01 - 1.5726e-01 -1.4797e+00 4.9900e-01 - 1.2589e-01 -1.4827e+00 4.9900e-01 - 9.4469e-02 -1.4850e+00 4.9900e-01 - 6.3003e-02 -1.4867e+00 4.9900e-01 - 3.1509e-02 -1.4877e+00 4.9900e-01 - -2.7334e-16 -1.4880e+00 4.9900e-01 - -2.6691e-16 -1.4530e+00 6.2950e-01 - -2.6048e-16 -1.4180e+00 7.6000e-01 + 4 1 42 1.00 + 1.4180e+00 0.0000e+00 7.6000e-01 + 1.4177e+00 3.0026e-02 7.6000e-01 + 1.4167e+00 6.0039e-02 7.6000e-01 + 1.4151e+00 9.0025e-02 7.6000e-01 + 1.4129e+00 1.1997e-01 7.6000e-01 + 1.4101e+00 1.4986e-01 7.6000e-01 + 1.4066e+00 1.7969e-01 7.6000e-01 + 1.4024e+00 2.0943e-01 7.6000e-01 + 1.3977e+00 2.3908e-01 7.6000e-01 + 1.3923e+00 2.6862e-01 7.6000e-01 + 1.3863e+00 2.9805e-01 7.6000e-01 + 1.3797e+00 3.2733e-01 7.6000e-01 + 1.3725e+00 3.5648e-01 7.6000e-01 + 1.3646e+00 3.8546e-01 7.6000e-01 + 1.3561e+00 4.1427e-01 7.6000e-01 + 1.3471e+00 4.4289e-01 7.6000e-01 + 1.3374e+00 4.7132e-01 7.6000e-01 + 1.3271e+00 4.9953e-01 7.6000e-01 + 1.3162e+00 5.2752e-01 7.6000e-01 + 1.3162e+00 5.2752e-01 7.6000e-01 + 1.3487e+00 5.4054e-01 6.2950e-01 + 1.3812e+00 5.5356e-01 4.9900e-01 + 1.3926e+00 5.2419e-01 4.9900e-01 + 1.4034e+00 4.9458e-01 4.9900e-01 + 1.4136e+00 4.6475e-01 4.9900e-01 + 1.4231e+00 4.3472e-01 4.9900e-01 + 1.4320e+00 4.0449e-01 4.9900e-01 + 1.4402e+00 3.7407e-01 4.9900e-01 + 1.4478e+00 3.4349e-01 4.9900e-01 + 1.4548e+00 3.1276e-01 4.9900e-01 + 1.4611e+00 2.8188e-01 4.9900e-01 + 1.4667e+00 2.5088e-01 4.9900e-01 + 1.4717e+00 2.1977e-01 4.9900e-01 + 1.4760e+00 1.8856e-01 4.9900e-01 + 1.4797e+00 1.5726e-01 4.9900e-01 + 1.4827e+00 1.2589e-01 4.9900e-01 + 1.4850e+00 9.4469e-02 4.9900e-01 + 1.4867e+00 6.3003e-02 4.9900e-01 + 1.4877e+00 3.1509e-02 4.9900e-01 + 1.4880e+00 0.0000e+00 4.9900e-01 + 1.4530e+00 0.0000e+00 6.2950e-01 + 1.4180e+00 0.0000e+00 7.6000e-01 + 8.6827e-17 1.4180e+00 7.6000e-01 + -3.0026e-02 1.4177e+00 7.6000e-01 + -6.0039e-02 1.4167e+00 7.6000e-01 + -9.0025e-02 1.4151e+00 7.6000e-01 + -1.1997e-01 1.4129e+00 7.6000e-01 + -1.4986e-01 1.4101e+00 7.6000e-01 + -1.7969e-01 1.4066e+00 7.6000e-01 + -2.0943e-01 1.4024e+00 7.6000e-01 + -2.3908e-01 1.3977e+00 7.6000e-01 + -2.6862e-01 1.3923e+00 7.6000e-01 + -2.9805e-01 1.3863e+00 7.6000e-01 + -3.2733e-01 1.3797e+00 7.6000e-01 + -3.5648e-01 1.3725e+00 7.6000e-01 + -3.8546e-01 1.3646e+00 7.6000e-01 + -4.1427e-01 1.3561e+00 7.6000e-01 + -4.4289e-01 1.3471e+00 7.6000e-01 + -4.7132e-01 1.3374e+00 7.6000e-01 + -4.9953e-01 1.3271e+00 7.6000e-01 + -5.2752e-01 1.3162e+00 7.6000e-01 + -5.2752e-01 1.3162e+00 7.6000e-01 + -5.4054e-01 1.3487e+00 6.2950e-01 + -5.5356e-01 1.3812e+00 4.9900e-01 + -5.2419e-01 1.3926e+00 4.9900e-01 + -4.9458e-01 1.4034e+00 4.9900e-01 + -4.6475e-01 1.4136e+00 4.9900e-01 + -4.3472e-01 1.4231e+00 4.9900e-01 + -4.0449e-01 1.4320e+00 4.9900e-01 + -3.7407e-01 1.4402e+00 4.9900e-01 + -3.4349e-01 1.4478e+00 4.9900e-01 + -3.1276e-01 1.4548e+00 4.9900e-01 + -2.8188e-01 1.4611e+00 4.9900e-01 + -2.5088e-01 1.4667e+00 4.9900e-01 + -2.1977e-01 1.4717e+00 4.9900e-01 + -1.8856e-01 1.4760e+00 4.9900e-01 + -1.5726e-01 1.4797e+00 4.9900e-01 + -1.2589e-01 1.4827e+00 4.9900e-01 + -9.4469e-02 1.4850e+00 4.9900e-01 + -6.3003e-02 1.4867e+00 4.9900e-01 + -3.1509e-02 1.4877e+00 4.9900e-01 + 9.1114e-17 1.4880e+00 4.9900e-01 + 8.8971e-17 1.4530e+00 6.2950e-01 + 8.6827e-17 1.4180e+00 7.6000e-01 + -1.4180e+00 1.7365e-16 7.6000e-01 + -1.4177e+00 -3.0026e-02 7.6000e-01 + -1.4167e+00 -6.0039e-02 7.6000e-01 + -1.4151e+00 -9.0025e-02 7.6000e-01 + -1.4129e+00 -1.1997e-01 7.6000e-01 + -1.4101e+00 -1.4986e-01 7.6000e-01 + -1.4066e+00 -1.7969e-01 7.6000e-01 + -1.4024e+00 -2.0943e-01 7.6000e-01 + -1.3977e+00 -2.3908e-01 7.6000e-01 + -1.3923e+00 -2.6862e-01 7.6000e-01 + -1.3863e+00 -2.9805e-01 7.6000e-01 + -1.3797e+00 -3.2733e-01 7.6000e-01 + -1.3725e+00 -3.5648e-01 7.6000e-01 + -1.3646e+00 -3.8546e-01 7.6000e-01 + -1.3561e+00 -4.1427e-01 7.6000e-01 + -1.3471e+00 -4.4289e-01 7.6000e-01 + -1.3374e+00 -4.7132e-01 7.6000e-01 + -1.3271e+00 -4.9953e-01 7.6000e-01 + -1.3162e+00 -5.2752e-01 7.6000e-01 + -1.3162e+00 -5.2752e-01 7.6000e-01 + -1.3487e+00 -5.4054e-01 6.2950e-01 + -1.3812e+00 -5.5356e-01 4.9900e-01 + -1.3926e+00 -5.2419e-01 4.9900e-01 + -1.4034e+00 -4.9458e-01 4.9900e-01 + -1.4136e+00 -4.6475e-01 4.9900e-01 + -1.4231e+00 -4.3472e-01 4.9900e-01 + -1.4320e+00 -4.0449e-01 4.9900e-01 + -1.4402e+00 -3.7407e-01 4.9900e-01 + -1.4478e+00 -3.4349e-01 4.9900e-01 + -1.4548e+00 -3.1276e-01 4.9900e-01 + -1.4611e+00 -2.8188e-01 4.9900e-01 + -1.4667e+00 -2.5088e-01 4.9900e-01 + -1.4717e+00 -2.1977e-01 4.9900e-01 + -1.4760e+00 -1.8856e-01 4.9900e-01 + -1.4797e+00 -1.5726e-01 4.9900e-01 + -1.4827e+00 -1.2589e-01 4.9900e-01 + -1.4850e+00 -9.4469e-02 4.9900e-01 + -1.4867e+00 -6.3003e-02 4.9900e-01 + -1.4877e+00 -3.1509e-02 4.9900e-01 + -1.4880e+00 1.8223e-16 4.9900e-01 + -1.4530e+00 1.7794e-16 6.2950e-01 + -1.4180e+00 1.7365e-16 7.6000e-01 + -2.6048e-16 -1.4180e+00 7.6000e-01 + 3.0026e-02 -1.4177e+00 7.6000e-01 + 6.0039e-02 -1.4167e+00 7.6000e-01 + 9.0025e-02 -1.4151e+00 7.6000e-01 + 1.1997e-01 -1.4129e+00 7.6000e-01 + 1.4986e-01 -1.4101e+00 7.6000e-01 + 1.7969e-01 -1.4066e+00 7.6000e-01 + 2.0943e-01 -1.4024e+00 7.6000e-01 + 2.3908e-01 -1.3977e+00 7.6000e-01 + 2.6862e-01 -1.3923e+00 7.6000e-01 + 2.9805e-01 -1.3863e+00 7.6000e-01 + 3.2733e-01 -1.3797e+00 7.6000e-01 + 3.5648e-01 -1.3725e+00 7.6000e-01 + 3.8546e-01 -1.3646e+00 7.6000e-01 + 4.1427e-01 -1.3561e+00 7.6000e-01 + 4.4289e-01 -1.3471e+00 7.6000e-01 + 4.7132e-01 -1.3374e+00 7.6000e-01 + 4.9953e-01 -1.3271e+00 7.6000e-01 + 5.2752e-01 -1.3162e+00 7.6000e-01 + 5.2752e-01 -1.3162e+00 7.6000e-01 + 5.4054e-01 -1.3487e+00 6.2950e-01 + 5.5356e-01 -1.3812e+00 4.9900e-01 + 5.2419e-01 -1.3926e+00 4.9900e-01 + 4.9458e-01 -1.4034e+00 4.9900e-01 + 4.6475e-01 -1.4136e+00 4.9900e-01 + 4.3472e-01 -1.4231e+00 4.9900e-01 + 4.0449e-01 -1.4320e+00 4.9900e-01 + 3.7407e-01 -1.4402e+00 4.9900e-01 + 3.4349e-01 -1.4478e+00 4.9900e-01 + 3.1276e-01 -1.4548e+00 4.9900e-01 + 2.8188e-01 -1.4611e+00 4.9900e-01 + 2.5088e-01 -1.4667e+00 4.9900e-01 + 2.1977e-01 -1.4717e+00 4.9900e-01 + 1.8856e-01 -1.4760e+00 4.9900e-01 + 1.5726e-01 -1.4797e+00 4.9900e-01 + 1.2589e-01 -1.4827e+00 4.9900e-01 + 9.4469e-02 -1.4850e+00 4.9900e-01 + 6.3003e-02 -1.4867e+00 4.9900e-01 + 3.1509e-02 -1.4877e+00 4.9900e-01 + -2.7334e-16 -1.4880e+00 4.9900e-01 + -2.6691e-16 -1.4530e+00 6.2950e-01 + -2.6048e-16 -1.4180e+00 7.6000e-01 diff --git a/src/InnerLayer/GGJ/Galerkin.jl b/src/InnerLayer/GGJ/Galerkin.jl index 7cd422f40c..1fa75c2401 100644 --- a/src/InnerLayer/GGJ/Galerkin.jl +++ b/src/InnerLayer/GGJ/Galerkin.jl @@ -604,7 +604,7 @@ function _parity_bcs_and_solve!(ws::GalerkinWorkspace, params::GGJParameters, Q: if i > ws.ndim continue end - for jj in max(1, i-ws.kl):min(ws.ndim, i+ws.kl) + for jj in max(1, i - ws.kl):min(ws.ndim, i + ws.kl) ws.mat[offset+i-jj, jj, isol] = 0 end end diff --git a/src/InnerLayer/GGJ/Ray.jl b/src/InnerLayer/GGJ/Ray.jl index 0abe74451c..32136da918 100644 --- a/src/InnerLayer/GGJ/Ray.jl +++ b/src/InnerLayer/GGJ/Ray.jl @@ -355,7 +355,7 @@ function solve_ray(params::GGJParameters, Q::ComplexF64; local Us, Ub, E, bc_cond, Sbvp if s_m < 0.98 * Sv - UsS, UbS = handoff === nothing ? _power_states(cache, cis(θ) * Sv) : handoff + UsS, UbS = handoff === nothing ? _power_states(cache, cis(θ) * Sv) : handoff Us, Ub, E = march_boundary(params, Q, θ, Sv, s_m, UsS, UbS; rtol=march_rtol, growth=growth, seed=seed, ztrk=ztrk, purge_budget=purge_budget, dp_res=dp_res) @@ -370,7 +370,9 @@ function solve_ray(params::GGJParameters, Q::ComplexF64; bc_cond < 1e12 || @warn "boundary basis poorly conditioned (cond ≈ $bc_cond)" breaks = ray_breaks(params, Q, θ, Sbvp; cfl=cfl, min_cells=min_cells) - length(breaks) - 1 > warn_cells && @warn "GGJ ray: $(length(breaks) - 1) Hermite cells exceed warn_cells = $warn_cells, so the solve is slow; the grid follows cfl = $cfl, min_cells = $min_cells and the local rates at this Q" Q maxlog = 1 _id = (:ggj_ray_cells, warn_cells) + length(breaks) - 1 > warn_cells && + @warn "GGJ ray: $(length(breaks) - 1) Hermite cells exceed warn_cells = $warn_cells, so the solve is slow; the grid follows cfl = $cfl, min_cells = $min_cells and the local rates at this Q" Q maxlog = + 1 _id = (:ggj_ray_cells, warn_cells) ws = _galerkin_workspace(breaks, fill(CT_NONE, length(breaks) - 1)) Δraw, cexp = _assemble_and_solve_point!(ws, params, Q, θ, Us, Ub, E; nq=nq) verbose && @printf(" BVP: %d Hermite cells on [0, %.4g], Δraw[1] = %.6e %+.6eim\n", diff --git a/src/InnerLayer/SLAYER/LayerInputs.jl b/src/InnerLayer/SLAYER/LayerInputs.jl index d17cb06a59..899d82726a 100644 --- a/src/InnerLayer/SLAYER/LayerInputs.jl +++ b/src/InnerLayer/SLAYER/LayerInputs.jl @@ -398,7 +398,7 @@ function build_slayer_inputs(equil, sings, profiles::KineticProfiles; @warn( "build_slayer_inputs: sing.restype not populated; using the " * "slab Mercier exponent α = 1/2 for the Δ' reference-length " * - "conversion at all such surfaces.", maxlog=1) + "conversion at all such surfaces.", maxlog = 1) 0.5 else sqrt(max(-(rg.E + rg.F + rg.H - 0.25), 0.0)) diff --git a/src/InnerLayer/SLAYER/LayerParameters.jl b/src/InnerLayer/SLAYER/LayerParameters.jl index 7f84e1bf5b..abf740b894 100644 --- a/src/InnerLayer/SLAYER/LayerParameters.jl +++ b/src/InnerLayer/SLAYER/LayerParameters.jl @@ -218,6 +218,7 @@ model does not use may be left `nothing`; one it uses must be given, positive an # Arguments - `n_e` -- electron density in m⁻³ + - `t_e`, `t_i` -- electron and ion temperatures in eV - `omega_e`, `omega_i` -- electron and ion diamagnetic frequencies in rad/s - `qval` -- safety factor at the surface @@ -234,6 +235,7 @@ model does not use may be left `nothing`; one it uses must be given, positive an - `a_minor` -- plasma minor radius `a` in m; needed by `:tau_E` - `dlnn`, `dlnTe`, `dlnTi` -- radial log-gradients of n, T_e, T_i in any common radial variable (only ratios enter); needed by `:D_perp` - `P_perp_model` -- how `P_perp` is built (default `:D_perp`, as in the `[SLAYER]` deck): + + `:D_perp` -- τ_R/τ_⊥ with τ_⊥ = r_s²/D_⊥, Fitzpatrick, Phys. Plasmas 29, 032507 (2022), Eq. 16 (with Eqs. 4-6 and η_⊥ = η_∥); its τ = (T_e/T_i)(1+η_e)/(1+η_i) is the inverse of the `tau` field; flat temperatures give the `:c_beta` value + `:chi_perp_e`, `:chi_perp_i` -- τ_R·χ⊥/r_s² with the electron or ion χ⊥ @@ -365,8 +367,12 @@ function slayer_parameters(; all(x -> x !== nothing && isfinite(x), (dlnn, dlnTe, dlnTi)) || throw(ArgumentError("slayer_parameters: P_perp_model=:D_perp needs finite dlnn, dlnTe and dlnTi; got $dlnn, $dlnTe, $dlnTi")) dp = t_e * (dlnn + dlnTe) + t_i * (dlnn + dlnTi) - dp != 0 || throw(ArgumentError("slayer_parameters: P_perp_model=:D_perp is undefined where the pressure gradient vanishes " * - "(t_e(dlnn+dlnTe) + t_i(dlnn+dlnTi) = 0); choose another P_perp_model for this surface")) + dp != 0 || throw( + ArgumentError( + "slayer_parameters: P_perp_model=:D_perp is undefined where the pressure gradient vanishes " * + "(t_e(dlnn+dlnTe) + t_i(dlnn+dlnTi) = 0); choose another P_perp_model for this surface" + ) + ) D_perp = c_beta^2 * eta / MU_0 + (2 / 3) * (1 - c_beta^2) * (t_e * dlnTe * chi_perp_e + t_i * dlnTi * chi_perp_i) / dp tau_r / (rs^2 / D_perp) @@ -377,8 +383,12 @@ function slayer_parameters(; "Allowed: $(ALLOWED_P_PERP_MODELS)")) end isfinite(P_perp) && P_perp > 0 || - throw(ArgumentError("slayer_parameters: P_perp_model=:$P_perp_model gave P_perp=$P_perp; check its inputs " * - "(chi_perp_e=$chi_perp_e, chi_perp_i=$chi_perp_i, dlnn=$dlnn, dlnTe=$dlnTe, dlnTi=$dlnTi)")) + throw( + ArgumentError( + "slayer_parameters: P_perp_model=:$P_perp_model gave P_perp=$P_perp; check its inputs " * + "(chi_perp_e=$chi_perp_e, chi_perp_i=$chi_perp_i, dlnn=$dlnn, dlnTe=$dlnTe, dlnTi=$dlnTi)" + ) + ) # Normalized beta-related width and Δ-normalization d_beta = c_beta * d_i diff --git a/src/KineticForces/KineticForces.jl b/src/KineticForces/KineticForces.jl index 154975ca88..26b2c4c4e4 100644 --- a/src/KineticForces/KineticForces.jl +++ b/src/KineticForces/KineticForces.jl @@ -41,11 +41,11 @@ include("CalculatedKineticMatrices.jl") # Global constants const mp = 1.672_614e-27 # proton mass (kg) const me = 9.109_1e-31 # electron mass (kg) -const e = 1.602_191_7e-19 # elementary charge (C) +const e = 1.602_191_7e-19 # elementary charge (C) const eV = e # joules per electron-volt const twopi = 2π -const μ₀ = 4e-7 * π +const μ₀ = 4e-7 * π const rad2deg = 180 / π const deg2rad = π / 180 diff --git a/src/KineticForces/Output.jl b/src/KineticForces/Output.jl index 10b55dcb39..4810d1d8a9 100644 --- a/src/KineticForces/Output.jl +++ b/src/KineticForces/Output.jl @@ -119,7 +119,12 @@ const KF_METHOD_H5_ANNOTATIONS = [ "dTdpsi" => (; long_name="complex torque density dT/dψ_N = dT_φ/dψ_N + 2i·n·dδW_k/dψ_N; divide by dVdpsi for dT/dV", units="N*m", dims=("psi",), attach=(1 => "psi",)), "T" => (; long_name="cumulative complex torque T(ψ_N) = T_φ + 2i·n·δW_k (trapezoidal)", units="N*m", dims=("psi",), attach=(1 => "psi",)), "ell" => (; long_name="bounce harmonic ℓ ∈ -nl:nl of the per-harmonic torque density", scale="ell"), - "dTdpsi_ell" => (; long_name="complex torque density dT/dψ_N resolved by bounce harmonic ℓ; summing over ℓ reproduces dTdpsi", units="N*m", dims=("psi", "ell"), attach=(1 => "psi", 2 => "ell")), + "dTdpsi_ell" => (; + long_name="complex torque density dT/dψ_N resolved by bounce harmonic ℓ; summing over ℓ reproduces dTdpsi", + units="N*m", + dims=("psi", "ell"), + attach=(1 => "psi", 2 => "ell") + ), "dVdpsi" => (; long_name="flux-surface volume derivative dV/dψ_N at quadrature points", units="m^3", dims=("psi",), attach=(1 => "psi",)), "KineticMatrices/A" => (; long_name="Logan 2015 drift-kinetic coefficient matrix A = W_Z†W_Z (energy normalization)", dims=("mode_row", "mode_col")), "KineticMatrices/B" => (; long_name="Logan 2015 drift-kinetic coefficient matrix B = W_Z†W_X (energy normalization)", dims=("mode_row", "mode_col")), diff --git a/src/KineticForces/Torque.jl b/src/KineticForces/Torque.jl index 8ef06bfed7..82e6102ac5 100644 --- a/src/KineticForces/Torque.jl +++ b/src/KineticForces/Torque.jl @@ -28,7 +28,6 @@ Toroidal torque from nonambipolar transport at one ψ and bounce harmonic `l`, w - `op_wmats::Array{ComplexF64,3}`: Store ForceFreeStates matrix elements - `surface`: Per-surface bundle from `_torque_surface`, shared across the bounce harmonics of one ψ; built here when not given - """ function tpsi!(tpsi_var::Ref{ComplexF64}, psi::Float64, n::Int, l::Int, zi::Int, mi::Int, wdfac::Float64, divxfac::Float64, diff --git a/src/KineticForces/Utils.jl b/src/KineticForces/Utils.jl index 88b09b7262..60c3aa03e8 100644 --- a/src/KineticForces/Utils.jl +++ b/src/KineticForces/Utils.jl @@ -136,7 +136,7 @@ function kinetic_axis_validity_psi(kinetic_profiles::Equilibrium.KineticProfileS end end psi_outer > 0 && @warn "kinetic_axis_validity_psi: orbit widths also reach ⟨r⟩ out to ψ=$(round(psi_outer; sigdigits=3)), " * - "outside the axis-contiguous region ending at ψ_c=$(round(psi_c; sigdigits=3)); only the latter is suppressed" maxlog = 1 + "outside the axis-contiguous region ending at ψ_c=$(round(psi_c; sigdigits=3)); only the latter is suppressed" maxlog = 1 return psi_c end @@ -354,9 +354,12 @@ function resonance_grid_nodes(ctrl, kf_ctrl::KineticForcesControl, kinetic_profi (ForceFreeStates.kinetic_source(ctrl.kinetic) isa KineticForcesControl && kinetic_profiles !== nothing) || return nodes for n_res in intr.nlow:intr.nhigh n_res == 0 && continue - append!(nodes, kinetic_resonance_psi_nodes(kinetic_profiles, equil; - n=n_res, nl=kf_ctrl.bounce_harmonic_max, zi=main_ion(equil)[1], mi=main_ion(equil)[2], - electron=kf_ctrl.electron, wdfac=kf_ctrl.magnetic_drift_factor)) + append!( + nodes, + kinetic_resonance_psi_nodes(kinetic_profiles, equil; + n=n_res, nl=kf_ctrl.bounce_harmonic_max, zi=main_ion(equil)[1], mi=main_ion(equil)[2], + electron=kf_ctrl.electron, wdfac=kf_ctrl.magnetic_drift_factor) + ) end psi_c = axis_validity_boundary(kf_ctrl, species, kinetic_profiles, equil, rationals) psi_c > 0 && filter!(p -> p > psi_c, nodes) diff --git a/src/LocalStability/Ballooning.jl b/src/LocalStability/Ballooning.jl index 6b989a7058..2c8470f009 100644 --- a/src/LocalStability/Ballooning.jl +++ b/src/LocalStability/Ballooning.jl @@ -714,17 +714,18 @@ function ballooning_alpha_crossings( ref = salpha_reference(psi_idx, plasma_eq) # Δ' vs α scaling at fixed shear; a failed evaluation (extreme corrections) returns NaN and is not a crossing. - delta_at(scale) = try - ballooning_delta_prime( - psi_idx, - plasma_eq; - corr_qprime=0.0, - corr_pprime=ref.pprime_norm_ref * (scale - 1.0), - theta_k=theta_k - ).delta_prime - catch - NaN - end + delta_at(scale) = + try + ballooning_delta_prime( + psi_idx, + plasma_eq; + corr_qprime=0.0, + corr_pprime=ref.pprime_norm_ref * (scale - 1.0), + theta_k=theta_k + ).delta_prime + catch + NaN + end samples = collect(range(0.0, max_alpha_scale; length=n_scan + 1)) scales = _ballooning_marginal_crossings(delta_at, samples, tol; max_crossings=max_crossings) @@ -896,7 +897,7 @@ function ballooning_alpha_boundaries( length(cr.alphas) >= 2 && (alpha_critical2[i] = cr.alphas[2]) catch err if verbose - @warn "ballooning alpha boundaries failed" psi_idx=i exception=err + @warn "ballooning alpha boundaries failed" psi_idx = i exception = err end end end @@ -930,17 +931,18 @@ function ballooning_qprime_crossings( ref = salpha_reference(psi_idx, plasma_eq) # Δ' vs q' scaling at the experimental pressure gradient; a failed evaluation returns NaN and is not a crossing. - delta_at(scale) = try - ballooning_delta_prime( - psi_idx, - plasma_eq; - corr_qprime=ref.qprime_norm_ref * (scale - 1.0), - corr_pprime=0.0, - theta_k=theta_k - ).delta_prime - catch - NaN - end + delta_at(scale) = + try + ballooning_delta_prime( + psi_idx, + plasma_eq; + corr_qprime=ref.qprime_norm_ref * (scale - 1.0), + corr_pprime=0.0, + theta_k=theta_k + ).delta_prime + catch + NaN + end samples = collect(range(max_qprime_scale, min_qprime_scale; length=n_scan + 1)) scales = _ballooning_marginal_crossings(delta_at, samples, tol) @@ -985,7 +987,7 @@ function ballooning_qprime_boundaries( length(cr.qprimes) >= 2 && (qprime_critical2[i] = cr.qprimes[2]) catch err if verbose - @warn "ballooning qprime boundaries failed" psi_idx=i exception=err + @warn "ballooning qprime boundaries failed" psi_idx = i exception = err end end end @@ -1029,7 +1031,7 @@ function ballooning_delta_prime_map( delta_prime[k, :] .= vec(res.delta_prime) catch err if verbose - @warn "ballooning delta-prime map failed" psi_idx=i exception=err + @warn "ballooning delta-prime map failed" psi_idx = i exception = err end end end @@ -1076,7 +1078,7 @@ function ballooning_qprime_delta_prime_map( delta_prime[k, :] .= vec(res.delta_prime) catch err if verbose - @warn "ballooning qprime delta-prime map failed" psi_idx=i exception=err + @warn "ballooning qprime delta-prime map failed" psi_idx = i exception = err end end end diff --git a/src/PerturbedEquilibrium/PerturbedEquilibrium.jl b/src/PerturbedEquilibrium/PerturbedEquilibrium.jl index b930fabcfd..2beaa59163 100644 --- a/src/PerturbedEquilibrium/PerturbedEquilibrium.jl +++ b/src/PerturbedEquilibrium/PerturbedEquilibrium.jl @@ -58,8 +58,10 @@ surface inductance is free-space, and the response needs Λ from the same no-wal function plasma_response(ffs::ForceFreeStatesResult; compute_singular_coupling::Bool=true, compute_torque_response::Bool=false, verbose::Bool=false)::PlasmaResponse ffs.wall_settings === nothing || - error("plasma_response needs a force-free-states solve without a wall: its surface inductance L is free-space, " * - "so Λ from a wall run would mix two vacuum models. Solve with wall=nothing (no [Wall] section).") + error( + "plasma_response needs a force-free-states solve without a wall: its surface inductance L is free-space, " * + "so Λ from a wall run would mix two vacuum models. Solve with wall=nothing (no [Wall] section)." + ) # Galerkin and matched results publish no plasma edge response wp, so no operator could be built. if ffs.free_boundary === nothing && (ffs.integrator === :galerkin || ffs.closure === :matched) error("plasma_response needs the free-boundary δW, which a $(ffs.integrator) result with $(ffs.closure) closure " * diff --git a/src/PerturbedEquilibrium/ResonantCoupling.jl b/src/PerturbedEquilibrium/ResonantCoupling.jl index 9a786a7e03..4ba63adbeb 100644 --- a/src/PerturbedEquilibrium/ResonantCoupling.jl +++ b/src/PerturbedEquilibrium/ResonantCoupling.jl @@ -126,9 +126,13 @@ no basis, which is the bare-matrix construction. function check_mode_basis(dom::DominantCoupling, m_modes::AbstractVector{Int}, n_modes::AbstractVector{Int}, what::AbstractString) isempty(dom.m_modes) && return nothing (dom.m_modes == m_modes && dom.n_modes == n_modes) && return nothing - throw(ArgumentError("$what: the decomposition is on a different (m, n) basis than the spectrum — " * - "m $(extrema(dom.m_modes)) vs $(extrema(m_modes)), n $(extrema(dom.n_modes)) vs $(extrema(n_modes)). " * - "Decompose the same ResonantCoupling the spectrum was built from.")) + throw( + ArgumentError( + "$what: the decomposition is on a different (m, n) basis than the spectrum — " * + "m $(extrema(dom.m_modes)) vs $(extrema(m_modes)), n $(extrema(dom.n_modes)) vs $(extrema(n_modes)). " * + "Decompose the same ResonantCoupling the spectrum was built from." + ) + ) end """ diff --git a/src/PerturbedEquilibrium/Response.jl b/src/PerturbedEquilibrium/Response.jl index 539b9f57c9..1d3c3ce17a 100644 --- a/src/PerturbedEquilibrium/Response.jl +++ b/src/PerturbedEquilibrium/Response.jl @@ -92,7 +92,7 @@ function apply_response!(state::PerturbedEquilibriumState; verbose::Bool=false) py = zero(ComplexF64) state.toroidal_torque = 0.0 for (in, nn) in enumerate(ffs.nlow:ffs.nhigh) - blk = ((in - 1) * ffs.mpert + 1):(in * ffs.mpert) + blk = ((in-1)*ffs.mpert+1):(in*ffs.mpert) Φ_n = response_flux[blk] py_n = dot(Φ_n, inv(plasma_inductance[blk, blk]) * Φ_n) / 4 py += py_n diff --git a/src/PerturbedEquilibrium/SingularCoupling.jl b/src/PerturbedEquilibrium/SingularCoupling.jl index 084aa0e168..ac1f26afba 100644 --- a/src/PerturbedEquilibrium/SingularCoupling.jl +++ b/src/PerturbedEquilibrium/SingularCoupling.jl @@ -129,7 +129,7 @@ function _solution_at( # Same-side candidate nodes around the bracket, trimmed to the 4 nearest psi. side = sign(psi - psi_surf) - idxs = [j for j in max(1, il-3):min(nstep, ir+3) if sign(odet.psi_store[j] - psi_surf) == side] + idxs = [j for j in max(1, il - 3):min(nstep, ir + 3) if sign(odet.psi_store[j] - psi_surf) == side] while length(idxs) > 4 abs(odet.psi_store[idxs[1]] - psi) > abs(odet.psi_store[idxs[end]] - psi) ? popfirst!(idxs) : pop!(idxs) end @@ -284,7 +284,7 @@ function build_coupling_operators!(resp::PlasmaResponse; verbose::Bool=false) resp.C_penetrated_area_weighted_field = zeros(ComplexF64, n_rational, numpert_total) else resp.C_penetrated_area_weighted_field = zeros(ComplexF64, 0, 0) - @warn "No inner-layer B_pen supplied; penetrated field not computed." maxlog=1 + @warn "No inner-layer B_pen supplied; penetrated field not computed." maxlog = 1 end resp.C_delta_prime = zeros(ComplexF64, n_rational, numpert_total) resp.rational_psi = zeros(Float64, n_rational) diff --git a/src/Tearing/CriticalResonantField/TorqueBalance.jl b/src/Tearing/CriticalResonantField/TorqueBalance.jl index dee4a99682..3933652d84 100644 --- a/src/Tearing/CriticalResonantField/TorqueBalance.jl +++ b/src/Tearing/CriticalResonantField/TorqueBalance.jl @@ -107,7 +107,7 @@ function torque_balance_scan(tb::TorqueBalance; Qmin=nothing, Qmax=nothing, n::I # Follow the first resolved interior maximum without joining samples across excluded intervals. path = [i for i in sort(eachindex(Qs); by=i -> abs(Qs[i] - Qa)) - if min(Qa, Qb) < Qs[i] < max(Qa, Qb) && isfinite(bal[i]) && bal[i] > 0 && abs(Δs[i]) > tb.alpha] + if min(Qa, Qb) < Qs[i] < max(Qa, Qb) && isfinite(bal[i]) && bal[i] > 0 && abs(Δs[i]) > tb.alpha] idx = 0 valley = peak = previous = 0 for i in path diff --git a/src/Tearing/Dispersion/BruteForceScan.jl b/src/Tearing/Dispersion/BruteForceScan.jl index 4b84f9d92c..351037f8bc 100644 --- a/src/Tearing/Dispersion/BruteForceScan.jl +++ b/src/Tearing/Dispersion/BruteForceScan.jl @@ -5,12 +5,12 @@ Output of a brute-force or AMR Q-plane scan. -| field | meaning | -|------------|---------------------------------------------------| -| `Q` | Complex Q values (`Matrix` for grid, `Vector` for AMR) | -| `Δ` | Residual values, same shape as `Q` | -| `re_axis` | Real-axis grid (only for regular-grid `ScanResult`) | -| `im_axis` | Imaginary-axis grid (only for regular-grid `ScanResult`) | +| field | meaning | +|:--------- |:-------------------------------------------------------- | +| `Q` | Complex Q values (`Matrix` for grid, `Vector` for AMR) | +| `Δ` | Residual values, same shape as `Q` | +| `re_axis` | Real-axis grid (only for regular-grid `ScanResult`) | +| `im_axis` | Imaginary-axis grid (only for regular-grid `ScanResult`) | """ struct ScanResult Q::Matrix{ComplexF64} @@ -43,16 +43,16 @@ from the result. - `threaded` -- distribute Q evaluations across `Threads.@threads` """ function brute_force_scan(f, Q_re_range::NTuple{2,<:Real}, - Q_im_range::NTuple{2,<:Real}; - nre::Integer, nim::Integer, - threaded::Bool=true) + Q_im_range::NTuple{2,<:Real}; + nre::Integer, nim::Integer, + threaded::Bool=true) nre >= 2 || throw(ArgumentError("brute_force_scan: nre must be ≥ 2")) nim >= 2 || throw(ArgumentError("brute_force_scan: nim must be ≥ 2")) re_axis = collect(range(Float64(Q_re_range[1]); stop=Float64(Q_re_range[2]), - length=nre)) + length=nre)) im_axis = collect(range(Float64(Q_im_range[1]); stop=Float64(Q_im_range[2]), - length=nim)) - Q = ComplexF64[(qr + qi*im) for qr in re_axis, qi in im_axis] + length=nim)) + Q = ComplexF64[(qr + qi * im) for qr in re_axis, qi in im_axis] Δ = Matrix{ComplexF64}(undef, nre, nim) if threaded # Pin BLAS to one thread: concurrent multithreaded `det()` reductions are not reproducible diff --git a/src/Tearing/Dispersion/ContourSearchAMR.jl b/src/Tearing/Dispersion/ContourSearchAMR.jl index 40ba8a1fe3..bb74e4d751 100644 --- a/src/Tearing/Dispersion/ContourSearchAMR.jl +++ b/src/Tearing/Dispersion/ContourSearchAMR.jl @@ -28,11 +28,11 @@ end Output of `amr_scan`. -| field | meaning | -|:----------- |:-------------------------------------------------------- | -| `cells` | Final list of `AMRCell` after all refinement passes | -| `Q` | Flat `Vector{ComplexF64}` of every unique residual eval | -| `Δ` | Corresponding `Vector{ComplexF64}` of residual values | +| field | meaning | +|:----------- |:------------------------------------------------------------------------------------ | +| `cells` | Final list of `AMRCell` after all refinement passes | +| `Q` | Flat `Vector{ComplexF64}` of every unique residual eval | +| `Δ` | Corresponding `Vector{ComplexF64}` of residual values | | `truncated` | `true` if `max_cells` was hit under `:warn_truncate`; the scan is then not converged | """ struct AMRResult diff --git a/src/Tearing/GGJTearing.jl b/src/Tearing/GGJTearing.jl index c712c841d7..c6780aac95 100644 --- a/src/Tearing/GGJTearing.jl +++ b/src/Tearing/GGJTearing.jl @@ -88,7 +88,7 @@ function CommonSolve.solve(prob::TearingProblem, model::GGJModel; config::Tearin if coupled outside = [labels[k] for k in 1:count if !(config.omega_range[1] <= n * Ω_shift[k] <= config.omega_range[2])] isempty(outside) || @warn "GGJ tearing: the E×B frames of $(join(outside, ", ")) (ω = n·Ω_E) lie outside omega_range = " * - "$(config.omega_range); a root near those frequencies would be missed." + "$(config.omega_range); a root near those frequencies would be missed." end # Coupled: each layer sees the lab-frame growth rate in its own E×B frame, s_k = s + i·n·Ω_E,k, so differential diff --git a/src/Tearing/HDF5Output.jl b/src/Tearing/HDF5Output.jl index 7f7f8aa22e..dbf1ed5b9f 100644 --- a/src/Tearing/HDF5Output.jl +++ b/src/Tearing/HDF5Output.jl @@ -164,7 +164,8 @@ const TEARING_H5_ANNOTATIONS = [ (; long_name="critical normalized resonant field b_r/B_φ for error-field penetration (Cole-Fitzpatrick 2006 Eq. 62; NaN = no maximum or surface skipped)", units="1", dims=("surface",)), "CriticalResonantField/br_crit_tesla" => - (; long_name="critical resonant field br_crit·|B0| with the layer's B0, approximately comparable to SingularCoupling/resonant_area_weighted_field (NaN = no maximum or surface skipped)", + (; + long_name="critical resonant field br_crit·|B0| with the layer's B0, approximately comparable to SingularCoupling/resonant_area_weighted_field (NaN = no maximum or surface skipped)", units="T", dims=("surface",)), "CriticalResonantField/Q0" => (; long_name="normalized natural E×B rotation Q0 = τ_k·n·Ω_E of the m/n mode (NaN where Ω_E is unknown)", units="1", dims=("surface",)) diff --git a/src/Tearing/Result.jl b/src/Tearing/Result.jl index 97f91e7d71..b91f2bb08f 100644 --- a/src/Tearing/Result.jl +++ b/src/Tearing/Result.jl @@ -56,6 +56,7 @@ Output of the tearing solve: summary eigenvalues (ω, γ) and the full root-extr ## Fields - `enabled` -- `true` only when the analysis actually ran + - `problem` -- producing TearingProblem, or nothing for a low-level scan - `config` -- resolved scan configuration used by the model - `model` -- the inner-layer model solved; `nothing` when the analysis did not run diff --git a/src/Tearing/SLAYERTearing.jl b/src/Tearing/SLAYERTearing.jl index 8b522013c3..7913ac1cb7 100644 --- a/src/Tearing/SLAYERTearing.jl +++ b/src/Tearing/SLAYERTearing.jl @@ -175,10 +175,12 @@ function run_slayer_from_inputs(model::InnerLayer.InnerLayerModel, rational_q::Vector{Float64}=Float64[], omega_E::Vector{Float64}=Float64[]) validate(problem, config) - _is_ggj(model) && throw(ArgumentError( - "run_slayer_from_inputs: this runner scans SLAYER's tearing-only residual, which gives no GGJ growth rate. " * - "Use Dispersion.ggj_growth_rates on multi_surface_coupling_full, which matches both parity channels, and " * - "solve_inner for the GGJ layer's Δ.")) + _is_ggj(model) && throw( + ArgumentError( + "run_slayer_from_inputs: this runner scans SLAYER's tearing-only residual, which gives no GGJ growth rate. " * + "Use Dispersion.ggj_growth_rates on multi_surface_coupling_full, which matches both parity channels, and " * + "solve_inner for the GGJ layer's Δ.") + ) isempty(params) && return empty_tearing_result(problem, config, model) n = length(params) @@ -333,8 +335,12 @@ function run_critical_resonant_field(model::InnerLayer.InnerLayerModel, params:: length(omega_E) == nsurf || throw(ArgumentError("run_critical_resonant_field: omega_E has $(length(omega_E)) entries for $nsurf surfaces.")) nsurf > 0 && all(isnan, omega_E) && - throw(ArgumentError("run_critical_resonant_field: the critical resonant field needs the E×B rotation, but the " * - "kinetic file has no omega_E and no [SLAYER] omega_E_kHz entry covers the surfaces.")) + throw( + ArgumentError( + "run_critical_resonant_field: the critical resonant field needs the E×B rotation, but the " * + "kinetic file has no omega_E and no [SLAYER] omega_E_kHz entry covers the surfaces." + ) + ) Q_peak, br_crit = fill(NaN, nsurf), fill(NaN, nsurf) # Cole Sec. IV: ω0 is the mode frequency in the E×B frame; diamagnetic parts enter via Q_e, Q_i. Q0 = Float64[p.tauk * p.n * Ω for (p, Ω) in zip(params, omega_E)] @@ -348,4 +354,3 @@ function run_critical_resonant_field(model::InnerLayer.InnerLayerModel, params:: br_crit_tesla = br_crit .* Float64[abs(p.bt) for p in params] return CriticalResonantFieldResult(true, Int[p.ising for p in params], Q_peak, br_crit, br_crit_tesla, Q0, scan) end - diff --git a/src/Tearing/TearingProblem.jl b/src/Tearing/TearingProblem.jl index 323dcaada2..fb223e2741 100644 --- a/src/Tearing/TearingProblem.jl +++ b/src/Tearing/TearingProblem.jl @@ -29,7 +29,7 @@ Both models report lab-frame roots from each surface's E×B rotation. ffs::R eta::E = nothing rho::D = nothing - gamma::Float64 = 5/3 + gamma::Float64 = 5 / 3 coupling_mode::Symbol = :uncoupled msing_max::Int = 3 omega_E_kHz::Dict{String,Float64} = Dict{String,Float64}() @@ -83,7 +83,7 @@ end function CommonSolve.solve(prob::TearingProblem, model::SLAYERModel; config::TearingConfig=TearingConfig()) config.gamma_range === nothing && config.omega_range === nothing || throw(ArgumentError("SLAYER uses Q_re_range and Q_im_range.")) - prob.eta === nothing && prob.rho === nothing && prob.gamma == 5/3 || + prob.eta === nothing && prob.rho === nothing && prob.gamma == 5 / 3 || throw(ArgumentError("SLAYER derives its layer inputs from transport profiles; eta, rho, and gamma overrides are GGJ settings.")) ffs = prob.ffs equil = ffs.equil diff --git a/src/Utilities/FourierTransforms.jl b/src/Utilities/FourierTransforms.jl index e7a977cbbc..8ca61697d7 100644 --- a/src/Utilities/FourierTransforms.jl +++ b/src/Utilities/FourierTransforms.jl @@ -33,7 +33,7 @@ function compute_fourier_coefficients(mtheta::Int, m_modes::AbstractVector{<:Int @assert length(ζ) == mtheta "ζ must have length mtheta" - θ_grid = range(; start=0, length=mtheta, step=2π/mtheta) + θ_grid = range(; start=0, length=mtheta, step=2π / mtheta) arg = m_modes' .* θ_grid .- n .* ζ return transpose(exp.(-im .* arg)) end @@ -67,8 +67,8 @@ function compute_fourier_coefficients( ) @assert nzeta % nfp == 0 "nzeta ($nzeta) must be divisible by nfp ($nfp)" - θ_grid = range(; start=0, length=mtheta, step=2π/mtheta) - ζ_grid = range(; start=0, length=nzeta, step=2π/nzeta) + θ_grid = range(; start=0, length=mtheta, step=2π / mtheta) + ζ_grid = range(; start=0, length=nzeta, step=2π / nzeta) nzeta_out = nzeta ÷ nfp mpert = length(m_modes) diff --git a/src/Utilities/HDF5Annotations.jl b/src/Utilities/HDF5Annotations.jl index 44fa2f40a0..08a4bb15c4 100644 --- a/src/Utilities/HDF5Annotations.jl +++ b/src/Utilities/HDF5Annotations.jl @@ -7,8 +7,7 @@ Self-describing metadata for `gpec.h5` (the contract in datasets are marked as HDF5 Dimension Scales (netCDF-4 coordinate variables) attached to the arrays that share their axis, so h5py/xarray/HDFView read the file unaided. -Writers stay table-driven: each writer keeps a table of `path => (; long_name, units, -dims)` entries next to it and calls [`annotate!`](@ref) once after its datasets are +Writers stay table-driven: each writer keeps a table of `path => (; long_name, units, dims)` entries next to it and calls [`annotate!`](@ref) once after its datasets are written. Paths absent from the file are skipped silently (many writes are conditional). """ module HDF5Annotations diff --git a/src/Utilities/HDF5Schema.jl b/src/Utilities/HDF5Schema.jl index 2b322b7a7b..c5a6de57e4 100644 --- a/src/Utilities/HDF5Schema.jl +++ b/src/Utilities/HDF5Schema.jl @@ -152,9 +152,17 @@ const MAIN_H5_ANNOTATIONS = [ "ForceFreeStates/LayerOverlap/width_visco" => (; long_name="visco-resistive comparison scale as a normalized-flux width", dims=("surface_overlap",), attach=(1 => "ForceFreeStates/LayerOverlap/psi",)), "ForceFreeStates/LayerOverlap/width_dr" => - (; long_name="diffusive-resistive layer width (Fitzpatrick 2025 Eq. 100) as a normalized-flux width; the channel the criterion uses", dims=("surface_overlap",), attach=(1 => "ForceFreeStates/LayerOverlap/psi",)), + (; + long_name="diffusive-resistive layer width (Fitzpatrick 2025 Eq. 100) as a normalized-flux width; the channel the criterion uses", + dims=("surface_overlap",), + attach=(1 => "ForceFreeStates/LayerOverlap/psi",) + ), "ForceFreeStates/LayerOverlap/extrapolated" => - (; long_name="1 where the surface was located beyond the equilibrium grid via the separatrix edge q-law", dims=("surface_overlap",), attach=(1 => "ForceFreeStates/LayerOverlap/psi",)), + (; + long_name="1 where the surface was located beyond the equilibrium grid via the separatrix edge q-law", + dims=("surface_overlap",), + attach=(1 => "ForceFreeStates/LayerOverlap/psi",) + ), "ForceFreeStates/LayerOverlap/psilim_overlap" => (; long_name="domain limit implied by the first layer overlap, at the last surface with a well-separated inner region (NaN when no overlap was found in range)"), "ForceFreeStates/LayerOverlap/first_overlap_index" => diff --git a/src/Utilities/PhysicalConstants.jl b/src/Utilities/PhysicalConstants.jl index a9668b6116..6511e6df2e 100644 --- a/src/Utilities/PhysicalConstants.jl +++ b/src/Utilities/PhysicalConstants.jl @@ -10,11 +10,11 @@ All quantities in SI units. module PhysicalConstants # Match the Fortran GPEC/SLAYER sglobal_mod values exactly so cross-code numerical comparison is meaningful. -const MU_0 = 4.0e-7 * π # vacuum permeability [H/m] -const M_E = 9.1094e-31 # electron mass [kg] -const M_P = 1.6726e-27 # proton mass [kg] +const MU_0 = 4.0e-7 * π # vacuum permeability [H/m] +const M_E = 9.1094e-31 # electron mass [kg] +const M_P = 1.6726e-27 # proton mass [kg] const E_CHG = 1.6021917e-19 # elementary charge [C] -const K_B = 1.3807e-23 # Boltzmann constant [J/K] +const K_B = 1.3807e-23 # Boltzmann constant [J/K] const EPS_0 = 8.8542e-12 # vacuum permittivity [F/m] export MU_0, M_E, M_P, E_CHG, K_B, EPS_0 diff --git a/src/Vacuum/DataTypes.jl b/src/Vacuum/DataTypes.jl index f8d3c9e97e..a15f08e330 100644 --- a/src/Vacuum/DataTypes.jl +++ b/src/Vacuum/DataTypes.jl @@ -76,7 +76,7 @@ function VacuumInput( x=r[1:(end-1)], z=z[1:(end-1)], ν=ν[1:(end-1)], - mtheta_in=length(r)-1, + mtheta_in=length(r) - 1, m_modes=collect(Int, m_modes), n_modes=collect(Int, n_modes), mtheta=mtheta, @@ -375,8 +375,8 @@ function PlasmaGeometry(inputs::VacuumInput) @assert length(inputs.x) == length(inputs.z) == length(inputs.ν) "x, z, and ν must have the same length" # Interpolate arrays from input onto mtheta grid - θ_in = range(; start=0, length=inputs.mtheta_in, step=2π/inputs.mtheta_in) - θ_out = range(; start=0, length=inputs.mtheta, step=2π/inputs.mtheta) + θ_in = range(; start=0, length=inputs.mtheta_in, step=2π / inputs.mtheta_in) + θ_out = range(; start=0, length=inputs.mtheta, step=2π / inputs.mtheta) x = cubic_interp(θ_in, inputs.x, θ_out; bc=PeriodicBC(; endpoint=:exclusive)) z = cubic_interp(θ_in, inputs.z, θ_out; bc=PeriodicBC(; endpoint=:exclusive)) @@ -456,8 +456,8 @@ function PlasmaGeometry3D(inputs::VacuumInput) # Interpolate 3D inputs onto vacuum grid @assert !isempty(inputs.y) "y must be provided for 3D (nzeta_in > 1) inputs" @assert length(inputs.x) == length(inputs.y) == length(inputs.z) == inputs.mtheta_in * inputs.nzeta_in "x, y, z must have length mtheta_in * nzeta_in" - θ_in = range(; start=0, length=inputs.mtheta_in, step=2π/inputs.mtheta_in) - ζ_in = range(; start=0, length=inputs.nzeta_in, step=2π/inputs.nzeta_in) + θ_in = range(; start=0, length=inputs.mtheta_in, step=2π / inputs.mtheta_in) + ζ_in = range(; start=0, length=inputs.nzeta_in, step=2π / inputs.nzeta_in) θ_flat = repeat(collect(θ_grid), nzeta) ζ_flat = repeat(collect(ζ_grid); inner=mtheta) grid_points = (θ_flat, ζ_flat) @@ -573,7 +573,7 @@ function WallGeometry(inputs::VacuumInput, plasma_surf::PlasmaGeometry, wall_set zrad = 0.5 * (zmax - zmin) zh = sqrt(abs(zrad^2 - r_minor^2)) - zmuw = log((a/zh) + sqrt((a/zh)^2 + 1)) + zmuw = log((a / zh) + sqrt((a / zh)^2 + 1)) bw_eff = (zh * cosh(zmuw)) / a for i in 1:mtheta @@ -590,7 +590,7 @@ function WallGeometry(inputs::VacuumInput, plasma_surf::PlasmaGeometry, wall_set for i in 1:mtheta the = (i - 1) * (2π / mtheta) x_wall[i] = wcentr + r_minor * (1.0 + a - cw) * cos(the + dw * sin(the)) - z_wall[i] = bw * r_minor * (1.0 + a - cw) * sin(the + tw * sin(2.0*the)) + aw * r_minor * sin(2.0*the) + z_wall[i] = bw * r_minor * (1.0 + a - cw) * sin(the + tw * sin(2.0 * the)) + aw * r_minor * sin(2.0 * the) end elseif wall_settings isa ModDeeWall @@ -601,7 +601,7 @@ function WallGeometry(inputs::VacuumInput, plasma_surf::PlasmaGeometry, wall_set for i in 1:mtheta the = (i - 1) * (2π / mtheta) x_wall[i] = cw + a * cos(the + dw * sin(the)) - z_wall[i] = bw * a * sin(the + tw * sin(2.0*the)) + aw * sin(2.0*the) + z_wall[i] = bw * a * sin(the + tw * sin(2.0 * the)) + aw * sin(2.0 * the) end else diff --git a/src/Vacuum/Kernel2D.jl b/src/Vacuum/Kernel2D.jl index 0fadc644a0..9a2d36004f 100644 --- a/src/Vacuum/Kernel2D.jl +++ b/src/Vacuum/Kernel2D.jl @@ -112,9 +112,9 @@ grad_greenfunction is not zeroed since it fills a different block of the populate_greenfunction && fill!(greenfunction, 0.0) # S₁ᵢ logarithmic correction factors [Chance Phys. Plasmas 1997 2161 eq. 78] - log_correction_0=16.0*dtheta*(log(2*dtheta)-68.0/15.0)/15.0 - log_correction_1=128.0*dtheta*(log(2*dtheta)-8.0/15.0)/45.0 - log_correction_2=4.0*dtheta*(7.0*log(2*dtheta)-11.0/15.0)/45.0 + log_correction_0 = 16.0 * dtheta * (log(2 * dtheta) - 68.0 / 15.0) / 15.0 + log_correction_1 = 128.0 * dtheta * (log(2 * dtheta) - 8.0 / 15.0) / 45.0 + log_correction_2 = 4.0 * dtheta * (7.0 * log(2 * dtheta) - 11.0 / 15.0) / 45.0 log_correction_array = SVector(log_correction_2, log_correction_1, log_correction_0, log_correction_1, log_correction_2) # Precompute the n-dependent prefactor 2√π·Γ(1/2-n) [Chance Phys. Plasmas 1997 2161 eq. 40] @@ -272,14 +272,14 @@ function elliptic_integral_e(m1) (m1 < 0.0 || m1 > 1.0) && throw(DomainError(m1, "Input `x1` must be in the range (0, 1].")) log_x1 = log(m1) - ae1=0.44325141463 - ae2=0.0626060122 - ae3=0.04757383546 - ae4=0.01736506451 - be1=0.2499836831 - be2=0.09200180037 - be3=0.04069697526 - be4=0.00526449639 + ae1 = 0.44325141463 + ae2 = 0.0626060122 + ae3 = 0.04757383546 + ae4 = 0.01736506451 + be1 = 0.2499836831 + be2 = 0.09200180037 + be3 = 0.04069697526 + be4 = 0.00526449639 p = @evalpoly(m1, 1.0, ae1, ae2, ae3, ae4) q = @evalpoly(m1, 0.0, be1, be2, be3, be4) @@ -300,7 +300,7 @@ end # The paper's eq. 50 has a typo: the exponent is -1/4, not +1/2 function P0_plus_half(s) m1 = (s + sqrt(s^2 - 1))^(-2) - return 2 / π * m1^(-1/4) * elliptic_integral_e(m1) # This is correct + return 2 / π * m1^(-1 / 4) * elliptic_integral_e(m1) # This is correct end @@ -450,8 +450,8 @@ function elliptic_integrals_bulirsch(m1::Float64; error::Float64=1e-8, maxit::In convergence = max(convergence, 1.0e-100) # Calculate final K and E values - K = π/2 * (bval1 + aval1 * emval) / (emval * (emval + pval)) - E = π/2 * (bval2 + aval2 * emval) / (emval * (emval + pval)) + K = π / 2 * (bval1 + aval1 * emval) / (emval * (emval + pval)) + E = π / 2 * (bval2 + aval2 * emval) / (emval * (emval + pval)) return K, E, convergence, kounter end diff --git a/src/Vacuum/Kernel3D.jl b/src/Vacuum/Kernel3D.jl index 2d9bf534d1..c668dbf21b 100644 --- a/src/Vacuum/Kernel3D.jl +++ b/src/Vacuum/Kernel3D.jl @@ -194,12 +194,12 @@ Fused Laplace kernels: `single = 1/r`, `double = (Δx·n)/r³`. Shares `√(r²) dx = ox - sx dy = oy - sy dz = oz - sz - r2 = dx*dx + dy*dy + dz*dz + r2 = dx * dx + dy * dy + dz * dz r2 < 1e-30 && return (0.0, 0.0) rinv = inv(sqrt(r2)) single = rinv r3inv = rinv * rinv * rinv - double = (dx*nx + dy*ny + dz*nz) * r3inv + double = (dx * nx + dy * ny + dz * nz) * r3inv return (single, double) end diff --git a/src/Vacuum/Utilities.jl b/src/Vacuum/Utilities.jl index 22ce9625a7..906b9819cf 100644 --- a/src/Vacuum/Utilities.jl +++ b/src/Vacuum/Utilities.jl @@ -74,7 +74,7 @@ function distribute_to_equal_arc_grid(xin::Vector{Float64}, zin::Vector{Float64} end # Interpolate the original (x, z) at equal arc-length targets - arc_length_targets = range(; start=0, length=mtheta, step=arc_length[end]/mtheta) + arc_length_targets = range(; start=0, length=mtheta, step=arc_length[end] / mtheta) # arc_length is an uneven grid so we have to specify the period explicitly periodic_bc = PeriodicBC(; endpoint=:exclusive, period=arc_length[end]) xout = cubic_interp(arc_length[1:(end-1)], xin, arc_length_targets; bc=periodic_bc) @@ -108,8 +108,8 @@ Write the cross product of rows `idx` of the `(N, 3)` matrices `a` and `b` into b2 = b[idx, 2] b3 = b[idx, 3] - c[idx, 1] = a2*b3 - a3*b2 - c[idx, 2] = a3*b1 - a1*b3 - c[idx, 3] = a1*b2 - a2*b1 + c[idx, 1] = a2 * b3 - a3 * b2 + c[idx, 2] = a3 * b1 - a1 * b3 + c[idx, 3] = a1 * b2 - a2 * b1 end end diff --git a/test/runtests.jl b/test/runtests.jl index bc7263af4d..95346ac03b 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -22,7 +22,7 @@ using GeneralizedPerturbedEquilibrium function run_test_file(testfile) @info "Running test file" testfile measurement = @timed include(testfile) - @info "Completed test file" testfile elapsed_seconds=measurement.time compile_seconds=measurement.compile_time recompile_seconds=measurement.recompile_time + @info "Completed test file" testfile elapsed_seconds = measurement.time compile_seconds = measurement.compile_time recompile_seconds = measurement.recompile_time return nothing end diff --git a/test/runtests_analysis_plots.jl b/test/runtests_analysis_plots.jl index d17239ec65..4739fe5ba5 100644 --- a/test/runtests_analysis_plots.jl +++ b/test/runtests_analysis_plots.jl @@ -28,11 +28,16 @@ using TOML @testset "ideal run with coil forcing and the perturbed-equilibrium response" begin mktempdir() do dir - h5 = _run_fixture(dir, "regression_tj_ideal_example"; ffs=Dict{String,Any}("dW_edge_scan_start" => 0.95), sections=Dict{String,Any}( - "ForcingTerms" => Dict{String,Any}("forcing_data_format" => "coil", "mtheta_coil" => 240, "nzeta_coil" => 32, - "coil_set" => [Dict{String,Any}("name" => "hoop", "source" => "pf_hoop", "radius" => 12.0, "height" => 1.2, - "currents" => [2.0e3], "tiltx" => [3.0])]), - "PerturbedEquilibrium" => Dict{String,Any}("compute_singular_coupling" => true))) + h5 = _run_fixture( + dir, + "regression_tj_ideal_example"; + ffs=Dict{String,Any}("dW_edge_scan_start" => 0.95), + sections=Dict{String,Any}( + "ForcingTerms" => Dict{String,Any}("forcing_data_format" => "coil", "mtheta_coil" => 240, "nzeta_coil" => 32, + "coil_set" => [Dict{String,Any}("name" => "hoop", "source" => "pf_hoop", "radius" => 12.0, "height" => 1.2, + "currents" => [2.0e3], "tiltx" => [3.0])]), + "PerturbedEquilibrium" => Dict{String,Any}("compute_singular_coupling" => true)) + ) for f in (A.Equilibrium.plot_qprofile, A.Equilibrium.plot_pressure_profile, A.Equilibrium.plot_f_profile, A.Equilibrium.plot_flux_surfaces, A.Equilibrium.plot_equilibrium_summary) diff --git a/test/runtests_coils.jl b/test/runtests_coils.jl index 7a22105bdd..2a12db7f8b 100644 --- a/test/runtests_coils.jl +++ b/test/runtests_coils.jl @@ -739,7 +739,7 @@ end open(flipped, "w") do io for (i, l) in enumerate(lines) if i == 4 # header line 4 starts with the plasma current - vals = [l[j:min(j+15, end)] for j in 1:16:length(l)] + vals = [l[j:min(j + 15, end)] for j in 1:16:length(l)] cur = -parse(Float64, strip(vals[1])) l = @sprintf("%16.9E", cur) * l[17:end] end diff --git a/test/runtests_coordinate_invariant.jl b/test/runtests_coordinate_invariant.jl index 2a98861ee2..c4c9991def 100644 --- a/test/runtests_coordinate_invariant.jl +++ b/test/runtests_coordinate_invariant.jl @@ -140,10 +140,10 @@ end @testset "power_norm_matrix! is the ∮J|ξ|² quadratic form" begin FFS = GeneralizedPerturbedEquilibrium.ForceFreeStates mpert, mlow, ntheta, dV_dpsi = 5, -2, 64, 1.7 - θ = range(; start=0, length=ntheta, step=2π/ntheta) + θ = range(; start=0, length=ntheta, step=2π / ntheta) J = 1 .+ 0.3 .* cos.(θ) .+ 0.2 .* sin.(θ) .+ 0.1 .* sin.(2θ) # Fourfit storage: jmat[mpert − d] = J_d = ⟨J e^{−idθ}⟩ - jmat = ComplexF64[sum(J .* cis.(-d .* θ)) / ntheta for d in (mpert-1):-1:(-(mpert-1))] + jmat = ComplexF64[sum(J .* cis.(-d .* θ)) / ntheta for d in (mpert-1):-1:(-(mpert - 1))] m_modes = mlow:(mlow+mpert-1) ξ = ComplexF64[cis(0.7m) * (1 + 0.2m) for m in m_modes] ξθ = [sum(ξ[l] * cis(m * t) for (l, m) in enumerate(m_modes)) for t in θ] diff --git a/test/runtests_core_capped_knots.jl b/test/runtests_core_capped_knots.jl index 84ad3f2405..8ff0c584b5 100644 --- a/test/runtests_core_capped_knots.jl +++ b/test/runtests_core_capped_knots.jl @@ -15,7 +15,7 @@ using GeneralizedPerturbedEquilibrium # Nothing at or above ψ = 0.1 is dropped. @test count(>=(0.1), kept) == count(>=(0.1), xs) # Below it, kept knots are at least 5% of ψ apart. - core = kept[kept .< 0.1] + core = kept[kept.<0.1] @test all(diff(core) .>= 0.05 .* core[2:end]) # A core rational ends the capped region before its resolution window. diff --git a/test/runtests_critical_resonant_field.jl b/test/runtests_critical_resonant_field.jl index c95a3098ea..b1c6ef313f 100644 --- a/test/runtests_critical_resonant_field.jl +++ b/test/runtests_critical_resonant_field.jl @@ -191,8 +191,9 @@ InnerLayer.layer_response(::_VRiStub, p, Q::Number) = (; tearing=_vri(-real(Q), @test_throws "needs the E×B rotation" run_slayer_from_inputs(model, params, dp, c, scan; rational_psi=[0.5, 0.7], rational_q=[2.0, 1.5]) # A surface the override leaves without a rotation is skipped; the others are still computed. c_part = TearingProblem(nothing; critical_resonant_field=ctrl, omega_E_kHz=Dict("2/1" => ω_E / (2π * 1e3))) - r_part = (@test_logs (:warn, r"2/2 surface skipped") match_mode = :any run_slayer_from_inputs(model, params, dp, c_part, scan; - rational_psi=[0.5, 0.7], rational_q=[2.0, 1.5])).critical_resonant_field + r_part = + (@test_logs (:warn, r"2/2 surface skipped") match_mode = :any run_slayer_from_inputs(model, params, dp, c_part, scan; + rational_psi=[0.5, 0.7], rational_q=[2.0, 1.5])).critical_resonant_field @test r_part.Q0[1] ≈ r.Q0[1] && isnan(r_part.Q0[2]) @test isequal(r_part.br_crit[1], r.br_crit[1]) && isnan(r_part.br_crit[2]) end diff --git a/test/runtests_dispersion_amr.jl b/test/runtests_dispersion_amr.jl index a29fb8f8cf..8b528f7aad 100644 --- a/test/runtests_dispersion_amr.jl +++ b/test/runtests_dispersion_amr.jl @@ -176,7 +176,8 @@ # ========================================================================= # multi_box_amr_scan # ========================================================================= - using GeneralizedPerturbedEquilibrium.Tearing.Dispersion: BoxActivity, NoActivity, + using GeneralizedPerturbedEquilibrium.Tearing.Dispersion: + BoxActivity, NoActivity, ReZeroCrossing, ImZeroCrossing, PoleMagnitude, MultiBoxAMRResult, multi_box_amr_scan, as_amr_result diff --git a/test/runtests_dispersion_ggj.jl b/test/runtests_dispersion_ggj.jl index 77347cf167..e83af9ae70 100644 --- a/test/runtests_dispersion_ggj.jl +++ b/test/runtests_dispersion_ggj.jl @@ -36,7 +36,7 @@ GeneralizedPerturbedEquilibrium.Tearing.Dispersion._residual_domain_radius(f::GG D = GeneralizedPerturbedEquilibrium.Tearing.Dispersion g = GGJDispersion(mc) center = 0.01im - re_paths = [ComplexF64[-1 + center, 1 + center]] + re_paths = [ComplexF64[-1+center, 1+center]] im_paths = [ComplexF64[0.001im, 0.02im]] # A coarse contour estimate must not pull a stable root into a positive-growth search. f = GGJDomainTestResidual(g, Q -> Q + 0.1im, ComplexF64[]) diff --git a/test/runtests_dispersion_polish.jl b/test/runtests_dispersion_polish.jl index cbf8b81d60..c5963fb522 100644 --- a/test/runtests_dispersion_polish.jl +++ b/test/runtests_dispersion_polish.jl @@ -142,7 +142,7 @@ r1 = 0.30 + 0.20im r2 = r1 + 0.002 # second root 0.002 away f(Q) = (ComplexF64(Q) - r1) * (ComplexF64(Q) - r2) - pts = ComplexF64[r1 + 0.0003, r2 - 0.0003] # coarse contour estimates + pts = ComplexF64[r1+0.0003, r2-0.0003] # coarse contour estimates R1 = D._polish_trust_radius(pts, 1, 0.001) R2 = D._polish_trust_radius(pts, 2, 0.001) p1 = D._polish_root(f, pts[1], R1)[1] @@ -160,7 +160,7 @@ im_paths = [ComplexF64[0+0im, 0+0.1im]] # segment length 0.1 @test D._median_segment_length(re_paths, im_paths) ≈ 0.1 # median of [0.1,0.2,0.1] @test D._median_segment_length(Vector{Vector{ComplexF64}}(), - Vector{Vector{ComplexF64}}()) == 0.0 + Vector{Vector{ComplexF64}}()) == 0.0 end # ---------------------------------------------------------------- diff --git a/test/runtests_dispersion_residual.jl b/test/runtests_dispersion_residual.jl index 92586f9692..32cb81d0a8 100644 --- a/test/runtests_dispersion_residual.jl +++ b/test/runtests_dispersion_residual.jl @@ -20,50 +20,50 @@ InnerLayerResponse(m.a + m.b * ComplexF64(Q), zero(ComplexF64)) function _slayer_ref() - return slayer_parameters( + return slayer_parameters(; n_e=5.0e19, t_e=1000.0, t_i=1000.0, omega_e=-1.0e4, omega_i=5.0e3, qval=2.0, sval_r=1.0, bt=2.0, rs=0.5, R0=1.7, mu_i=2.0, zeff=1.0, - chi_perp_e=1.0, chi_tor=1.0, m=2, n=1; P_perp_model=:chi_perp_e) + chi_perp_e=1.0, chi_tor=1.0, m=2, n=1, P_perp_model=:chi_perp_e) end @testset "Constructor scale defaults" begin # SLAYER: scale = lu^(1/3) so the dimensionless Δ from riccati_f # is mapped to outer ψ-units (Fortran SLAYER growthrates routine) - p_sl = _slayer_ref() + p_sl = _slayer_ref() sc_sl = surface_coupling(SLAYERModel(), p_sl, -1.0 + 0.0im) - @test sc_sl.scale ≈ p_sl.lu^(1/3) + @test sc_sl.scale ≈ p_sl.lu^(1 / 3) @test sc_sl.dc == 0.0 @test sc_sl.dp_diag == ComplexF64(-1.0) # GGJ: scale = 1 because rescale_delta is applied inside solve_inner - p_ggj = glasser_wang_2020_eq55() - sc_ggj = surface_coupling(GGJModel(solver=:shooting), p_ggj, - -1.0 + 0.0im) + p_ggj = glasser_wang_2020_eq55() + sc_ggj = surface_coupling(GGJModel(; solver=:shooting), p_ggj, + -1.0 + 0.0im) @test sc_ggj.scale == 1.0 # Generic fallback honors explicit scale + dc kwargs - sc_lin = surface_coupling(LinearTestModel(0.0im, 1.0+0im), nothing, - 3.0 + 0.0im; dc=0.5, scale=2.0) + sc_lin = surface_coupling(LinearTestModel(0.0im, 1.0 + 0im), nothing, + 3.0 + 0.0im; dc=0.5, scale=2.0) @test sc_lin.scale == 2.0 @test sc_lin.dc == 0.5 end @testset "Residual arithmetic on synthetic linear model" begin # r(Q) = dp_diag - scale·(a + b·Q) - dc - a, b = 1.0 + 2.0im, -0.5 + 1.0im - scale = 3.0 - dc = 0.25 + a, b = 1.0 + 2.0im, -0.5 + 1.0im + scale = 3.0 + dc = 0.25 Q_root = -0.7 + 0.3im dp_diag = (a + b * Q_root) * scale + dc # construct a known root sc = surface_coupling(LinearTestModel(a, b), nothing, dp_diag; - dc=dc, scale=scale) + dc=dc, scale=scale) @test sc(Q_root) ≈ 0 atol = 1e-12 # Off-root residual matches the closed form - for Q in (0.0+0im, 1.5-0.5im, -0.2+1.2im) + for Q in (0.0 + 0im, 1.5 - 0.5im, -0.2 + 1.2im) expected = dp_diag - scale * (a + b * Q) - dc @test sc(Q) ≈ expected end @@ -76,7 +76,7 @@ m = SLAYERModel() Q_pin = 0.3 + 0.4im Δ_pin = layer_response(m, p, Q_pin).tearing - dp_diag = p.lu^(1/3) * Δ_pin + dp_diag = p.lu^(1 / 3) * Δ_pin sc = surface_coupling(m, p, dp_diag) @test abs(sc(Q_pin)) < 1e-13 # self-consistent @@ -90,14 +90,14 @@ # Both inner-layer models flow through the same SurfaceCoupling # API. Numerical agreement is *not* asserted (different physics) — # only that both pipelines construct and evaluate. - p_sl = _slayer_ref() + p_sl = _slayer_ref() sc_sl = surface_coupling(SLAYERModel(), p_sl, -100.0 + 0.0im) @test sc_sl isa SurfaceCoupling{SLAYERModel{:fitzpatrick},SLAYERParameters} @test sc_sl(0.0 + 0.5im) isa ComplexF64 - p_ggj = glasser_wang_2020_eq55() - sc_ggj = surface_coupling(GGJModel(solver=:shooting), p_ggj, - -1.0 + 0.0im) + p_ggj = glasser_wang_2020_eq55() + sc_ggj = surface_coupling(GGJModel(; solver=:shooting), p_ggj, + -1.0 + 0.0im) @test sc_ggj isa SurfaceCoupling{<:GGJModel{:shooting},GGJParameters} @test sc_ggj(1e-3 + 0.0im) isa ComplexF64 end @@ -105,10 +105,10 @@ @testset "Residual is callable on grids (broadcast)" begin # Brute-force / AMR scans (PR 5/6) will broadcast `sc` over a 2D # complex-Q grid; verify that broadcasting works element-wise. - a, b = 0.0+0im, 1.0+0im - sc = surface_coupling(LinearTestModel(a, b), nothing, 2.0+0im; - dc=0.0, scale=1.0) - Q_grid = [(qr + qi*im) for qr in -1.0:0.5:1.0, qi in -1.0:0.5:1.0] + a, b = 0.0 + 0im, 1.0 + 0im + sc = surface_coupling(LinearTestModel(a, b), nothing, 2.0 + 0im; + dc=0.0, scale=1.0) + Q_grid = [(qr + qi * im) for qr in -1.0:0.5:1.0, qi in -1.0:0.5:1.0] Δ_grid = sc.(Q_grid) @test size(Δ_grid) == size(Q_grid) @test all(d -> d isa ComplexF64, Δ_grid) diff --git a/test/runtests_dispersion_scan.jl b/test/runtests_dispersion_scan.jl index 92dc380ec9..7635ceb190 100644 --- a/test/runtests_dispersion_scan.jl +++ b/test/runtests_dispersion_scan.jl @@ -7,36 +7,36 @@ @testset "brute_force_scan: regular grid evaluation" begin f(Q) = ComplexF64(Q)^2 - 1 scan = brute_force_scan(f, (-2.0, 2.0), (-1.0, 1.0); - nre=21, nim=11, threaded=false) + nre=21, nim=11, threaded=false) @test scan isa ScanResult @test size(scan.Q) == (21, 11) @test size(scan.Δ) == (21, 11) @test length(scan.re_axis) == 21 @test length(scan.im_axis) == 11 - @test scan.re_axis[1] == -2.0 - @test scan.re_axis[end] == 2.0 - @test scan.im_axis[1] == -1.0 - @test scan.im_axis[end] == 1.0 + @test scan.re_axis[1] == -2.0 + @test scan.re_axis[end] == 2.0 + @test scan.im_axis[1] == -1.0 + @test scan.im_axis[end] == 1.0 # Spot-check a grid value i, j = 11, 6 - @test scan.Q[i, j] ≈ scan.re_axis[i] + scan.im_axis[j]*im + @test scan.Q[i, j] ≈ scan.re_axis[i] + scan.im_axis[j] * im @test scan.Δ[i, j] ≈ scan.Q[i, j]^2 - 1 end @testset "brute_force_scan: threaded vs non-threaded agree" begin f(Q) = sin(ComplexF64(Q)) s_t = brute_force_scan(f, (-1.0, 1.0), (-0.5, 0.5); - nre=15, nim=10, threaded=true) + nre=15, nim=10, threaded=true) s_n = brute_force_scan(f, (-1.0, 1.0), (-0.5, 0.5); - nre=15, nim=10, threaded=false) + nre=15, nim=10, threaded=false) @test s_t.Δ == s_n.Δ end @testset "brute_force_scan: argument validation" begin @test_throws ArgumentError brute_force_scan(identity, (0.0, 1.0), - (0.0, 1.0); nre=1, nim=10) + (0.0, 1.0); nre=1, nim=10) @test_throws ArgumentError brute_force_scan(identity, (0.0, 1.0), - (0.0, 1.0); nre=10, nim=1) + (0.0, 1.0); nre=10, nim=1) end @testset "find_growth_rates: single isolated root" begin @@ -44,7 +44,7 @@ Q_root = 0.42 + 0.27im f(Q) = ComplexF64(Q) - Q_root scan = brute_force_scan(f, (-1.0, 1.5), (-0.5, 1.0); - nre=80, nim=60, threaded=false) + nre=80, nim=60, threaded=false) result = find_growth_rates(scan, 1.0) @test result isa GrowthRateResult @test isempty(result.poles) @@ -60,7 +60,7 @@ Q2 = -0.4 + 0.1im # lower γ f(Q) = (ComplexF64(Q) - Q1) * (ComplexF64(Q) - Q2) scan = brute_force_scan(f, (-1.0, 1.0), (-0.3, 0.8); - nre=100, nim=80, threaded=false) + nre=100, nim=80, threaded=false) result = find_growth_rates(scan, 1.0) @test length(result.valid_roots) == 2 @test abs(result.Q_root - Q1) < 1e-3 # higher-γ root chosen @@ -73,7 +73,7 @@ Q_p = -0.5 + 0.6im # pole at higher γ f(Q) = (ComplexF64(Q) - Q_r) / (ComplexF64(Q) - Q_p) scan = brute_force_scan(f, (-1.5, 1.5), (-0.5, 1.5); - nre=120, nim=100, threaded=false) + nre=120, nim=100, threaded=false) result = find_growth_rates(scan, 1.0; pole_threshold=10.0) # Pole correctly classified — but the root is at lower γ than the # pole, so even with filter_above_poles=true the root must survive. @@ -86,14 +86,14 @@ Q_root = 1.0 + 2.0im f(Q) = ComplexF64(Q) - Q_root scan = brute_force_scan(f, (-2.0, 3.0), (-1.0, 4.0); - nre=80, nim=80, threaded=false) + nre=80, nim=80, threaded=false) tauk = 5.0e-5 result = find_growth_rates(scan, tauk) @test result.omega ≈ real(result.Q_root) / tauk @test result.gamma ≈ imag(result.Q_root) / tauk # Check sensible orders of magnitude (Q_root ≈ 1+2im, tauk ≈ 5e-5) - @test result.omega ≈ 1 / tauk atol = 1 / tauk * 5e-3 - @test result.gamma ≈ 2 / tauk atol = 2 / tauk * 5e-3 + @test result.omega ≈ 1 / tauk atol = 1 / tauk * 5e-3 + @test result.gamma ≈ 2 / tauk atol = 2 / tauk * 5e-3 end @testset "find_growth_rates: empty result when no contour intersections" begin @@ -102,7 +102,7 @@ f(Q) = 1.0 + ComplexF64(Q) # Choose a box where Δ has no zeros — far above the real axis scan = brute_force_scan(f, (1.0, 2.0), (1.0, 2.0); - nre=30, nim=30, threaded=false) + nre=30, nim=30, threaded=false) result = find_growth_rates(scan, 1.0) # Either no valid roots, or a NaN Q_root @test isempty(result.valid_roots) || isnan(real(result.Q_root)) @@ -121,10 +121,10 @@ # Single-surface scan via SurfaceCoupling (Q_root by construction = 0.7-0.3im) Q_pin = 0.7 - 0.3im - sc = surface_coupling(LinModel(0.0im, 1.0+0im), nothing, - Q_pin; scale=1.0, tauk=1.0) + sc = surface_coupling(LinModel(0.0im, 1.0 + 0im), nothing, + Q_pin; scale=1.0, tauk=1.0) scan = brute_force_scan(sc, (-0.5, 1.5), (-1.0, 0.5); - nre=80, nim=80, threaded=false) + nre=80, nim=80, threaded=false) res = find_growth_rates(scan, sc.tauk) @test abs(res.Q_root - Q_pin) < 1e-3 @@ -135,14 +135,14 @@ # so to put a root at Q = Q_pin_k we need dp[k,k] = Q_pin_k (the # full complex value, not just its real part). Q_a, Q_b = 0.7 - 0.3im, -0.4 + 0.5im - sc1 = surface_coupling(LinModel(0.0im, 1.0+0im), nothing, - ComplexF64(0); scale=1.0, tauk=1.0) - sc2 = surface_coupling(LinModel(0.0im, 1.0+0im), nothing, - ComplexF64(0); scale=1.0, tauk=1.0) + sc1 = surface_coupling(LinModel(0.0im, 1.0 + 0im), nothing, + ComplexF64(0); scale=1.0, tauk=1.0) + sc2 = surface_coupling(LinModel(0.0im, 1.0 + 0im), nothing, + ComplexF64(0); scale=1.0, tauk=1.0) dp = ComplexF64[Q_a 0.0; 0.0 Q_b] # diagonal Δ' mc = multi_surface_coupling([sc1, sc2], dp) scan_c = brute_force_scan(mc, (-1.0, 1.5), (-1.0, 1.0); - nre=120, nim=100, threaded=false) + nre=120, nim=100, threaded=false) res_c = find_growth_rates(scan_c, mc.surfaces[mc.ref_idx].tauk) # With diagonal Δ', det = (Q_a - Q)·(Q_b - Q) → roots at Q_a, Q_b. # The higher-γ root is Q_b (γ = 0.5). diff --git a/test/runtests_equil.jl b/test/runtests_equil.jl index d3e7de69ad..df5706f0d6 100644 --- a/test/runtests_equil.jl +++ b/test/runtests_equil.jl @@ -230,8 +230,12 @@ using TOML @test_throws r"set kinetic_file in \[Equilibrium\]" GPEC.parse_deck(with(d -> d["ForceFreeStates"]["kinetic"] = "KineticForces"), template) # Values take their field types: an array becomes a range or a tuple, a string a Symbol. @test GPEC.parse_deck(with(d -> d["ForceFreeStates"]["nn"] = [1, 2]), template).force_balance.keywords[:nn] == 1:2 - tearing = GPEC.tearing_from_deck(Dict{String,Any}("Tearing" => Dict{String,Any}("inner_model" => "SLAYER", "coupling_mode" => "coupled", - "search" => Dict{String,Any}("Q_re_range" => [-2, 2]), "CriticalResonantField" => Dict{String,Any}("n" => 500)))) + tearing = GPEC.tearing_from_deck( + Dict{String,Any}( + "Tearing" => Dict{String,Any}("inner_model" => "SLAYER", "coupling_mode" => "coupled", + "search" => Dict{String,Any}("Q_re_range" => [-2, 2]), "CriticalResonantField" => Dict{String,Any}("n" => 500)) + ) + ) @test tearing.problem[:coupling_mode] === :coupled @test tearing.config.Q_re_range === (-2.0, 2.0) @test tearing.problem[:critical_resonant_field].n == 500 diff --git a/test/runtests_fouriertransforms.jl b/test/runtests_fouriertransforms.jl index b1013d9b5c..2eb09fd641 100644 --- a/test/runtests_fouriertransforms.jl +++ b/test/runtests_fouriertransforms.jl @@ -16,7 +16,7 @@ const ATOL_TIGHT = 1e-12 # in-place vs allocating / formula-vs-formula checks @testset "single real mode → ±m amplitudes" begin N, mlow, mpert = 64, -10, 21 # modes -10..10 ft = FourierTransform(N, mpert, mlow) - θ = range(; start=0, length=N, step=2π/N) + θ = range(; start=0, length=N, step=2π / N) # cos(m0 θ): real 0.5 at m = ±m0, zero elsewhere; imag ≈ 0 everywhere. m0 = 3 @@ -36,7 +36,7 @@ const ATOL_TIGHT = 1e-12 # in-place vs allocating / formula-vs-formula checks @testset "single complex mode → Kronecker delta" begin N, mlow, mpert = 64, -10, 21 ft = FourierTransform(N, mpert, mlow) - θ = collect(range(; start=0, length=N, step=2π/N)) + θ = collect(range(; start=0, length=N, step=2π / N)) for m0 in (3, -2, 0) modes = ft(exp.(im .* m0 .* θ)) @@ -65,7 +65,7 @@ const ATOL_TIGHT = 1e-12 # in-place vs allocating / formula-vs-formula checks @testset "matrix input transforms columns independently" begin N, mlow, mpert = 48, -8, 17 # modes -8..8 ft = FourierTransform(N, mpert, mlow) - θ = collect(range(; start=0, length=N, step=2π/N)) + θ = collect(range(; start=0, length=N, step=2π / N)) data = hcat(cos.(3 .* θ), sin.(5 .* θ)) modes = ft(data) @@ -88,7 +88,7 @@ end basis = compute_fourier_coefficients(N, m_modes, n, ζ) @test size(basis) == (mpert, N) - θ = collect(range(; start=0, length=N, step=2π/N)) + θ = collect(range(; start=0, length=N, step=2π / N)) for (l, m) in enumerate(m_modes) expected = exp.(-im .* (m .* θ .- n .* ζ)) @test all(isapprox.(basis[l, :], expected; atol)) diff --git a/test/runtests_galerkin_cholesky.jl b/test/runtests_galerkin_cholesky.jl index 3b0130b84c..11a38764f9 100644 --- a/test/runtests_galerkin_cholesky.jl +++ b/test/runtests_galerkin_cholesky.jl @@ -8,7 +8,7 @@ using LinearAlgebra @test FFS.empty_galerkin_result().solver == "none" n, kl, fixed = 24, 3, [1, 2, 24] H = zeros(ComplexF64, n, n) - for j in 1:n, i in max(1, j-kl):min(n, j+kl) + for j in 1:n, i in max(1, j - kl):min(n, j + kl) H[i, j] = i == j ? 4 + sin(j) : 0.3 * cis(0.3i + 0.7j) end H = (H + H') / 2 # diagonally dominant → positive definite @@ -17,7 +17,7 @@ using LinearAlgebra A[fixed, :] .= 0 A[fixed, fixed] = I(length(fixed)) ab = zeros(ComplexF64, 3kl + 1, n) - for j in 1:n, i in max(1, j-kl):min(n, j+kl) + for j in 1:n, i in max(1, j - kl):min(n, j + kl) ab[2kl+1+i-j, j] = A[i, j] end return A, ab @@ -91,7 +91,7 @@ using LinearAlgebra kl_rect, ku_rect = 2, 1 A = ComplexF64[3 0.1 0; 0.2 3 0.1; 0.1 0.2 3] ab = zeros(ComplexF64, 2kl_rect + ku_rect + 1, 3) - for j in 1:3, i in max(1, j-ku_rect):min(3, j+kl_rect) + for j in 1:3, i in max(1, j - ku_rect):min(3, j + kl_rect) ab[kl_rect+ku_rect+1+i-j, j] = A[i, j] end b = ones(ComplexF64, 3, 1) @@ -120,7 +120,7 @@ end intr = FFS.ForceFreeStatesInternal(; mlow=1, mhigh=2, nlow=1, nhigh=1, numpert_total=2) for x in (xs..., 0.42), q in (1.0, 1.4) L = reshape(lower(x), 2, 2) - Q = Diagonal([1-q, 2-q]) + Q = Diagonal([1 - q, 2 - q]) expected = Q * L * L' * Q F, K, G = FFS.gal_get_fkg(mats, intr, x, q) @test F ≈ expected diff --git a/test/runtests_imas.jl b/test/runtests_imas.jl index de35804fcf..e0b5f61314 100644 --- a/test/runtests_imas.jl +++ b/test/runtests_imas.jl @@ -20,52 +20,52 @@ using GeneralizedPerturbedEquilibrium.Equilibrium # COCOS 11: ψ_IMAS = 2π × ψ_internal psi_axis_int = 0.0 - psi_bnd_int = 1.5 # Wb/rad + psi_bnd_int = 1.5 # Wb/rad cf = cocos == 11 ? 2π : 1.0 - eqt.global_quantities.psi_axis = psi_axis_int * cf - eqt.global_quantities.psi_boundary = psi_bnd_int * cf + eqt.global_quantities.psi_axis = psi_axis_int * cf + eqt.global_quantities.psi_boundary = psi_bnd_int * cf nw = 64 psi_1d = collect(LinRange(psi_axis_int, psi_bnd_int, nw)) - eqt.profiles_1d.psi = psi_1d .* cf - eqt.profiles_1d.f = fill(5.0, nw) + eqt.profiles_1d.psi = psi_1d .* cf + eqt.profiles_1d.f = fill(5.0, nw) eqt.profiles_1d.pressure = collect(LinRange(1e4, 0.0, nw)) - eqt.profiles_1d.q = collect(LinRange(1.0, 3.0, nw)) + eqt.profiles_1d.q = collect(LinRange(1.0, 3.0, nw)) # Minimal 2D ψ(R,Z) — rough circular flux surfaces nR, nZ = 32, 40 R_grid = collect(LinRange(1.0, 2.5, nR)) Z_grid = collect(LinRange(-0.8, 0.8, nZ)) - Rmag = 1.75 + Rmag = 1.75 psi_rz = [min((R - Rmag)^2 / 0.6 + Z^2 / 0.7, psi_bnd_int * 1.4) for R in R_grid, Z in Z_grid] resize!(eqt.profiles_2d, 1; wipe=true) - prof2d = eqt.profiles_2d[1] + prof2d = eqt.profiles_2d[1] prof2d.grid_type.index = 1 - prof2d.grid.dim1 = R_grid - prof2d.grid.dim2 = Z_grid - prof2d.psi = psi_rz .* cf + prof2d.grid.dim1 = R_grid + prof2d.grid.dim2 = Z_grid + prof2d.psi = psi_rz .* cf return dd, psi_bnd_int end # Test 1: read_imas — COCOS 11 conversion produces correct psio @testset "read_imas: COCOS 11 conversion" begin - dd, psi_bnd_int = make_mock_dd(cocos=11) + dd, psi_bnd_int = make_mock_dd(; cocos=11) config = EquilibriumConfig(; eq_type="imas", eq_filename="mock", imas_cocos=11) result = Equilibrium.read_imas(config, dd) # psio should equal |psi_boundary - psi_axis| in internal (COCOS 2) units @test isapprox(result.psio, psi_bnd_int; rtol=1e-6) - @test result.rmin ≈ 1.0 atol=1e-9 - @test result.rmax ≈ 2.5 atol=1e-9 + @test result.rmin ≈ 1.0 atol = 1e-9 + @test result.rmax ≈ 2.5 atol = 1e-9 end # Test 2: read_imas — COCOS 2 input requires no conversion @testset "read_imas: COCOS 2 no-op conversion" begin - dd, psi_bnd_int = make_mock_dd(cocos=2) + dd, psi_bnd_int = make_mock_dd(; cocos=2) config = EquilibriumConfig(; eq_type="imas", eq_filename="mock", imas_cocos=2) result = Equilibrium.read_imas(config, dd) @@ -133,16 +133,16 @@ using GeneralizedPerturbedEquilibrium.Equilibrium dd = IMASdd.dd() # Mock result: npert=1 (n=1 only), 3 eigenvalues sorted ascending - mock_et = [0.4+0im, 0.7+0im, 1.1+0im] - mock_n_idx = [0, 0, 0] # all belong to n=1 (j=0) - mock_result = ( - ffs = (integrator=:forward, free_boundary=(et=mock_et, n_tor_idx=mock_n_idx), + mock_et = [0.4 + 0im, 0.7 + 0im, 1.1 + 0im] + mock_n_idx = [0, 0, 0] # all belong to n=1 (j=0) + mock_result = ( + ffs=(integrator=:forward, free_boundary=(et=mock_et, n_tor_idx=mock_n_idx), numpert_total=3, npert=1, nlow=1), ) GeneralizedPerturbedEquilibrium.write_imas(dd, mock_result) - @test dd.mhd_linear.code.name == "GPEC" + @test dd.mhd_linear.code.name == "GPEC" @test dd.mhd_linear.ideal_flag == 1 @test length(dd.mhd_linear.time_slice) == 1 @@ -163,10 +163,10 @@ using GeneralizedPerturbedEquilibrium.Equilibrium # et sorted ascending (least stable globally first). # n=1 (j=0) has eigenvalues: real=0.3 (idx 1) and real=0.6 (idx 3) # n=2 (j=1) has eigenvalues: real=0.5 (idx 2) and real=0.7 (idx 4) - mock_et = [0.3+0im, 0.5+0im, 0.6+0im, 0.7+0im] + mock_et = [0.3 + 0im, 0.5 + 0im, 0.6 + 0im, 0.7 + 0im] mock_n_idx = [0, 1, 0, 1] mock_result = ( - ffs = (integrator=:forward, free_boundary=(et=mock_et, n_tor_idx=mock_n_idx), + ffs=(integrator=:forward, free_boundary=(et=mock_et, n_tor_idx=mock_n_idx), numpert_total=4, npert=2, nlow=1), ) @@ -190,7 +190,7 @@ using GeneralizedPerturbedEquilibrium.Equilibrium # Single n=1 run dd_single = IMASdd.dd() result_single = ( - ffs = (integrator=:forward, free_boundary=(et=[0.3+0im, 0.6+0im], n_tor_idx=[0, 0]), + ffs=(integrator=:forward, free_boundary=(et=[0.3 + 0im, 0.6 + 0im], n_tor_idx=[0, 0]), numpert_total=2, npert=1, nlow=1), ) GeneralizedPerturbedEquilibrium.write_imas(dd_single, result_single) @@ -198,7 +198,7 @@ using GeneralizedPerturbedEquilibrium.Equilibrium # Single n=2 run dd_single2 = IMASdd.dd() result_single2 = ( - ffs = (integrator=:forward, free_boundary=(et=[0.5+0im, 0.7+0im], n_tor_idx=[0, 0]), + ffs=(integrator=:forward, free_boundary=(et=[0.5 + 0im, 0.7 + 0im], n_tor_idx=[0, 0]), numpert_total=2, npert=1, nlow=2), ) GeneralizedPerturbedEquilibrium.write_imas(dd_single2, result_single2) @@ -206,14 +206,14 @@ using GeneralizedPerturbedEquilibrium.Equilibrium # Combined n=1,2 run (same eigenvalues, now interleaved) dd_multi = IMASdd.dd() result_multi = ( - ffs = (integrator=:forward, free_boundary=(et=[0.3+0im, 0.5+0im, 0.6+0im, 0.7+0im], n_tor_idx=[0, 1, 0, 1]), + ffs=(integrator=:forward, free_boundary=(et=[0.3 + 0im, 0.5 + 0im, 0.6 + 0im, 0.7 + 0im], n_tor_idx=[0, 1, 0, 1]), numpert_total=4, npert=2, nlow=1), ) GeneralizedPerturbedEquilibrium.write_imas(dd_multi, result_multi) - ts_single = dd_single.mhd_linear.time_slice[1] + ts_single = dd_single.mhd_linear.time_slice[1] ts_single2 = dd_single2.mhd_linear.time_slice[1] - ts_multi = dd_multi.mhd_linear.time_slice[1] + ts_multi = dd_multi.mhd_linear.time_slice[1] # n=1 energy from combined run must match the standalone n=1 run @test ts_multi.toroidal_mode[1].energy_perturbed == ts_single.toroidal_mode[1].energy_perturbed diff --git a/test/runtests_kinetic.jl b/test/runtests_kinetic.jl index 51b4d4fe72..c7f2b5aa8c 100644 --- a/test/runtests_kinetic.jl +++ b/test/runtests_kinetic.jl @@ -810,7 +810,10 @@ using TOML deck = TOML.parsefile(joinpath(template, "gpec.toml")) sol = GeneralizedPerturbedEquilibrium.analytic_config(deck) # A relative kinetic_file resolves against the deck directory, like eq_filename. - config = GeneralizedPerturbedEquilibrium.equilibrium_config(merge(deck, Dict{String,Any}("Equilibrium" => merge(deck["Equilibrium"], Dict{String,Any}("kinetic_file" => relpath(h5, template))))), template) + config = GeneralizedPerturbedEquilibrium.equilibrium_config( + merge(deck, Dict{String,Any}("Equilibrium" => merge(deck["Equilibrium"], Dict{String,Any}("kinetic_file" => relpath(h5, template))))), + template + ) @test config.kinetic_file == normpath(h5) raw = E.read_kinetic_file(h5) equil = E.PlasmaEquilibrium(config, sol) diff --git a/test/runtests_layer_overlap_fitzpatrick.jl b/test/runtests_layer_overlap_fitzpatrick.jl index 5ed3703911..bb1f86a49d 100644 --- a/test/runtests_layer_overlap_fitzpatrick.jl +++ b/test/runtests_layer_overlap_fitzpatrick.jl @@ -123,7 +123,7 @@ psi1 = overlap_psi(1) @test psi1 !== nothing - psi1 === nothing || @info "Fitzpatrick 2025 overlap, n = 1" psi=psi1 paper=0.9985 deviation=abs(psi1 - 0.9985) atol=1e-4 + psi1 === nothing || @info "Fitzpatrick 2025 overlap, n = 1" psi = psi1 paper = 0.9985 deviation = abs(psi1 - 0.9985) atol = 1e-4 # Paper: Psi = 0.9985 (n = 1); measured deviation 1.1e-5. The bound covers the pedestal read # off Fig. 8 and the resistivity closure (Spitzer-Harm here against the paper's Eqs. 70-72), # and still leaves ~9x margin. Pure quadrature and root-finding, no BLAS, so platform spread @@ -132,7 +132,7 @@ psi4 = overlap_psi(4) @test psi4 !== nothing - psi4 === nothing || @info "Fitzpatrick 2025 overlap, n = 4" psi=psi4 paper=0.9952 deviation=abs(psi4 - 0.9952) atol=1e-3 + psi4 === nothing || @info "Fitzpatrick 2025 overlap, n = 4" psi = psi4 paper = 0.9952 deviation = abs(psi4 - 0.9952) atol = 1e-3 # Paper: Psi = 0.9952 (n = 4); measured deviation 2.3e-4. Looser than n = 1 because these # surfaces sit further in, where the digitised pedestal shape matters more. @test isapprox(psi4, 0.9952; atol=1e-3) @@ -301,7 +301,8 @@ end trunc_dir = joinpath(dirname(@__DIR__), "examples", "DIIID-like_truncation_example") trunc_inputs = TOML.parsefile(joinpath(trunc_dir, "gpec.toml")) diiid(psihigh) = Equilibrium.PlasmaEquilibrium( - GeneralizedPerturbedEquilibrium.equilibrium_config(Dict{String,Any}("Equilibrium" => merge(trunc_inputs["Equilibrium"], Dict("psihigh" => psihigh))), trunc_dir), nothing) + GeneralizedPerturbedEquilibrium.equilibrium_config(Dict{String,Any}("Equilibrium" => merge(trunc_inputs["Equilibrium"], Dict("psihigh" => psihigh))), trunc_dir), + nothing) long, short = diiid(0.9999), diiid(0.995) # One lower X-point, 1e-4 outside this file's boundary; the law reproduces the traced shear. diff --git a/test/runtests_multiion.jl b/test/runtests_multiion.jl index ce7c273a41..03f3d1001d 100644 --- a/test/runtests_multiion.jl +++ b/test/runtests_multiion.jl @@ -27,8 +27,9 @@ using HDF5 @testset "IonSpecies + TOML conversion" begin # TOML.jl parses [[Equilibrium.ion_species]] as a Vector{Any} of Dicts. v = Any[Dict("z" => 1, "m" => 2, "fraction" => 0.5), Dict("z" => 1, "m" => 3, "fraction" => 0.5)] - c = GeneralizedPerturbedEquilibrium.equilibrium_config(Dict{String,Any}("Equilibrium" => - Dict{String,Any}("eq_type" => "efit", "kinetic_file" => "k.h5", "ion_species" => v)), ".") + c = GeneralizedPerturbedEquilibrium.equilibrium_config( + Dict{String,Any}("Equilibrium" => + Dict{String,Any}("eq_type" => "efit", "kinetic_file" => "k.h5", "ion_species" => v)), ".") @test length(c.ion_species) == 2 @test (c.ion_species[1].z, c.ion_species[1].m, c.ion_species[1].fraction) == (1, 2, 0.5) @test GeneralizedPerturbedEquilibrium.Equilibrium.EquilibriumConfig().ion_species === nothing # default single-ion diff --git a/test/runtests_parallel_integration.jl b/test/runtests_parallel_integration.jl index 61bd56a61d..7ddba18d9f 100644 --- a/test/runtests_parallel_integration.jl +++ b/test/runtests_parallel_integration.jl @@ -67,8 +67,8 @@ end GeneralizedPerturbedEquilibrium.ForceFreeStates.apply_propagator!(odet, prop) - @test odet.u[:, :, 1] ≈ u1_in rtol=1e-12 - @test odet.u[:, :, 2] ≈ u2_in rtol=1e-12 + @test odet.u[:, :, 1] ≈ u1_in rtol = 1e-12 + @test odet.u[:, :, 2] ≈ u2_in rtol = 1e-12 end @testset "apply_propagator! linearity" begin @@ -104,8 +104,8 @@ end u1_expected = U1_upper * u1_in + U1_lower * u2_in u2_expected = U2_upper * u1_in + U2_lower * u2_in - @test odet.u[:, :, 1] ≈ u1_expected rtol=1e-12 - @test odet.u[:, :, 2] ≈ u2_expected rtol=1e-12 + @test odet.u[:, :, 1] ≈ u1_expected rtol = 1e-12 + @test odet.u[:, :, 2] ≈ u2_expected rtol = 1e-12 end @testset "apply_propagator_inverse! is inverse of apply_propagator!" begin @@ -139,8 +139,8 @@ end GeneralizedPerturbedEquilibrium.ForceFreeStates.apply_propagator_inverse!(odet, prop) GeneralizedPerturbedEquilibrium.ForceFreeStates.apply_propagator!(odet, prop) - @test odet.u[:, :, 1] ≈ u1_in rtol=1e-12 - @test odet.u[:, :, 2] ≈ u2_in rtol=1e-12 + @test odet.u[:, :, 1] ≈ u1_in rtol = 1e-12 + @test odet.u[:, :, 2] ≈ u2_in rtol = 1e-12 end @testset "balance_integration_chunks produces target count" begin @@ -176,7 +176,7 @@ end # gap of ≈2·singfac_min/|n·q1| between the pre-crossing and post-crossing intervals. for i in eachindex(balanced)[2:end] if !balanced[i-1].needs_crossing - @test balanced[i].psi_start ≈ balanced[i-1].psi_end rtol=1e-10 + @test balanced[i].psi_start ≈ balanced[i-1].psi_end rtol = 1e-10 else # Inner-layer gap: post-crossing chunk starts AFTER the rational surface @test balanced[i].psi_start > balanced[i-1].psi_end @@ -204,7 +204,7 @@ end # An nchunks below the singular-surface floor is clamped up, with a warning. min_chunks = 2 * intr.msing + 3 ctrl_few = first(_prepared_solovev("Riccati"; nchunks=1)) - balanced_few = @test_logs (:warn,) match_mode=:any GeneralizedPerturbedEquilibrium.ForceFreeStates.balance_integration_chunks(base_chunks, ctrl_few, intr) + balanced_few = @test_logs (:warn,) match_mode = :any GeneralizedPerturbedEquilibrium.ForceFreeStates.balance_integration_chunks(base_chunks, ctrl_few, intr) @test length(balanced_few) == max(min_chunks, length(base_chunks)) end diff --git a/test/runtests_riccati.jl b/test/runtests_riccati.jl index 76eead85b4..b3904b9702 100644 --- a/test/runtests_riccati.jl +++ b/test/runtests_riccati.jl @@ -28,8 +28,8 @@ end 0.0 1.2+0.1im 0.0im 0.2; 0.1+0.1im 0.0 0.9+0.3im 0.1im; 0.0im 0.2 0.0 1.1+0.2im] - U1 = rng .+ 0.5*I(N) - U2 = 0.5*rng .+ I(N) # near-identity to ensure invertibility + U1 = rng .+ 0.5 * I(N) + U2 = 0.5 * rng .+ I(N) # near-identity to ensure invertibility u = zeros(ComplexF64, N, N, 2) u[:, :, 1] .= U1 @@ -40,7 +40,7 @@ end FFS.renormalize_riccati_inplace!(u, N) @test u[:, :, 2] ≈ I(N) - @test u[:, :, 1] ≈ S_expected rtol=1e-12 + @test u[:, :, 1] ≈ S_expected rtol = 1e-12 end @testset "renormalize_riccati_inplace! idempotent" begin @@ -56,7 +56,7 @@ end FFS.renormalize_riccati_inplace!(u, N) @test u[:, :, 2] ≈ I(N) - @test u[:, :, 1] ≈ S rtol=1e-12 + @test u[:, :, 1] ≈ S rtol = 1e-12 end @testset "renormalize_riccati! (OdeState)" begin @@ -64,8 +64,8 @@ end rng = [1.0+0.5im 0.2im 0.1; 0.0im 1.2+0.1im 0.0; 0.1+0.1im 0.0 0.9+0.3im] - U1 = rng .+ 0.5*I(N) - U2 = 0.2*rng .+ I(N) + U1 = rng .+ 0.5 * I(N) + U2 = 0.2 * rng .+ I(N) odet = FFS.OdeState(N, 10, 5, 1) odet.u[:, :, 1] .= U1 @@ -77,7 +77,7 @@ end FFS.renormalize_riccati!(odet, intr) @test odet.u[:, :, 2] ≈ I(N) - @test odet.u[:, :, 1] ≈ S_expected rtol=1e-12 + @test odet.u[:, :, 1] ≈ S_expected rtol = 1e-12 end # ── Shared Solovev setup ────────────────────────────────────────────────── @@ -134,7 +134,7 @@ end @testset "Riccati end state has U₂ ≈ I" begin # The outer-region re-integration that closes riccati_eulerlagrange_integration # leaves odet.u[:,:,2] as the identity (canonical Riccati convention after final renorm) - @test odet_ric.u[:, :, 2] ≈ I(N) rtol=1e-10 + @test odet_ric.u[:, :, 2] ≈ I(N) rtol = 1e-10 end @testset "riccati_der! formula — Glasser 2018 Eq. 19" begin diff --git a/test/runtests_sfl_sampling.jl b/test/runtests_sfl_sampling.jl index 161a43c12a..d6506fd152 100644 --- a/test/runtests_sfl_sampling.jl +++ b/test/runtests_sfl_sampling.jl @@ -18,10 +18,12 @@ using GeneralizedPerturbedEquilibrium @testset "Solovev: rzphi independent of ψ resolution and on the exact surfaces" begin sc = Eq.SolovevConfig(64, 64, 64, 1.6, 0.33, 1.0, 1.9, 1.0, 1.0, 1.0) - sol_eq(mpsi) = Eq.equilibrium_solver(Eq.sol_run( - Eq.EquilibriumConfig(; eq_type="sol", eq_filename="unused", jac_type="pest", grid_type="core_edge_packed", - psilow=1e-4, psihigh=0.99999, mpsi=mpsi, mtheta=128), - sc)) + sol_eq(mpsi) = Eq.equilibrium_solver( + Eq.sol_run( + Eq.EquilibriumConfig(; eq_type="sol", eq_filename="unused", jac_type="pest", grid_type="core_edge_packed", + psilow=1e-4, psihigh=0.99999, mpsi=mpsi, mtheta=128), + sc) + ) coarse, fine = sol_eq(64), sol_eq(128) # ν is identically zero for this equilibrium, so it carries no signal. for field in (:rzphi_offset, :rzphi_jac, :rzphi_rsquared) @@ -44,8 +46,10 @@ using GeneralizedPerturbedEquilibrium @testset "EFIT: efit and efit_arclength tracers agree" begin efit_file = joinpath(@__DIR__, "test_data", "CHEASE_test_data", "EQDSK_COCOS_02") - efit_eq(kind) = Eq.PlasmaEquilibrium(Eq.EquilibriumConfig(; eq_filename=efit_file, eq_type=kind, - jac_type="boozer", grid_type="core_edge_packed", psilow=0.01, psihigh=0.994, mpsi=64, mtheta=128)) + efit_eq(kind) = Eq.PlasmaEquilibrium( + Eq.EquilibriumConfig(; eq_filename=efit_file, eq_type=kind, + jac_type="boozer", grid_type="core_edge_packed", psilow=0.01, psihigh=0.994, mpsi=64, mtheta=128) + ) direct, arclength = efit_eq("efit"), efit_eq("efit_arclength") # Two integrators with different step sequences along the same surfaces. Measured: Jacobian 8.7e-8 # and ν 1.7e-7 when both sample at common abscissae; 2.9e-4 and 1.1e-5 with per-surface resampling. diff --git a/test/runtests_sing.jl b/test/runtests_sing.jl index 18d11fec70..277d57bd88 100644 --- a/test/runtests_sing.jl +++ b/test/runtests_sing.jl @@ -233,7 +233,8 @@ using FastInterpolations: cubic_interp, CubicFit, LinearBinarySearch, Series, Ex @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), truncate_at_rational_offset=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 diff --git a/test/runtests_slayer_inputs.jl b/test/runtests_slayer_inputs.jl index 851d5dbda2..89b27dfbdc 100644 --- a/test/runtests_slayer_inputs.jl +++ b/test/runtests_slayer_inputs.jl @@ -193,7 +193,7 @@ end @testset "build_slayer_inputs: P_perp_model forwards the profile gradients and minor radius" begin - sings = [_mk_sing(psi=0.5, q=2.4, q1=1.2, m=2, n=1)] + sings = [_mk_sing(; psi=0.5, q=2.4, q1=1.2, m=2, n=1)] kw = (; bt=2.0, chi_perp_e=2.0, chi_perp_i=1.0, chi_tor=1.5, dr_val=0.0) ref = build_slayer_inputs(equil, sings, profiles; P_perp_model=:chi_perp_e, kw...)[1] # Flat n_e and linear T_e, T_i: the D_⊥ weights are T_s′/(T_e′ + T_i′) = 7/13 and 6/13 diff --git a/test/runtests_slayer_runner.jl b/test/runtests_slayer_runner.jl index 947a2e7c16..e3f205cbc7 100644 --- a/test/runtests_slayer_runner.jl +++ b/test/runtests_slayer_runner.jl @@ -188,7 +188,8 @@ # The LAR deck carries no kinetic profiles, so a missing ρ is named in the error. @test_throws "GGJ tearing: give rho, or load kinetic profiles" solve(TearingProblem(ffs; eta=1e-7), GGJModel(); config=TearingConfig(; gamma_range=(600.0, 2600.0), omega_range=(-1500.0, 1500.0))) - @test_throws "SLAYER input" solve(TearingProblem(ffs; eta=1e-7, rho=1e-7, + @test_throws "SLAYER input" solve( + TearingProblem(ffs; eta=1e-7, rho=1e-7, critical_resonant_field=GeneralizedPerturbedEquilibrium.Tearing.CriticalResonantFieldControl()), GGJModel(); config=TearingConfig(; gamma_range=(600.0, 2600.0), omega_range=(-1500.0, 1500.0))) end diff --git a/test/runtests_solve_api.jl b/test/runtests_solve_api.jl index a9b12fe8a8..5fe1278b45 100644 --- a/test/runtests_solve_api.jl +++ b/test/runtests_solve_api.jl @@ -199,8 +199,11 @@ using HDF5 @test dp.matrix ≈ FFS.pest3_decompose(dp.raw).Δ @test dp.A !== nothing # the parity blocks the galerkin solve persists - reference = GPEC.main([_stage_deck(dir; ffs_overrides=Dict{String,Any}("integrator" => "Galerkin"), - extra_sections=Dict{String,Any}("Galerkin" => Dict{String,Any}("nx" => 32)))]).ffs + reference = + GPEC.main([ + _stage_deck(dir; ffs_overrides=Dict{String,Any}("integrator" => "Galerkin"), + extra_sections=Dict{String,Any}("Galerkin" => Dict{String,Any}("nx" => 32))) + ]).ffs @test reference.galerkin.msing == msing @test dp.matrix ≈ reference.delta_prime.matrix rtol = 1e-12 @test dp.raw ≈ reference.delta_prime.raw rtol = 1e-12 @@ -287,7 +290,7 @@ using HDF5 @test d.terms[2].scale == 1.0 + 0.0im @test (im * a).scale == im @test (a * 3).scale == 3.0 + 0.0im - @test (a - b).terms[2].scale == -0.5 + 0.0im + @test (a-b).terms[2].scale == -0.5 + 0.0im @test (-a).scale == -1.0 + 0.0im end @@ -385,12 +388,19 @@ using HDF5 @test prob.rotation ≈ fill(1e3, n) # A supplied Coulomb logarithm replaces the closure's in η: the Spitzer η is linear in lnΛ. keq = deepcopy(kgal.equil) - spitzer_eta(lnL) = (attach_kinetic_profiles!(keq, KineticProfileData(; psi, n_e=flat(5e19), n_i=flat(5e19), T_e=flat(1e3), - T_i=flat(1e3), omega_E=flat(2π * 1e3), resistivity_model=:spitzer, lnLambda=flat(lnL))); GPEC.InnerLayer.layer_eta(keq, kgal.surfaces)) + spitzer_eta(lnL) = ( + attach_kinetic_profiles!( + keq, + KineticProfileData(; psi, n_e=flat(5e19), n_i=flat(5e19), T_e=flat(1e3), + T_i=flat(1e3), omega_E=flat(2π * 1e3), resistivity_model=:spitzer, lnLambda=flat(lnL)) + ); + GPEC.InnerLayer.layer_eta(keq, kgal.surfaces) + ) @test spitzer_eta(30.0) ≈ 2 .* spitzer_eta(15.0) rtol = 1e-12 # The neoclassical closure takes it too, through the Spitzer factor and ν*_e: η rises with lnΛ. sauter_eta(lnL) = (attach_kinetic_profiles!(keq, KineticProfileData(; psi, n_e=flat(5e19), n_i=flat(5e19), T_e=flat(1e3), - T_i=flat(1e3), omega_E=flat(2π * 1e3), lnLambda=flat(lnL))); GPEC.InnerLayer.layer_eta(keq, kgal.surfaces)) + T_i=flat(1e3), omega_E=flat(2π * 1e3), lnLambda=flat(lnL))); + GPEC.InnerLayer.layer_eta(keq, kgal.surfaces)) @test all(sauter_eta(30.0) .> sauter_eta(15.0)) # Explicit values override each default on its own. given = MatchProblem(kgal; eta=1e-7, rotation=fill(3.0, n)) @@ -562,9 +572,10 @@ using HDF5 @testset "deck kinetic source names" begin # A deck selects only the KineticForces model; test matrices and the retired key stop the parse. - parsed(overrides) = mktempdir() do dir - GPEC.parse_deck(TOML.parsefile(joinpath(_stage_deck(dir; ffs_overrides=overrides), "gpec.toml")), dir) - end + parsed(overrides) = + mktempdir() do dir + GPEC.parse_deck(TOML.parsefile(joinpath(_stage_deck(dir; ffs_overrides=overrides), "gpec.toml")), dir) + end @test_throws "script-only" parsed(Dict{String,Any}("kinetic" => "TestKineticMatrices")) @test_throws "not a kinetic source" parsed(Dict{String,Any}("kinetic" => "fixed")) @test_throws "no longer a key" parsed(Dict{String,Any}("kinetic_source" => "fixed")) diff --git a/test/runtests_vacuum.jl b/test/runtests_vacuum.jl index 2184fe2e2e..bdea95309c 100644 --- a/test/runtests_vacuum.jl +++ b/test/runtests_vacuum.jl @@ -879,11 +879,11 @@ using GeneralizedPerturbedEquilibrium.Vacuum: ConformalWall, EllipticalWall, Dee # so the two agree only if both use the same relation. Negating ν in 2D alone misses by O(0.1). @testset "2D and 3D paths agree for a nonzero ν" begin mtheta = 48 - θ = range(; start=0, length=mtheta, step=2π/mtheta) + θ = range(; start=0, length=mtheta, step=2π / mtheta) R = 1.7 .+ 0.3 .* cos.(θ) Z = 0.3 .* sin.(θ) .+ 0.08 .* sin.(2θ) .+ 0.08 .* cos.(θ) ν = 0.25 .* sin.(θ) .+ 0.1 .* cos.(2θ) .+ 0.05 - make(nzeta, ν) = VacuumInput(x=collect(R), z=collect(Z), ν=collect(ν), + make(nzeta, ν) = VacuumInput(; x=collect(R), z=collect(Z), ν=collect(ν), mtheta_in=mtheta, nzeta_in=1, m_modes=collect(-3:3), n_modes=[1], mtheta=mtheta, nzeta=nzeta) nowall = nothing @@ -896,7 +896,7 @@ using GeneralizedPerturbedEquilibrium.Vacuum: ConformalWall, EllipticalWall, Dee # D = diag(exp(i·m·a)). An operator left in the conjugate frame follows D†·A·D instead and fails this by O(0.1). @testset "wv and I_v follow a θ-origin shift in the same Fourier frame" begin mtheta, k = 64, 4 - θ = range(; start=0, length=mtheta, step=2π/mtheta) + θ = range(; start=0, length=mtheta, step=2π / mtheta) R = 1.7 .+ 0.5 .* cos.(θ .+ 0.3 .* sin.(θ)) Z = 0.8 .* sin.(θ) .+ 0.08 .* cos.(θ) .+ 0.1 .* cos.(2θ) m_modes = collect(-4:6) @@ -914,21 +914,22 @@ using GeneralizedPerturbedEquilibrium.Vacuum: ConformalWall, EllipticalWall, Dee @testset "clockwise contours" begin mtheta = 32 - θ = range(; start=0, length=mtheta, step=2π/mtheta) + θ = range(; start=0, length=mtheta, step=2π / mtheta) R, Z = 1.7 .+ 0.3 .* cos.(θ), 0.4 .* sin.(θ) make(x, z) = VacuumInput(; x, z, ν=zeros(mtheta), mtheta_in=mtheta, nzeta_in=1, m_modes=collect(-2:2), n_modes=[1], mtheta, nzeta=1) @test_throws ArgumentError compute_vacuum_response(make(reverse(R), reverse(Z)), nothing) # A wall file written clockwise is turned round and gives the same operator as its counter-clockwise twin Rw, Zw = 1.7 .+ 0.6 .* cos.(θ), 0.8 .* sin.(θ) - wall_response(x, z) = mktempdir() do dir - path = joinpath(dir, "wall.dat") - open(path, "w") do io - println(io, mtheta, "\n", 1.7, "\nindex R Z") - foreach(i -> println(io, i, " ", x[i], " ", z[i]), 1:mtheta) + wall_response(x, z) = + mktempdir() do dir + path = joinpath(dir, "wall.dat") + open(path, "w") do io + println(io, mtheta, "\n", 1.7, "\nindex R Z") + foreach(i -> println(io, i, " ", x[i], " ", z[i]), 1:mtheta) + end + compute_vacuum_response(make(collect(R), collect(Z)), FileWall(; file=path, equal_arc_wall=false)) end - compute_vacuum_response(make(collect(R), collect(Z)), FileWall(; file=path, equal_arc_wall=false)) - end ccw = wall_response(Rw, Zw) cw = @test_logs (:warn, r"runs clockwise") match_mode = :any wall_response(circshift(reverse(Rw), 1), circshift(reverse(Zw), 1)) @test cw.wv ≈ ccw.wv From fce38ab47edf9090cdaa3d0d914518d9ea827760 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sun, 1 Nov 2026 19:53:13 -0500 Subject: [PATCH 05/14] Test - TEST - Write the supplied-lnLambda resistivity check as one helper Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VdRsGGg4YRG6E9fgLGbpdF --- test/runtests_solve_api.jl | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/test/runtests_solve_api.jl b/test/runtests_solve_api.jl index 5fe1278b45..153ba9224a 100644 --- a/test/runtests_solve_api.jl +++ b/test/runtests_solve_api.jl @@ -388,20 +388,17 @@ using HDF5 @test prob.rotation ≈ fill(1e3, n) # A supplied Coulomb logarithm replaces the closure's in η: the Spitzer η is linear in lnΛ. keq = deepcopy(kgal.equil) - spitzer_eta(lnL) = ( + function layer_eta_with(lnL; kwargs...) attach_kinetic_profiles!( keq, - KineticProfileData(; psi, n_e=flat(5e19), n_i=flat(5e19), T_e=flat(1e3), - T_i=flat(1e3), omega_E=flat(2π * 1e3), resistivity_model=:spitzer, lnLambda=flat(lnL)) - ); - GPEC.InnerLayer.layer_eta(keq, kgal.surfaces) - ) - @test spitzer_eta(30.0) ≈ 2 .* spitzer_eta(15.0) rtol = 1e-12 + KineticProfileData(; psi, n_e=flat(5e19), n_i=flat(5e19), T_e=flat(1e3), T_i=flat(1e3), + omega_E=flat(2π * 1e3), lnLambda=flat(lnL), kwargs...) + ) + return GPEC.InnerLayer.layer_eta(keq, kgal.surfaces) + end + @test layer_eta_with(30.0; resistivity_model=:spitzer) ≈ 2 .* layer_eta_with(15.0; resistivity_model=:spitzer) rtol = 1e-12 # The neoclassical closure takes it too, through the Spitzer factor and ν*_e: η rises with lnΛ. - sauter_eta(lnL) = (attach_kinetic_profiles!(keq, KineticProfileData(; psi, n_e=flat(5e19), n_i=flat(5e19), T_e=flat(1e3), - T_i=flat(1e3), omega_E=flat(2π * 1e3), lnLambda=flat(lnL))); - GPEC.InnerLayer.layer_eta(keq, kgal.surfaces)) - @test all(sauter_eta(30.0) .> sauter_eta(15.0)) + @test all(layer_eta_with(30.0) .> layer_eta_with(15.0)) # Explicit values override each default on its own. given = MatchProblem(kgal; eta=1e-7, rotation=fill(3.0, n)) @test given.eta == fill(1e-7, n) && given.rho == prob.rho && given.rotation == fill(3.0, n) From 25033d47e3fd3c51bf3ff78e97a175da8f0f489c Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sun, 1 Nov 2026 20:57:32 -0500 Subject: [PATCH 06/14] KineticForces - BUGFIX - Warn when a pitch or energy integral stops short of its tolerance or is not finite The inner QuadGK calls discarded their error estimates, so an integral that stopped at QuadGK's evaluation cap returned its estimate silently; the psi quadrature check sees only the values. Each kind now warns once. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VdRsGGg4YRG6E9fgLGbpdF --- src/KineticForces/EnergyIntegration.jl | 9 ++++++--- src/KineticForces/PitchIntegration.jl | 6 ++++-- src/KineticForces/Utils.jl | 14 ++++++++++++++ test/runtests_kinetic.jl | 7 +++++++ 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/KineticForces/EnergyIntegration.jl b/src/KineticForces/EnergyIntegration.jl index b321f546de..54bf50fdcc 100644 --- a/src/KineticForces/EnergyIntegration.jl +++ b/src/KineticForces/EnergyIntegration.jl @@ -308,7 +308,8 @@ function _integrate_energy_resonant(p::EnergyParams, leff::Float64, wb::Float64, u_max = sqrt(X_ENERGY_MAX) if npole == 0 - val, _ = quadgk(integrand_u, 0.0, u_max; atol=atol, rtol=rtol, segbuf=segbuf) + val, err = quadgk(integrand_u, 0.0, u_max; atol=atol, rtol=rtol, segbuf=segbuf) + check_inner_quadrature(val, err, atol, rtol, "energy") return val end @@ -330,7 +331,8 @@ function _integrate_energy_resonant(p::EnergyParams, leff::Float64, wb::Float64, end sort!(pts) unique!(pts) - smooth_val, _ = _quadgk_breaks(integrand_u, pts, atol, rtol, segbuf) + smooth_val, err = _quadgk_breaks(integrand_u, pts, atol, rtol, segbuf) + check_inner_quadrature(smooth_val, err, atol, rtol, "energy") return smooth_val + pole_contribution end @@ -358,7 +360,8 @@ function integrate_energy(wn::Float64, wt::Float64, we::Float64, wd::Float64, # CGL has no resonance denominator and no pole — integrate the physical # x-space integrand directly over the half line (QuadGK maps [0,∞) itself). if f0type == "cgl" - val, _ = quadgk(x -> _energy_integrand_real(x, p), 0.0, Inf; atol=atol, rtol=rtol, segbuf=segbuf) + val, err = quadgk(x -> _energy_integrand_real(x, p), 0.0, Inf; atol=atol, rtol=rtol, segbuf=segbuf) + check_inner_quadrature(val, err, atol, rtol, "CGL energy") return val end diff --git a/src/KineticForces/PitchIntegration.jl b/src/KineticForces/PitchIntegration.jl index de1714071a..ba09d92773 100644 --- a/src/KineticForces/PitchIntegration.jl +++ b/src/KineticForces/PitchIntegration.jl @@ -96,11 +96,12 @@ function integrate_pitch_gar_quadgk( bobmax_clip = clamp(bobmax, lambda_min, lambda_max) buf = zeros(ComplexF64, nqty) kernel! = (out, λ) -> _pitch_gar_kernel_quadgk!(out, λ, params) - I, _ = if lambda_min < bobmax_clip < lambda_max + I, E = if lambda_min < bobmax_clip < lambda_max quadgk!(kernel!, buf, lambda_min, bobmax_clip, lambda_max; atol=pitch_atol, rtol=pitch_rtol) else quadgk!(kernel!, buf, lambda_min, lambda_max; atol=pitch_atol, rtol=pitch_rtol) end + check_inner_quadrature(I, E, pitch_atol, pitch_rtol, "pitch") return copy(I) end @@ -185,11 +186,12 @@ function integrate_pitch_gar_quadgk_wt( bobmax_clip = clamp(bobmax, lambda_min, lambda_max) buf = zeros(ComplexF64, 2 * nqty) kernel! = (out, λ) -> _pitch_gar_kernel_quadgk_wt!(out, λ, params) - I, _ = if lambda_min < bobmax_clip < lambda_max + I, E = if lambda_min < bobmax_clip < lambda_max quadgk!(kernel!, buf, lambda_min, bobmax_clip, lambda_max; atol=pitch_atol, rtol=pitch_rtol) else quadgk!(kernel!, buf, lambda_min, lambda_max; atol=pitch_atol, rtol=pitch_rtol) end + check_inner_quadrature(I, E, pitch_atol, pitch_rtol, "pitch") return copy(I) end diff --git a/src/KineticForces/Utils.jl b/src/KineticForces/Utils.jl index 60c3aa03e8..5b16156154 100644 --- a/src/KineticForces/Utils.jl +++ b/src/KineticForces/Utils.jl @@ -5,6 +5,20 @@ Shared numerical helpers for KineticForces: the generic sign-change root scan an kinetic-resonance surface locator built on it. """ +""" + check_inner_quadrature(I, E, atol, rtol, what) + +Warn once per integral kind `what` when a pitch or energy quadrature returned an error estimate `E` +above its tolerance `max(atol, rtol·|I|)`, which QuadGK does only when it stops at its evaluation cap, +or a non-finite result. The ψ quadrature sees only the values these return, not their errors. +""" +function check_inner_quadrature(I, E, atol, rtol, what::String) + nI = norm(I) + (isfinite(nI) && isfinite(E) && E <= max(atol, rtol * nI)) || + @warn "KineticForces $what integral did not converge: error estimate $E vs |I| = $nI" maxlog = 1 _id = Symbol(what) + return nothing +end + """ find_sign_change_roots(f, grid) → Vector{Float64} diff --git a/test/runtests_kinetic.jl b/test/runtests_kinetic.jl index c7f2b5aa8c..7adb721632 100644 --- a/test/runtests_kinetic.jl +++ b/test/runtests_kinetic.jl @@ -717,6 +717,13 @@ using TOML @test_throws ErrorException KF.check_psi_quadrature_convergence(total, NaN, ctrl, "fgar") end + @testset "check_inner_quadrature" begin + # QuadGK returns an error above tolerance only at its evaluation cap; that, or a NaN, must not pass silently + @test_logs KF.check_inner_quadrature(1.0 + 0im, 1e-6, 0.0, 1e-5, "test converged") + @test_logs (:warn, r"did not converge") KF.check_inner_quadrature(1.0 + 0im, 1e-3, 0.0, 1e-5, "test capped") + @test_logs (:warn, r"did not converge") KF.check_inner_quadrature([1.0 + 0im, NaN], 0.0, 0.0, 1e-5, "test nan") + end + @testset "METHOD_REGISTRY" begin # The registry is the single source of truth for NTV methods. Every entry's # flag must be a real KineticForcesControl field (the TOML kwargs splat relies From d42c17bb4ff5852048e71809f1b04e4ab631dcdd Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sun, 1 Nov 2026 20:58:55 -0500 Subject: [PATCH 07/14] ErrorFields - BUGFIX - Keep one quiet logger for the NTV rotation scan so maxlog warnings print once per scan Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VdRsGGg4YRG6E9fgLGbpdF --- src/ErrorFields/NTVLimits.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ErrorFields/NTVLimits.jl b/src/ErrorFields/NTVLimits.jl index d75abd21a2..c216f702a5 100644 --- a/src/ErrorFields/NTVLimits.jl +++ b/src/ErrorFields/NTVLimits.jl @@ -530,10 +530,11 @@ function efc_couplings( KineticForces.set_perturbation_data!(kf_intr, pe_state, ffs, equil, ffs.metric) return kf_intr end + # One quiet logger for the whole scan, so a `maxlog` warning prints once per scan rather than once per point. + logger = verbose ? Base.CoreLogging.current_logger() : Base.CoreLogging.SimpleLogger(stderr, Base.CoreLogging.Warn) # Torque and cumulative torque profile (resampled onto the kinetic ψ grid) of one perturbation at one rotation. function torque_of(kf_intr, profiles) kf_state = KineticForces.KineticForcesState() - logger = verbose ? Base.CoreLogging.current_logger() : Base.CoreLogging.SimpleLogger(stderr, Base.CoreLogging.Warn) Base.CoreLogging.with_logger(logger) do KineticForces.compute_torque_all_methods!(kf_state, kf_intr, kf_ctrl, equil, profiles) end From 0787c5ebb56d3d90de409175ca45d8a8cf269cdf Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sun, 1 Nov 2026 21:23:19 -0500 Subject: [PATCH 08/14] Docs - DOCS - Show how to decompose any of the five coupling matrices over any window of surfaces Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VdRsGGg4YRG6E9fgLGbpdF --- docs/src/perturbed_equilibrium.md | 34 +++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/docs/src/perturbed_equilibrium.md b/docs/src/perturbed_equilibrium.md index 016c70d471..42dbfcef5a 100644 --- a/docs/src/perturbed_equilibrium.md +++ b/docs/src/perturbed_equilibrium.md @@ -74,6 +74,40 @@ As a summary the run also stores the core-window decomposition (`ψ_N ≤ CORE_P `PerturbedEquilibrium/SingularCoupling/DominantMode/`, with `forcing_overlap` holding the run's own forcing coefficients `Vᴴ·b̃_x`. +### Other coupling matrices and local windows + +The run writes five coupling matrices under `PerturbedEquilibrium/SingularCoupling/`. Each has one row per +rational surface (labelled by `rational_psi`, `rational_q`, `rational_m`, `rational_n`) and acts on the same +applied b̃ columns: + +| Dataset | Maps b̃ to | Units | +|:--- |:--- |:--- | +| `C_resonant_area_weighted_field` | resonant area-weighted field b̄ʳ = Φʳ/Aʳ | dimensionless | +| `C_resonant_current` | pitch-resonant current | A/T | +| `C_island_width_sq` | squared island half-width | 1/T | +| `C_penetrated_area_weighted_field` | penetrated area-weighted field | dimensionless | +| `C_Delta_prime` | forcing-driven Δ′ | 1/T | + +`ResonantCoupling` wraps the first. Any of the others decomposes the same way through the bare-matrix form +of `dominant_coupling`; take the b̃ conform from a `ResonantCoupling` of the same file: + +```julia +using HDF5 +C, ψr = h5open("gpec.h5") do f + g = f["PerturbedEquilibrium/SingularCoupling"] + read(g["C_resonant_current"]), read(g["rational_psi"]) +end +dom = dominant_coupling(C, ψr; psi_low=0.3, psi_high=0.6) # any window of surfaces +c = coupling_overlap(dom, rootarea_field(rc, coil_modes)) # rc = ResonantCoupling("gpec.h5") +dom.singular_values .* c # the quantity each mode drives, in C's units times T +``` + +The bare form carries no (m, n) labels, so `check_mode_basis` cannot guard the projection: build `rc` from +the same file as `C`. + +The window replaces Fortran GPEC's local-coupling outputs. There, a different range of surfaces meant a new +run; here any range, on any of the five matrices, takes one SVD of the stored matrix. + ```@docs GeneralizedPerturbedEquilibrium.PerturbedEquilibrium.ResonantCoupling GeneralizedPerturbedEquilibrium.PerturbedEquilibrium.DominantCoupling From da929129faf846ffdaa53d482377603b4bd6d6e6 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sun, 1 Nov 2026 21:40:42 -0500 Subject: [PATCH 09/14] Docs - DOCS - Note how the coupling SVD ranks modes in a multi-n run and how to take one n Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VdRsGGg4YRG6E9fgLGbpdF --- docs/src/perturbed_equilibrium.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/src/perturbed_equilibrium.md b/docs/src/perturbed_equilibrium.md index 42dbfcef5a..cccccbc6b5 100644 --- a/docs/src/perturbed_equilibrium.md +++ b/docs/src/perturbed_equilibrium.md @@ -70,6 +70,11 @@ convention the forcing loaders and coil integration produce and the b̃ basis th `coupling_overlap` takes care of the conjugation. Singular vectors carry an arbitrary global phase, so compare `abs` of overlaps across runs, not the complex value. +In a multi-n run the matrix is block-diagonal in n, so every singular mode has a single n. The ranking spans all n, +so the dominant mode belongs to the most strongly coupled n, and `DominantMode` stores that mode. For one n, decompose +its block: `dominant_coupling(rc.C[rows, cols], rc.rational_psi[rows])` with `rows = rc.rational_n .== n` and +`cols = rc.n_modes .== n`, and project `rootarea_field(rc, modes)[cols]`. + As a summary the run also stores the core-window decomposition (`ψ_N ≤ CORE_PSI_HIGH = 0.9`) under `PerturbedEquilibrium/SingularCoupling/DominantMode/`, with `forcing_overlap` holding the run's own forcing coefficients `Vᴴ·b̃_x`. From 0404a9028018c30d1d9e23f1498fe306cf11a5af Mon Sep 17 00:00:00 2001 From: logan-nc Date: Mon, 2 Nov 2026 02:08:21 -0500 Subject: [PATCH 10/14] Test - TEST - Give the hand-built SLAYER profile view the lnLambda field the real one carries Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VdRsGGg4YRG6E9fgLGbpdF --- test/runtests_slayer_inputs.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/runtests_slayer_inputs.jl b/test/runtests_slayer_inputs.jl index 89b27dfbdc..b7a327214d 100644 --- a/test/runtests_slayer_inputs.jl +++ b/test/runtests_slayer_inputs.jl @@ -179,7 +179,7 @@ # The model key, through the same assembly TearingProblem uses. Tearing = GeneralizedPerturbedEquilibrium.Tearing - loaded = (; profiles, chi_perp_e=nothing, chi_perp_i=nothing, chi_tor=nothing, zeff=nothing, zeff_source=:scalar) + loaded = (; profiles, chi_perp_e=nothing, chi_perp_i=nothing, chi_tor=nothing, zeff=nothing, zeff_source=:scalar, lnLambda=nothing) model(; c...) = SLAYERModel(; P_perp_model=:chi_perp_e, D_R=0.01, D_c_type=:fitzpatrick, c...) attached = equil.kinetic equil.kinetic = Equilibrium.KineticProfileData(; psi=psi_pts, n_e=fill(5.0e19, length(psi_pts)), From 83396e096f6abc45d3de91b0640bac5a50e38bad Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Mon, 2 Nov 2026 11:07:13 +0100 Subject: [PATCH 11/14] KineticForces - DOCS - Note that supplied collision rates bypass the model and trim the new docs --- docs/src/kinetic_forces.md | 23 ++++++++++++----------- docs/src/perturbed_equilibrium.md | 3 +-- src/Equilibrium/KineticProfiles.jl | 13 +++++++------ src/KineticForces/Utils.jl | 5 ++--- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/src/kinetic_forces.md b/docs/src/kinetic_forces.md index b6272e4710..a3b00b3b25 100644 --- a/docs/src/kinetic_forces.md +++ b/docs/src/kinetic_forces.md @@ -28,17 +28,18 @@ extension: compatibility, or nine columns that append `lnLambda nu_i[1/s] nu_e[1/s]` (the Fortran PENTRC `kinetic_format = "rates-v1"` table). Header rows are skipped. -Supplied collision rates replace the internal Krook collision model of the NTV calculation, which -otherwise computes them from ``n`` and ``T`` (Logan & Park 2013 Eq. 6). Each is optional and must be -positive. Like Fortran PENTRC, they are interpolated in log space, geometrically between table -points; outside the table's ψ range the end interval's curve is extended in log space, so supply -the rates over the whole ψ range. A supplied `lnLambda` also replaces the `lnLambda_form` Coulomb -logarithm of the resistivity closure, so the tearing layers see the same value; the closure's -electron collisionality ``\nu_{*e}`` is still built from ``n_e`` and ``T_e`` with that ``\ln\Lambda``, -not from a supplied `nu_e`. A supplied `nu_i` is the first main ion's rate; the other ion species, -the impurity among them, keep the internal model's ``(Z_s/Z_1)^2\sqrt{m_1/m_s}`` ratio to it. The -density and temperature scaling factors of `[KineticForces]` do not rescale supplied rates, while -`collisionality_factor` still multiplies them. +Supplied collision rates replace the internal Krook collision model of the NTV calculation +(Logan & Park 2013 Eq. 6), which otherwise computes them from ``n`` and ``T``: + + - Each of `lnLambda`, `nu_i` and `nu_e` is optional and must be positive. + - They are interpolated in log space. Outside the table's ψ range the end interval is extended in log + space, so supply them over the whole ψ range. + - A supplied `nu_i` is the first main ion's rate; the other ion species and the impurity keep the + model's ``(Z_s/Z_1)^2\sqrt{m_1/m_s}`` ratio to it. + - A supplied `lnLambda` also replaces the `lnLambda_form` one in the tearing layers' resistivity. Their + ``\nu_{*e}`` is built from ``n_e``, ``T_e`` and that ``\ln\Lambda``, not from a supplied `nu_e`. + - The density and temperature scaling factors of `[KineticForces]` do not rescale supplied rates; + `collisionality_factor` still multiplies them. The NTV calculation consumes `n_i, n_e, T_i, T_e, omega_E`; `chi_e`/`chi_i`/`chi_phi`/`zeff`, when present, are carried for the resistive-layer (SLAYER) analysis and ignored diff --git a/docs/src/perturbed_equilibrium.md b/docs/src/perturbed_equilibrium.md index cccccbc6b5..c218a87594 100644 --- a/docs/src/perturbed_equilibrium.md +++ b/docs/src/perturbed_equilibrium.md @@ -110,8 +110,7 @@ dom.singular_values .* c # the quantity each The bare form carries no (m, n) labels, so `check_mode_basis` cannot guard the projection: build `rc` from the same file as `C`. -The window replaces Fortran GPEC's local-coupling outputs. There, a different range of surfaces meant a new -run; here any range, on any of the five matrices, takes one SVD of the stored matrix. +Any window, on any of the five matrices, is one SVD of the stored matrix. ```@docs GeneralizedPerturbedEquilibrium.PerturbedEquilibrium.ResonantCoupling diff --git a/src/Equilibrium/KineticProfiles.jl b/src/Equilibrium/KineticProfiles.jl index 468ddee5f3..e581fdc62c 100644 --- a/src/Equilibrium/KineticProfiles.jl +++ b/src/Equilibrium/KineticProfiles.jl @@ -40,8 +40,7 @@ end # Names kinetic profiles in error messages by their provenance, when they carry one. _describe(data) = isempty(data.provenance) ? "kinetic profiles" : "kinetic profiles ($(data.provenance))" -# A supplied rate profile on the kinetic grid: pchip in log space, as Fortran PENTRC `rates-v1`, so it interpolates -# geometrically across decades. +# Supplied rate on the kinetic grid: pchip in log space, as Fortran rates-v1, so it stays positive between points. _supplied_rate(psi_in, v, psi_reg) = v === nothing ? nothing : exp.(_pchip_resample(psi_in, log.(v), psi_reg)) # Hard stop on an unphysical negative density or temperature (bad input or an extrapolation past the table). @@ -90,7 +89,7 @@ const _KINETIC_H5_PROFILES = ("n_i" => "m^-3", "n_e" => "m^-3", "T_i" => "eV", " Read a kinetic-profile file, dispatching on extension: HDF5 (`.h5`/`.hdf5`) via the GPEC kinetic schema, otherwise a 6-column ASCII table (`.gpeckf`/`.kin`/`.dat`: `psi_n n_i n_e T_i[eV] T_e[eV] omega_E`), or a 9-column one that appends `lnLambda nu_i[1/s] nu_e[1/s]` -as Fortran PENTRC `kinetic_format = "rates-v1"`. Returns the raw columns; downstream consumers +(the Fortran PENTRC rates-v1 table). Returns the raw columns; downstream consumers validate the fields they require. """ function read_kinetic_file(path::AbstractString; group::AbstractString="/") @@ -236,7 +235,8 @@ paths (the KineticForces quadrature and the self-consistent kinetic-matrix build this set. Errors on any negative density. Each descriptor's `profiles` carries that species' density as `ni_spline` and its full-composition -Krook collision frequency (Logan & Park 2013 Eq. 6) as `nui_spline`; the other profiles are shared. +Krook collision frequency (Logan & Park 2013 Eq. 6) as `nui_spline`, or, when `nu_i` is supplied, that rate +scaled by (z_s/z₁)²·√(m₁/m_s); the other profiles are shared. A main ion's density is `fraction · n_i` (`n_i` is the total main-ion density) or the named `n_*` dataset given by `density` (e.g. `"n_D"`). In a mixed fraction/density list the impurity content is set by the file's n_i/n_e deficit. The `zpitch` closure is exact for z=1 main ions and approximate @@ -390,8 +390,9 @@ The NTV view of raw kinetic profiles (e.g. `equil.kinetic`): interpolate onto a 101-point ψ grid, optionally apply profile scaling knobs, derive collisional / Z_eff diagnostics, and return a `KineticProfileSplines` of named splines (pchip for lnΛ and ν, cubic otherwise). -Needs `n_e`, `T_i`, `T_e` and `omega_E`; `n_i` defaults to `n_e`. See -[`read_kinetic_file`](@ref) for the file formats. +Needs `n_e`, `T_i`, `T_e` and `omega_E`; `n_i` defaults to `n_e`. Supplied `lnLambda`, `nu_i` and `nu_e` +replace the model's values and are not rescaled by the scaling factors. See [`read_kinetic_file`](@ref) +for the file formats. # Arguments diff --git a/src/KineticForces/Utils.jl b/src/KineticForces/Utils.jl index 5b16156154..5f8a7ae2b8 100644 --- a/src/KineticForces/Utils.jl +++ b/src/KineticForces/Utils.jl @@ -8,9 +8,8 @@ kinetic-resonance surface locator built on it. """ check_inner_quadrature(I, E, atol, rtol, what) -Warn once per integral kind `what` when a pitch or energy quadrature returned an error estimate `E` -above its tolerance `max(atol, rtol·|I|)`, which QuadGK does only when it stops at its evaluation cap, -or a non-finite result. The ψ quadrature sees only the values these return, not their errors. +Warn once per integral kind `what` and Julia session when the error estimate `E` exceeds `max(atol, rtol·‖I‖)`, +i.e. QuadGK stopped short of its tolerance (evaluation cap or endpoint roundoff), or when `I` or `E` is not finite. """ function check_inner_quadrature(I, E, atol, rtol, what::String) nI = norm(I) From f5b64a9338db5656f197dcd0251f8f9a3288888b Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Mon, 2 Nov 2026 12:08:38 +0100 Subject: [PATCH 12/14] InnerLayer - BUGFIX - Keep the resistivity closure's own Coulomb logarithm when kinetic profiles supply one --- src/Equilibrium/PlasmaEquilibrium.jl | 2 +- src/InnerLayer/LayerPlasma.jl | 3 +-- src/InnerLayer/SLAYER/KineticView.jl | 9 +------- src/InnerLayer/SLAYER/LayerInputs.jl | 26 +++++++++--------------- src/InnerLayer/SLAYER/LayerParameters.jl | 6 ++---- src/Tearing/LayerOverlap.jl | 10 ++++----- src/Tearing/TearingProblem.jl | 3 +-- test/runtests_slayer_inputs.jl | 2 +- 8 files changed, 21 insertions(+), 40 deletions(-) diff --git a/src/Equilibrium/PlasmaEquilibrium.jl b/src/Equilibrium/PlasmaEquilibrium.jl index e802b0c351..52d62d72b2 100644 --- a/src/Equilibrium/PlasmaEquilibrium.jl +++ b/src/Equilibrium/PlasmaEquilibrium.jl @@ -146,7 +146,7 @@ layer-overlap scan) reads the composition and closure from here, so they all see | `chi_i` | ion perpendicular heat diffusivity χ⊥,i | | `chi_phi` | toroidal momentum diffusivity χ_φ | | `zeff` | effective charge Z_eff(ψ) supplied directly (optional); the resistive-layer analysis prefers it to its closure | -| `lnLambda` | Coulomb logarithm supplied directly (optional); replaces the internal one in the collision model and resistivity | +| `lnLambda` | Coulomb logarithm supplied directly (optional); replaces the internal one in the NTV collision model | | `nu_i`/`nu_e` | main-ion / electron collision rates in 1/s supplied directly (optional); replace the internal collision model | | `species_densities` | named per-species densities (e.g. `"n_D"`, `"n_T"`) for explicit multi-ion input; `nothing` for ASCII / no extras | | `provenance` | short string recording the source file/format | diff --git a/src/InnerLayer/LayerPlasma.jl b/src/InnerLayer/LayerPlasma.jl index 28d86db489..f8dd77105d 100644 --- a/src/InnerLayer/LayerPlasma.jl +++ b/src/InnerLayer/LayerPlasma.jl @@ -25,8 +25,7 @@ function layer_eta(equil, surfaces) data = _layer_kinetic_data(equil, "resistivity η") loaded = SLAYER._slayer_profiles(data, nothing) return SLAYER.layer_resistivity(equil, surfaces, loaded.profiles; zeff=SLAYER._layer_zeff(loaded, data), - resistivity_model=SLAYER._build_resistivity_model(data.resistivity_model), lnLambda_form=data.lnLambda_form, - lnLambda=loaded.lnLambda) + resistivity_model=SLAYER._build_resistivity_model(data.resistivity_model), lnLambda_form=data.lnLambda_form) end """ diff --git a/src/InnerLayer/SLAYER/KineticView.jl b/src/InnerLayer/SLAYER/KineticView.jl index 392744f1a9..b404b6b7da 100644 --- a/src/InnerLayer/SLAYER/KineticView.jl +++ b/src/InnerLayer/SLAYER/KineticView.jl @@ -49,15 +49,8 @@ function _slayer_profiles(data::KineticProfileData, control) cubic_interp(psi_xs, z) end zeff_source = _usable(data.zeff) ? :file : zeff === nothing ? :scalar : :closure - # A supplied Coulomb logarithm replaces the closure's lnLambda_form one; interpolated in log space as the collision model's. - lnLambda = if data.lnLambda === nothing - nothing - else - log_spl = pchip_interp(psi_xs, log.(data.lnLambda); extrap=ExtendExtrap()) - ψ -> exp(log_spl(ψ)) - end return (profiles=profiles, chi_perp_e=chi_perp_e, chi_perp_i=chi_perp_i, chi_tor=chi_tor, zeff=zeff, zeff_source=zeff_source, - has_omega_E=(data.omega_E !== nothing), lnLambda=lnLambda) + has_omega_E=(data.omega_E !== nothing)) end # Map the TOML resistivity_model symbol to a NeoResistivityModel instance. diff --git a/src/InnerLayer/SLAYER/LayerInputs.jl b/src/InnerLayer/SLAYER/LayerInputs.jl index 899d82726a..3a77f7fd87 100644 --- a/src/InnerLayer/SLAYER/LayerInputs.jl +++ b/src/InnerLayer/SLAYER/LayerInputs.jl @@ -6,7 +6,7 @@ using ..Utilities: KineticProfiles using ...Utilities.NeoclassicalResistivity: NeoResistivityModel, SpitzerModel, coulomb_log_e, nu_star_e -using FastInterpolations: DerivOp, integrate, cubic_interp, pchip_interp, cumulative_integrate, ExtendExtrap +using FastInterpolations: DerivOp, integrate, cubic_interp, cumulative_integrate, ExtendExtrap """ surface_minor_radius(equil, psi; theta=0.0) -> Float64 @@ -256,7 +256,6 @@ Keywords documented as scalar-or-callable are evaluated per surface at ψ. - `resistivity_model` -- `SauterNeoModel()` (default), `RedlNeoModel()`, `SpitzerModel()` or `SpitzerHarmModel()`; the neoclassical `f_trap` and ν*_e come from `sing.restype` when populated, else the ε-only forms - `lnLambda_form` -- Coulomb-log form (default `:nrl`; `:wesson` with `SpitzerHarmModel()` reproduces the legacy SLAYER) - - `lnLambda` -- supplied Coulomb logarithm, a scalar or a callable of ψ; when given it replaces the `lnLambda_form` one """ function build_slayer_inputs(equil, sings, profiles::KineticProfiles; bt=nothing, @@ -278,8 +277,7 @@ function build_slayer_inputs(equil, sings, profiles::KineticProfiles; dc_type::Symbol=:none, theta::Real=0.0, resistivity_model::NeoResistivityModel=SauterNeoModel(), - lnLambda_form::Symbol=:nrl, - lnLambda=nothing) + lnLambda_form::Symbol=:nrl) R0_use = R0 === nothing ? equil.ro : Float64(R0) _eval(x, ψ) = x === nothing ? nothing : x isa Real ? Float64(x) : Float64(x(ψ)) @@ -330,9 +328,8 @@ function build_slayer_inputs(equil, sings, profiles::KineticProfiles; # Trapped-fraction inputs from ResistGeometry when populated; else slayer_parameters uses the ε-only forms. rg = sing.restype - lnL = _eval(lnLambda, psi) (; f_trap, nu_e_star, R_major_eff) = _resistivity_geometry(rg, prof.n_e, prof.T_e, q, _eval(zeff, psi); - resistivity_model=resistivity_model, lnLambda_form=lnLambda_form, lnLamb=lnL) + resistivity_model=resistivity_model, lnLambda_form=lnLambda_form) # Resistive interchange index D_R = E + F + H² (Glasser-Greene-Johnson 1975), the (−D_R) of Connor et al. 2015 Eq. 59; # not the Mercier index D_I = E + F + H − 1/4. @@ -426,7 +423,6 @@ function build_slayer_inputs(equil, sings, profiles::KineticProfiles; nu_e_star=nu_e_star, R_major_eff=R_major_eff, lnLambda_form=lnLambda_form, - lnLamb=lnL, k_ref=k_ref_k, alpha_mercier=alpha_k ) @@ -437,12 +433,12 @@ end # Trapped fraction, electron collisionality and effective major radius of the neoclassical resistivity # closure at one surface, from its `ResistGeometry` when populated; `nothing` lets `surface_resistivity` # fall back to the ε = r_s/R form. -function _resistivity_geometry(rg, n_e, T_e, q, zeff; resistivity_model::NeoResistivityModel, lnLambda_form::Symbol, lnLamb=nothing) +function _resistivity_geometry(rg, n_e, T_e, q, zeff; resistivity_model::NeoResistivityModel, lnLambda_form::Symbol) rg === nothing && return (f_trap=nothing, nu_e_star=nothing, R_major_eff=nothing) nu_e_star = if resistivity_model isa Union{SpitzerModel,SpitzerHarmModel} nothing else - lnL = lnLamb === nothing ? coulomb_log_e(n_e, T_e; form=lnLambda_form) : lnLamb + lnL = coulomb_log_e(n_e, T_e; form=lnLambda_form) nu_star_e(n_e, T_e, rg.R_major, rg.eps_local, q, zeff; lnLamb=lnL) end return (f_trap=rg.f_trap, nu_e_star=nu_e_star, R_major_eff=rg.R_major) @@ -450,26 +446,24 @@ end """ layer_resistivity(equil, sings, profiles::KineticProfiles; zeff=1.0, R0=nothing, rs_method=:midplane, - theta=0.0, q_beyond=nothing, resistivity_model=SauterNeoModel(), lnLambda_form=:nrl, - lnLambda=nothing) -> Vector{Float64} + theta=0.0, q_beyond=nothing, resistivity_model=SauterNeoModel(), lnLambda_form=:nrl) -> Vector{Float64} Parallel resistivity η in Ω·m at each surface of `sings`, with the same closure and per-surface inputs `build_slayer_inputs` gives the SLAYER layer, so every resistive inner layer of a run sees one η. -`zeff` and a supplied Coulomb logarithm `lnLambda` are scalars or callables of ψ. +`zeff` is a scalar or a callable of ψ. """ function layer_resistivity(equil, sings, profiles::KineticProfiles; zeff=1.0, R0=nothing, rs_method::Symbol=:midplane, theta::Real=0.0, q_beyond=nothing, - resistivity_model::NeoResistivityModel=SauterNeoModel(), lnLambda_form::Symbol=:nrl, lnLambda=nothing) + resistivity_model::NeoResistivityModel=SauterNeoModel(), lnLambda_form::Symbol=:nrl) R0_use = R0 === nothing ? equil.ro : Float64(R0) rs_at, _ = radial_label(equil; rs_method=rs_method, theta=theta, q_beyond=q_beyond) return map(sings) do sing psi = sing.psifac prof = profiles(psi) z = zeff isa Real ? Float64(zeff) : Float64(zeff(psi)) - lnL = lnLambda === nothing ? nothing : lnLambda isa Real ? Float64(lnLambda) : Float64(lnLambda(psi)) geometry = _resistivity_geometry(sing.restype, prof.n_e, prof.T_e, sing.q, z; - resistivity_model=resistivity_model, lnLambda_form=lnLambda_form, lnLamb=lnL) + resistivity_model=resistivity_model, lnLambda_form=lnLambda_form) surface_resistivity(; n_e=prof.n_e, t_e=prof.T_e, zeff=z, qval=sing.q, rs=rs_at(psi), R0=R0_use, - resistivity_model=resistivity_model, lnLambda_form=lnLambda_form, lnLamb=lnL, geometry...) + resistivity_model=resistivity_model, lnLambda_form=lnLambda_form, geometry...) end end diff --git a/src/InnerLayer/SLAYER/LayerParameters.jl b/src/InnerLayer/SLAYER/LayerParameters.jl index abf740b894..639d4fd8f2 100644 --- a/src/InnerLayer/SLAYER/LayerParameters.jl +++ b/src/InnerLayer/SLAYER/LayerParameters.jl @@ -255,7 +255,6 @@ model does not use may be left `nothing`; one it uses must be given, positive an and Sauter 1999 Eq. 18b with ε = `rs / R_major_eff` - `R_major_eff` -- ⟨R⟩ at the surface for the ν*_e formula (default `R0`) - `lnLambda_form` -- Coulomb-log form: `:nrl` (default), `:sauter` or `:wesson` - - `lnLamb` -- supplied Coulomb logarithm; when given it replaces the `lnLambda_form` one - `k_ref` -- K = r_s·(dψ_N/dr), converting the ψ_N-referenced outer Δ' to the r_s reference (default 1, no conversion) - `alpha_mercier` -- Mercier exponent α = √(−D_I) of the same conversion (default 0.5, the slab value) """ @@ -278,12 +277,11 @@ function slayer_parameters(; nu_e_star::Union{Real,Nothing}=nothing, R_major_eff::Union{Real,Nothing}=nothing, lnLambda_form::Symbol=:nrl, - lnLamb::Union{Real,Nothing}=nothing, k_ref::Real=1.0, alpha_mercier::Real=0.5) - # Coulomb logarithm shared by the resistivity closure and τ_ee: the supplied one, else `lnLambda_form`'s. - lnLamb = lnLamb === nothing ? coulomb_log_e(n_e, t_e; form=lnLambda_form) : Float64(lnLamb) + # Coulomb logarithm shared by the resistivity closure and τ_ee. + lnLamb = coulomb_log_e(n_e, t_e; form=lnLambda_form) eta = surface_resistivity(; n_e=n_e, t_e=t_e, zeff=zeff, qval=qval, rs=rs, R0=R0, resistivity_model=resistivity_model, lnLambda_form=lnLambda_form, f_trap=f_trap, nu_e_star=nu_e_star, diff --git a/src/Tearing/LayerOverlap.jl b/src/Tearing/LayerOverlap.jl index e77a80dbf3..3375731a4a 100644 --- a/src/Tearing/LayerOverlap.jl +++ b/src/Tearing/LayerOverlap.jl @@ -124,7 +124,7 @@ comparison only. - `theta` -- poloidal angle for the minor-radius chord, used only by `rs_method=:midplane` (default `0.0`) - `rs_method` -- radial label of the widths and their ψ Jacobian (default `:flux`, the label Fitzpatrick Eq. (100) is anchored to; see `radial_label`) - - `bt`, `mu_i`, `mu_rho`, `zeff`, `chi_perp_e`, `chi_perp_i`, `chi_tor`, `tau_E`, `P_perp_model`, `resistivity_model`, `lnLambda_form`, `lnLambda` + - `bt`, `mu_i`, `mu_rho`, `zeff`, `chi_perp_e`, `chi_perp_i`, `chi_tor`, `tau_E`, `P_perp_model`, `resistivity_model`, `lnLambda_form` -- passed through to `build_slayer_inputs`. The defaults are placeholders: pass the run's own values Surface `k` overlaps its inner neighbour when `ψ_k − w_k/2 < ψ_{k-1} + w_{k-1}/2`. Both surfaces are @@ -149,8 +149,7 @@ function resistive_layer_overlap(equil, profiles::KineticProfiles; tau_E::Union{Real,Nothing}=nothing, P_perp_model::Symbol=:D_perp, resistivity_model::NeoResistivityModel=SauterNeoModel(), - lnLambda_form::Symbol=:nrl, - lnLambda=nothing) + lnLambda_form::Symbol=:nrl) n_tor > 0 || throw(ArgumentError("resistive_layer_overlap: n_tor must be positive, got $n_tor")) psihigh_safe = Float64(psihigh_safe) @@ -280,7 +279,7 @@ function resistive_layer_overlap(equil, profiles::KineticProfiles; bt=bt, mu_i=mu_i, mu_rho=mu_rho, zeff=zeff, chi_perp_e=chi_perp_e, chi_perp_i=chi_perp_i, chi_tor=chi_tor, tau_E=tau_E, P_perp_model=P_perp_model, dr_val=0.0, dc_type=:none, theta=theta, - resistivity_model=resistivity_model, lnLambda_form=lnLambda_form, lnLambda=lnLambda) + resistivity_model=resistivity_model, lnLambda_form=lnLambda_form) catch err # Typically a DomainError: profiles that stop short of the surface, or negative shear under a sqrt. err isa Union{ArgumentError,DomainError} || rethrow() @@ -411,8 +410,7 @@ function layer_overlap_scan(equil, model::SLAYERModel, ns) resistive_layer_overlap(equil, loaded.profiles; n_tor=n, rs_method=:flux, chi..., tau_E=data.tau_E, P_perp_model=model.P_perp_model, mu_i=_layer_ion_mass(data), mu_rho=_layer_mass_per_electron(data, zeff), zeff=zeff, theta=model.theta_sample, - resistivity_model=_build_resistivity_model(data.resistivity_model), lnLambda_form=data.lnLambda_form, - lnLambda=loaded.lnLambda) + resistivity_model=_build_resistivity_model(data.resistivity_model), lnLambda_form=data.lnLambda_form) for n in ns ] for (n, scan) in zip(ns, scans) diff --git a/src/Tearing/TearingProblem.jl b/src/Tearing/TearingProblem.jl index fb223e2741..1d1b5a75d0 100644 --- a/src/Tearing/TearingProblem.jl +++ b/src/Tearing/TearingProblem.jl @@ -60,8 +60,7 @@ function _tearing_params(model::SLAYERModel, equil, surfaces, loaded) dc_type=model.D_c_type, theta=model.theta_sample, resistivity_model=_build_resistivity_model(data.resistivity_model), - lnLambda_form=data.lnLambda_form, - lnLambda=loaded.lnLambda) + lnLambda_form=data.lnLambda_form) end """ diff --git a/test/runtests_slayer_inputs.jl b/test/runtests_slayer_inputs.jl index b7a327214d..89b27dfbdc 100644 --- a/test/runtests_slayer_inputs.jl +++ b/test/runtests_slayer_inputs.jl @@ -179,7 +179,7 @@ # The model key, through the same assembly TearingProblem uses. Tearing = GeneralizedPerturbedEquilibrium.Tearing - loaded = (; profiles, chi_perp_e=nothing, chi_perp_i=nothing, chi_tor=nothing, zeff=nothing, zeff_source=:scalar, lnLambda=nothing) + loaded = (; profiles, chi_perp_e=nothing, chi_perp_i=nothing, chi_tor=nothing, zeff=nothing, zeff_source=:scalar) model(; c...) = SLAYERModel(; P_perp_model=:chi_perp_e, D_R=0.01, D_c_type=:fitzpatrick, c...) attached = equil.kinetic equil.kinetic = Equilibrium.KineticProfileData(; psi=psi_pts, n_e=fill(5.0e19, length(psi_pts)), From 3ede86983c711c6ee2ea8b2806a4a55b786b7810 Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Mon, 2 Nov 2026 12:09:15 +0100 Subject: [PATCH 13/14] Equilibrium - FEATURE - Select the ASCII kinetic layout with kinetic_format, as Fortran --- docs/src/kinetic_forces.md | 8 +++--- docs/src/perturbed_equilibrium.md | 4 +-- src/Equilibrium/Config.jl | 12 ++++++-- src/Equilibrium/KineticProfiles.jl | 46 ++++++++++++++++++------------ test/runtests_kinetic.jl | 2 +- test/runtests_multiion.jl | 7 +++-- 6 files changed, 49 insertions(+), 30 deletions(-) diff --git a/docs/src/kinetic_forces.md b/docs/src/kinetic_forces.md index a3b00b3b25..5a315ff9a0 100644 --- a/docs/src/kinetic_forces.md +++ b/docs/src/kinetic_forces.md @@ -25,8 +25,9 @@ extension: `write_kinetic_h5` (which round-trips the per-species densities). - **ASCII (`.gpeckf`/`.kin`/`.dat`)** — legacy six-column whitespace table `psi_n n_i n_e T_i[eV] T_e[eV] omega_E`, retained for backward - compatibility, or nine columns that append `lnLambda nu_i[1/s] nu_e[1/s]` - (the Fortran PENTRC `kinetic_format = "rates-v1"` table). Header rows are skipped. + compatibility, or, with `kinetic_format = "rates-v1"` in `[Equilibrium]`, nine columns that append + `lnLambda nu_i[1/s] nu_e[1/s]` (the Fortran PENTRC rates-v1 table). The column count must match + `kinetic_format`. Header rows are skipped. Supplied collision rates replace the internal Krook collision model of the NTV calculation (Logan & Park 2013 Eq. 6), which otherwise computes them from ``n`` and ``T``: @@ -36,8 +37,7 @@ Supplied collision rates replace the internal Krook collision model of the NTV c space, so supply them over the whole ψ range. - A supplied `nu_i` is the first main ion's rate; the other ion species and the impurity keep the model's ``(Z_s/Z_1)^2\sqrt{m_1/m_s}`` ratio to it. - - A supplied `lnLambda` also replaces the `lnLambda_form` one in the tearing layers' resistivity. Their - ``\nu_{*e}`` is built from ``n_e``, ``T_e`` and that ``\ln\Lambda``, not from a supplied `nu_e`. + - They feed only the NTV calculation; the tearing layers' resistivity keeps its `lnLambda_form` closure. - The density and temperature scaling factors of `[KineticForces]` do not rescale supplied rates; `collisionality_factor` still multiplies them. diff --git a/docs/src/perturbed_equilibrium.md b/docs/src/perturbed_equilibrium.md index c218a87594..e23e3c5626 100644 --- a/docs/src/perturbed_equilibrium.md +++ b/docs/src/perturbed_equilibrium.md @@ -81,9 +81,9 @@ own forcing coefficients `Vᴴ·b̃_x`. ### Other coupling matrices and local windows -The run writes five coupling matrices under `PerturbedEquilibrium/SingularCoupling/`. Each has one row per +The run writes up to five coupling matrices under `PerturbedEquilibrium/SingularCoupling/`. Each has one row per rational surface (labelled by `rational_psi`, `rational_q`, `rational_m`, `rational_n`) and acts on the same -applied b̃ columns: +applied b̃ columns (`C_penetrated_area_weighted_field` only when the run computes penetrated fields): | Dataset | Maps b̃ to | Units | |:--- |:--- |:--- | diff --git a/src/Equilibrium/Config.jl b/src/Equilibrium/Config.jl index 646c1da2ea..9738fae5b1 100644 --- a/src/Equilibrium/Config.jl +++ b/src/Equilibrium/Config.jl @@ -60,6 +60,8 @@ specified in the input. - `kinetic_file::String` - Kinetic-profile file read once into `PlasmaEquilibrium.kinetic` (see [`read_kinetic_file`](@ref)); empty for none. Every kinetic consumer (NTV, tearing, the layer-overlap scan, grid refinement) derives its view from it. - `kinetic_group::String` - HDF5 group of `kinetic_file` holding the profiles + - `kinetic_format::String` - Column layout of an ASCII `kinetic_file`: `"legacy"` (6 columns) or `"rates-v1"` (9 columns, + appending supplied `lnLambda nu_i nu_e`); see [`read_kinetic_file`](@ref) - `ion_species`, `impurity_charge`, `impurity_mass`, `zeff_default`, `chi_e_default`, `chi_i_default`, `chi_phi_default`, `tau_E`, `resistivity_model`, `lnLambda_form` - The plasma composition, profile defaults and resistivity closure applied to the profiles of `kinetic_file` (see [`KineticProfileData`](@ref)); `nothing` @@ -96,6 +98,7 @@ specified in the input. kinetic_file::String = "" kinetic_group::String = "/" + kinetic_format::String = "legacy" ion_species::Union{Nothing,Vector{IonSpecies}} = nothing impurity_charge::Union{Nothing,Int} = nothing impurity_mass::Union{Nothing,Int} = nothing @@ -114,7 +117,7 @@ specified in the input. 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, flux_surface_rtol, - imas_cocos, kinetic_file, kinetic_group, composition...) + imas_cocos, kinetic_file, kinetic_group, kinetic_format, composition...) if jac_type == "hamada" @info "Forcing hamada coordinate jacobian exponents: power_*" power_b = 0 @@ -179,15 +182,20 @@ specified in the input. @warn "psihigh = $psihigh exceeds 1.0 (separatrix); clamping to 1.0" end psihigh = min(psihigh, 1.0) + kinetic_format in KINETIC_FORMATS || error("kinetic_format = \"$kinetic_format\" must be one of $KINETIC_FORMATS") isempty(kinetic_file) && any(!isnothing, composition) && error("the kinetic composition keys of [Equilibrium] describe the profiles of kinetic_file; set kinetic_file") 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, flux_surface_rtol, - imas_cocos, kinetic_file, kinetic_group, composition...) + imas_cocos, kinetic_file, kinetic_group, kinetic_format, composition...) end end +# ASCII kinetic-table layouts, named as Fortran PENTRC `kinetic_format`, and their column counts. +const KINETIC_FORMATS = ("legacy", "rates-v1") +const KINETIC_FORMAT_COLUMNS = Dict("legacy" => 6, "rates-v1" => 9) + # The kinetic composition keys of a config, the ones it sets, as `KineticProfileData` keywords. const KINETIC_COMPOSITION_KEYS = (:ion_species, :impurity_charge, :impurity_mass, :zeff_default, :chi_e_default, :chi_i_default, :chi_phi_default, :tau_E, :resistivity_model, :lnLambda_form) diff --git a/src/Equilibrium/KineticProfiles.jl b/src/Equilibrium/KineticProfiles.jl index e581fdc62c..b3cad89300 100644 --- a/src/Equilibrium/KineticProfiles.jl +++ b/src/Equilibrium/KineticProfiles.jl @@ -28,11 +28,13 @@ _collision_tkev(T) = max(T / _KEV_J, _T_FLOOR_KEV) # NRL-formulary Coulomb logarithm (natural log) at the floored temperature, floored at 1; n_e in m⁻³, T_e in J. _coulomb_log(ne, Te) = max(17.3 - 0.5 * log(ne / 1.0e20) + 1.5 * log(_collision_tkev(Te)), 1.0) -# Warn once, as Fortran PENTRC, when a cold node engages the temperature or Coulomb-logarithm floor. -function _warn_collision_floors(source, ne, Te, Ti) +# Warn once, as Fortran PENTRC, when a cold node engages the temperature or Coulomb-logarithm floor of a model rate +# (supplied rates in `data` use neither). +function _warn_collision_floors(source, data, ne, Te, Ti) + data.nu_i !== nothing && data.nu_e !== nothing && return nothing any(T -> T / _KEV_J < _T_FLOOR_KEV, Te) | any(T -> T / _KEV_J < _T_FLOOR_KEV, Ti) && @warn "$source: flooring T at 1 meV in the collision frequencies" maxlog = 1 - any(i -> 17.3 - 0.5 * log(ne[i] / 1.0e20) + 1.5 * log(_collision_tkev(Te[i])) < 1, eachindex(ne)) && + data.lnLambda === nothing && any(i -> 17.3 - 0.5 * log(ne[i] / 1.0e20) + 1.5 * log(_collision_tkev(Te[i])) < 1, eachindex(ne)) && @warn "$source: flooring the Coulomb logarithm at 1 in the collision frequencies" maxlog = 1 return nothing end @@ -84,26 +86,31 @@ const _KINETIC_H5_PROFILES = ("n_i" => "m^-3", "n_e" => "m^-3", "T_i" => "eV", " "lnLambda" => "dimensionless", "nu_i" => "1/s", "nu_e" => "1/s") """ - read_kinetic_file(path; group="/") -> KineticProfileData + read_kinetic_file(path; group="/", format="legacy") -> KineticProfileData Read a kinetic-profile file, dispatching on extension: HDF5 (`.h5`/`.hdf5`) via -the GPEC kinetic schema, otherwise a 6-column ASCII table (`.gpeckf`/`.kin`/`.dat`: -`psi_n n_i n_e T_i[eV] T_e[eV] omega_E`), or a 9-column one that appends `lnLambda nu_i[1/s] nu_e[1/s]` -(the Fortran PENTRC rates-v1 table). Returns the raw columns; downstream consumers -validate the fields they require. +the GPEC kinetic schema, otherwise an ASCII table (`.gpeckf`/`.kin`/`.dat`) whose layout `format` names: +`"legacy"`, 6 columns `psi_n n_i n_e T_i[eV] T_e[eV] omega_E`, or `"rates-v1"`, 9 columns that append +`lnLambda nu_i[1/s] nu_e[1/s]` (the Fortran PENTRC rates-v1 table). Returns the raw columns; downstream +consumers validate the fields they require. """ -function read_kinetic_file(path::AbstractString; group::AbstractString="/") +function read_kinetic_file(path::AbstractString; group::AbstractString="/", format::AbstractString="legacy") isfile(path) || error("Kinetic profile file not found: $path") + format in KINETIC_FORMATS || throw(ArgumentError("kinetic format \"$format\" must be one of $KINETIC_FORMATS")) ext = lowercase(splitext(path)[2]) - return ext in _KINETIC_H5_EXTS ? _read_kinetic_h5(path; group=group) : _read_kinetic_ascii(path) + return ext in _KINETIC_H5_EXTS ? _read_kinetic_h5(path; group=group) : _read_kinetic_ascii(path, format) end """ -Read a 6-column ASCII kinetic table, or a 9-column one with supplied collision rates, into a `KineticProfileData`. +Read an ASCII kinetic table in the layout `format` names into a `KineticProfileData`. """ -function _read_kinetic_ascii(path::AbstractString) +function _read_kinetic_ascii(path::AbstractString, format::AbstractString) cols = _read_kinetic_table(path) - rates = length(cols) == 9 ? (; lnLambda=cols[7], nu_i=cols[8], nu_e=cols[9]) : (;) + ncol = KINETIC_FORMAT_COLUMNS[format] + length(cols) == ncol || + error("kinetic file $path has $(length(cols)) columns, but kinetic_format = \"$format\" needs $ncol " * + "(\"legacy\": 6; \"rates-v1\": 9, appending lnLambda nu_i nu_e)") + rates = format == "rates-v1" ? (; lnLambda=cols[7], nu_i=cols[8], nu_e=cols[9]) : (;) return KineticProfileData(; psi=cols[1], n_i=cols[2], n_e=cols[3], T_i=cols[4], T_e=cols[5], omega_E=cols[6], rates..., provenance="ASCII $(length(cols))-column: $(basename(path))") end @@ -135,7 +142,7 @@ function _read_kinetic_h5(path::AbstractString; group::AbstractString="/") end """ - attach_kinetic_profiles!(equil, path; group="/", kwargs...) -> equil + attach_kinetic_profiles!(equil, path; group="/", format="legacy", kwargs...) -> equil attach_kinetic_profiles!(equil, data::KineticProfileData) -> equil Attach kinetic profiles to `equil` as `equil.kinetic`. Every kinetic consumer (the NTV torque, the @@ -150,13 +157,14 @@ function attach_kinetic_profiles!(equil::PlasmaEquilibrium, data::KineticProfile return equil end -attach_kinetic_profiles!(equil::PlasmaEquilibrium, path::AbstractString; group::AbstractString="/", kwargs...) = - attach_kinetic_profiles!(equil, KineticProfileData(read_kinetic_file(path; group=group); kwargs...)) +attach_kinetic_profiles!(equil::PlasmaEquilibrium, path::AbstractString; group::AbstractString="/", format::AbstractString="legacy", + kwargs...) = + attach_kinetic_profiles!(equil, KineticProfileData(read_kinetic_file(path; group=group, format=format); kwargs...)) # The kinetic profiles a config names with its composition applied, read once at construction; `nothing` when it names none. configured_kinetic_profiles(config::EquilibriumConfig) = isempty(config.kinetic_file) ? nothing : - KineticProfileData(read_kinetic_file(config.kinetic_file; group=config.kinetic_group); kinetic_composition(config)...) + KineticProfileData(read_kinetic_file(config.kinetic_file; group=config.kinetic_group, format=config.kinetic_format); kinetic_composition(config)...) """ write_kinetic_h5(path, data; group="/", schema_version="1.0", provenance=data.provenance) @@ -271,7 +279,7 @@ function resolve_ntv_species(data::KineticProfileData, ion_species::AbstractVect _assert_positive_density(source, "n_i (total)", ni_total, psi_reg) _assert_nonneg_profile(source, "T_i", Ti, psi_reg) _assert_nonneg_profile(source, "T_e", Te, psi_reg) - all(f -> getfield(data, f) !== nothing, (:lnLambda, :nu_i, :nu_e)) || _warn_collision_floors(source, ne, Te, Ti) + _warn_collision_floors(source, data, ne, Te, Ti) # Fractions are shares of the total n_i: never above 1, and exactly 1 when every species is fraction-specified. fracs = [s.fraction for s in ion_species if !isnan(s.fraction)] @@ -443,7 +451,7 @@ function kinetic_profile_splines(data::KineticProfileData; _assert_positive_density(_describe(data), "n_i", ni, psi_reg) _assert_nonneg_profile(_describe(data), "T_i", Ti, psi_reg) _assert_nonneg_profile(_describe(data), "T_e", Te, psi_reg) - all(f -> getfield(data, f) !== nothing, (:lnLambda, :nu_i, :nu_e)) || _warn_collision_floors(_describe(data), ne, Te, Ti) + _warn_collision_floors(_describe(data), data, ne, Te, Ti) needs_rotation_reform = density_factor != 1.0 || temperature_factor != 1.0 || toroidal_rotation_factor != 1.0 any_scaling = needs_rotation_reform || ExB_rotation_factor != 1.0 diff --git a/test/runtests_kinetic.jl b/test/runtests_kinetic.jl index 7adb721632..1d15a7d00c 100644 --- a/test/runtests_kinetic.jl +++ b/test/runtests_kinetic.jl @@ -718,7 +718,7 @@ using TOML end @testset "check_inner_quadrature" begin - # QuadGK returns an error above tolerance only at its evaluation cap; that, or a NaN, must not pass silently + # An error estimate above tolerance (QuadGK stopped short) or a NaN must not pass silently @test_logs KF.check_inner_quadrature(1.0 + 0im, 1e-6, 0.0, 1e-5, "test converged") @test_logs (:warn, r"did not converge") KF.check_inner_quadrature(1.0 + 0im, 1e-3, 0.0, 1e-5, "test capped") @test_logs (:warn, r"did not converge") KF.check_inner_quadrature([1.0 + 0im, NaN], 0.0, 0.0, 1e-5, "test nan") diff --git a/test/runtests_multiion.jl b/test/runtests_multiion.jl index 03f3d1001d..6cfb5fd58f 100644 --- a/test/runtests_multiion.jl +++ b/test/runtests_multiion.jl @@ -242,11 +242,14 @@ using HDF5 println(io, join((psi[k], own.n_i[k], own.n_e[k], own.T_i[k], own.T_e[k], own.omega_E[k], own.lnLambda[k], own.nu_i[k], own.nu_e[k]), " ")) end end - table = EQ.read_kinetic_file(ascii) + table = EQ.read_kinetic_file(ascii; format="rates-v1") @test table.nu_i ≈ own.nu_i && table.lnLambda ≈ own.lnLambda + # The layout comes from the format, not the column count: a 9-column table read as legacy is an error. + @test_throws r"needs 6" EQ.read_kinetic_file(ascii) + @test_throws ArgumentError EQ.read_kinetic_file(ascii; format="rates-v2") # A data row with fewer columns than the others is an error, not a silently dropped row. open(io -> println(io, join((1.0, own.n_i[end], own.n_e[end], own.T_i[end], own.T_e[end], own.omega_E[end]), " ")), ascii, "a") - @test_throws r"fewer columns" EQ.read_kinetic_file(ascii) + @test_throws r"fewer columns" EQ.read_kinetic_file(ascii; format="rates-v1") rm(ascii) end From 0ed35062c6f5ec7dd911370c1cc428d2d46b0704 Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Mon, 2 Nov 2026 12:09:26 +0100 Subject: [PATCH 14/14] Regression - TEST - Track the NTV torque with supplied collision rates --- .../TJ_kinetic_NTV_example/kinetic_rates.dat | 44 +++++++++++++++++++ .../cases/tj_kinetic_ntv_rates.toml | 40 +++++++++++++++++ test/runtests_solve_api.jl | 33 +++++++++----- 3 files changed, 105 insertions(+), 12 deletions(-) create mode 100644 examples/TJ_kinetic_NTV_example/kinetic_rates.dat create mode 100644 regression-harness/cases/tj_kinetic_ntv_rates.toml diff --git a/examples/TJ_kinetic_NTV_example/kinetic_rates.dat b/examples/TJ_kinetic_NTV_example/kinetic_rates.dat new file mode 100644 index 0000000000..d786af31c7 --- /dev/null +++ b/examples/TJ_kinetic_NTV_example/kinetic_rates.dat @@ -0,0 +1,44 @@ +# kinetic.dat with supplied collision rates (kinetic_format = "rates-v1"): lnLambda is the internal +# collision model's, and nu_i, nu_e are 3x the model's, so a run that ignores them moves its NTV torque. +# psi_n n_i n_e T_i[eV] T_e[eV] omega_E lnLambda nu_i[1/s] nu_e[1/s] +0.000000 1.223086e+19 1.223086e+19 200.0000 200.0000 20000.0000 15.936447 1.320814e+04 8.004180e+05 +0.025000 1.222817e+19 1.222817e+19 195.5000 195.5000 19500.0000 15.902471 1.363412e+04 8.262323e+05 +0.050000 1.203522e+19 1.203522e+19 191.0000 191.0000 19000.0000 15.875444 1.387293e+04 8.407042e+05 +0.075000 1.183797e+19 1.183797e+19 186.5000 186.5000 18500.0000 15.847943 1.411790e+04 8.555497e+05 +0.100000 1.163625e+19 1.163625e+19 182.0000 182.0000 18000.0000 15.819900 1.436971e+04 8.708092e+05 +0.125000 1.142988e+19 1.142988e+19 177.5000 177.5000 17500.0000 15.791293 1.462851e+04 8.864927e+05 +0.150000 1.121868e+19 1.121868e+19 173.0000 173.0000 17000.0000 15.762099 1.489446e+04 9.026097e+05 +0.175000 1.100245e+19 1.100245e+19 168.5000 168.5000 16500.0000 15.732297 1.516771e+04 9.191682e+05 +0.200000 1.078097e+19 1.078097e+19 164.0000 164.0000 16000.0000 15.701861 1.544832e+04 9.361739e+05 +0.225000 1.055403e+19 1.055403e+19 159.5000 159.5000 15500.0000 15.670764 1.573641e+04 9.536320e+05 +0.250000 1.032142e+19 1.032142e+19 155.0000 155.0000 15000.0000 15.638979 1.603203e+04 9.715466e+05 +0.275000 1.008285e+19 1.008285e+19 150.5000 150.5000 14500.0000 15.606479 1.633510e+04 9.899126e+05 +0.300000 9.838102e+18 9.838102e+18 146.0000 146.0000 14000.0000 15.573231 1.664558e+04 1.008728e+06 +0.325000 9.586866e+18 9.586866e+18 141.5000 141.5000 13500.0000 15.539205 1.696325e+04 1.027979e+06 +0.350000 9.328821e+18 9.328821e+18 137.0000 137.0000 13000.0000 15.504369 1.728774e+04 1.047643e+06 +0.375000 9.063714e+18 9.063714e+18 132.5000 132.5000 12500.0000 15.468687 1.761871e+04 1.067700e+06 +0.400000 8.791260e+18 8.791260e+18 128.0000 128.0000 12000.0000 15.432119 1.795560e+04 1.088116e+06 +0.425000 8.511117e+18 8.511117e+18 123.5000 123.5000 11500.0000 15.394627 1.829757e+04 1.108839e+06 +0.450000 8.222884e+18 8.222884e+18 119.0000 119.0000 11000.0000 15.356177 1.864340e+04 1.129796e+06 +0.475000 7.926125e+18 7.926125e+18 114.5000 114.5000 10500.0000 15.316732 1.899140e+04 1.150886e+06 +0.500000 7.620476e+18 7.620476e+18 110.0000 110.0000 10000.0000 15.276253 1.933963e+04 1.171988e+06 +0.525000 7.305553e+18 7.305553e+18 105.5000 105.5000 9500.0000 15.234701 1.968551e+04 1.192949e+06 +0.550000 6.980924e+18 6.980924e+18 101.0000 101.0000 9000.0000 15.192042 2.002559e+04 1.213557e+06 +0.575000 6.646101e+18 6.646101e+18 96.5000 96.5000 8500.0000 15.148252 2.035526e+04 1.233536e+06 +0.600000 6.300621e+18 6.300621e+18 92.0000 92.0000 8000.0000 15.103311 2.066864e+04 1.252527e+06 +0.625000 5.944099e+18 5.944099e+18 87.5000 87.5000 7500.0000 15.057211 2.095834e+04 1.270082e+06 +0.650000 5.576149e+18 5.576149e+18 83.0000 83.0000 7000.0000 15.009964 2.121461e+04 1.285613e+06 +0.675000 5.196380e+18 5.196380e+18 78.5000 78.5000 6500.0000 14.961619 2.142465e+04 1.298341e+06 +0.700000 4.804420e+18 4.804420e+18 74.0000 74.0000 6000.0000 14.912282 2.157127e+04 1.307226e+06 +0.725000 4.399955e+18 4.399955e+18 69.5000 69.5000 5500.0000 14.862145 2.163009e+04 1.310791e+06 +0.750000 3.982956e+18 3.982956e+18 65.0000 65.0000 5000.0000 14.811521 2.157612e+04 1.307520e+06 +0.775000 3.553750e+18 3.553750e+18 60.5000 60.5000 4500.0000 14.760915 2.136511e+04 1.294733e+06 +0.800000 3.113088e+18 3.113088e+18 56.0000 56.0000 4000.0000 14.711172 2.094570e+04 1.269317e+06 +0.825000 2.662383e+18 2.662383e+18 51.5000 51.5000 3500.0000 14.663713 2.024605e+04 1.226918e+06 +0.850000 2.204114e+18 2.204114e+18 47.0000 47.0000 3000.0000 14.621011 1.916915e+04 1.161657e+06 +0.875000 1.742718e+18 1.742718e+18 42.5000 42.5000 2500.0000 14.587459 1.758553e+04 1.065689e+06 +0.900000 1.285949e+18 1.285949e+18 38.0000 38.0000 2000.0000 14.571583 1.533181e+04 9.291128e+05 +0.925000 8.470999e+17 8.470999e+17 33.5000 33.5000 1500.0000 14.591339 1.221607e+04 7.402979e+05 +0.950000 4.497124e+17 4.497124e+17 29.0000 29.0000 1000.0000 14.691469 8.108515e+03 4.913788e+05 +0.975000 1.378556e+17 1.378556e+17 24.5000 24.5000 500.0000 15.024582 3.302452e+03 2.001298e+05 +1.000000 2.762506e+16 2.762506e+16 20.0000 20.0000 0.0000 15.529067 9.192673e+02 5.570792e+04 diff --git a/regression-harness/cases/tj_kinetic_ntv_rates.toml b/regression-harness/cases/tj_kinetic_ntv_rates.toml new file mode 100644 index 0000000000..e4885c1df0 --- /dev/null +++ b/regression-harness/cases/tj_kinetic_ntv_rates.toml @@ -0,0 +1,40 @@ +# Regression case: the TJ-analytic n=1 NTV torque with supplied collision rates. +# Reuses the TJ kinetic NTV deck via [overrides], reading kinetic_rates.dat as a rates-v1 table: its lnΛ is the +# internal model's and its ν_i, ν_e are 3× the model's, so a reader or spline path that drops the supplied rates +# moves the torque away from this case's reference. The only harness coverage of supplied collision rates. +# Each [quantities.*] block names an HDF5 path in the run output, how to extract it, and the +# noise floor below which a difference is treated as zero. +[case] +name = "tj_kinetic_ntv_rates" +description = "TJ-analytic equilibrium, n=1, no wall, NTV torque with supplied rates-v1 collision rates (reuses the TJ NTV deck via overrides)" +example_dir = "examples/TJ_kinetic_NTV_example" + +[overrides] +"Equilibrium.kinetic_file" = "kinetic_rates.dat" # the same profiles with supplied lnLambda, nu_i, nu_e +"Equilibrium.kinetic_format" = "rates-v1" # read columns 7-9 as supplied collision rates + +# NTV torque with the supplied rates. [Re, Im] pair; deterministic for fixed code and inputs, so the +# noise floor sits near FP noise. +[quantities.ntv_torque] +h5path = "KineticForces/fgar/total_torque" +type = "real_vector" +extract = "all_real" +label = "NTV torque fgar with supplied rates [Re, Im]" +noise_threshold = 1e-8 +order = 10 + +[quantities.ntv_psi_nsteps] +h5path = "KineticForces/fgar/psi_nsteps" +type = "int_scalar" +extract = "value" +label = "NTV ψ quadrature evaluations" +noise_threshold = 0 +order = 11 + +# Runtime (special: not from H5) +[quantities.runtime] +h5path = "" +type = "runtime" +extract = "value" +label = "Runtime (s)" +noise_threshold = 0.0 diff --git a/test/runtests_solve_api.jl b/test/runtests_solve_api.jl index 153ba9224a..225866850c 100644 --- a/test/runtests_solve_api.jl +++ b/test/runtests_solve_api.jl @@ -386,19 +386,11 @@ using HDF5 @test prob.eta == GPEC.InnerLayer.layer_eta(kgal.equil, kgal.surfaces) && all(>(0), prob.eta) @test prob.rho ≈ fill(2 * GPEC.Utilities.M_P * 5e19, n) @test prob.rotation ≈ fill(1e3, n) - # A supplied Coulomb logarithm replaces the closure's in η: the Spitzer η is linear in lnΛ. + # A supplied Coulomb logarithm feeds only the NTV collision model; η keeps its own closure. keq = deepcopy(kgal.equil) - function layer_eta_with(lnL; kwargs...) - attach_kinetic_profiles!( - keq, - KineticProfileData(; psi, n_e=flat(5e19), n_i=flat(5e19), T_e=flat(1e3), T_i=flat(1e3), - omega_E=flat(2π * 1e3), lnLambda=flat(lnL), kwargs...) - ) - return GPEC.InnerLayer.layer_eta(keq, kgal.surfaces) - end - @test layer_eta_with(30.0; resistivity_model=:spitzer) ≈ 2 .* layer_eta_with(15.0; resistivity_model=:spitzer) rtol = 1e-12 - # The neoclassical closure takes it too, through the Spitzer factor and ν*_e: η rises with lnΛ. - @test all(layer_eta_with(30.0) .> layer_eta_with(15.0)) + attach_kinetic_profiles!(keq, KineticProfileData(; psi, n_e=flat(5e19), n_i=flat(5e19), T_e=flat(1e3), T_i=flat(1e3), + omega_E=flat(2π * 1e3), lnLambda=flat(30.0))) + @test GPEC.InnerLayer.layer_eta(keq, kgal.surfaces) == prob.eta # Explicit values override each default on its own. given = MatchProblem(kgal; eta=1e-7, rotation=fill(3.0, n)) @test given.eta == fill(1e-7, n) && given.rho == prob.rho && given.rotation == fill(3.0, n) @@ -567,6 +559,23 @@ using HDF5 end end + @testset "kinetic_format" begin + # A rates-v1 kinetic file read through the deck's [Equilibrium] keys and through the API keyword. + mktempdir() do dir + psi = range(0.0, 1.0; length=11) + open(joinpath(dir, "rates.kin"), "w") do io + for x in psi + println(io, join((x, 5e19, 5e19, 1e3, 1e3, 1e3, 16.0 + x, 1e3 * exp(x), 1e5 * exp(-x)), " ")) + end + end + eq_overrides = Dict{String,Any}("kinetic_file" => "rates.kin", "kinetic_format" => "rates-v1") + deck_dir = _stage_deck(dir; eq_overrides=eq_overrides) + from_deck = GPEC.Equilibrium.configured_kinetic_profiles(GPEC.equilibrium_config(TOML.parsefile(joinpath(deck_dir, "gpec.toml")), deck_dir)) + from_api = attach_kinetic_profiles!(deepcopy(equil), joinpath(dir, "rates.kin"); format="rates-v1").kinetic + @test from_deck.nu_i == from_api.nu_i && from_deck.lnLambda == from_api.lnLambda && from_deck.nu_e == from_api.nu_e + end + end + @testset "deck kinetic source names" begin # A deck selects only the KineticForces model; test matrices and the retired key stop the parse. parsed(overrides) =