-
Notifications
You must be signed in to change notification settings - Fork 7
InnerLayer.SLAYER - BUGFIX - Restore the toroidal mode number in the diamagnetic frequencies #433
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
53fffc0
28a0425
8ae06cd
21fd724
4c70d42
00621d5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,173 @@ | ||
| # Regression case: DIII-D-like H-mode n=2 SLAYER tearing-mode growth rates. | ||
| # Reuses the n=1 SLAYER deck via [overrides] at toroidal mode number 2, where the deck's | ||
| # qmin = 1.204 puts the 3/2 surface in the plasma. Exists to guard the n-dependence of the | ||
| # layer build — in particular the diamagnetic frequencies ω_* ∝ n feeding Q_e/Q_i, which | ||
| # no n=1 case can constrain — with the inner rationals 3/2, 4/2, 5/2 as the tracked roots. | ||
| # Each [quantities.*] block names an HDF5 path in the run output, how to extract it, | ||
| # and the noise floor below which a difference is treated as zero. | ||
| [case] | ||
| name = "diiid_slayer_n2" | ||
| description = "DIII-D-like H-mode equilibrium, n=2, SLAYER tearing-mode analysis guarding the 3/2 surface and the n-scaling of Q_e/Q_i (reuses the n=1 deck via overrides)" | ||
| example_dir = "examples/DIIID-like_SLAYER_example" | ||
|
|
||
| # Run the shared SLAYER deck at n = 2 (no separate n=2 example directory). | ||
| [overrides] | ||
| "ForceFreeStates.nn_low" = 2 | ||
| "ForceFreeStates.nn_high" = 2 | ||
|
|
||
| # Per-surface SLAYER layer parameters (geometry + dimensionless) | ||
| [quantities.slayer_ising] | ||
| h5path = "Tearing/PerSurface/rational_index" | ||
| type = "real_vector" | ||
| extract = "all_real" | ||
| label = "SLAYER surface indices" | ||
| noise_threshold = 0 | ||
| order = 10 | ||
|
|
||
| [quantities.slayer_m] | ||
| h5path = "Tearing/PerSurface/m" | ||
| type = "real_vector" | ||
| extract = "all_real" | ||
| label = "SLAYER poloidal m" | ||
| noise_threshold = 0 | ||
| order = 11 | ||
|
|
||
| [quantities.slayer_n] | ||
| h5path = "Tearing/PerSurface/n" | ||
| type = "real_vector" | ||
| extract = "all_real" | ||
| label = "SLAYER toroidal n" | ||
| noise_threshold = 0 | ||
| order = 12 | ||
|
|
||
| [quantities.slayer_rs] | ||
| h5path = "Tearing/PerSurface/rs" | ||
| type = "real_vector" | ||
| extract = "all_real" | ||
| label = "SLAYER minor radius rs" | ||
| noise_threshold = 1e-10 | ||
| order = 13 | ||
|
|
||
| [quantities.slayer_sval_r] | ||
| h5path = "Tearing/PerSurface/sval_r" | ||
| type = "real_vector" | ||
| extract = "all_real" | ||
| label = "SLAYER r-based shear" | ||
| noise_threshold = 1e-10 | ||
| order = 14 | ||
|
|
||
| [quantities.slayer_lu] | ||
| h5path = "Tearing/PerSurface/lu" | ||
| type = "real_vector" | ||
| extract = "all_real" | ||
| label = "SLAYER Lundquist S" | ||
| noise_threshold = 1e-8 | ||
| order = 15 | ||
|
|
||
| [quantities.slayer_D_norm] | ||
| h5path = "Tearing/PerSurface/D_norm" | ||
| type = "real_vector" | ||
| extract = "all_real" | ||
| label = "SLAYER D_norm" | ||
| noise_threshold = 1e-10 | ||
| order = 16 | ||
|
|
||
| [quantities.slayer_P_perp] | ||
| h5path = "Tearing/PerSurface/P_perp" | ||
| type = "real_vector" | ||
| extract = "all_real" | ||
| label = "SLAYER P_perp" | ||
| noise_threshold = 1e-8 | ||
| order = 17 | ||
|
|
||
| [quantities.slayer_tauk] | ||
| h5path = "Tearing/PerSurface/tau_k" | ||
| type = "real_vector" | ||
| extract = "all_real" | ||
| label = "SLAYER tauk" | ||
| noise_threshold = 1e-12 | ||
| order = 18 | ||
|
|
||
| [quantities.slayer_Q_e] | ||
| h5path = "Tearing/PerSurface/Q_e" | ||
| type = "real_vector" | ||
| extract = "all_real" | ||
| label = "SLAYER Q_e" | ||
| noise_threshold = 1e-12 | ||
| order = 20 | ||
|
|
||
| [quantities.slayer_Q_i] | ||
| h5path = "Tearing/PerSurface/Q_i" | ||
| type = "real_vector" | ||
| extract = "all_real" | ||
| label = "SLAYER Q_i" | ||
| noise_threshold = 1e-12 | ||
| order = 21 | ||
|
|
||
| [quantities.slayer_iota_e] | ||
| h5path = "Tearing/PerSurface/iota_e" | ||
| type = "real_vector" | ||
| extract = "all_real" | ||
| label = "SLAYER iota_e" | ||
| noise_threshold = 1e-12 | ||
| order = 19 | ||
|
|
||
| # Tearing eigenvalue (coupled mode → length 1). The headline deliverable: | ||
| # a real, nonzero growth rate on a realistic equilibrium. Root-extraction | ||
| # is sensitive to the AMR cell topology and ODE solver, so the Q/ω/γ | ||
| # thresholds are absolute and modestly loose; re-pin intentionally if a | ||
| # solver/AMR change shifts the root inventory. | ||
| # Growth-rate / frequency / root outputs. Pinned for the inner three rational | ||
| # surfaces only (3/2, 4/2, 5/2) via `first_3`: the Δ'/γ contour search is | ||
| # numerically unreliable on the outermost surfaces near the edge, so those are | ||
| # deliberately not golden-tracked. | ||
| [quantities.slayer_Q] | ||
| h5path = "Tearing/Roots/Q_root" | ||
| type = "complex_vector" | ||
| extract = "first_3_complex" | ||
| label = "SLAYER Q_root [3/2,4/2,5/2]" | ||
| noise_threshold = 1e-4 | ||
| order = 30 | ||
|
|
||
| [quantities.slayer_omega_Hz] | ||
| h5path = "Tearing/Roots/omega" | ||
| type = "real_vector" | ||
| extract = "first_3" | ||
| label = "SLAYER ω_Hz [3/2,4/2,5/2]" | ||
| noise_threshold = 1.0 | ||
| order = 32 | ||
|
|
||
| [quantities.slayer_gamma_Hz] | ||
| h5path = "Tearing/Roots/gamma" | ||
| type = "real_vector" | ||
| extract = "first_3" | ||
| label = "SLAYER γ_Hz [3/2,4/2,5/2]" | ||
| noise_threshold = 1e-1 | ||
| order = 33 | ||
|
|
||
| # no_root flag (1 = extraction failed). Pinned for the inner three surfaces | ||
| # so the 3/2, 4/2, 5/2 root inventory cannot drift silently. | ||
| [quantities.slayer_no_root] | ||
| h5path = "Tearing/Roots/no_root" | ||
| type = "real_vector" | ||
| extract = "first_3" | ||
| label = "SLAYER no_root flags [3/2,4/2,5/2]" | ||
| noise_threshold = 0 | ||
| order = 34 | ||
|
|
||
| # Settings (catches accidental config drift) | ||
| [quantities.slayer_enabled] | ||
| h5path = "Tearing/enabled" | ||
| type = "int_scalar" | ||
| extract = "value" | ||
| label = "SLAYER enabled flag" | ||
| noise_threshold = 0 | ||
| order = 90 | ||
|
|
||
| [quantities.runtime] | ||
| h5path = "" | ||
| type = "runtime" | ||
| extract = "value" | ||
| label = "Runtime (s)" | ||
| noise_threshold = 0.0 | ||
| order = 999 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -217,20 +217,23 @@ function build_slayer_inputs(equil, sings, profiles::KineticProfiles; | |
| da_dpsi = _da_dpsi_at(psi) | ||
| sval_r = r_based_shear(rs, q, q1, da_dpsi) | ||
|
|
||
| prof = profiles(psi) | ||
| # Override ω_*e, ω_*i with spline-derivative values when requested. | ||
| ω_e_use, ω_i_use = if compute_omega_star | ||
| _omega_star_at(psi) | ||
| else | ||
| (prof.omega_e, prof.omega_i) | ||
| end | ||
|
|
||
| # Resonant (m, n): take the first element of the mode-number vectors. | ||
| # Parallel-FM `sing.m`/`sing.n` hold exactly one entry each; ideal | ||
| # DCON may hold multiple — we pick the first and document the choice. | ||
| m_res = sing.m[1] | ||
| n_res = sing.n[1] | ||
|
|
||
| prof = profiles(psi) | ||
| # Override ω_*e, ω_*i with spline-derivative values when requested. In flux coordinates | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When I see override, I think of something that is already set; in this case its just another branch of how to set this value. I think more true to form would be ω_e_use, ω_i_use = (prof.omega_e, prof.omega_i)
# Override ω_*e, ω_*i with spline-derivative values when requested
compute_omega_star && (ω_e_use, ω_i_use = _omega_star_at(psi, n))or something like that. The PR comment does suggest that prof.omega_e/i are never actually used, but I didn't factor that in here since I am unfamiliar with the rest of the relevant code |
||
| # ω_* = n·(dp/dψ)/(e·n_e); `_omega_star_at` returns the n = 1 value, so restore the | ||
| # factor n here. Values supplied through `profiles` are taken to be physical already. | ||
| ω_e_use, ω_i_use = if compute_omega_star | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Personally, I think the something = an entire if-else block is kinda confusing code, but it seems like that's in a lot of this file so its probably ok. But if I am understanding this PR correctly - the actual omega* has an included n, so its a little bit deceptive to have |
||
| ωe1, ωi1 = _omega_star_at(psi) | ||
| (n_res * ωe1, n_res * ωi1) | ||
| else | ||
| (prof.omega_e, prof.omega_i) | ||
| end | ||
|
|
||
| # Pull geometric trapped-fraction inputs from ResistGeometry when | ||
| # available (populated by ForceFreeStates.resist_eval_all!); else | ||
| # fall back to nothing and let slayer_parameters compute them from | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the entire purpose of this regression case is to guard the n-dependence of the layer module... that seems like overkill for a regression test that takes 5 minutes to run. I speak selfishly because my (and I'm assuming other's) regress running is automatically chosen by Claude. So if I change anything related to this module's code, Claude will queue up both of these cases to test something relatively small.
So personally, I am against regression case bloat and don't want them to take the place of unit tests. So my question here for your consideration - if the n1 and n2 case here actually needed and worth the additional regression harness runtime, which can already get quite long? Or is this something where we could keep a single regression case (perhaps n2 to try to catch n-dependent things like this in the future) and supplement with unit tests that check for specifics to slayer more cheaply?