From e1954db0054e44092a6dcf6d8d748813d34da557 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sun, 1 Nov 2026 17:33:40 -0500 Subject: [PATCH 01/13] KineticForces - BUGFIX! - Hold the precession integrand from itself past the bounce midpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fortran GPEC 1.6.0 fixed the v∥ ≤ 0 fill that copied the bounce-frequency column into the precession column; Julia had kept the old copy for parity. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VdRsGGg4YRG6E9fgLGbpdF --- src/KineticForces/BounceAveraging.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/KineticForces/BounceAveraging.jl b/src/KineticForces/BounceAveraging.jl index b3a54c3aac..b63fffc6f8 100644 --- a/src/KineticForces/BounceAveraging.jl +++ b/src/KineticForces/BounceAveraging.jl @@ -1023,10 +1023,8 @@ function _bounce_samples!( continue else # After midpoint: hold the previous sample to the end of the grid. - # The wd slot is deliberately held from the wb integrand (g_wb), - # reproducing the Fortran behavior for parity. fill!(view(g_wb, i:ntheta), g_wb[i-1]) - fill!(view(g_wd, i:ntheta), g_wb[i-1]) + fill!(view(g_wd, i:ntheta), g_wd[i-1]) fill!(view(jvtheta, i:ntheta), jvtheta[i-1]) break end From d45567603fdea3582d1034385d8e78de55b0a996 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sun, 1 Nov 2026 17:33:59 -0500 Subject: [PATCH 02/13] =?UTF-8?q?KineticForces=20-=20BUGFIX=20-=20Stop=20o?= =?UTF-8?q?n=20a=20non-finite=20=CF=88=20torque=20instead=20of=20returning?= =?UTF-8?q?=20it=20without=20a=20warning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VdRsGGg4YRG6E9fgLGbpdF --- src/KineticForces/Compute.jl | 4 +++- test/runtests_kinetic.jl | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/KineticForces/Compute.jl b/src/KineticForces/Compute.jl index 0c806e64c0..dd087dc941 100644 --- a/src/KineticForces/Compute.jl +++ b/src/KineticForces/Compute.jl @@ -34,10 +34,12 @@ end Warn when the ψ torque quadrature terminated without satisfying the requested tolerances (hit `maxevals_psi`), or when a nonzero user-set `atol_psi` dominated termination — the -silent-garbage scenario for weak applied fields, since NTV scales as δB². +silent-garbage scenario for weak applied fields, since NTV scales as δB². Error when the +torque or its error estimate is not finite, which the tolerance comparisons cannot catch. """ function check_psi_quadrature_convergence(total::ComplexF64, quad_err::Float64, ctrl::KineticForcesControl, method::String) + isfinite(total) && isfinite(quad_err) || error("ψ torque quadrature ($method) returned a non-finite torque $total (error estimate $quad_err).") if quad_err > max(ctrl.atol_psi, ctrl.rtol_psi * abs(total)) @warn "ψ torque quadrature ($method) did not converge within maxevals_psi=$(ctrl.maxevals_psi): " * "error estimate $quad_err vs |T|=$(abs(total)) N·m. Raise maxevals_psi or loosen rtol_psi." diff --git a/test/runtests_kinetic.jl b/test/runtests_kinetic.jl index 5b03717985..916842edb0 100644 --- a/test/runtests_kinetic.jl +++ b/test/runtests_kinetic.jl @@ -712,6 +712,8 @@ using TOML # Nonzero atol_psi dominating a small torque: the silent-garbage scenario ctrl_atol = KF.KineticForcesControl(; atol_psi=1e-2) @test_logs (:warn, r"atol_psi") KF.check_psi_quadrature_convergence(1e-3 + 0.0im, 1e-3, ctrl_atol, "fgar") + # A NaN torque fails every tolerance comparison, so it must error instead of passing silently + @test_throws ErrorException KF.check_psi_quadrature_convergence(complex(NaN), NaN, ctrl, "fgar") end @testset "METHOD_REGISTRY" begin From f00d20ff81d5189d96fbc98c36c9f50bbb3bfb1d Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sun, 1 Nov 2026 17:34:46 -0500 Subject: [PATCH 03/13] KineticForces - API! - Remove the placeholder RLAR and CLAR torque methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RLAR used a placeholder κ average in place of the Fortran κ integral and CLAR returned zero, so neither matched Fortran GPEC. A deck setting rlar_flag or clar_flag now stops with a pointer to fgar_flag or tgar_flag. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VdRsGGg4YRG6E9fgLGbpdF --- src/Deck.jl | 2 + src/KineticForces/KineticForcesStructs.jl | 9 +-- src/KineticForces/Torque.jl | 81 ++--------------------- src/KineticForces/Utils.jl | 2 +- test/runtests_kinetic.jl | 2 +- 5 files changed, 10 insertions(+), 86 deletions(-) diff --git a/src/Deck.jl b/src/Deck.jl index cc68e19c94..a3f13ca92e 100644 --- a/src/Deck.jl +++ b/src/Deck.jl @@ -49,6 +49,8 @@ const _MOVED_KEYS = Dict{String,Dict{String,String}}( "impurity_charge" => "moved to [Equilibrium]", "impurity_mass" => "moved to [Equilibrium]", "nn" => "removed; KineticForces takes the toroidal mode number from the run", + "rlar_flag" => "removed; the large-aspect-ratio methods are not ported, use fgar_flag or tgar_flag", + "clar_flag" => "removed; the large-aspect-ratio methods are not ported, use fgar_flag or tgar_flag", "kinetic_file" => _KINETIC_FILE_HOME), "SLAYER" => Dict{String,String}( "enabled" => "a [Tearing] section with inner_model = \"SLAYER\" runs the tearing analysis", diff --git a/src/KineticForces/KineticForcesStructs.jl b/src/KineticForces/KineticForcesStructs.jl index 7a91500f09..33cadb66be 100644 --- a/src/KineticForces/KineticForcesStructs.jl +++ b/src/KineticForces/KineticForcesStructs.jl @@ -7,8 +7,7 @@ Single source of truth for the NTV calculation methods. Each entry is a NamedTup - `name` — short method identifier used as the HDF5 group key and in `intr.method` - `flag` — the `KineticForcesControl` field symbol that enables the method - `kind` — dispatch routing tag consumed by `method_kind` / `Torque.jl` - (`:gar` for the GAR/matrix family, `:fcgl`/`:rlar`/`:clar` for the - three special-cased methods) + (`:gar` for the GAR/matrix family, `:fcgl` for the fluid CGL method) - `doc` — one-line description printed in verbose output To add a method: append an entry here and add the matching `*_flag` field @@ -18,8 +17,6 @@ const METHOD_REGISTRY = ( (name="fgar", flag=:fgar_flag, kind=:gar, doc="Full general-aspect-ratio calculation"), (name="tgar", flag=:tgar_flag, kind=:gar, doc="Trapped particle general-aspect-ratio calculation"), (name="pgar", flag=:pgar_flag, kind=:gar, doc="Passing particle general-aspect-ratio calculation"), - (name="rlar", flag=:rlar_flag, kind=:rlar, doc="Trapped particle large-aspect-ratio calculation"), - (name="clar", flag=:clar_flag, kind=:clar, doc="Trapped particle cylindrical large-aspect-ratio calculation"), (name="fcgl", flag=:fcgl_flag, kind=:fcgl, doc="Fluid Chew–Goldberger–Low calculation"), (name="fwmm", flag=:fwmm_flag, kind=:gar, doc="Full energy calculation using MXM Euler–Lagrange matrix"), (name="twmm", flag=:twmm_flag, kind=:gar, doc="Trapped energy calculation using MXM Euler–Lagrange matrix"), @@ -37,7 +34,7 @@ const METHOD_REGISTRY = ( """ method_kind(name) -> Symbol -Return the dispatch routing tag (`:gar`, `:fcgl`, `:rlar`, `:clar`) for the NTV +Return the dispatch routing tag (`:gar` or `:fcgl`) for the NTV method `name`, looked up from [`METHOD_REGISTRY`]. Errors on an unknown method. """ function method_kind(name::AbstractString) @@ -80,8 +77,6 @@ Immutable: vary a field by building a new control. fgar_flag::Bool = true # Full general-aspect-ratio tgar_flag::Bool = false # Trapped particle GAR pgar_flag::Bool = false # Passing particle GAR - rlar_flag::Bool = false # Trapped particle large-aspect-ratio - clar_flag::Bool = false # Trapped cylindrical LAR fcgl_flag::Bool = false # Fluid Chew-Goldberger-Low fwmm_flag::Bool = false # Full energy via MXM E-L matrix twmm_flag::Bool = false # Trapped energy via MXM E-L matrix diff --git a/src/KineticForces/Torque.jl b/src/KineticForces/Torque.jl index 597b9fc9bb..8ef06bfed7 100644 --- a/src/KineticForces/Torque.jl +++ b/src/KineticForces/Torque.jl @@ -16,7 +16,7 @@ Toroidal torque from nonambipolar transport at one ψ and bounce harmonic `l`, w - `wdfac::Float64`: Drift factor - `divxfac::Float64`: Divergence factor - `electron::Bool`: Calculate quantities for electrons (zi,mi ignored) - - `method::String`: Integration method (RLAR, CLAR, *GAR, *TMM, *WMM, *KMM) + - `method::String`: Integration method (FCGL, *GAR, *TMM, *WMM, *KMM) where * = F,T,P for full,trapped,passing - `equil`: PlasmaEquilibrium with 2D interpolants and named profile/geometry splines - `intr::KineticForcesInternal`: Internal state with mode indexing and perturbation splines @@ -59,10 +59,7 @@ function tpsi!(tpsi_var::Ref{ComplexF64}, psi::Float64, n::Int, l::Int, _torque_surface(psi, zi, mi, electron, method, equil, intr, kinetic_profiles; need_matrices=(!isnothing(op_wmats)), n, wdfac, divxfac) : surface - (; chrg, mass, tspl, bmax, bmin, q, n_s, T_s, welec, wdian, wdiat, wtran, wgyro, nuk, epsr, dVdpsi, dbob_m_f, divx_m_f) = surf - wbhat = (π / 4) * sqrt(epsr / 2) * wtran - wdhat = q^3 * wtran^2 / (4 * epsr * wgyro) * wdfac - nueff = nuk / (2 * epsr) + (; chrg, mass, tspl, bmax, bmin, q, n_s, T_s, welec, wdian, wdiat, nuk, epsr, dbob_m_f, divx_m_f) = surf # Method selection — route on the registry dispatch tag (errors on unknown method) kind = method_kind(method) @@ -70,16 +67,6 @@ function tpsi!(tpsi_var::Ref{ComplexF64}, psi::Float64, n::Int, l::Int, tpsi_var[] = calculate_fcgl(psi, n, l, tspl, dbob_m_f, divx_m_f, divxfac, n_s, T_s, equil, intr) - elseif kind == :rlar - tpsi_var[] = calculate_rlar(psi, n, l, q, epsr, wdian, wdiat, welec, - wdhat, wbhat, nueff, dVdpsi, n_s, T_s, - dbob_m_f, intr.bo, bmin) - - elseif kind == :clar - tpsi_var[] = calculate_clar(psi, n, l, q, epsr, wdian, wdiat, welec, - nuk, intr.bo, bmax, bmin, n_s, T_s, mass, chrg, - tspl, dbob_m_f, divx_m_f, divxfac, wdfac) - else # :gar — fgar/tgar/pgar + all *mm matrix methods tpsi_var[] = calculate_gar(psi, n, l, q, epsr, wdian, wdiat, welec, nuk, intr.bo, bmax, bmin, n_s, T_s, mass, chrg, @@ -168,63 +155,6 @@ function calculate_fcgl(psi, n, l, tspl, dbob_m_f, divx_m_f, divxfac::Float64, end -""" - calculate_rlar(psi, n, l, q, epsr, wdian, wdiat, welec, wdhat, wbhat, - nueff, dVdpsi, n_s, T_s, dbob_m_f, bo, bmin)::ComplexF64 - -RLAR (Reduced Large Aspect Ratio) torque: energy-space integration with a pitch-averaged -bounce response. Valid for large aspect ratio (ε ≪ 1); the κ bounce average is a placeholder estimate. - -Reference: [Logan et al., Phys. Plasmas, 2013] -""" -function calculate_rlar(psi, n, l, q, epsr, wdian, wdiat, welec, - wdhat, wbhat, nueff, dVdpsi::Float64, - n_s::Float64, T_s::Float64, - dbob_m_f, bo, bmin=0.5)::ComplexF64 - - # Setup parameters for energy integration - lnq = Float64(l) # Resonant mode for trapped particles - - # Maximum pitch angle parameter (use safe estimate) - lmdamax = min(1.0 / (1 - epsr), bo / bmin) - - # Energy-space quadrature ∫ K(x) dx of the resonance operator K - xint = integrate_energy(wdian, wdiat, welec, wdhat, wbhat, nueff, - l, lnq, n, psi, lmdamax, "rlar") - - # Kappa/bounce averaging (effect of field perturbations) - # Placeholder: simplified estimate - kappaint_val = sqrt(mean(abs.(dbob_m_f) .^ 2)) - - # dV/dpsi gradient term from Clebsch coordinate Jacobian - psi_factor = dVdpsi - - # Normalization: √(ε/(2π³)) * n² * n_s * T_s - norm = sqrt(epsr / (2.0 * π^3)) * Float64(n)^2 * n_s * T_s - - # dT/dψ = -0.5 · dV/dψ · κ_int · x_int · norm - result = psi_factor * kappaint_val * 0.5 * (-xint) * norm - - return result -end - - -""" - calculate_clar(psi, n, l, q, epsr, wdian, wdiat, welec, nuk, bo, - bmax, bmin, n_s, T_s, mass, chrg, tspl, dbob_m_f, divx_m_f, - divxfac, wdfac)::ComplexF64 - -CLAR (Circular Large Aspect Ratio) torque. Not implemented: warns once and returns zero. -""" -function calculate_clar(psi, n, l, q, epsr, wdian, wdiat, welec, nuk, bo, - bmax, bmin, n_s::Float64, T_s::Float64, mass, chrg, tspl, - dbob_m_f, divx_m_f, divxfac, wdfac)::ComplexF64 - - @warn "CLAR method not yet fully implemented, returning zero" maxlog = 1 - return ComplexF64(0.0, 0.0) -end - - """ calculate_gar(psi, n, l, q, epsr, wdian, wdiat, welec, nuk, bo, bmax, bmin, n_s, T_s, mass, chrg, tspl, dbob_m_f, divx_m_f, @@ -522,8 +452,6 @@ function _setup_surface_state( wdian = -twopi * T_s * dn_s_dpsi / (chrg * intr.chi1 * n_s) wdiat = -twopi * dT_s_dpsi / (chrg * intr.chi1) - wtran = sqrt(2 * T_s / mass) / (q * intr.ro) - wgyro = chrg * intr.bo / mass nuk = nu_s rsquared_bmin = equil.rzphi_rsquared((psi, theta_bmin)) @@ -533,7 +461,6 @@ function _setup_surface_state( avg_r = equil.geometry.avg_r_spline(psi) avg_R = equil.geometry.avg_R_spline(psi) epsr = avg_r / avg_R - dVdpsi = equil.profiles.dVdpsi_spline(psi) # Cells and stationary points of B(θ), reused by every λ of every harmonic. bf = _surface_b_field(B_vpar) @@ -542,8 +469,8 @@ function _setup_surface_state( chrg, mass, tspl, B_vpar, bf, bmax, bmin, theta_bmax, q, n_s, T_s, welec, - wdian, wdiat, wtran, wgyro, nuk, - epsr, dVdpsi + wdian, wdiat, nuk, + epsr ) end diff --git a/src/KineticForces/Utils.jl b/src/KineticForces/Utils.jl index 2d760e4060..88b09b7262 100644 --- a/src/KineticForces/Utils.jl +++ b/src/KineticForces/Utils.jl @@ -56,7 +56,7 @@ density peaks (Logan & Park, Phys. Plasmas 20, 122507 (2013), §IV–V). `xeval` peak of the Maxwellian-weighted drive `x^2.5·e^−x`. The ℓ = 0 node is the ω_d-shifted ExB (superbanana-plateau) resonance. -The frequencies are the cheap large-aspect-ratio closed forms of the `rlar` method, +The frequencies are the cheap large-aspect-ratio closed forms `ω_b = (π/4)·√(ε/2)·wtran` and `ω_d = q·T_s/(2·ε·R₀²·Z·e·B₀)·wdfac`, with ε = ⟨r⟩/⟨R⟩ clamped away from the axis; no bounce averaging, which is enough for panel placement. """ diff --git a/test/runtests_kinetic.jl b/test/runtests_kinetic.jl index 916842edb0..325ccbf487 100644 --- a/test/runtests_kinetic.jl +++ b/test/runtests_kinetic.jl @@ -724,7 +724,7 @@ using TOML for entry in KF.METHOD_REGISTRY @test entry.flag in fieldnames(KF.KineticForcesControl) @test endswith(string(entry.flag), "_flag") - @test entry.kind in (:gar, :fcgl, :rlar, :clar) + @test entry.kind in (:gar, :fcgl) @test KF.method_kind(entry.name) == entry.kind end end From 4f2ad22aec5606810a0aafa24b2ba092aca71d66 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sun, 1 Nov 2026 17:35:09 -0500 Subject: [PATCH 04/13] Equilibrium - API! - Reject a negative newq0 and drop the q0 sign flip GPEC requires q > 0, matching Fortran GPEC 1.6.0: newq0 = -1 no longer flips the sign of q, and any negative newq0 stops at configuration. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VdRsGGg4YRG6E9fgLGbpdF --- src/Equilibrium/Axisymmetric/Direct.jl | 6 +----- src/Equilibrium/Axisymmetric/Inverse.jl | 5 +---- src/Equilibrium/Config.jl | 3 ++- test/runtests_equil.jl | 12 ++---------- 4 files changed, 6 insertions(+), 20 deletions(-) diff --git a/src/Equilibrium/Axisymmetric/Direct.jl b/src/Equilibrium/Axisymmetric/Direct.jl index 9eb785ffcf..e0c2b93e7a 100644 --- a/src/Equilibrium/Axisymmetric/Direct.jl +++ b/src/Equilibrium/Axisymmetric/Direct.jl @@ -609,17 +609,13 @@ Direct equilibrium driver: locates the axis, traces each flux surface with `fiel if q0 <= 0.0 @warn "q0 extrapolation to axis gives q0 = $(@sprintf("%.3f", q0)) ≤ 0 — likely a spline artifact from psilow being too large; check psilow or use newq0 to override." end - # newq0 = -1 flips the extrapolated q0; resolved locally so the config keeps the user's request. newq0 = equil_params.newq0 - if newq0 == -1 - newq0 = -q0 - end if newq0 != 0.0 @info "Revising q-profile for newq0 = $(@sprintf("%.3f", newq0))" f0 = profiles.F_spline.y[1] - profiles.F_deriv(psi_nodes[1]; hint=Ref(1)) * psi_nodes[1] f0fac = f0^2 * ((newq0 / q0)^2 - 1.0) for i in 1:(mpsi+1) - ffac = sqrt(1.0 + f0fac / profiles.F_spline.y[i]^2) * sign(newq0) + ffac = sqrt(1.0 + f0fac / profiles.F_spline.y[i]^2) sq_nodes[i, 1] *= ffac sq_nodes[i, 4] *= ffac rzphi_nodes[i, :, 3] .*= ffac diff --git a/src/Equilibrium/Axisymmetric/Inverse.jl b/src/Equilibrium/Axisymmetric/Inverse.jl index 4f246126ac..02adab7eeb 100644 --- a/src/Equilibrium/Axisymmetric/Inverse.jl +++ b/src/Equilibrium/Axisymmetric/Inverse.jl @@ -283,16 +283,13 @@ function equilibrium_solver(input::InverseRunInput; override_psi_nodes::Union{No f1_sq_lo = sq_deriv(sq_xs[1]) f1_sq_hi = sq_deriv(sq_xs[end]) q0 = f_sq[1, 4] - f1_sq_lo[4] * sq_xs[1] - if newq0 == -1 - newq0 = -q0 - end if newq0 != 0 f0 = f_sq[1, 2] - f1_sq_lo[2] * sq_xs[1] f0fac = f0^2 * ((newq0 / q0)^2 - 1) q0 = newq0 for ipsi in 0:mpsi - ffac = sqrt(1 + f0fac / f_sq[ipsi+1, 1]^2) * sign(newq0) + ffac = sqrt(1 + f0fac / f_sq[ipsi+1, 1]^2) sq_fs[ipsi+1, 1] *= ffac sq_fs[ipsi+1, 4] *= ffac rzphi_fs[ipsi+1, :, 3] *= ffac diff --git a/src/Equilibrium/Config.jl b/src/Equilibrium/Config.jl index ac091a75dd..646c1da2ea 100644 --- a/src/Equilibrium/Config.jl +++ b/src/Equilibrium/Config.jl @@ -53,7 +53,7 @@ specified in the input. two-pass auto grid (knot count scales as τ^(-1/3)) - `mtheta::Int` - Number of poloidal grid points - `newq0::Float64` - Target on-axis safety factor q(0); the q and F profiles are rescaled to - meet it (0 = use input value, -1 = use the axis extrapolation with its sign flipped) + meet it (0 = use input value); q is positive, so negative values are rejected - `flux_surface_rtol::Float64` - Relative tolerance of the ODE solves that trace each flux surface; their absolute tolerance is `equil_abstol(flux_surface_rtol)`, i.e. `flux_surface_rtol` capped at `EQUIL_ABSTOL_MAX` - `imas_cocos::Int` - Expected COCOS convention of an IMAS `dd.equilibrium` input (11 = IMAS standard, 2 = GPEC internal) @@ -174,6 +174,7 @@ specified in the input. error("Cannot recognize jac_type = $(jac_type)") end (isfinite(flux_surface_rtol) && flux_surface_rtol > 0) || error("flux_surface_rtol = $flux_surface_rtol must be a positive, finite relative tolerance") + newq0 >= 0 || error("newq0 = $newq0 is not supported: q must be positive. Use newq0 = 0 (keep the input q) or newq0 > 0.") if psihigh > 1.0 @warn "psihigh = $psihigh exceeds 1.0 (separatrix); clamping to 1.0" end diff --git a/test/runtests_equil.jl b/test/runtests_equil.jl index 84dc30c587..d3e7de69ad 100644 --- a/test/runtests_equil.jl +++ b/test/runtests_equil.jl @@ -116,7 +116,7 @@ using TOML @testset "newq0 q-profile revision" begin # newq0 is a target q(0), not an index: non-integer requests must survive the config, - # and neither the -1 sentinel nor an explicit target may write back to the frozen config. + # and q is positive, so a negative target is rejected. newq0_config(newq0) = GeneralizedPerturbedEquilibrium.Equilibrium.EquilibriumConfig(; eq_filename=joinpath(data_dir, "EQDSK_COCOS_02"), eq_type="efit", @@ -128,15 +128,7 @@ using TOML newq0=newq0 ) - base_config = newq0_config(0) - eq_base = GeneralizedPerturbedEquilibrium.Equilibrium.PlasmaEquilibrium(base_config) - - # -1 means "flip the sign of the axis extrapolation": f0fac vanishes, so ffac is - # exactly -1 and the revised q-profile is the negated baseline. - flip_config = newq0_config(-1) - eq_flip = GeneralizedPerturbedEquilibrium.Equilibrium.PlasmaEquilibrium(flip_config) - @test flip_config.newq0 == -1.0 - @test eq_flip.profiles.q_spline.y == -eq_base.profiles.q_spline.y + @test_throws ErrorException newq0_config(-1) # A non-integer target used to throw InexactError at construction (newq0 was ::Int) target_config = newq0_config(1.05) From e812b0e6c4b34cdc3c74780bb204e23edeca842b Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sun, 1 Nov 2026 17:36:56 -0500 Subject: [PATCH 05/13] Equilibrium - BUGFIX! - Floor kinetic temperatures and the Coulomb logarithm and take monotone collision-rate slopes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Match Fortran GPEC 1.6.0: the collision model floors T at 1 meV and lnΛ at 1, so a cold edge node gets a large finite ν instead of zero, negative input temperatures stop the run, and lnΛ, ν_i and ν_e are pchip interpolants. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VdRsGGg4YRG6E9fgLGbpdF --- src/Equilibrium/KineticProfiles.jl | 36 ++++++++++++++++------------ src/Equilibrium/PlasmaEquilibrium.jl | 22 +++++++++-------- test/runtests_multiion.jl | 18 ++++++++++++++ 3 files changed, 51 insertions(+), 25 deletions(-) diff --git a/src/Equilibrium/KineticProfiles.jl b/src/Equilibrium/KineticProfiles.jl index 3fda7d3bad..9ee5128327 100644 --- a/src/Equilibrium/KineticProfiles.jl +++ b/src/Equilibrium/KineticProfiles.jl @@ -20,15 +20,19 @@ const _KEV_J = 1.602e-16 # 1 keV in J (temperature normalization in the const _NU_PREFAC = 3.5e17 # Krook collision-frequency prefactor const _MP = 1.672_614e-27 # proton mass [kg] const _ME = 9.109_1e-31 # electron mass [kg] +const _T_FLOOR_KEV = 1.0e-6 # collision-model temperature floor (1 meV), as Fortran PENTRC -# NRL-formulary Coulomb logarithm (natural log); n_e in m⁻³, T_e in J. -_coulomb_log(ne, Te) = 17.3 - 0.5 * log(ne / 1.0e20) + 1.5 * log(Te / _KEV_J) +# Temperature in keV for the collision model, floored so a cold edge node keeps a finite, positive ν. +_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) = ne > 0 ? max(17.3 - 0.5 * log(ne / 1.0e20) + 1.5 * log(_collision_tkev(Te)), 1.0) : 1.0 # Names kinetic profiles in error messages by their provenance, when they carry one. _describe(data) = isempty(data.provenance) ? "kinetic profiles" : "kinetic profiles ($(data.provenance))" -# Hard stop on an unphysical negative density (bad input or a cubic-resample overshoot). -function _assert_nonneg_density(source, label, arr, psi_reg) +# Hard stop on an unphysical negative density or temperature (bad input or a resample overshoot). +function _assert_nonneg_profile(source, label, arr, psi_reg) i = findfirst(<(0), arr) i === nothing || error("$source: negative $label = $(arr[i]) at ψ_n = $(psi_reg[i]) — unphysical") @@ -232,6 +236,8 @@ function resolve_ntv_species(data::KineticProfileData, ion_species::AbstractVect Te_in = _need(data.T_e, "T_e") omE_in = _need(data.omega_E, "omega_E") ni_in = data.n_i === nothing ? copy(ne_in) : data.n_i # TOTAL main-ion density + _assert_nonneg_profile(source, "T_i", Ti_in, psi_in) + _assert_nonneg_profile(source, "T_e", Te_in, psi_in) nkin = _NKIN psi_reg = collect(0:nkin) ./ nkin @@ -240,8 +246,8 @@ function resolve_ntv_species(data::KineticProfileData, ion_species::AbstractVect Ti = _cubic_resample(psi_in, Ti_in, psi_reg) .* _EV_J Te = _cubic_resample(psi_in, Te_in, psi_reg) .* _EV_J omegaE = _cubic_resample(psi_in, omE_in, psi_reg) - _assert_nonneg_density(source, "n_e", ne, psi_reg) - _assert_nonneg_density(source, "n_i (total)", ni_total, psi_reg) + _assert_nonneg_profile(source, "n_e", ne, psi_reg) + _assert_nonneg_profile(source, "n_i (total)", ni_total, psi_reg) # 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)] @@ -279,7 +285,7 @@ function resolve_ntv_species(data::KineticProfileData, ion_species::AbstractVect else ns[si] = s.fraction .* ni_total end - _assert_nonneg_density(source, "ion_species[$si] density", ns[si], psi_reg) + _assert_nonneg_profile(source, "ion_species[$si] density", ns[si], psi_reg) end # Shared composition + collisionality (natural-log Coulomb log), per grid point. @@ -296,18 +302,18 @@ function resolve_ntv_species(data::KineticProfileData, ion_species::AbstractVect zpitch[i] = zp n_main[i] = nm n_imp[i] = nimp - loglam[i] = (ne[i] > 0 && Te[i] > 0) ? _coulomb_log(ne[i], Te[i]) : 0.0 - nue[i] = Te[i] > 0 ? (zp / _NU_PREFAC) * ne[i] * loglam[i] / (sqrt(_ME / _MP) * (Te[i] / _KEV_J)^1.5) : 0.0 + loglam[i] = _coulomb_log(ne[i], Te[i]) + nue[i] = (zp / _NU_PREFAC) * ne[i] * loglam[i] / (sqrt(_ME / _MP) * _collision_tkev(Te[i])^1.5) end # A negative impurity density means the main ions over-neutralize (Σ z_s n_s > n_e) — unphysical. - _assert_nonneg_density(source, "impurity density (n_e < Σ z_s n_s)", n_imp, psi_reg) + _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. maximum(zeff) > 0.9 * zimp && @warn "max(Zeff) = $(maximum(zeff)) is within 10% of zimp = $zimp — zpitch closure near its pole; check ion_species densities/fractions" # 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) = [Ti[i] > 0 ? (zpitch[i] / _NU_PREFAC) * zsp^2 * n_main[i] * loglam[i] / (sqrt(Float64(msp)) * (Ti[i] / _KEV_J)^1.5) : 0.0 for i in 1:npts] + _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] _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. @@ -383,6 +389,8 @@ function kinetic_profile_splines(data::KineticProfileData; Te_input_eV = _need(data.T_e, "T_e") omegaE_input = _need(data.omega_E, "omega_E") ni_input = data.n_i === nothing ? copy(ne_input) : data.n_i + _assert_nonneg_profile(_describe(data), "T_i", Ti_input_eV, psi_input) + _assert_nonneg_profile(_describe(data), "T_e", Te_input_eV, psi_input) nkin = _NKIN psi_reg = collect(0:nkin) ./ nkin @@ -453,10 +461,8 @@ function kinetic_profile_splines(data::KineticProfileData; loglam[i] = ll # Krook ν (Logan & Park 2013 Eq. 6) with a test-particle z²; the single-species limit of resolve_ntv_species. - nui[i] = T_i > 0 ? - (zpitch / _NU_PREFAC) * zi^2 * n_i * ll / (sqrt(1.0 * mi) * (T_i / _KEV_J)^1.5) : 0.0 - nue[i] = T_e > 0 ? - (zpitch / _NU_PREFAC) * n_e * ll / (sqrt(_ME / _MP) * (T_e / _KEV_J)^1.5) : 0.0 + nui[i] = (zpitch / _NU_PREFAC) * zi^2 * n_i * ll / (sqrt(1.0 * mi) * _collision_tkev(T_i)^1.5) + nue[i] = (zpitch / _NU_PREFAC) * n_e * ll / (sqrt(_ME / _MP) * _collision_tkev(T_e)^1.5) zeff[i] = z end diff --git a/src/Equilibrium/PlasmaEquilibrium.jl b/src/Equilibrium/PlasmaEquilibrium.jl index fed3b79963..bb90626bcf 100644 --- a/src/Equilibrium/PlasmaEquilibrium.jl +++ b/src/Equilibrium/PlasmaEquilibrium.jl @@ -224,7 +224,8 @@ KineticProfileData(data::KineticProfileData; kwargs...) = Named 1D cubic spline interpolants for kinetic profiles (densities, temperatures, ExB rotation, collisional diagnostics) on the regular kinetic ψ grid, with derivative views of the densities and -temperatures for the diamagnetic frequencies of the NTV kernel. +temperatures for the diamagnetic frequencies of the NTV kernel. The Coulomb logarithm and collision +frequencies are monotone (pchip) interpolants, so a steep cold edge cannot ring below zero. # Fields @@ -237,7 +238,7 @@ temperatures for the diamagnetic frequencies of the NTV kernel. - `zeff_spline`: Effective charge Z_eff - `ni_deriv`, `ne_deriv`, `Ti_deriv`, `Te_deriv`: Derivative views for ψ-derivative access """ -struct KineticProfileSplines{S,D} +struct KineticProfileSplines{S,D,C} xs::Vector{Float64} npts::Int npts_minus_1::Int @@ -246,9 +247,9 @@ struct KineticProfileSplines{S,D} Ti_spline::S Te_spline::S omegaE_spline::S - loglam_spline::S - nui_spline::S - nue_spline::S + loglam_spline::C + nui_spline::C + nue_spline::C zeff_spline::S ni_deriv::D ne_deriv::D @@ -261,7 +262,8 @@ end extrap=ExtendExtrap()) Build the kinetic profile splines from arrays of values defined on the shared -ψ grid `xs`. Uses CubicFit boundary conditions to match `ProfileSplines`. +ψ grid `xs`. Uses CubicFit boundary conditions to match `ProfileSplines`, and pchip for the +Coulomb logarithm and collision frequencies as Fortran PENTRC. Temperatures must already be in Joules. """ function KineticProfileSplines(xs::Vector{Float64}, @@ -278,9 +280,9 @@ function KineticProfileSplines(xs::Vector{Float64}, Ti_spline = cubic_interp(xs, Ti; extrap=extrap) Te_spline = cubic_interp(xs, Te; extrap=extrap) omegaE_spline = cubic_interp(xs, omegaE; extrap=extrap) - loglam_spline = cubic_interp(xs, loglam; extrap=extrap) - nui_spline = cubic_interp(xs, nui; extrap=extrap) - nue_spline = cubic_interp(xs, nue; extrap=extrap) + loglam_spline = pchip_interp(xs, loglam; extrap=extrap) + nui_spline = pchip_interp(xs, nui; extrap=extrap) + nue_spline = pchip_interp(xs, nue; extrap=extrap) zeff_spline = cubic_interp(xs, zeff; extrap=extrap) ni_deriv = deriv1(ni_spline) @@ -288,7 +290,7 @@ function KineticProfileSplines(xs::Vector{Float64}, Ti_deriv = deriv1(Ti_spline) Te_deriv = deriv1(Te_spline) - KineticProfileSplines{typeof(ni_spline),typeof(ni_deriv)}( + KineticProfileSplines{typeof(ni_spline),typeof(ni_deriv),typeof(nui_spline)}( xs, npts, npts - 1, ni_spline, ne_spline, Ti_spline, Te_spline, omegaE_spline, loglam_spline, nui_spline, nue_spline, zeff_spline, diff --git a/test/runtests_multiion.jl b/test/runtests_multiion.jl index 2cf775e6ec..9b6ce045b7 100644 --- a/test/runtests_multiion.jl +++ b/test/runtests_multiion.jl @@ -177,6 +177,24 @@ using HDF5 rm(h5) end + @testset "cold edge keeps the collision model finite and monotone" begin + psi = collect(0.0:0.05:1.0) + cold(a) = @. a * (1 - psi)^2 + data = EQ.KineticProfileData(; psi=psi, n_i=fill(1.0e19, length(psi)), n_e=fill(1.1e19, length(psi)), + T_i=cold(2000.0), T_e=cold(2500.0), omega_E=fill(1.0e4, length(psi))) + fine = 0.0:0.001:1.0 + for kp in (EQ.kinetic_profile_splines(data; zi=1, zimp=6, mi=2, mimp=12), + only(s.profiles for s in EQ.resolve_ntv_species(data, [KF.IonSpecies(; z=1, m=2, fraction=1.0)]; zimp=6, mimp=12) if s.z == 1)) + @test all(>=(1.0), kp.loglam_spline.(fine)) + for nu in (kp.nui_spline, kp.nue_spline) + @test all(v -> isfinite(v) && v > 0, nu.(fine)) + # The ν rise toward the cold edge stays monotone between grid nodes + @test issorted(nu.(0.5:0.001:1.0)) + end + end + @test_throws ErrorException EQ.kinetic_profile_splines(EQ.KineticProfileData(data; T_e=cold(2500.0) .- 1.0)) + end + @testset "combine_species_states" begin # Combined profile = Σ species dT/dψ interpolated onto the union grid; total = Σ total. mk(psi, dt) = ( From 0c038188bfa04664da538aa298f3c8a506be5458 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sun, 1 Nov 2026 17:37:10 -0500 Subject: [PATCH 06/13] Equilibrium - BUGFIX! - Resample tabulated kinetic profiles with monotone pchip fits Match Fortran GPEC 1.6.0, which fits the kinetic table with pchip so a steep edge cannot overshoot between table points. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VdRsGGg4YRG6E9fgLGbpdF --- src/Equilibrium/KineticProfiles.jl | 32 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/Equilibrium/KineticProfiles.jl b/src/Equilibrium/KineticProfiles.jl index 9ee5128327..097171d778 100644 --- a/src/Equilibrium/KineticProfiles.jl +++ b/src/Equilibrium/KineticProfiles.jl @@ -241,11 +241,11 @@ function resolve_ntv_species(data::KineticProfileData, ion_species::AbstractVect nkin = _NKIN psi_reg = collect(0:nkin) ./ nkin - ne = _cubic_resample(psi_in, ne_in, psi_reg) - ni_total = _cubic_resample(psi_in, ni_in, psi_reg) - Ti = _cubic_resample(psi_in, Ti_in, psi_reg) .* _EV_J - Te = _cubic_resample(psi_in, Te_in, psi_reg) .* _EV_J - omegaE = _cubic_resample(psi_in, omE_in, psi_reg) + ne = _pchip_resample(psi_in, ne_in, psi_reg) + ni_total = _pchip_resample(psi_in, ni_in, psi_reg) + Ti = _pchip_resample(psi_in, Ti_in, psi_reg) .* _EV_J + Te = _pchip_resample(psi_in, Te_in, psi_reg) .* _EV_J + omegaE = _pchip_resample(psi_in, omE_in, psi_reg) _assert_nonneg_profile(source, "n_e", ne, psi_reg) _assert_nonneg_profile(source, "n_i (total)", ni_total, psi_reg) @@ -281,7 +281,7 @@ function resolve_ntv_species(data::KineticProfileData, ion_species::AbstractVect "ion_species[$si]: density profile `$(s.density)` not found in the kinetic profiles " * "(available: $(data.species_densities === nothing ? "none — ASCII files carry only n_i" : join(keys(data.species_densities), ", ")))" ) - ns[si] = _cubic_resample(psi_in, data.species_densities[s.density], psi_reg) + ns[si] = _pchip_resample(psi_in, data.species_densities[s.density], psi_reg) else ns[si] = s.fraction .* ni_total end @@ -395,12 +395,12 @@ function kinetic_profile_splines(data::KineticProfileData; nkin = _NKIN psi_reg = collect(0:nkin) ./ nkin - # Cubic-spline resample as in Fortran PENTRC; linear interpolation misplaces ω_E zero crossings and so the resonance. - ni = _cubic_resample(psi_input, ni_input, psi_reg) - ne = _cubic_resample(psi_input, ne_input, psi_reg) - Ti = _cubic_resample(psi_input, Ti_input_eV, psi_reg) .* _EV_J - Te = _cubic_resample(psi_input, Te_input_eV, psi_reg) .* _EV_J - omegaE = _cubic_resample(psi_input, omegaE_input, psi_reg) + # Monotone resample as in Fortran PENTRC; linear interpolation misplaces ω_E zero crossings and so the resonance. + ni = _pchip_resample(psi_input, ni_input, psi_reg) + ne = _pchip_resample(psi_input, ne_input, psi_reg) + Ti = _pchip_resample(psi_input, Ti_input_eV, psi_reg) .* _EV_J + Te = _pchip_resample(psi_input, Te_input_eV, psi_reg) .* _EV_J + omegaE = _pchip_resample(psi_input, omegaE_input, psi_reg) 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 @@ -494,11 +494,11 @@ function _read_kinetic_table(kinetic_file::AbstractString) end """ -Cubic spline of `y(x)` on the irregular input grid, evaluated at `x_new` with smooth cubic -extrapolation, as Fortran PENTRC `spline_fit(..., "extrap")`. +Monotone (pchip) fit of `y(x)` on the irregular input grid, evaluated at `x_new` with +extrapolation, as Fortran PENTRC `spline_fit_pchip`: no overshoot between table points. """ -function _cubic_resample(x::AbstractVector, y::AbstractVector, x_new::AbstractVector) - spl = cubic_interp(collect(Float64, x), collect(Float64, y); extrap=ExtendExtrap()) +function _pchip_resample(x::AbstractVector, y::AbstractVector, x_new::AbstractVector) + spl = pchip_interp(collect(Float64, x), collect(Float64, y); extrap=ExtendExtrap()) return [spl(xv) for xv in x_new] end From e184c7500c1129ac003d66919e3e7677b97bd92e Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sun, 1 Nov 2026 18:47:12 -0500 Subject: [PATCH 07/13] Docs - DOCS - Point the bounce and precession frequency convention at BounceAveraging Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VdRsGGg4YRG6E9fgLGbpdF --- docs/src/conventions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/conventions.md b/docs/src/conventions.md index f0a97b2777..154afed892 100644 --- a/docs/src/conventions.md +++ b/docs/src/conventions.md @@ -379,7 +379,7 @@ Here: - ``\omega_b`` and ``\omega_D`` are the bounce and magnetic-precession frequency **coefficients at thermal energy** (``x = 1``): ``\omega_b \propto v_\mathrm{th}`` and - ``\omega_D \propto v_\mathrm{th}^2`` (`wbhat`, `wdhat` in `Torque.jl`). Their velocity-space + ``\omega_D \propto v_\mathrm{th}^2`` (`wbbar`, `wdbar` times `bhat`, `dhat` in `BounceAveraging.jl`). Their velocity-space dependence enters explicitly as ``\sqrt{x}`` (bounce ``\propto v``) and ``x`` (precession ``\propto E``). - ``\ell_\mathrm{eff}`` is the effective bounce harmonic (`PitchIntegration.jl`): From 5abf20f1bc3c4196097d904499114ca4ed2b40f2 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sun, 1 Nov 2026 19:22:36 -0500 Subject: [PATCH 08/13] Equilibrium - BUGFIX - Check kinetic densities and temperatures after resampling and warn when a collision floor engages Densities must be positive and temperatures non-negative on the resampled grid, as Fortran PENTRC checks them; a zero density used to give an infinite collision rate. The decks' newq0 comments now match the rejection of negative values. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01VdRsGGg4YRG6E9fgLGbpdF --- examples/DIIID-like_SLAYER_example/gpec.toml | 2 +- .../DIIID-like_error_field_example/gpec.toml | 2 +- .../gpec.toml | 2 +- .../gpec.toml | 2 +- examples/DIIID-like_ideal_example/gpec.toml | 2 +- .../gpec.toml | 2 +- .../gpec.toml | 2 +- .../DIIID-like_truncation_example/gpec.toml | 2 +- examples/Solovev_ideal_example/gpec.toml | 2 +- examples/Solovev_ideal_example_3D/gpec.toml | 2 +- .../Solovev_ideal_example_multi_n/gpec.toml | 2 +- .../single_n_1/gpec.toml | 2 +- .../single_n_2/gpec.toml | 2 +- .../Solovev_kinetic_NTV_example/gpec.toml | 2 +- .../gpec.toml | 2 +- .../gpec.toml | 2 +- examples/a10_kinetic_example/gpec.toml | 2 +- src/Equilibrium/KineticProfiles.jl | 40 ++++++++++++++----- src/Equilibrium/PlasmaEquilibrium.jl | 2 +- test/runtests_kinetic.jl | 1 + test/runtests_multiion.jl | 3 ++ .../gpec.toml | 2 +- .../gpec.toml | 2 +- .../gpec.toml | 2 +- .../gpec.toml | 2 +- 25 files changed, 56 insertions(+), 32 deletions(-) diff --git a/examples/DIIID-like_SLAYER_example/gpec.toml b/examples/DIIID-like_SLAYER_example/gpec.toml index 2ad60ab72d..fa41a0dd50 100644 --- a/examples/DIIID-like_SLAYER_example/gpec.toml +++ b/examples/DIIID-like_SLAYER_example/gpec.toml @@ -18,7 +18,7 @@ psihigh = 0.9995 # Upper limit of normalized flux coordinate mpsi = 0 # Number of radial grid points (0 = auto-compute from psi_accuracy) psi_accuracy = 0.001 # Target absolute error in q for auto-mpsi mtheta = 256 # Number of poloidal grid points -newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) +newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value; q is positive, so negative values are rejected) flux_surface_rtol = 1e-10 # Relative tolerance of the ODE solves that trace each flux surface # Presence runs the LocalStability stage (Mercier D_I, resistive D_R, ballooning Δ', first α boundary); keys are the LocalStabilityProblem settings, numerics in [Ballooning] diff --git a/examples/DIIID-like_error_field_example/gpec.toml b/examples/DIIID-like_error_field_example/gpec.toml index c7738d769b..070ffa8172 100644 --- a/examples/DIIID-like_error_field_example/gpec.toml +++ b/examples/DIIID-like_error_field_example/gpec.toml @@ -22,7 +22,7 @@ psihigh = 0.995 # Upper limit of normalized poloidal flux (captur mpsi = 0 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) psi_accuracy = 0.001 # Target relative accuracy of splined profile derivatives for the auto grid mtheta = 256 # Number of poloidal grid points -newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) +newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value; q is positive, so negative values are rejected) flux_surface_rtol = 1e-10 # Relative tolerance of the ODE solves that trace each flux surface impurity_charge = 6 # Impurity charge (carbon) impurity_mass = 12 # Impurity mass [proton masses] diff --git a/examples/DIIID-like_gal_resistive_example/gpec.toml b/examples/DIIID-like_gal_resistive_example/gpec.toml index 12d3c8e646..19fc8cfa2f 100644 --- a/examples/DIIID-like_gal_resistive_example/gpec.toml +++ b/examples/DIIID-like_gal_resistive_example/gpec.toml @@ -17,7 +17,7 @@ psihigh = 0.993 # Upper limit of normalized flux coordinate (0.99 mpsi = 128 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) psi_accuracy = 0.001 # Target relative accuracy of splined profile derivatives for the auto grid mtheta = 256 # Number of poloidal grid points -newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) +newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value; q is positive, so negative values are rejected) flux_surface_rtol = 1e-10 # Relative tolerance of the ODE solves that trace each flux surface # Presence runs the LocalStability stage (Mercier D_I, resistive D_R, ballooning Δ', first α boundary); keys are the LocalStabilityProblem settings, numerics in [Ballooning] diff --git a/examples/DIIID-like_gal_resistive_pe_example/gpec.toml b/examples/DIIID-like_gal_resistive_pe_example/gpec.toml index 73c500c5ce..d9d48d1c98 100644 --- a/examples/DIIID-like_gal_resistive_pe_example/gpec.toml +++ b/examples/DIIID-like_gal_resistive_pe_example/gpec.toml @@ -18,7 +18,7 @@ psihigh = 0.993 # Upper limit of normalized flux coordinate (0.99 mpsi = 128 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) psi_accuracy = 0.001 # Target relative accuracy of splined profile derivatives for the auto grid mtheta = 256 # Number of poloidal grid points -newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) +newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value; q is positive, so negative values are rejected) flux_surface_rtol = 1e-10 # Relative tolerance of the ODE solves that trace each flux surface # Presence runs the LocalStability stage (Mercier D_I, resistive D_R, ballooning Δ', first α boundary); keys are the LocalStabilityProblem settings, numerics in [Ballooning] diff --git a/examples/DIIID-like_ideal_example/gpec.toml b/examples/DIIID-like_ideal_example/gpec.toml index 56705328d9..f7feb30261 100644 --- a/examples/DIIID-like_ideal_example/gpec.toml +++ b/examples/DIIID-like_ideal_example/gpec.toml @@ -17,7 +17,7 @@ psihigh = 0.995 # Upper limit of normalized poloidal flux (captur mpsi = 0 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) psi_accuracy = 0.001 # Target relative accuracy of splined profile derivatives for the auto grid mtheta = 256 # Number of poloidal grid points -newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) +newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value; q is positive, so negative values are rejected) flux_surface_rtol = 1e-10 # Relative tolerance of the ODE solves that trace each flux surface impurity_charge = 6 # Impurity charge (carbon) impurity_mass = 12 # Impurity mass [proton masses] diff --git a/examples/DIIID-like_ideal_multi_n_example/gpec.toml b/examples/DIIID-like_ideal_multi_n_example/gpec.toml index 8db24007c8..c6bfa903ee 100644 --- a/examples/DIIID-like_ideal_multi_n_example/gpec.toml +++ b/examples/DIIID-like_ideal_multi_n_example/gpec.toml @@ -17,7 +17,7 @@ psilow = 1e-4 # Lower limit of normalized poloidal flux psihigh = 0.9 # Upper limit of normalized poloidal flux mpsi = 128 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) mtheta = 256 # Number of poloidal grid points -newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) +newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value; q is positive, so negative values are rejected) flux_surface_rtol = 1e-10 # Relative tolerance of the ODE solves that trace each flux surface # Presence runs the LocalStability stage (Mercier D_I, resistive D_R, ballooning Δ', first α boundary); keys are the LocalStabilityProblem settings, numerics in [Ballooning] diff --git a/examples/DIIID-like_riccati_deltaprime_example/gpec.toml b/examples/DIIID-like_riccati_deltaprime_example/gpec.toml index 77f4960279..31457af0d4 100644 --- a/examples/DIIID-like_riccati_deltaprime_example/gpec.toml +++ b/examples/DIIID-like_riccati_deltaprime_example/gpec.toml @@ -17,7 +17,7 @@ psihigh = 0.995 # Upper limit of normalized poloidal flux (captur mpsi = 0 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) psi_accuracy = 0.001 # Target relative accuracy of splined profile derivatives for the auto grid mtheta = 256 # Number of poloidal grid points -newq0 = 0 # Override for on-axis safety factor (0 = use input value) +newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value; q is positive, so negative values are rejected) flux_surface_rtol = 1e-10 # Relative tolerance of the ODE solves that trace each flux surface # Presence runs the LocalStability stage (Mercier D_I, resistive D_R, ballooning Δ', first α boundary); keys are the LocalStabilityProblem settings, numerics in [Ballooning] diff --git a/examples/DIIID-like_truncation_example/gpec.toml b/examples/DIIID-like_truncation_example/gpec.toml index 90c47c52e5..eb6539369c 100644 --- a/examples/DIIID-like_truncation_example/gpec.toml +++ b/examples/DIIID-like_truncation_example/gpec.toml @@ -20,7 +20,7 @@ psihigh = 0.9999 # Upper limit of normalized poloidal flux (past t mpsi = 0 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) psi_accuracy = 0.001 # Target relative accuracy of splined profile derivatives for the auto grid mtheta = 256 # Number of poloidal grid points -newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) +newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value; q is positive, so negative values are rejected) flux_surface_rtol = 1e-10 # Relative tolerance of the ODE solves that trace each flux surface [ForceFreeStates] diff --git a/examples/Solovev_ideal_example/gpec.toml b/examples/Solovev_ideal_example/gpec.toml index 330f1bba69..75860e752e 100644 --- a/examples/Solovev_ideal_example/gpec.toml +++ b/examples/Solovev_ideal_example/gpec.toml @@ -13,7 +13,7 @@ psilow = 1e-4 # Lower limit of normalized poloidal flu psihigh = 0.9995 # Upper limit of normalized poloidal flux mpsi = 128 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) mtheta = 256 # Number of poloidal grid points -newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) +newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value; q is positive, so negative values are rejected) flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface [Wall] diff --git a/examples/Solovev_ideal_example_3D/gpec.toml b/examples/Solovev_ideal_example_3D/gpec.toml index 8eeab5d6f2..a7b68e8d62 100644 --- a/examples/Solovev_ideal_example_3D/gpec.toml +++ b/examples/Solovev_ideal_example_3D/gpec.toml @@ -12,7 +12,7 @@ psilow = 1e-4 # Lower limit of normalized poloidal flu psihigh = 0.9995 # Upper limit of normalized poloidal flux mpsi = 128 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) mtheta = 256 # Number of poloidal grid points -newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) +newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value; q is positive, so negative values are rejected) flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface # Presence runs the LocalStability stage (Mercier D_I, resistive D_R, ballooning Δ', first α boundary); keys are the LocalStabilityProblem settings, numerics in [Ballooning] diff --git a/examples/Solovev_ideal_example_multi_n/gpec.toml b/examples/Solovev_ideal_example_multi_n/gpec.toml index 0509be3445..c8e51b8c78 100644 --- a/examples/Solovev_ideal_example_multi_n/gpec.toml +++ b/examples/Solovev_ideal_example_multi_n/gpec.toml @@ -13,7 +13,7 @@ psilow = 1e-4 # Lower limit of normalized poloidal flu psihigh = 0.9995 # Upper limit of normalized poloidal flux mpsi = 128 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) mtheta = 256 # Number of poloidal grid points -newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) +newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value; q is positive, so negative values are rejected) flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface [Wall] diff --git a/examples/Solovev_ideal_example_multi_n/single_n_1/gpec.toml b/examples/Solovev_ideal_example_multi_n/single_n_1/gpec.toml index af79fd46ff..0c272a9a0a 100644 --- a/examples/Solovev_ideal_example_multi_n/single_n_1/gpec.toml +++ b/examples/Solovev_ideal_example_multi_n/single_n_1/gpec.toml @@ -13,7 +13,7 @@ psilow = 1e-4 # Lower limit of normalized poloidal flu psihigh = 0.9995 # Upper limit of normalized poloidal flux mpsi = 128 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) mtheta = 256 # Number of poloidal grid points -newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) +newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value; q is positive, so negative values are rejected) flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface [Wall] diff --git a/examples/Solovev_ideal_example_multi_n/single_n_2/gpec.toml b/examples/Solovev_ideal_example_multi_n/single_n_2/gpec.toml index e051a232ea..f200df8c2a 100644 --- a/examples/Solovev_ideal_example_multi_n/single_n_2/gpec.toml +++ b/examples/Solovev_ideal_example_multi_n/single_n_2/gpec.toml @@ -13,7 +13,7 @@ psilow = 1e-4 # Lower limit of normalized poloidal flu psihigh = 0.9995 # Upper limit of normalized poloidal flux mpsi = 128 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) mtheta = 256 # Number of poloidal grid points -newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) +newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value; q is positive, so negative values are rejected) flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface [Wall] diff --git a/examples/Solovev_kinetic_NTV_example/gpec.toml b/examples/Solovev_kinetic_NTV_example/gpec.toml index 6c363ffb55..fca3bc3b85 100644 --- a/examples/Solovev_kinetic_NTV_example/gpec.toml +++ b/examples/Solovev_kinetic_NTV_example/gpec.toml @@ -16,7 +16,7 @@ psilow = 1e-4 # Lower limit of normalized poloidal flu psihigh = 0.9995 # Upper limit of normalized poloidal flux mpsi = 128 # Number of radial grid points (0 = auto-compute from psi_accuracy) mtheta = 256 # Number of poloidal grid points -newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) +newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value; q is positive, so negative values are rejected) flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface [Wall] diff --git a/examples/Solovev_kinetic_calculated_example/gpec.toml b/examples/Solovev_kinetic_calculated_example/gpec.toml index 416c8ee9a8..788b1e0025 100644 --- a/examples/Solovev_kinetic_calculated_example/gpec.toml +++ b/examples/Solovev_kinetic_calculated_example/gpec.toml @@ -18,7 +18,7 @@ psilow = 1e-4 # Lower limit of normalized flux coordin psihigh = 0.9995 # Upper limit of normalized flux coordinate mpsi = 16 # Number of radial grid points mtheta = 256 # Number of poloidal grid points -newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) +newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value; q is positive, so negative values are rejected) flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface [[Equilibrium.ion_species]] diff --git a/examples/Solovev_kinetic_multiion_example/gpec.toml b/examples/Solovev_kinetic_multiion_example/gpec.toml index fc96aa1203..e262b668bc 100644 --- a/examples/Solovev_kinetic_multiion_example/gpec.toml +++ b/examples/Solovev_kinetic_multiion_example/gpec.toml @@ -16,7 +16,7 @@ psilow = 1e-4 # Lower limit of normalized poloidal flu psihigh = 0.9995 # Upper limit of normalized poloidal flux mpsi = 128 # Number of radial grid points (0 = auto-compute from psi_accuracy) mtheta = 256 # Number of poloidal grid points -newq0 = 0 # Override for on-axis safety factor (0 = use input value) +newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value; q is positive, so negative values are rejected) flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface impurity_charge = 6 # Impurity charge (fundamental units); closes quasineutrality impurity_mass = 12 # Impurity mass (proton masses) diff --git a/examples/a10_kinetic_example/gpec.toml b/examples/a10_kinetic_example/gpec.toml index bd48a5331c..24b0c408f1 100644 --- a/examples/a10_kinetic_example/gpec.toml +++ b/examples/a10_kinetic_example/gpec.toml @@ -19,7 +19,7 @@ psilow = 1e-3 # Lower limit of normalized poloidal flux psihigh = 0.99 # Upper limit of normalized poloidal flux mpsi = 16 # Number of radial grid points (low value for fast iteration) mtheta = 256 # Number of poloidal grid points -newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) +newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value; q is positive, so negative values are rejected) flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface impurity_charge = 6 # Impurity charge impurity_mass = 12 # Impurity mass diff --git a/src/Equilibrium/KineticProfiles.jl b/src/Equilibrium/KineticProfiles.jl index 097171d778..98f6db0847 100644 --- a/src/Equilibrium/KineticProfiles.jl +++ b/src/Equilibrium/KineticProfiles.jl @@ -26,18 +26,34 @@ const _T_FLOOR_KEV = 1.0e-6 # collision-model temperature floor (1 meV), as _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) = ne > 0 ? max(17.3 - 0.5 * log(ne / 1.0e20) + 1.5 * log(_collision_tkev(Te)), 1.0) : 1.0 +_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) + 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)) && + @warn "$source: flooring the Coulomb logarithm at 1 in the collision frequencies" maxlog = 1 + return nothing +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))" -# Hard stop on an unphysical negative density or temperature (bad input or a resample overshoot). +# 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) i === nothing || error("$source: negative $label = $(arr[i]) at ψ_n = $(psi_reg[i]) — unphysical") end +# Hard stop on a non-positive electron or main-ion density, which the collision model divides by. +function _assert_positive_density(source, label, arr, psi_reg) + i = findfirst(<=(0), arr) + i === nothing || + error("$source: $label = $(arr[i]) at ψ_n = $(psi_reg[i]) — densities must be positive") +end + """ ResolvedNTVSpecies{P} @@ -236,8 +252,6 @@ function resolve_ntv_species(data::KineticProfileData, ion_species::AbstractVect Te_in = _need(data.T_e, "T_e") omE_in = _need(data.omega_E, "omega_E") ni_in = data.n_i === nothing ? copy(ne_in) : data.n_i # TOTAL main-ion density - _assert_nonneg_profile(source, "T_i", Ti_in, psi_in) - _assert_nonneg_profile(source, "T_e", Te_in, psi_in) nkin = _NKIN psi_reg = collect(0:nkin) ./ nkin @@ -246,8 +260,11 @@ function resolve_ntv_species(data::KineticProfileData, ion_species::AbstractVect Ti = _pchip_resample(psi_in, Ti_in, psi_reg) .* _EV_J Te = _pchip_resample(psi_in, Te_in, psi_reg) .* _EV_J omegaE = _pchip_resample(psi_in, omE_in, psi_reg) - _assert_nonneg_profile(source, "n_e", ne, psi_reg) - _assert_nonneg_profile(source, "n_i (total)", ni_total, psi_reg) + _assert_positive_density(source, "n_e", ne, psi_reg) + _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) # 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)] @@ -349,7 +366,7 @@ end The NTV view of raw kinetic profiles (e.g. `equil.kinetic`): interpolate onto a regular 101-point ψ grid, optionally apply profile scaling knobs, derive collisional / Z_eff -diagnostics, and return a `KineticProfileSplines` with independent named cubic splines. +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. @@ -389,8 +406,6 @@ function kinetic_profile_splines(data::KineticProfileData; Te_input_eV = _need(data.T_e, "T_e") omegaE_input = _need(data.omega_E, "omega_E") ni_input = data.n_i === nothing ? copy(ne_input) : data.n_i - _assert_nonneg_profile(_describe(data), "T_i", Ti_input_eV, psi_input) - _assert_nonneg_profile(_describe(data), "T_e", Te_input_eV, psi_input) nkin = _NKIN psi_reg = collect(0:nkin) ./ nkin @@ -401,6 +416,11 @@ function kinetic_profile_splines(data::KineticProfileData; Ti = _pchip_resample(psi_input, Ti_input_eV, psi_reg) .* _EV_J Te = _pchip_resample(psi_input, Te_input_eV, psi_reg) .* _EV_J omegaE = _pchip_resample(psi_input, omegaE_input, psi_reg) + _assert_positive_density(_describe(data), "n_e", ne, psi_reg) + _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) 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 @@ -454,7 +474,7 @@ function kinetic_profile_splines(data::KineticProfileData; T_i = Ti[i] T_e = Te[i] - z = n_e > 0 ? zimp - (n_i / n_e) * zi * (zimp - zi) : Float64(zimp) + 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) diff --git a/src/Equilibrium/PlasmaEquilibrium.jl b/src/Equilibrium/PlasmaEquilibrium.jl index bb90626bcf..dd1737bf66 100644 --- a/src/Equilibrium/PlasmaEquilibrium.jl +++ b/src/Equilibrium/PlasmaEquilibrium.jl @@ -222,7 +222,7 @@ KineticProfileData(data::KineticProfileData; kwargs...) = """ KineticProfileSplines -Named 1D cubic spline interpolants for kinetic profiles (densities, temperatures, ExB rotation, +Named 1D spline interpolants for kinetic profiles (densities, temperatures, ExB rotation, collisional diagnostics) on the regular kinetic ψ grid, with derivative views of the densities and temperatures for the diamagnetic frequencies of the NTV kernel. The Coulomb logarithm and collision frequencies are monotone (pchip) interpolants, so a steep cold edge cannot ring below zero. diff --git a/test/runtests_kinetic.jl b/test/runtests_kinetic.jl index 325ccbf487..51b4d4fe72 100644 --- a/test/runtests_kinetic.jl +++ b/test/runtests_kinetic.jl @@ -714,6 +714,7 @@ using TOML @test_logs (:warn, r"atol_psi") KF.check_psi_quadrature_convergence(1e-3 + 0.0im, 1e-3, ctrl_atol, "fgar") # A NaN torque fails every tolerance comparison, so it must error instead of passing silently @test_throws ErrorException KF.check_psi_quadrature_convergence(complex(NaN), NaN, ctrl, "fgar") + @test_throws ErrorException KF.check_psi_quadrature_convergence(total, NaN, ctrl, "fgar") end @testset "METHOD_REGISTRY" begin diff --git a/test/runtests_multiion.jl b/test/runtests_multiion.jl index 9b6ce045b7..96172a3247 100644 --- a/test/runtests_multiion.jl +++ b/test/runtests_multiion.jl @@ -193,6 +193,9 @@ using HDF5 end end @test_throws ErrorException EQ.kinetic_profile_splines(EQ.KineticProfileData(data; T_e=cold(2500.0) .- 1.0)) + @test_throws ErrorException EQ.resolve_ntv_species(EQ.KineticProfileData(data; T_i=cold(2000.0) .- 1.0), [KF.IonSpecies(; z=1, m=2, fraction=1.0)]) + # The collision model divides by the densities, so a zero density stops the run as in Fortran PENTRC. + @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 "combine_species_states" begin diff --git a/test/test_data/regression_solovev_ideal_example/gpec.toml b/test/test_data/regression_solovev_ideal_example/gpec.toml index 63eaaf40bc..b2663d180e 100644 --- a/test/test_data/regression_solovev_ideal_example/gpec.toml +++ b/test/test_data/regression_solovev_ideal_example/gpec.toml @@ -9,7 +9,7 @@ psilow = 1e-4 # Lower limit of normalized poloidal flu psihigh = 0.9995 # Upper limit of normalized poloidal flux mpsi = 16 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) mtheta = 256 # Number of poloidal grid points -newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) +newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value; q is positive, so negative values are rejected) flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface [Wall] diff --git a/test/test_data/regression_solovev_ideal_example_multi_n/gpec.toml b/test/test_data/regression_solovev_ideal_example_multi_n/gpec.toml index 7bab16e4ef..0571479748 100644 --- a/test/test_data/regression_solovev_ideal_example_multi_n/gpec.toml +++ b/test/test_data/regression_solovev_ideal_example_multi_n/gpec.toml @@ -9,7 +9,7 @@ psilow = 1e-4 # Lower limit of normalized poloidal flu psihigh = 0.9995 # Upper limit of normalized poloidal flux mpsi = 16 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) mtheta = 256 # Number of poloidal grid points -newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) +newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value; q is positive, so negative values are rejected) flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface [Wall] diff --git a/test/test_data/regression_solovev_kinetic_calculated/gpec.toml b/test/test_data/regression_solovev_kinetic_calculated/gpec.toml index 8769042f13..9285035b38 100644 --- a/test/test_data/regression_solovev_kinetic_calculated/gpec.toml +++ b/test/test_data/regression_solovev_kinetic_calculated/gpec.toml @@ -10,7 +10,7 @@ psilow = 1e-4 # Lower limit of normalized poloidal flu psihigh = 0.9995 # Upper limit of normalized poloidal flux mpsi = 16 # Number of radial grid intervals (0 = two-pass auto grid from psi_accuracy) mtheta = 256 # Number of poloidal grid points -newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) +newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value; q is positive, so negative values are rejected) flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface [Wall] diff --git a/test/test_data/regression_solovev_kinetic_nuzero/gpec.toml b/test/test_data/regression_solovev_kinetic_nuzero/gpec.toml index ac207f8e42..2e8954e36a 100644 --- a/test/test_data/regression_solovev_kinetic_nuzero/gpec.toml +++ b/test/test_data/regression_solovev_kinetic_nuzero/gpec.toml @@ -11,7 +11,7 @@ psilow = 1e-4 # Lower limit of normalized flux coordin psihigh = 0.9995 # Upper limit of normalized flux coordinate mpsi = 16 # Number of radial grid points mtheta = 256 # Number of poloidal grid points -newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value, -1 = flip axis extrapolation) +newq0 = 0 # Target on-axis safety factor q(0) (0 = use input value; q is positive, so negative values are rejected) flux_surface_rtol = 1e-7 # Relative tolerance of the ODE solves that trace each flux surface [Wall] From 09e25641d7d3fa1dcaef8c2d5fb7917c69ce9dc2 Mon Sep 17 00:00:00 2001 From: logan-nc Date: Sun, 1 Nov 2026 19:51:31 -0500 Subject: [PATCH 09/13] 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 | 16 +- test/runtests_vacuum.jl | 23 +- 99 files changed, 2059 insertions(+), 1883 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 2cfef836b9..3a77f7fd87 100644 --- a/src/InnerLayer/SLAYER/LayerInputs.jl +++ b/src/InnerLayer/SLAYER/LayerInputs.jl @@ -395,7 +395,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 a48b88b192..639d4fd8f2 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 χ⊥ @@ -363,8 +365,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) @@ -375,8 +381,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 1f54504473..1d1b5a75d0 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}() @@ -82,7 +82,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 96172a3247..56669e5e82 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 134ae91295..78f35b5d6a 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 @@ -553,9 +556,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 675ea6ff035bea013e7f8ab9cb9674241dd7b2e9 Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Mon, 2 Nov 2026 12:31:01 +0100 Subject: [PATCH 10/13] Equilibrium - BUGFIX! - Rescale F from its axis value under newq0 on the inverse path and require a positive CHEASE q Co-Authored-By: Claude Opus 5.5 --- src/Equilibrium/Axisymmetric/Inverse.jl | 2 +- src/Equilibrium/Sources/Chease.jl | 8 ++++++-- test/runtests_equil.jl | 11 +++++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Equilibrium/Axisymmetric/Inverse.jl b/src/Equilibrium/Axisymmetric/Inverse.jl index 3500c48581..440bee1b04 100644 --- a/src/Equilibrium/Axisymmetric/Inverse.jl +++ b/src/Equilibrium/Axisymmetric/Inverse.jl @@ -285,7 +285,7 @@ function equilibrium_solver(input::InverseRunInput; override_psi_nodes::Union{No q0 = f_sq[1, 4] - f1_sq_lo[4] * sq_xs[1] if newq0 != 0 - f0 = f_sq[1, 2] - f1_sq_lo[2] * sq_xs[1] + f0 = f_sq[1, 1] - f1_sq_lo[1] * sq_xs[1] f0fac = f0^2 * ((newq0 / q0)^2 - 1) q0 = newq0 for ipsi in 0:mpsi diff --git a/src/Equilibrium/Sources/Chease.jl b/src/Equilibrium/Sources/Chease.jl index 734981e641..085532955f 100644 --- a/src/Equilibrium/Sources/Chease.jl +++ b/src/Equilibrium/Sources/Chease.jl @@ -1,3 +1,7 @@ +# CHEASE supplies q directly and GPEC requires q > 0, so a non-positive q stops here, as Fortran's chease reader does. +_chease_q(zq, config) = + minimum(zq) > 0 ? zq : error("CHEASE input $(config.eq_filename) has q = $(minimum(zq)) ≤ 0; GPEC requires q > 0") + """ read_chease_binary(config) -> InverseRunInput @@ -40,7 +44,7 @@ function read_chease_binary(config::EquilibriumConfig) fs = zeros(npsi1, 4) fs[:, 1] .= ztmf fs[:, 2] .= zcppr - fs[:, 3] .= zq + fs[:, 3] .= _chease_q(zq, config) # Pressure from the cumulative integral of the P′ column. itp_pressure = cubic_interp(xs, fs[:, 2]) @@ -205,7 +209,7 @@ function read_chease_ascii(config::EquilibriumConfig) # ztmf equals zq .* zfb (both are F). fs[:, 1] .= ztmf fs[:, 2] .= zcppr # normalized Pressure - fs[:, 3] .= zq # q profile + fs[:, 3] .= _chease_q(zq, config) # q profile # Pressure from the cumulative integral of the P′ column. itp_pressure = cubic_interp(xs, fs[:, 2]) fsi_pressure = FastInterpolations.cumulative_integrate(itp_pressure) diff --git a/test/runtests_equil.jl b/test/runtests_equil.jl index df5706f0d6..8378c52cd5 100644 --- a/test/runtests_equil.jl +++ b/test/runtests_equil.jl @@ -140,6 +140,17 @@ using TOML xs = eq_target.profiles.xs q_axis = eq_target.profiles.q_spline.y[1] - eq_target.profiles.q_deriv(xs[1]; hint=Ref(1)) * xs[1] @test isapprox(q_axis, 1.05; rtol=0.02) + + # The inverse path rescales F to meet the target too. + Eq = GeneralizedPerturbedEquilibrium.Equilibrium + eq_inv = Eq.PlasmaEquilibrium( + Eq.EquilibriumConfig(; eq_type="chease_binary", eq_filename=joinpath(data_dir, "INP1_binary"), + jac_type="boozer", mpsi=16, psilow=0.01, psihigh=0.99, r0_norm=6.8, b0_norm=7.4, newq0=1.5) + ) + xs = eq_inv.profiles.xs + @test isapprox(eq_inv.profiles.q_spline.y[1] - eq_inv.profiles.q_deriv(xs[1]; hint=Ref(1)) * xs[1], 1.5; rtol=0.02) + # CHEASE supplies q directly, so a non-positive q stops the read. + @test_throws r"GPEC requires q > 0" Eq._chease_q([1.2, -0.1], (; eq_filename="INP1")) end @testset "flux_surface_rtol must be positive and finite" begin From e38b2c995fccf109ef6e7d25905a57796811e841 Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Mon, 2 Nov 2026 12:31:01 +0100 Subject: [PATCH 11/13] KineticForces - BUGFIX! - Sort bounce points ascending so the deepest-well search pairs neighbouring roots Co-Authored-By: Claude Opus 5.5 --- src/KineticForces/BounceAveraging.jl | 10 +++--- test/runtests_kinetic.jl | 46 +++++++++++++++++++++++++--- 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/KineticForces/BounceAveraging.jl b/src/KineticForces/BounceAveraging.jl index b63fffc6f8..c8e6975295 100644 --- a/src/KineticForces/BounceAveraging.jl +++ b/src/KineticForces/BounceAveraging.jl @@ -834,8 +834,8 @@ bounce point, so a monotone interval between stationary points of B contains one `bo/λ` lies strictly between its endpoint B values; the crossing is then solved from the cell's cubic, with `hints` carrying each interval's cell over from the previous λ. -Returns roots sorted descending, the order the deepest-well and marginally-trapped logic -downstream assumes; fewer than two roots signals a degenerate λ and sends the caller to +Returns roots sorted ascending, as Fortran `spline_roots`, so the deepest-well search pairs neighbouring +roots; fewer than two roots signals a degenerate λ and sends the caller to the fallback. `hints` is sized `length(bf.theta) + 1`, the extra slot being the far half of the interval that wraps through the seam. """ @@ -870,7 +870,7 @@ function _bounce_points_at_lambda!( push!(bpts, mod(root, 1.0)) end - sort!(bpts; rev=true) + sort!(bpts) return bpts end @@ -885,14 +885,14 @@ function _find_bounce_points_and_grid( ntheta::Int, bf::SurfaceBField, bpts_buf::Vector{Float64}, hints::Vector{Int} ) if sigma == 0 # trapped - # Bounce points: roots of v_par(θ) in (0,1), sorted descending as the well logic below assumes. + # Bounce points: roots of v_par(θ) in (0,1), sorted ascending so neighbours bound one well or barrier. bpts = _bounce_points_at_lambda!(bpts_buf, hints, bf, lmda, bo) if length(bpts) < 2 # Degenerate λ (v_par tangent to zero at an extremum, or < 2 stationary points of B): rare, # so fall back to an adaptive whole-interval root scan. vpar_fn = θ -> _vpar_from_spline(B_vpar, lmda, bo, θ) - bpts = sort!(Roots.find_zeros(vpar_fn, 0.0, 1.0); rev=true) + bpts = sort!(Roots.find_zeros(vpar_fn, 0.0, 1.0)) end nbpts = length(bpts) diff --git a/test/runtests_kinetic.jl b/test/runtests_kinetic.jl index c7f2b5aa8c..2be7e099d7 100644 --- a/test/runtests_kinetic.jl +++ b/test/runtests_kinetic.jl @@ -273,7 +273,7 @@ using TOML for btarget in range(blo, bhi; length=400)[2:end-1] lmda = bo / btarget roots = copy(KF._bounce_points_at_lambda!(buf, hints, bf2, lmda, bo)) - ref = sort!(KF.Roots.find_zeros(θ -> KF._vpar_from_spline(Bv2, lmda, bo, θ), 0.0, 1.0); rev=true) + ref = sort!(KF.Roots.find_zeros(θ -> KF._vpar_from_spline(Bv2, lmda, bo, θ), 0.0, 1.0)) @test length(roots) == length(ref) # The crossing inside the shared cell lies strictly between the two # stationary points that bound it. @@ -295,13 +295,13 @@ using TOML hints = ones(Int, length(bf.theta) + 1) for lmda in range(lmdatpb * (1 + 1e-6), lmdamax * (1 - 1e-6); length=200) new_roots = copy(KF._bounce_points_at_lambda!(buf, hints, bf, lmda, bo)) - ref_roots = sort!(KF.Roots.find_zeros(θ -> KF._vpar_from_spline(B_vpar, lmda, bo, θ), 0.0, 1.0); rev=true) + ref_roots = sort!(KF.Roots.find_zeros(θ -> KF._vpar_from_spline(B_vpar, lmda, bo, θ), 0.0, 1.0)) @test length(new_roots) == length(ref_roots) if length(new_roots) == length(ref_roots) @test all(abs.(new_roots .- ref_roots) .< 1e-9) end - # Roots are returned in the descending order downstream assumes. - @test issorted(new_roots; rev=true) + # Roots are returned ascending, as Fortran spline_roots, so the well search pairs neighbours. + @test issorted(new_roots) # v_par vanishes at each root. for θ in new_roots # Solved directly from the cell's cubic, so v_par vanishes to @@ -311,6 +311,44 @@ using TOML end end + @testset "deepest well of a four-root surface lies between neighbouring roots" begin + # Two B minima give four bounce points at λ between the minima's B values; every well must be a + # neighbouring pair, so the chosen window holds no other root and v_par > 0 at its midpoint. + xs4 = collect(range(0.0, 1.0; length=257)) + B4 = @. (1 + 0.1cos(4pi * xs4 + 1.1)) / (1 + 0.05cos(2pi * xs4)) + B4[end] = B4[1] + Bv4 = cubic_interp(xs4, B4; bc=PeriodicBC()) + bf4 = KF._surface_b_field(Bv4) + buf = Float64[] + hints = ones(Int, length(bf4.theta) + 1) + nfour = 0 + for lmda in range(1 / maximum(B4), 1 / minimum(B4); length=200)[2:end-1] + roots = copy(KF._bounce_points_at_lambda!(buf, hints, bf4, lmda, 1.0)) + length(roots) == 4 || continue + nfour += 1 + t1, t2 = KF._find_deepest_well(roots, Bv4, lmda, 1.0) + @test !any(r -> t1 < r < t2 || t1 < r + 1 < t2, roots) + @test KF._vpar_from_spline(Bv4, lmda, 1.0, mod(0.5 * (t1 + t2), 1.0)) > 0 + end + @test nfour > 0 + end + + @testset "past the midpoint the drift integrand holds its own last value" begin + # v_par turns negative at θ = 0.75: the bounce and drift integrands each hold their own previous sample. + ntheta = 32 + pts = collect(range(0.0, 1.0; length=ntheta)) + wts = fill(1 / (ntheta - 1), ntheta) + tspl = (out, θ) -> (out[1] = 1.0; out[2] = 0.5; out[4] = 1.0; out[5] = 0.3; out) + Bstep = θ -> θ < 0.75 ? 1.0 : 3.0 + scr = KF.BounceScratch(ntheta, 1) + KF._bounce_samples!(zeros(ntheta), zeros(ComplexF64, ntheta), nothing, nothing, pts, wts, 0.5, 1, 1.0, 1.0, tspl, Bstep, 1.0, + [1], [0.0im], [0.0im], 1.0, false, 1, scr) + k = findfirst(>=(0.75), pts) + @test scr.g_wd[end] == scr.g_wd[k-1] != 0 + @test scr.g_wb[end] == scr.g_wb[k-1] + @test scr.g_wd[end] != scr.g_wb[end] + end + @testset "resolves the pair straddling an extremum" begin # Approaching a boundary, the two bounce points collapse onto the extremum # of B from either side. Assert that directly rather than against another From 50a7c8efbc4fffbf962bcd68e17520c6a60077ca Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Mon, 2 Nov 2026 12:31:01 +0100 Subject: [PATCH 12/13] Equilibrium - BUGFIX - Reject non-positive kinetic scaling factors and keep unusable profiles out of the two-pass grid Co-Authored-By: Claude Opus 5.5 --- src/Equilibrium/KineticProfiles.jl | 21 +++++++++++++++---- .../CalculatedKineticMatrices.jl | 9 ++++---- test/runtests_multiion.jl | 15 +++++++++++++ 3 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/Equilibrium/KineticProfiles.jl b/src/Equilibrium/KineticProfiles.jl index 00a3605cfc..dd910df689 100644 --- a/src/Equilibrium/KineticProfiles.jl +++ b/src/Equilibrium/KineticProfiles.jl @@ -28,12 +28,12 @@ _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. +# Warn once per kinetic source, as Fortran PENTRC, when a cold node engages the temperature or Coulomb-logarithm floor. function _warn_collision_floors(source, ne, Te, Ti) 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 + @warn "$source: flooring T at 1 meV in the collision frequencies" maxlog = 1 _id = Symbol("T_floor ", source) 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 + @warn "$source: flooring the Coulomb logarithm at 1 in the collision frequencies" maxlog = 1 _id = Symbol("lnLambda_floor ", source) return nothing end @@ -364,6 +364,16 @@ Whether `data` carries the profiles the NTV view needs (`n_e`, `T_i`, `T_e` and """ has_ntv_fields(data::KineticProfileData) = all(!isnothing, (data.n_e, data.T_i, data.T_e, data.omega_E)) +""" + ntv_profiles_usable(data::KineticProfileData) -> Bool + +Whether `data` has the NTV fields with positive densities and non-negative temperatures at its table points, +the inputs `kinetic_profile_splines` accepts. +""" +ntv_profiles_usable(data::KineticProfileData) = + has_ntv_fields(data) && all(>(0), data.n_e) && (data.n_i === nothing || all(>(0), data.n_i)) && + all(>=(0), data.T_i) && all(>=(0), data.T_e) + """ kinetic_profile_splines(data::KineticProfileData; zi::Int=1, zimp::Int=6, mi::Int=2, mimp::Int=12, @@ -406,6 +416,8 @@ function kinetic_profile_splines(data::KineticProfileData; ExB_rotation_factor::Float64=1.0, toroidal_rotation_factor::Float64=1.0, chi1::Union{Nothing,Float64}=nothing) + density_factor > 0 && temperature_factor > 0 || + throw(ArgumentError("density_factor = $density_factor and temperature_factor = $temperature_factor must be positive")) # n_i defaults to n_e (quasineutrality); omega_tor and the diffusivities are unused here. _need(field, name) = field === nothing ? error("$(_describe(data)) missing required '$name'") : field psi_input = data.psi @@ -428,7 +440,6 @@ 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) 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 @@ -470,6 +481,8 @@ function kinetic_profile_splines(data::KineticProfileData; if ExB_rotation_factor != 1.0 omegaE .*= ExB_rotation_factor end + # After scaling, so a small temperature_factor that engages the floor is reported. + _warn_collision_floors(_describe(data), ne, Te, Ti) loglam = zeros(Float64, nkin + 1) nui = zeros(Float64, nkin + 1) diff --git a/src/KineticForces/CalculatedKineticMatrices.jl b/src/KineticForces/CalculatedKineticMatrices.jl index 710aae592e..7ffea77ce2 100644 --- a/src/KineticForces/CalculatedKineticMatrices.jl +++ b/src/KineticForces/CalculatedKineticMatrices.jl @@ -171,14 +171,15 @@ end # The force-balance solve's two-pass grid inputs: the kinetic profiles as the KineticForces model views them (its # control when the source is one, else the default control) and the kinetic-resonance surfaces to pin. The model -# needs the NTV view, so a missing profile errors; otherwise profiles read for another consumer (e.g. SLAYER without -# omega_E) are left out and the grid follows the equilibrium alone. +# needs the NTV view, so an unusable profile errors; otherwise profiles read for another consumer (e.g. SLAYER without +# omega_E, or with a zero edge density) are left out and the grid follows the equilibrium alone. function ForceFreeStates.kinetic_grid_inputs(source, ctrl, equil, intr; rationals) kf_ctrl = source isa KineticForcesControl ? source : KineticForcesControl() data = equil.kinetic - ntv = source isa KineticForcesControl || (data !== nothing && Equilibrium.has_ntv_fields(data)) + ntv = source isa KineticForcesControl || (data !== nothing && Equilibrium.ntv_profiles_usable(data)) data !== nothing && !ntv && - @info "Two-pass psi grid: the kinetic profiles lack an NTV field (n_e, T_i, T_e, omega_E), so the grid follows the equilibrium alone" + @info "Two-pass psi grid: the kinetic profiles lack an NTV field (n_e, T_i, T_e, omega_E) or have a non-positive density " * + "or negative temperature, so the grid follows the equilibrium alone" kinetic_profiles = ntv ? ntv_profiles(kf_ctrl, equil) : nothing species = ntv ? ntv_species(kf_ctrl, equil) : nothing pinned = resonance_grid_nodes(ctrl, kf_ctrl, kinetic_profiles, species, equil, intr; rationals) diff --git a/test/runtests_multiion.jl b/test/runtests_multiion.jl index 56669e5e82..7a1e62c8c6 100644 --- a/test/runtests_multiion.jl +++ b/test/runtests_multiion.jl @@ -197,6 +197,21 @@ using HDF5 @test_throws ErrorException EQ.resolve_ntv_species(EQ.KineticProfileData(data; T_i=cold(2000.0) .- 1.0), [KF.IonSpecies(; z=1, m=2, fraction=1.0)]) # The collision model divides by the densities, so a zero density stops the run as in Fortran PENTRC. @test_throws r"densities must be positive" EQ.kinetic_profile_splines(EQ.KineticProfileData(data; n_e=[fill(1.1e19, length(psi) - 1); 0.0])) + # ...but only for the NTV view: the two-pass grid leaves such a file out instead of stopping a deck that does not use NTV. + @test EQ.ntv_profiles_usable(data) + @test !EQ.ntv_profiles_usable(EQ.KineticProfileData(data; n_e=[fill(1.1e19, length(psi) - 1); 0.0])) + # Scaling factors must keep the profiles physical. + @test_throws ArgumentError EQ.kinetic_profile_splines(data; temperature_factor=-1.0, chi1=1.0) + @test_throws ArgumentError EQ.kinetic_profile_splines(data; density_factor=0.0, chi1=1.0) + + # A pedestal table: the cubic resample overshoots the top, the monotone one stays within the table's range. + ped_psi = [0.0, 0.3, 0.6, 0.85, 0.9, 0.95, 1.0] + ped_T = [2000.0, 1900.0, 1700.0, 1500.0, 400.0, 100.0, 50.0] + reg = collect(0:100) ./ 100 + @test maximum(EQ.cubic_interp(ped_psi, ped_T; extrap=EQ.ExtendExtrap()).(reg)) > maximum(ped_T) || + minimum(EQ.cubic_interp(ped_psi, ped_T; extrap=EQ.ExtendExtrap()).(reg)) < minimum(ped_T) + resampled = EQ._pchip_resample(ped_psi, ped_T, reg) + @test minimum(ped_T) <= minimum(resampled) && maximum(resampled) <= maximum(ped_T) end @testset "combine_species_states" begin From e0bb4f7c1dac3f778b60d6a188427c6c816b3747 Mon Sep 17 00:00:00 2001 From: Matthew Pharr Date: Mon, 2 Nov 2026 12:31:01 +0100 Subject: [PATCH 13/13] Deck - TEST - Check that the removed torque-method keys stop the parse Co-Authored-By: Claude Opus 5.5 --- test/runtests_solve_api.jl | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/runtests_solve_api.jl b/test/runtests_solve_api.jl index 78f35b5d6a..975a2b57ed 100644 --- a/test/runtests_solve_api.jl +++ b/test/runtests_solve_api.jl @@ -563,6 +563,13 @@ using HDF5 @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")) + # The removed large-aspect-ratio torque methods stop the parse whatever their value. + for key in ("rlar_flag", "clar_flag"), value in (true, false) + mktempdir() do dir + deck_dir = _stage_deck(dir; extra_sections=Dict{String,Any}("KineticForces" => Dict{String,Any}(key => value))) + @test_throws "large-aspect-ratio methods are not ported" GPEC.parse_deck(TOML.parsefile(joinpath(deck_dir, "gpec.toml")), deck_dir) + end + end end @testset "kinetic torque" begin