Galerkin Δ′ Solver (RDCON outer region)
The Galerkin solver is the Julia port of RDCON's gal.f — Dewar's singular Galerkin method for the outer-region Δ′ computation. Instead of integrating the Euler-Lagrange ODE radially, it discretizes the displacement on a packed radial grid of Hermite-cubic finite elements and solves a single global banded system. Cells adjacent to each rational surface ("resonant" and "extension" cells) carry the large/small power-law asymptotic solutions analytically, so the singular behavior is built into the basis rather than resolved numerically.
The solve produces the inter-surface Δ′ matrix and the PEST-3 matching blocks ($A'$, $B'$, $\Gamma'$, $\Delta'$), written to the HDF5 output under the galerkin/ group. These are the outer-region inputs to resistive matched-asymptotic stability analysis [Glasser 2016, Phys. Plasmas 23, 072505].
The implementation lives in src/ForceFreeStates/Galerkin/:
| File | Content |
|---|---|
GalerkinStructs.jl | Cell, interval, and workspace types mirroring the Fortran derived types |
GalerkinGrid.jl | Packed grid construction and local→global DOF mapping |
GalerkinAssembly.jl | Element-level assembly: Hermite basis, Gauss-Lobatto stiffness, resonant and extension cells, boundary conditions |
GalerkinSolution.jl | Reconstruct ξ(ψ) and analytic ξ′(ψ) on the gal-native grid |
GalerkinMatch.jl | DRIVEN/RPEC outer↔inner asymptotic matching and the matched OdeState for PerturbedEquilibrium |
GalerkinSolve.jl | Top-level driver galerkin_solve, banded solve, Δ′ extraction, PEST-3 blocks, HDF5 output |
API Reference
GeneralizedPerturbedEquilibrium.ForceFreeStates.GalCell — Type
Construct an empty GalCell for mpert poloidal modes and Hermite order np.
GeneralizedPerturbedEquilibrium.ForceFreeStates.GalCell — Type
GalCellOne grid cell of the Galerkin discretization. Port of Fortran cell_type (gal.f).
Fields
etype::GalCellType— none / resonant / extension(1,2)extra::GalSide— none / left / right (side of the neighbouring singular surface)emap::Int— global index of the extra (small resonant) DOF; 0 if nonemap::Matrix{Int}—(mpert, np+1)local→global DOF map (Fortrancell%map(mpert, 0:np))x::NTuple{2,Float64}—[x_left, x_right]of the cellx_lsode::Float64— resonant-integration truncation point (res cells only)erhs::ComplexF64,ediag::ComplexF64— resonant RHS and diagonal contributionsrhs::Matrix{ComplexF64}—(mpert, np+1)cell RHS (driving term from the big solution)emat::Matrix{ComplexF64}—(mpert, np+1)coupling of the extra DOF to the Hermite DOFsmat::Array{ComplexF64,4}—(mpert, mpert, np+1, np+1)cell stiffness block
GeneralizedPerturbedEquilibrium.ForceFreeStates.GalCellType — Type
Cell type flanking a singular surface (Fortran cell%etype, gal.f).
GeneralizedPerturbedEquilibrium.ForceFreeStates.GalInterval — Type
GalIntervalOne interval between consecutive singular surfaces (or between a surface and a domain edge). Port of Fortran interval_type (gal.f). x/dx are length nx+1 (Fortran 0:nx).
GeneralizedPerturbedEquilibrium.ForceFreeStates.GalMatchResult — Type
GalMatchResultCoil-driven RPEC matched solution from the outer↔inner asymptotic matching (Fortran rmatch match_rpec). Populated by gal_match_rpec (GalerkinMatch.jl) when gal_match_flag.
cout::Matrix{ComplexF64}—(2·msing, mcoil)outer-region plasma-solution coefficients.cin::Matrix{ComplexF64}—(2·msing, mcoil)inner-region coefficients.xi::Array{ComplexF64,3},xi_deriv::Array{ComplexF64,3}—(mpert, ngrid, mcoil)matched ξ(ψ) and analytic ξ′(ψ) on the gal grid, one column per coil drive (identity-at-edge basis).deltar::Matrix{ComplexF64}—(msing, 2)inner-layer matching data(Δ₁, Δ₂)per surface.bpen::Matrix{ComplexF64}—(msing, mcoil)inner-layer penetrated (reconnected) resonant field at each rational surface, one column per coil drive. Read off the GGJ inner solution at the layer center (X=0) exactly as Fortranmatch_output_solutionbuildsintotsol_b(match.f) — cusp-free, fit-free. Zero for the ideal branch (gal_ideal_flag), where the inner layer is skipped.inner_psi::Vector{Vector{Float64}}— per surface, the inner-layer ψ gridψ_s ± X·x0/v1(left wing reversed then right wing, so ψ ascends throughψ_s). Empty in the ideal branch.inner_xi::Vector{Matrix{ComplexF64}}— per surface, the inner-layer displacementξ_ψ(ψ)oninner_psi,(length(inner_psi[s]), mcoil), one column per coil drive. This is Fortranmatch_solution'sintotsol(deltac component 2):resc·(±Ξ₁·cin[2s] + Ξ₂·cin[2s-1]), odd parityΞ₁antisymmetric acrossψ_s, even parityΞ₂symmetric,resc=(v1/x0)^(1/2+p1). The raw inner-layer contribution only (no regular outer background added), so it shares the singular asymptote with the outer nearψ_s.rpec_eig::Vector{ComplexF64}— forced eigenvaluesγ_s = 2πi·n·f_sper surface.residual::Float64— relative linear-solve residual‖mat·cof − rmat‖/‖rmat‖.
GeneralizedPerturbedEquilibrium.ForceFreeStates.GalSide — Type
Which side of the adjacent singular surface a cell lies on (Fortran cell%extra).
GeneralizedPerturbedEquilibrium.ForceFreeStates.GalSingAsymp — Type
GalSingAsympTwo-sided singular asymptotics for the Galerkin solver: separate right (sig=+1) and left (sig=−1) power series, mirroring Fortran sing.f vmatr/vmatl. Each is evaluated at a positive distance |ψ−ψ_s| (real √, real pfac, no z<0 renormalization), so the directly-extracted Δ′ matches gal.f.
GeneralizedPerturbedEquilibrium.ForceFreeStates.GalWorkspace — Type
GalWorkspaceGlobal workspace for the Galerkin solve. Port of Fortran gal_type (gal.f).
intvl has length msing+1 (Fortran intvl(0:msing), accessed as intvl[ising+1]). kl = ku = mpert*(np+1). The banded mat storage layout depends on solver (gal.f):
"LU"→ldab = 2*kl + ku + 1, full band (LAPACKgbtrf!/gbtrs!)"cholesky"→ldab = kl + 1, lower band only (LAPACKpbtrf!/pbtrs!,'L')
GeneralizedPerturbedEquilibrium.ForceFreeStates.GalerkinResult — Type
GalerkinResultOutputs of the outer-region Galerkin solve.
Fields
delta::Matrix{ComplexF64}— Δ′ matrix,(nsol, 2*msing)withnsol = 2*msing; the small resonant coefficients (Fortrandelta, gal.f).Ap, Bp, Gammap, Deltap::Matrix{ComplexF64}— PEST-3 matching blocks, each(msing, msing)(Fortrangal_write_pest3_data, gal.f).msing::Int— number of resonant singular surfaces included.sing_psi, sing_q::Vector{Float64},sing_m, sing_n::Vector{Int}— per-surface identifiers.di::Vector{Float64},alpha::Vector{ComplexF64}— Mercier index and exponent per surface.solution::Union{Nothing,GalerkinSolution}— reconstructed radial ξ(ψ) and analytic ξ′(ψ) on the gal-native grid;nothingif no resonant surfaces.
GeneralizedPerturbedEquilibrium.ForceFreeStates.GalerkinSolution — Type
GalerkinSolutionReconstructed outer-region radial solution on the gal-native grid (ξ and analytic ξ′ consumed by PerturbedEquilibrium). Populated by gal_output_solution (GalerkinSolution.jl).
psi::Vector{Float64},q::Vector{Float64}— radial grid (inner→edge) and its safety factor.issing::Vector{Bool}— grid points sitting on a singular surface (skipped; left zero).xi::Array{ComplexF64,3},xi_deriv::Array{ComplexF64,3}—(mpert, ngrid, nsol)ξ and dξ/dψ.
GeneralizedPerturbedEquilibrium.ForceFreeStates.gal_make_grid! — Method
gal_make_grid!(intvl::GalInterval, ising::Int, msing::Int, sings::Vector{SingType},
nn::Int, psilow::Float64, psihigh::Float64, ctrl::ForceFreeStatesControl)Set the cell types (res/ext/ext1/ext2/none), sides, resonant integration limits, and packed node positions for interval ising (0-based, 0:msing). Port of Fortran gal_make_grid (gal.f), dx1dx2_flag=true path. intvl.x/intvl.dx have length nx+1, intvl.cells length nx (preallocated by the caller). sings is the filtered list of resonant surfaces (1-based, sings[ising] = Fortran sing(ising)).
Note (faithful to Fortran): the lower-surface width scale uses |nn·q1| (gal.f) but the upper-surface scale uses |q1| without nn (gal.f). Identical for nn = 1.
GeneralizedPerturbedEquilibrium.ForceFreeStates.gal_make_map! — Method
gal_make_map!(ws::GalWorkspace, mpert::Int)Build the local→global DOF numbering with the 2*mpert C¹ overlap between adjacent Hermite cells and the interleaved extra (small resonant) DOFs (emap). Port of Fortran gal_make_map (gal.f). Sets ws.ndim. The skip/emap bookkeeping reserves a global slot for each resonant coefficient and shares it between the res cell and its neighbouring ext cell.
GeneralizedPerturbedEquilibrium.ForceFreeStates.gal_pack — Method
gal_pack(nx::Int, pfac::Float64, side::String) -> Vector{Float64}Packed logical grid on [-1, 1]. Port of Fortran gal_pack (gal.f). For side="both" returns 2*nx+1 nodes (Fortran -nx:nx) clustered toward both ends when pfac < 1 (strong packing near the bracketing singular surfaces). pfac > 1 packs toward the centre; pfac == 1 is uniform.
GeneralizedPerturbedEquilibrium.ForceFreeStates.gal_resonant_surfaces — Method
gal_resonant_surfaces(intr, equil) -> (sings, psilow, psihigh)Resonant surfaces inside the integration domain, in increasing ψ (Fortran sing(1:msing)), with the domain bounds. psilow is raised above the axis by sing_min! when qlow > qmin (excludes the q<qlow core); it falls back to the equilibrium axis bound if sing_min! was not run (intr.psilow still 0).
GeneralizedPerturbedEquilibrium.ForceFreeStates.gal_assemble_mat! — Method
gal_assemble_mat!(ws, mpert)Scatter every cell's mat/emat/ediag into the banded global matrix ws.mat. Port of gal_assemble_mat (gal.f). Supports both "LU" (full band, offset = kl+ku+1) and "cholesky" (lower band only, offset = 1).
GeneralizedPerturbedEquilibrium.ForceFreeStates.gal_assemble_rhs! — Method
gal_assemble_rhs!(ws, mpert)Scatter every cell's rhs/erhs into the global RHS ws.rhs. Port of gal_assemble_rhs (gal.f). A new RHS column (isol) begins at each big-solution driver: an ext2-left cell or a res-right cell.
GeneralizedPerturbedEquilibrium.ForceFreeStates.gal_extension! — Method
gal_extension!(cell, ising, ffit, profiles, intr, asymps, sings, nn, nodes, weights)Build cell.emat, cell.ediag, cell.rhs, cell.erhs for an extension cell (ext/ext1/ext2). Port of gal_extension (gal.f). The big solution (column 1 of the gal asymptotic) drives the RHS; for ext cells the small solution (column 2) also forms the resonant coupling emat/ediag.
GeneralizedPerturbedEquilibrium.ForceFreeStates.gal_gauss_quad! — Method
gal_gauss_quad!(cell, ffit, profiles, intr, nodes, weights, swap_edge)Accumulate the nonresonant Hermite stiffness block cell.mat by Gauss-Lobatto quadrature. Port of gal_gauss_quad (gal.f). When swap_edge (the final cell of the last interval), the right-node value/slope DOFs (Fortran pb(2)↔pb(3)) are swapped so the edge-value DOF lands at index 3, matching the free-boundary BC (gal.f).
GeneralizedPerturbedEquilibrium.ForceFreeStates.gal_get_fkg — Method
gal_get_fkg(ffit, intr, x, q) -> (F, K, G)Evaluate the mpert×mpert matrices F = Q F̄ Qᴴ, K = Q K̄, G = Ḡ at flux x with safety factor q, where Q = diag(singfac) and singfac = m - n q (direct). Port of gal_get_fkg (gal.f). Uses the un-factored reduced ffit.fmats_gal (F̄), ffit.kmats (K̄), ffit.gmats (Ḡ). F/K/G are the ideal-MHD Euler–Lagrange coefficient matrices of the outer-region weak form (Glasser 2016, PoP 23, 112506).
GeneralizedPerturbedEquilibrium.ForceFreeStates.gal_hermite — Method
gal_hermite(x, x0, x1) -> (pb, qb)Hermite cubic basis values pb and derivatives qb on [x0, x1] (length 4, Fortran 0:3). Port of gal_hermite (gal.f).
GeneralizedPerturbedEquilibrium.ForceFreeStates.gal_resonant! — Method
gal_resonant!(cell, ising, ffit, profiles, intr, asymps, sings, nn, gal_tol, gal_gnstep, verbose)Compute the resonant-cell contributions cell.erhs, cell.ediag, cell.rhs, cell.emat by adaptive quadrature (QuadGK) of the residual-operator integrand. Port of gal_lsode_int/gal_lsode_der (gal.f): the Fortran LSODE accumulation is replaced by quadgk over [x_bdy, x_lsode].
GeneralizedPerturbedEquilibrium.ForceFreeStates.gal_set_boundary! — Method
gal_set_boundary!(ws, mpert, wv_edge)Apply the axis (u(0)=0 identity) and edge boundary conditions to the boundary cells' mat, plus the rpec coil-source RHS injection, before global assembly. Port of gal_set_boundary (gal.f), including its full three-way edge branch (rpec count ncoil = ws.nsol - 2*msing):
- rpec (
ncoil > 0): identity edge and a unit source at the edge value DOF of coil column2*msing+ipertfor each poloidal modeipert(gal.f).ws.rhsis already allocated andgal_assemble_rhs!only fills columns1:2*msing, so the injection is not clobbered. - free boundary (
wv_edge !== nothing): add the vacuum blockwvac·psio²at the edge value DOF (gal.f). - fixed boundary (else): identity edge (gal.f).
wv_edge is nothing for the rpec and fixed cases.
GeneralizedPerturbedEquilibrium.ForceFreeStates.gal_get_solution — Method
gal_get_solution(ws, asymps, sings, intr, x, iintvl, icell, isol; want_d=true)
-> (sol, dsol, iintvl, icell)Reconstruct the displacement sol (length mpert) — and, when want_d, its analytic radial derivative dsol = d(sol)/dψ — at flux x for solution column isol, from the solved Galerkin coefficients ws.sol. Port of gal_get_solution (gal.f, non-cut path). iintvl/icell are a monotone cell cursor advanced and returned for the next call. dsol is nothing if !want_d.
GeneralizedPerturbedEquilibrium.ForceFreeStates.gal_output_solution — Method
gal_output_solution(ws, asymps, sings, intr, profiles, psihigh) -> GalerkinSolutionBuild the gal-native packed radial grid (inner→edge; GAL_INTERP_NP_RES points per resonant/extension cell, GAL_INTERP_NP elsewhere, plus the edge point ψ=psihigh) and evaluate ξ AND the analytic ξ′ over it for every solution column. Port of gal_output_solution (gal.f); the binary/ASCII writes and the b_flag ξ→b^ψ conversion (off by default) are not ported — we keep ξ itself.
GeneralizedPerturbedEquilibrium.ForceFreeStates.gal_match_rpec — Method
gal_match_rpec(ctrl, equil, intr, gal_result) -> GalMatchResultSolve the coil-driven RPEC matching from the outer Δ′ (gal_result) and the per-surface inner-layer Δ. Requires gal_result.solution (reconstructed outer ξ/ξ′) and the rpec coil block gal_result.delta_coil.
The resistive path uses per-surface inputs ctrl.gal_eta/gal_rho/gal_rotation (length msing) + ctrl.gal_gamma. When ctrl.gal_ideal_flag, the inner layer is skipped and cout=0, so the matched solution is the bare ideal coil column (Fortran rmatch coil%ideal_flag) — the DCON/EL reference.
GeneralizedPerturbedEquilibrium.ForceFreeStates.gal_matched_odestate — Method
gal_matched_odestate(gal_result, ffit, intr) -> OdeStatePack the RPEC-matched outer solution into an OdeState shaped exactly like the shooting integrator's, so PerturbedEquilibrium consumes it unchanged. Mirrors Fortran idcon_build's gal branch (idcon.f) and globalsol.bin (the on-surface issing points are dropped, match.f):
u_store[:,:,1,ip] = ξ_ψ(matched fundamental matrix, mode×coil-drive, identity-at-edge basis)ud_store[:,:,1,ip] = dξ_ψ/dψ(analytic)ud_store[:,:,2,ip] = ξ_s = −A⁻¹(B·ξ′ + C·ξ)viaffit(same outer ideal-MHD relation assing_der!)u_store[:,:,2] = 0(PE never reads it; matches Fortran's unused u2 in the gal path)
The grid is the gal-native grid (inner→edge); step indexes the edge so build_flux_matrix derives the edge BC from u_store[:,:,1,step].
GeneralizedPerturbedEquilibrium.ForceFreeStates.empty_galerkin_result — Method
Empty GalerkinResult for a domain with no resonant surfaces.
GeneralizedPerturbedEquilibrium.ForceFreeStates.gal_make_arrays! — Method
gal_make_arrays!(ws, ctrl, equil, ffit, intr, asymps, sings, nn, wv_edge)Assemble the global banded matrix and RHS. For each cell: Gauss-Lobatto Hermite stiffness (gal_gauss_quad!), then the resonant (gal_resonant!) or extension (gal_extension!) contributions; then the boundary conditions and the scatter into ws.mat/ws.rhs. Port of gal_make_arrays.
GeneralizedPerturbedEquilibrium.ForceFreeStates.gal_pest3_blocks — Method
gal_pest3_blocks(delta, msing) -> (Ap, Bp, Gammap, Deltap)PEST-3 matching blocks (each msing×msing) as ± combinations of the left/right Δ′ entries. Port of gal_write_pest3_data. Row index 2i-1/2i = left/right of surface i.
GeneralizedPerturbedEquilibrium.ForceFreeStates.galerkin_solve — Method
galerkin_solve(ctrl::ForceFreeStatesControl, equil, ffit::FourFitVars,
intr::ForceFreeStatesInternal; vac_data=nothing) -> GalerkinResultCompute the outer-region Δ′ matching matrix by the singular Galerkin method. Port of gal_solve (gal.f). Single toroidal mode only (intr.npert == 1). Returns a GalerkinResult; if there are no resonant surfaces in the domain it returns an empty result.
vac_data (a VacuumData from free_run!) supplies the free-boundary edge term wv_edge = vac_data.wv · psio²; pass nothing for a fixed-boundary edge.
GeneralizedPerturbedEquilibrium.ForceFreeStates.write_galerkin! — Method
write_galerkin!(out_h5, result::GalerkinResult)Write the Galerkin Δ′ outputs into the open HDF5 file under the galerkin/ group. Replaces the Fortran delta_gw/pest3_data ASCII/binary outputs.
See also
docs/src/stability.md— the ForceFreeStates module this solver belongs to (EL system, metric matrices, singular surfaces)docs/src/inner_layer.md— inner-layer solutions to be matched against the outer-region Δ′