From a5dea9fedebece5d2902ed54088f0932f01e5165 Mon Sep 17 00:00:00 2001 From: d-burg Date: Fri, 28 Aug 2026 02:07:42 -0400 Subject: [PATCH 1/4] InnerLayer.SLAYER - BUGFIX! - Normalize D by the electron diamagnetic share, not the temperature ratio D_norm used sqrt(tau/(1+tau)) with tau = T_i/T_e. Fitzpatrick defines the ion-sound-radius normalization as D = S^(1/3) iota_e^(1/2) (d_beta/r_s), and iota_e reduces to 1/(1+tau), not tau/(1+tau) -- the same expression with the electron and ion temperatures transposed. The two agree only at T_i = T_e. Use the iota_e that slayer_parameters already computes from omega_*e and omega_*i. That is Fitzpatrick's own definition, iota_e = Q_e/(Q_e - Q_i), and it is strictly more general than either closed form: it carries the ratio of the electron and ion pressure-gradient scale lengths rather than assuming they match. Guard against a non-positive iota_e so a same-sign diamagnetic input raises a clear error instead of a DomainError under the square root. Results move on any deck with T_i != T_e, including the shipped SLAYER deck. Co-Authored-By: Claude Opus 5 --- src/InnerLayer/SLAYER/LayerParameters.jl | 20 ++++++++++++++++++-- test/runtests_slayer_params.jl | 5 +++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/InnerLayer/SLAYER/LayerParameters.jl b/src/InnerLayer/SLAYER/LayerParameters.jl index 9718ad666..f8edd7306 100644 --- a/src/InnerLayer/SLAYER/LayerParameters.jl +++ b/src/InnerLayer/SLAYER/LayerParameters.jl @@ -27,7 +27,7 @@ de-normalization. The parametrization uses `P_perp`, `P_tor`, and | `tau` | T_i / T_e | | `lu` | Lundquist number S = τ_R / τ_H | | `c_beta` | Compressibility √(β_local / (1 + β_local)) | -| `D_norm` | (d_β/r_s) · S^(1/3) · √(τ/(1+τ)) (Fitzpatrick normalized scale) | +| `D_norm` | (d_β/r_s) · S^(1/3) · √ι_e (Fitzpatrick normalized scale) | | `P_perp` | Perpendicular Prandtl number τ_R / τ_⊥ | | `P_tor` | Toroidal-direction Prandtl number τ_R / τ_‖tor | | `Q_e` | Normalized electron diamagnetic: −tauk · ω_*e | @@ -329,6 +329,19 @@ function slayer_parameters(; ) ) iota_e = Q_e / Q_e_minus_Q_i + # D below takes sqrt(iota_e). iota_e lies in (0,1) whenever ω_*e and ω_*i have + # opposite signs — the physical case and the convention stated above. Surface a + # negative value as a clear error rather than a bare DomainError. + iota_e > 0 || + throw( + ArgumentError( + "slayer_parameters: iota_e = Q_e/(Q_e - Q_i) = $iota_e is " * + "not positive, so the ion-sound-radius normalization " * + "D_norm = (d_beta/r_s)·S^(1/3)·sqrt(iota_e) is undefined. " * + "Check the diamagnetic-frequency inputs ω_*e, ω_*i — they " * + "are expected to carry opposite signs." + ) + ) # Plasma beta and compressibility lbeta = (5.0 / 3.0) * MU_0 * n_e * E_CHG * (t_e + t_i) / bt^2 @@ -342,7 +355,10 @@ function slayer_parameters(; # Normalized beta-related width and Δ-normalization d_beta = c_beta * d_i - D_norm = (d_beta / rs) * lu^(1.0 / 3.0) * sqrt(tau / (1.0 + tau)) + # D = S^(1/3)·iota_e^(1/2)·(d_beta/r_s). iota_e is the electron share of the + # total diamagnetic frequency; it reduces to 1/(1+tau) only when the electron + # and ion pressure-gradient scale lengths match, so use the computed value. + D_norm = (d_beta / rs) * lu^(1.0 / 3.0) * sqrt(iota_e) delta_n = lu^(1.0 / 3.0) / rs # Critical-Δ offset from chi_parallel matching diff --git a/test/runtests_slayer_params.jl b/test/runtests_slayer_params.jl index 330ba7297..c6f42a531 100644 --- a/test/runtests_slayer_params.jl +++ b/test/runtests_slayer_params.jl @@ -83,8 +83,9 @@ @test p.P_perp ≈ p.P_tor @test p.P_perp > 0 - # D_norm = (d_β/r_s)·S^(1/3)·√(τ/(1+τ)) - D_norm_expected = (p.d_beta / p.rs) * p.lu^(1 / 3) * sqrt(p.tau / (1 + p.tau)) + # D_norm = (d_β/r_s)·S^(1/3)·√ι_e — the electron share of the total + # diamagnetic frequency, not the temperature ratio. + D_norm_expected = (p.d_beta / p.rs) * p.lu^(1 / 3) * sqrt(p.iota_e) @test p.D_norm ≈ D_norm_expected rtol = 1e-12 # delta_n = S^(1/3)/r_s From 8df7535e3c8b6d487f223167ef838ca881602c1b Mon Sep 17 00:00:00 2001 From: d-burg Date: Fri, 28 Aug 2026 13:23:33 -0400 Subject: [PATCH 2/4] InnerLayer.SLAYER - MINOR - Deduplicate the iota_e normalization commentary Co-Authored-By: Claude Fable 5 --- src/InnerLayer/SLAYER/LayerParameters.jl | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/InnerLayer/SLAYER/LayerParameters.jl b/src/InnerLayer/SLAYER/LayerParameters.jl index f8edd7306..15bc6aac1 100644 --- a/src/InnerLayer/SLAYER/LayerParameters.jl +++ b/src/InnerLayer/SLAYER/LayerParameters.jl @@ -355,9 +355,8 @@ function slayer_parameters(; # Normalized beta-related width and Δ-normalization d_beta = c_beta * d_i - # D = S^(1/3)·iota_e^(1/2)·(d_beta/r_s). iota_e is the electron share of the - # total diamagnetic frequency; it reduces to 1/(1+tau) only when the electron - # and ion pressure-gradient scale lengths match, so use the computed value. + # iota_e is the electron share of the total diamagnetic frequency; it reduces to + # 1/(1+tau) only when the electron and ion pressure-gradient scale lengths match. D_norm = (d_beta / rs) * lu^(1.0 / 3.0) * sqrt(iota_e) delta_n = lu^(1.0 / 3.0) / rs From 9b44f8cfc6b8e34002a4c952ccce2148c47efcc5 Mon Sep 17 00:00:00 2001 From: d-burg Date: Fri, 28 Aug 2026 14:40:20 -0400 Subject: [PATCH 3/4] InnerLayer.SLAYER - MINOR - Restore the D-normalization formula citation Put the explicit D = S^(1/3)*iota_e^(1/2)*(d_beta/r_s) statement back above the code so the formula-to-theory traceability survives next to what it computes. Co-Authored-By: Claude Opus 5 --- src/InnerLayer/SLAYER/LayerParameters.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/InnerLayer/SLAYER/LayerParameters.jl b/src/InnerLayer/SLAYER/LayerParameters.jl index 15bc6aac1..ea707e17c 100644 --- a/src/InnerLayer/SLAYER/LayerParameters.jl +++ b/src/InnerLayer/SLAYER/LayerParameters.jl @@ -355,6 +355,7 @@ function slayer_parameters(; # Normalized beta-related width and Δ-normalization d_beta = c_beta * d_i + # D = S^(1/3)·iota_e^(1/2)·(d_beta/r_s), Fitzpatrick's ion-sound-radius normalization. # iota_e is the electron share of the total diamagnetic frequency; it reduces to # 1/(1+tau) only when the electron and ion pressure-gradient scale lengths match. D_norm = (d_beta / rs) * lu^(1.0 / 3.0) * sqrt(iota_e) From e32740a80895f6ba13e3835e92fbaef5501260dc Mon Sep 17 00:00:00 2001 From: d-burg Date: Fri, 28 Aug 2026 16:14:45 -0400 Subject: [PATCH 4/4] InnerLayer.SLAYER - TEST - Give the shared fixtures the physical opposite-drift diamagnetic signs The shared test inputs set omega_e and omega_i both positive, which no plasma produces: electrons and ions drift oppositely, the pipeline computes omega_*e < 0 < omega_*i (all six shipped-deck surfaces have Q_e > 0 > Q_i, matching TJ), and same-sign inputs put iota_e = 2 outside its physical (0, 1) range. Flip omega_e to -1e4 so iota_e = 2/3, matching the hand-built small-D fixture, and update the two assertions that encoded the old values. Branch selection cannot move: iota_e appears on both sides of the regime test and cancels. Co-Authored-By: Claude Opus 5 --- test/runtests_dispersion_coupled.jl | 2 +- test/runtests_dispersion_residual.jl | 2 +- test/runtests_slayer_params.jl | 14 +++++++------- test/runtests_slayer_riccati.jl | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/test/runtests_dispersion_coupled.jl b/test/runtests_dispersion_coupled.jl index 5a65539ff..1b950016e 100644 --- a/test/runtests_dispersion_coupled.jl +++ b/test/runtests_dispersion_coupled.jl @@ -21,7 +21,7 @@ function _slayer_ref() return slayer_parameters( n_e=5.0e19, t_e=1000.0, t_i=1000.0, - omega=0.0, omega_e=1.0e4, omega_i=5.0e3, + omega=0.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=1.0, chi_tor=1.0, m=2, n=1) diff --git a/test/runtests_dispersion_residual.jl b/test/runtests_dispersion_residual.jl index d0235a0da..e8af06da4 100644 --- a/test/runtests_dispersion_residual.jl +++ b/test/runtests_dispersion_residual.jl @@ -21,7 +21,7 @@ function _slayer_ref() return slayer_parameters( n_e=5.0e19, t_e=1000.0, t_i=1000.0, - omega=0.0, omega_e=1.0e4, omega_i=5.0e3, + omega=0.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=1.0, chi_tor=1.0, m=2, n=1) diff --git a/test/runtests_slayer_params.jl b/test/runtests_slayer_params.jl index c6f42a531..397bee29e 100644 --- a/test/runtests_slayer_params.jl +++ b/test/runtests_slayer_params.jl @@ -9,7 +9,7 @@ function _ref_kwargs(; dr_val=0.0, dc_type=:none) return ( n_e=5.0e19, t_e=1000.0, t_i=1000.0, - omega=0.0, omega_e=1.0e4, omega_i=5.0e3, + omega=0.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=1.0, chi_tor=1.0, @@ -36,13 +36,13 @@ # Trivially exact ratios @test p.tau ≈ 1.0 - # Q_e = −tauk·1e4 = negative; Q_i = −tauk·5e3 = negative - # Q_e − Q_i = −tauk·5e3 = Q_i (since Q_e = 2·Q_i) ⇒ iota_e = Q_e/Q_i = 2 - @test p.iota_e ≈ 2.0 + # Physical opposite-drift inputs: ω_*e < 0 < ω_*i, so Q = −tauk·ω gives + # Q_e > 0 > Q_i and iota_e = Q_e/(Q_e − Q_i) = 1e4/(1e4 + 5e3) = 2/3 + @test p.iota_e ≈ 2 / 3 - # Sign convention check (SLAYER layerinputs) - @test p.Q_e == -p.tauk * 1.0e4 - @test p.Q_i == -p.tauk * 5.0e3 # SLAYER params convention: Q_i = −tauk·ω*i + # Sign convention check (SLAYER layerinputs): Q = −tauk·ω + @test p.Q_e == p.tauk * 1.0e4 + @test p.Q_i == -p.tauk * 5.0e3 # Default resistivity closure is neoclassical (Sauter F_33). The # trapped-particle correction raises η above plain Spitzer at the diff --git a/test/runtests_slayer_riccati.jl b/test/runtests_slayer_riccati.jl index 22f7fc549..9d4a95462 100644 --- a/test/runtests_slayer_riccati.jl +++ b/test/runtests_slayer_riccati.jl @@ -15,7 +15,7 @@ function _ref_params_large_D() return slayer_parameters(; n_e=5.0e19, t_e=3000.0, t_i=3000.0, - omega=0.0, omega_e=1.0e4, omega_i=5.0e3, + omega=0.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=1.0, chi_tor=1.0,