-
Notifications
You must be signed in to change notification settings - Fork 7
PerturbedEquilibrium - BUGFIX - change singular coupling calculations to match Fortran, using du_store instead of splined xi' #348
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
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 |
|---|---|---|
|
|
@@ -645,6 +645,7 @@ function cross_ideal_singular_surf!( | |
| odet.q_store[odet.step] = odet.q | ||
| odet.u_store[:, :, :, odet.step] = odet.u | ||
| odet.ud_store[:, :, :, odet.step] = odet.ud | ||
| odet.du_store[:, :, :, odet.step] = du1 | ||
| odet.step += 1 | ||
| end | ||
|
|
||
|
|
@@ -696,6 +697,7 @@ function cross_kinetic_singular_surf!( | |
| odet.q_store[odet.step] = odet.q | ||
| odet.u_store[:, :, :, odet.step] = odet.u | ||
| odet.ud_store[:, :, :, odet.step] = odet.ud | ||
| odet.du_store[:, :, :, odet.step] = du1 | ||
| odet.step += 1 | ||
| end | ||
|
|
||
|
|
@@ -757,12 +759,6 @@ function integrate_el_region!( | |
|
|
||
| compute_solution_norms!(integrator.u, odet, ctrl, intr, false) | ||
|
|
||
| # If Gaussian reduction modified u, recompute ud to keep it consistent. | ||
| # Matches Fortran ode_output.f which calls sing_der before writing euler.bin. | ||
| if odet.new | ||
| sing_der!(du_buffer, integrator.u, integrator.p, integrator.t) | ||
| end | ||
|
|
||
| # Save near segment boundaries (symmetric, in q not psi) and every Nth step. | ||
| # The step-count fallback (== 1) guarantees the first step is always saved | ||
| # even for near-degenerate segments where q_range ≈ 0. | ||
|
|
@@ -775,11 +771,15 @@ function integrate_el_region!( | |
| if odet.step >= size(odet.u_store, 4) | ||
| resize_storage!(odet) | ||
| end | ||
| sing_der!(du_buffer, integrator.u, integrator.p, integrator.t) | ||
| odet.psi_store[odet.step] = integrator.t | ||
| @views odet.u_store[:, :, :, odet.step] .= integrator.u | ||
| odet.q_store[odet.step] = odet.q | ||
| @views odet.ud_store[:, :, :, odet.step] .= odet.ud | ||
| @views odet.du_store[:, :, :, odet.step] .= du_buffer | ||
| odet.step += 1 | ||
| elseif odet.new | ||
| sing_der!(du_buffer, integrator.u, integrator.p, integrator.t) | ||
|
Collaborator
Author
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. The old block kept a comment explaining why this call exists; the Generated by Claude Code
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. Agreed - I think is dead code. The way this is set up, sing_der! is called again whenever we're saving anyway. A sing_der! call on its own doesn't do anything, it just filled in the buffer which becomes unused. It only does anything if paired with 1) an integrator which uses it to take a step or 2) to recompute the derivatives when u changed so we can write them to memory |
||
| end | ||
| end | ||
|
|
||
|
|
@@ -794,10 +794,12 @@ function integrate_el_region!( | |
| if odet.step >= size(odet.u_store, 4) | ||
| resize_storage!(odet) | ||
| end | ||
| sing_der!(du_buffer, sol.u[end], (ctrl, equil, ffit, intr, odet, chunk), sol.t[end]) | ||
| odet.psi_store[odet.step] = sol.t[end] | ||
| @views odet.u_store[:, :, :, odet.step] .= sol.u[end] | ||
| odet.q_store[odet.step] = odet.q | ||
| @views odet.ud_store[:, :, :, odet.step] .= odet.ud | ||
| @views odet.du_store[:, :, :, odet.step] .= du_buffer | ||
| odet.step += 1 | ||
| end | ||
|
|
||
|
|
@@ -1035,6 +1037,10 @@ function transform_u!(odet::OdeState, intr::ForceFreeStatesInternal) | |
| odet.ud_store[:, :, 1, istep] .= gauss_buffer | ||
| mul!(gauss_buffer, odet.ud_store[:, :, 2, istep], transforms[:, :, ifix]) | ||
| odet.ud_store[:, :, 2, istep] .= gauss_buffer | ||
| mul!(gauss_buffer, odet.du_store[:, :, 1, istep], transforms[:, :, ifix]) | ||
| odet.du_store[:, :, 1, istep] .= gauss_buffer | ||
| mul!(gauss_buffer, odet.du_store[:, :, 2, istep], transforms[:, :, ifix]) | ||
| odet.du_store[:, :, 2, istep] .= gauss_buffer | ||
| end | ||
| jfix = kfix + 1 | ||
| end | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -346,6 +346,8 @@ end | |
| # Kinetic torque matrix splines: 6 components | ||
| ktmats::Vector{S} = [_empty_series_interp_complex(numpert_total^2, itp_opts) for _ in 1:6] | ||
|
|
||
| kinetic_populated::Bool = false # set by make_kinetic_matrix; fmats_lower/kmats are then not the EL operators | ||
|
Collaborator
Author
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. Minor: this comment states the reason slightly wrong. Generated by Claude Code
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. Idk anything about kinetic physics, you can judge if this is needed or not |
||
|
|
||
| # Pre-computed FKG kinetic matrices (populated by make_kinetic_matrix) | ||
| f0mats::S = _empty_series_interp_complex(numpert_total^2, itp_opts) | ||
| pmats::S = _empty_series_interp_complex(numpert_total^2, itp_opts) | ||
|
|
@@ -492,7 +494,9 @@ and a small set of temporary matrices and factors used to compute singular-layer | |
| - `u_store::Array{ComplexF64,4}` - Stored solution arrays at each saved step with shape | ||
| `(numpert_total, numpert_total, 2, numsteps_init)` (complex solution state used by the solver). | ||
|
|
||
| - `ud_store::Array{ComplexF64,4}` - Stored derivatives of the solution at each saved step with same shape as `u_store`. | ||
| - `ud_store::Array{ComplexF64,4}` - Stored derivatives of the solution at each saved step with same shape as `u_store`; slot 2 holds the Clebsch Ξ_s, not du₂/dψ. | ||
|
|
||
| - `du_store::Array{ComplexF64,4}` - du₁/dψ and du₂/dψ at the accepted point of each saved step, populated only by the serial EL path. | ||
|
|
||
| - `crit_store::Vector{Float64}` - Stored crit parameter values (smallest eigenvalue of W⁻ꜝ) (length `numsteps_init`). | ||
|
|
||
|
|
@@ -560,6 +564,7 @@ and a small set of temporary matrices and factors used to compute singular-layer | |
| q_store::Vector{Float64} = Vector{Float64}(undef, numsteps_init) | ||
| u_store::Array{ComplexF64,4} = Array{ComplexF64}(undef, numpert_total, numpert_total, 2, numsteps_init) | ||
| ud_store::Array{ComplexF64,4} = Array{ComplexF64}(undef, numpert_total, numpert_total, 2, numsteps_init) | ||
| du_store::Array{ComplexF64,4} = zeros(ComplexF64, numpert_total, numpert_total, 2, numsteps_init) | ||
|
Collaborator
Author
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. Observation (non-blocking): slot 1 of This array is Also: Generated by Claude Code
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. I think this is a good suggestion - I think what I would propose is:
The new struct then looks like |
||
| crit_store::Vector{Float64} = Vector{Float64}(undef, numsteps_init) | ||
| ca_r::Array{ComplexF64,4} = Array{ComplexF64}(undef, numpert_total, numpert_total, 2, msing) | ||
| ca_l::Array{ComplexF64,4} = Array{ComplexF64}(undef, numpert_total, numpert_total, 2, msing) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,6 +43,106 @@ end | |
| # Reflect a periodic theta-space vector θ → -θ (the theta reversal in gpvacuum_flxsurf). | ||
| _reverse_theta(v::AbstractVector) = circshift(reverse(v), 1) | ||
|
|
||
| """ | ||
| _solution_at(psi, psi_surf, odet, equil, ffs_intr, nstep) -> (u1, du1) | ||
|
|
||
| Evaluate Ξ_ψ and Ξ′_ψ at `psi` from the stored ODE solution: cubic Hermite for the value, | ||
| Lagrange cubic through the singfac-weighted `du_store` nodes for the derivative. Works for | ||
| any formulation; ideal runs use the more accurate `_el_solution_at`. The Ξ′ stencil stays | ||
| on `psi`'s side of the singular surface `psi_surf`, where the stored solution is | ||
| discontinuous. Requires a populated `odet.du_store`. | ||
| """ | ||
| function _solution_at( | ||
| psi::Float64, | ||
| psi_surf::Float64, | ||
| odet::OdeState, | ||
| equil::Equilibrium.PlasmaEquilibrium, | ||
| ffs_intr::ForceFreeStatesInternal, | ||
| nstep::Int | ||
| ) | ||
| il, ir, _ = _psi_bracket(odet.psi_store, psi, nstep) | ||
| psi_a, psi_b = odet.psi_store[il], odet.psi_store[ir] | ||
|
|
||
| u1_a = @view odet.u_store[:, :, 1, il] | ||
| u1_b = @view odet.u_store[:, :, 1, ir] | ||
| du1_a = @view odet.du_store[:, :, 1, il] | ||
| du1_b = @view odet.du_store[:, :, 1, ir] | ||
|
|
||
| u1_e = _hermite_cubic_val(u1_a, u1_b, du1_a, du1_b, psi_a, psi_b, psi) | ||
|
|
||
| # 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] | ||
| while length(idxs) > 4 | ||
| abs(odet.psi_store[idxs[1]] - psi) > abs(odet.psi_store[idxs[end]] - psi) ? popfirst!(idxs) : pop!(idxs) | ||
| end | ||
|
Comment on lines
+73
to
+78
Collaborator
Author
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. Two minor robustness notes on the stencil selection:
Neither is blocking given the current save density; flagging for awareness. Generated by Claude Code |
||
|
|
||
| # Interpolate singfac·Ξ′ and divide the ~1/singfac pole back out at psi. | ||
| singfac_at(p) = vec([m - equil.profiles.q_spline(p) * n for m in ffs_intr.mlow:ffs_intr.mhigh, n in ffs_intr.nlow:ffs_intr.nhigh]) | ||
| du1_e = zeros(ComplexF64, size(odet.du_store, 1), size(odet.du_store, 2)) | ||
| for k in eachindex(idxs) | ||
| w = 1.0 | ||
| for j in eachindex(idxs) | ||
| j == k && continue | ||
| w *= (psi - odet.psi_store[idxs[j]]) / (odet.psi_store[idxs[k]] - odet.psi_store[idxs[j]]) | ||
| end | ||
| du1_e .+= (w .* singfac_at(odet.psi_store[idxs[k]])) .* @view(odet.du_store[:, :, 1, idxs[k]]) | ||
| end | ||
| du1_e ./= singfac_at(psi) | ||
|
|
||
| return u1_e, du1_e | ||
| end | ||
|
|
||
| """ | ||
| _el_solution_at(psi, odet, ffit, equil, ffs_intr, nstep) -> (u1, du1) | ||
|
|
||
| Evaluate Ξ_ψ and Ξ′_ψ at `psi` from the stored ODE solution via the ideal Euler-Lagrange | ||
| relation Ξ′ = Q⁻¹·F̄⁻¹·(Q⁻¹·u₂ − K̄·u₁) [Glasser 2016 eqs. 22-24], with u₁, u₂ Hermite- | ||
| interpolated to `psi`. Only valid for ideal runs where `ffit.fmats_lower` and `kmats` | ||
| generated the solution. Requires a populated `odet.du_store`. | ||
| """ | ||
| function _el_solution_at( | ||
| psi::Float64, | ||
| odet::OdeState, | ||
| ffit::FourFitVars, | ||
| equil::Equilibrium.PlasmaEquilibrium, | ||
| ffs_intr::ForceFreeStatesInternal, | ||
| nstep::Int | ||
| ) | ||
| npert = ffs_intr.numpert_total | ||
| il, ir, _ = _psi_bracket(odet.psi_store, psi, nstep) | ||
| psi_a, psi_b = odet.psi_store[il], odet.psi_store[ir] | ||
|
|
||
| u1_a = @view odet.u_store[:, :, 1, il] | ||
| u1_b = @view odet.u_store[:, :, 1, ir] | ||
| u2_a = @view odet.u_store[:, :, 2, il] | ||
| u2_b = @view odet.u_store[:, :, 2, ir] | ||
| du1_a = @view odet.du_store[:, :, 1, il] | ||
| du1_b = @view odet.du_store[:, :, 1, ir] | ||
| du2_a = @view odet.du_store[:, :, 2, il] | ||
| du2_b = @view odet.du_store[:, :, 2, ir] | ||
|
|
||
| hint = Ref(1) | ||
| kmat = Matrix{ComplexF64}(undef, npert, npert) | ||
|
|
||
| u1_e = _hermite_cubic_val(u1_a, u1_b, du1_a, du1_b, psi_a, psi_b, psi) | ||
| u2_e = _hermite_cubic_val(u2_a, u2_b, du2_a, du2_b, psi_a, psi_b, psi) | ||
|
|
||
| # Ξ′ = Q⁻¹·F̄⁻¹·(Q⁻¹·u₂ − K̄·u₁) with Q⁻¹ = diag(1/(m − n·q)) | ||
| q_e = equil.profiles.q_spline(psi) | ||
| singfac_inv = vec([1.0 / (m - q_e * n) for m in ffs_intr.mlow:ffs_intr.mhigh, n in ffs_intr.nlow:ffs_intr.nhigh]) | ||
| fmat_lower = Matrix{ComplexF64}(undef, npert, npert) | ||
| ffit.fmats_lower(vec(fmat_lower), psi; hint=hint) | ||
| ffit.kmats(vec(kmat), psi; hint=hint) | ||
| du1_e = u2_e .* singfac_inv | ||
| du1_e .-= kmat * u1_e | ||
| ldiv!(LowerTriangular(fmat_lower), du1_e) | ||
| ldiv!(UpperTriangular(fmat_lower'), du1_e) | ||
| du1_e .*= singfac_inv | ||
|
|
||
| return u1_e, du1_e | ||
| end | ||
|
|
||
| """ | ||
| compute_singular_coupling_metrics!( | ||
| state::PerturbedEquilibriumState, | ||
|
|
@@ -51,7 +151,8 @@ _reverse_theta(v::AbstractVector) = circshift(reverse(v), 1) | |
| vac_data::VacuumData, | ||
| ffs_intr::ForceFreeStatesInternal, | ||
| intr::PerturbedEquilibriumInternal, | ||
| ctrl::PerturbedEquilibriumControl | ||
| ctrl::PerturbedEquilibriumControl, | ||
| ffit::FourFitVars | ||
| ) | ||
|
|
||
| Compute singular layer coupling matrices and applied resonant vectors. | ||
|
|
@@ -82,7 +183,8 @@ function compute_singular_coupling_metrics!( | |
| vac_data::VacuumData, | ||
| ffs_intr::ForceFreeStatesInternal, | ||
| intr::PerturbedEquilibriumInternal, | ||
| ctrl::PerturbedEquilibriumControl | ||
| ctrl::PerturbedEquilibriumControl, | ||
| ffit::FourFitVars | ||
| ) | ||
| ctrl.verbose && @info "Computing singular coupling metrics (GPEC method)" | ||
|
|
||
|
|
@@ -159,6 +261,10 @@ function compute_singular_coupling_metrics!( | |
| # @threads region. | ||
| psi_store_all = ForceFreeStates_results.psi_store | ||
| nstep = ForceFreeStates_results.step | ||
| # ξ′ evaluation preference: ideal EL relation, then interpolated stored RHS for kinetic, | ||
| # then chord slope for paths that never populate du_store like the gal-matched odet. | ||
| use_du_store = any(!iszero, ForceFreeStates_results.du_store) | ||
|
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. I think this logic is kinda confusing. My quick understanding from this is that:
If this is correct, I have a few comments
# Evaluate u and dξ/dψ at lpsi and rpsi from the stored ODE solution
if !use_du_store
# Interpolate u and finite-difference for dxi/dpsi
...
elseif use_el
# Interpolate u and compute du from EL equation
etc. |
||
| use_el = use_du_store && !ffit.kinetic_populated | ||
| _blas_nthreads = BLAS.get_num_threads() | ||
| BLAS.set_num_threads(1) | ||
| try | ||
|
|
@@ -169,7 +275,7 @@ function compute_singular_coupling_metrics!( | |
|
|
||
| resnum = findfirst(j -> intr.m_modes[j] == m_res && intr.n_modes[j] == nn, 1:numpert_total) | ||
| if resnum === nothing | ||
| @warn "Could not find index for resonant mode (m=$m_res, n=$nn)" maxlog=1 | ||
| @warn "Could not find index for resonant mode (m=$m_res, n=$nn)" maxlog = 1 | ||
| continue | ||
| end | ||
|
|
||
|
|
@@ -201,36 +307,46 @@ function compute_singular_coupling_metrics!( | |
| lpsi = sing_surf.psifac - spot_psi | ||
| rpsi = sing_surf.psifac + spot_psi | ||
|
|
||
| # Interpolate u and du/dψ at lpsi and rpsi using stored ODE solution. | ||
| # Value (u): Hermite cubic using ud_store for smoother interpolation than linear. | ||
| # Derivative (ud): chord slope from u_store only — see comment below. | ||
| il_l, ir_l, _ = _psi_bracket(psi_store_all, lpsi, nstep) | ||
| il_r, ir_r, _ = _psi_bracket(psi_store_all, rpsi, nstep) | ||
|
|
||
| u_node = ForceFreeStates_results.u_store | ||
| ud_node = ForceFreeStates_results.ud_store | ||
| ua_l = u_node[resnum, :, 1, il_l] | ||
| ub_l = u_node[resnum, :, 1, ir_l] | ||
| ua_r = u_node[resnum, :, 1, il_r] | ||
| ub_r = u_node[resnum, :, 1, ir_r] | ||
| dua_l = ud_node[resnum, :, 1, il_l] | ||
| dub_l = ud_node[resnum, :, 1, ir_l] | ||
| dua_r = ud_node[resnum, :, 1, il_r] | ||
| dub_r = ud_node[resnum, :, 1, ir_r] | ||
|
|
||
| psi_il_l = psi_store_all[il_l] | ||
| psi_ir_l = psi_store_all[ir_l] | ||
| psi_il_r = psi_store_all[il_r] | ||
| psi_ir_r = psi_store_all[ir_r] | ||
|
|
||
| u_l = _hermite_cubic_val(ua_l, ub_l, dua_l, dub_l, psi_il_l, psi_ir_l, lpsi) | ||
| u_r = _hermite_cubic_val(ua_r, ub_r, dua_r, dub_r, psi_il_r, psi_ir_r, rpsi) | ||
| # Derivative (ud): chord slope from u_store only — ud_store can be systematically off near | ||
| # outer surfaces (q=4/5) where the ODE solution varies rapidly; near-cancellation in bwp1 | ||
| # then amplifies even a ~10% ud_store error into a large Delta' error. Chord slope avoids | ||
| # this by using only u values, which are accurately stored by the ODE integrator. | ||
| ud_l = (ub_l .- ua_l) ./ (psi_ir_l - psi_il_l) | ||
| ud_r = (ub_r .- ua_r) ./ (psi_ir_r - psi_il_r) | ||
| # Evaluate u and dξ/dψ at lpsi and rpsi from the stored ODE solution | ||
| if !use_du_store | ||
|
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. In the long term, I think 3 different methods here seems overkill, but from what I can find this is a faithful reproduction of the current state of the Fortran so its a good starting point, just wanted to note |
||
| il_l, ir_l, _ = _psi_bracket(psi_store_all, lpsi, nstep) | ||
| il_r, ir_r, _ = _psi_bracket(psi_store_all, rpsi, nstep) | ||
|
|
||
| u_node = ForceFreeStates_results.u_store | ||
| ud_node = ForceFreeStates_results.ud_store | ||
| ua_l = u_node[resnum, :, 1, il_l] | ||
|
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. I also feel like a large amount of this file is unpacking indices and its ugly. Again, a future fix tho |
||
| ub_l = u_node[resnum, :, 1, ir_l] | ||
| ua_r = u_node[resnum, :, 1, il_r] | ||
| ub_r = u_node[resnum, :, 1, ir_r] | ||
| dua_l = ud_node[resnum, :, 1, il_l] | ||
| dub_l = ud_node[resnum, :, 1, ir_l] | ||
| dua_r = ud_node[resnum, :, 1, il_r] | ||
| dub_r = ud_node[resnum, :, 1, ir_r] | ||
|
|
||
| psi_il_l = psi_store_all[il_l] | ||
| psi_ir_l = psi_store_all[ir_l] | ||
| psi_il_r = psi_store_all[il_r] | ||
| psi_ir_r = psi_store_all[ir_r] | ||
|
|
||
| u_l = _hermite_cubic_val(ua_l, ub_l, dua_l, dub_l, psi_il_l, psi_ir_l, lpsi) | ||
| u_r = _hermite_cubic_val(ua_r, ub_r, dua_r, dub_r, psi_il_r, psi_ir_r, rpsi) | ||
| ud_l = (ub_l .- ua_l) ./ (psi_ir_l - psi_il_l) | ||
| ud_r = (ub_r .- ua_r) ./ (psi_ir_r - psi_il_r) | ||
| elseif use_el | ||
| u1m_l, du1m_l = _el_solution_at(lpsi, ForceFreeStates_results, ffit, equil, ffs_intr, nstep) | ||
| u1m_r, du1m_r = _el_solution_at(rpsi, ForceFreeStates_results, ffit, equil, ffs_intr, nstep) | ||
| u_l = u1m_l[resnum, :] | ||
|
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. Can you pass resnum to this function too to make this more readable? Such that _solution_at and _el_solution_at just return u_l, u_r, ud_l, and ud_r directly? This section of code is already super difficult to read as a human because of the amount of lines dedicated to naming left and right variables |
||
| u_r = u1m_r[resnum, :] | ||
| ud_l = du1m_l[resnum, :] | ||
| ud_r = du1m_r[resnum, :] | ||
| else | ||
| u1m_l, du1m_l = _solution_at(lpsi, sing_surf.psifac, ForceFreeStates_results, equil, ffs_intr, nstep) | ||
| u1m_r, du1m_r = _solution_at(rpsi, sing_surf.psifac, ForceFreeStates_results, equil, ffs_intr, nstep) | ||
| u_l = u1m_l[resnum, :] | ||
| u_r = u1m_r[resnum, :] | ||
| ud_l = du1m_l[resnum, :] | ||
| ud_r = du1m_r[resnum, :] | ||
| end | ||
|
|
||
| q_l = equil.profiles.q_spline(lpsi) | ||
| q1_l = equil.profiles.q_deriv(lpsi) | ||
|
|
@@ -242,10 +358,11 @@ function compute_singular_coupling_metrics!( | |
| jump_vec = Vector{ComplexF64}(undef, numpert_total) | ||
| for k in 1:numpert_total | ||
| ck = @view C_coeffs[:, k] | ||
| xsp_l = dot(u_l, ck) | ||
| xsp1_l = dot(ud_l, ck) | ||
| xsp_r = dot(u_r, ck) | ||
| xsp1_r = dot(ud_r, ck) | ||
| # unconjugated contraction, dot would conjugate u | ||
|
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. Bad Claude comment - it always tries to reference code it got rid of. Its ok to just fix the bug here and not explain why code that is no longer there was removed |
||
| xsp_l = transpose(u_l) * ck | ||
| xsp1_l = transpose(ud_l) * ck | ||
| xsp_r = transpose(u_r) * ck | ||
| xsp1_r = transpose(ud_r) * ck | ||
| bwp1_l = 2π * im * chi1 * (singfac_l * xsp1_l - nn * q1_l * xsp_l) | ||
| bwp1_r = 2π * im * chi1 * (singfac_r * xsp1_r - nn * q1_r * xsp_r) | ||
| jump_vec[k] = bwp1_r - bwp1_l | ||
|
|
||
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.
This feels super inefficient - more or less every integrator step (if save_interval = 1), we compute the derivative twice for no reason, not just when we renormalize the solutions.
If you redo
OdeStatebased on my comment below, you could just make it such that it stores all the necessary information at the end ofsing_der!in odet.u and new variables odet.du and odet.xi_s. You could then keep the same logic of only recomputing if u has changed.I think there are even cleaner ways to do this but I think this would be relatively simple but helpful
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.
If you're feeling adventurous, I'm not realizing this could just be a Utility function since we call it 4 separate times