Skip to content

Commit dac2d89

Browse files
authored
Merge pull request #306 from OpenFUSIONToolkit/feature/3Dvac_nfp
VACUUM (3D) - taking advantage of field period symmetry for nonaxisymmetric vacuum calculations
2 parents 908099e + 7d1eecb commit dac2d89

18 files changed

Lines changed: 712 additions & 1599 deletions

benchmarks/benchmark_fourier_transforms.jl

Lines changed: 0 additions & 237 deletions
This file was deleted.

src/Equilibrium/CoordinateInvariant.jl

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ w(θ) = √(J·|∇ψ|).
5454
5555
Operationally, sqrtamat is the mode-space √weight operator: for a field b with
5656
Fourier coefficients b_fft, it satisfies the identity
57-
`‖sqrtamat·b_fft‖² = N² · ∫ |b|² · J|∇ψ| dθ`
57+
`‖sqrtamat·b_fft‖² = N² · ∫ |b|² · J|∇ψ| dθ`
5858
which is Jacobian-invariant on a given flux surface (see
5959
`scripts/test_power_norm_invariance.jl`).
6060
@@ -78,13 +78,8 @@ function compute_sqrtamat(
7878
e_k .= 0.0
7979
e_k[k] = 1.0 + 0.0im
8080

81-
# Standard backward FT: f(θ_j) = (1/N) Σ_m c_m exp(-imθ_j)
82-
# exp(-imθ) = cos(mθ) - i·sin(mθ), so:
83-
# Re(f) = (1/N)(cslth·Re(c) + snlth·Im(c))
84-
# Im(f) = (1/N)(cslth·Im(c) - snlth·Re(c))
85-
real_part = (ft.cslth * real.(e_k) .+ ft.snlth * imag.(e_k)) ./ mtheta
86-
imag_part = (ft.cslth * imag.(e_k) .- ft.snlth * real.(e_k)) ./ mtheta
87-
theta_vec = complex.(real_part, imag_part)
81+
# Standard backward FT: f(θ_j) = (1/N) Σ_m c_m exp(-imθ_j) = (transpose(basis) * c) / N
82+
theta_vec = (transpose(ft.basis) * e_k) ./ mtheta
8883

8984
# Multiply pointwise by √(J·|∇ψ|) in theta-space
9085
theta_vec .*= sqrt_jdp

src/ForceFreeStates/ForceFreeStatesStructs.jl

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ A mutable struct holding data related to the singular surfaces in the equilibriu
1111
- `n::Vector{Int}` - Toroidal mode number(s)
1212
- `q::Float64` - Safety factor (= m/n)
1313
- `q1::Float64` - Derivative of safety factor with respect to ψ
14-
- `grri::Array{Float64,2}` - Interior Green's function at this surface [2*mthvac, 2*mpert]
15-
- `grre::Array{Float64,2}` - Exterior Green's function at this surface [2*mthvac, 2*mpert]
14+
- `grri::Array{ComplexF64,2}` - Interior Green's function at this surface [mthvac, mpert]
15+
- `grre::Array{ComplexF64,2}` - Exterior Green's function at this surface [mthvac, mpert]
1616
- `delta_prime::Vector{ComplexF64}` - **STUB (not physically valid)**. Per-surface ca-based Δ' estimate retained for future work / debugging only. The physically valid Δ' is `ForceFreeStatesInternal.delta_prime_matrix`, computed via the STRIDE global BVP (Glasser 2018 PoP 25, 032501). Do not use this field for tearing-stability analysis; do not expect agreement with `delta_prime_matrix`.
1717
- `delta_prime_col::Matrix{ComplexF64}` - **STUB (not physically valid)**. Per-surface ca-based Δ' column retained for future work / debugging only. Shape (numpert_total × n_res_modes); `delta_prime_col[j, i] = (ca_r[j,ipert_res_i,2] - ca_l[j,ipert_res_i,2]) / (4π²·psio)`. The diagonal element matches the (also stubbed) `delta_prime[i]`. Only populated for the Riccati/parallel FM paths. The physically valid Δ' is `ForceFreeStatesInternal.delta_prime_matrix`; this field exists for future development on intra-surface coupling diagnostics, not for production use.
1818
"""
@@ -23,8 +23,8 @@ A mutable struct holding data related to the singular surfaces in the equilibriu
2323
n::Vector{Int} = Int[]
2424
q::Float64 = 0.0
2525
q1::Float64 = 0.0
26-
grri::Array{Float64,2} = Array{Float64}(undef, 0, 0)
27-
grre::Array{Float64,2} = Array{Float64}(undef, 0, 0)
26+
grri::Array{ComplexF64,2} = Array{ComplexF64}(undef, 0, 0)
27+
grre::Array{ComplexF64,2} = Array{ComplexF64}(undef, 0, 0)
2828
delta_prime::Vector{ComplexF64} = ComplexF64[]
2929
delta_prime_col::Matrix{ComplexF64} = Matrix{ComplexF64}(undef, 0, 0)
3030
ua_left::Array{ComplexF64,3} = Array{ComplexF64}(undef, 0, 0, 0) # asymptotic basis at left inner-layer boundary
@@ -410,8 +410,8 @@ Populated in `Free.jl`.
410410
- `et::Vector{ComplexF64}` - Total energy eigenvalues of the pencil (W, N): power-normalized and invariant to the working (Jacobian) coordinate; et = ep + ev per mode
411411
- `n_tor_idx::Vector{Int}` - 0-based toroidal mode number index of each sorted eigenvalue (numpert_total). Needed in `write_imas`
412412
- `vacuum_eigenvalue::Float64` - Least stable (minimum) eigenvalue of the pencil (wv, N), clamped to zero
413-
- `grri::Array{Float64, 2}` - Interior Green's function matrices (2 * mthvac * nzvac × 2 * numpert_total)
414-
- `grre::Array{Float64, 2}` - Exterior Green's function matrices (2 * mthvac * nzvac × 2 * numpert_total)
413+
- `grri::Array{ComplexF64, 2}` - Interior Green's function matrices (2 * mthvac * nzvac × numpert_total)
414+
- `grre::Array{ComplexF64, 2}` - Exterior Green's function matrices (2 * mthvac * nzvac × numpert_total)
415415
- `plasma_pts::Array{Float64, 3}` - Cartesian coordinates of plasma points, shape (mthvac * nzvac) × 3 for (x, y, z)
416416
- `wall_pts::Array{Float64, 3}` - Cartesian coordinates of wall points, shape (mthvac * nzvac) × 3 for (x, y, z)
417417
"""
@@ -429,9 +429,8 @@ Populated in `Free.jl`.
429429
et::Vector{ComplexF64} = Vector{ComplexF64}(undef, numpert_total)
430430
n_tor_idx::Vector{Int} = zeros(Int, numpert_total)
431431
vacuum_eigenvalue::Float64 = NaN
432-
433-
grri::Array{Float64,2} = Array{Float64}(undef, 2 * numpoints, 2 * numpert_total)
434-
grre::Array{Float64,2} = Array{Float64}(undef, 2 * numpoints, 2 * numpert_total)
432+
grri::Array{ComplexF64,2} = Array{ComplexF64}(undef, 2 * numpoints, numpert_total)
433+
grre::Array{ComplexF64,2} = Array{ComplexF64}(undef, 2 * numpoints, numpert_total)
435434
plasma_pts::Array{Float64,2} = Array{Float64}(undef, numpoints, 3)
436435
wall_pts::Array{Float64,2} = Array{Float64}(undef, numpoints, 3)
437436
end

src/ForceFreeStates/Free.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ and data dumping.
5454
end
5555

5656
# Compute vacuum response matrix in-place (handles 2D single-n, 2D multi-n block-diagonal, and 3D)
57-
vac_inputs = Vacuum.VacuumInput(equil, psilim, ctrl.mthvac, ctrl.nzvac, mpert, mlow, npert, nlow)
57+
vac_inputs = Vacuum.VacuumInput(equil, psilim, ctrl.mthvac, ctrl.nzvac, mlow:mhigh, nlow:nhigh)
5858
Vacuum.compute_vacuum_response!(vac_data, vac_inputs, wall_settings)
5959

6060
# Scale by (m - n*q)(m' - n'*q) [Chance Phys. Plasmas 1997 2161 eq. 126]
@@ -176,7 +176,7 @@ q-window minimum.
176176
)
177177

178178
# Compute raw vacuum matrix at the actual scan psi (singfac NOT applied; free_compute_total applies it analytically)
179-
vac_inputs = Vacuum.VacuumInput(equil, psi_array[i], ctrl.mthvac, ctrl.nzvac, intr.mpert, intr.mlow, intr.npert, intr.nlow)
179+
vac_inputs = Vacuum.VacuumInput(equil, psi_array[i], ctrl.mthvac, ctrl.nzvac, intr.mlow:intr.mhigh, intr.nlow:intr.nhigh)
180180
wv, _, _, _, _ = Vacuum.compute_vacuum_response(vac_inputs, intr.wall_settings)
181181
@views wv_array[i, :, :] .= wv
182182
end

0 commit comments

Comments
 (0)