Repository navigation
Conversation
Contributor
|
This pull request is missing a reviewer. If you are not ready to name them, mark this pull request as a draft. |
1 similar comment
Contributor
|
This pull request is missing a reviewer. If you are not ready to name them, mark this pull request as a draft. |
…e inner-layer Q argument The coupled determinant evaluated every surface's inner layer at the same lab-frame frequency, so there was no way to impose differential rotation between rational surfaces and no way to test the coupled growth rate for rotation invariance. Add a real `q_shift` to `SurfaceCoupling`, applied to the layer's Q argument in the scalar residual, the reduced m x m determinant, and the 4m x 4m Pletzer-Dewar matching. Re(Q) is the lab-frame frequency in each surface's own normalization, so the offset is real and Dopplers that surface away from the common eigenvalue; zero reproduces the previous static behaviour exactly. The runner builds the shifts from a new `[SLAYER] omega_shift_kHz` vector (per-surface lab-frame offsets in kHz, core to edge), converting with each surface's tau_k, and records the applied dRe(Q) at `PerSurface/q_shift`. On the shipped DIII-D-like deck in coupled mode, a 3 kHz shift on the 3/1 alone moves the n=1 coupled growth rate from 205.6 to 233.4 1/s (+13.5%), with omega essentially unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit bdafb63dbe9334232bcead6357e7ed9190cb5835)
…he direct tauk rescale Verified the layer normalization against Fitzpatrick's TJ (Documentation/Layer.tex and Layer/Layer.cpp). TJ solves for the layer eigenvalue as ghat = i(Q_E - omega*tau_k) with Q_E = tau_k*omega_E, and reports omega = (Q_E - Im(ghat))/tau_k. Our Riccati bridge already documents ghat = -i*Q, so Q_k = tau_k*(omega - omega_E_k). Two consequences: Rename `omega_shift_kHz` to `omega_E_kHz` and negate the conversion, so the input is the surface's E*B rotation frequency and the layer responds to the mode frequency in its own fluid frame, as in TJ. The knob was new in the previous commit, so nothing downstream depended on the old spelling. Add `tauk_rescale`, selecting the inter-surface Q normalization used by the coupled determinant. The default `:legacy` keeps Q*tauk_ref/tauk_k, which every coupled growth rate produced by this code so far has used. `:direct` uses Q*tauk_k/tauk_ref, the direction implied both by our own extraction convention omega = Re(Q)/tauk and by TJ. The two differ by (tauk_k/tauk_ref)^2 and move results, so the correction is opt-in rather than applied silently. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit d1c4e6766ffe958a548a61deb47627728a25588a)
…in the coupled determinant The coupled determinant evaluated surface k's inner layer at Q_k = Q * (tauk_ref / tauk_k). That direction is inverted. Q is defined as tauk * omega -- Fortran GPEC slayer/params.f sets `Q = Qconv*omega` with `Qconv = lu**(1/3)*tau_h`, this code builds Q_e and Q_i the same way, and the growth-rate extraction inverts it as omega = Re(Q)/tauk. One shared physical eigenvalue therefore reaches surface k by MULTIPLYING by tauk_k, giving Q_k = Q * (tauk_k / tauk_ref). The inconsistency is visible inside a single layer solve: the scanned frequency was scaled as 1/tauk_k while that surface's Q_e/Q_i scale as tauk_k, yet the layer equations add them (ghat + i*Q_e). The two were mis-scaled by (tauk_k/tauk_ref)^2 -- a factor of 18 at the 3/1 on the TJ pc=0.1 equilibrium. Confirmed three ways: the definitional argument above; Fitzpatrick's TJ, where ghat_k = tauk_k * g_k (TJ Documentation/Layer.tex); and a rigid-rotation invariance test, which the corrected direction satisfies to four significant figures in omega while the old one misses it by 15x and swings gamma by 44%. `tauk_rescale` now defaults to `:direct`. `:legacy` restores the old behaviour for reproducing results generated before this correction. The existing rescale test only restated the implementation, so it is rewritten to pin the physics and to cover both directions. NOTE: Fortran GPEC has the same inversion on its `slayer_growthrate` branch, slayer/growthrates.f: `g_tmp = (g_in*sl_in%Qconv_arr(1))/tauk`. This Julia code was a faithful port; the upstream fix is still outstanding. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit d352ae24373a0052f44277b7d63cb460ab7dbaa5)
…rom slayer_parameters slayer_parameters required an `omega` rotation keyword but never read it, and build_slayer_inputs dutifully passed the kinetic file's rotation into it. A single layer is solved in its own plasma frame, so rotation has no place in the per-surface parameters; it belongs in the coupled determinant, where surfaces rotating at different rates must share one lab-frame eigenvalue. Drop the argument. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e imaginary rotation term in the full coupled determinant The reduced determinant was corrected to evaluate surface k at Q·tauk_k/tauk_ref, but the full 4m×4m Pletzer-Dewar form still used the inverted tauk_ref/tauk_k and still added i·ntor·rotation[k] alongside the new real q_shift. That imaginary term is Fortran's guess_modify carried over without the change of convention: with Q = tauk·(ω + iγ) it moves γ rather than ω, and it adds a frequency in s⁻¹ to a dimensionless Q. Give the full form the same tauk_rescale switch (default :direct) and take the Doppler offset solely from each SurfaceCoupling's real q_shift. The rotation and ntor fields are removed. Tests pin the per-surface Q argument under both rescalings and that the shift moves Re(Q) only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…y it only to the coupled determinant run_slayer loaded the kinetic file's omega_E and then discarded it; the only way to rotate a surface was a hand-typed omega_E_kHz vector. That vector was also applied to uncoupled scans, where a layer is already solved in its own plasma frame and a shift only relabels the reported frequency, and it omitted the toroidal mode number: the file's omega_E is per unit n, while the layer sees n·Ω_E (TJ's ω_E = (m/r_s)·V_E). - Sample Ω_E from the kinetic file at each rational surface by default; a non-empty omega_E_kHz overrides it in the same per-unit-n convention. - Doppler-shift only the coupled determinant, by ΔRe(Q_k) = −τ_k·n·Ω_E,k. Uncoupled runs apply no shift. GGJ surfaces carry no time normalization and are not shifted. - Record the shifts actually applied in SLAYERResult.q_shift and write those to PerSurface/q_shift, instead of recomputing from the control block. - Warn when a surface's offset in the scanned Q falls outside Q_re_range, since the lab-frame root then silently becomes :no_root. The sign needs no mapping onto TJ's conventions. On the DIII-D-like deck the static uncoupled roots sit on ω_*e = −Q_e/τ_k (0.1%, 0.04%, 0.4% at the 2/1, 3/1, 4/1), and the file's omega_E is built from the same ion diamagnetic formula as SLAYER's ω_*i, so ω, ω_* and Ω_E share one convention and the Doppler term is a plain subtraction. In coupled mode the deck's root then lands at ω = 30993 rad/s against the predicted n·Ω_E + ω_*e = 30986 at the 2/1, with γ = 195.0 s⁻¹ against the uncoupled 194.8. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
No harness case exercised the coupled determinant, so neither the inter-surface Q normalization nor the E×B Doppler shift was guarded. Reuse the n=1 SLAYER deck in coupled mode, widening Re(Q) to [-4, 6] because the kinetic-file rotation moves the root to Re(Q) ≈ 2.06, outside the deck's [-2, 2]. The run adds about 17 s of SLAYER time on top of the shared equilibrium and stability stages. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s from KineticProfiles KineticProfiles carried omega_e and omega_i, but nothing could put a real value in them: run_slayer filled them with zeros, the kinetic file schema has no ω_* column, and compute_omega_star defaulted to true with no caller in src/ ever passing it, so build_slayer_inputs always derived ω_*e and ω_*i from the density and temperature splines instead. The fields, the zeros and the compute_omega_star branch are removed; the container keeps the profiles that are actually read, including omega, which now carries the E×B rotation into the coupled determinant. The two tests that used the pass-through to pin Q_e and Q_i now assert the derived values instead: the fixtures are linear in ψ at constant n_e, so ω_*e = -700/psio and ω_*i = +600/psio exactly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…and tidy the rotation plumbing - Drop `tauk_rescale` entirely: `Q·tauk_k/tauk_ref` is the only normalization, so the `:legacy` reproduction shim (which could not reproduce pre-correction results once the kinetic file carries omega_E) goes, with its field, keyword, TOML key and validation. - The example deck's rotation note gives the shift in the scanned plane (+τ_ref·n·Ω_E, the same for every surface) rather than each surface's own q_shift. - `Roots/omega` documents its frame: E×B per surface when uncoupled, lab when coupled. - GGJ surfaces take no q_shift (they have no time normalization); Ω_E is resolved once per run. - Replace the documentation-file citations with the relation itself and trim the comments that narrated the old bug. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d roots are reproducible `_extract_growth_rates_amr` triangulated the AMR samples with DelaunayTriangulation's default randomized insertion order, drawn from the global RNG, which Julia seeds from entropy at startup. An AMR grid is full of cocircular points, where the Delaunay triangulation is not unique, so each run could pick different diagonals and hence different contour crossings. Polished roots converge past that; the unpolished coupled root did not. On the DIII-D-like coupled deck, the identical scan samples gave γ = 190.646 or 191.888 s⁻¹ depending only on the seed (10 seeds, 5/5 split). This is the run-to-run scatter this PR reported. Threads were not the cause. The triangulation now draws its insertion order from a fixed-seed Xoshiro, and a test asserts that extraction leaves the global RNG untouched. The coupled root is now reproducible, but it still carries the triangulation's ~0.7% interpolation error until coupled root polishing exists. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
d-burg
force-pushed
the
bugfix/coupled-slayer-tauk-rescale-and-rotation
branch
from
September 23, 2026 22:28
6e2ff45 to
0058655
Compare
…and rescale test - Throw when a surface has E×B rotation but SLAYERParameters.n < 1, instead of silently zeroing the Doppler offset. - Put the toroidal mode number back into the documented q_shift = −τ_k·n·Ω_E. - State the omega_E_kHz units correctly (per unit n like the kinetic file's omega_E, but kHz rather than rad/s) and drop the duplicated inline comment. - Define the coupled reference surface once and word the Doppler-window check as a heuristic; drop the per-run Doppler @info (q_shift is in the output). - Drop deck-specific numbers from the coupled-root comment and example deck; note that coupled runs need a wider Q_re_range. - Fix the known-coupled-root test to use the tauk_k/tauk_ref direction and a second surface with a different tauk, so it exercises the rescale. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ga to omega_E The field and keyword now carry the same name as the kinetic-file dataset they are read from, Ω_E per unit n in rad/s. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…otation per surface
omega_E_kHz is now a TOML table keyed by the surface's m/n, e.g. {"2/1" = 0.0, "3/1" = 3.0}; unlisted surfaces keep the kinetic file's Ω_E and an m/n matching no analysed surface is an error. The positional list form is removed.
The Ω_E actually resolved on each surface is carried as SLAYERResult.omega_E and written to Tearing/PerSurface/omega_E [rad/s, per unit n] in both coupling modes; PerSurface/q_shift stays the applied Doppler offset (zero when uncoupled).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
State the lab-to-E×B-frame relation behind q_shift = −τ_k·n·Ω_E in the _q_shift docstring, and test on two real SLAYER surfaces that the rotating coupled determinant at Q + τ_ref·n·Ω_E equals the static one at Q, so every root keeps its γ and its lab frequency moves by +n·Ω_E. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s rounding noise Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…yer-tauk-rescale-and-rotation Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tity in the coupled SLAYER case Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd convention statements Rotation is tested once, through run_slayer_from_inputs, with the n = 2 and wrong-length cases moved there. omega_E_kHz keys are validated in one place, the run-time check against the analysed surfaces. The Doppler convention is derived once, in _q_shift. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e coupled-determinant fix The fix takes E×B rotation from the kinetic file only. The per-surface override is a rotation-scan feature and goes to its own pull request. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…yer-tauk-rescale-and-rotation
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Release note
coupling_mode = "coupled"). Ondiiid_slayer_n1_coupledthe root's ω moves from −10.5 k to +31.0 k rad/s (into the lab frame, by n·Ω_E at the 2/1). Its γ moves by 0.3–0.8 %: develop's own coupled γ is not reproducible (191.35 and 190.29 s⁻¹ in repeated runs), while this branch gives 191.89 s⁻¹ every time. Uncoupled results are unchanged: every layer input is bit-identical and γ differs only by develop's unseeded-triangulation noise (≤ 0.02 %). (harness @ 6a9d9e8)omega_Enow report the root in the lab frame; widenQ_re_rangeif the run warns that Doppler offsets fall outside it. Interface removals:slayer_parameters(omega=…),multi_surface_coupling_full(rotation=…, ntor=…),KineticProfiles(omega_e=…, omega_i=…)andbuild_slayer_inputs(compute_omega_star=…); drop those arguments.KineticProfiles.omegais renamedomega_E.The coupled tearing determinant evaluated every non-reference surface at the wrong Q (off by
(τ_k/τ_ref)²), so its roots depended on which surface was chosen as reference. It also ignored the E×B rotation that the kinetic file already supplies. Both are fixed, and the root extraction is now reproducible run to run.Left: on develop the coupled root sits at the 2/1's own-frame frequency. With this PR it moves by n·Ω_E at the 2/1 (41.5 krad/s) into the lab frame, landing where shifting the uncoupled 2/1 root predicts (30,993 against 30,996 rad/s); γ changes by under 1 %. Right: the E×B Doppler shift now applied at each surface, taken from the kinetic file.
What changed
Q·τ_k/τ_ref, notQ·τ_ref/τ_kCoupled.jl,CoupledFullMatch.jl(one line each)q_shift = −τ_k·n·Ω_E, from the kinetic file'somega_E, applied in coupled mode onlySurfaceCoupling.jl,run_slayer.jl+ i·ntor·rotation[k]is removed: it shifted γ instead of ω and added s⁻¹ to a dimensionless QCoupledFullMatch.jlGrowthRateExtraction.jl(3 lines)PerSurface/omega_EandPerSurface/q_shiftare written to the outputResult.jl,HDF5Output.jlKineticProfiles.jl,LayerInputs.jl,LayerParameters.jland eight test filesdiiid_slayer_n1_coupledregression-harness/cases/The source change is +169/−119 lines. Most of the 23 files are tests that only drop the removed arguments.
What moves
regress --cases diiid_slayer_n1,diiid_slayer_n1_coupled --refs 6fc89d021,6a9d9e848 --forceon feynman (current develop against this branch with develop merged).diiid_slayer_n1(uncoupled)diiid_slayer_n1_coupled: ωdiiid_slayer_n1_coupled: γdiiid_slayer_n1_coupled:q_shiftThe branch's values are bit-identical to its runs before the latest develop merge and before the last two commits, which only trim tests and comments and take out the override.
For the reviewer
τ_k/τ_ref. WithQ = τ_k·(ω + iγ), one physical eigenvalue reaches surface k asQ·τ_k/τ_ref. With a diagonal Δ′ the coupled roots must then reproduce each surface's uncoupled root whichever surface is the reference; the old direction got only the reference surface right. Pinned by "Coupled roots do not depend on the reference surface" inruntests_dispersion_rotation.jl.omega_Eis per unit n, so the mode seesn·Ω_E.Q_layer = τ_k·ω_lab − τ_k·n·Ω_E. A test on two real SLAYER surfaces checks that the rotating determinant atQ + τ_ref·n·Ω_Eequals the static one atQto within 1e-4 (measured agreement 3e-6, the layer solve's rounding noise); the opposite sign is off by 27–394 %.slayer_growthratebranch, so the earlier Julia/Fortran coupled comparison did not validate the rescaling direction.omega_E_kHz) is split into Tearing - FEATURE - 🌱 Add a per-surface omega_E_kHz override of the kinetic-file E×B rotation #519, stacked on this one.Full regression report
In this run the uncoupled case is unchanged. In other runs its γ line differs by up to 0.02 %, which is develop's unseeded-triangulation noise: the same difference shows up on PRs that do not touch SLAYER.
The run-to-run scatter in the coupled γ, and its fix
Other notes
PerSurface/omega_Eis written in both modes.KineticProfileswithomega_e=zeros(...), omega_i=zeros(...)must drop those two arguments.ForceFreeStates.ResistiveMatch(; rotation)is unaffected: it uses the Laplace-variable eigenvalue, where the imaginary sign is correct.NO MERGE WITHOUT HUMAN REVIEW. This PR is a draft and needs a named human reviewer before it can be considered for merge.
🤖 Generated with Claude Code