diff --git a/.github/workflows/make_test.yml b/.github/workflows/make_test.yml index d8077abcf..0e5044617 100644 --- a/.github/workflows/make_test.yml +++ b/.github/workflows/make_test.yml @@ -231,6 +231,14 @@ jobs: ulimit -s unlimited ../../../bin/rdcon && ../../../bin/rmatch && ../../../bin/gpec && ../../../bin/stride + - name: Run Soloviev energy decomposition example + run: | + export GPECHOME=$(pwd) + export LD_LIBRARY_PATH=$PREFIX/lib:$LD_LIBRARY_PATH + cd docs/examples/regression_solovev_recon_example + ulimit -s unlimited + ../../../bin/dcon && ../../../bin/gpec && python3 check_recon.py + make_gpec_ifort: runs-on: ubuntu-latest needs: build-netcdf-ifort @@ -310,3 +318,13 @@ jobs: cd docs/examples/regression_solovev_resistive_example ulimit -s unlimited ../../../bin/rdcon && ../../../bin/rmatch && ../../../bin/gpec && ../../../bin/stride + + - name: Run Soloviev energy decomposition example + run: | + source /opt/intel/oneapi/setvars.sh + export GPECHOME=$(pwd) + export LD_LIBRARY_PATH=$PREFIX/lib:$LD_LIBRARY_PATH + export FOR_DISABLE_STACK_TRACE=TRUE + cd docs/examples/regression_solovev_recon_example + ulimit -s unlimited + ../../../bin/dcon && ../../../bin/gpec && python3 check_recon.py diff --git a/docs/examples/regression_solovev_recon_example/README b/docs/examples/regression_solovev_recon_example/README new file mode 100644 index 000000000..58b42f60c --- /dev/null +++ b/docs/examples/regression_solovev_recon_example/README @@ -0,0 +1,27 @@ +Soloviev Energy Decomposition Regression Example +================================================ + +This example checks the GPEC ideal energy decomposition (effective_field_flag) against the DCON eigenvalue it reconstructs. It reuses the Soloviev equilibrium of regression_solovev_ideal_example (sol.in, equil.in, vac.in are copies). dcon.in differs only in delta_mlow=delta_mhigh=8: the effective-field form relies on identities that hold only for untruncated fields, so the default band leaves a ~21% error, while 8 extra harmonics on each side bring it to 0.4%. (standard_form_flag matches ep(1) to 0.02% even with the default band.) + +gpec.in selects the least stable eigenmode (mode_flag=t, mode=1) with reg_flag=f and no external field, turns off the usual outputs, and sets effective_field_flag=t and curl_check_flag=t. + +Run from this directory:: + + ../../../bin/dcon + ../../../bin/gpec + python3 check_recon.py + +check_recon.py reads the last GPEC_RECON block of gpec.log (gpec.log is appended to, so delete it before re-running by hand) and passes when + +- the recon psi range equals the DCON psi range (psilow to psilim), +- dW_p(recon-normalize) matches DCON ep(1) within 2% relative, and +- the curl check reports PASS (a consistency check on C; it passes even when the truncated run misses the energy). + +Reference values (gfortran 15, macOS arm64):: + + recon psi range = 1.0E-04 9.9999E-01 (same as DCON psi range) + dW_p(recon-normalize) = -1.04353063E+001 + DCON ep(1) = -1.03926066E+001 (relative difference 4.1E-03) + C verify: PASS (rel 2.8E-13 against tol 1E-08) + +The 2% tolerance leaves room for compiler and optimization differences while still catching a broken decomposition: the truncated 8-harmonic run above misses by 21%. diff --git a/docs/examples/regression_solovev_recon_example/check_recon.py b/docs/examples/regression_solovev_recon_example/check_recon.py new file mode 100755 index 000000000..55570eb87 --- /dev/null +++ b/docs/examples/regression_solovev_recon_example/check_recon.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +""" +Regression check for the GPEC energy decomposition (effective_field_flag). + +Run after ``dcon`` and ``gpec`` in this directory. Reads the last +GPEC_RECON block of gpec.log and checks that + + * the recon psi range covers the DCON psi range (psilow to psilim), + * dW_p(recon-normalize) matches DCON ep(mode) within REL_TOL, and + * the curl check reports PASS. + +Exits 0 on success and 1 on failure, so it can be chained in CI. +""" + +import sys + +REL_TOL = 0.02 # reference run agrees to 0.4%; see README +RANGE_TOL = 1e-6 + + +def last_recon_block(path): + with open(path) as f: + lines = f.read().splitlines() + starts = [i for i, l in enumerate(lines) + if l.startswith("GPEC_RECON final results:")] + if not starts: + sys.exit("FAIL: no GPEC_RECON block in " + path) + block = [] + for l in lines[starts[-1] + 1:]: + if l.startswith("GPEC_RECON"): + break + block.append(l) + return block + + +def values(block, key): + for l in block: + if key in l: + return [float(v) for v in l.split("=")[-1].split()] + sys.exit("FAIL: '{}' not found in GPEC_RECON block".format(key)) + + +def main(): + block = last_recon_block("gpec.log") + dw = values(block, "dW_p(recon-normalize)")[0] + ep = values(block, "DCON ep(")[0] + rlo, rhi = values(block, "recon psi range") + dlo, dhi = values(block, "DCON psi range") + rel = abs(dw - ep) / abs(ep) + verdict = [l.split(":")[-1].strip() for l in block if "C verify" in l] + range_ok = abs(rlo - dlo) <= RANGE_TOL and abs(rhi - dhi) <= RANGE_TOL + print("recon psi range = {:.6e} {:.6e}".format(rlo, rhi)) + print("DCON psi range = {:.6e} {:.6e}".format(dlo, dhi)) + print("dW_p(recon-normalize) = {: .8e}".format(dw)) + print("DCON ep = {: .8e}".format(ep)) + print("relative difference = {:.3e} (tol {:.1e})".format(rel, REL_TOL)) + print("curl check = {}".format(verdict[-1] if verdict + else "not run")) + ok = (range_ok and rel <= REL_TOL and bool(verdict) + and verdict[-1] == "PASS") + print("PASS" if ok else "FAIL") + return 0 if ok else 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/docs/examples/regression_solovev_recon_example/dcon.in b/docs/examples/regression_solovev_recon_example/dcon.in new file mode 100644 index 000000000..2b6b12189 --- /dev/null +++ b/docs/examples/regression_solovev_recon_example/dcon.in @@ -0,0 +1,61 @@ +&DCON_CONTROL + bal_flag=f ! Ideal MHD ballooning criterion for short wavelengths + mat_flag=t ! Construct coefficient matrices for diagnostic purposes + ode_flag=t ! Integrate ODE's for determining stability of internal long-wavelength mode (must be true for GPEC) + vac_flag=t ! Compute plasma, vacuum, and total energies for free-boundary modes + mer_flag=t ! Evaluate the Mercier criterian + + sas_flag=f ! Safety factor (q) limit determined as q_ir+dmlim where q_ir is the equil outermost rational + dmlim=0.2 ! See sas_flag + qlow=1.02 ! Integration initiated at q determined by minimum of qlow and q0 from equil + qhigh=1e3 ! Integration terminated at q limit determined by minimum of qhigh and qa from equil + sing_start=0 ! Start integration at the sing_start'th rational from the axis (psilow) + + nn=1 ! Toroidal mode number + delta_mlow=8 ! Expands lower bound of Fourier harmonics + delta_mhigh=8 ! Expands upper bound of Fourier harmonics + delta_mband=0 ! Integration keeps only this wide a band of solutions along the diagonal in m,m' + mthvac=64 ! Number of points used in splines over poloidal angle at plasma-vacuum interface. Overrides vac.in mth. + thmax0=1 ! Linear multiplier on the automatic choice of theta integration bounds for high-n ideal ballooning stability computation (strictly, -inf to -inf) + + kin_flag = f ! Kinetic EL equation (default: false) + con_flag = f ! Continue integration through layers (default: false) + kinfac1 = 1.0 ! Scale factor for energy contribution (default : 1.0) + kinfac2 = 1.0 ! Scale factor for torque contribution (default : 1.0) + kingridtype = 0 ! Regular grid method (default : 0) + passing_flag = f ! Includes passing particle effects (default: false) + ktanh_flag = t ! Ignore kinetic effects in the core smoothly (default: false) + ktc = 0.1 ! Parameter activated by ktanh_flag: roughly corresponds to core width ignored (default: 0.1) + ktw = 50.0 ! Parameter activated by ktanh_flag: width of hyper-tangential functions (default: 50.0) + ion_flag = t ! Include ion dW_k when kin_flag is true (summed with electron contribution if electron_flag true) + electron_flag = f ! Include electron dW_k when kin_flag is true (summed with ion contribution if ion_flag true) + + tol_nr=1e-6 ! Relative tolerance of dynamic integration steps away from rationals + tol_r=1e-7 ! Relative tolerance of dynamic integration steps near rationals + crossover=1e-2 ! Fractional distance from rational q at which tolerance is switched to tol_r + singfac_min=1e-4 ! Fractional distance from rational q at which ideal jump condition is enforced + ucrit=1e3 ! Maximum fraction of solutions allowed before re-normalized + + wv_farwall_flag = f ! Force nowall gpec calculations while calculating mutual inductance with the wall, when set true. + termbycross_flag = f ! Terminate ODE solver in the event of a zero crossing + + use_classic_splines = f ! Use a classical cubic spline instead of tri-diagonal solution for splines with extrapolation boundary conditions +/ +&DCON_OUTPUT + crit_break=t ! Color of the crit curve changes when crossing a singular surface + + ahb_flag=f ! Output normal magnetic field eigenvalues and eigenfunctons at plasma-vacuum interface (must be false for GPEC) + msol_ahb=1 ! Number of eigenfunctions output by ahb_flag=t ? + mthsurf0=1 ! Linear multiplier on number of boundary points used to display surface eigenfunctions for ahgb_flag=t + + bin_euler=t ! Output M psi-by-M euler-lagrange solutions to binary file euler.bin + bin_vac=f ! Output deprecated binary vacuum.bin containing vacuum energy matrices + euler_stride=1 ! Output only every euler_stride'th psi step to binary file + + out_bal1=f ! Ascii output for bal_flag poloidal functions + bin_bal1=f ! Binary output for bal_flag poloidal functions + out_bal2=f ! Ascii output for bal_flag functions + bin_bal2=f ! Binary output for bal_flag functions + + netcdf_out=t ! Replicate ascii dcon.out information in a netcdf file +/ diff --git a/docs/examples/regression_solovev_recon_example/equil.in b/docs/examples/regression_solovev_recon_example/equil.in new file mode 100644 index 000000000..994a522b4 --- /dev/null +++ b/docs/examples/regression_solovev_recon_example/equil.in @@ -0,0 +1,30 @@ +&EQUIL_CONTROL + eq_type="soloviev" ! Type of the input 2D equilibrium file. Accepts efit, chease, fluxgrid, transp, jsolver, lar, sol, etc. + eq_filename = "sol.in" ! Path to input file + + jac_type="pest" ! Working coordinate system for all DCON and GPEC calculations. Overrides individual powers. Accepts hamada, pest, boozer, equal_arc + power_bp=0 ! del.B ~ B_p**power_bp * B**power_b / R**power_r + power_b=0 ! del.B ~ B_p**power_bp * B**power_b / R**power_r + power_r=0 ! del.B ~ B_p**power_bp * B**power_b / R**power_r + + grid_type="ldp" ! Radial grid packing of equilibrium quantities. Accepts rho, ldp, pow1, pow2, or original. ldp packs points near the core and edge. pow* packs near the edge. + psilow=1e-4 ! Minimum value of psi, normalized from 0 to 1 + psihigh=0.99999 ! Maximum value of psi, normalized from 0 to 1 + mpsi=16 ! Number of radial grid intervals for equilibrium quantities + mtheta=256 ! Number of equally spaced poloidal grid intervals for all splines + newq0=0 ! Grad-Shafranov solution invariant adjustment of the q profile to give the specified value of q at the axis. Default 0 uses input file value. + use_classic_splines = f ! Use a classical cubic spline instead of tri-diagonal solution for splines with extrapolation boundary conditions + + input_only=f ! Generate information about the input and then quit with no further calculation +/ +&EQUIL_OUTPUT + gse_flag=f ! Produces diagnostic output for accuracy of solution to Grad-Shafranov equation + out_eq_1d=f ! Ascii output of 1D equilibrium file data + bin_eq_1d=f ! Binary output of 1D equilibrium file data + out_eq_2d=f ! Ascii output of 2D equilibrium file data + bin_eq_2d=t ! Binary output of 2D equilibrium file data (set true for GPEC) + out_2d=f ! Ascii output of processed 2D data + bin_2d=f ! Binary output of processed 2D data + dump_flag=f ! Binary dump of basic equilibrium data and 2D rzphi spline + out_ahg2msc=f ! Output deprecated ahg2msc.out files. This is used to communicate with vacuum, but is now done through memory. +/ diff --git a/docs/examples/regression_solovev_recon_example/gpec.in b/docs/examples/regression_solovev_recon_example/gpec.in new file mode 100644 index 000000000..b4ff8b6e4 --- /dev/null +++ b/docs/examples/regression_solovev_recon_example/gpec.in @@ -0,0 +1,92 @@ +&GPEC_INPUT + dcon_dir = './' ! Path to DCON outputs. Individual file paths are appended to this + idconfile = 'euler.bin' ! Eigenfunctions output by DCON run + ieqfile = 'psi_in.bin' ! Flux surfaces output by DCON run + rdconfile = 'globalsol.bin' ! Galerkin eigenfunctions output by resistive DCON run + + gal_flag = f ! Use the Galerkin eigenfunctions from resistive DCON instead of the ideal DCON shooting method solutions + + jac_in="pest" ! Coordinate system of external perturbation (affects data_flag and harmonic_flag). Options include: pest,boozer,hamada + jsurf_in=0 ! True(1) if external perturbation is area weighted (i.e. flux) + tmag_in =0 ! True(1) if external perturbation has toroidal angle defined by jac_in. False(0) if machine angle. + mthsurf =0 ! Number of poloidal angle gridpoints. Minimum of twice the Nyquist condition from DCON m limits is enforced. + + coil_flag=f ! Calculate external perturbation from coils in coil.in + + data_flag=f ! Apply perturbation from thrid party code output + data_type="surfmn" ! Name of said code. Choose: surfmn,?? + infile="path/to/file" ! Path to said output + nmin=0 ! Extra parameters needed to read surfmn files. + nmax=64 ! Extra parameters needed to read surfmn files. + mmin=-64 ! Extra parameters needed to read surfmn files. + mmax=64 ! Extra parameters needed to read surfmn files. + + harmonic_flag=f ! Apply self-defined perturbations + cosmn(3)=1e-4 ! Real part of m=3 component (n defined by DCON run). + sinmn(6)=1e-4 ! Imaginary part of m=6 component. etc. + + displacement_flag=f ! Perturbations are displacement (m). Default is field (T). + fixed_boundary_flag=f ! Total perturbation = external perturbation + + mode_flag = t ! Return only a single DCON eigenfunction defined by mode (ignores external pert.) + mode = 1 ! DCON eigen function index (1 is least stable) + filter_types = 'w' ! Isolate the filter_modes of energy (w), reluctance (r), permiability (p) or singular coupling (s) eigenvectors/singular-vectors + filter_modes = 0 ! Number of modes isolated, negative values isolate backwards from the last mode +/ +&GPEC_CONTROL + resp_index=0 ! Integer 0-4, determining method of inductance matrix formation. 0 uses energy identity, and is recommended. + sing_spot=5e-4 ! Minimum distance to singular surface in (m-nq) space + sing_resspot=1.0 ! If use_res_spot=t, use inverse cube root of lundquist number scaled by sing_resspot in psi space as spot size + use_res_spot=f ! Use resistive singular layer width instead of fixed sing_spot + sing_interpspot=1.0 ! Scaling of the interpolation width, in units of spot size, for smoothed outputs of singular surface quantities + sing_npsi=100 ! Number of points between rational surfaces used for b-normal splines across sin_spot + reg_flag=f ! Regularize the solution near resonant surfaces + reg_spot=5e-2 ! Regularize by (m-nq)^2/((m-nq)^2+reg_spot^2) + chebyshev_flag=f ! Fit Chebyshev polynomials to the displacement. + nche=20 ! Number of Chebyshev polynomials used + use_classic_splines = f ! Use a classical cubic spline instead of tri-diagonal solution for splines with extrapolation boundary conditions +/ +&GPEC_OUTPUT + effective_field_flag=t ! Bernstein effective-field reconstruction of dW_p (needs mode_flag=t, reg_flag=f, coil_flag=f) + curl_check_flag=t ! Check (curl C).grad(psi)=0 and report PASS/WARN + recon_out=t ! Write recon files and the gpec.log summary read by check_recon.py + jac_out="pest" ! Coordinate system of outputs + jsurf_out=0 ! True(1) for area weighted outputs (only appends standard gpec_control output) + tmag_out=1 ! True(1) for magnetic toroidal angle outputs (affects singcoup and singfld, appends gpec_control) + mlim_out=64 ! If positive, Fourier decomposed outputs will span -mlim_out<=m<=mlim_out. Default (<=0) outputs on internal m range. + + resp_flag=f ! Output energy, reluctance, inductance, and permeability eigenvalues, eigenvectors, and matrices + filter_flag=f ! Outputs energy, reluctance, permeability, and (optionally) the singular-coupling eigenmodes on the control surface + singcoup_flag=f ! Calculate coupling of each m to resonant surfaces + singfld_flag=f ! Output of resonant surface quantities (flux,current,etc.) + vsingfld_flag=f ! Output of vacuum resonant surface quantities + + pmodb_flag=f ! Outputs delta-B_Lagrangian + xbnormal_flag=f ! Outputs normal displacement and field in plasma + vbnormal_flag=f ! Outputs vacuum normal displacement and field in plasma + xclebsch_flag=f ! Outputs clebsch coordinate displacement for PENT + dw_flag=f ! Outputs self-consistent energy and torque profiles (when kin_flag=t from DCON) + + eqbrzphi_flag=f ! Outputs equilibrium field on regular (r,z) grid + brzphi_flag=f ! Outputs perturbed field on regular (r,z) grid + xrzphi_flag=f ! Outputs displacement on regular (r,z) grid + vbrzphi_flag=f ! Outputs field on regular (r,z) grid due to total boundary surface current (not a real field) + vvbrzphi_flag=f ! Outputs field on regular (r,z) grid due to external perturbation surface current (not a real field) + vsbrzphi_flag=f ! Outputs the field on regular (r,z) grid due to singular surface currents + ss_flag(3)=f ! Outputs the field on regular (r,z) grid due to the 3rd singular surface current, etc. + xbrzphifun_flag=f ! Outputs r,z, and perturbation vectors on psi,theta grid + arzphifun_flag=f ! Outputs r,z, and vector potential on psi,theta grid + + nr = 3 ! Number of radial grid points in rzphi outputs + nz = 3 ! Number of vertical grid points in rzphi outputs + fun_flag=f ! Write outputs to (r,z) in addition to default (psi,m) + flux_flag=f ! Write outputs to (psi,theta) in addition to default (psi,m) + bin_flag=f ! Write binary outputs for use with xdraw + bin_2d_flag=f ! Write 2D binary outputs for use with xdraw + max_linesout=0 ! Maximum length of ascii data tables enforced by increasing radial step size (iff .gt. 0). + verbose=t ! Print run log to terminal +/ +&GPEC_DIAGNOSE + timeit=t ! Print timer splits for major subroutines + radvar_flag=f ! Map various radial variables (rho,psi_tor) on psi_n grid +/ diff --git a/docs/examples/regression_solovev_recon_example/sol.in b/docs/examples/regression_solovev_recon_example/sol.in new file mode 100644 index 000000000..e7aba88fe --- /dev/null +++ b/docs/examples/regression_solovev_recon_example/sol.in @@ -0,0 +1,14 @@ +! Construct Soloviev equilibrium +&SOL_INPUT + mr = 128 ! number of radial grid zones + mz = 128 ! number of axial grid zones + ma = 128 ! number of flux grid zones + e = 1.6 ! elongation + a = 0.33 ! minor radius + r0 = 1.0 ! major radius + q0 = 1.9 ! safety factor at the o-point + p0fac=1 ! scale on-axis pressure (P-> P+P0*p0fac. beta changes. Phi,q constant) + b0fac=1 ! scale toroidal field at constant beta (s*Phi,s*f,s^2*P. bt changes. Shape,beta constant) + f0fac=1 ! scale toroidal field at constant pressure (s*f. beta,q changes. Phi,p,bp constant) +/ + diff --git a/docs/examples/regression_solovev_recon_example/vac.in b/docs/examples/regression_solovev_recon_example/vac.in new file mode 100644 index 000000000..7be7c808d --- /dev/null +++ b/docs/examples/regression_solovev_recon_example/vac.in @@ -0,0 +1,99 @@ +! **** Running DCON's input *** + +&MODES + mth = 32 + xiin(1:9) = 0 0 0 0 0 0 0 1 0 + lsymz = .TRUE. + leqarcw = 1 + lzio = 0 + lgato = 0 + lrgato = 0 +/ +&DEBUGS + checkd = .FALSE. + check1 = .FALSE. + check2 = .FALSE. + checke = .FALSE. + checks = .FALSE. + wall = .FALSE. + lkplt = 0 +/ +&VACDAT + ishape = 6 + aw = 0.05 + bw = 1.5 + cw = 0 + dw = 0.5 + tw = 0.05 + nsing = 500 + epsq = 1e-05 + noutv = 37 + idgt = 6 + idot = 0 + idsk = 0 + delg = 15.01 + delfac = 0.001 + cn0 = 1 +/ +&SHAPE + ipshp = 0 + xpl = 100 + apl = 1 + a = 0.2415 + b = 170 + bpl = 1 + dpl = 0 + r = 1 + abulg = 0.932 + bbulg = 17.0 + tbulg = 0.02 + qain = 2.5 +/ +&DIAGNS + lkdis = .FALSE. + ieig = 0 + iloop = 0 + lpsub = 1 + nloop = 128 + nloopr = 0 + nphil = 3 + nphse = 1 + xofsl = 0 + ntloop = 32 + aloop = 0.01 + bloop = 1.6 + dloop = 0.5 + rloop = 1.0 + deloop = 0.001 + mx = 21 + mz = 21 + nph = 0 + nxlpin = 6 + nzlpin = 11 + epslp = 0.02 + xlpmin = 0.7 + xlpmax = 2.7 + zlpmin = -1.5 + zlpmax = 1.5 + linterior = 2 +/ +&SPRK + nminus = 0 + nplus = 0 + mphi = 16 + lwrt11 = 0 + civ = 0.0 + sp2sgn1 = 1 + sp2sgn2 = 1 + sp2sgn3 = 1 + sp2sgn4 = 1 + sp2sgn5 = 1 + sp3sgn1 = -1 + sp3sgn2 = -1 + sp3sgn3 = -1 + sp3sgn4 = 1 + sp3sgn5 = 1 + lff = 0 + ff = 1.6 + fv = 1.6 1.6 1.6 1.6 1.6 1.0 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 1.6 +/ diff --git a/docs/namelists.rst b/docs/namelists.rst index b04b1d7f6..4629dade7 100644 --- a/docs/namelists.rst +++ b/docs/namelists.rst @@ -29,6 +29,14 @@ This namelist sets the inputs, controls the behavior, and determines the outputs .. include:: ../input/gpec.in :literal: +The MATCH Namelist +================== + +This namelist controls the MATCH code, including which DCON eigenfunction (sol_num) it reconstructs. + +.. include:: ../input/match.in + :literal: + The PENTRC Namelist ==================== diff --git a/docs/outputs.rst b/docs/outputs.rst index 22bce6bf7..d11f74c1d 100644 --- a/docs/outputs.rst +++ b/docs/outputs.rst @@ -208,6 +208,39 @@ A number of output files have a similar structure. Here the * in the file name i **Info** The false perturbed magnetic field in the vacuum region on the \left(r,z,\phi\right) grid calculated using the GPEC boundary surface current from the external fields alone. +Energy Decomposition Outputs +---------------------------- + +These outputs reconstruct the ideal plasma energy :math:`\delta W_p` of a single DCON eigenmode on every flux surface and break it into physically distinct terms. They require a pure DCON eigenmode: set mode_flag=t, choose mode (1 is the least stable eigenmode), and keep reg_flag=f and coil_flag=data_flag=harmonic_flag=f. GPEC stops at input if any of the three decomposition flags is set otherwise. The derivations, integrands and the meaning of every printed quantity are documented in :download:`docs/tex/recon/main.tex ` (build with pdflatex and bibtex). + +Each route prints a summary to the terminal that ends with its :math:`\delta W_p` total, the same total in DCON normalization (dW_p(recon-normalize) and its analogues), the DCON eigenvalue ep(mode) it should reproduce, and the :math:`\psi` range it integrated over beside the DCON range psilow to psilim. In the files below is the selected mode. recon_out=f keeps only the terminal summary, and recon_int ("spline", the default, or "trapezoid") selects the radial quadrature. + +The three routes converge differently with the number of poloidal harmonics. The standard form (standard_form_flag) reproduces the DCON eigenvalue at any truncation, because it evaluates the same Galerkin energy DCON minimizes. The two effective-field forms rely on identities that hold only for the untruncated fields, so they need extra harmonics: in the Soloviev regression example they miss ep(1) by about 20% with the default band and agree to 0.4% with delta_mlow=delta_mhigh=8. The curl check and the identity residual measure this truncation; a PASS on the curl check does not by itself confirm the energy. + +gpec_recon_*_sol.out + + **Flag** effective_field_flag + + **Info** Bernstein effective-field form :math:`2\delta W_p = \int |\mathbf{C}|^2/\mu_0 - \int K \xi_n^2`. integration: radial integrands and running totals. terms: per-(psi,theta) densities of the C and K terms. k, k_sigma_check, shear_fun, curvature: the K kernel and its shear, parallel-current and curvature parts on the (psi,theta,R,Z) grid. cwfun/cwmn, cvfun/cvmn, cv2fun/cv2mn: contravariant and covariant components of C in real and mode space. qvmn: covariant components of the bare perturbed field Q. A summary block is appended to gpec.log. + +gpec_recon_cveri_sol.out + + **Flag** curl_check_flag (with effective_field_flag) + + **Info** Pointwise residual of :math:`(\nabla\times\mathbf{C})\cdot\nabla\psi = 0` on the (psi,theta,R,Z) grid. The terminal and gpec.log report PASS or WARN for the maximum residual relative to the size of the two cancelling terms :math:`(|\partial_\theta C_\zeta|+|\partial_\zeta C_\theta|)/|J|`, together with the absolute maximum and rms. + +gpec_recon2_terms_sol.out, gpec_recon2_integration_sol.out + + **Flag** standard_form_flag + + **Info** Standard (first) form of the energy with the :math:`\gamma|\nabla\cdot\xi|^2` term omitted: :math:`|Q|^2/\mu_0`, :math:`\mathbf{j}\cdot(\mathbf{Q}\times\xi^*)`, and :math:`(\nabla\cdot\xi)^*(\xi\cdot\nabla p)` densities per surface (terms) and their radial integrals (integration). + +gpec_recon3_terms_sol.out, gpec_recon3_integration_sol.out + + **Flag** effective_field_terms_flag + + **Info** Split of :math:`|\mathbf{C}|^2 = |\mathbf{Q}|^2 + 2\mathrm{Re}(\mathbf{V}^*\cdot\mathbf{Q}) + |\mathbf{V}|^2` into A, B, Cpar and Iperp, with the identity check epf = A + B + Cpar + Iperp, per surface (terms) and integrated (integration). + Binary File Outputs ------------------- @@ -237,3 +270,8 @@ bnormal_spectrum.bin **Info** Surfmn type contours of the normal perturbed magnetic fields as a function of poloidal harmonic number and \Psi_{N}. +solutions.bin, solutions.bin + + **Flag** sol_num (match.in, MATCH) + + **Info** Ideal Euler-Lagrange solution built from DCON eigenfunction sol_num (1 is the most unstable). sol_num=1 writes solutions.bin; sol_num=n>1 writes solutions.bin so several eigenfunctions can be kept side by side. diff --git a/docs/tex/recon/QvsC_quant_n1.png b/docs/tex/recon/QvsC_quant_n1.png new file mode 100644 index 000000000..bb697a4be Binary files /dev/null and b/docs/tex/recon/QvsC_quant_n1.png differ diff --git a/docs/tex/recon/main.tex b/docs/tex/recon/main.tex new file mode 100644 index 000000000..e66099b3c --- /dev/null +++ b/docs/tex/recon/main.tex @@ -0,0 +1,3394 @@ +\documentclass[10pt,a4paper]{article} + +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} +\usepackage{lmodern} +\usepackage{geometry} +\geometry{margin=1in} +\usepackage{graphicx} +\usepackage{float} +\usepackage{amsmath, amssymb} +\usepackage{array} +\usepackage{hyperref} +\usepackage{cancel} +\usepackage{color} +\usepackage{pdflscape} +\numberwithin{equation}{section} + +\title{Bernstein--Chance decomposition of the perturbed potential energy in GPEC} +\author{Sunjae Lee} +\date{\today} + +\begin{document} + +\maketitle + +% \begin{abstract} +% This is the abstract of the document. Briefly describe the purpose and content here. +% \end{abstract} + +\section{Definition of covariant and contravariant basis} +The dcon context will be in black and \color{blue}the reference\cite{chance1992mhd} would be in blue. +\color{black}\subsection{Jacobian-scaled basis $(\vec{v}_i = \vec{e}_i/\mathcal{J})$} + +The $\vec{v}_i$ are \emph{not} the true covariant tangent basis $\vec{e}_i \equiv \partial\vec{r}/\partial q^i$, but rather its Jacobian-scaled counterpart: +\begin{equation} + \vec{v}_i = \frac{\vec{e}_i}{\mathcal{J}}, \qquad + \mathcal{J}^{-1} \equiv \vec{\nabla}\psi\cdot(\vec{\nabla}\theta\times\vec{\nabla}\zeta). +\end{equation} +Explicitly, +\begin{equation} + \begin{aligned} + \vec{v}_1 &= \vec{\nabla}\theta \times \vec{\nabla}\zeta, & + \vec{v}_2 &= \vec{\nabla}\zeta \times \vec{\nabla}\psi, & + \vec{v}_3 &= \vec{\nabla}\psi \times \vec{\nabla}\theta. + \end{aligned} +\end{equation} + +\begin{equation} + \vec{v}_i = v_{ij}\hat{e}_j. +\end{equation} + + +\begin{equation} +\begin{aligned} +v_{11} &= \vec{v}_1 \cdot \hat{e}_1 = \frac{1}{2r\mathcal{J}}\frac{\partial r^2}{\partial \psi}, \\[6pt] +v_{12} &= \vec{v}_1 \cdot \hat{e}_2 = \frac{2\pi r}{\mathcal{J}}\frac{\partial \eta}{\partial \psi}, \\[6pt] +v_{13} &= \vec{v}_1 \cdot \hat{e}_3 = \frac{R}{\mathcal{J}}\frac{\partial \phi}{\partial \psi}, \\[6pt] +\end{aligned} +\end{equation} + +\begin{equation} +\begin{aligned} +v_{21} &= \vec{v}_2 \cdot \hat{e}_1 = \frac{1}{2r\mathcal{J}}\frac{\partial r^2}{\partial \theta}, \\[6pt] +v_{22} &= \vec{v}_2 \cdot \hat{e}_2 = \frac{2\pi r}{\mathcal{J}}\left(1+\frac{\partial \eta}{\partial \theta}\right), \\[6pt] +v_{23} &= \vec{v}_2 \cdot \hat{e}_3 = \frac{R}{\mathcal{J}}\frac{\partial \phi}{\partial \theta}, \\[6pt] +\end{aligned} +\end{equation} + +\begin{equation} +\begin{aligned} +v_{31} &= \vec{v}_3 \cdot \hat{e}_1 = \frac{1}{2r\mathcal{J}}\frac{\partial r^2}{\partial \zeta} = 0, \\[6pt] +v_{32} &= \vec{v}_3 \cdot \hat{e}_2 = \frac{2\pi r}{\mathcal{J}}\frac{\partial \eta}{\partial \zeta} = 0, \\[6pt] +v_{33} &= \vec{v}_3 \cdot \hat{e}_3 = \frac{R}{\mathcal{J}}\frac{\partial \phi}{\partial \zeta} = \frac{2\pi R}{\mathcal{J}} +\end{aligned} +\end{equation} + + +\subsection{Gradient basis $(\vec w_i=\nabla q^i)$} +\begin{equation} +\begin{aligned} +\vec{w}_1 &= \vec{\nabla}\psi, & +\vec{w}_2 &= \vec{\nabla}\theta, & +\vec{w}_3 &= \vec{\nabla}\zeta, +\end{aligned} +\end{equation} + +\begin{equation} + \begin{aligned} + \vec{w}_i &= w_{ij}\hat{e}_j, \qquad & + \mathcal{J}\,\vec{v}_i \cdot \vec{w}_j &= \delta_{ij}. + \end{aligned} +\end{equation} + +% ==== All w_{ij}: cross-form -> v-components ==== +% Conventions: +% v_1 = ∇θ × ∇ζ, v_2 = ∇ζ × ∇ψ, v_3 = ∇ψ × ∇θ +% w_1 = ∇ψ, w_2 = ∇θ, w_3 = ∇ζ +% v_i = v_{ij} \hat e_j, w_i = w_{ij} \hat e_j +% General identity: w_{i\alpha} = J\, \varepsilon_{\alpha\beta\gamma}\, v_{j\beta} v_{k\gamma} +% with (i,j,k) cyclic: (1,2,3), (2,3,1), (3,1,2) +\begin{equation} + \begin{aligned} + % i = 1 + w_{11} + &= \vec\nabla\psi \cdot \hat e_1 + = \mathcal{J}\,\Big[\,(\vec\nabla\zeta \times \vec\nabla\psi)\times(\vec\nabla\psi \times \vec\nabla\theta)\,\Big]\!\cdot\hat e_1 + = \mathcal{J}\,(v_{22}v_{33}-v_{23}v_{32}) + = \frac{4\pi^{2} r R}{\mathcal{J}}\left(1+\frac{\partial \eta}{\partial \theta}\right), \\[6pt] + w_{12} + &= \vec\nabla\psi \cdot \hat e_2 + = \mathcal{J}\,\Big[\,(\vec\nabla\zeta \times \vec\nabla\psi)\times(\vec\nabla\psi \times \vec\nabla\theta)\,\Big]\!\cdot\hat e_2 + = \mathcal{J}\,(v_{23}v_{31}-v_{21}v_{33}) + = -\,\frac{\pi R}{r\,\mathcal{J}}\frac{\partial r^{2}}{\partial \theta}, \\[6pt] + w_{13} + &= \vec\nabla\psi \cdot \hat e_3 + = \mathcal{J}\,\Big[\,(\vec\nabla\zeta \times \vec\nabla\psi)\times(\vec\nabla\psi \times \vec\nabla\theta)\,\Big]\!\cdot\hat e_3 + = \mathcal{J}\,(v_{21}v_{32}-v_{22}v_{31}) + = 0 + \end{aligned} +\end{equation} + +\begin{equation} + \begin{aligned} + w_{21} + &= \vec\nabla\theta \cdot \hat e_1 + = \mathcal{J}\,\Big[\,(\vec\nabla\psi \times \vec\nabla\theta)\times(\vec\nabla\theta \times \vec\nabla\zeta)\,\Big]\!\cdot\hat e_1 + = \mathcal{J}\,(v_{32}v_{13}-v_{33}v_{12}) + = -\,\frac{4\pi^{2} r R}{\mathcal{J}}\frac{\partial \eta}{\partial \psi}, \\[6pt] + w_{22} + &= \vec\nabla\theta \cdot \hat e_2 + = \mathcal{J}\,\Big[\,(\vec\nabla\psi \times \vec\nabla\theta)\times(\vec\nabla\theta \times \vec\nabla\zeta)\,\Big]\!\cdot\hat e_2 + = \mathcal{J}\,(v_{33}v_{11}-v_{31}v_{13}) + = \frac{\pi R}{r\,\mathcal{J}}\frac{\partial r^{2}}{\partial \psi}, \\[6pt] + w_{23} + &= \vec\nabla\theta \cdot \hat e_3 + = \mathcal{J}\,\Big[\,(\vec\nabla\psi \times \vec\nabla\theta)\times(\vec\nabla\theta \times \vec\nabla\zeta)\,\Big]\!\cdot\hat e_3 + = \mathcal{J}\,(v_{31}v_{12}-v_{32}v_{11}) + = 0 + \end{aligned} +\end{equation} + +\begin{equation} + \begin{aligned} + w_{31} + &= \vec\nabla\zeta \cdot \hat e_1 + = \mathcal{J}\,\Big[\,(\vec\nabla\theta \times \vec\nabla\zeta)\times(\vec\nabla\zeta \times \vec\nabla\psi)\,\Big]\!\cdot\hat e_1 + = \mathcal{J}\,(v_{12}v_{23}-v_{13}v_{22}) + = \frac{2\pi r R}{\mathcal{J}}\!\left( + \frac{\partial \eta}{\partial \psi}\frac{\partial \phi}{\partial \theta} + - \frac{\partial \phi}{\partial \psi}\left(1+\frac{\partial \eta}{\partial \theta}\right) + \right), \\[6pt] + w_{32} + &= \vec\nabla\zeta \cdot \hat e_2 + = \mathcal{J}\,\Big[\,(\vec\nabla\theta \times \vec\nabla\zeta)\times(\vec\nabla\zeta \times \vec\nabla\psi)\,\Big]\!\cdot\hat e_2 + = \mathcal{J}\,(v_{13}v_{21}-v_{11}v_{23}) + = \frac{R}{2r\,\mathcal{J}}\!\left( + \frac{\partial \phi}{\partial \psi}\frac{\partial r^{2}}{\partial \theta} + - \frac{\partial r^{2}}{\partial \psi}\frac{\partial \phi}{\partial \theta} + \right), \\[6pt] + w_{33} + &= \vec\nabla\zeta \cdot \hat e_3 + = \mathcal{J}\,\Big[\,(\vec\nabla\theta \times \vec\nabla\zeta)\times(\vec\nabla\zeta \times \vec\nabla\psi)\,\Big]\!\cdot\hat e_3 + = \mathcal{J}\,(v_{11}v_{22}-v_{12}v_{21}) + = \frac{1}{2\pi R}. + \end{aligned} +\end{equation} + +\section{Coordinate system} + +The coordinate system comparing between DCON and reference\cite{chance1992mhd}. +In reference \cite{chance1992mhd}, $2 \pi \psi$ is poloidal flux +\begin{equation} +\Psi_p(\psi) = 2\pi \psi += \int_{0}^{2\pi} d\phi + \int_{\text{axis}}^{\psi} + \mathbf{B}_p \cdot d\mathbf{S} +\end{equation} + +\color{red}In DCON, $\psi_{dcon}$, $\theta_{dcon}$, $\zeta_{dcon}$ are all normalized in [0,1]. $\chi_{dcon}$ representing the poloidal flux function and $\alpha_{dcon}$ for SFL angle. Also to scale the actual poloidal flux $\chi\prime_{dcon}$ is used.($\chi\prime_{dcon} = 2\pi(ssibry-ssimag)$) +\color{black}\begin{align} +\mathbf{B} &= \nabla \chi_{dcon} \times \nabla \alpha_{dcon}\\ +% +&= \chi\prime_{dcon} \nabla \psi_{dcon} \times \nabla \alpha_{dcon}\\ +% +&= \chi\prime_{dcon} \nabla \psi_{dcon} \times (q\nabla \theta - \nabla \zeta)\\ +% +&= f_{dcon} \nabla \phi + \nabla \phi \times \frac{\chi\prime_{dcon}}{2\pi} \nabla \psi_{dcon} \\ +% +&= \color{blue} \nabla \psi \times (- \nabla \alpha)\\ +% +&= \color{blue} g(\psi) \nabla \phi + \nabla \phi \times \nabla \psi +\end{align} + +\begin{equation} + \alpha_{dcon} = \color{blue} {- \alpha / 2 \pi} \color{black}= q(\psi)\theta - \zeta +\end{equation} +\begin{equation} +g(\psi) \leftrightarrow \text{(toroidal field functions)} \leftrightarrow f_{dcon} +\end{equation} + +In the following, the blue-side variable $\psi$ is Chance's flux +coordinate. Its relation to the DCON normalized flux is +\[ +\color{blue}{\psi} +\;\;\longleftrightarrow\;\; +\color{black}\frac{\chi_{\mathrm{dcon}}}{2\pi} +\] +% +\[ +\color{blue}{\psi} += \color{black}\frac{\chi'_{\mathrm{dcon}}}{2\pi}\, +\psi_{\mathrm{dcon}}. +\] +Therefore +\[ +{\chi}_{\mathrm{dcon}} = \color{black}\chi'_{\mathrm{dcon}} \psi_{\mathrm{dcon}}. +\] + + +\subsection{Derivation of Eq.~(29) in GPEC note} + +\begin{equation} + \begin{aligned} + \mathbf{B} + &= \chi\prime \nabla \psi \times (q\nabla \theta - \nabla \zeta) = \chi\prime q \vec{v}_3 + \chi\prime \vec{v}_2 \\ + &= \mathcal{J}(B^\psi \vec{v}_1 + B^\theta \vec{v}_2 + B^\zeta \vec{v}_3) \\ + &= (B_\psi \vec{w}_1 + B_\theta \vec{w}_2 + B_\zeta \vec{w}_3) +\end{aligned} +\end{equation} + +% \begin{equation} +% S \;=\; -\,\frac{\vec{B} \times \vec{\nabla}\psi}{\lvert \vec{\nabla}\psi \rvert^{2}} +% \;\cdot\; +% \left(\vec{\nabla} \times +% \frac{\vec{B} \times \vec{\nabla} \psi}{\lvert \vec{\nabla} \psi \rvert^{2}}\right) +% \end{equation} + +\begin{equation} +\begin{aligned} +B^\theta &\equiv \vec{B}\cdot \vec{w}_2 = \frac{\chi'}{\mathcal{J}}, +\qquad +B^\zeta \equiv \vec{B}\cdot \vec{w}_3 = \frac{q\,\chi'}{\mathcal{J}}, \\[4pt] +\vec{B} \times \vec{w}_1 +&= \vec{B}\times\nabla\psi += \chi'\big(g^{\psi\zeta}-q\,g^{\psi\theta}\big)\,\nabla\psi ++ q\,\chi'\,g^{\psi\psi}\,\nabla\theta +- \chi'\,g^{\psi\psi}\,\nabla\zeta \\ +&= \chi'\big(g^{\psi\zeta}-q\,g^{\psi\theta}\big)\,\vec{w}_1 ++ q\,\chi'\,g^{\psi\psi}\,\vec{w}_2 +- \chi'\,g^{\psi\psi}\,\vec{w}_3, +\end{aligned} +\end{equation} + +\begin{equation} +(\vec{B} \times \vec{w}_1)^\psi = 0, \qquad +(\vec{B} \times \vec{w}_1)^\theta = \frac{\chi'\,(g_{23}+q\,g_{33})}{\mathcal{J}^{2}}, \qquad +(\vec{B} \times \vec{w}_1)^\zeta = -\,\frac{\chi'\,(g_{22}+q\,g_{23})}{\mathcal{J}^{2}}, +\end{equation} +where $g_{ij}=\mathcal J^2(\vec v_i\cdot\vec v_j)$ is the true covariant +metric. The important distinction is that $\mathbf B\times\nabla\psi$ is most +naturally written first in the gradient basis $\{\vec w_i\}$ and only then, if +needed, converted to contravariant components in the $\{\vec v_i\}$ basis. + +\subsection{Metric Tensor and Index Operations} + +Throughout this section we distinguish carefully between the true tangent basis +$\vec e_i=\partial\vec r/\partial q^i=\mathcal J\vec v_i$ and the dual gradient +basis $\vec e^{\,i}=\vec w_i=\nabla q^i$. The vector $\vec B$ is expanded as +\begin{equation} + \vec{B} = \mathcal{J}\,B^j\,\vec{v}_j = B^j\,\vec{e}_j + \qquad\text{(contravariant expansion)}, +\end{equation} +\begin{equation} + \vec{B} = B_j\,\vec{w}_j = B_j\,\vec{e}^j + \qquad\text{(covariant expansion)}, +\end{equation} +where the contravariant components satisfy +$B^i = \vec{B}\cdot\vec{w}_i$ and the covariant components satisfy +$B_i = \vec{B}\cdot\vec{e}_i$. These definitions are consistent because +\begin{equation} +\vec{B}\cdot\vec{w}_i += \mathcal{J} B^j \vec{v}_j\cdot\vec{w}_i += \mathcal{J} B^j \frac{\delta_{ji}}{\mathcal J} += B^i. +\end{equation} + +\subsubsection{Lowering Operator ($B^j \to B_i$)} +To transform contravariant components $B^j$ into covariant components $B_i$, we use the true covariant tangent basis $\vec{e}_i = \mathcal{J}\vec{v}_i$. +Since $B_i \equiv \vec{B}\cdot\vec{e}_i$ and $\vec{B} = \mathcal{J}B^j\vec{v}_j$: + +Using the definition $\vec{B} = \mathcal{J} B^j \vec{v}_j$ and taking the dot product with $\vec{e}_i = \mathcal{J}\vec{v}_i$: +\begin{equation} + \begin{aligned} + B_i &= \vec{B} \cdot \vec{e}_i \\ + &= (\mathcal{J} B^j \vec{v}_j) \cdot (\mathcal{J} \vec{v}_i) \\ + &= \mathcal{J}^2 (\vec{v}_i \cdot \vec{v}_j) B^j. + \end{aligned} +\end{equation} +Thus, the covariant metric tensor $g_{ij}$ must include the square of the Jacobian: +\begin{equation} + g_{ij} \equiv \mathcal{J}^2 (\vec{v}_i \cdot \vec{v}_j). +\end{equation} + +\subsubsection{Raising Operator ($B_j \to B^i$)} +To transform covariant components $B_j$ into contravariant components $B^i$, we use the gradient basis $\vec{w}_i$, which corresponds to the dual basis $\vec{e}^i$ (i.e., $\vec{e}^1 = \vec{w}_1 = \vec{\nabla}\psi$, etc.). + +Using the definition $\vec{B} = B_j \vec{w}_j$ and taking the dot product with $\vec{w}_i$: +\begin{equation} + \begin{aligned} + B^i &= \vec{B} \cdot \vec{w}_i \\ + &= (B_j \vec{w}_j) \cdot \vec{w}_i \\ + &= (\vec{w}_i \cdot \vec{w}_j) B_j. + \end{aligned} +\end{equation} +Unlike the lowering operator, the contravariant metric tensor $g^{ij}$ does not require the Jacobian: +\begin{equation} + g^{ij} \equiv \vec{w}_i \cdot \vec{w}_j. +\end{equation} + +\subsection{Derivation of Eq. (35) in gpec note} + +In accordance with the actual code implementation, $w_{11}$ and $w_{12}$ include the full +phase derivative $(1 + \partial_\theta\eta)$: +\[ +w_{11} = \frac{(2\pi)^2 r R}{\mathcal{J}} \left(1 + \frac{\partial \eta}{\partial \theta}\right), +\quad +v_{22} = \frac{2 \pi r}{\mathcal{J}} \left(1 + \frac{\partial \eta}{\partial \theta}\right), +\] +\[ +w_{12} = \frac{-\pi R}{r \mathcal{J}} \frac{\partial r^2}{\partial \theta}, +\quad +v_{21} = \frac{1}{2r \mathcal{J}} \frac{\partial r^2}{\partial \theta} +\] + +Note that $w_{11} = 2 \pi R v_{22}$ remains true with this definition, and +$w_{12} = -2 \pi R v_{21}$ is unchanged. + + +\[ +q\bigl({\vec{\nabla}} \psi \cdot {\vec{\nabla}} \theta \bigr) +- {\vec{\nabla}} \psi \cdot {\vec{\nabla}} \zeta +\] +\[ += q\,(w_{11} w_{21} + w_{12} w_{22}) + - (w_{11} w_{31} + w_{12} w_{32}) +\] +\[ += 2\pi R \mathcal{J} \Bigl[ + - q v_{33} (v_{22} v_{12} + v_{21} v_{11}) + + v_{13}(v_{22}^2 + v_{21}^2) + - v_{23}(v_{22} v_{12} + v_{11} v_{21}) +\Bigr] +\] +\[ += 2\pi R \mathcal{J} \Bigl[ + - (v_{22} v_{12} + v_{21} v_{11})(v_{23}+q v_{33}) + + v_{13}(v_{22}^2 + v_{21}^2) +\Bigr] +\] + +\section{Energy principle\cite{RevModPhys.76.1071}\cite{Park2009Ideal}} +\begin{align} +\delta W_p &= \frac{1}{2\mu_0} \int d\tau_p +\Big\{ |\mathbf{Q}|^2 - \mu_0\,\mathbf{j} \cdot \, \mathbf{Q} \times \boldsymbol{\xi}^* ++ \mu_0 \gamma p |\nabla \cdot \boldsymbol{\xi}|^2 ++ \mu_0 (\nabla \cdot \boldsymbol{\xi})^* \boldsymbol{\xi} \cdot \nabla p \Big\} \label{eq:ep_first}\\ +&= \frac{1}{2\mu_0} \int d\tau_p +\Big\{ |\mathbf{Q}_\perp|^2 ++ \frac{1}{B^2} |\mathbf{Q} \cdot \mathbf{B} - \mu_0\,\boldsymbol{\xi} \cdot \nabla p|^2 ++ \mu_0\,\sigma\, \mathbf{B} \times \boldsymbol{\xi}^* \cdot \mathbf{Q} ++ \mu_0 \gamma p |\nabla \cdot \boldsymbol{\xi}|^2 +- 2 \mu_0\,\boldsymbol{\xi} \cdot \nabla p\, \boldsymbol{\xi}^* \cdot \boldsymbol{\kappa} +\Big\} \label{eq:ep_intuitive}\\ +&= \frac{1}{2\mu_0} \int d\tau_p +\Big\{ |\mathbf{Q} + \xi_n (\mu_0\mathbf{j} \times \mathbf{n})|^2 ++ \mu_0 \gamma p |\nabla \cdot \boldsymbol{\xi}|^2 +- 2\,\mu_0\mathbf{j} \times \mathbf{n} \cdot (\mathbf{B} \cdot \nabla \mathbf{n})\, |\xi_n|^2 +\Big\}\label{eq:ep_bernstein}\\ +&= \frac{1}{2\mu_0} \int d\tau_p +\Big\{ |\mathbf{Q} + \xi_n (\mu_0 \mathbf{j} \times \mathbf{n})|^2 ++ \mu_0 \gamma p |\nabla \cdot \boldsymbol{\xi}|^2 +- \mu_0 K\, |\xi_n|^2 +\Big\}\label{eq:ep_bernsteinK} +\end{align} + +The derivation of \eqref{eq:ep_intuitive} can be found in \cite{RevModPhys.76.1071} and the original derivation of \eqref{eq:ep_bernstein} is in \cite{Bernstein1983}. +The $K$ term in \eqref{eq:ep_bernsteinK} is defined in \cite{chance1992mhd}. + +\subsection{Transformation of $\delta W_F$ into the Bernstein form (modified SI/DCON units)} +\label{sec:bernstein_transform} + +We follow the derivation of \cite{Bernstein1983}, while keeping the present SI/DCON conventions. +Throughout this subsection, we employ the following definitions: +\begin{equation} +\sigma \equiv \frac{\mathbf{j}\cdot\mathbf{B}}{B^2}, \qquad +\mathbf Q \equiv \nabla\times(\boldsymbol{\xi}\times\mathbf B), \qquad +\mu_0\mathbf j = \nabla\times\mathbf B, +\end{equation} +\begin{equation} +\hat{\mathbf n}\equiv \frac{\nabla\psi}{|\nabla\psi|}, \qquad +\xi_n \equiv \hat{\mathbf n}\cdot\boldsymbol{\xi}, \qquad +\delta\mathbf B = \mathbf Q, +\end{equation} +with \(p=p(\psi)\). + +We begin from +\begin{equation} +2\mu_0 \delta W_F += +\int d^3r\left\{ +|\mathbf Q|^2 ++\mu_0\gamma p\,|\nabla\cdot\boldsymbol{\xi}|^2 ++\mu_0(\nabla\cdot\boldsymbol{\xi})^*\,\boldsymbol{\xi}\cdot\nabla p ++(\boldsymbol{\xi}^*\times\mathbf Q)\cdot(\nabla\times\mathbf B) +\right\}. +\end{equation} + +The vectors $\mathbf{B}$, $\nabla p$, and $\nabla\times\mathbf{B}$ form a local +basis away from degenerate points where \(p'=0\) or where \(\mathbf B\) and +\(\nabla\times\mathbf B\) become linearly dependent. Here $\nabla p$ is +orthogonal to both $\mathbf B$ and $\nabla\times\mathbf B$ (since +$\mathbf{B}\cdot\nabla p = (\nabla\times\mathbf{B})\cdot\nabla p = 0$, i.e. +$\mathbf B$ and $\mathbf j$ lie in the flux surface), while $\mathbf B$ and +$\nabla\times\mathbf B$ are independent but not orthogonal +($\mathbf{B}\cdot(\nabla\times\mathbf{B})=\mu_0 j_\parallel B\neq0$). We thus +introduce +\begin{equation} +\boldsymbol{\xi} += +a\,\nabla p + b\,\nabla\times\mathbf B + c\,\mathbf B. +\end{equation} +Since \(p=p(\psi)\), +\begin{equation} +\nabla p = p'(\psi)\nabla\psi = p'(\psi)|\nabla\psi|\,\hat{\mathbf n}, +\qquad +\xi_n = \hat{\mathbf n}\cdot\boldsymbol{\xi} = a\,p'(\psi)|\nabla\psi|, +\end{equation} +so that +\begin{equation} +a\,\nabla p = \xi_n\,\hat{\mathbf n}. +\end{equation} +Also, because \(\mathbf B\cdot\nabla p=0\) and \((\nabla\times\mathbf B)\cdot\nabla p=0\), +\begin{align} +\boldsymbol{\xi}\times\mathbf B +&= +-a\,\mathbf B\times\nabla p ++b\,(\nabla\times\mathbf B)\times\mathbf B += +-a\,\mathbf B\times\nabla p+\mu_0 b\,\nabla p, \\ +\boldsymbol{\xi}\times(\nabla\times\mathbf B) +&= +-a\,(\nabla\times\mathbf B)\times\nabla p ++c\,\mathbf B\times(\nabla\times\mathbf B) += +-a\,(\nabla\times\mathbf B)\times\nabla p-\mu_0 c\,\nabla p. +\end{align} +Hence +\begin{equation} +\mathbf Q += +\nabla\times\bigl(-a\,\mathbf B\times\nabla p+\mu_0 b\,\nabla p\bigr). +\end{equation} + +Substituting $\boldsymbol{\xi} = a\,\nabla p + b\,\nabla\times\mathbf B + c\,\mathbf B$: +\begin{align} +\nabla\cdot\boldsymbol{\xi} &= \nabla\cdot(a\nabla p)+\nabla\cdot(b\nabla\times\mathbf B+c\mathbf B), \quad \boldsymbol{\xi}\cdot\nabla p = a(\nabla p)^2. +\end{align} +By the product rule: +\begin{equation} +a(\nabla p)^2\nabla\cdot(b\nabla\times\mathbf B+c\mathbf B) = +\nabla\cdot\Bigl[a(\nabla p)^2(b\nabla\times\mathbf B+c\mathbf B)\Bigr] +- (b\nabla\times\mathbf B+c\mathbf B)\cdot\nabla\bigl[a(\nabla p)^2\bigr]. +\end{equation} +After integration over a volume bounded by flux surfaces, the divergence term +has no surface contribution, giving +\begin{align} +a(\nabla p)^2\nabla\cdot\bigl(b\nabla\times\mathbf B+c\mathbf B\bigr) +&= +-\bigl(b\nabla\times\mathbf B+c\mathbf B\bigr)\cdot\nabla\!\bigl[a(\nabla p)^2\bigr] +\nonumber\\ +&\quad ++\nabla\cdot\!\Bigl[a(\nabla p)^2\bigl(b\nabla\times\mathbf B+c\mathbf B\bigr)\Bigr]. +\end{align} +For a volume bounded by flux surfaces, \(\hat{\mathbf n}\cdot\mathbf B=0\) and \(\hat{\mathbf n}\cdot(\nabla\times\mathbf B)=0\), so the surface integral of the divergence term vanishes. Using also \(\nabla\cdot\mathbf B=0\) and \(\nabla\cdot(\nabla\times\mathbf B)=0\), one obtains +\begin{align} +2\mu_0 \delta W_F += +\int d^3r\Bigl\{ +&\mu_0\gamma p\,|\nabla\cdot\boldsymbol{\xi}|^2 ++\Bigl|\mathbf Q+a(\nabla\times\mathbf B)\times\nabla p\Bigr|^2 +\nonumber\\ +&\quad +-a^2\Bigl|(\nabla\times\mathbf B)\times\nabla p\Bigr|^2 ++a(\nabla\times\mathbf B)\times\nabla p\cdot\nabla\times(a\,\mathbf B\times\nabla p) +\nonumber\\ +&\quad ++\mu_0 a(\nabla p)^2\nabla\cdot(a\nabla p) +\Bigr\}. +\end{align} + +Using $\mu_0\nabla p = (\nabla\times\mathbf B)\times\mathbf B$ and $\hat{\mathbf n}\cdot\mathbf B=0$, the last three scalar terms combine to: +\begin{align} +&-a^2\Bigl|(\nabla\times\mathbf B)\times\nabla p\Bigr|^2 +a(\nabla\times\mathbf B)\times\nabla p\cdot\nabla\times(a\,\mathbf B\times\nabla p) +\mu_0 a(\nabla p)^2\nabla\cdot(a\nabla p) \nonumber\\ +&= a^2(\nabla p)^2 \Bigl[-|\nabla\times\mathbf B|^2 +(\nabla\times\mathbf B)\times\hat{\mathbf n}\cdot\Bigl((\hat{\mathbf n}\cdot\nabla)\mathbf B-(\mathbf B\cdot\nabla)\hat{\mathbf n}\Bigr)\Bigr]. +\end{align} + +Now, since \(\hat{\mathbf n}\cdot\mathbf B=0\), +\begin{equation} +0=\nabla(\hat{\mathbf n}\cdot\mathbf B) += +(\hat{\mathbf n}\cdot\nabla)\mathbf B ++(\mathbf B\cdot\nabla)\hat{\mathbf n} ++\hat{\mathbf n}\times(\nabla\times\mathbf B) ++\mathbf B\times(\nabla\times\hat{\mathbf n}). +\end{equation} +Moreover, +\begin{equation} +(\nabla\times\mathbf B)\cdot\hat{\mathbf n}=0, +\end{equation} +and, since \(\hat{\mathbf n}=\lambda\nabla\psi\) with \(\lambda=|\nabla\psi|^{-1}\), +\begin{equation} +\nabla\times\hat{\mathbf n} += +\nabla\lambda\times\nabla\psi, +\qquad +\hat{\mathbf n}\cdot(\nabla\times\hat{\mathbf n})=0. +\end{equation} +Therefore, +\begin{equation} +-|\nabla\times\mathbf B|^2 ++(\nabla\times\mathbf B)\times\hat{\mathbf n}\cdot +\Bigl((\hat{\mathbf n}\cdot\nabla)\mathbf B-(\mathbf B\cdot\nabla)\hat{\mathbf n}\Bigr) += +-2\,(\nabla\times\mathbf B)\times\hat{\mathbf n}\cdot(\mathbf B\cdot\nabla\hat{\mathbf n}). +\end{equation} + +Using \(a\nabla p=\xi_n\hat{\mathbf n}\) and \(\mu_0\mathbf j=\nabla\times\mathbf B\), we finally obtain +\begin{equation} +2\mu_0 \delta W_F += +\int d^3r\left\{ +\Bigl|\mathbf Q+\xi_n(\mu_0\mathbf j\times\hat{\mathbf n})\Bigr|^2 ++\mu_0\gamma p\,|\nabla\cdot\boldsymbol{\xi}|^2 +-2\,|\xi_n|^2\,(\mu_0\mathbf j\times\hat{\mathbf n})\cdot(\mathbf B\cdot\nabla\hat{\mathbf n}) +\right\}. +\end{equation} +Defining +\begin{equation} +K \equiv 2\,(\mathbf j\times\hat{\mathbf n})\cdot(\mathbf B\cdot\nabla\hat{\mathbf n}), +\end{equation} +the Bernstein form becomes +\begin{equation} +\delta W_F += +\frac{1}{2\mu_0}\int d^3r\left\{ +\Bigl|\mathbf Q+\xi_n(\mu_0\mathbf j\times\hat{\mathbf n})\Bigr|^2 ++\mu_0\gamma p\,|\nabla\cdot\boldsymbol{\xi}|^2 +-\mu_0 K\,|\xi_n|^2 +\right\}. +\end{equation} + +\subsection{Derivation of $K$ with explicit $\mu_0$} + +We start from +\begin{equation} +\color{blue} K \equiv 2(\mathbf j\times \hat{\mathbf n})\cdot(\mathbf B\cdot\nabla)\hat{\mathbf n}. +\end{equation} +Since +\color{blue}\begin{align} +(\mathbf B\cdot\nabla)\hat{\mathbf n} &= (\mathbf B\cdot\nabla)\left(\frac{\nabla\psi}{|\nabla\psi|}\right), +(\mathbf j\times \hat{\mathbf n})\cdot \nabla\psi = 0, +\end{align} +\color{black} +we have +\begin{equation} +\color{blue}K +\equiv 2(\mathbf j\times \hat{\mathbf n})\cdot(\mathbf B\cdot\nabla)\hat{\mathbf n} += +\frac{2}{|\nabla\psi|^2} +(\mathbf j\times\nabla\psi)\cdot(\mathbf B\cdot\nabla)\nabla\psi. +\end{equation} + +Now use $\mathbf B\cdot\nabla\psi=0$: +\begin{equation} +0=\nabla(\mathbf B\cdot\nabla\psi) = (\mathbf B\cdot\nabla)\nabla\psi + (\nabla\psi\cdot\nabla)\mathbf B + \nabla\psi\times(\nabla\times\mathbf B), +\end{equation} +thus +\color{blue}\begin{align} +K &= 2\mu_0 j^2 - \frac{2}{|\nabla\psi|^2}(\mathbf j\times\nabla\psi)\cdot(\nabla\psi\cdot\nabla)\mathbf B. +\end{align} +\color{black} + +Decompose $\mathbf j = \sigma\mathbf B + \frac{p'}{B^2}\mathbf B\times\nabla\psi \equiv \mathbf j_{\parallel}+\mathbf j_{\perp}$: +\begin{equation} +\color{blue}K = K_{\parallel}+K_{\perp}, +\end{equation} +\color{blue}\begin{align} +K_{\parallel} +&= +2\mu_0 j_{\parallel}^2 +-\frac{2}{|\nabla\psi|^2} +(\mathbf j_{\parallel}\times\nabla\psi)\cdot(\nabla\psi\cdot\nabla)\mathbf B, +\\ +K_{\perp} +&= +2\mu_0 j_{\perp}^2 +-\frac{2}{|\nabla\psi|^2} +(\mathbf j_{\perp}\times\nabla\psi)\cdot(\nabla\psi\cdot\nabla)\mathbf B. +\end{align} + +\color{black} +\paragraph{1. Perpendicular part $K_{\perp}$} +With $\mathbf j_{\perp}=\frac{p'}{B^2}\mathbf B\times\nabla\psi$: +\begin{equation} +j_{\perp}^{2} = \frac{p'^{\,2}}{B^{2}}|\nabla\psi|^{2}, +\end{equation} +where $\mathbf B\cdot\nabla\psi=0$. Also, +\begin{equation} +\mathbf j_{\perp}\times\nabla\psi = -\frac{p'}{B^{2}}\mathbf B|\nabla\psi|^{2}. +\end{equation} +Hence +\begin{equation} +K_{\perp} = \frac{2\mu_{0}p'^{\,2}}{B^{2}}|\nabla\psi|^{2} + \frac{2p'}{B^{2}}\mathbf B\cdot(\nabla\psi\cdot\nabla)\mathbf B. +\end{equation} + +To simplify, use $0=\nabla(\mathbf B\cdot\nabla\psi) = (\mathbf B\cdot\nabla)\nabla\psi + (\nabla\psi\cdot\nabla)\mathbf B + \nabla\psi\times(\nabla\times\mathbf B)$. +Dotting with $\mathbf B$ and using $\nabla\times\mathbf B=\mu_{0}\mathbf j$: +\begin{equation} +\mathbf B\cdot(\nabla\psi\cdot\nabla)\mathbf B = -\mathbf B\cdot(\mathbf B\cdot\nabla)\nabla\psi - \mu_{0}\mathbf B\cdot(\nabla\psi\times\mathbf j). +\end{equation} +From $(\mathbf B\cdot\nabla)(\mathbf B\cdot\nabla\psi)=0$: +\begin{equation} +\mathbf B\cdot(\mathbf B\cdot\nabla)\nabla\psi = -((\mathbf B\cdot\nabla)\mathbf B)\cdot\nabla\psi. +\end{equation} +From equilibrium $\mathbf j\times\mathbf B=p'\nabla\psi$: +\begin{equation} +\mathbf B\cdot(\nabla\psi\times\mathbf j) = p'|\nabla\psi|^{2}. +\end{equation} +Therefore +\begin{equation} +\mathbf B\cdot(\nabla\psi\cdot\nabla)\mathbf B = ((\mathbf B\cdot\nabla)\mathbf B)\cdot\nabla\psi - \mu_{0}p'|\nabla\psi|^{2}. +\end{equation} + +Substituting: +\begin{align} +K_{\perp} &= \frac{2\mu_{0}p'^{\,2}}{B^{2}}|\nabla\psi|^{2} + \frac{2p'}{B^{2}}\left[((\mathbf B\cdot\nabla)\mathbf B)\cdot\nabla\psi - \mu_{0}p'|\nabla\psi|^{2}\right] \\ +&= \frac{2p'}{B^{2}}((\mathbf B\cdot\nabla)\mathbf B)\cdot\nabla\psi. +\end{align} + +With $\mathbf B=B\mathbf b$ and $\mathbf b\cdot\nabla\psi=0$: +\begin{equation} +((\mathbf B\cdot\nabla)\mathbf B)\cdot\nabla\psi = B^2\,\boldsymbol{\kappa}\cdot\nabla\psi = B^2\kappa_\psi. +\end{equation} +Thus, $\boxed{K_{\perp}=2p'\kappa_\psi}$. + +\paragraph{2. Parallel part $K_{\parallel}$} +With $\mathbf j_{\parallel}=\sigma\mathbf B$: +\begin{equation} +K_{\parallel} = 2\mu_0\sigma^2B^2 - \frac{2\sigma}{|\nabla\psi|^2}(\mathbf B\times\nabla\psi)\cdot(\nabla\psi\cdot\nabla)\mathbf B. +\end{equation} + +Define $\mathbf A \equiv \mathbf B\times\nabla\psi$. Using +\begin{equation} +\nabla\times(\mathbf B\times\nabla\psi) = \mathbf B\nabla^2\psi + (\nabla\psi\cdot\nabla)\mathbf B - (\mathbf B\cdot\nabla)\nabla\psi, +\end{equation} +and from $0=\nabla(\mathbf B\cdot\nabla\psi) = (\mathbf B\cdot\nabla)\nabla\psi + (\nabla\psi\cdot\nabla)\mathbf B + \nabla\psi\times(\nabla\times\mathbf B)$ with $\nabla\times\mathbf B=\mu_0\mathbf j$: +\begin{equation} +(\mathbf B\cdot\nabla)\nabla\psi = -(\nabla\psi\cdot\nabla)\mathbf B - \mu_0\nabla\psi\times\mathbf j. +\end{equation} +Therefore +\begin{equation} +\nabla\times(\mathbf B\times\nabla\psi) = \mathbf B\nabla^2\psi + 2(\nabla\psi\cdot\nabla)\mathbf B + \mu_0\nabla\psi\times\mathbf j. +\end{equation} + +Dotting with $\mathbf A=\mathbf B\times\nabla\psi$ (note: $\mathbf A\cdot\mathbf B=0$): +\begin{equation} +\mathbf A\cdot\nabla\times(\mathbf B\times\nabla\psi) = 2\mathbf A\cdot(\nabla\psi\cdot\nabla)\mathbf B + \mu_0\mathbf A\cdot(\nabla\psi\times\mathbf j), +\end{equation} +giving +\begin{equation} +\mathbf A\cdot(\nabla\psi\cdot\nabla)\mathbf B = \frac{1}{2}\left[\mathbf A\cdot\nabla\times(\mathbf B\times\nabla\psi) - \mu_0\mathbf A\cdot(\nabla\psi\times\mathbf j)\right]. +\end{equation} + +Substituting into $K_{\parallel}$: +\begin{equation} +K_{\parallel} = 2\mu_0\sigma^2B^2 - \frac{\sigma}{|\nabla\psi|^2}\mathbf A\cdot\nabla\times(\mathbf B\times\nabla\psi) + \frac{\mu_0\sigma}{|\nabla\psi|^2}\mathbf A\cdot(\nabla\psi\times\mathbf j). +\end{equation} + +For the last term, using the vector identity $(\mathbf a\times\mathbf b)\cdot(\mathbf c\times\mathbf d) = (\mathbf a\cdot\mathbf c)(\mathbf b\cdot\mathbf d) - (\mathbf a\cdot\mathbf d)(\mathbf b\cdot\mathbf c)$: +\begin{equation} +(\mathbf B\times\nabla\psi)\cdot(\nabla\psi\times\mathbf j) = (\mathbf B\cdot\nabla\psi)(\nabla\psi\cdot\mathbf j) - (\mathbf B\cdot\mathbf j)|\nabla\psi|^2 = -\sigma B^2|\nabla\psi|^2, +\end{equation} +where $\mathbf B\cdot\nabla\psi=0$ and $\mathbf B\cdot\mathbf j=\sigma B^2$ were used. Therefore +\begin{align} +K_{\parallel} &= 2\mu_0\sigma^2B^2 - \frac{\sigma}{|\nabla\psi|^2}(\mathbf B\times\nabla\psi)\cdot\nabla\times(\mathbf B\times\nabla\psi) - \mu_0\sigma^2B^2 \\ +&= \mu_0\sigma^2B^2 - \frac{\sigma}{|\nabla\psi|^2}(\mathbf B\times\nabla\psi)\cdot\nabla\times(\mathbf B\times\nabla\psi). +\end{align} + +Define +\begin{equation} +S \equiv -\frac{\mathbf B\times\nabla\psi}{|\nabla\psi|^2}\cdot\nabla\times\left(\frac{\mathbf B\times\nabla\psi}{|\nabla\psi|^2}\right), +\end{equation} +which is equivalent to $S = -\frac{1}{|\nabla\psi|^4}(\mathbf B\times\nabla\psi)\cdot\nabla\times(\mathbf B\times\nabla\psi)$ by the product rule for curl. +Thus +\begin{equation} +\boxed{K_{\parallel} = \mu_0\sigma^2B^2 + \sigma|\nabla\psi|^2 S}. +\end{equation} + +\paragraph{3. Final expression} +\begin{equation} +\boxed{ +K = |\nabla\psi|^2 \sigma S + \mu_0\sigma^2B^2 + 2p'\kappa_\psi +} +\end{equation} + +\subsection{Expression of $K$ in DCON coordinates} + +Following the Bernstein formulation from §3.1-3.2, the energy principle yields +\color{blue} +\begin{equation} +K = |\nabla\psi|^2 \sigma S + \mu_0 \sigma^2 B^2 + 2p'\kappa_\psi, \quad +\sigma \equiv \frac{\mathbf j\cdot\mathbf B}{B^2}, \quad +\mathbf j \equiv \frac{\nabla\times\mathbf B}{\mu_0}. +\end{equation} +\color{black} +In §3.2, this expression was obtained in a general flux-coordinate form. We now rewrite +it so that each ingredient can be evaluated directly from the DCON equilibrium data. From +this point on, $\psi$ denotes the DCON normalized flux coordinate, $\psi\in[0,1]$, and +$p' \equiv dp/d\psi$ is the derivative with respect to this normalized flux label. + +In DCON coordinates $(\psi,\theta,\zeta)$, +\begin{equation} +\mathbf B = \chi'(\psi)\,\nabla\psi\times\nabla\alpha, \quad +\alpha \equiv q(\psi)\theta-\zeta, \quad +\chi' \equiv \frac{d\chi}{d\psi}, \quad +\mathcal J^{-1} \equiv \nabla\psi\cdot(\nabla\theta\times\nabla\zeta). +\end{equation} + +We use the contravariant metric elements +\begin{equation} +g^{ij}\equiv \nabla q^i\cdot\nabla q^j, +\qquad +(q^1,q^2,q^3) \equiv (\psi,\theta,\zeta), +\end{equation} +with in particular +\begin{equation} +g^{\psi\psi}\equiv \nabla\psi\cdot\nabla\psi, \quad +g^{\psi\theta}\equiv \nabla\psi\cdot\nabla\theta, \quad +g^{\psi\zeta}\equiv \nabla\psi\cdot\nabla\zeta. +\end{equation} +The combinations involving $\alpha=q\theta-\zeta$ are +\begin{equation} +g^{\psi\alpha}\equiv \nabla\psi\cdot\nabla\alpha += q\,g^{\psi\theta}-g^{\psi\zeta}, +\end{equation} +\begin{equation} +g^{\alpha\alpha}\equiv \nabla\alpha\cdot\nabla\alpha += q^2 g^{\theta\theta}-2q\,g^{\theta\zeta}+g^{\zeta\zeta}. +\end{equation} +Accordingly, the parallel derivative operator becomes +\begin{equation} +\mathbf B\cdot\nabla += +\frac{\chi'}{\mathcal J} +\left( +\frac{\partial}{\partial\theta} ++ +q\frac{\partial}{\partial\zeta} +\right), +\end{equation} +and the field strength is written as +\begin{equation} +\boxed{ +B^2 += +\chi'^2|\nabla\psi\times\nabla\alpha|^2 += +\chi'^2\left(g^{\psi\psi}g^{\alpha\alpha}-(g^{\psi\alpha})^2\right). +} +\end{equation} +These relations provide the DCON input form used below for the explicit evaluation of +$S$, $\xi_\psi$, $\kappa_\psi$, and finally $K$. + +\subsubsection{Shear parameter $S$ in DCON} + +\color{blue} +For any flux label $\lambda$, the shear parameter associated with that label is +\begin{equation} +S_{\lambda} +\equiv +-\, +\frac{\mathbf B\times\nabla\lambda}{|\nabla\lambda|^2} +\cdot +\nabla\times +\left( +\frac{\mathbf B\times\nabla\lambda}{|\nabla\lambda|^2} +\right), +\end{equation} +and the shear contribution to $K$ is $|\nabla\lambda|^2\sigma S_{\lambda}$. + +\color{black} +In this subsection we take $\lambda=\psi$, where $\psi$ is the DCON normalized flux label. + +Introduce +\begin{equation} +\boldsymbol{\Lambda}_\psi +\equiv +\frac{\mathbf B\times\nabla\psi}{|\nabla\psi|^2}. +\end{equation} +Using $\mathbf B=\chi'\nabla\psi\times\nabla\alpha$, we obtain +\begin{align} +\boldsymbol{\Lambda}_\psi +&= +\chi' +\frac{(\nabla\psi\times\nabla\alpha)\times\nabla\psi}{|\nabla\psi|^2} +\\ +&= +\chi' +\left[ +\nabla\alpha +- +\frac{\nabla\psi\cdot\nabla\alpha}{|\nabla\psi|^2}\nabla\psi +\right]. +\end{align} + +Define +\begin{equation} +\Gamma_\alpha +\equiv +\frac{\nabla\psi\cdot\nabla\alpha}{|\nabla\psi|^2}. +\end{equation} +Then +\begin{equation} +\boldsymbol{\Lambda}_\psi += +\chi'(\nabla\alpha-\Gamma_\alpha \nabla\psi). +\end{equation} + +Its curl is +\begin{align} +\nabla\times\boldsymbol{\Lambda}_\psi +&= +\nabla\chi' \times (\nabla\alpha-\Gamma_\alpha\nabla\psi) ++ +\chi'\nabla\times(\nabla\alpha-\Gamma_\alpha\nabla\psi) +\\ +&= +\nabla\chi' \times (\nabla\alpha-\Gamma_\alpha\nabla\psi) +- +\chi'\,\nabla\Gamma_\alpha\times\nabla\psi, +\end{align} +since $\nabla\times\nabla\alpha=0$ and $\nabla\times\nabla\psi=0$. + +Now, because $\nabla\chi' \parallel \nabla\psi$, the first term vanishes when contracted +with $\boldsymbol{\Lambda}_\psi$, so +\begin{align} +S_{\psi} +&= +-\boldsymbol{\Lambda}_\psi\cdot(\nabla\times\boldsymbol{\Lambda}_\psi) +\\ +&= +\chi'^2 +(\nabla\alpha-\Gamma_\alpha\nabla\psi)\cdot +(\nabla\Gamma_\alpha\times\nabla\psi) +\\ +&= +\chi'^2\,\nabla\alpha\cdot(\nabla\Gamma_\alpha\times\nabla\psi) +\\ +&= +\chi'^2\,(\nabla\psi\times\nabla\alpha)\cdot\nabla\Gamma_\alpha +\\ +&= +\chi'\,\mathbf B\cdot\nabla\Gamma_\alpha. +\end{align} +Therefore, +\begin{equation} +\boxed{ +S_{\psi} += +\chi'\,\mathbf B\cdot\nabla +\left( +\frac{\nabla\psi\cdot\nabla\alpha}{\nabla\psi\cdot\nabla\psi} +\right). +} +\end{equation} +Here $S_{\psi}$ denotes the shear parameter associated with the DCON normalized flux +label $\psi$, not the $\chi/(2\pi)$-based shear used in the GPEC note. + +Next, +\begin{align} +\nabla\psi\cdot\nabla\alpha +&= +\nabla\psi\cdot\nabla\big(q(\psi)\theta-\zeta\big) +\\ +&= +q'(\nabla\psi\cdot\nabla\psi)\,\theta ++ +q(\nabla\psi\cdot\nabla\theta) +- +(\nabla\psi\cdot\nabla\zeta), +\end{align} +so that +\begin{equation} +\Gamma_\alpha += +q'\theta ++ +\frac{q\,g^{\psi\theta}-g^{\psi\zeta}}{g^{\psi\psi}}. +\end{equation} +The \(q'\theta\) piece is a gauge term coming from +\(\alpha=q(\psi)\theta-\zeta\); it is not itself periodic in \(\theta\). In the +following expressions only its derivative, \(\partial_\theta(q'\theta)=q'\), +enters the physical scalar \(S_\psi\), while integrations by parts must be +applied only to the resulting periodic combinations. + +For an axisymmetric tokamak equilibrium, all equilibrium scalars and metric elements are +independent of $\zeta$, hence $\partial\Gamma_\alpha/\partial\zeta=0$. Using +\begin{equation} +\mathbf B\cdot\nabla += +\frac{\chi'}{\mathcal J} +\left( +\frac{\partial}{\partial\theta} ++ +q\frac{\partial}{\partial\zeta} +\right), +\end{equation} +we obtain +\begin{align} +S_{\psi} +&= +\frac{\chi'^2}{\mathcal J} +\frac{\partial}{\partial\theta} +\left[ +q'\theta ++ +\frac{q\,g^{\psi\theta}-g^{\psi\zeta}}{g^{\psi\psi}} +\right] +\\ +&= +\boxed{ +\frac{\chi'^2}{\mathcal J} +\left[ +q' ++ +\frac{\partial}{\partial\theta} +\left( +\frac{q\,g^{\psi\theta}-g^{\psi\zeta}}{g^{\psi\psi}} +\right) +\right]. +} +\end{align} +Therefore, in DCON coordinates the shear contribution to $K$ is written as +\begin{equation} +\boxed{ +K_{\mathrm{shear}}^{DCON} += +g^{\psi\psi}\sigma S_{\psi} += +g^{\psi\psi}\sigma +\frac{\chi'^2}{\mathcal J} +\left[ +q' ++ +\frac{\partial}{\partial\theta} +\left( +\frac{q\,g^{\psi\theta}-g^{\psi\zeta}}{g^{\psi\psi}} +\right) +\right]. +} +\end{equation} + +\noindent\textbf{Relation to the GPEC note.} +If one instead uses the flux label +\begin{equation} +\lambda = \frac{\chi(\psi)}{2\pi}, +\end{equation} +then \(\nabla\lambda=(\chi'/2\pi)\nabla\psi\) on each flux surface and +\begin{equation} +S_{\lambda} += +\left(\frac{2\pi}{\chi'}\right)^2 S_{\psi} += +\frac{(2\pi)^2}{\mathcal J} +\left[ +q' ++ +\frac{\partial}{\partial\theta} +\left( +\frac{q\,g^{\psi\theta}-g^{\psi\zeta}}{g^{\psi\psi}} +\right) +\right]. +\end{equation} +This is the shear normalization used in the GPEC note. The first term of $K$ is the +same only when the corresponding gradient factor is used consistently: +\begin{equation} +\left|\nabla\left(\frac{\chi}{2\pi}\right)\right|^2\sigma S_{\lambda} += +g^{\psi\psi}\sigma S_{\psi}. +\end{equation} + +\subsubsection{Normal displacement $\xi_\psi$} + +In DCON coordinates, the flux-normal displacement variable is +\begin{equation} +\xi_\psi +\equiv +\boldsymbol{\xi}\cdot\nabla\psi += +\xi^\psi_{\rm tang}, +\qquad +\hat{\mathbf n} += +\frac{\nabla\psi}{|\nabla\psi|}. +\end{equation} +Here \(\xi^\psi_{\rm tang}\) denotes the contravariant component in the true +tangent basis \(\vec e_i=\partial\vec r/\partial q^i\). It should not be +confused with a covariant component in the gradient basis. +Therefore, +\begin{equation} +\boxed{ +\boldsymbol{\xi}_\perp\cdot\hat{\mathbf n} += +\frac{\boldsymbol{\xi}\cdot\nabla\psi}{|\nabla\psi|} += +\frac{\xi_\psi}{|\nabla\psi|}. +} +\end{equation} +The variable $\xi_\psi$ itself depends on the chosen flux label, but the ratio +$\xi_\psi/|\nabla\psi|$ does not. + +Using the DCON Fourier convention, +\begin{equation} +\xi_\psi(\psi,\theta,\zeta) += +\sum_{m,n} +v_{m,n}(\psi)\, +e^{2\pi i(m\theta-n\zeta)}, +\end{equation} +we obtain +\begin{equation} +\boxed{ +\boldsymbol{\xi}_\perp\cdot\hat{\mathbf n} += +\frac{1}{|\nabla\psi|} +\sum_{m,n} +v_{m,n}(\psi)\, +e^{2\pi i(m\theta-n\zeta)}. +} +\end{equation} + +For the quadratic form in the energy principle, the magnitude squared is: +\begin{equation} +\boxed{ +(\boldsymbol{\xi}_\perp\cdot\hat{\mathbf n}) +(\boldsymbol{\xi}_\perp\cdot\hat{\mathbf n})^* += +\frac{\xi_\psi(\xi_\psi)^*}{g^{\psi\psi}}, +\qquad +g^{\psi\psi}=|\nabla\psi|^2. +} +\end{equation} +Since $\xi_\psi$ is represented as a complex Fourier expansion, the product with its +complex conjugate gives the magnitude squared. Therefore the Bernstein scalar term is +evaluated in DCON as +\begin{equation} +\boxed{ +K\left|\boldsymbol{\xi}_\perp\cdot\hat{\mathbf n}\right|^2 += +\frac{K}{g^{\psi\psi}}|\xi_\psi|^2. +} +\end{equation} + +\subsubsection{Curvature $\kappa_\psi$} + +\color{blue} +For any flux label $\lambda$, define +\begin{equation} +\kappa_\lambda +\equiv +\boldsymbol{\kappa}\cdot\nabla\lambda, +\qquad +\boldsymbol{\kappa}\equiv(\mathbf b\cdot\nabla)\mathbf b, +\qquad +\mathbf b\equiv\frac{\mathbf B}{B}. +\end{equation} +This is the magnetic-field-line curvature projected along the chosen flux-gradient +direction. + +\color{black} +In DCON coordinates we take $\lambda=\psi$, so +\begin{equation} +\kappa_\psi\equiv\boldsymbol{\kappa}\cdot\nabla\psi, +\qquad +p'\equiv\frac{dp}{d\psi}. +\end{equation} +Both $p'$ and $\kappa_\psi$ depend on this flux-label normalization, while their product +$p'\kappa_\psi$ is the invariant pressure-curvature combination entering $K$. + +Using the equilibrium force balance +\begin{equation} +(\nabla\times\mathbf B)\times\mathbf B += +\mu_0\nabla p, +\qquad +\nabla p = p'(\psi)\nabla\psi, +\end{equation} +together with +\begin{equation} +(\nabla\times\mathbf B)\times\mathbf B += +(\mathbf B\cdot\nabla)\mathbf B +- +\nabla\left(\frac{B^2}{2}\right), +\end{equation} +we obtain +\begin{equation} +(\mathbf B\cdot\nabla)\mathbf B += +\mu_0\nabla p ++ +\nabla\left(\frac{B^2}{2}\right). +\end{equation} + +Taking the dot product with $\nabla\psi$ gives +\begin{equation} +B^2\kappa_\psi += +\mu_0 p' |\nabla\psi|^2 ++ +\frac{1}{2}\,\nabla B^2\cdot\nabla\psi. +\end{equation} + +Now expand +\begin{equation} +\nabla B^2 += +\frac{\partial B^2}{\partial\psi}\nabla\psi ++ +\frac{\partial B^2}{\partial\theta}\nabla\theta ++ +\frac{\partial B^2}{\partial\zeta}\nabla\zeta. +\end{equation} +For an axisymmetric tokamak equilibrium, $\partial B^2/\partial\zeta=0$, so +\begin{equation} +\nabla B^2\cdot\nabla\psi += +\frac{\partial B^2}{\partial\psi}g^{\psi\psi} ++ +\frac{\partial B^2}{\partial\theta}g^{\psi\theta}. +\end{equation} +Hence +\begin{equation} +\boxed{ +\kappa_\psi += +\frac{1}{B^2} +\left[ +\mu_0 p' g^{\psi\psi} ++ +\frac{1}{2}\frac{\partial B^2}{\partial\psi}g^{\psi\psi} ++ +\frac{1}{2}\frac{\partial B^2}{\partial\theta}g^{\psi\theta} +\right]. +} +\end{equation} +Equivalently, +\begin{equation} +\boxed{ +\kappa_\psi += +\frac{g^{\psi\psi}}{B^2} +\left[ +\mu_0 p' ++ +\frac{1}{2}\frac{\partial B^2}{\partial\psi} ++ +\frac{1}{2}\frac{\partial B^2}{\partial\theta} +\frac{g^{\psi\theta}}{g^{\psi\psi}} +\right]. +} +\end{equation} +Here $B^2$, $\partial B^2/\partial\psi$, and $\partial B^2/\partial\theta$ are evaluated +from the DCON field-strength expression in §3.3.1. + +\subsubsection{Final expression} + +\color{black} +Combining the DCON expressions above, we obtain +\begin{equation} +\boxed{ +K_{DCON} += +g^{\psi\psi}\sigma S_{\psi} ++\mu_0\sigma^2B^2 ++2p'\kappa_\psi. +} +\end{equation} +Here each flux-label dependent quantity is evaluated with the DCON normalized coordinate +$\psi$. Explicitly: +\begin{align} +K_{DCON}(\psi,\theta) +&= +g^{\psi\psi}\sigma +\frac{\chi'^2}{\mathcal J} +\left[ +q' ++ +\frac{\partial}{\partial\theta} +\left( +\frac{q\,g^{\psi\theta}-g^{\psi\zeta}}{g^{\psi\psi}} +\right) +\right] ++\mu_0\sigma^2B^2 +\\ +&\quad ++\frac{2p'}{B^2} +\left[ +\mu_0 p' g^{\psi\psi} ++ +\frac{1}{2}\frac{\partial B^2}{\partial\psi}g^{\psi\psi} ++ +\frac{1}{2}\frac{\partial B^2}{\partial\theta}g^{\psi\theta} +\right]. +\end{align} + +\section{Reconstruction of energy principle variables in DCON} + +\begin{equation} +\begin{aligned} +2\mu_0\delta W +&= \int \big[|\vec{C}|^2- \mu_0K(\vec{\xi} \cdot \hat{n})^2 +\gamma p \mu_0|\vec{\nabla} \cdot \vec{\xi}|^2 \big] dx^3\\ +&= \int d\psi d\theta d\zeta \mathcal{J} \Big\{ [ \nabla \xi_s \times \nabla \psi ++ (\nabla \theta \times \nabla \zeta)\,\mathcal{J}\,\mathbf{B}\cdot\nabla \xi_\psi +- \mathbf{B}\,\xi_\psi' \\ +&- +\left( + \chi'' \nabla \zeta \times \nabla \psi + + (q\chi')' \nabla \psi \times \nabla \theta +\right)\xi_\psi \\ +&+\frac{\mathcal J\xi_\psi}{|\nabla \psi|^2} [ +\mu_0j^\theta +\big( +\nabla\psi(\nabla\zeta\cdot\nabla\psi) +- +\nabla\zeta|\nabla\psi|^2 +\big)+\mu_0j^\zeta +\big( +\nabla\theta|\nabla\psi|^2 +- +\nabla\psi(\nabla\theta\cdot\nabla\psi) +\big)]]^2 \\ +&-\mu_0 K(\vec{\xi} \cdot \hat{n})^2 +\gamma p \mu_0|\vec{\nabla} \cdot \vec{\xi}|^2\} +\end{aligned} +\end{equation} + +\subsection{Reconstruction v1: real-space reconstruction on the $(\psi,\theta)$ grid} +\begin{align} +\xi_\psi = \sum_{m}^{} \sum_{n}^{} v_{m,n}(\psi) \cdot +\exp\!\bigl[2\pi i(m\theta -n\zeta)\bigr] \\ +\xi_s = \sum_{m}^{} \sum_{n}^{} u_{m,n}(\psi) \cdot +\exp\!\bigl[2\pi i(m\theta -n\zeta)\bigr] +\end{align} +Here $\xi_\psi\equiv\vec\xi\cdot\nabla\psi$ is the normal displacement amplitude used by DCON, and $\xi_s$ is the surface displacement variable. +\begin{align} +\vec{Q} &= \mathcal{J} (Q^{\psi} \nabla \theta \times \nabla \zeta + Q^{\theta} \nabla \zeta \times \nabla \psi + Q^{\zeta} \nabla \psi \times \nabla \theta) &= \mathcal{J} ( Q^{\psi} \vec{v_1} + Q^{\theta} \vec{v_2} + Q^{\zeta} \vec{v_3}) \\ +&= Q_1 \nabla \psi + Q_2 \nabla \theta + Q_3 \nabla \zeta +\end{align} + +\begin{equation} +Q^{\psi} = \frac{\chi^\prime}{\mathcal{J}} (\frac{\partial}{\partial \theta} + q \frac{\partial}{\partial \zeta} )\xi_\psi = \frac{\chi^\prime}{\mathcal{J}} \sum_{m}^{} \sum_{n}^{} v_{m,n}(\psi) \cdot \exp\!\bigl[2 \pi i(m\theta -n\zeta)\bigr]\cdot 2 \pi i (m-nq) +\end{equation} + +\begin{align} +Q^{\theta} &= - \frac{1}{\mathcal{J}} \frac{\partial}{\partial \psi}(\chi ^ \prime \xi_\psi) + \frac{1}{\mathcal{J}} \frac{\partial \xi_s}{\partial \zeta} \\ +&= - \frac{1}{\mathcal{J}} \sum_{m}^{} \sum_{n}^{} \left(\chi ^ \prime \frac{\partial}{\partial \psi}(v_{m,n}(\psi)) + \chi^{\prime\prime}v_{m,n}(\psi) + 2\pi i n u_{m,n}(\psi) \right) \cdot \exp\!\bigl[2\pi i(m\theta -n\zeta)\bigr] +\end{align} + +\begin{align} +Q^{\zeta} +&= - \frac{1}{\mathcal{J}} \frac{\partial}{\partial \psi} (\chi^\prime q \xi_\psi) + -\frac{1}{\mathcal{J}} \frac{\partial \xi_s}{\partial \theta} \\ +&= - \frac{1}{\mathcal{J}} \sum_{m} \sum_{n} + \left( \chi ^ \prime q \frac{\partial v_{m,n}(\psi)}{\partial \psi} + + (\chi^{\prime\prime}q+\chi^\prime q^\prime) v_{m,n}(\psi) + + 2\pi i m u_{m,n}(\psi) \right) + \exp\!\bigl(2 \pi i(m\theta - n\zeta)\bigr). +\end{align} + +Then the current vector can be reconstructed by the Grad-Shafranov equation: +\begin{align} +\mu_0 \vec{j} &= \mathcal{J} (\mu_0 j^{\psi} \nabla \theta \times \nabla \zeta + \mu_0 j^{\theta} \nabla \zeta \times \nabla \psi + \mu_0 j^{\zeta} \nabla \psi \times \nabla \theta) \\ +&= \mathcal{J} (\mu_0 j^{\psi} \vec{v_1} + \mu_0 j^{\theta} \vec{v_2} + \mu_0 j^{\zeta} \vec{v_3})\\ +&=\mathcal{J} (\mu_0 j^{\theta} \nabla \zeta \times \nabla \psi + \mu_0 j^{\zeta} \nabla \psi \times \nabla \theta) +\end{align} +\begin{equation} + \mu_0 j^\theta = \mu_0 j \cdot \nabla \theta = - 2 \pi f^\prime / \mathcal{J}, \mu_0 j^\zeta = \mu_0 j \cdot \nabla \zeta = - \mu_0 p^\prime / \chi^\prime - 2 \pi f^\prime q / \mathcal{J} +\end{equation} +Now we can reconstruct the cross term in the energy principle: +\begin{align} +\vec{C} &\equiv \vec{Q} + (\vec{\xi} \cdot \hat{n} ) (\mu_0 \vec{j} \times \hat{n}) \\ +&= \mathcal{J} ( C^\psi \vec{v}_1 + C^\theta \vec{v}_2 + C^\zeta \vec{v}_3 )\\ +&=C_1 \nabla\psi + C_2 \nabla\theta + C_3 \nabla\zeta +\end{align} +\begin{equation} +\begin{aligned} +C^\psi &= \vec{C}\cdot\nabla\psi = Q^\psi +&\qquad +C_1 &= Q_1 + \frac{\mathcal{J}\xi_\psi}{|\nabla\psi|^2} +\bigl[ \mu_0 j^\theta (\nabla\psi\cdot\nabla\zeta) +- \mu_0 j^\zeta (\nabla\psi\cdot\nabla\theta) \bigr] \\ +C^\theta &= \vec{C}\cdot\nabla\theta = Q^\theta + \xi_\psi \frac{1}{|\nabla\psi|^2 \mathcal{J}}(\mu_0j^\theta g_{23} + \mu_0j^\zeta g_{33}) +&\qquad +C_2 &= Q_2 + \mathcal{J}\xi_\psi \mu_0 j^\zeta \\ +C^\zeta &= \vec{C}\cdot\nabla\zeta = Q^\zeta - \xi_\psi \frac{1}{|\nabla\psi|^2 \mathcal{J}}(\mu_0j^\theta g_{22} + \mu_0j^\zeta g_{23}) +&\qquad +C_3 &= Q_3 - \mathcal{J}\xi_\psi \mu_0 j^\theta +\end{aligned} +\end{equation} + +The correction term that turns the bare perturbed field $\vec Q$ into the effective field +$\vec C$, namely $\vec V\equiv\vec C-\vec Q=(\vec\xi\cdot\hat n)(\mu_0\vec j\times\hat n)$, has +contravariant and covariant components ($\vec V=V_i\nabla x^i$): +\begin{equation} +\begin{aligned} +V^\psi &= 0 +&\qquad +V_\psi &= \frac{\mathcal{J}\xi_\psi}{|\nabla\psi|^2} +\bigl[ \mu_0 j^\theta (\nabla\psi\cdot\nabla\zeta) +- \mu_0 j^\zeta (\nabla\psi\cdot\nabla\theta) \bigr] \\ +V^\theta &= \frac{\xi_\psi}{|\nabla\psi|^2 \mathcal{J}} +(\mu_0j^\theta g_{23} + \mu_0j^\zeta g_{33}) +&\qquad +V_\theta &= \mathcal{J}\xi_\psi\,\mu_0 j^\zeta \\ +V^\zeta &= -\frac{\xi_\psi}{|\nabla\psi|^2 \mathcal{J}} +(\mu_0j^\theta g_{22} + \mu_0j^\zeta g_{23}) +&\qquad +V_\zeta &= -\mathcal{J}\xi_\psi\,\mu_0 j^\theta +\end{aligned} +\label{eq:Vcomp} +\end{equation} + +For direct reconstruction on the $(\psi,\theta)$ grid, the energy density is closed by the metric contraction +\begin{equation} +|\vec C|^2 += +g_{ij}C^{i*}C^j += +C_i^*C^i, +\qquad +C_i=g_{ij}C^j, +\qquad +i,j\in\{\psi,\theta,\zeta\}. +\end{equation} +Equivalently, +\begin{equation} +\begin{aligned} +|\vec C|^2 +&= +g_{\psi\psi}|C^\psi|^2 ++g_{\theta\theta}|C^\theta|^2 ++g_{\zeta\zeta}|C^\zeta|^2 \\ +&\quad ++2\operatorname{Re}\!\left[ +g_{\psi\theta}C^{\psi*}C^\theta ++g_{\psi\zeta}C^{\psi*}C^\zeta ++g_{\theta\zeta}C^{\theta*}C^\zeta +\right]. +\end{aligned} +\end{equation} + +\paragraph{Verification: $\vec C$ as an effective field and the $\nabla\times\vec C$ check.} +\label{par:cveri} +The reconstruction is closed by a self-consistency test of the condition +$(\nabla\times\vec C)\cdot\nabla\psi=0$, which holds on every flux surface and admits both a +variational and a physical reading. \emph{Variationally}, it is the Euler--Lagrange +equation obtained when the surface displacement $\xi_s$ is eliminated by minimizing +$\delta W$ at fixed $\xi_\psi$: because $\xi_s$ enters the energy only through +$\nabla\xi_s\times\nabla\psi$ in $|\vec C|^2$---the compressional term +$\gamma p\,|\nabla\cdot\vec\xi|^2$ being carried by a separate (parallel) displacement +component---the stationarity condition is $\nabla\cdot(\vec C\times\nabla\psi)=0$, i.e.\ +$(\nabla\times\vec C)\cdot\nabla\psi=0$. + +\emph{Physically}, in analogy with Amp\`ere's law the curl of $\vec C$ defines an effective +perturbed current, +\begin{equation} +\nabla\times\vec B=\mu_0\vec j +\quad\Longrightarrow\quad +\vec j_{\mathrm{eff}}\equiv\frac{\nabla\times\vec C}{\mu_0}, +\end{equation} +so the reconstruction condition states exactly that this effective current is tangent to the +flux surface, $(\nabla\times\vec C)\cdot\nabla\psi=0\Leftrightarrow\vec j_{\mathrm{eff}}\cdot\nabla\psi=0$. In the equilibrium the current +lies in flux surfaces, $\vec j\cdot\nabla\psi=0$. Under an ideal-MHD displacement the flux +is frozen into the fluid, so the equilibrium current is convected with the displaced surface +$\Psi=\psi-\vec\xi\cdot\nabla\psi$ (the Lagrangian picture) and keeps no component through it. +Referred instead to the \emph{fixed} (Eulerian) surface $\psi$ that DCON uses, this convected +current acquires an \emph{apparent} normal component; with $\vec Q=\delta\vec B$, +\begin{equation} +\frac{1}{\mu_0}(\nabla\times\vec Q)\cdot\nabla\psi +=\nabla\cdot\!\big[(\vec\xi\cdot\nabla\psi)\,\vec j\big] +=-\,\nabla\psi\cdot\nabla\times\!\big[\xi_n(\vec j\times\hat n)\big]\neq0, +\end{equation} +where the middle equality uses $\nabla\cdot(a\vec j)=\vec j\cdot\nabla a$ (as +$\nabla\cdot\vec j=0$), and the last uses $\nabla\psi\cdot\nabla\times\vec G=\nabla\cdot(\vec G\times\nabla\psi)$ +(valid since $\nabla\times\nabla\psi=0$) together with $\nabla\psi=|\nabla\psi|\hat n$ and the +triple product $(\vec j\times\hat n)\times\hat n=(\vec j\cdot\hat n)\hat n-\vec j=-\vec j$ +(because $\vec j\cdot\hat n=0$), giving +$\nabla\cdot[(\vec\xi\cdot\nabla\psi)\vec j]=-\nabla\psi\cdot\nabla\times[\xi_n(\vec j\times\hat n)]$. +This apparent normal is a frame effect---the frozen current viewed against the unperturbed +surface. The cross term $\xi_n(\mu_0\vec j\times\hat n)$ in the definition of $\vec C$ is +exactly this convective (Lagrangian$\leftrightarrow$Eulerian) contribution and cancels it, +\begin{equation} +(\nabla\times\vec C)\cdot\nabla\psi +=(\nabla\times\vec Q)\cdot\nabla\psi ++\mu_0\,\nabla\psi\cdot\nabla\times\!\big[\xi_n(\vec j\times\hat n)\big]=0. +\end{equation} +Hence $\vec C$ is an \emph{effective perturbed magnetic field}---$\vec Q$ with the +frozen-current artifact removed, so that its curl is a genuine surface current on the +\emph{fixed} (Eulerian) flux surface (Fig.~\ref{fig:C_frame}); where $\vec j=0$ (e.g.\ outside the plasma) the +correction drops and $\vec C=\vec Q$. (See Boozer~\cite{RevModPhys.76.1071}; +Park~\cite{Park2009Ideal}, \S2.2.) + +Table~\ref{tab:QvsC} contrasts the bare perturbed field with the effective field. + +\begin{figure} + \centering + \includegraphics[width=0.8\linewidth]{QvsC_quant_n1.png} + \caption{Quantitative check of $(\nabla\times\vec C)\cdot\nabla\psi=0$ on the actual run + (DIII-D \#147131, $n=1$). The real normal effective-current density + $(\nabla\times\,\cdot\,)\cdot\nabla\psi$ (at $\phi=0$) on the equilibrium flux grid for + (a)~the bare perturbed field $\vec Q=\delta\vec B$ and (b)~the effective field + $\vec C=\vec Q+\vec V$, \emph{both in the fixed (Eulerian) frame}. $\vec Q$'s effective + current pierces the flux surface (RMS $=2.1\times10^{-3}$) while $\vec C$'s is tangent to + numerical roundoff (RMS $=2.1\times10^{-12}$). Computed spectrally from the code's + covariant field components ($Q_i,C_i$) and validated against the \texttt{C verify} diagnostic; the + near-axis speckle is a $1/|\nabla\psi|$ reconstruction artifact.} + \label{fig:C_frame} +\end{figure} + +\begin{table}[ht] +\centering +\renewcommand{\arraystretch}{1.3} +\begin{tabular}{l >{\raggedright\arraybackslash}p{0.32\linewidth} >{\raggedright\arraybackslash}p{0.38\linewidth}} +\hline\hline + & $\vec Q=\delta\vec B$ & $\vec C=\vec Q+\vec V$ \\ +\hline +Name & perturbed field (Eulerian $\delta\vec B$) & effective perturbed field ($\delta\vec B+\text{correction}$) \\ +Normal field (flux) & $b_n$ & $b_n$\quad\emph{(identical)} \\ +$\nabla\times$ on fixed $\psi$ & not tangent --- apparent normal current & tangent: $(\nabla\times\vec C)\cdot\nabla\psi=0$ \\ +\hline\hline +\end{tabular} +\caption{The bare perturbed field $\vec Q=\delta\vec B$ versus the effective perturbed +field $\vec C=\vec Q+\vec V$. Both carry the \emph{same} normal field (flux) $b_n$ and differ +only in their tangential (surface-current) part, so that only $\nabla\times\vec C$ is tangent +to the fixed flux surface ($\vec j_{\mathrm{eff}}\cdot\nabla\psi=0$).} +\label{tab:QvsC} +\end{table} + +The code's \texttt{C verify} check tests this condition on the reconstructed components +through its flux-coordinate form +\begin{equation} +(\nabla\times \vec C)\cdot \nabla \psi += +\frac{1}{\mathcal{J}} +\left( +\frac{\partial C_\zeta}{\partial \theta} +- +\frac{\partial C_\theta}{\partial \zeta} +\right), +\end{equation} +with $C_\theta,C_\zeta$ the covariant components of +$\vec C=C_\psi\nabla\psi+C_\theta\nabla\theta+C_\zeta\nabla\zeta$; it thus verifies that the +reconstructed components satisfy this cancellation to numerical roundoff on each flux +surface. + +\subsection{Reconstruction v2: radial matrix representation of $\delta W$} +In this part, we show that the reconstructed Bernstein form can be written in the same radial matrix structure as the DCON/Newcomb formulation. +\begin{equation} +\boxed{\;\vec v_i\cdot\vec v_j=\frac{g_{ij}}{\mathcal{J}^2}\;} +\end{equation} +Here $g_{ij}$ denotes the true covariant metric used in the previous sections. +The DCON matrix coefficients use the Fourier projection of $\vec v_i\cdot\vec v_j$, not of $g_{ij}$ itself. + +\begin{equation} +\boxed{ +\left\langle F \right\rangle_{mm'} +\equiv \int_{0}^{1} d\theta\; +\mathcal{J}(\psi,\theta)\, +\exp\!\bigl(2\pi i(m'-m)\theta\bigr)\, +F(\psi,\theta) +} +\end{equation} + +\begin{equation} +\boxed{ +(G_{ij})_{mm'} \equiv \left\langle \frac{g_{ij}}{\mathcal{J}^2} \right\rangle_{mm'} +} +\end{equation} +where $G_{ij}$ is the DCON matrix representation of $\vec v_i\cdot\vec v_j$ in Fourier space. + +The reconstructed Bernstein form is written in the DCON/Newcomb radial matrix +form as +\begin{equation} +\boxed{ +\begin{aligned} +2\mu_0\delta W +&= +\int d\psi\, +\Big[ +\Xi_s^\dagger \mathbf A \Xi_s ++\Xi_s^\dagger(\mathbf B\Xi_\psi' + \mathbf C\Xi_\psi) ++(\Xi_\psi^{\prime\dagger}\mathbf B^\dagger+\Xi_\psi^\dagger\mathbf C^\dagger)\Xi_s \\ +&\qquad ++\Xi_\psi^{\prime\dagger}\mathbf D\Xi_\psi' ++\Xi_\psi^{\prime\dagger}\mathbf E\Xi_\psi ++\Xi_\psi^\dagger\mathbf E^\dagger\Xi_\psi' ++\Xi_\psi^\dagger\mathbf H\Xi_\psi +\Big]. +\end{aligned} +} +\end{equation} +Here +\begin{equation} +\Xi_s\equiv\{u_m\}, +\qquad +\Xi_\psi\equiv\{v_m\}, +\qquad +\mathbf M_{mm'}=m\delta_{mm'}, +\qquad +\mathbf Q_{mm'}=(m-nq)\delta_{mm'}. +\end{equation} +We also use +\begin{equation} +\mathbf J\equiv\langle 1\rangle, +\qquad +\mathbf J'\equiv\left\langle \frac{\mathcal J'}{\mathcal J}\right\rangle, +\qquad +\mathbf I_{mm'}=\delta_{mm'}. +\end{equation} +With the DCON-normalized flux scale $v'\leftrightarrow\chi'$ and $P'\leftrightarrow\mu_0p'$, the coefficient matrices are +\begin{equation} +\boxed{ +\begin{aligned} +\mathbf A +&=(2\pi)^2 +\left[ +n(n\mathbf G_{22}+\mathbf G_{23}\mathbf M) ++\mathbf M(n\mathbf G_{23}+\mathbf G_{33}\mathbf M) +\right],\\ +\mathbf B +&=-2\pi i\chi' +\left[ +n(\mathbf G_{22}+q\mathbf G_{23}) ++\mathbf M(\mathbf G_{23}+q\mathbf G_{33}) +\right],\\ +\mathbf C +&=-2\pi i +\left[ +\chi''(n\mathbf G_{22}+\mathbf M\mathbf G_{23}) ++(q\chi')'(n\mathbf G_{23}+\mathbf M\mathbf G_{33}) +\right] \\ +&\quad +-(2\pi)^2\chi'(n\mathbf G_{12}+\mathbf M\mathbf G_{31})\mathbf Q ++2\pi i +\left( +2\pi f'\mathbf Q +-n\frac{\mu_0p'}{\chi'}\mathbf J +\right),\\ +\mathbf D +&=\chi'^2 +\left[ +(\mathbf G_{22}+q\mathbf G_{23}) ++q(\mathbf G_{23}+q\mathbf G_{33}) +\right],\\ +\mathbf E +&=\chi' +\left[ +\chi''(\mathbf G_{22}+q\mathbf G_{23}) ++(q\chi')'(\mathbf G_{23}+q\mathbf G_{33}) +\right] \\ +&\quad +-2\pi i\chi'^2(\mathbf G_{12}+q\mathbf G_{31})\mathbf Q ++\mu_0p'\mathbf J,\\ +\mathbf H +&= +\chi'' +\left[ +\chi''\mathbf G_{22}+(q\chi')'\mathbf G_{23} +\right] ++(q\chi')' +\left[ +\chi''\mathbf G_{23}+(q\chi')'\mathbf G_{33} +\right] \\ +&\quad ++2\pi i\chi' +\left[ +\chi''(\mathbf M\mathbf G_{12}-\mathbf G_{12}\mathbf M) ++(q\chi')'(\mathbf M\mathbf G_{31}-\mathbf G_{31}\mathbf M) +\right] \\ +&\quad ++(2\pi\chi')^2\mathbf Q\mathbf G_{11}\mathbf Q ++\left[ +\mu_0p'\left(\frac{\chi''}{\chi'}\mathbf J+\mathbf J'\right) +-2\pi f'q'\chi'\mathbf I +\right]. +\end{aligned} +} +\end{equation} + +Decompose the $\vec C$ term as +\begin{equation} +\begin{aligned} +&\mathbf L \equiv \nabla \xi_s \times \nabla \psi=-2\pi i\sum_m u_m e_m\,(n\vec v_2+m\vec v_3),\\ +&\mathbf M \equiv (\nabla \theta \times \nabla \zeta)\, \mathcal J \, \mathbf B \cdot \nabla \xi_\psi=2\pi i\chi'\sum_m (m-nq)v_m e_m\,\vec v_1\\ +&\mathbf N \equiv -\mathbf B\, \xi_\psi'=-\chi'\sum_m v_m' e_m\,(\vec v_2+q\vec v_3)\\ +&\mathbf O \equiv -\left( +\chi'' \nabla \zeta \times \nabla \psi ++ +(q\chi')' +\nabla \psi \times \nabla \theta +\right)\xi_\psi=-\sum_m v_m e_m\, +\left[\chi''\vec v_2+(q\chi')'\vec v_3\right]\\ +&\mathbf V \equiv +\frac{\xi_\psi}{|\nabla \psi|^2} +\, \mu_0 \mathbf j \times \nabla \psi. +\end{aligned} +\label{eq:fieldpieces} +\end{equation} + +\noindent Define +\begin{equation} +\mathbf Q \equiv \mathbf L+\mathbf M+\mathbf N+\mathbf O, +\qquad +\vec C=\mathbf Q+\mathbf V . +\label{eq:CeqQplusV} +\end{equation} +Then the Bernstein part of the energy is +\begin{equation} +\begin{aligned} +\int d^3x\left(|\vec C|^2-\mu_0K\frac{|\xi_\psi|^2}{|\nabla\psi|^2}\right) +&= +\int d\psi d\theta d\zeta\,\mathcal J +\Big[ +|\mathbf Q|^2 ++\mathbf Q^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf Q \\ +&\qquad ++|\mathbf V|^2 +-\mu_0K\frac{|\xi_\psi|^2}{|\nabla\psi|^2} +\Big]. +\end{aligned} +\end{equation} + +This is the actual route from the real-space Bernstein expression to the radial matrix form. The factors \(m\) and \(m-nq\) are then written as the diagonal matrices \(\mathbf M\) and \(\mathbf Q\). +For instance, +\begin{equation} +\begin{aligned} +\int d\theta\,\mathcal J\,\mathbf L^*\cdot\mathbf L +&= +\Xi_s^\dagger +(2\pi)^2 +\left[ +n^2\mathbf G_{22} +{}+n\mathbf G_{23}\mathbf M +{}+n\mathbf M\mathbf G_{23} +{}+\mathbf M\mathbf G_{33}\mathbf M +\right]\Xi_s,\\ +\int d\theta\,\mathcal J\,\mathbf L^*\cdot\mathbf N +&= +\Xi_s^\dagger +\left\{ +-2\pi i\chi' +\left[ +n(\mathbf G_{22}+q\mathbf G_{23}) +{}+\mathbf M(\mathbf G_{23}+q\mathbf G_{33}) +\right] +\right\} +\Xi_\psi'. +\end{aligned} +\end{equation} +The same projection rule gives \(\mathbf C_1,\mathbf C_2,\mathbf D,\mathbf E_1,\mathbf E_2,\mathbf H_1,\mathbf H_2,\mathbf H_3\) below. The only nontrivial part is the current term \(\mathbf V\), because its pure-\(\Xi_\psi\) pieces must be recombined with the scalar Bernstein term involving \(K\). + +\begin{equation} +\begin{aligned} +2\mu_0\delta W_{\mathrm{term1}} & = \int d\psi d\theta d\zeta \mathcal{J} [|\vec{C}|^2]\\ +&= \int d\psi d\theta d\zeta \mathcal{J}\{ |\mathbf Q|^2+ +\left[\mathbf Q\cdot\mathbf V^* + \mathbf V \cdot \mathbf Q^* +\right]+|\mathbf V|^2\} . +\end{aligned} +\end{equation} + +\paragraph{Blocks from \(\mathbf Q^*\cdot\mathbf Q\)} + +Applying the projection rule above to the products that do not contain \(\mathbf V\) gives the following blocks: +\begin{equation} +|\mathbf L|^2 \leadsto \Xi_s^\dagger \mathbf A \Xi_s, +\qquad +\mathbf A=(2\pi)^2\left[n^2 \mathbf G_{22}+n \mathbf G_{23}\mathbf M+n \mathbf M \mathbf G_{23}+\mathbf M \mathbf G_{33}\mathbf M\right]. +\end{equation} +\begin{equation} +\mathbf L^* \cdot \mathbf N + \mathbf N^* \cdot \mathbf L +\leadsto +\Xi_s^\dagger \mathbf B \Xi_\psi^\prime + \Xi_\psi^{\prime\dagger} \mathbf B^\dagger \Xi_s, +\qquad +\mathbf B=-2\pi i\chi' \left[n(\mathbf G_{22}+q\mathbf G_{23})+\mathbf M(\mathbf G_{23}+q\mathbf G_{33})\right]. +\end{equation} +\begin{equation} +\mathbf L^* \cdot \mathbf M + \mathbf M^* \cdot \mathbf L +\leadsto +\Xi_s^\dagger \mathbf C_1 \Xi_\psi + \Xi_\psi^\dagger \mathbf C_1^\dagger \Xi_s, +\qquad +\mathbf C_1=-(2\pi)^2\chi'(\mathbf M \mathbf G_{31}+n\mathbf G_{12})\mathbf Q. +\end{equation} +\begin{equation} +\mathbf L^* \cdot \mathbf O + \mathbf O^* \cdot \mathbf L +\leadsto +\Xi_s^\dagger \mathbf C_2 \Xi_\psi + \Xi_\psi^\dagger \mathbf C_2^\dagger \Xi_s, +\qquad +\mathbf C_2=-2\pi i \left[\chi''(n\mathbf G_{22}+\mathbf M \mathbf G_{23})+(q\chi')'(n\mathbf G_{23}+\mathbf M \mathbf G_{33})\right]. +\end{equation} +\begin{equation} +|\mathbf M|^2 \leadsto \Xi_\psi^\dagger \mathbf H_1 \Xi_\psi, +\qquad +\mathbf H_1=(2\pi\chi')^2\mathbf Q\mathbf G_{11}\mathbf Q. +\end{equation} +\begin{equation} +\mathbf M^* \cdot \mathbf N + \mathbf N^* \cdot \mathbf M +\leadsto +\Xi_\psi^{\prime\dagger}\mathbf E_1 \Xi_\psi + \Xi_\psi^\dagger \mathbf E_1^\dagger \Xi_\psi^\prime, +\qquad +\mathbf E_1=-2\pi i\chi'^2(\mathbf G_{12}+q\mathbf G_{31})\mathbf Q. +\end{equation} +\begin{equation} +\mathbf M^* \cdot \mathbf O + \mathbf O^* \cdot \mathbf M +\leadsto +\Xi_\psi^\dagger \mathbf H_2 \Xi_\psi, +\qquad +\mathbf H_2=2\pi i\chi'\left[\chi''(\mathbf M \mathbf G_{12}-\mathbf G_{12}\mathbf M)+(q\chi')'(\mathbf M \mathbf G_{31}-\mathbf G_{31}\mathbf M)\right]. +\end{equation} +\begin{equation} +|\mathbf N|^2 \leadsto \Xi_\psi^{\prime\dagger} \mathbf D \Xi_\psi^\prime, +\qquad +\mathbf D=\chi'^2\left[(\mathbf{G}_{22}+q\,\mathbf{G}_{23})+q(\mathbf{G}_{23}+q\,\mathbf{G}_{33})\right]. +\end{equation} +\begin{equation} +\mathbf N^* \cdot \mathbf O + \mathbf O^* \cdot \mathbf N +\leadsto +\Xi_\psi^{\prime\dagger}\mathbf E_2 \Xi_\psi + \Xi_\psi^\dagger \mathbf E_2^\dagger \Xi_\psi^\prime, +\qquad +\mathbf E_2=\chi'\left[\chi''(\mathbf{G}_{22}+q\,\mathbf{G}_{23})+(q\chi')'(\mathbf{G}_{23}+q\,\mathbf{G}_{33})\right]. +\end{equation} + +Finally, +\begin{equation} +|\mathbf O|^2 \leadsto \Xi_\psi^\dagger \mathbf H_3 \Xi_\psi, +\qquad +\mathbf{H}_3 += +\chi'' +\left[ +\chi''\,\mathbf{G}_{22} ++ +(q\chi')'\,\mathbf{G}_{23} +\right] ++ +(q\chi')' +\left[ +\chi''\,\mathbf{G}_{23} ++ +(q\chi')'\,\mathbf{G}_{33} +\right] +\end{equation} + +\paragraph{Correction of the $\mu_0\vec j\times\nabla\psi$ decomposition} + +Before continuing, one algebraic correction is needed. +From +\begin{equation} +\vec v_2 \times \nabla\psi += +\frac{1}{\mathcal J}(g_{23}\vec v_2-g_{22}\vec v_3), +\end{equation} +and +\begin{align} +\vec v_3 \times \nabla\psi +&= +\vec v_3\times \mathcal J(\vec v_2\times \vec v_3) \\ +&= +\mathcal J\big[\vec v_2(\vec v_3\cdot \vec v_3)-\vec v_3(\vec v_3\cdot \vec v_2)\big] \\ +&= +\mathcal J\left[ +\vec v_2\left(\frac{g_{33}}{\mathcal J^2}\right) +- +\vec v_3\left(\frac{g_{23}}{\mathcal J^2}\right) +\right] \\ +&= +\frac{1}{\mathcal J}(g_{33}\vec v_2-g_{23}\vec v_3), +\end{align} +we obtain +\begin{align} +\mu_0\vec j\times \nabla\psi +&= +\mathcal J\mu_0j^\theta(\vec v_2\times \nabla\psi) ++\mathcal J\mu_0j^\zeta(\vec v_3\times \nabla\psi) \\ +&= +(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33})\vec v_2 +- +(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})\vec v_3. +\end{align} + +Therefore +\begin{equation} +\boxed{ +\mathbf V += +\frac{\xi_\psi}{|\nabla\psi|^2} +\left[ +(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33})\vec v_2 +- +(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})\vec v_3 +\right]. +} +\end{equation} + +For compactness, define +\begin{equation} +\mathcal A_2 \equiv \mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33}, +\qquad +\mathcal A_3 \equiv \mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23}, +\end{equation} +so that +\begin{equation} +\mathbf V += +\frac{\xi_\psi}{|\nabla\psi|^2} +\left( +\mathcal A_2 \vec v_2-\mathcal A_3 \vec v_3 +\right). +\end{equation} + +We will also use the cofactor identities of the covariant metric: +\begin{equation} +\boxed{ +g_{22}g_{33}-g_{23}^2=\mathcal J^2|\nabla\psi|^2, +} +\end{equation} +\begin{equation} +\boxed{ +g_{12}g_{23}-g_{31}g_{22} += +\mathcal J^2(\nabla\zeta\cdot\nabla\psi), +} +\end{equation} +\begin{equation} +\boxed{ +g_{12}g_{33}-g_{31}g_{23} += +-\mathcal J^2(\nabla\theta\cdot\nabla\psi). +} +\end{equation} + +\paragraph{Bernstein-current correction terms} + +The vector $\mathbf V$ is not an additional physical term. It appears only because the Bernstein form rewrites the original current--pressure contribution into +\[ +\vec C=\vec Q+\frac{\xi_\psi}{|\nabla\psi|^2}\mu_0\vec j\times\nabla\psi +=\mathbf L+\mathbf M+\mathbf N+\mathbf O+\mathbf V. +\] +Therefore the $\mathbf V$-dependent pieces from $|\vec C|^2$ must be combined with the scalar term +$-\mu_0K|\xi_\psi|^2/|\nabla\psi|^2$. After this recombination, the same $\mathbf C$, $\mathbf E$, and $\mathbf H$ blocks in the DCON matrix form are obtained. + +The cross term $\mathbf L^* \cdot \mathbf V + \mathbf V^* \cdot \mathbf L$ produces the current part of the $\mathbf C$ block: +\[ +\Xi_s^\dagger \mathbf C_3 \Xi_\psi + \Xi_\psi^\dagger \mathbf C_3^\dagger \Xi_s, +\] +where $\mathbf C_3$ is obtained as follows. + +\begin{align} +\nabla \xi_s +&=\sum_m +\left[ +u_m' \nabla\psi ++ +2\pi i\,m\,u_m \nabla\theta +- +2\pi i\,n\,u_m \nabla\zeta +\right]e^{2\pi i(m\theta-n\zeta)} +\end{align} +\begin{align} +\mathbf L +&= +\nabla\xi_s\times\nabla\psi \\ +&= +\sum_m +2\pi i\,u_m e^{2\pi i(m\theta-n\zeta)} +\left( +m\,\nabla\theta\times\nabla\psi +- +n\,\nabla\zeta\times\nabla\psi +\right)\\ +\mathbf L^* +&= +\sum_m +(-2\pi i)\,u_m^* e^{-2\pi i(m\theta-n\zeta)} +\left( +m\,\nabla\theta\times\nabla\psi +- +n\,\nabla\zeta\times\nabla\psi +\right) \\ +\mathbf V +&= +\frac{\xi_\psi}{|\nabla\psi|^2}\,\mu_0\mathbf j\times\nabla\psi += +\sum_{m'} +v_{m'} e^{2\pi i(m'\theta-n\zeta)} +\frac{1}{|\nabla\psi|^2} +\mu_0\mathbf j\times\nabla\psi\\ +\mathbf V^* +&= +\frac{\xi_\psi^*}{|\nabla\psi|^2}\,\mu_0\mathbf j\times\nabla\psi += +\sum_{m'} +v_{m'}^* e^{-2\pi i(m'\theta-n\zeta)} +\frac{1}{|\nabla\psi|^2} +\mu_0\mathbf j\times\nabla\psi +\end{align} + +Therefore +\begin{align} +\mathbf L^*\cdot \mathbf V +&= +\sum_{m,m'} +(-2\pi i)\, +u_m^* v_{m'}\, +e^{2\pi i(m'-m)\theta} +\frac{\mu_0}{|\nabla\psi|^2} +\Big[ +m(\nabla\theta\times\nabla\psi)\cdot(\mathbf j\times\nabla\psi) +- +n(\nabla\zeta\times\nabla\psi)\cdot(\mathbf j\times\nabla\psi) +\Big] +\end{align} + +Now use +$(\mathbf a\times\mathbf c)\cdot(\mathbf b\times\mathbf c)=(\mathbf a\cdot\mathbf b)|\mathbf c|^2-(\mathbf a\cdot\mathbf c)(\mathbf b\cdot\mathbf c)$ +with $\mathbf c=\nabla\psi$, together with $\mathbf j\cdot\nabla\psi=0$. Then +\begin{align} +(\nabla\theta\times\nabla\psi)\cdot(\mathbf j\times\nabla\psi) +&=(\nabla\theta\cdot\mathbf j)|\nabla\psi|^2-(\nabla\theta\cdot\nabla\psi)(\mathbf j\cdot\nabla\psi) &=j^\theta |\nabla\psi|^2\\ +(\nabla\zeta\times\nabla\psi)\cdot(\mathbf j\times\nabla\psi)&=j^\zeta |\nabla\psi|^2 +\label{eq:Vtriple} +\end{align} + +Hence +\begin{align} +\mathbf L^*\cdot \mathbf V +&= +\sum_{m,m'} +(-2\pi i)\, +u_m^* v_{m'}\, +e^{2\pi i(m'-m)\theta}\, +\mu_0\left(mj^\theta-nj^\zeta\right) +\end{align} + +Now substitute the equilibrium current components. In the normalization used here, +\begin{equation} +\mu_0 j^\theta = -\frac{2\pi f'}{\mathcal J}, +\qquad +\mu_0 j^\zeta = -\frac{\mu_0 p'}{\chi'}-\frac{2\pi f' q}{\mathcal J} +\label{eq:dconcurrents} +\end{equation} +\begin{align} +\mu_0(mj^\theta-nj^\zeta) +&= +-\frac{2\pi f'}{\mathcal J}m ++ +n\left( +\frac{\mu_0 p'}{\chi'} ++ +\frac{2\pi f'q}{\mathcal J} +\right) \\ +&= +-\frac{2\pi f'}{\mathcal J}(m-nq) ++ +n\frac{\mu_0 p'}{\chi'} +\end{align} +Therefore +\begin{align} +(\mathbf C_3)_{mm'} +&= +\int_0^1 d\theta\; +\mathcal J\,e^{2\pi i(m'-m)\theta} +(-2\pi i) +\left[ +-\frac{2\pi f'}{\mathcal J}(m-nq) ++ +n\frac{\mu_0 p'}{\chi'} +\right] \\ +&= +\int_0^1 d\theta\; +e^{2\pi i(m'-m)\theta} +\left[ +(2\pi i)(2\pi f')(m-nq) +- +(2\pi i)n\frac{\mu_0 p'}{\chi'}\mathcal J +\right] +\end{align} + +\begin{equation} +\boxed{ +\mathbf C_3 += +2\pi i +\left( +2\pi f' \mathbf Q +- +n\frac{\mu_0 p'}{\chi'}\mathbf J +\right) +} +\end{equation} + +\medskip +\noindent\rule{\linewidth}{0.4pt} +\medskip + +Next, the $\mathbf N$--$\mathbf V$ block produces the pressure part of the $\mathbf E$ block: +\[ +\Xi_\psi^{\prime\dagger}\mathbf E_3 \Xi_\psi + \Xi_\psi^\dagger \mathbf E_3^\dagger \Xi_\psi^\prime. +\] +\begin{align} +\mathbf N^*\cdot \mathbf V +&= +\sum_{m,m'} +v_m^{\prime *}v_{m'}\,e^{2\pi i(m'-m)\theta} +\left[ +-\chi' +\frac{ +\mathcal A_2(g_{22}+qg_{23})-\mathcal A_3(g_{23}+qg_{33}) +}{ +\mathcal J^2|\nabla\psi|^2 +} +\right] +\end{align} +\begin{align} +\mathcal A_2(g_{22}+qg_{23})-\mathcal A_3(g_{23}+qg_{33}) +&= +(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33})(g_{22}+qg_{23}) +- +(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})(g_{23}+qg_{33}) \\ +&= +(\mu_0j^\zeta-q\mu_0j^\theta)(g_{22}g_{33}-g_{23}^2) \\ +&= +(\mu_0j^\zeta-q\mu_0j^\theta)\mathcal J^2|\nabla\psi|^2 . +\end{align} +\begin{align} +\mathbf N^*\cdot \mathbf V +&= +-\chi'(\mu_0j^\zeta-q\mu_0j^\theta) +\sum_{m,m'}v_m^{\prime *}v_{m'}\,e^{2\pi i(m'-m)\theta} \\ +&= +\mu_0 p'\sum_{m,m'}v_m^{\prime *}v_{m'}\,e^{2\pi i(m'-m)\theta}. +\end{align} +\begin{equation} +\boxed{ +\begin{aligned} +\mathbf N^*\cdot \mathbf V+\mathbf V^*\cdot\mathbf N +\leadsto\; +\Xi_\psi^{\prime\dagger}\mathbf E_3 \Xi_\psi ++ +\Xi_\psi^\dagger \mathbf E_3^\dagger \Xi_\psi^\prime, \\ +\qquad +\mathbf E_3 +&= +\mu_0p'\mathbf J . +\end{aligned} +} +\end{equation} + + +\medskip +\noindent\rule{\linewidth}{0.4pt} +\paragraph{Closed form of the $\mathbf V$-sector ($\mathbf H_4$).} + +The four pure-$\Xi_\psi$ contributions assemble into +\begin{equation} +\boxed{\;\mathbf H_4=\mathbf H_{MV}+\mathbf H_{OV}+\mathbf H_V+\mathbf H_K\;,} +\end{equation} +equal, term by term, to the $\mathbf V$-sector of the Bernstein form +\eqref{eq:ep_bernsteinK} without invoking any equilibrium identity. The +Grad--Shafranov equation reduces it to the compact block +\begin{equation} +\boxed{\; +\mathbf H_4=\mu_0p'\left(\frac{\chi''}{\chi'}\mathbf J+\mathbf J'\right) +-2\pi f'q'\chi'\,\mathbf I\;,} +\end{equation} +as proved in Appendix~\ref{app:h4}. + +The complete term-to-matrix assignment is therefore +\begin{equation} +\boxed{ +\begin{aligned} +\mathbf A &: \quad \mathbf L\!\cdot\mathbf L,\\ +\mathbf B &: \quad \mathbf L\!\cdot\mathbf N,\\ +\mathbf C &: \quad +\mathbf L\!\cdot\mathbf M\to\mathbf C_1,\quad +\mathbf L\!\cdot\mathbf O\to\mathbf C_2,\quad +\mathbf L\!\cdot\mathbf V\to\mathbf C_3,\\ +\mathbf D &: \quad \mathbf N\!\cdot\mathbf N,\\ +\mathbf E &: \quad +\mathbf N\!\cdot\mathbf M\to\mathbf E_1,\quad +\mathbf N\!\cdot\mathbf O\to\mathbf E_2,\quad +\mathbf N\!\cdot\mathbf V\to\mathbf E_3,\\ +\mathbf H &: \quad +\mathbf M\!\cdot\mathbf M\to\mathbf H_1,\quad +\mathbf M\!\cdot\mathbf O+\to\mathbf H_2,\quad +\mathbf O\!\cdot\mathbf O\to\mathbf H_3,\\ +&\quad +\mathbf M\!\cdot\mathbf V ++\mathbf O\!\cdot\mathbf V ++|\mathbf V|^2 +-\mu_0K\frac{|\xi_\psi|^2}{|\nabla\psi|^2} +\to\mathbf H_4 . +\end{aligned} +} +\end{equation} +Thus the final coefficient matrices are +\begin{equation} +\boxed{ +\begin{aligned} +\mathbf C&=\mathbf C_1+\mathbf C_2+\mathbf C_3, \\ +\mathbf E&=\mathbf E_1+\mathbf E_2+\mathbf E_3, \\ +\mathbf H&=\mathbf H_1+\mathbf H_2+\mathbf H_3+\mathbf H_4. +\end{aligned} +} +\end{equation} +Subject to the boundary and reduced-compressional assumptions stated above, +this is the DCON-normalized version of the matrix structure in Eq.~(35) of +\texttt{dcon.tex}: the Bernstein form and the original DCON current--pressure +form yield the same radial matrix functional, the $\mathbf V$-dependent pieces +being purely intermediate recombination terms. +\section{Variable Naming Convention and Mathematical Symbols} +\label{sec:naming} + +This section summarizes the code implementation of the $\vec C$ and $\vec Q$ vectors and the tensor transformations between their contravariant and covariant representations. + +\subsection{Fundamental Transformation Laws} + +The C and Q vectors are defined using a dual gradient basis and a tangent basis: + +\textbf{Gradient basis (covariant one-forms):} +$\vec{w}_i \equiv \nabla \psi, \nabla \theta, \nabla \zeta$ \\ +\textbf{Jacobian-scaled tangent basis:} +$\vec{v}_i \equiv \nabla \theta \times \nabla \zeta, \nabla \zeta \times \nabla \psi, \nabla \psi \times \nabla \theta$, +so that the true tangent basis is \(\vec e_i=\mathcal J\vec v_i\). + +\begin{equation} +\boxed{ +\begin{aligned} +\text{Contravariant} \quad &Q^i = \frac{b^i}{\mathcal{J}} \\ +\text{Covariant} \quad &Q_i = g_{ij} Q^j = g_{ij} \frac{b^j}{\mathcal{J}} \\ +\text{Physics method} \quad &C_i = Q_i + \left[\xi_n(\mu_0 \mathbf{j} \times \hat{n})\right]_i +\end{aligned} +} +\end{equation} + +where $\mathcal{J}$ is the Jacobian, \(g_{ij}\) is the true covariant tangent +metric, \(\xi_n=\boldsymbol\xi\cdot\hat n\), and \(b^i\equiv\mathcal J Q^i\) +denotes the Jacobian-weighted contravariant arrays stored by the code +(\texttt{bwp}, \texttt{bwt}, \texttt{bwz}). +\subsection{Code Variable Mapping Table} + +\subsubsection*{C Vector - Contravariant components in the tangent basis ($C^i$)} +\begin{table}[h!] +\centering +\begin{tabular}{|l|l|} +\hline +\textbf{Variable} & \textbf{Symbol/Definition} \\ +\hline +\texttt{cwp\_tmp, cwp\_fun, cwp\_mn} & $\mathcal{J}C^\psi$ (contravariant component) \\ +\texttt{cwt\_tmp, cwt\_fun, cwt\_mn} & $\mathcal{J}C^\theta$ (contravariant component) \\ +\texttt{cwz\_tmp, cwz\_fun, cwz\_mn} & $\mathcal{J}C^\zeta$ (contravariant component) \\ +\hline +\end{tabular} +\end{table} + +\subsubsection*{C Vector - Covariant components in the gradient basis ($C_i$)} +\begin{table}[h!] +\centering +\begin{tabular}{|l|l|} +\hline +\textbf{Variable} & \textbf{Symbol/Definition} \\ +\hline +\texttt{cvp\_tmp, cvp\_fun} & $C_\psi$ reconstructed directly from $Q_\psi+(\xi\cdot\hat n)(\mu_0\mathbf j\times\hat n)_\psi$ \\ +\texttt{cvt\_tmp, cvt\_fun} & $C_\theta$ reconstructed directly from $Q_\theta+\mathcal J\xi_\psi\mu_0j^\zeta$ \\ +\texttt{cvz\_tmp, cvz\_fun} & $C_\zeta$ reconstructed directly from $Q_\zeta-\mathcal J\xi_\psi\mu_0j^\theta$ \\ +\texttt{c2vp\_mn, cv2p\_fun} & $g_{\psi j}C^j$ from metric lowering of the reconstructed upper components \\ +\texttt{c2vt\_mn, cv2t\_fun} & $g_{\theta j}C^j$ from metric lowering of the reconstructed upper components \\ +\texttt{c2vz\_mn, cv2z\_fun} & $g_{\zeta j}C^j$ from metric lowering of the reconstructed upper components \\ +\hline +\end{tabular} +\end{table} + +\section{Log-output quantities and their exact integrands} +\label{sec:logmap} + +This section gives the precise mathematical definition of every scalar +printed to the terminal and to \texttt{gpec.log} by the three reconstruction +diagnostics. The three blocks correspond to the input flags +\texttt{effective\_field\_flag} (\texttt{GPEC\_RECON}, driver \texttt{gpout\_recon}), +\texttt{standard\_form\_flag} (\texttt{GPEC\_RECON2}, driver \texttt{gpout\_recon2} +$\to$ \texttt{gpeq\_firstform}), and \texttt{effective\_field\_terms\_flag} +(\texttt{GPEC\_RECON3}, driver \texttt{gpout\_recon3} $\to$ +\texttt{gpeq\_epfterms}). + +Three further switches in \texttt{\&GPEC\_OUTPUT} control how these +diagnostics run: +\begin{itemize} +\item \texttt{recon\_out} (default \texttt{.TRUE.}) gates the + \emph{file} output. When true, each route writes its per-surface + ASCII tables and appends its summary block to \texttt{gpec.log}; + when false the terminal summary is still printed, but no files are + written. +\item \texttt{recon\_int} (default \texttt{"spline"}, alternative + \texttt{"trapezoid"}) selects the quadrature used for the radial + $\psi$ integration on the \texttt{psifac} grid. Every + $\delta W_p$ total reported below is built with this scheme, so the + choice affects the reported numbers at the level of the quadrature + error. Each route prints the $\psi$ range it integrated over + (\texttt{recon psi range}) next to the DCON range + $[\psi_{\rm low},\psi_{\rm lim}]$ (\texttt{DCON psi range}), so a + truncated integration is visible beside the $\delta W_p$ comparison. +\item \texttt{curl\_check\_flag} (default \texttt{.FALSE.}) enables the + $(\nabla\times\vec C)\cdot\nabla\psi=0$ verification of + \S\ref{par:cveri} alongside \texttt{effective\_field\_flag}. The pointwise + residual is accumulated over all surfaces. Because the identity is + a cancellation between $\partial_\theta C_\zeta$ and + $\partial_\zeta C_\theta$, the verdict uses the relative residual + \begin{equation*} + \texttt{rel}=\frac{\max|(\nabla\times\vec C)\cdot\nabla\psi|} + {\max\big(|\partial_\theta C_\zeta|+|\partial_\zeta C_\theta|\big)/|J|}, + \end{equation*} + with both maxima taken over all $(\psi,\theta)$ points. It is + reported as \texttt{PASS} or \texttt{WARN} against a tolerance of + $10^{-8}$ (\texttt{SKIP} if no points were checked), together with + the absolute maximum and rms, the scale, and \texttt{rel}. +\end{itemize} + +The DCON normalization factor that converts the GPEC energy to the +DCON-normalized $\delta W$, used in the normalized outputs below, is +\begin{equation} +\texttt{dcon\_fac} +=\frac{2\mu_0}{\psi_o^{2}\,(10^{-3}\chi')^{2}}, +\qquad +\chi'=2\pi\psi_o . +\end{equation} + +\subsection{\texttt{GPEC\_RECON} (\texttt{effective\_field\_flag})} + +This route reconstructs the closed Bernstein form \eqref{eq:ep_bernsteinK}, +\begin{equation} +\delta W_p=\frac{1}{2\mu_0}\int d\tau_p +\Big\{\,|\vec C|^2 ++\mu_0\gamma p\,|\nabla\!\cdot\!\vec\xi|^2 +-\mu_0 K\,|\xi_n|^2\,\Big\}, +\qquad +\vec C\equiv\vec Q+\xi_n\,(\mu_0\vec j\times\hat n), +\label{eq:dWp_recon1} +\end{equation} +For the reduced diagnostic used here the positive compressional norm +\(\gamma p\,|\nabla\!\cdot\!\vec\xi|^2\) is omitted; this is a reconstruction +assumption, not a general ideal-MHD identity. The reconstructed energy is then +\(\delta W_p=\tfrac{1}{2\mu_0}\int(|\vec C|^2-\mu_0K|\xi_n|^2)\,d\tau_p\). +Here the symbols are +\begin{itemize} +\item $\vec Q=\nabla\times(\vec\xi\times\vec B)$ is the perturbed field; +\item $\xi_n\equiv\vec\xi\cdot\hat n$ is the normal displacement, $\hat + n=\nabla\psi/|\nabla\psi|$ (code \texttt{xno\_mn}); +\item $\vec C=\vec Q+\xi_n(\mu_0\vec j\times\hat n)$ is the cross vector, + whose squared norm is closed by the metric contraction + $|\vec C|^2=g_{ij}C^{i*}C^{j}=C_i^*C^i$ (routine \texttt{gpeq\_epf}); +\item $K=K_1+K_2+K_3$ is the destabilizing curvature kernel (routine + \texttt{gpeq\_K}) integrated against $|\xi_n|^2$ (routine + \texttt{gpeq\_dst}). +\end{itemize} +The printed scalars are the radial/poloidal integrals of these pieces: + +\begin{align} +\texttt{C2\_psi/mu0} +&=\int\! d\psi\,\frac{1}{\mu_0}\oint + \mathrm{Re}\!\big[(C^\psi)^*\,C_\psi\big]\,\mathcal J\,d\theta,\\ +\texttt{C2\_theta/mu0} +&=\int\! d\psi\,\frac{1}{\mu_0}\oint + \mathrm{Re}\!\big[(C^\theta)^*\,C_\theta\big]\,\mathcal J\,d\theta,\\ +\texttt{C2\_zeta/mu0} +&=\int\! d\psi\,\frac{1}{\mu_0}\oint + \mathrm{Re}\!\big[(C^\zeta)^*\,C_\zeta\big]\,\mathcal J\,d\theta,\\ +\texttt{int\_J\_C2\_over\_mu0} +&=\texttt{C2\_psi/mu0}+\texttt{C2\_theta/mu0}+\texttt{C2\_zeta/mu0} + =\int\! d\psi\,\frac{1}{\mu_0}\oint |\vec C|^2\,\mathcal J\,d\theta. +\end{align} +The curvature ($K$) pieces, with +$K_1=|\nabla\psi|^2\,\sigma\,S$ (shear $S$), +$K_2=\mu_0 B^2\sigma^2$ (parallel current), and +$K_3=2p'\kappa_\psi$ (curvature projection +\(\kappa_\psi=\boldsymbol\kappa\cdot\nabla\psi\)), give +\begin{align} +\texttt{Ki\_xin2} +&=\int\! d\psi\oint K_i\,|\xi_n|^2\,\mathcal J\,d\theta, +\qquad i=1,2,3,\\ +\texttt{int\_J\_K\_xin2} +&=\sum_{i=1}^{3}\texttt{Ki\_xin2} + =\int\! d\psi\oint K\,|\xi_n|^2\,\mathcal J\,d\theta. +\end{align} +The reconstructed potential energy and its DCON-normalized value are +\begin{align} +\texttt{dW\_p(recon)} +&=\tfrac12\bigl(\texttt{int\_J\_C2\_over\_mu0}-\texttt{int\_J\_K\_xin2}\bigr) + =\tfrac12\!\int\!\Big[\tfrac{|\vec C|^2}{\mu_0}-K|\xi_n|^2\Big] + \mathcal J\,d\psi\,d\theta,\\ +\texttt{dW\_p(recon-normalize)} +&=\texttt{dcon\_fac}\times\texttt{dW\_p(recon)} . +\end{align} +The \texttt{C verify} block reports $\max$/$\mathrm{rms}$ of +$(\nabla\times\vec C)\cdot\nabla\psi=\tfrac1{\mathcal J} +(\partial_\theta C_\zeta-\partial_\zeta C_\theta)$, which must vanish to +\texttt{tol}. + +\subsection{\texttt{GPEC\_RECON2} (\texttt{standard\_form\_flag})} + +This route reconstructs the original ``first form'' of $\delta W_p$, Eq.~\eqref{eq:ep_first}, +\begin{equation} +\delta W_p=\frac{1}{2\mu_0}\int d\tau_p +\Big\{\,|\vec Q|^2 +-\mu_0\,\vec j\cdot\vec Q\times\vec\xi^{*} ++\mu_0\gamma p\,|\nabla\!\cdot\!\vec\xi|^2 ++\mu_0(\nabla\!\cdot\!\vec\xi)^{*}\,\vec\xi\cdot\nabla p\,\Big\}, +\label{eq:dWp_recon2} +\end{equation} +The reduced diagnostic omits only the positive +\(\gamma p|\nabla\!\cdot\!\vec\xi|^2\) norm; the pressure--compression coupling +term is retained. The three retained groups map one-to-one onto the printed +scalars, with +\begin{itemize} +\item $|\vec Q|^2$ $\to$ \texttt{int\_J\_Q2\_over\_mu0}; +\item the current cross term $-\vec j\cdot\vec Q\times\vec\xi^{*}$ $\to$ + \texttt{int\_J\_jQxixi\_over\_mu0} (evaluated as a determinant of the + Jacobian-weighted contravariant $Q$ and $\xi$ components against + $\mu_0 j^\theta,\mu_0 j^\zeta$); +\item the pressure--compression term + $(\nabla\!\cdot\!\vec\xi)^{*}\,\vec\xi\cdot\nabla p$ $\to$ + \texttt{int\_J\_pdiv\_term}. +\end{itemize} +The route (routine \texttt{gpeq\_firstform}) evaluates these groups pointwise +in $\theta$ and integrates in $\psi$. Writing +$\tilde Q^i\equiv\mathcal JQ^i$ and $\tilde\xi^i\equiv\mathcal J\xi^i$ for the +Jacobian-weighted contravariant components, +\begin{align} +\texttt{int\_J\_Q2\_over\_mu0} +&=\int\! d\psi\,\frac{1}{\mu_0}\oint + \mathrm{Re}\!\big[Q_\psi^*Q^\psi+Q_\theta^*Q^\theta+Q_\zeta^*Q^\zeta\big] + \mathcal J\,d\theta + =\int\! d\psi\,\frac{1}{\mu_0}\oint|\vec Q|^2\,\mathcal J\,d\theta,\\ +\texttt{int\_J\_jQxixi\_over\_mu0} +&=\int\! d\psi\,\frac{1}{\mu_0}\oint\frac{1}{\mathcal J} + \Big[-\mu_0 j^\theta\bigl(\tilde Q^\psi\,\tilde\xi^{\zeta*}-\tilde Q^\zeta\,\tilde\xi^{\psi*}\bigr) + \notag\\ +&\qquad\qquad + +\mu_0 j^\zeta\bigl(\tilde Q^\psi\,\tilde\xi^{\theta*}-\tilde Q^\theta\,\tilde\xi^{\psi*}\bigr) + \Big]\mathcal J\,d\theta + \notag\\ +&=\int\! d\psi\oint \vec j\cdot(\vec Q\times\vec\xi^{\,*})\,\mathcal J\,d\theta,\\ +\texttt{int\_J\_pdiv\_term} +&=\int\! d\psi\oint + (\nabla\!\cdot\!\vec\xi)^{*}\,p'\,\xi^{\psi}\, + \mathcal J\,d\theta,\\ +\texttt{int\_J\_total} +&=\texttt{int\_J\_Q2\_over\_mu0} + -\texttt{int\_J\_jQxixi\_over\_mu0} + +\texttt{int\_J\_pdiv\_term}. +\end{align} +Here $\tilde Q^i=\mathcal JQ^i$ and $\tilde\xi^i=\mathcal J\xi^i$ are the +Jacobian-weighted \emph{contravariant} components (code variables +\texttt{bwp\_fun}, \texttt{bmt\_fun}, \texttt{bmz\_fun} and +\texttt{xwp\_fun}, \texttt{xmt\_fun}, \texttt{xmz\_fun}), and +$\nabla\!\cdot\!\vec\xi$ is the compressional density assembled in +\texttt{divxi\_density}. The reconstructed energy is +\begin{equation} +\texttt{dW\_p(recon2)}=\tfrac12\,\texttt{int\_J\_total}, +\qquad +\texttt{dW\_p(recon2-normalize)}=\texttt{dcon\_fac}\times\texttt{dW\_p(recon2)} . +\end{equation} +Each line is printed as a complex pair (real, imaginary); the imaginary part +is a roundoff diagnostic and should vanish. + +\subsection{\texttt{GPEC\_RECON3} (\texttt{effective\_field\_terms\_flag})} + +This route keeps the same closed form as recon1, Eq.~\eqref{eq:dWp_recon1}, +but expands the cross-energy density $|\vec C|^2$ term by term using +$\vec C=\vec Q+\vec V$, Eq.~\eqref{eq:CeqQplusV}, +\begin{equation} +|\vec C|^2=|\vec Q|^2+2\,\mathrm{Re}\bigl(\vec V^{*}\!\cdot\vec Q\bigr) ++|\vec V|^2 . +\label{eq:Cdecomp} +\end{equation} +Crucially, $\vec V$ is evaluated \emph{directly} from its explicit components +in Eq.~\eqref{eq:Vcomp} (equilibrium current, metric, and $\xi_\psi$), not as +$\vec C-\vec Q$, so the $B$ term below does not depend on the reconstructed +$\vec C$. Using that $\vec j$ is tangent to the flux surface ($\vec +j\cdot\hat n=0$), the last piece separates into a parallel-current and a +perpendicular (Pfirsch--Schl\"uter) contribution, +\begin{equation} +|\vec V|^2=\mu_0^2\,|\xi_n|^2\Bigl(\sigma^2B^2 ++\frac{p'^2|\nabla\psi|^2}{B^2}\Bigr). +\end{equation} +The five printed scalars are the $\psi,\theta$ integrals of the four density +pieces of Eq.~\eqref{eq:Cdecomp} plus the full $|\vec C|^2$: +\begin{align} +\texttt{A\_total} +&=\int\! d\psi\,\frac{1}{\mu_0}\oint|\vec Q|^2\,\mathcal J\,d\theta + \;\;(=\texttt{int\_J\_Q2\_over\_mu0}\ \text{of recon2}),\\ +\texttt{B\_total} +&=\int\! d\psi\,\frac{2}{\mu_0}\oint + \mathrm{Re}\!\big[V_i^{*}\,Q^{i}\big]\,\mathcal J\,d\theta + \;\;\bigl(=\tfrac{2}{\mu_0}\!\int\!\mathrm{Re}(\vec V^{*}\!\cdot\vec Q)\bigr),\\ +\texttt{Cpar\_total} +&=\int\! d\psi\oint \mu_0\,\sigma^2 B^2\,|\xi_n|^2\,\mathcal J\,d\theta + \;\;(=\texttt{K2\_xin2}\ \text{of recon1}=dst_2),\\ +\texttt{Iperp\_total} +&=\int\! d\psi\oint \frac{\mu_0\,(p')^2\,|\nabla\psi|^2}{B^2}\,|\xi_n|^2\, + \mathcal J\,d\theta,\\ +\texttt{epf\_total} +&=\int\! d\psi\,\frac{1}{\mu_0}\oint|\vec C|^2\,\mathcal J\,d\theta + \;\;(=\texttt{int\_J\_C2\_over\_mu0}\ \text{of recon1}). +\end{align} +The closure of the decomposition is reported as +\begin{equation} +\texttt{identity\_res} +=\texttt{epf\_total} +-\bigl(\texttt{A\_total}+\texttt{B\_total} + +\texttt{Cpar\_total}+\texttt{Iperp\_total}\bigr), +\end{equation} +which must vanish to roundoff since +$|\vec C|^2=|\vec Q|^2+2\,\mathrm{Re}(\vec V^{*}\!\cdot\vec Q)+|\vec V|^2$. + +\subsubsection{Combined $\delta W_p$ and the $K_2$ cancellation} +\label{sec:recon3_combined} + +The recon3 outputs $(A,B,I_\perp)$ can be combined with the recon1 +destabilizing pieces $\mathrm{dst}_1,\mathrm{dst}_3$ to write $\delta W_p$ in a +form in which the parallel-current contribution cancels identically. Start +from the recon1 form, Eq.~\eqref{eq:dWp_recon1}, +\begin{equation} +\delta W_p=\frac12\Bigl[\int\mathcal J\,\frac{|\vec C|^2}{\mu_0}\, +-\int\mathcal J\,K\,|\xi_n|^2\Bigr]. +\end{equation} +The recon3 decomposition of the stabilizing density reads +\begin{equation} +\int\mathcal J\,\frac{|\vec C|^2}{\mu_0} +=A+B+C_\parallel+C_\perp, +\qquad +C_\parallel=\mathrm{dst}_2\ (=K_2|\xi_n|^2\ \text{integral}),\quad +C_\perp=I_\perp, +\end{equation} +while the curvature kernel splits as +$\int\mathcal J\,K\,|\xi_n|^2=\mathrm{dst}_1+\mathrm{dst}_2+\mathrm{dst}_3$. +Substituting, +\begin{equation} +2\,\delta W_p +=(A+B+\mathrm{dst}_2+I_\perp) +-(\mathrm{dst}_1+\mathrm{dst}_2+\mathrm{dst}_3). +\end{equation} +The term $\mathrm{dst}_2$ ($=K_2|\xi_n|^2$, the parallel-current$^2$ piece) +enters $|\vec C|^2$ and $K$ with \emph{opposite} sign and cancels exactly, +leaving +\begin{equation} +\boxed{\;2\,\delta W_p=A+B+I_\perp-\mathrm{dst}_1-\mathrm{dst}_3\;.} +\label{eq:dWp_combined} +\end{equation} +Written out term by term, +\begin{equation} +\delta W_p +=\frac12\!\int\!\mathcal J\,d\psi\,d\theta\, +\Bigl[ +\underbrace{\tfrac{|\vec Q|^2}{\mu_0}}_{A_{\text{density}}} +\;+\; +\underbrace{\tfrac{2\,\mathrm{Re}(\vec V^{*}\!\cdot\vec Q)}{\mu_0}}_{B_{\text{density}}} +\;+\; +\underbrace{\tfrac{\mu_0\,p'^2\,|\nabla\psi|^2}{B^2}\,|\xi_n|^2}_{I_{\perp,\text{density}}} +\;-\; +\underbrace{|\nabla\psi|^2\,\sigma S\,|\xi_n|^2}_{K_1|\xi_n|^2\,(\mathrm{dst}_1)} +\;-\; +\underbrace{2p'\kappa_\psi\,|\xi_n|^2}_{K_3|\xi_n|^2\,(\mathrm{dst}_3)} +\Bigr], +\end{equation} +with $\vec V=\xi_n\,\mu_0\,\vec j\times\hat n$ and +$\sigma=\vec j\cdot\vec B/B^2$. In the DIII-D example +($n=1$, \texttt{gpec.log}) this gives +$2\,\delta W_p=0.81768+(-0.99951)+0.04056-(-0.03096)-0.12767=-0.23798$, +i.e. $\delta W_p=-0.11899$, matching \texttt{dW\_p(recon)} to roundoff. + +\paragraph{Physical role of each term.} +\begin{itemize} +\item $+A$ (sign $+$, stabilizing): magnetic energy of the perturbed field + $\vec Q$ itself (field-line bending). +\item $+B$ (sign-indefinite): cross-coupling of $\vec Q$ with the equilibrium + current, $2\,\mathrm{Re}(\vec V^{*}\!\cdot\vec Q)$ --- acts as a kink + driver or suppressor depending on phase. +\item $+I_\perp$ (sign $+$): Pfirsch--Schl\"uter perpendicular-current$^2$ + contribution $\propto p'^2|\nabla\psi|^2|\xi_n|^2/B^2$, positive definite. +\item $-\mathrm{dst}_1$ (sign-indefinite): shear--current coupling + $|\nabla\psi|^2\sigma S\,|\xi_n|^2$; stabilizing or destabilizing with the + sign of $K_1$. +\item $-\mathrm{dst}_3$ (sign-indefinite): pressure--curvature term + $2p'\kappa_\psi\,|\xi_n|^2$; a ballooning drive when $p'\kappa_\psi<0$. +\end{itemize} + +\paragraph{Meaning of the vanishing $K_2$.} +The cancellation does \emph{not} mean the parallel current $j_\parallel^2$ is +absent from $\delta W$. The same quantity $C_\parallel=K_2|\xi_n|^2$ enters the +stabilizing $|\vec C|^2$ and the destabilizing $K$ with opposite sign, so the +explicit $j_\parallel^2$ piece cancels in the energy balance. The residual +effect of $j_\parallel$ survives only indirectly --- through the shear term +$K_1$ (its dependence on $\sigma S$) and through the cross term $B$. + +\subsection{Cross-checks between the three routes} + +The identities below are exact and are confirmed at roundoff in the DIII-D +example ($n=1$, \texttt{gpec.log}): +\begin{align} +\texttt{recon3:A\_total} &= \texttt{recon2:int\_J\_Q2\_over\_mu0} + &&(0.817676),\\ +\texttt{recon3:Cpar\_total} &= \texttt{recon1:K2\_xin2} + = \texttt{gpeq\_dst}:dst_2 &&(1.082617),\\ +\texttt{recon3:epf\_total} &= \texttt{recon1:int\_J\_C2\_over\_mu0} + &&(0.941346),\\ +\texttt{recon1:dW\_p} &\approx \texttt{recon2:dW\_p} + &&(-0.1190\ \text{vs}\ -0.1198). +\end{align} + + +\clearpage +\appendix + +\section{Full reduction of the $\mathbf H_4$ ($\mathbf V$-sector) block}\label{app:h4} + +This appendix proves the $\mathbf H_4$ ($\mathbf V$-sector) identity quoted in +the main text at two levels: (i)~the equilibrium-free Bernstein form +$\mathbf H_4=\mathbf H_{MV}+\mathbf H_{OV}+\mathbf H_V+\mathbf H_K$, equal term by +term to \eqref{eq:ep_bernsteinK}; and (ii)~its collapse to the compact block +$\mathbf H_4=\mu_0p'\!\left(\tfrac{\chi''}{\chi'}\mathbf J+\mathbf J'\right)-2\pi +f'q'\chi'\,\mathbf I$, whose only equilibrium input is the Grad--Shafranov +equation. The steps that invoke an equilibrium relation (force balance, the +magnetic-surface condition $\mathbf B\cdot\nabla\psi=0$, Grad--Shafranov) are +noted where they occur. + +\subsection{Explicit pure-$\Xi_\psi$ term reductions}\label{app:reductions} + +The following term-by-term reductions of the pure-$\Xi_\psi$ contributions +were relocated here from the main text; they build the four pieces +$\mathbf H_{MV},\mathbf H_{OV},\mathbf H_V,\mathbf H_K$ assembled in the +closed form of Sec.~\ref{app:h4}. + +The term $\mathbf M^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf M$ is also a +pure-$\Xi_\psi$ contribution, but it is not closed as an independent DCON +block. It must be combined with the other pure scalar current--pressure terms +before the final $\mathbf H_4$ block is obtained. + +\begin{align} +\mathbf M +&= +(\nabla\theta\times\nabla\zeta)\,\mathcal J\,\mathbf B\cdot\nabla\xi_\psi= +2\pi i\,\chi' +\sum_m (m-nq)\,v_m\,e^{2\pi i(m\theta-n\zeta)}\,\vec v_1 \\ +\mathbf M^*&= +(-2\pi i)\chi' +\sum_m (m-nq)\,v_m^*\,e^{-2\pi i(m\theta-n\zeta)}\,\vec v_1 +\end{align} + +Using the corrected form of $\mathbf V$, +\begin{align} +\mathbf M^*\cdot\mathbf V +&= +(-2\pi i)\chi' +\sum_{m,m'} +v_m^*(m-nq)v_{m'} +e^{2\pi i(m'-m)\theta} +\; +\frac{1}{|\nabla\psi|^2}\, +\vec v_1\cdot(\mathcal A_2\vec v_2-\mathcal A_3\vec v_3) \\ +&= +(-2\pi i)\chi' +\sum_{m,m'} +v_m^*(m-nq)v_{m'} +e^{2\pi i(m'-m)\theta} +\; +\frac{1}{\mathcal J^2|\nabla\psi|^2} +\big( +\mathcal A_2 g_{12}-\mathcal A_3 g_{31} +\big) +\end{align} + +Now expand the bracket: +\begin{align} +\mathcal A_2 g_{12}-\mathcal A_3 g_{31} +&= +(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33})g_{12} +- +(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})g_{31} \\ +&= +\mu_0j^\theta(g_{12}g_{23}-g_{31}g_{22}) ++ +\mu_0j^\zeta(g_{12}g_{33}-g_{31}g_{23}) \\ +&= +\mathcal J^2 +\left[ +\mu_0j^\theta(\nabla\zeta\cdot\nabla\psi) +- +\mu_0j^\zeta(\nabla\theta\cdot\nabla\psi) +\right] +\end{align} +\begin{equation} +\boxed{ +\begin{aligned} +\mathbf M^*\cdot\mathbf V +&= +(-2\pi i)\chi' +\sum_{m,m'} +v_m^*(m-nq)v_{m'}e^{2\pi i(m'-m)\theta} \\ +&\qquad\times +\mu_0 +\frac{ +j^\theta(\nabla\zeta\cdot\nabla\psi) +- +j^\zeta(\nabla\theta\cdot\nabla\psi) +}{ +|\nabla\psi|^2 +}. +\end{aligned} +} +\end{equation} + +It is more useful to rewrite this in field form. Since +\begin{equation} +\mathcal J\,\mathbf B\cdot\nabla += +\chi'\left(\partial_\theta+q\partial_\zeta\right), +\end{equation} +we may write +\begin{align} +\mathcal J(\mathbf M^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf M) +&= +\chi' +\left[ +(\partial_\theta+q\partial_\zeta)\xi_\psi^* +\right] +\xi_\psi\, +\mathcal F ++ +\chi'\, +\xi_\psi^* +\left[ +(\partial_\theta+q\partial_\zeta)\xi_\psi +\right]\mathcal F, +\end{align} +where +\begin{equation} +\mathcal F +\equiv +\mathcal J +\frac{ +(\mu_0 j^\theta)(\nabla\zeta\cdot\nabla\psi) +- +(\mu_0 j^\zeta)(\nabla\theta\cdot\nabla\psi) +}{ +|\nabla\psi|^2 +}. +\end{equation} + +Because the equilibrium is axisymmetric and periodic in $\theta,\zeta$, we may integrate by parts in $\theta$ and drop the boundary term: +\begin{align} +\int d\theta\,d\zeta\;\mathcal J(\mathbf M^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf M) +&= +\chi'\int d\theta\,d\zeta\; +\partial_\theta(|\xi_\psi|^2)\,\mathcal F \\ +&= +-\chi' \int d\theta\,d\zeta\; +|\xi_\psi|^2\,\partial_\theta \mathcal F. +\end{align} + +Substituting $\mu_0 j^\theta$ and $\mu_0 j^\zeta$, +\begin{align} +\mathcal F +&= +\mathcal J +\left[ +-\frac{2\pi f'}{\mathcal J} +\frac{\nabla\zeta\cdot\nabla\psi}{|\nabla\psi|^2} ++ +\left( +\frac{\mu_0p'}{\chi'} ++ +\frac{2\pi f'q}{\mathcal J} +\right) +\frac{\nabla\theta\cdot\nabla\psi}{|\nabla\psi|^2} +\right] \\ +&= +2\pi f' +\frac{ +q(\nabla\psi\cdot\nabla\theta)-(\nabla\psi\cdot\nabla\zeta) +}{ +|\nabla\psi|^2 +} ++ +\frac{\mu_0p'}{\chi'}\, +\mathcal J\, +\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2}. +\end{align} + +Therefore +\begin{align} +\partial_\theta \mathcal F +&= +2\pi f'\, +\partial_\theta +\left[ +\frac{ +q(\nabla\psi\cdot\nabla\theta)-(\nabla\psi\cdot\nabla\zeta) +}{ +|\nabla\psi|^2 +} +\right] ++ +\frac{\mu_0p'}{\chi'}\, +\partial_\theta +\left[ +\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2} +\right]. +\end{align} + +Using the DCON-normalized shear parameter, +\begin{equation} +S_\psi += +\frac{\chi'^2}{\mathcal J} +\left[ +q' ++ +\partial_\theta +\left( +\frac{ +q(\nabla\psi\cdot\nabla\theta)-(\nabla\psi\cdot\nabla\zeta) +}{ +|\nabla\psi|^2 +} +\right) +\right], +\end{equation} +we obtain +\begin{equation} +\partial_\theta +\left[ +\frac{ +q(\nabla\psi\cdot\nabla\theta)-(\nabla\psi\cdot\nabla\zeta) +}{ +|\nabla\psi|^2 +} +\right] += +\frac{\mathcal J}{\chi'^2}S_\psi-q'. +\end{equation} + +Hence +\begin{align} +\partial_\theta\mathcal F +&= +\frac{2\pi f'}{\chi'^2}\,\mathcal J\,S_\psi +- +2\pi f'q' ++ +\frac{\mu_0p'}{\chi'}\, +\partial_\theta +\left[ +\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2} +\right]. +\end{align} + +\medskip +\noindent\rule{\linewidth}{0.4pt} +\medskip + +\paragraph{The term $\mathbf O^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf O$.} + +Since both $\mathbf O$ and $\mathbf V$ are proportional to $\xi_\psi$, this contribution is a pure-$\Xi_\psi$ block and can be written as +\[ +\Xi_\psi^\dagger \mathbf H_{UV} \Xi_\psi. +\] + +Recall +\begin{equation} +\mathbf O += +-\left( +\chi''\vec v_2+(q\chi')'\vec v_3 +\right)\xi_\psi. +\end{equation} +Hence +\begin{align} +\mathbf O^*\cdot\mathbf V +&= +-\frac{|\xi_\psi|^2}{|\nabla\psi|^2} +\left( +\chi''\vec v_2+(q\chi')'\vec v_3 +\right)\cdot +\left( +\mathcal A_2\vec v_2-\mathcal A_3\vec v_3 +\right) \\ +&= +-\frac{|\xi_\psi|^2}{\mathcal J^2|\nabla\psi|^2} +\left[ +\chi''(\mathcal A_2 g_{22}-\mathcal A_3 g_{23}) ++ +(q\chi')'(\mathcal A_2 g_{23}-\mathcal A_3 g_{33}) +\right]. +\end{align} + +Now simplify each bracket: +\begin{align} +\mathcal A_2 g_{22}-\mathcal A_3 g_{23} +&= +(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33})g_{22} +- +(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})g_{23} \\ +&= +\mu_0j^\zeta(g_{22}g_{33}-g_{23}^2) \\ +&= +\mu_0j^\zeta \mathcal J^2 |\nabla\psi|^2, +\end{align} +and +\begin{align} +\mathcal A_2 g_{23}-\mathcal A_3 g_{33} +&= +(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33})g_{23} +- +(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})g_{33} \\ +&= +-\mu_0j^\theta(g_{22}g_{33}-g_{23}^2) \\ +&= +-\mu_0j^\theta \mathcal J^2 |\nabla\psi|^2. +\end{align} +Therefore +\begin{align} +\mathbf O^*\cdot\mathbf V +&= +-|\xi_\psi|^2 +\left[ +\mu_0j^\zeta\chi'' +- +\mu_0j^\theta(q\chi')' +\right]. +\end{align} + +Substituting $j^\theta$ and $j^\zeta$, +\begin{align} +\mathbf O^*\cdot\mathbf V +&= +-|\xi_\psi|^2 +\left[ +\left( +-\frac{\mu_0p'}{\chi'}-\frac{2\pi f'q}{\mathcal J} +\right)\chi'' +- +\left( +-\frac{2\pi f'}{\mathcal J} +\right)(q\chi')' +\right] \\ +&= +|\xi_\psi|^2 +\left[ +\mu_0p'\frac{\chi''}{\chi'} ++ +\frac{2\pi f'}{\mathcal J} +\left( +q\chi''-(q\chi')' +\right) +\right]. +\end{align} +Using +\begin{equation} +(q\chi')'=q'\chi'+q\chi'', +\end{equation} +we obtain +\begin{equation} +\boxed{ +\mathbf O^*\cdot\mathbf V += +|\xi_\psi|^2 +\left[ +\mu_0p'\frac{\chi''}{\chi'} +- +\frac{2\pi f' q'\chi'}{\mathcal J} +\right]. +} +\end{equation} + +\begin{equation} +\boxed{ +\begin{aligned} +\mathbf O^*\cdot\mathbf V+\mathbf V^*\cdot\mathbf O +\leadsto\; +\Xi_\psi^\dagger \mathbf H_{UV}\Xi_\psi, \\ +\qquad +\mathbf H_{UV} +&= +2\mu_0p'\frac{\chi''}{\chi'}\,\mathbf J +-4\pi f'q'\chi'\,\mathbf I. +\end{aligned} +} +\end{equation} + +\medskip +\noindent\rule{\linewidth}{0.4pt} +\medskip + +\paragraph{The term $|\mathbf V|^2$.} + +This is again a pure-$\Xi_\psi$ contribution, so we write +\[ +\Xi_\psi^\dagger \mathbf H_V \Xi_\psi. +\] + +From +\begin{equation} +\mathbf V += +\frac{\xi_\psi}{|\nabla\psi|^2} +(\mathcal A_2\vec v_2-\mathcal A_3\vec v_3), +\end{equation} +we have +\begin{align} +|\mathbf V|^2 +&= +\frac{|\xi_\psi|^2}{|\nabla\psi|^4} +(\mathcal A_2\vec v_2-\mathcal A_3\vec v_3)\cdot +(\mathcal A_2\vec v_2-\mathcal A_3\vec v_3) \\ +&= +\frac{|\xi_\psi|^2}{\mathcal J^2|\nabla\psi|^4} +\left[ +\mathcal A_2^2 g_{22} +-2\mathcal A_2\mathcal A_3 g_{23} ++\mathcal A_3^2 g_{33} +\right]. +\end{align} + +Now expand the quadratic form: +\begin{align} +&\mathcal A_2^2 g_{22} +-2\mathcal A_2\mathcal A_3 g_{23} ++\mathcal A_3^2 g_{33} \\ +&= +(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33})^2 g_{22} +-2(\mu_0j^\theta g_{23}+\mu_0j^\zeta g_{33}) +(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})g_{23} \\ +&\qquad ++(\mu_0j^\theta g_{22}+\mu_0j^\zeta g_{23})^2 g_{33} \\ +&= +(g_{22}g_{33}-g_{23}^2) +\left[ +\mu_0^2g_{22}(j^\theta)^2 ++ +2\mu_0^2g_{23}j^\theta j^\zeta ++ +\mu_0^2g_{33}(j^\zeta)^2 +\right]. +\end{align} +Therefore +\begin{align} +|\mathbf V|^2 +&= +\frac{|\xi_\psi|^2}{\mathcal J^2|\nabla\psi|^4} +(g_{22}g_{33}-g_{23}^2) +\left[ +\mu_0^2g_{22}(j^\theta)^2 ++ +2\mu_0^2g_{23}j^\theta j^\zeta ++ +\mu_0^2g_{33}(j^\zeta)^2 +\right] \\ +&= +\frac{|\xi_\psi|^2}{|\nabla\psi|^2} +\left[ +\mu_0^2g_{22}(j^\theta)^2 ++ +2\mu_0^2g_{23}j^\theta j^\zeta ++ +\mu_0^2g_{33}(j^\zeta)^2 +\right]. +\end{align} + +This is the completely explicit covariant-form result. + +It is also useful to rewrite it invariantly. Since +\begin{equation} +\mathbf j += +\sigma \mathbf B ++\frac{p'}{B^2}\mathbf B\times \nabla\psi, +\qquad +\sigma\equiv \frac{\mathbf j\cdot\mathbf B}{B^2}, +\end{equation} +one finds +\begin{equation} +\frac{|\mu_0\mathbf j\times\nabla\psi|^2}{|\nabla\psi|^2} += +\mu_0\sigma\,(\mu_0\mathbf j\cdot\mathbf B) ++ +\frac{(\mu_0p')^2}{B^2}|\nabla\psi|^2. +\end{equation} +Hence +\begin{equation} +\boxed{ +|\mathbf V|^2 += +|\xi_\psi|^2 +\left[ +\frac{\mu_0\sigma\,(\mu_0\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2} ++ +\frac{(\mu_0p')^2}{B^2} +\right]. +} +\end{equation} + +With the Lagrange identity +$|\mathbf j|^2B^2=(\mathbf j\cdot\mathbf B)^2+|\mathbf j\times\mathbf B|^2$ and +$\sigma\equiv\mathbf j\cdot\mathbf B/B^2$ (so the bracket +$\mu_0^2[g_{22}(j^\theta)^2+2g_{23}j^\theta j^\zeta+g_{33}(j^\zeta)^2]=\mu_0^2|\mathbf j|^2$), +the difference between the two forms of $|\mathbf V|^2/|\xi_\psi|^2$ is an exact +equality, not a proportionality: +\begin{equation} +\frac{\mu_0^2|\mathbf j|^2}{|\nabla\psi|^2} +-\left[\frac{\mu_0^2\sigma(\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2} ++\frac{(\mu_0p')^2}{B^2}\right] +=\frac{\mu_0^2|\mathbf j\times\mathbf B|^2-(\mu_0p')^2|\nabla\psi|^2} + {B^2|\nabla\psi|^2} +=0 , +\end{equation} +the numerator vanishing identically by force balance +$\mu_0\mathbf j\times\mathbf B=\mu_0\nabla p=\mu_0 p'\nabla\psi$. +\begin{equation} +\begin{aligned} +|\mathbf V|^2 +&= +\frac{|\xi_\psi|^2}{|\nabla\psi|^2} +\left[ +\mu_0^2 g_{22}(j^\theta)^2 ++2\mu_0^2 g_{23}\,j^\theta j^\zeta ++\mu_0^2 g_{33}(j^\zeta)^2 +\right] \\ +&= +|\xi_\psi|^2 +\left[ +\frac{\mu_0^2\sigma(\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2} ++\frac{(\mu_0p')^2}{B^2} +\right]. +\end{aligned} +\end{equation} +Equivalently, in +the DCON currents~\eqref{eq:dconcurrents} the $2\pi f'q/\mathcal J$ pieces +cancel and $\chi'(q\,\mu_0 j^\theta-\mu_0 j^\zeta)=\mu_0p'$, whence +$\mu_0^2|\mathbf j\times\mathbf B|^2 +=\chi'^2(q\,\mu_0 j^\theta-\mu_0 j^\zeta)^2|\nabla\psi|^2=(\mu_0p')^2|\nabla\psi|^2$: +the parallel ($f'$) current supplies the +$\mu_0^2\sigma(\mathbf j\cdot\mathbf B)$ term and the in-surface pressure +current supplies the $(\mu_0p')^2/B^2$ term. + +\medskip +\noindent\rule{\linewidth}{0.4pt} +\medskip + +The four $\mathbf V$-touching contributions combine, under a common +$\mathcal J$ weight and before any equilibrium input, as + +\begin{equation} +\begin{aligned} +&\int d\theta\,d\zeta\;\mathcal J +\left[ +(\mathbf M^*\!\cdot\mathbf V+\mathbf V^*\!\cdot\mathbf M) ++(\mathbf O^*\!\cdot\mathbf V+\mathbf V^*\!\cdot\mathbf O) ++|\mathbf V|^2 +-\mu_0K\frac{|\xi_\psi|^2}{|\nabla\psi|^2} +\right]\\ +&=\int d\theta\,d\zeta\, +\bigg[|\xi_\psi|^2\Big( +\underbrace{-\frac{2\pi f'}{\chi'}\,\mathcal J\,S_\psi+{\color{red}\cancel{2\pi \chi' f'q'}} +-\mu_0p'\partial_\theta\left[\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2}\right]}_{\mathbf M^*\mathbf V+\mathbf V^*\mathbf M} ++\underbrace{2\mu_0p'\mathcal J\tfrac{\chi''}{\chi'}-2\pi f'q'\chi'\,{\color{red}\cancel{-\,2\pi f'q'\chi'}}}_{\mathbf O^*\mathbf V+\mathbf V^*\mathbf O}\\ +&+\underbrace{{\color{blue}\cancel{\mathcal J\tfrac{\mu_0^2\sigma(\mathbf j\cdot\mathbf B)}{|\nabla\psi|^2}}} ++\mathcal J\tfrac{(\mu_0p')^2}{B^2}}_{|\mathbf V|^2} +\underbrace{-\,\mathcal J\mu_0\sigma S_\psi +\,{\color{blue}\cancel{-\,\mathcal J\tfrac{\mu_0^2\sigma^2B^2}{|\nabla\psi|^2}}} +-2\mu_0p'\mathcal J\tfrac{\kappa_\psi}{|\nabla\psi|^2}}_{-\mu_0K|\xi_\psi|^2/|\nabla\psi|^2} +\Big) +\bigg]\\ +&=\int d\theta\,d\zeta\, +\bigg[|\xi_\psi|^2\Big( +-\mu_0p'\partial_\theta\left[\mathcal J\frac{\nabla\psi\cdot\nabla\theta}{|\nabla\psi|^2}\right] ++2\mu_0p'\mathcal J\tfrac{\chi''}{\chi'}-2\pi f'q'\chi'\, ++\mathcal J\tfrac{(\mu_0p')^2}{B^2} +-\,\big(\mu_0\sigma +\frac{2\pi f'}{\chi'}\big)\mathcal JS_\psi +-2\mu_0p'\mathcal J\tfrac{\kappa_\psi}{|\nabla\psi|^2} +\Big)\bigg]. +\end{aligned} +\end{equation} + +\subsection{The $\sigma$ identity: collapse of the parallel-current coefficient} +\label{app:sigma} + +The remaining bracket closes because of a single physical identity: the +shear/parallel-current coefficient $\tfrac{2\pi f'}{\chi'}+\mu_0\sigma$ is not +zero but a pure pressure--geometry quantity. Write +$\sigma=\mathbf j\cdot\mathbf B/B^2$ in current components, using +$\mathbf B=\chi'\,(q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta)$ and +$\mathbf j=\mathcal J\,(j^\theta\,\nabla\zeta\times\nabla\psi+j^\zeta\,\nabla\psi\times\nabla\theta)$: +\begin{equation} +\begin{aligned} +\mu_0\mathbf j\cdot\mathbf B +&=\mathcal J\chi'\big[\mu_0 j^\theta\,(\nabla\zeta\times\nabla\psi)+\mu_0 j^\zeta\,(\nabla\psi\times\nabla\theta)\big] +\cdot(q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta),\\ +B^2&=\chi'^2\,|q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta|^2. +\end{aligned} +\end{equation} +Inserting the DCON contravariant currents +$\mu_0 j^\theta=-\dfrac{2\pi f'}{\mathcal J}$, +$\mu_0 j^\zeta=-\dfrac{\mu_0p'}{\chi'}-\dfrac{2\pi f'q}{\mathcal J}$ +and using $\nabla\zeta\times\nabla\psi=-\nabla\psi\times\nabla\zeta$ to group the $f'$ terms, +\begin{equation} +\begin{aligned} +\mu_0\mathbf j\cdot\mathbf B +&=\mathcal J\chi'\left[-\frac{2\pi f'}{\mathcal J}(q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta)-\frac{\mu_0p'}{\chi'}(\nabla\psi\times\nabla\theta)\right]\\ +&\qquad\cdot(q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta)\\ +&=-2\pi f'\chi'\,|q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta|^2 +-\mu_0p'\mathcal J\,(\nabla\psi\times\nabla\theta)\cdot(q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta). +\end{aligned} +\end{equation} +Dividing by $B^2$, +\begin{equation} +\mu_0\sigma=-\frac{2\pi f'}{\chi'}-\frac{\mu_0p'\mathcal J\,(\nabla\psi\times\nabla\theta)\cdot(q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta)}{B^2}, +\end{equation} +so the parallel-current coefficient collapses to a pure-$p'$ form, +\begin{equation} +\frac{2\pi f'}{\chi'}+\mu_0\sigma +=-\frac{\mu_0p'\mathcal J\,(\nabla\psi\times\nabla\theta)\cdot(q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta)}{B^2}. +\end{equation} +Applying the cross-product identity +$(\mathbf A\times\mathbf B)\cdot(\mathbf C\times\mathbf D)=(\mathbf A\cdot\mathbf C)(\mathbf B\cdot\mathbf D)-(\mathbf A\cdot\mathbf D)(\mathbf B\cdot\mathbf C)$ +to the two cross-product factors, + +$|q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta|^2=(\nabla\psi\cdot\nabla\psi)(q^2\nabla\theta\cdot\nabla\theta-2q\,\nabla\theta\cdot\nabla\zeta+\nabla\zeta\cdot\nabla\zeta) +-(q\,\nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta)^2,$ +$(\nabla\psi\times\nabla\theta)\cdot(q\,\nabla\psi\times\nabla\theta-\nabla\psi\times\nabla\zeta)=(\nabla\psi\cdot\nabla\psi)(q\,\nabla\theta\cdot\nabla\theta-\nabla\theta\cdot\nabla\zeta) +-(\nabla\psi\cdot\nabla\theta)(q\,\nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta).$ + +The first is $B^2/\chi'^2$; the second turns the collapse into the pure-$p'$, dot-product form +\begin{equation} +\frac{2\pi f'}{\chi'}+\mu_0\sigma +=-\frac{\mu_0p'\mathcal J}{B^2} +\big[(\nabla\psi\cdot\nabla\psi)(q\,\nabla\theta\cdot\nabla\theta-\nabla\theta\cdot\nabla\zeta) +-(\nabla\psi\cdot\nabla\theta)(q\,\nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta)\big]. +\end{equation} + +and define $\mathcal D\equiv\big[(\nabla\psi\cdot\nabla\psi)(q\,\nabla\theta\cdot\nabla\theta-\nabla\theta\cdot\nabla\zeta) +-(\nabla\psi\cdot\nabla\theta)(q\,\nabla\psi\cdot\nabla\theta-\nabla\psi\cdot\nabla\zeta)\big]$ to obtain the final form + +\begin{equation} +\frac{2\pi f'}{\chi'}+\mu_0\sigma=-\frac{\mu_0p'\mathcal J\,\mathcal D}{B^2}. +\end{equation} +This $\mathcal D$ can be expressed in terms of the metric coefficients as +\begin{equation} +\begin{aligned} +\mathcal D +&\equiv g^{\psi\psi}(q\,g^{\theta\theta}-g^{\theta\zeta}) +-g^{\psi\theta}(q\,g^{\psi\theta}-g^{\psi\zeta})=\frac{q\,g_{\zeta\zeta}+g_{\theta\zeta}}{\mathcal J^2}. +%=\frac{\partial_q G_\alpha}{2\mathcal J^2}, +\end{aligned} +\end{equation} + +\begin{equation} +G_\alpha\equiv g_{\theta\theta}+2q\,g_{\theta\zeta}+q^2g_{\zeta\zeta}=\frac{\mathcal J^2B^2}{\chi'^2}. +\end{equation} + +\subsection{Master identity and the Grad--Shafranov closure} +\label{app:master} + +The closure is most transparent pointwise in $\theta$. Write the sector as +$\int d\theta\,\mathcal W\,|\xi_\psi|^2$, abbreviating $g^{\psi\psi}=|\nabla\psi|^2$. The +assembled pointwise weight $\mathcal W$ was obtained at the end of +Sec.~\ref{app:reductions}; inserting the $\sigma$ identity of +Sec.~\ref{app:sigma}, +$\tfrac{2\pi f'}{\chi'}+\mu_0\sigma=-\tfrac{\mu_0p'\mathcal J\,\mathcal D}{B^2}$, +into its shear term $-\big(\mu_0\sigma+\tfrac{2\pi f'}{\chi'}\big)\mathcal J S_\psi$ +gives +\begin{equation} +\mathcal W=-2\pi f'q'\chi' ++2\mu_0p'\tfrac{\chi''}{\chi'}\mathcal J ++\mathcal J\tfrac{(\mu_0p')^2}{B^2} +-\tfrac{2\mu_0p'\mathcal J\kappa_\psi}{g^{\psi\psi}} +-\mu_0p'\,\partial_\theta\!\big(\mathcal J\tfrac{g^{\psi\theta}}{g^{\psi\psi}}\big) ++\tfrac{\mu_0p'\mathcal J^2\mathcal D}{B^2}\,S_\psi . +\label{eq:step3} +\end{equation} +The target is +$\;W_\star=\mu_0p'\tfrac{\chi''}{\chi'}\mathcal J+\mu_0p'\mathcal J'-2\pi f'q'\chi'$, +i.e.\ $\mathbf H_4=\mu_0p'(\tfrac{\chi''}{\chi'}\mathbf J+\mathbf J')-2\pi f'q'\chi'\mathbf I$. + +\paragraph{Master identity ($\mathcal W=W_\star$, divided by $\mu_0p'$).} +Subtracting $W_\star$ from \eqref{eq:step3} (the $-2\pi f'q'\chi'$ and one $\mu_0p'\tfrac{\chi''}{\chi'}\mathcal J$ match): +\begin{equation} +\boxed{\; +\mathcal J\frac{\chi''}{\chi'} ++\frac{\mathcal J\,\mu_0p'}{B^2} +-\frac{2\mathcal J\,\kappa_\psi}{g^{\psi\psi}} +-\partial_\theta\!\Big(\mathcal J\frac{g^{\psi\theta}}{g^{\psi\psi}}\Big) ++\frac{\mathcal J^2\mathcal D}{B^2}\,S_\psi +=\mathcal J' \;} +\label{eq:master} +\end{equation} +with the inputs +\begin{equation} +S_\psi=\frac{\chi'^2}{\mathcal J}\Big[q'+\partial_\theta G_s\Big], +\qquad +G_s\equiv\frac{q\,g^{\psi\theta}-g^{\psi\zeta}}{g^{\psi\psi}}, +\label{eq:DSdef} +\end{equation} +and $\mathcal D$ as defined in Sec.~\ref{app:sigma}. + +\paragraph{Direct proof from Amp\`ere's law.} +The master identity \eqref{eq:master} is proved directly from +\(\nabla\times\mathbf B=\mu_0\mathbf j\); nothing beyond the covariant field +components $B_i$ is needed. Since $B^\theta=\chi'/\mathcal J$, +$B^\zeta=q\chi'/\mathcal J$, and $B^\psi=0$, +\begin{equation} +B_i=g_{i\theta}B^\theta+g_{i\zeta}B^\zeta +=\frac{\chi'}{\mathcal J}\,(g_{i\theta}+qg_{i\zeta}), +\qquad +B^2=B_\theta B^\theta+B_\zeta B^\zeta +=\frac{\chi'}{\mathcal J}\,\big(B_\theta+qB_\zeta\big), +\label{eq:Bcov_GS} +\end{equation} +so that +\begin{equation} +B_\theta+qB_\zeta=\frac{\mathcal J B^2}{\chi'}, +\qquad +\mathcal D +=\frac{q\,g_{\zeta\zeta}+g_{\theta\zeta}}{\mathcal J^2} +=\frac{B_\zeta}{\mathcal J\chi'}, +\label{eq:Dzeta_GS} +\end{equation} +the second using the cofactor form of $\mathcal D$ from +Sec.~\ref{app:sigma} together with +$B_\zeta=\tfrac{\chi'}{\mathcal J}(g_{\theta\zeta}+qg_{\zeta\zeta})$ +from \eqref{eq:Bcov_GS}. Moreover, raising the index with $B^\psi=0$, +\begin{equation} +0=B^\psi=g^{\psi\psi}B_\psi+g^{\psi\theta}B_\theta+g^{\psi\zeta}B_\zeta +\;\Longrightarrow\; +B_\psi +=-\frac{g^{\psi\theta}}{g^{\psi\psi}}\,\frac{\mathcal J B^2}{\chi'} ++G_s\,B_\zeta , +\label{eq:Bpsi_GS} +\end{equation} +where the definition \eqref{eq:DSdef} of $G_s$ supplies +$g^{\psi\zeta}/g^{\psi\psi}=q\,g^{\psi\theta}/g^{\psi\psi}-G_s$ and +\eqref{eq:Dzeta_GS} recombines $B_\theta+qB_\zeta$. + +For an axisymmetric equilibrium ($\partial_\zeta=0$) the contravariant +components of $\mu_0\mathbf j=\nabla\times\mathbf B$ are +\begin{equation} +\mu_0\mathcal J\,j^\psi=\partial_\theta B_\zeta, +\qquad +\mu_0\mathcal J\,j^\theta=-\partial_\psi B_\zeta, +\qquad +\mu_0\mathcal J\,j^\zeta=\partial_\psi B_\theta-\partial_\theta B_\psi . +\end{equation} +With the currents \eqref{eq:dconcurrents} and the magnetic-surface condition +$\mathbf j\cdot\nabla\psi=0$, +\begin{equation} +\partial_\theta B_\zeta=0, +\qquad +\partial_\psi B_\zeta=2\pi f', +\qquad +\partial_\psi B_\theta-\partial_\theta B_\psi +=-\frac{\mathcal J\mu_0p'}{\chi'}-2\pi qf' , +\label{eq:ampere_GS} +\end{equation} +i.e.\ $B_\zeta=2\pi f(\psi)$ is a flux function and the $j^\zeta$ component +carries the Grad--Shafranov force balance. + +Now differentiate $\mathcal JB^2/\chi'=B_\theta+qB_\zeta$ radially and insert +\eqref{eq:ampere_GS}; the explicit $2\pi qf'$ terms cancel: +\begin{equation} +\partial_\psi\!\Big(\frac{\mathcal JB^2}{\chi'}\Big) +=\partial_\psi B_\theta+q'B_\zeta+2\pi qf' +=\partial_\theta B_\psi+q'B_\zeta-\frac{\mathcal J\mu_0p'}{\chi'} . +\end{equation} +Expanding the left side by the product rule and multiplying through by +$\chi'/B^2$, +\begin{equation} +\mathcal J' +=\mathcal J\frac{\chi''}{\chi'} +-\mathcal J\frac{\partial_\psi B^2}{B^2} +-\frac{\mathcal J\mu_0p'}{B^2} ++\frac{\chi'}{B^2}\big(\partial_\theta B_\psi+q'B_\zeta\big) . +\label{eq:Jprime_GS} +\end{equation} +The $\theta$-derivative of \eqref{eq:Bpsi_GS} (with $\partial_\theta B_\zeta=0$) +gives +\begin{equation} +\frac{\chi'}{B^2}\,\partial_\theta B_\psi +=-\partial_\theta\!\Big(\mathcal J\frac{g^{\psi\theta}}{g^{\psi\psi}}\Big) +-\mathcal J\frac{g^{\psi\theta}}{g^{\psi\psi}}\,\frac{\partial_\theta B^2}{B^2} ++\frac{\chi' B_\zeta}{B^2}\,\partial_\theta G_s , +\end{equation} +while \eqref{eq:Dzeta_GS} and the definition \eqref{eq:DSdef} of $S_\psi$ +combine the $q'$ and $\partial_\theta G_s$ pieces into +\begin{equation} +\frac{\chi' B_\zeta}{B^2}\,\big(q'+\partial_\theta G_s\big) +=\frac{\mathcal J^2\mathcal D}{B^2}\,S_\psi . +\end{equation} +Hence \eqref{eq:Jprime_GS} becomes +\begin{equation} +\mathcal J' +=\mathcal J\frac{\chi''}{\chi'} +-\frac{\mathcal J\mu_0p'}{B^2} +-\frac{\mathcal J}{B^2}\Big(\partial_\psi B^2 ++\frac{g^{\psi\theta}}{g^{\psi\psi}}\,\partial_\theta B^2\Big) +-\partial_\theta\!\Big(\mathcal J\frac{g^{\psi\theta}}{g^{\psi\psi}}\Big) ++\frac{\mathcal J^2\mathcal D}{B^2}\,S_\psi . +\label{eq:Jprime_GS2} +\end{equation} +Finally, the curvature +$\kappa_\psi=\tfrac{g^{\psi\psi}}{B^2}\big[\mu_0p' ++\tfrac12\partial_\psi B^2+\tfrac12\partial_\theta B^2\,\tfrac{g^{\psi\theta}}{g^{\psi\psi}}\big]$ +identifies +\begin{equation} +-\frac{\mathcal J}{B^2}\Big(\partial_\psi B^2 ++\frac{g^{\psi\theta}}{g^{\psi\psi}}\,\partial_\theta B^2\Big) +=-\frac{2\mathcal J\kappa_\psi}{g^{\psi\psi}} ++\frac{2\mathcal J\mu_0p'}{B^2}, +\end{equation} +which turns \eqref{eq:Jprime_GS2} into exactly \eqref{eq:master}. Hence the +master identity is an analytic consequence of the Grad--Shafranov +equilibrium, not a numerical closure. + +This closes the reduction: multiplying~\eqref{eq:master} by $\mu_0p'$ +converts the geometric $\theta$-derivative +$-\mu_0p'\partial_\theta(\mathcal J\,g^{\psi\theta}/g^{\psi\psi})$, the +$(\mu_0p')^2/B^2$ term of $|\mathbf V|^2$, and the pressure--curvature part of +$K$ into $\mu_0p'\mathcal J'$, so the corresponding bracket of~\eqref{eq:step3} becomes +$\mu_0p'(\mathcal J'-\mathcal J\chi''/\chi')$. Together with the clean +\(\mu_0p'\mathcal J\chi''/\chi'\) piece already isolated in +\eqref{eq:step3}, this gives +\[ +\mathcal W +=-2\pi f'q'\chi' ++\mu_0p'\left(\mathcal J\frac{\chi''}{\chi'}+\mathcal J'\right), +\] +and the compact $\mathbf H_4$ follows by Fourier projection. + +\subsection{The two equivalent final forms of $\mathbf H_4$} + +\paragraph{Equilibrium-free form.} +Projecting the reduced pointwise weights of Sec.~\ref{app:reductions} directly, +\begin{equation} +\boxed{\;\mathbf H_4=\mathbf H_{YV}+\mathbf H_{UV}+\mathbf H_V+\mathbf H_K\;,} +\end{equation} +\begin{equation} +\begin{aligned} +\mathbf H_{UV}&=2\mu_0p'\tfrac{\chi''}{\chi'}\mathbf J-4\pi f'q'\chi'\,\mathbf I, +&\; +(\mathbf H_V)_{mm'}&=\Big\langle\tfrac{(\mu_0p')^2}{B^2}+\tfrac{\mu_0\sigma(\mu_0\mathbf j\cdot\mathbf B)}{g^{\psi\psi}}\Big\rangle_{mm'},\\[4pt] +(\mathbf H_K)_{mm'}&=-\Big\langle\mu_0\sigma S_\psi+\tfrac{\mu_0^2\sigma^2B^2}{g^{\psi\psi}}+\tfrac{2\mu_0p'\kappa_\psi}{g^{\psi\psi}}\Big\rangle_{mm'}, +&\; +(\mathbf H_{YV})_{mm'}&=-\chi'\!\int_0^1\! e^{2\pi i(m'-m)\theta}\,\partial_\theta\mathcal F\,d\theta, +\end{aligned} +\end{equation} +exact term by term ($=$ Eq.~\eqref{eq:ep_bernsteinK}), with no equilibrium +identity invoked. The compact form +$\mathbf H_4=\mu_0p'(\tfrac{\chi''}{\chi'}\mathbf J+\mathbf J')-2\pi f'q'\chi'\mathbf I$ +is the same matrix, obtained through the +Grad--Shafranov closure \eqref{eq:master} derived above. The global +term-to-matrix assignment and the resulting $\mathbf C,\mathbf E,\mathbf H$ +matrices are collected in the main text. + +\bibliographystyle{plain} +\bibliography{references} + +\end{document} diff --git a/docs/tex/recon/references.bib b/docs/tex/recon/references.bib new file mode 100644 index 000000000..deb3bc5fc --- /dev/null +++ b/docs/tex/recon/references.bib @@ -0,0 +1,58 @@ +@techreport{chance1992mhd, + author = {Chance, M. S. and Jardin, S. C. and Kessel, C. E. and Okabayashi, M. and others}, + title = {Mhd stability of tokamak plasmas}, + institution = {Princeton Univ., NJ (United States). Plasma Physics Lab}, + year = {1992}, + type = {Technical Report} +} +@article{RevModPhys.76.1071, + title = {Physics of magnetically confined plasmas}, + author = {Boozer, Allen H.}, + journal = {Rev. Mod. Phys.}, + volume = {76}, + issue = {4}, + pages = {1071--1141}, + numpages = {0}, + year = {2005}, + month = {Jan}, + publisher = {American Physical Society}, + doi = {10.1103/RevModPhys.76.1071}, + url = {https://link.aps.org/doi/10.1103/RevModPhys.76.1071} +} +@phdthesis{Park2009Ideal, + author = {Park, Jong-Kyu}, + title = {Ideal Perturbed Equilibria in Tokamaks}, + school = {Princeton University}, + year = {2009}, + month = {June}, + address = {Princeton, NJ}, + note = {Advisers: Jonathan E. Menard and Allen H. Boozer}, + department = {Department of Astrophysical Sciences, Program in Plasma Physics} +} +@incollection{Bernstein1983, + author = {Bernstein, I. B.}, + title = {Basic Plasma Physics I}, + booktitle = {Handbook of Plasma Physics, Volume 1}, + editor = {A. A. Galeev and R. N. Sudan}, + publisher = {North-Holland}, + address = {Amsterdam}, + year = {1983}, + pages = {421} +} +@article{10.1063/1.4958328, + author = {Glasser, A. H.}, + title = {The direct criterion of Newcomb for the ideal MHD stability of an axisymmetric toroidal plasma}, + journal = {Physics of Plasmas}, + volume = {23}, + number = {7}, + pages = {072505}, + year = {2016}, + month = {07}, + abstract = {A method is presented for determining the ideal magnetohydrodynamic stability of an axisymmetric toroidal plasma, based on a toroidal generalization of the method developed by Newcomb for fixed-boundary modes in a cylindrical plasma. For toroidal mode number n≠0, the stability problem is reduced to the numerical integration of a high-order complex system of ordinary differential equations, the Euler-Lagrange equation for extremizing the potential energy, for the coupled amplitudes of poloidal harmonics m as a function of the radial coordinate ψ in a straight-fieldline flux coordinate system. Unlike the cylindrical case, different poloidal harmonics couple to each other, which introduces coupling between adjacent singular intervals. A boundary condition is used at each singular surface, where m = nq and q(ψ) is the safety factor, to cross the singular surface and continue the solutions beyond it. Fixed-boundary instability is indicated by the vanishing of a real determinant of a Hermitian complex matrix constructed from the fundamental matrix of solutions, the generalization of Newcomb's crossing criterion. In the absence of fixed-boundary instabilities, an M × M plasma response matrix WP, with M the number of poloidal harmonics used, is constructed from the Euler-Lagrange solutions at the plasma-vacuum boundary. This is added to a vacuum response matrix WV to form a total response matrix WT. The existence of negative eigenvalues of WT indicates the presence of free-boundary instabilities. The method is implemented in the fast and accurate DCON code.}, + issn = {1070-664X}, + doi = {10.1063/1.4958328}, + url = {https://doi.org/10.1063/1.4958328}, + eprint = {https://pubs.aip.org/aip/pop/article-pdf/doi/10.1063/1.4958328/14131506/072505_1_online.pdf}, +} + + diff --git a/gpec/gpec.f b/gpec/gpec.f index 5342d12e5..3a8cc5cfc 100644 --- a/gpec/gpec.f +++ b/gpec/gpec.f @@ -39,7 +39,8 @@ PROGRAM gpec_main $ arbsurf_flag,angles_flag,surfmode_flag,rzpgrid_flag, $ singcurs_flag,m3d_flag,cas3d_flag,test_flag,nrzeq_flag, $ arzphifun_flag,xbrzphifun_flag,pmodbmn_flag,xclebsch_flag, - $ filter_flag,gal_flag,delpsi_flag,use_res_spot, + $ filter_flag,gal_flag,delpsi_flag,effective_field_flag, + $ use_res_spot, $ singthresh_callen_flag,singthresh_slayer_flag,singthresh_flag LOGICAL, DIMENSION(100) :: ss_flag COMPLEX(r8), DIMENSION(:), POINTER :: finmn,foutmn,xspmn, @@ -73,7 +74,9 @@ PROGRAM gpec_main $ xclebsch_flag,pbrzphi_flag,verbose,max_linesout,filter_flag, $ netcdf_flag,ascii_flag,singthresh_flag, $ singthresh_callen_flag,singthresh_slayer_flag, - $ singthresh_slayer_inpr,singthresh_slayer_inpr_prof + $ singthresh_slayer_inpr,singthresh_slayer_inpr_prof, + $ effective_field_flag,recon_int,curl_check_flag,recon_out, + $ standard_form_flag,effective_field_terms_flag NAMELIST/gpec_diagnose/singcurs_flag,xbcontra_flag, $ xbnobo_flag,d3_flag,div_flag,xbst_flag,jacfac_flag, $ pmodbmn_flag,rzphibx_flag,radvar_flag,eigen_flag,magpot_flag, @@ -199,6 +202,12 @@ PROGRAM gpec_main test_flag=.FALSE. eigm_flag=.FALSE. mutual_test_flag=.FALSE. + effective_field_flag=.FALSE. + standard_form_flag=.FALSE. + effective_field_terms_flag=.FALSE. + recon_int="spline" + curl_check_flag=.FALSE. + recon_out=.TRUE. majr=10.0 minr=1.0 @@ -229,6 +238,28 @@ PROGRAM gpec_main READ(in_unit,NML=gpec_output) READ(in_unit,NML=gpec_diagnose) CALL ascii_close(in_unit) + SELECT CASE(TRIM(recon_int)) + CASE("spline","trapezoid") + CASE DEFAULT + CALL gpec_stop("gpec_output recon_int must be spline or "// + $ "trapezoid") + END SELECT + ! recon diagnostics compare against ep(mode), so they need the + ! bare, unregularized DCON eigenmode selected by mode_flag. + IF (effective_field_flag .OR. standard_form_flag .OR. + $ effective_field_terms_flag) THEN + IF (.NOT. mode_flag .OR. reg_flag .OR. coil_flag .OR. + $ data_flag .OR. harmonic_flag) THEN + CALL gpec_stop("effective_field_flag/standard_form_flag/"// + $ "effective_field_terms_flag "// + $ "require mode_flag=t, reg_flag=f, and "// + $ "coil_flag=data_flag=harmonic_flag=f") + ENDIF + ENDIF + IF (curl_check_flag .AND. .NOT. effective_field_flag) THEN + PRINT *,"!! WARNING: curl_check_flag has no effect without "// + $ "effective_field_flag" + ENDIF galsol%gal_flag=gal_flag IF(timeit) CALL gpec_timer(0) c----------------------------------------------------------------------- @@ -619,7 +650,15 @@ PROGRAM gpec_main IF (arzphifun_flag) THEN CALL gpout_arzphifun(mode,xspmn) ENDIF - + IF (effective_field_flag) THEN + CALL gpout_recon(mode,xspmn) + ENDIF + IF (standard_form_flag) THEN + CALL gpout_recon2(mode,xspmn) + ENDIF + IF (effective_field_terms_flag) THEN + CALL gpout_recon3(mode,xspmn) + ENDIF c----------------------------------------------------------------------- c diagnose. c----------------------------------------------------------------------- @@ -775,4 +814,3 @@ PROGRAM gpec_main CALL gpec_dealloc CALL gpec_stop("Normal termination.") END PROGRAM gpec_main - diff --git a/gpec/gpeq.f b/gpec/gpeq.f index c0e503a96..04498bb72 100644 --- a/gpec/gpeq.f +++ b/gpec/gpeq.f @@ -14,17 +14,27 @@ c 6. gpeq_parallel c 7. gpeq_rzphi c 8. gpeq_surface -c 9. gpeq_fcoords -c 10. gpeq_fcoordsout -c 11. gpeq_bcoords -c 12. gpeq_bcoordsout -c 13. gpeq_weight -c 14. gpeq_rzpgrid -c 15. gpeq_rzpdiv -c 16. gpeq_alloc -c 17. gpeq_dealloc -c 18. gpeq_interp_singsurf -c 19. gpeq_interp_sol +c 9. gpeq_prep_c (reconstruction: build psi-local C state) +c 10. gpeq_c (reconstruction: C vector components) +c 11. gpeq_cveri (reconstruction: (curl C).grad(psi) check) +c 12. gpeq_firstform (reconstruction: first-form energy terms) +c 13. gpeq_epf (reconstruction: |C|^2 surface integral) +c 14. gpeq_dst (reconstruction: K xi_n^2 surface integral) +c 15. gpeq_shear (reconstruction: magnetic shear) +c 16. gpeq_curvature (reconstruction: curvature) +c 17. gpeq_K (reconstruction: Bernstein K quantity) +c 18. gpeq_epfterms (reconstruction: |C|^2 decomposition) +c 19. gpeq_fcoords +c 20. gpeq_fcoordsout +c 21. gpeq_bcoords +c 22. gpeq_bcoordsout +c 23. gpeq_weight +c 24. gpeq_rzpgrid +c 25. gpeq_rzpdiv +c 26. gpeq_alloc +c 27. gpeq_dealloc +c 28. gpeq_interp_singsurf +c 29. gpeq_interp_sol c----------------------------------------------------------------------- c subprogram 0. gpeq_mod. c module declarations. @@ -36,6 +46,9 @@ MODULE gpeq_mod USE idcon_mod IMPLICIT NONE +c floor for the recon divisors rfac, |grad psi|^2 and B^2, which +c vanish on the magnetic axis; the Jacobian is assumed nonzero. + REAL(r8), PARAMETER, PRIVATE :: recon_tiny = 1.0e-30_r8 CONTAINS c----------------------------------------------------------------------- @@ -96,7 +109,7 @@ SUBROUTINE gpeq_sol(psi) xss_mn=-MATMUL(bmat,xsp1_mn)-MATMUL(cmat,xsp_mn) ENDIF c----------------------------------------------------------------------- -c compute contravariant b fields. +c compute Jacobian-weighted contravariant b fields: J b^i. c----------------------------------------------------------------------- bwp_mn=(chi1*singfac*twopi*ifac*xsp_mn) bwt_mn=-(chi1*xsp1_mn+twopi*ifac*nn*xss_mn) @@ -225,7 +238,8 @@ SUBROUTINE gpeq_contra(psi) END SUBROUTINE gpeq_contra c----------------------------------------------------------------------- c subprogram 3. gpeq_cova. -c compute covariant components. +c compute covariant components from Jacobian-weighted ones. +c metric%cs holds g_ij/J, so the contraction cancels J. c----------------------------------------------------------------------- SUBROUTINE gpeq_cova(psi) c----------------------------------------------------------------------- @@ -261,7 +275,7 @@ SUBROUTINE gpeq_cova(psi) g31(1:mband)=CONJG(g31(-1:-mband:-1)) g12(1:mband)=CONJG(g12(-1:-mband:-1)) c----------------------------------------------------------------------- -c compute covariant components with metric tensors. +c compute Jacobian-weighted covariant components with metric tensors. c----------------------------------------------------------------------- ipert=0 xvp_mn=0 @@ -645,7 +659,1117 @@ SUBROUTINE gpeq_surface(psi) RETURN END SUBROUTINE gpeq_surface c----------------------------------------------------------------------- -c subprogram 9. gpeq_fcoords. +c subprogram 9. gpeq_prep_c. +c build the psi-local perturbed equilibrium state and the C vector +c in one pass: gpeq_sol/contra/cova/normal followed by gpeq_c. +c The recon kernels gpeq_cveri, gpeq_epf, gpeq_dst and +c gpeq_epfterms assume this has been called for the current psi. +c----------------------------------------------------------------------- + SUBROUTINE gpeq_prep_c(psi) +c----------------------------------------------------------------------- +c declaration. +c----------------------------------------------------------------------- + REAL(r8), INTENT(IN) :: psi + + CALL gpeq_sol(psi) + CALL gpeq_contra(psi) + CALL gpeq_cova(psi) + CALL gpeq_normal(psi) + CALL gpeq_c(psi, 0) +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gpeq_prep_c +c----------------------------------------------------------------------- +c subprogram 10. gpeq_c. +c compute C vector (covariant + contravariant) for EPF calculation. +c----------------------------------------------------------------------- + SUBROUTINE gpeq_c(psi, ipsi) +c----------------------------------------------------------------------- +c declaration. +c----------------------------------------------------------------------- + REAL(r8), INTENT(IN) :: psi + INTEGER, INTENT(IN) :: ipsi + + INTEGER :: itheta, ipert, jpert, m1, dm + + REAL(r8) :: q, f1raw, p1, chi1, jac + REAL(r8) :: eta, rfac, v21, v22, v23, v33, dpsq + REAL(r8), DIMENSION(3,3) :: w + REAL(r8), DIMENSION(0:mthsurf) :: jacs, r_vec + + COMPLEX(r8), DIMENSION(0:mthsurf) :: xwp_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: bwp_fun, bmt_fun, bmz_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: bvp_fun, bvt_fun, bvz_fun + +c Local temporal arrays for computation + COMPLEX(r8), DIMENSION(0:mthsurf) :: cwp_fun, cwt_fun, cwz_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: cvp_fun, cvt_fun, cvz_fun + +c Supporting variables for Fourier reconstruction + REAL(r8) :: jwt, jwz + +c Supporting functions + REAL(r8), DIMENSION(0:mthsurf) :: delpsi, dpdt, dpdz + REAL(r8), DIMENSION(0:mthsurf) :: g_22, g_23, g_33 + COMPLEX(r8), DIMENSION(-mband:mband) :: g11,g22,g33,g23,g31,g12 + + IF(debug_flag) PRINT *, "Entering gpeq_c at ipsi=", ipsi +c----------------------------------------------------------------------- +c 1) Setup: equilibrium and metric at this psi. +c----------------------------------------------------------------------- + CALL spline_eval(sq, psi, 1) + CALL cspline_eval(metric%cs, psi, 0) + q = sq%f(4) + f1raw = sq%f1(1) + p1 = sq%f1(2) / mu0 + chi1 = psio * twopi +c----------------------------------------------------------------------- + CALL iscdftb(mfac,mpert,xwp_fun,mthsurf,xwp_mn) +c----------------------------------------------------------------------- +c 3) Reconstruct eigenfunctions in spatial representation. +c----------------------------------------------------------------------- + CALL iscdftb(mfac, mpert, bwp_fun, mthsurf, bwp_mn) + CALL iscdftb(mfac, mpert, bmt_fun, mthsurf, bmt_mn) + CALL iscdftb(mfac, mpert, bmz_fun, mthsurf, bmz_mn) + CALL iscdftb(mfac, mpert, bvp_fun, mthsurf, bvp_mn) + CALL iscdftb(mfac, mpert, bvt_fun, mthsurf, bvt_mn) + CALL iscdftb(mfac, mpert, bvz_fun, mthsurf, bvz_mn) + +c----------------------------------------------------------------------- +c 4) Evaluate equilibrium geometry and metric on the theta grid. +c No Jacobian division: upper family stays J-weighted (J Q^i, +c J C^i), lower family is plain covariant. +c----------------------------------------------------------------------- + DO itheta = 0, mthsurf + CALL bicube_eval(rzphi, psi, theta(itheta), 1) + jac = rzphi%f(4) + rfac = MAX(SQRT(MAX(rzphi%f(1), 0.0_r8)), recon_tiny) + eta = twopi*(theta(itheta) + rzphi%f(2)) + r_vec(itheta) = ro + rfac*COS(eta) + jacs(itheta) = jac + + w(1,1) = (1.0_r8+ rzphi%fy(2))*twopi**2*rfac*r_vec(itheta)/jac + w(1,2) = -rzphi%fy(1)*pi*r_vec(itheta)/(rfac*jac) + + w(2,1) = -twopi**2*rfac*r_vec(itheta)*rzphi%fx(2)/jac + w(2,2) = pi*r_vec(itheta)*rzphi%fx(1)/(rfac*jac) + + w(3,1) = (twopi*r_vec(itheta)*rfac/jac)* + $ (rzphi%fx(2)*rzphi%fy(3)-rzphi%fx(3)*(1+rzphi%fy(2))) + w(3,2) = (r_vec(itheta)/(2*rfac*jac))* + $ (rzphi%fx(3)*rzphi%fy(1)-rzphi%fx(1)*rzphi%fy(3)) + + delpsi(itheta) = SQRT(w(1,1)**2 + w(1,2)**2) + dpdt(itheta) = w(1,1)*w(2,1) + w(1,2)*w(2,2) + dpdz(itheta) = w(1,1)*w(3,1) + w(1,2)*w(3,2) + +c contravariant basis vectors (idcon_metric style - NO jac) + v21 = rzphi%fy(1)/(2*rfac) + v22 = (1+rzphi%fy(2))*twopi*rfac + v23 = rzphi%fy(3)*r_vec(itheta) + v33 = twopi*r_vec(itheta) + +c metric tensor: g_ij = sum(v_i * v_j) + g_22(itheta) = (v21**2 + v22**2 + v23**2) + g_33(itheta) = (v33**2) + g_23(itheta) = (v23*v33) + ENDDO +c----------------------------------------------------------------------- +c 6) Compute C components. +c bwp stores J Q^psi, while bmt/bmz store the modified +c upper-family J Q^theta/J Q^zeta used consistently by gpeq_cova. +c +c The metric stored in metric%cs is g_ij / J, so lowering via gpeq_cova +c maps J Q^i -> Q_i. Therefore bv* and cv2* are lower/covariant +c components without an extra Jacobian factor. +c +c xwp_fun stores J xi^psi. Therefore: +c - upper J C^i corrections use xwp_fun/(J |grad psi|^2), +c - lower C_i corrections use xwp_fun/|grad psi|^2 or xwp_fun*j^i. +c This matches Eqs. (148)-(151) together with the metric convention +c used in idcon_metric. +c----------------------------------------------------------------------- + DO itheta = 0, mthsurf + jac = jacs(itheta) + jwt = - f1raw / jac + jwz = - p1 * mu0 / chi1 - f1raw * q / jac + dpsq = MAX(delpsi(itheta)**2, recon_tiny) + +c J C^psi = J Q^psi + cwp_fun(itheta)= bwp_fun(itheta) + +c J C^theta = J Q^theta + xi^psi/|grad psi|^2 +c * [mu0 j^theta g_theta_zeta + mu0 j^zeta g_zeta_zeta] +c = J Q^theta + xwp_fun/(J |grad psi|^2) * [...] + cwt_fun(itheta) = bmt_fun(itheta) + xwp_fun(itheta) + $ / (dpsq * jac )* + $ (jwt * g_23(itheta) + jwz * g_33(itheta)) + +c J C^zeta = J Q^zeta - xi^psi/|grad psi|^2 +c * [mu0 j^theta g_theta_theta + mu0 j^zeta g_theta_zeta] +c = J Q^zeta - xwp_fun/(J |grad psi|^2) * [...] + cwz_fun(itheta) = bmz_fun(itheta) - xwp_fun(itheta) + $ / (dpsq * jac )* + $ (jwt * g_22(itheta) + jwz * g_23(itheta)) + +c C_psi = Q_psi + (J xi^psi)/|grad psi|^2 +c * [mu0 j^theta (grad psi.grad zeta) - mu0 j^zeta (grad psi.grad theta)] + cvp_fun(itheta) = bvp_fun(itheta) + + $ xwp_fun(itheta) / dpsq * + $ (jwt * dpdz(itheta) - jwz * dpdt(itheta)) + +c C_theta = Q_theta + (J xi^psi) mu0 j^zeta + cvt_fun(itheta) = bvt_fun(itheta) + + $ xwp_fun(itheta) * jwz + +c C_zeta = Q_zeta - (J xi^psi) mu0 j^theta + cvz_fun(itheta) = bvz_fun(itheta) - + $ xwp_fun(itheta) * jwt + + ENDDO + cwp_mn = 0 + cwt_mn = 0 + cwz_mn = 0 +c----------------------------------------------------------------------- +c compute lower half of matrices. +c----------------------------------------------------------------------- + g11(0:-mband:-1)=metric%cs%f(1:mband+1) + g22(0:-mband:-1)=metric%cs%f(mband+2:2*mband+2) + g33(0:-mband:-1)=metric%cs%f(2*mband+3:3*mband+3) + g23(0:-mband:-1)=metric%cs%f(3*mband+4:4*mband+4) + g31(0:-mband:-1)=metric%cs%f(4*mband+5:5*mband+5) + g12(0:-mband:-1)=metric%cs%f(5*mband+6:6*mband+6) +c----------------------------------------------------------------------- +c compute upper half of matrices. +c----------------------------------------------------------------------- + g11(1:mband)=CONJG(g11(-1:-mband:-1)) + g22(1:mband)=CONJG(g22(-1:-mband:-1)) + g33(1:mband)=CONJG(g33(-1:-mband:-1)) + g23(1:mband)=CONJG(g23(-1:-mband:-1)) + g31(1:mband)=CONJG(g31(-1:-mband:-1)) + g12(1:mband)=CONJG(g12(-1:-mband:-1)) + + CALL iscdftf(mfac, mpert, cwp_fun, mthsurf,cwp_mn) + CALL iscdftf(mfac, mpert, cwt_fun, mthsurf,cwt_mn) + CALL iscdftf(mfac, mpert, cwz_fun, mthsurf,cwz_mn) + CALL iscdftf(mfac, mpert, cvp_fun, mthsurf,cvp_mn) + CALL iscdftf(mfac, mpert, cvt_fun, mthsurf,cvt_mn) + CALL iscdftf(mfac, mpert, cvz_fun, mthsurf,cvz_mn) + + ipert = 0 + c2vp_mn = 0 + c2vt_mn = 0 + c2vz_mn = 0 + DO m1=mlow,mhigh + ipert=ipert+1 + DO dm=MAX(1-ipert,-mband),MIN(mpert-ipert,mband) + jpert=ipert+dm + c2vp_mn(ipert)=c2vp_mn(ipert)+g11(dm)*cwp_mn(jpert)+ + $ g12(dm)*cwt_mn(jpert)+g31(dm)*cwz_mn(jpert) + c2vt_mn(ipert)=c2vt_mn(ipert)+g12(dm)*cwp_mn(jpert)+ + $ g22(dm)*cwt_mn(jpert)+g23(dm)*cwz_mn(jpert) + c2vz_mn(ipert)=c2vz_mn(ipert)+g31(dm)*cwp_mn(jpert)+ + $ g23(dm)*cwt_mn(jpert)+g33(dm)*cwz_mn(jpert) + ENDDO + ENDDO + IF(debug_flag) PRINT *, "->Leaving gpeq_c at ipsi=", ipsi +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gpeq_c +c----------------------------------------------------------------------- +c subprogram 11. gpeq_cveri. +c verify the C-vector identity +c +c (curl C).grad(psi) +c = (d C_zeta / d theta - d C_theta / d zeta) / J +c +c using the single-n Fourier convention +c +c exp[2*pi*i*(m*theta - n*zeta)]. +c +c Therefore +c +c d/dtheta -> 2*pi*i*m +c d/dzeta -> -2*pi*i*n +c +c and +c +c (curl C).grad(psi) +c = (d_theta C_zeta + 2*pi*i*n*C_theta)/J. +c +c Assumes gpeq_prep_c(psi) has been called for this psi, so the +c module cvt_mn/cvz_mn hold the covariant C state. +c +c cveri_scale (optional) returns the size of the two terms that +c must cancel, (|d_theta C_zeta| + |d_zeta C_theta|)/|J|, so the +c residual can be judged relative to them. +c----------------------------------------------------------------------- + SUBROUTINE gpeq_cveri(psi, cveri_fun, cveri_scale) +c----------------------------------------------------------------------- +c declaration. +c----------------------------------------------------------------------- + REAL(r8), INTENT(IN) :: psi + COMPLEX(r8), DIMENSION(0:mthsurf), INTENT(OUT) :: cveri_fun + REAL(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) :: + $ cveri_scale + + INTEGER :: itheta, ipert + REAL(r8) :: jac + COMPLEX(r8), DIMENSION(mpert) :: dth_cvz_mn, dzt_cvt_mn + COMPLEX(r8), DIMENSION(mpert) :: curlpsi_mn + COMPLEX(r8), DIMENSION(0:mthsurf) :: dth_cvz_fun, dzt_cvt_fun + + IF(debug_flag) PRINT *, "Entering gpeq_cveri" +c----------------------------------------------------------------------- +c exact theta/zeta derivatives in mode space. +c----------------------------------------------------------------------- + DO ipert = 1, mpert + dth_cvz_mn(ipert) = twopi * ifac * mfac(ipert) * cvz_mn(ipert) + dzt_cvt_mn(ipert) = -twopi * ifac * nn * cvt_mn(ipert) + curlpsi_mn(ipert) = dth_cvz_mn(ipert) - dzt_cvt_mn(ipert) + ENDDO + + CALL iscdftb(mfac, mpert, cveri_fun, mthsurf, curlpsi_mn) + IF (PRESENT(cveri_scale)) THEN + CALL iscdftb(mfac, mpert, dth_cvz_fun, mthsurf, dth_cvz_mn) + CALL iscdftb(mfac, mpert, dzt_cvt_fun, mthsurf, dzt_cvt_mn) + ENDIF + +c----------------------------------------------------------------------- +c divide by Jacobian pointwise: +c (curl C).grad(psi) = (d_theta C_zeta - d_zeta C_theta)/J +c----------------------------------------------------------------------- + DO itheta = 0, mthsurf + CALL bicube_eval(rzphi, psi, theta(itheta), 0) + jac = rzphi%f(4) + cveri_fun(itheta) = cveri_fun(itheta) / jac + IF (PRESENT(cveri_scale)) cveri_scale(itheta) = + $ (ABS(dth_cvz_fun(itheta)) + ABS(dzt_cvt_fun(itheta))) + $ / ABS(jac) + ENDDO + + IF(debug_flag) PRINT *, "->Leaving gpeq_cveri" +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gpeq_cveri +c----------------------------------------------------------------------- +c subprogram 12. gpeq_firstform. +c evaluate the first-form plasma energy density components on one +c flux surface with gamma term omitted: +c +c |Q|^2 / mu0 +c j . (Q x xi*) / mu0 +c (div xi)* (xi . grad p) +c +c Here xi.grad p is evaluated directly as p'(psi) xi^psi, and +c div xi is evaluated directly from the Jacobian-weighted +c contravariant displacement: +c +c div xi = 1/J [ d(J xi^psi)/dpsi +c + d(J xi^theta)/dtheta +c + d(J xi^zeta)/dzeta ]. +c----------------------------------------------------------------------- + SUBROUTINE gpeq_firstform(psi, q2_int, jqx_int, pdiv_int, + $ total_int, q2_fun, jqx_fun, pdiv_fun, total_fun) +c----------------------------------------------------------------------- +c declaration. +c----------------------------------------------------------------------- + REAL(r8), INTENT(IN) :: psi + REAL(r8), INTENT(OUT) :: q2_int + COMPLEX(r8), INTENT(OUT) :: jqx_int, pdiv_int, total_int + COMPLEX(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) :: + $ q2_fun, jqx_fun, pdiv_fun, total_fun + + INTEGER :: itheta + REAL(r8) :: f1raw, p1, chi1, jac, jac1 + REAL(r8) :: q2_density + REAL(r8) :: mu0jtheta, mu0jzeta + COMPLEX(r8) :: jqx_density, total_density, pdiv_density + COMPLEX(r8) :: det_term, divxi_density, xigradp_density + COMPLEX(r8), DIMENSION(0:mthsurf) :: xsp_fun, xmp1_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: xwp_fun, xmt_fun, xmz_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: bwp_fun, bmt_fun, bmz_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: bvp_fun, bvt_fun, bvz_fun + TYPE(cspline_type) :: divspl + + IF(debug_flag) PRINT *, "Entering gpeq_firstform" +c----------------------------------------------------------------------- +c prepare psi-local perturbed equilibrium quantities. +c----------------------------------------------------------------------- + CALL gpeq_sol(psi) + CALL gpeq_contra(psi) + CALL gpeq_cova(psi) + + CALL spline_eval(sq, psi, 1) + f1raw = sq%f1(1) + p1 = sq%f1(2) / mu0 + chi1 = psio * twopi + + CALL iscdftb(mfac, mpert, xsp_fun, mthsurf, xsp_mn) + CALL iscdftb(mfac, mpert, xmp1_fun, mthsurf, xmp1_mn) + CALL iscdftb(mfac, mpert, xwp_fun, mthsurf, xwp_mn) + CALL iscdftb(mfac, mpert, xmt_fun, mthsurf, xmt_mn) + CALL iscdftb(mfac, mpert, xmz_fun, mthsurf, xmz_mn) + CALL iscdftb(mfac, mpert, bwp_fun, mthsurf, bwp_mn) + CALL iscdftb(mfac, mpert, bmt_fun, mthsurf, bmt_mn) + CALL iscdftb(mfac, mpert, bmz_fun, mthsurf, bmz_mn) + CALL iscdftb(mfac, mpert, bvp_fun, mthsurf, bvp_mn) + CALL iscdftb(mfac, mpert, bvt_fun, mthsurf, bvt_mn) + CALL iscdftb(mfac, mpert, bvz_fun, mthsurf, bvz_mn) + + CALL cspline_alloc(divspl, mthsurf, 2) + divspl%xs = theta + DO itheta = 0, mthsurf + divspl%fs(itheta,1) = xmt_fun(itheta) + divspl%fs(itheta,2) = xmz_fun(itheta) + ENDDO + CALL cspline_fit(divspl, "periodic") + + q2_int = 0.0_r8 + jqx_int = CMPLX(0.0_r8, 0.0_r8, r8) + pdiv_int = CMPLX(0.0_r8, 0.0_r8, r8) + total_int = CMPLX(0.0_r8, 0.0_r8, r8) + + DO itheta = 0, mthsurf + CALL bicube_eval(rzphi, psi, theta(itheta), 1) + CALL cspline_eval(divspl, theta(itheta), 1) + jac = rzphi%f(4) + jac1 = rzphi%fx(4) + +c pointwise contravariant current components: mu0 j^theta, +c mu0 j^zeta. These match the definitions already used in gpeq_c. + mu0jtheta = -f1raw / jac + mu0jzeta = -mu0 * p1 / chi1 - sq%f(4) * f1raw / jac + + q2_density = REAL(CONJG(bwp_fun(itheta)) * bvp_fun(itheta) + + $ CONJG(bmt_fun(itheta)) * bvt_fun(itheta) + + $ CONJG(bmz_fun(itheta)) * bvz_fun(itheta), r8) + $ / (mu0 * jac) + + det_term = -mu0jtheta * + $ (bwp_fun(itheta) * CONJG(xmz_fun(itheta)) - + $ bmz_fun(itheta) * CONJG(xwp_fun(itheta))) + + $ mu0jzeta * + $ (bwp_fun(itheta) * CONJG(xmt_fun(itheta)) - + $ bmt_fun(itheta) * CONJG(xwp_fun(itheta))) + jqx_density = det_term / (mu0 * jac) + + divxi_density = xmp1_fun(itheta) + + $ (jac1 / jac) * xsp_fun(itheta) + + $ divspl%f1(1) / jac - + $ (twopi * ifac * nn) * divspl%f(2) / jac + xigradp_density = p1 * xsp_fun(itheta) + pdiv_density = CONJG(divxi_density) * xigradp_density + total_density = CMPLX(q2_density, 0.0_r8, r8) - + $ jqx_density + pdiv_density + + IF (PRESENT(q2_fun)) q2_fun(itheta) = + $ CMPLX(q2_density, 0.0_r8, r8) + IF (PRESENT(jqx_fun)) jqx_fun(itheta) = jqx_density + IF (PRESENT(pdiv_fun)) pdiv_fun(itheta) = pdiv_density + IF (PRESENT(total_fun)) total_fun(itheta) = total_density + + IF (itheta < mthsurf) THEN + q2_int = q2_int + q2_density * jac / REAL(mthsurf, r8) + jqx_int = jqx_int + jqx_density * jac / REAL(mthsurf, r8) + pdiv_int = pdiv_int + pdiv_density * jac / + $ REAL(mthsurf, r8) + total_int = total_int + total_density * jac / + $ REAL(mthsurf, r8) + ENDIF + ENDDO + + CALL cspline_dealloc(divspl) + + IF(debug_flag) PRINT *, "->Leaving gpeq_firstform" +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gpeq_firstform +c----------------------------------------------------------------------- +c subprogram 13. gpeq_epf. +c compute the flux-surface integral of the first EPF kernel, +c +c \int dtheta dzeta J |C|^2 / mu0 +c +c and return the theta/zeta integrated value for one psi. +c Assumes gpeq_prep_c(psi) has been called for this psi. +c----------------------------------------------------------------------- + SUBROUTINE gpeq_epf(psi, epf_int, epf_p, epf_t, epf_z, + $ epf_den_fun, epf_p_fun, epf_t_fun, epf_z_fun) +c----------------------------------------------------------------------- +c declaration. +c----------------------------------------------------------------------- + REAL(r8), INTENT(IN) :: psi + REAL(r8), INTENT(OUT) :: epf_int + REAL(r8), OPTIONAL, INTENT(OUT) :: epf_p, epf_t, epf_z +c optional per-theta C^2/mu0 densities for R-Z heatmaps. Same +c convention as gpeq_epfterms epf_density: integral = sum den*jac/mth. + REAL(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) :: + $ epf_den_fun, epf_p_fun, epf_t_fun, epf_z_fun + INTEGER :: itheta + COMPLEX(r8), DIMENSION(0:mthsurf) :: cwp_fun, cwt_fun, cwz_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: cvp_fun, cvt_fun, cvz_fun + REAL(r8) :: epf_psi_int, epf_theta_int, epf_zeta_int + REAL(r8) :: dpsi_d, dthe_d, dzet_d, jac + + IF(debug_flag) PRINT *, "Entering gpeq_epf" +c----------------------------------------------------------------------- +c reconstruct the C components prepared by gpeq_prep_c. +c----------------------------------------------------------------------- + CALL iscdftb(mfac, mpert, cwp_fun, mthsurf, cwp_mn) + CALL iscdftb(mfac, mpert, cwt_fun, mthsurf, cwt_mn) + CALL iscdftb(mfac, mpert, cwz_fun, mthsurf, cwz_mn) + CALL iscdftb(mfac, mpert, cvp_fun, mthsurf, cvp_mn) + CALL iscdftb(mfac, mpert, cvt_fun, mthsurf, cvt_mn) + CALL iscdftb(mfac, mpert, cvz_fun, mthsurf, cvz_mn) + + epf_int = 0.0_r8 + epf_psi_int = 0.0_r8 + epf_theta_int = 0.0_r8 + epf_zeta_int = 0.0_r8 + DO itheta = 0, mthsurf + CALL bicube_eval(rzphi, psi, theta(itheta), 0) + jac = rzphi%f(4) +c per-theta densities (Re(conjg(cw_i) cv_i)/(mu0 jac)). + dpsi_d = REAL(CONJG(cwp_fun(itheta)) * cvp_fun(itheta), r8) + $ / (mu0 * jac) + dthe_d = REAL(CONJG(cwt_fun(itheta)) * cvt_fun(itheta), r8) + $ / (mu0 * jac) + dzet_d = REAL(CONJG(cwz_fun(itheta)) * cvz_fun(itheta), r8) + $ / (mu0 * jac) + IF (PRESENT(epf_p_fun)) epf_p_fun(itheta) = dpsi_d + IF (PRESENT(epf_t_fun)) epf_t_fun(itheta) = dthe_d + IF (PRESENT(epf_z_fun)) epf_z_fun(itheta) = dzet_d + IF (PRESENT(epf_den_fun)) epf_den_fun(itheta) = + $ dpsi_d + dthe_d + dzet_d + IF (itheta < mthsurf) THEN + epf_psi_int = epf_psi_int + dpsi_d*jac/REAL(mthsurf, r8) + epf_theta_int = epf_theta_int + dthe_d*jac/REAL(mthsurf, r8) + epf_zeta_int = epf_zeta_int + dzet_d*jac/REAL(mthsurf, r8) + ENDIF + ENDDO + epf_int = epf_psi_int + epf_theta_int + epf_zeta_int + IF (PRESENT(epf_p)) epf_p = epf_psi_int + IF (PRESENT(epf_t)) epf_t = epf_theta_int + IF (PRESENT(epf_z)) epf_z = epf_zeta_int + + IF(debug_flag) PRINT *, "->Leaving gpeq_epf" +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gpeq_epf +c----------------------------------------------------------------------- +c subprogram 14. gpeq_dst. +c compute the flux-surface integral of the destabilizing kernel, +c +c \int dtheta dzeta J * K * xi_n^2 +c +c and return the theta/zeta integrated value for one psi. +c Assumes gpeq_prep_c(psi) has been called for this psi. +c----------------------------------------------------------------------- + SUBROUTINE gpeq_dst(psi, dst_int, dst_t1, dst_t2, dst_t3, + $ dst1_den_fun, dst2_den_fun, dst3_den_fun) + REAL(r8), INTENT(IN) :: psi + COMPLEX(r8), INTENT(OUT) :: dst_int + COMPLEX(r8), OPTIONAL, INTENT(OUT) :: dst_t1, dst_t2, dst_t3 +c optional per-theta K_i xi_n^2 densities for R-Z heatmaps. Same +c convention as gpeq_epfterms: integral = sum den*jac/mthsurf. + REAL(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) :: + $ dst1_den_fun, dst2_den_fun, dst3_den_fun + INTEGER :: itheta + COMPLEX(r8), DIMENSION(0:mthsurf) :: K_fun, xno_fun + REAL(r8), DIMENSION(0:mthsurf) :: K_term1, K_term2, K_term3 + REAL(r8) :: xin2_fac, xin2_loc, d1, d2, d3, jac + COMPLEX(r8) :: dst1_int, dst2_int, dst3_int + +c----------------------------------------------------------------------- +c DST(psi) = \int dtheta dzeta J * K * xi_n^2 +c----------------------------------------------------------------------- + CALL gpeq_K(psi, K_fun, K_term1, K_term2, K_term3) + CALL iscdftb(mfac, mpert, xno_fun, mthsurf, xno_mn) + dst_int = CMPLX(0.0_r8, 0.0_r8, r8) + dst1_int = CMPLX(0.0_r8, 0.0_r8, r8) + dst2_int = CMPLX(0.0_r8, 0.0_r8, r8) + dst3_int = CMPLX(0.0_r8, 0.0_r8, r8) + DO itheta = 0, mthsurf + CALL bicube_eval(rzphi, psi, theta(itheta), 0) + jac = rzphi%f(4) + xin2_loc = ABS(xno_fun(itheta))**2 + d1 = K_term1(itheta) * xin2_loc + d2 = K_term2(itheta) * xin2_loc + d3 = K_term3(itheta) * xin2_loc + IF (PRESENT(dst1_den_fun)) dst1_den_fun(itheta) = d1 + IF (PRESENT(dst2_den_fun)) dst2_den_fun(itheta) = d2 + IF (PRESENT(dst3_den_fun)) dst3_den_fun(itheta) = d3 + IF (itheta < mthsurf) THEN + xin2_fac = jac / REAL(mthsurf, r8) + dst1_int = dst1_int + CMPLX(d1 * xin2_fac, 0.0_r8, r8) + dst2_int = dst2_int + CMPLX(d2 * xin2_fac, 0.0_r8, r8) + dst3_int = dst3_int + CMPLX(d3 * xin2_fac, 0.0_r8, r8) + ENDIF + ENDDO + dst_int = dst1_int + dst2_int + dst3_int + IF (PRESENT(dst_t1)) dst_t1 = dst1_int + IF (PRESENT(dst_t2)) dst_t2 = dst2_int + IF (PRESENT(dst_t3)) dst_t3 = dst3_int +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gpeq_dst +c----------------------------------------------------------------------- +c subprogram 15. gpeq_shear. +c compute magnetic shear at a single psi level. +c evaluated on the theta grid and returned in real space. +c----------------------------------------------------------------------- + SUBROUTINE gpeq_shear(psi, shear_fun) + REAL(r8), INTENT(IN) :: psi + COMPLEX(r8), DIMENSION(0:mthsurf), INTENT(OUT) :: shear_fun + + INTEGER :: itheta + REAL(r8) :: r_val, theta_val, q, q1, chi1, jac, rfac, eta + REAL(r8) :: dpdp, dpdt, dpdz, shear_deriv + REAL(r8), DIMENSION(3,3) :: w, v + REAL(r8) :: shear_contra + TYPE(spline_type) :: shear_temp + +c----------------------------------------------------------------------- +c Setup: get equilibrium at this psi. +c----------------------------------------------------------------------- + CALL spline_eval(sq, psi, 1) + q = sq%f(4) + q1 = sq%f1(4) + chi1 = psio * twopi + +c----------------------------------------------------------------------- +c Allocate spline for shear_temp with 5 components: +c 1: covariant-form numerator (kept for reference, unused below) +c 2: (q*dpdt - dpdz) numerator +c 3: g_psi_g_psi denominator +c 4: covariant-form denominator (kept for reference, unused below) +c 5: DCON shear geometry term (component 2 / component 3) +c----------------------------------------------------------------------- + CALL spline_alloc(shear_temp, mthsurf, 5) + shear_temp%xs = theta(0:mthsurf) + shear_temp%name = "shear_" + +c----------------------------------------------------------------------- +c Step 1: Compute shear components at all theta points. +c----------------------------------------------------------------------- + DO itheta = 0, mthsurf + theta_val = theta(itheta) + CALL bicube_eval(rzphi, psi, theta(itheta), 1) + + jac = rzphi%f(4) + rfac = MAX(SQRT(MAX(rzphi%f(1), 0.0_r8)), recon_tiny) + eta = twopi*(theta_val + rzphi%f(2)) + r_val = ro + rfac*COS(eta) + + +c Compute w_ij contravariant metric in Cartesian components + w(1,1) = (1.0_r8 + rzphi%fy(2))*twopi**2*rfac*r_val/jac + w(1,2) = -rzphi%fy(1)*pi*r_val/(rfac*jac) + + w(2,1) = -twopi**2*rfac*r_val*rzphi%fx(2)/jac + w(2,2) = pi*r_val*rzphi%fx(1)/(rfac*jac) + + w(3,1) = (twopi*r_val*rfac/jac)* + $ (rzphi%fx(2)*rzphi%fy(3) - rzphi%fx(3)*(1.0_r8 + + $ rzphi%fy(2))) + w(3,2) = (r_val/(2.0_r8*rfac*jac))* + $ (rzphi%fx(3)*rzphi%fy(1) - rzphi%fx(1)*rzphi%fy(3)) + +c Compute covariant basis v_ij = dr/dxi_j (Cartesian components) + v(1,1) = rzphi%fx(1)/(2.0_r8*rfac*jac) + v(1,2) = rzphi%fx(2)*twopi*rfac/jac + v(1,3) = rzphi%fx(3)*r_val/jac + + v(2,1) = rzphi%fy(1)/(2.0_r8*rfac*jac) + v(2,2) = (1.0_r8 + rzphi%fy(2))*twopi*rfac/jac + v(2,3) = rzphi%fy(3)*r_val/jac + +c Component 33 of covariant basis + v(3,3) = twopi*r_val/jac + +c Contravariant dot products: dpdp = grad psi.grad psi, etc. + dpdp = w(1,1)**2 + w(1,2)**2 + dpdt = w(1,1)*w(2,1) + w(1,2)*w(2,2) + dpdz = w(1,1)*w(3,1) + w(1,2)*w(3,2) + + shear_temp%fs(itheta, 1) = twopi*r_val*jac* + $ (-(v(1,1)*v(2,1)+v(1,2)*v(2,2))*(v(2,3)+q*v(3,3)) + $ +v(1,3)*(v(2,1)**2+v(2,2)**2)) + shear_temp%fs(itheta, 2) = (q*dpdt - dpdz) + shear_temp%fs(itheta, 3) = dpdp + shear_temp%fs(itheta, 4) = + $ twopi**2 * r_val**2 * (v(2,1)**2 + v(2,2)**2) + + + shear_temp%fs(itheta, 5) = shear_temp%fs(itheta, 2) / + $ MAX(shear_temp%fs(itheta, 3), recon_tiny) + + ENDDO +c----------------------------------------------------------------------- +c Step 2: Fit spline to compute derivatives. +c----------------------------------------------------------------------- + CALL spline_fit(shear_temp, "periodic") +c----------------------------------------------------------------------- +c Step 3: Compute shear_fun = (chi1^2/J)*(q' + d shear_temp/d theta) +c with shear_temp = (q*g^psi_theta - g^psi_zeta)/g^psi_psi. +c----------------------------------------------------------------------- + DO itheta = 0, mthsurf + theta_val = theta(itheta) + + CALL spline_eval(shear_temp, theta_val, 1) + CALL bicube_eval(rzphi, psi, theta_val, 1) + jac = rzphi%f(4) + +c Contravariant approach: derivative of component 5 + shear_deriv = shear_temp%f1(5) + shear_contra = (chi1**2/jac)*(q1 + shear_deriv) + shear_fun(itheta) = CMPLX(shear_contra, 0.0_r8, r8) + + ENDDO + +c----------------------------------------------------------------------- +c Step 4: Release the theta spline. +c----------------------------------------------------------------------- + CALL spline_dealloc(shear_temp) +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gpeq_shear +c----------------------------------------------------------------------- +c subprogram 16. gpeq_curvature. +c compute curvature in spatial domain. +c kappa.grad psi = (|grad psi|^2/B^2)[mu0 p' + (1/2) dB^2/dpsi +c + (1/2)(dB^2/dtheta)(grad psi.grad theta)/|grad psi|^2] +c----------------------------------------------------------------------- + SUBROUTINE gpeq_curvature(psi, curv_fun) + REAL(r8), INTENT(IN) :: psi + COMPLEX(r8), DIMENSION(0:mthsurf), INTENT(OUT) :: curv_fun + + INTEGER :: itheta + REAL(r8) :: r_val, theta_val, p1, jac, rfac, eta + REAL(r8) :: delpsi + REAL(r8), DIMENSION(3,3) :: w + REAL(r8) :: bsq_val, bsq_psi, bsq_theta + REAL(r8) :: dpdt , kappa_psi + +c----------------------------------------------------------------------- +c Setup: get equilibrium at this psi. +c----------------------------------------------------------------------- + CALL spline_eval(sq, psi, 1) + p1 = sq%f1(2) / mu0 + +c----------------------------------------------------------------------- +c Compute curvature at all theta points. +c----------------------------------------------------------------------- + DO itheta = 0, mthsurf + theta_val = theta(itheta) + CALL bicube_eval(rzphi, psi, theta_val, 1) + + jac = rzphi%f(4) + rfac = MAX(SQRT(MAX(rzphi%f(1), 0.0_r8)), recon_tiny) + eta = twopi*(theta_val + rzphi%f(2)) + r_val = ro + rfac*COS(eta) + +c Compute contravariant metrics + w(1,1) = (1.0_r8+ rzphi%fy(2))*(twopi**2)*rfac*r_val/jac + w(1,2) = -rzphi%fy(1)*pi*r_val/(rfac*jac) + + w(2,1) = -(twopi**2)*rfac*r_val*rzphi%fx(2)/jac + w(2,2) = pi*r_val*rzphi%fx(1)/(rfac*jac) + + w(3,1) = (twopi*r_val*rfac/jac)* + $ (rzphi%fx(2)*rzphi%fy(3) - rzphi%fx(3)* + $ (1.0_r8 + rzphi%fy(2))) + w(3,2) = (r_val/(2.0_r8*rfac*jac))* + $ (rzphi%fx(3)*rzphi%fy(1) - rzphi%fx(1)*rzphi%fy(3)) + +c Compute |grad psi|^2 and grad psi.grad theta + delpsi = SQRT(w(1,1)**2 + w(1,2)**2) + dpdt = w(1,1)*w(2,1) + w(1,2)*w(2,2) + + CALL bicube_eval(eqfun, psi, theta_val, 1) +c Retrieve B^2 and derivatives + bsq_val = MAX(eqfun%f(1)**2, recon_tiny) + bsq_psi = eqfun%fx(1) * 2 * eqfun%f(1) + bsq_theta = eqfun%fy(1) * 2 * eqfun%f(1) + +c Compute kappa.grad psi + + kappa_psi = (delpsi**2 / bsq_val) * + $ (p1*mu0 + 0.5_r8*bsq_psi + + $ 0.5_r8*bsq_theta*dpdt/MAX(delpsi**2, recon_tiny)) + + curv_fun(itheta) = CMPLX(kappa_psi, 0.0_r8, r8) + + ENDDO +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gpeq_curvature +c----------------------------------------------------------------------- +c subprogram 17. gpeq_K. +c compute Bernstein K quantity (stability indicator). +c K = |grad psi_dcon|^2 * sigma * S_dcon +c + mu0 * B^2 * sigma^2 + 2 p' * kappa^psi +c Uses pre-computed metric from idcon_metric +c----------------------------------------------------------------------- + SUBROUTINE gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, + $ sigma_fun, jdotb_fun, shear_out, curv_out) + REAL(r8), INTENT(IN) :: psi + COMPLEX(r8), DIMENSION(0:mthsurf), INTENT(OUT) :: K_fun + REAL(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) :: + $ K_term1, K_term2, K_term3, sigma_fun, jdotb_fun + COMPLEX(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) :: + $ shear_out, curv_out + + INTEGER :: itheta + REAL(r8) :: f1raw, delpsi, chi1, p1, q + REAL(r8) :: jwt, jwz, bth, bze, bsq_val, sigma, jdotb_val + REAL(r8) :: g22, g23, g33, r_val, jac, rfac, eta + REAL(r8), DIMENSION(3,3) :: w, v + REAL(r8), DIMENSION(0:mthsurf) :: K_t1, K_t2, K_t3 + REAL(r8), DIMENSION(0:mthsurf) :: sigma_vals, jdotb_vals + COMPLEX(r8), DIMENSION(0:mthsurf) :: shear_fun, curv_fun + + chi1 = psio * twopi + + IF(debug_flag) PRINT *, "Entering gpeq_K" +c----------------------------------------------------------------------- +c load equilibrium parameters +c----------------------------------------------------------------------- + CALL spline_eval(sq, psi, 1) + f1raw = sq%f1(1) + p1 = sq%f1(2) / mu0 + q = sq%f(4) +c----------------------------------------------------------------------- +c compute shear and curvature +c----------------------------------------------------------------------- + CALL gpeq_shear(psi, shear_fun) + CALL gpeq_curvature(psi, curv_fun) +c----------------------------------------------------------------------- +c compute K in spatial domain (idcon_metric convention) +c----------------------------------------------------------------------- + DO itheta = 0, mthsurf + CALL bicube_eval(rzphi, psi, theta(itheta), 1) + jac = rzphi%f(4) + rfac = MAX(SQRT(MAX(rzphi%f(1), 0.0_r8)), recon_tiny) + eta = twopi*(theta(itheta) + rzphi%f(2)) + r_val = ro + rfac*COS(eta) + +c |grad psi| magnitude + w(1,1)=(1+rzphi%fy(2))*twopi**2*rfac*r_val/jac + w(1,2)=-rzphi%fy(1)*pi*r_val/(rfac*jac) + delpsi=SQRT(w(1,1)**2+w(1,2)**2) + +c current and magnetic field components +c note: -f1raw/jac is the mu0*j^theta component (DCON convention), +c so divide by mu0 to get the physical current j for K = mu0*sigma^2*B^2. + jwt = -f1raw/(jac*mu0) + jwz = q*jwt - p1/chi1 + bth = chi1 / jac + bze = q * chi1 / jac + +c contravariant basis vectors (idcon_metric style - NO jac) + v(2,1) = rzphi%fy(1)/(2*rfac) + v(2,2) = (1+rzphi%fy(2))*twopi*rfac + v(2,3) = rzphi%fy(3)*r_val + + v(3,3) = twopi*r_val + +c metric tensor: g_ij = sum(v_i * v_j) (idcon_metric convention) + g22 = (v(2,1)**2 + v(2,2)**2 + v(2,3)**2) + g33 = (v(3,3)**2) + g23 = (v(2,3)*v(3,3)) + +c j.B and sigma = (j.B)/B^2 + CALL bicube_eval(eqfun, psi, theta(itheta), 0) + bsq_val = MAX(eqfun%f(1)**2, recon_tiny) + + jdotb_val = g22*bth*jwt + g33*bze*jwz + + $ g23*(bth*jwz + bze*jwt) + sigma = jdotb_val / bsq_val + sigma_vals(itheta) = sigma + jdotb_vals(itheta) = jdotb_val + +c Term1: |grad psi_dcon|^2 * sigma * S_dcon + K_t1(itheta) = (delpsi**2) * sigma * REAL(shear_fun(itheta)) + +c Term2: mu0 * B^2 * sigma^2 + K_t2(itheta) = bsq_val * sigma**2 * mu0 + +c Term3: 2 p' * kappa^psi + K_t3(itheta) = 2.0_r8 * REAL(curv_fun(itheta)) * p1 + ENDDO + +c total K = T1 + T2 + T3 + DO itheta = 0, mthsurf + K_fun(itheta) = CMPLX(K_t1(itheta) + K_t2(itheta) + + $ K_t3(itheta), 0.0_r8, r8) + ENDDO + +c optionally return individual term values + IF (PRESENT(K_term1)) K_term1 = K_t1 + IF (PRESENT(K_term2)) K_term2 = K_t2 + IF (PRESENT(K_term3)) K_term3 = K_t3 + IF (PRESENT(sigma_fun)) sigma_fun = sigma_vals + IF (PRESENT(jdotb_fun)) jdotb_fun = jdotb_vals + IF (PRESENT(shear_out)) shear_out = shear_fun + IF (PRESENT(curv_out)) curv_out = curv_fun + + IF(debug_flag) PRINT *, "->Leaving gpeq_K" +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gpeq_K +c----------------------------------------------------------------------- +c subprogram 18. gpeq_epfterms. +c split the epf surface integral of gpeq_epf into terms of +c comparable magnitude, using C = Q + V, V = xi_n mu0 j x n_hat: +c A = int |Q|^2 / mu0 B = int 2 Re(V*.Q) / mu0 +c Cpar = int mu0 sigma^2 B^2 xin^2 (= gpeq_dst's dst2) +c Iperp = int mu0 p'^2 |grad psi|^2 xin^2 / B^2 +c each on the jac measure, as int (..) J dtheta / mthsurf. epf +c uses the Fourier-truncated C while the four terms are built +c pointwise, so epf - (A + B + Cpar + Iperp) measures the +c truncation error and shrinks as harmonics are added. +c Assumes gpeq_prep_c(psi) has been called for this psi. +c----------------------------------------------------------------------- + SUBROUTINE gpeq_epfterms(psi, A_int, B_int, Iperp_int, Cpar_int, + $ epf_int, A_fun, B_fun, Iperp_fun, Cpar_fun, surf_extra) + REAL(r8), INTENT(IN) :: psi + REAL(r8), INTENT(OUT) :: A_int, B_int, Iperp_int, Cpar_int, + $ epf_int + REAL(r8), DIMENSION(0:mthsurf), OPTIONAL, INTENT(OUT) :: + $ A_fun, B_fun, Iperp_fun, Cpar_fun +c surf_extra: packed per-theta fields for R-Z heatmaps (recon_out). +c columns: 1 VdotQ_re, 2 VdotQ_im, 3 Bcur_den, 4 Bpre_den, +c 5/6 Qp_re/im, 7/8 Qt_re/im, 9/10 Qz_re/im, +c 11/12 Vt_re/im, 13/14 Vz_re/im, 15 jpar, 16 jperp, +c 17/18 xin_re/im, 19 delpsi, 20 Bmod, +c 21 dst1_den (K1 xin2), 22 dst3_den (K3 xin2). + REAL(r8), DIMENSION(0:mthsurf,22), OPTIONAL, INTENT(OUT) :: + $ surf_extra + + INTEGER :: itheta + REAL(r8) :: f1raw, p1_local, chi1 + REAL(r8) :: A_density, B_density, Iperp_density, Cpar_density, + $ epf_density + REAL(r8) :: delpsi_val, dpsq, bsq_val, sigma_local, jdotb_val + REAL(r8) :: jwt, jwz, bth, bze, g22, g33, g23, r_val + REAL(r8) :: jac, rfac, eta + REAL(r8), DIMENSION(3,3) :: w, v + REAL(r8) :: xin2, mu0jwt, mu0jwz + REAL(r8) :: mu0jpar_t, mu0jpar_z, mu0jper_t, mu0jper_z + REAL(r8) :: jsq_val, jpar_val, bmod_val + COMPLEX(r8) :: jvt_loc, jvz_loc, vdotq_loc + COMPLEX(r8) :: jvt_cur, jvz_cur, jvt_pre, jvz_pre + COMPLEX(r8), DIMENSION(0:mthsurf) :: kfun_loc + REAL(r8), DIMENSION(0:mthsurf) :: kt1_loc, kt2_loc, kt3_loc + COMPLEX(r8), DIMENSION(0:mthsurf) :: bwp_fun, bmt_fun, bmz_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: bvp_fun, bvt_fun, bvz_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: cwp_fun, cwt_fun, cwz_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: cvp_fun, cvt_fun, cvz_fun + COMPLEX(r8), DIMENSION(0:mthsurf) :: xno_fun, xwp_fun + + IF(debug_flag) PRINT *, "Entering gpeq_epfterms" +c----------------------------------------------------------------------- +c reconstruct the C, Q state prepared by gpeq_prep_c. +c----------------------------------------------------------------------- + CALL spline_eval(sq, psi, 1) + f1raw = sq%f1(1) + p1_local = sq%f1(2) / mu0 + chi1 = psio * twopi + + CALL iscdftb(mfac, mpert, xno_fun, mthsurf, xno_mn) + CALL iscdftb(mfac, mpert, bwp_fun, mthsurf, bwp_mn) + CALL iscdftb(mfac, mpert, bmt_fun, mthsurf, bmt_mn) + CALL iscdftb(mfac, mpert, bmz_fun, mthsurf, bmz_mn) + CALL iscdftb(mfac, mpert, bvp_fun, mthsurf, bvp_mn) + CALL iscdftb(mfac, mpert, bvt_fun, mthsurf, bvt_mn) + CALL iscdftb(mfac, mpert, bvz_fun, mthsurf, bvz_mn) + CALL iscdftb(mfac, mpert, cwp_fun, mthsurf, cwp_mn) + CALL iscdftb(mfac, mpert, cwt_fun, mthsurf, cwt_mn) + CALL iscdftb(mfac, mpert, cwz_fun, mthsurf, cwz_mn) + CALL iscdftb(mfac, mpert, cvp_fun, mthsurf, cvp_mn) + CALL iscdftb(mfac, mpert, cvt_fun, mthsurf, cvt_mn) + CALL iscdftb(mfac, mpert, cvz_fun, mthsurf, cvz_mn) + CALL iscdftb(mfac, mpert, xwp_fun, mthsurf, xwp_mn) + +c K term breakdown (K1 shear, K3 curvature) for dst1/dst3 densities; +c only needed for the R-Z heatmap output. + IF (PRESENT(surf_extra)) THEN + CALL gpeq_K(psi, kfun_loc, kt1_loc, kt2_loc, kt3_loc) + ENDIF + + A_int = 0.0_r8 + B_int = 0.0_r8 + Iperp_int = 0.0_r8 + Cpar_int = 0.0_r8 + epf_int = 0.0_r8 +c----------------------------------------------------------------------- +c accumulate densities in theta with jac measure. +c----------------------------------------------------------------------- + DO itheta = 0, mthsurf + CALL bicube_eval(rzphi, psi, theta(itheta), 1) + CALL bicube_eval(eqfun, psi, theta(itheta), 0) + jac = rzphi%f(4) + rfac = MAX(SQRT(MAX(rzphi%f(1), 0.0_r8)), recon_tiny) + eta = twopi*(theta(itheta) + rzphi%f(2)) + r_val = ro + rfac*COS(eta) + bsq_val = MAX(eqfun%f(1)**2, recon_tiny) + +c |grad psi| + w(1,1)=(1+rzphi%fy(2))*twopi**2*rfac*r_val/jac + w(1,2)=-rzphi%fy(1)*pi*r_val/(rfac*jac) + delpsi_val=SQRT(w(1,1)**2+w(1,2)**2) + dpsq = MAX(delpsi_val**2, recon_tiny) + +c currents and B (idcon_metric convention, same as gpeq_K). + jwt = -f1raw/(jac*mu0) + jwz = sq%f(4)*jwt - p1_local/chi1 + bth = chi1 / jac + bze = sq%f(4) * chi1 / jac + + v(2,1) = rzphi%fy(1)/(2*rfac) + v(2,2) = (1+rzphi%fy(2))*twopi*rfac + v(2,3) = rzphi%fy(3)*r_val + v(3,3) = twopi*r_val + g22 = (v(2,1)**2 + v(2,2)**2 + v(2,3)**2) + g33 = (v(3,3)**2) + g23 = (v(2,3)*v(3,3)) + + jdotb_val = g22*bth*jwt + g33*bze*jwz + + $ g23*(bth*jwz + bze*jwt) + sigma_local = jdotb_val / bsq_val + + xin2 = REAL(CONJG(xno_fun(itheta))*xno_fun(itheta), r8) + +c |Q|^2 / mu0 density (matches gpeq_firstform q2_density) + A_density = REAL(CONJG(bwp_fun(itheta)) * bvp_fun(itheta) + + $ CONJG(bmt_fun(itheta)) * bvt_fun(itheta) + + $ CONJG(bmz_fun(itheta)) * bvz_fun(itheta), r8) + $ / (mu0 * jac) + +c |C|^2 / mu0 density (matches gpeq_epf integrand) + epf_density = REAL( + $ CONJG(cwp_fun(itheta)) * cvp_fun(itheta) + + $ CONJG(cwt_fun(itheta)) * cvt_fun(itheta) + + $ CONJG(cwz_fun(itheta)) * cvz_fun(itheta), r8) + $ / (mu0 * jac) + +c B density = 2 Re(V*.Q) / mu0, with V = xi_n (mu0 j x n_hat) +c computed DIRECTLY from the equilibrium current, NOT from C - Q. +c These are the same C^i - Q^i terms derived in main.tex (Sec. 5, +c the C component box): J V^psi = 0, and with xwp_fun = J xi^psi, +c J V^theta = xwp/(|grad psi|^2 J) (mu0 j^theta g23 + mu0 j^zeta g33) +c J V^zeta = -xwp/(|grad psi|^2 J) (mu0 j^theta g22 + mu0 j^zeta g23). +c mu0 j^i = mu0 * (physical j^i); jwt,jwz already hold physical j^i. + mu0jwt = mu0 * jwt + mu0jwz = mu0 * jwz + jvt_loc = xwp_fun(itheta) / (dpsq * jac) * + $ (mu0jwt * g23 + mu0jwz * g33) + jvz_loc = -xwp_fun(itheta) / (dpsq * jac) * + $ (mu0jwt * g22 + mu0jwz * g23) +c V is contravariant (J V^i); pair with covariant Q_i (bvt,bvz). + B_density = 2.0_r8 * REAL( + $ CONJG(jvt_loc) * bvt_fun(itheta) + + $ CONJG(jvz_loc) * bvz_fun(itheta), r8) / (mu0 * jac) + +c K_2 xi_n^2 density (parallel current; should match dst2) + Cpar_density = mu0 * sigma_local**2 * bsq_val * xin2 + +c I_perp density (perpendicular Pfirsch-Schlueter current^2) + Iperp_density = mu0 * p1_local**2 * delpsi_val**2 * + $ xin2 / bsq_val + +c Extra per-theta fields for R-Z heatmaps (only if requested). + IF (PRESENT(surf_extra)) THEN + bmod_val = SQRT(bsq_val) +c V*.Q (complex); B_density = 2 Re(vdotq_loc)/mu0. + vdotq_loc = (CONJG(jvt_loc) * bvt_fun(itheta) + + $ CONJG(jvz_loc) * bvz_fun(itheta)) / jac +c Split V = V_par + V_perp via j = j_par + j_perp. +c Parallel current: mu0 j_par^i = mu0 sigma B^i (B^t=bth,B^z=bze). + mu0jpar_t = mu0 * sigma_local * bth + mu0jpar_z = mu0 * sigma_local * bze + mu0jper_t = mu0jwt - mu0jpar_t + mu0jper_z = mu0jwz - mu0jpar_z + jvt_cur = xwp_fun(itheta) / (dpsq * jac) * + $ (mu0jpar_t * g23 + mu0jpar_z * g33) + jvz_cur = -xwp_fun(itheta) / (dpsq * jac) * + $ (mu0jpar_t * g22 + mu0jpar_z * g23) + jvt_pre = jvt_loc - jvt_cur + jvz_pre = jvz_loc - jvz_cur +c j_par = sigma B; |j_perp|^2 = |j|^2 - j_par^2 (j physical). + jpar_val = sigma_local * bmod_val + jsq_val = g22 * jwt**2 + g33 * jwz**2 + + $ 2.0_r8 * g23 * jwt * jwz + surf_extra(itheta,1) = REAL(vdotq_loc, r8) + surf_extra(itheta,2) = AIMAG(vdotq_loc) + surf_extra(itheta,3) = 2.0_r8 * REAL( + $ CONJG(jvt_cur) * bvt_fun(itheta) + + $ CONJG(jvz_cur) * bvz_fun(itheta), r8) / (mu0 * jac) + surf_extra(itheta,4) = 2.0_r8 * REAL( + $ CONJG(jvt_pre) * bvt_fun(itheta) + + $ CONJG(jvz_pre) * bvz_fun(itheta), r8) / (mu0 * jac) + surf_extra(itheta,5) = REAL(bvp_fun(itheta), r8) + surf_extra(itheta,6) = AIMAG(bvp_fun(itheta)) + surf_extra(itheta,7) = REAL(bvt_fun(itheta), r8) + surf_extra(itheta,8) = AIMAG(bvt_fun(itheta)) + surf_extra(itheta,9) = REAL(bvz_fun(itheta), r8) + surf_extra(itheta,10) = AIMAG(bvz_fun(itheta)) + surf_extra(itheta,11) = REAL(jvt_loc, r8) + surf_extra(itheta,12) = AIMAG(jvt_loc) + surf_extra(itheta,13) = REAL(jvz_loc, r8) + surf_extra(itheta,14) = AIMAG(jvz_loc) + surf_extra(itheta,15) = jpar_val + surf_extra(itheta,16) = SQRT(MAX(0.0_r8, + $ jsq_val - jpar_val**2)) + surf_extra(itheta,17) = REAL(xno_fun(itheta), r8) + surf_extra(itheta,18) = AIMAG(xno_fun(itheta)) + surf_extra(itheta,19) = delpsi_val + surf_extra(itheta,20) = bmod_val +c dst1 = K1 xin2 (shear), dst3 = K3 xin2 (curvature). + surf_extra(itheta,21) = kt1_loc(itheta) * xin2 + surf_extra(itheta,22) = kt3_loc(itheta) * xin2 + ENDIF + + IF (PRESENT(A_fun)) A_fun(itheta) = A_density + IF (PRESENT(B_fun)) B_fun(itheta) = B_density + IF (PRESENT(Iperp_fun)) Iperp_fun(itheta) = Iperp_density + IF (PRESENT(Cpar_fun)) Cpar_fun(itheta) = Cpar_density + + IF (itheta < mthsurf) THEN + A_int = A_int + A_density * jac / REAL(mthsurf, r8) + B_int = B_int + B_density * jac / REAL(mthsurf, r8) + epf_int = epf_int + epf_density * jac / REAL(mthsurf, r8) + Cpar_int = Cpar_int + Cpar_density * jac / + $ REAL(mthsurf, r8) + Iperp_int = Iperp_int + Iperp_density * jac / + $ REAL(mthsurf, r8) + ENDIF + ENDDO + + IF(debug_flag) PRINT *, "->Leaving gpeq_epfterms" +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gpeq_epfterms +c----------------------------------------------------------------------- +c subprogram 19. gpeq_fcoords. c transform coordinates to dcon coordinates. c----------------------------------------------------------------------- SUBROUTINE gpeq_fcoords(psi,ftnmn,amf,amp,ri,bpi,bi,rci,ti,ji) @@ -779,7 +1903,7 @@ SUBROUTINE gpeq_fcoords(psi,ftnmn,amf,amp,ri,bpi,bi,rci,ti,ji) RETURN END SUBROUTINE gpeq_fcoords c----------------------------------------------------------------------- -c subprogram 10. gpeq_fcoordsout. +c subprogram 20. gpeq_fcoordsout. c transform to dcon coordinates. Assumes mpert,lmpert,jac_out c----------------------------------------------------------------------- SUBROUTINE gpeq_fcoordsout(fmo,fmi,psi,ti,ji) @@ -842,7 +1966,7 @@ SUBROUTINE gpeq_fcoordsout(fmo,fmi,psi,ti,ji) RETURN END SUBROUTINE gpeq_fcoordsout c----------------------------------------------------------------------- -c subprogram 11. gpeq_bcoords. +c subprogram 21. gpeq_bcoords. c transform dcon coordinates to other coordinates. c----------------------------------------------------------------------- SUBROUTINE gpeq_bcoords(psi,ftnmn,amf,amp,ri,bpi,bi,rci,ti,ji) @@ -981,7 +2105,7 @@ SUBROUTINE gpeq_bcoords(psi,ftnmn,amf,amp,ri,bpi,bi,rci,ti,ji) RETURN END SUBROUTINE gpeq_bcoords c----------------------------------------------------------------------- -c subprogram 12. gpeq_bcoordsout. +c subprogram 22. gpeq_bcoordsout. c transform dcon to other coordinates. Assumes mpert,lmpert,jac_out c----------------------------------------------------------------------- SUBROUTINE gpeq_bcoordsout(fmo,fmi,psi,ti,ji) @@ -1044,7 +2168,7 @@ SUBROUTINE gpeq_bcoordsout(fmo,fmi,psi,ti,ji) RETURN END SUBROUTINE gpeq_bcoordsout c----------------------------------------------------------------------- -c subprogram 13. gpeq_weight. +c subprogram 23. gpeq_weight. c switch between a function and a weighted function. c----------------------------------------------------------------------- SUBROUTINE gpeq_weight(psi,ftnmn,amf,amp,wegt) @@ -1115,7 +2239,7 @@ SUBROUTINE gpeq_weight(psi,ftnmn,amf,amp,wegt) RETURN END SUBROUTINE gpeq_weight c----------------------------------------------------------------------- -c subprogram 14. gpeq_rzpgrid. +c subprogram 24. gpeq_rzpgrid. c find magnetic coordinates for given rz coords. c----------------------------------------------------------------------- SUBROUTINE gpeq_rzpgrid(nr,nz,psixy) @@ -1218,7 +2342,7 @@ SUBROUTINE gpeq_rzpgrid(nr,nz,psixy) RETURN END SUBROUTINE gpeq_rzpgrid c----------------------------------------------------------------------- -c subprogram 15. gpeq_rzpdiv. +c subprogram 25. gpeq_rzpdiv. c make zero divergence of rzphi functions. c----------------------------------------------------------------------- SUBROUTINE gpeq_rzpdiv(nr,nz,rval,zval,fr,fz,fp) @@ -1284,7 +2408,7 @@ SUBROUTINE gpeq_rzpdiv(nr,nz,rval,zval,fr,fz,fp) RETURN END SUBROUTINE gpeq_rzpdiv c----------------------------------------------------------------------- -c subprogram 16. gpeq_alloc. +c subprogram 26. gpeq_alloc. c allocate essential vectors in fourier space c----------------------------------------------------------------------- SUBROUTINE gpeq_alloc @@ -1299,7 +2423,10 @@ SUBROUTINE gpeq_alloc $ xno_mn(mpert),xta_mn(mpert),xpa_mn(mpert), $ bno_mn(mpert),bta_mn(mpert),bpa_mn(mpert), $ xrr_mn(mpert),xrz_mn(mpert),xrp_mn(mpert), - $ brr_mn(mpert),brz_mn(mpert),brp_mn(mpert)) + $ brr_mn(mpert),brz_mn(mpert),brp_mn(mpert), + $ c2vp_mn(mpert),c2vt_mn(mpert),c2vz_mn(mpert), + $ cvp_mn(mpert),cvt_mn(mpert),cvz_mn(mpert), + $ cwp_mn(mpert),cwt_mn(mpert),cwz_mn(mpert)) IF(debug_flag) PRINT *, "->Leaving gpeq_alloc" c----------------------------------------------------------------------- c terminate. @@ -1307,7 +2434,7 @@ SUBROUTINE gpeq_alloc RETURN END SUBROUTINE gpeq_alloc c----------------------------------------------------------------------- -c subprogram 17. gpeq_dealloc. +c subprogram 27. gpeq_dealloc. c deallocate essential vectors in fourier space c----------------------------------------------------------------------- SUBROUTINE gpeq_dealloc @@ -1317,7 +2444,9 @@ SUBROUTINE gpeq_dealloc $ xwp_mn,xwt_mn,xwz_mn,bwp_mn,bwt_mn,bwz_mn,xmt_mn,bmt_mn, $ xvp_mn,xvt_mn,xvz_mn,bvp_mn,bvt_mn,bvz_mn,xmz_mn,bmz_mn, $ xno_mn,xta_mn,xpa_mn,bno_mn,bta_mn,bpa_mn, - $ xrr_mn,xrz_mn,xrp_mn,brr_mn,brz_mn,brp_mn) + $ xrr_mn,xrz_mn,xrp_mn,brr_mn,brz_mn,brp_mn, + $ c2vp_mn,c2vt_mn,c2vz_mn,cvp_mn,cvt_mn,cvz_mn, + $ cwp_mn,cwt_mn,cwz_mn) IF(debug_flag) PRINT *, "->Leaving gpeq_dealloc" c----------------------------------------------------------------------- c terminate. @@ -1325,7 +2454,7 @@ SUBROUTINE gpeq_dealloc RETURN END SUBROUTINE gpeq_dealloc c----------------------------------------------------------------------- -c subprogram 18. gpeq_interp_singsurf. +c subprogram 28. gpeq_interp_singsurf. c create spline for interpretation of solution near singular surface. c----------------------------------------------------------------------- SUBROUTINE gpeq_interp_singsurf(fsp_sol,spots,npsi) @@ -1409,7 +2538,7 @@ SUBROUTINE gpeq_interp_singsurf(fsp_sol,spots,npsi) RETURN END SUBROUTINE gpeq_interp_singsurf c----------------------------------------------------------------------- -c subprogram 19. gpeq_interp_sol. +c subprogram 29. gpeq_interp_sol. c get bwn at psi after calling gpeq_interp_singsurf. c----------------------------------------------------------------------- SUBROUTINE gpeq_interp_sol(fsp_sol,psi,interpbwn) diff --git a/gpec/gpglobal.F b/gpec/gpglobal.F index ce75712dd..bc9732e4f 100644 --- a/gpec/gpglobal.F +++ b/gpec/gpglobal.F @@ -18,7 +18,9 @@ MODULE gpglobal_mod $ fun_flag,flux_flag,vsbrzphi_flag,displacement_flag, $ chebyshev_flag,coil_flag,eigm_flag,bwp_pest_flag,verbose, $ debug_flag,timeit,kin_flag,con_flag,resp_induct_flag, - $ netcdf_flag,ascii_flag,wv_farwall_flag,mutual_test_flag + $ netcdf_flag,ascii_flag,wv_farwall_flag,mutual_test_flag, + $ curl_check_flag,recon_out,standard_form_flag, + $ effective_field_terms_flag INTEGER :: mr,mz,mpsi,mstep,mpert,mband,mtheta,mthvac,mthsurf, $ mfix,mhigh,mlow,msing,nfm2,nths,nths2,lmpert,lmlow,lmhigh, @@ -38,8 +40,9 @@ MODULE gpglobal_mod CHARACTER(2) :: sn,ss CHARACTER(10) :: date,time,zone - CHARACTER(16) :: jac_type,jac_in,jac_out,data_type - CHARACTER(128) :: ieqfile,idconfile,ivacuumfile,rdconfile,dcon_dir + CHARACTER(16) :: jac_type,jac_in,jac_out,data_type,recon_int + CHARACTER(128) :: ieqfile,idconfile,ivacuumfile,rdconfile + CHARACTER(128) :: dcon_dir INTEGER, PARAMETER :: hmnum=128 REAL(r8), PARAMETER :: gauss=0.0001 @@ -56,8 +59,8 @@ MODULE gpglobal_mod REAL(r8), DIMENSION(:), ALLOCATABLE :: psifac,rhofac,qfac,singfac, $ r,z,theta,ee,surfee,surfei,rpsifac, - $ surf_indev,vsurf_indev,fsurf_indev,surf_indinvev - $ ,eft,efp + $ surf_indev,vsurf_indev,fsurf_indev,surf_indinvev, + $ eft,efp REAL(r8), DIMENSION(:,:), ALLOCATABLE :: xzpts, $ chperr,chpsqr,grri,grre,griw,grrw,gdr,gdz,gdpsi,gdthe,gdphi REAL(r8), DIMENSION(3,3) :: w,v @@ -72,6 +75,8 @@ MODULE gpglobal_mod $ chi_mn,che_mn,kax_mn,sbno_mn,sbno_fun,edge_mn,edge_fun, $ chy_mn,chx_mn,chw_mn,kaw_mn,mutual_indev,mutual_indinvev, $ bwp_mn_rmatch + COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: cwp_mn,cwt_mn, + $ cwz_mn, cvp_mn,cvt_mn,cvz_mn,c2vp_mn,c2vt_mn, c2vz_mn COMPLEX(r8), DIMENSION(:,:), ALLOCATABLE :: wt,wt0,chp_mn,kap_mn, $ permeabev,chimats,chemats,flxmats,kaxmats,singbno_mn, $ plas_indev,plas_indinvev,reluctev,indrelev,permeabsv, diff --git a/gpec/gpout.f b/gpec/gpout.f index 0e91c6994..bacaea13c 100644 --- a/gpec/gpout.f +++ b/gpec/gpout.f @@ -26,6 +26,9 @@ c 18. gpout_qrv c 19. gpout_init_netcdf c 20. gpout_close_netcdf +c 21. gpout_recon +c 22. gpout_recon2 +c 23. gpout_recon3 c----------------------------------------------------------------------- c subprogram 0. gpout_mod. c module declarations. @@ -35,6 +38,7 @@ c----------------------------------------------------------------------- MODULE gpout_mod USE netcdf + USE gpglobal_mod, ONLY : recon_out USE gpresp_mod USE gpvacuum_mod USE gpdiag_mod @@ -6892,4 +6896,1187 @@ SUBROUTINE gpout_close_netcdf RETURN END SUBROUTINE gpout_close_netcdf +c----------------------------------------------------------------------- +c subprogram 21. gpout_recon +c reconstruction diagnostics: shear, curvature, K, field C and +c the integrals epf, dst giving delta-W_p. +c----------------------------------------------------------------------- + SUBROUTINE gpout_recon(mode, xspmn) +c----------------------------------------------------------------------- +c declaration. +c----------------------------------------------------------------------- + INTEGER, INTENT(IN) :: mode + COMPLEX(r8), DIMENSION(:), INTENT(IN) :: xspmn + + INTEGER :: ipsi, itheta, ipert +c recon output units, assigned by OPEN(NEWUNIT=...). + INTEGER :: ushear_fun, ucurv, ucveri, uk, uk_sigma, ucw_fun, + $ ucw_mn, ucv_fun, ucv_mn, ucv2_fun, ucv2_mn, u_int, u_log, + $ u_terms, uqv_mn + REAL(r8), DIMENSION(0:mthsurf) :: epf_den_fun, epf_p_fun, + $ epf_t_fun, epf_z_fun, dst1_den_fun, dst2_den_fun, + $ dst3_den_fun + REAL(r8) :: psi, eta, rfac + REAL(r8), DIMENSION(0:mthsurf) :: rvals, zvals + REAL(r8) :: epf_int, epf_p_int, epf_t_int, epf_z_int + COMPLEX(r8) :: dst_int + COMPLEX(r8), DIMENSION(0:mthsurf) :: shear_fun, curv_fun, K_fun, + $ cveri_fun + COMPLEX(r8), DIMENSION(0:mthsurf,3) :: cw_fun, cv_fun, cv2_fun + COMPLEX(r8), DIMENSION(mpert) :: qvp_mn, qvt_mn, qvz_mn + REAL(r8), DIMENSION(0:mthsurf) :: K_term1, K_term2, K_term3 + REAL(r8), DIMENSION(0:mthsurf) :: sigma_vals, jdotb_vals + CHARACTER(8) :: smode + CHARACTER(128) :: shear_fun_file, curv_file, cveri_file + CHARACTER(128) :: k_file, k_sigma_file + CHARACTER(128) :: cw_fun_file, cw_mn_file, cv_fun_file, cv_mn_file + CHARACTER(128) :: cv2_fun_file, cv2_mn_file, int_file, terms_file + CHARACTER(128) :: qv_mn_file + REAL(r8) :: dpsi_local + INTEGER :: istep, nint_pts, iint, ep_index + REAL(r8) :: psi_prev, psi_curr, epf_prev, epf_curr, ep_selected + REAL(r8) :: psi_lo, psi_hi + REAL(r8) :: epf_p_prev, epf_t_prev, epf_z_prev + REAL(r8) :: epf_p_curr, epf_t_curr, epf_z_curr + REAL(r8) :: epf_int_total_hr, epf_p_total_hr, + $ epf_t_total_hr, epf_z_total_hr + REAL(r8) :: cveri_abs, cveri_max, cveri_rms, cveri_sumsq, + $ cveri_tol, cveri_scale_max, cveri_rel + REAL(r8), DIMENSION(0:mthsurf) :: cveri_scale + COMPLEX(r8) :: dst_prev, dst_curr, dst1_prev, dst2_prev, + $ dst3_prev, dst1_curr, dst2_curr, dst3_curr, + $ dst_int_total_k_hr, dst1_total_hr, dst2_total_hr, + $ dst3_total_hr, dw_raw_k_hr, dw_dcon_k_hr, dw_cum + REAL(r8), DIMENSION(:), ALLOCATABLE :: psi_int_pts, epf_vals, + $ epf_p_vals, epf_t_vals, epf_z_vals + COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: dst_vals, dst1_vals, + $ dst2_vals, dst3_vals + TYPE(cspline_type) :: recon_epf_spl, recon_dst_spl + INTEGER :: cveri_count + CHARACTER(4) :: cveri_stat + + IF(verbose) WRITE(*,*)"" + IF(verbose) WRITE(*,*)"GPOUT_RECON: Starting reconstruction"// + $ " diagnostics" + IF(verbose) WRITE(*,*)"__________________________________________" + + WRITE(smode,'(I8)') mode + smode = ADJUSTL(smode) + shear_fun_file = "gpec_recon_shear_fun_sol"//TRIM(smode)//".out" + curv_file = "gpec_recon_curvature_sol"//TRIM(smode)//".out" + cveri_file = "gpec_recon_cveri_sol"//TRIM(smode)//".out" + k_file = "gpec_recon_k_sol"//TRIM(smode)//".out" + k_sigma_file = "gpec_recon_k_sigma_check_sol"//TRIM(smode)//".out" + cw_fun_file = "gpec_recon_cwfun_sol"//TRIM(smode)//".out" + cw_mn_file = "gpec_recon_cwmn_sol"//TRIM(smode)//".out" + cv_fun_file = "gpec_recon_cvfun_sol"//TRIM(smode)//".out" + cv_mn_file = "gpec_recon_cvmn_sol"//TRIM(smode)//".out" + cv2_fun_file = "gpec_recon_cv2fun_sol"//TRIM(smode)//".out" + cv2_mn_file = "gpec_recon_cv2mn_sol"//TRIM(smode)//".out" + qv_mn_file = "gpec_recon_qvmn_sol"//TRIM(smode)//".out" + int_file = "gpec_recon_integration_sol"//TRIM(smode)//".out" + terms_file = "gpec_recon_terms_sol"//TRIM(smode)//".out" + + IF (recon_out) THEN + OPEN(NEWUNIT=ushear_fun, FILE=shear_fun_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=ucurv, FILE=curv_file, STATUS="UNKNOWN") + IF (curl_check_flag) THEN + OPEN(NEWUNIT=ucveri, FILE=cveri_file, STATUS="UNKNOWN") + ENDIF + OPEN(NEWUNIT=uk, FILE=k_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=uk_sigma, FILE=k_sigma_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=ucw_fun, FILE=cw_fun_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=ucw_mn, FILE=cw_mn_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=ucv_fun, FILE=cv_fun_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=ucv_mn, FILE=cv_mn_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=ucv2_fun, FILE=cv2_fun_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=ucv2_mn, FILE=cv2_mn_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=uqv_mn, FILE=qv_mn_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=u_int, FILE=int_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=u_terms, FILE=terms_file, STATUS="UNKNOWN") + WRITE(ushear_fun,'(6(1x,a16))') + $ "psi","theta","r","z","shear_re","shear_im" + WRITE(ucurv,'(6(1x,a16))') + $ "psi","theta","r","z","curv_re","curv_im" + IF (curl_check_flag) WRITE(ucveri,'(6(1x,a16))') + $ "psi","theta","r","z","cveri_re","cveri_im" + WRITE(uk,'(9(1x,a16))') + $ "psi","theta","r","z","K_re","K_im", + $ "T1_re","T2_re","T3_re" + WRITE(uk_sigma,'(8(1x,a16))') + $ "psi","theta","r","z","sigma_re","jdotb_re", + $ "mu0_bsig2_re","mu0_sigjdotb" + WRITE(ucw_fun,'(10(1x,a16))') + $ "psi","theta","r","z","cwp_re","cwp_im", + $ "cwt_re","cwt_im","cwz_re","cwz_im" + WRITE(ucw_mn,'(8(1x,a16))') + $ "psi","m","cwp_re","cwp_im","cwt_re","cwt_im", + $ "cwz_re","cwz_im" + WRITE(ucv_fun,'(10(1x,a16))') + $ "psi","theta","r","z","cvp_re","cvp_im", + $ "cvt_re","cvt_im","cvz_re","cvz_im" + WRITE(ucv_mn,'(8(1x,a16))') + $ "psi","m","cvp_re","cvp_im","cvt_re","cvt_im", + $ "cvz_re","cvz_im" + WRITE(ucv2_fun,'(10(1x,a16))') + $ "psi","theta","r","z","cv2p_re","cv2p_im", + $ "cv2t_re","cv2t_im","cv2z_re","cv2z_im" + WRITE(ucv2_mn,'(8(1x,a16))') + $ "psi","m","cv2p_re","cv2p_im","cv2t_re","cv2t_im", + $ "cv2z_re","cv2z_im" + WRITE(uqv_mn,'(8(1x,a16))') + $ "psi","m","qvp_re","qvp_im","qvt_re","qvt_im", + $ "qvz_re","qvz_im" + WRITE(u_int,'(15(1x,a16))') + $ "psi","c2_mu0","k_xin2_re","k_xin2_im", + $ "c2_mu0_sum","k_xin2_c_re","k_xin2_c_im", + $ "dw_c_re","dw_c_im","c2_psi","c2_theta","c2_zeta", + $ "k1_xin2_re","k2_xin2_re","k3_xin2_re" + WRITE(u_terms,'(11(1x,a16))') + $ "psi","theta","r","z","c2_den","c2_psi_den", + $ "c2_theta_den","c2_zeta_den","dst1_den","dst2_den", + $ "dst3_den" + ENDIF + ep_index = mode + IF (ep_index < 1 .OR. ep_index > mpert) THEN + CALL gpec_stop("gpout_recon selected mode is out of range") + ENDIF + ep_selected = REAL(ep(ep_index)) + cveri_max = 0.0_r8 + cveri_rms = 0.0_r8 + cveri_sumsq = 0.0_r8 + cveri_scale_max = 0.0_r8 + cveri_rel = 0.0_r8 +c curl check is judged on max|residual| relative to the largest +c (|d_theta C_zeta| + |d_zeta C_theta|)/|J|: the identity is a +c cancellation of these two terms, so their size is the natural +c amplitude- and unit-free reference. + cveri_tol = 1.0e-8_r8 + cveri_count = 0 +c----------------------------------------------------------------------- +c main loop over all psi levels. +c compute gpeq reconstruction diagnostics at each psi. +c----------------------------------------------------------------------- + CALL idcon_build(mode, xspmn) + CALL gpeq_alloc + + DO ipsi = 0, mpsi + psi = rzphi%xs(ipsi) + IF (psi > psilim) EXIT + +c Build the psi-local perturbed equilibrium and C state once; +c gpeq_epf, gpeq_cveri and gpeq_dst below all reuse it. + CALL gpeq_prep_c(psi) + CALL gpeq_epf(psi, epf_int, epf_den_fun=epf_den_fun, + $ epf_p_fun=epf_p_fun, epf_t_fun=epf_t_fun, + $ epf_z_fun=epf_z_fun) + +c Capture covariant bare-Q components (Q_i) set by gpeq_cova +c inside gpeq_prep_c, before gpeq_K/gpeq_dst run. These are the +c genuine perturbed-field covariant components delta-B_i; the +c effective field is cvmn = C_i = Q_i + V_i. Writing qvmn lets +c (curl Q).grad(psi) be formed from covariant components, the +c same way cveri forms (curl C).grad(psi) from cvmn. + IF (recon_out) THEN + qvp_mn = bvp_mn + qvt_mn = bvt_mn + qvz_mn = bvz_mn + ENDIF + +c Reconstruct detailed K diagnostics for output. Keep this on +c the original path so recon terminal totals remain unchanged. + CALL gpeq_K(psi, K_fun, K_term1, K_term2, K_term3, + $ sigma_vals, jdotb_vals, shear_fun, curv_fun) +c Verify (curl C).grad(psi)=0 using the C state prepared above. + IF (curl_check_flag) THEN + CALL gpeq_cveri(psi, cveri_fun, cveri_scale) + DO itheta = 0, mthsurf + cveri_abs = ABS(cveri_fun(itheta)) + cveri_max = MAX(cveri_max, cveri_abs) + cveri_scale_max = MAX(cveri_scale_max, + $ cveri_scale(itheta)) + cveri_sumsq = cveri_sumsq + cveri_abs**2 + cveri_count = cveri_count + 1 + ENDDO + ENDIF + + IF (recon_out) THEN +c Store spatial values with coordinates only when recon file +c output is requested. +c K_i xi_n^2 per-theta densities for the recon terms file. + CALL gpeq_dst(psi, dst_int, dst1_den_fun=dst1_den_fun, + $ dst2_den_fun=dst2_den_fun, dst3_den_fun=dst3_den_fun) + DO itheta = 0, mthsurf + CALL bicube_eval(rzphi, psi, theta(itheta), 1) + rfac = SQRT(MAX(rzphi%f(1), 0.0_r8)) + eta = twopi*(theta(itheta) + rzphi%f(2)) + rvals(itheta) = ro + rfac*COS(eta) + zvals(itheta) = zo + rfac*SIN(eta) + + WRITE(u_terms,'(11(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), epf_den_fun(itheta), + $ epf_p_fun(itheta), epf_t_fun(itheta), + $ epf_z_fun(itheta), dst1_den_fun(itheta), + $ dst2_den_fun(itheta), dst3_den_fun(itheta) + WRITE(ushear_fun,'(6(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), + $ REAL(shear_fun(itheta)), AIMAG(shear_fun(itheta)) + WRITE(ucurv,'(6(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), + $ REAL(curv_fun(itheta)), + $ AIMAG(curv_fun(itheta)) + IF (curl_check_flag) WRITE(ucveri,'(6(es17.8e3))') psi, + $ theta(itheta), rvals(itheta), zvals(itheta), + $ REAL(cveri_fun(itheta)), AIMAG(cveri_fun(itheta)) + WRITE(uk,'(9(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), REAL(K_fun(itheta)), + $ AIMAG(K_fun(itheta)), K_term1(itheta), + $ K_term2(itheta), K_term3(itheta) + WRITE(uk_sigma,'(8(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), sigma_vals(itheta), + $ jdotb_vals(itheta), K_term2(itheta), + $ mu0 * sigma_vals(itheta) * jdotb_vals(itheta) + ENDDO + WRITE(u_terms,*) + WRITE(ushear_fun,*) + WRITE(ucurv,*) + IF (curl_check_flag) WRITE(ucveri,*) + WRITE(uk,*) + WRITE(uk_sigma,*) + +c Reconstruct C fields from the already computed C state only +c for recon output tables. + CALL iscdftb(mfac, mpert, cw_fun(:,1), mthsurf, cwp_mn) + CALL iscdftb(mfac, mpert, cw_fun(:,2), mthsurf, cwt_mn) + CALL iscdftb(mfac, mpert, cw_fun(:,3), mthsurf, cwz_mn) + CALL iscdftb(mfac, mpert, cv_fun(:,1), mthsurf, cvp_mn) + CALL iscdftb(mfac, mpert, cv_fun(:,2), mthsurf, cvt_mn) + CALL iscdftb(mfac, mpert, cv_fun(:,3), mthsurf, cvz_mn) + CALL iscdftb(mfac, mpert, cv2_fun(:,1), mthsurf, c2vp_mn) + CALL iscdftb(mfac, mpert, cv2_fun(:,2), mthsurf, c2vt_mn) + CALL iscdftb(mfac, mpert, cv2_fun(:,3), mthsurf, c2vz_mn) + DO itheta = 0, mthsurf + WRITE(ucw_fun,'(10(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), + $ REAL(cw_fun(itheta,1)), AIMAG(cw_fun(itheta,1)), + $ REAL(cw_fun(itheta,2)), AIMAG(cw_fun(itheta,2)), + $ REAL(cw_fun(itheta,3)), AIMAG(cw_fun(itheta,3)) + WRITE(ucv_fun,'(10(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), + $ REAL(cv_fun(itheta,1)), AIMAG(cv_fun(itheta,1)), + $ REAL(cv_fun(itheta,2)), AIMAG(cv_fun(itheta,2)), + $ REAL(cv_fun(itheta,3)), AIMAG(cv_fun(itheta,3)) + WRITE(ucv2_fun,'(10(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), + $ REAL(cv2_fun(itheta,1)), AIMAG(cv2_fun(itheta,1)), + $ REAL(cv2_fun(itheta,2)), AIMAG(cv2_fun(itheta,2)), + $ REAL(cv2_fun(itheta,3)), AIMAG(cv2_fun(itheta,3)) + ENDDO + DO ipert = 1, mpert + WRITE(ucw_mn,'(1x,es17.8e3,1x,I8,6(es17.8e3))') psi, + $ mfac(ipert), cwp_mn(ipert), cwt_mn(ipert), + $ cwz_mn(ipert) + WRITE(ucv_mn,'(1x,es17.8e3,1x,I8,6(es17.8e3))') psi, + $ mfac(ipert), cvp_mn(ipert), cvt_mn(ipert), + $ cvz_mn(ipert) + WRITE(ucv2_mn,'(1x,es17.8e3,1x,I8,6(es17.8e3))') psi, + $ mfac(ipert), c2vp_mn(ipert), c2vt_mn(ipert), + $ c2vz_mn(ipert) + WRITE(uqv_mn,'(1x,es17.8e3,1x,I8,6(es17.8e3))') psi, + $ mfac(ipert), qvp_mn(ipert), qvt_mn(ipert), + $ qvz_mn(ipert) + ENDDO + WRITE(ucw_fun,*) + WRITE(ucw_mn,*) + WRITE(ucv_fun,*) + WRITE(ucv_mn,*) + WRITE(ucv2_fun,*) + WRITE(ucv2_mn,*) + WRITE(uqv_mn,*) + ENDIF + ENDDO + +c----------------------------------------------------------------------- +c Perform the energy check on the DCON solution grid. Do not use +c rzphi%xs for this comparison; it is a coarse geometry grid. +c----------------------------------------------------------------------- + epf_int_total_hr = 0.0_r8 + epf_p_total_hr = 0.0_r8 + epf_t_total_hr = 0.0_r8 + epf_z_total_hr = 0.0_r8 + dst_int_total_k_hr = CMPLX(0.0_r8, 0.0_r8, r8) + dst1_total_hr = CMPLX(0.0_r8, 0.0_r8, r8) + dst2_total_hr = CMPLX(0.0_r8, 0.0_r8, r8) + dst3_total_hr = CMPLX(0.0_r8, 0.0_r8, r8) + psi_prev = MAX(psifac(0), rzphi%xs(0)) + CALL gpeq_prep_c(psi_prev) + CALL gpeq_epf(psi_prev, epf_prev, epf_p_prev, epf_t_prev, + $ epf_z_prev) + CALL gpeq_dst(psi_prev, dst_prev, dst1_prev, dst2_prev, + $ dst3_prev) + IF (recon_out) THEN + WRITE(u_int,'(a)') "c psifac-grid integration results" + WRITE(u_int,'(a)') "c C2/mu0 = int J |C|^2 / mu0" + WRITE(u_int,'(a)') "c Kxin2 = int J K |xi_n|^2" + WRITE(u_int,'(a)') "c dW_gpec(K) = 0.5 * (C2/mu0 - Kxin2)" + WRITE(u_int,'(a,a)') "c recon_int = ", TRIM(recon_int) + ENDIF + nint_pts = 1 + DO istep = 0, mstep-1 + psi_curr = psifac(istep+1) + IF (psi_curr < rzphi%xs(0)) CYCLE + IF (psi_curr > rzphi%xs(mpsi)) EXIT + nint_pts = nint_pts + 1 + ENDDO + ALLOCATE(psi_int_pts(nint_pts), epf_vals(nint_pts), + $ epf_p_vals(nint_pts), epf_t_vals(nint_pts), + $ epf_z_vals(nint_pts), dst_vals(nint_pts), + $ dst1_vals(nint_pts), dst2_vals(nint_pts), + $ dst3_vals(nint_pts)) + psi_int_pts(1) = psi_prev + epf_vals(1) = epf_prev + epf_p_vals(1) = epf_p_prev + epf_t_vals(1) = epf_t_prev + epf_z_vals(1) = epf_z_prev + dst_vals(1) = dst_prev + dst1_vals(1) = dst1_prev + dst2_vals(1) = dst2_prev + dst3_vals(1) = dst3_prev + iint = 1 + DO istep = 0, mstep-1 + psi_curr = psifac(istep+1) + IF (psi_curr < rzphi%xs(0)) CYCLE + IF (psi_curr > rzphi%xs(mpsi)) EXIT + CALL gpeq_prep_c(psi_curr) + CALL gpeq_epf(psi_curr, epf_curr, epf_p_curr, + $ epf_t_curr, epf_z_curr) + CALL gpeq_dst(psi_curr, dst_curr, dst1_curr, dst2_curr, + $ dst3_curr) + iint = iint + 1 + psi_int_pts(iint) = psi_curr + epf_vals(iint) = epf_curr + epf_p_vals(iint) = epf_p_curr + epf_t_vals(iint) = epf_t_curr + epf_z_vals(iint) = epf_z_curr + dst_vals(iint) = dst_curr + dst1_vals(iint) = dst1_curr + dst2_vals(iint) = dst2_curr + dst3_vals(iint) = dst3_curr + ENDDO + IF (TRIM(recon_int) == "spline" .AND. nint_pts > 1) THEN + CALL cspline_alloc(recon_epf_spl, nint_pts-1, 4) + CALL cspline_alloc(recon_dst_spl, nint_pts-1, 4) + recon_epf_spl%xs = psi_int_pts + recon_dst_spl%xs = psi_int_pts + recon_epf_spl%fs(:,1) = CMPLX(epf_vals, 0.0_r8, r8) + recon_epf_spl%fs(:,2) = CMPLX(epf_p_vals, 0.0_r8, r8) + recon_epf_spl%fs(:,3) = CMPLX(epf_t_vals, 0.0_r8, r8) + recon_epf_spl%fs(:,4) = CMPLX(epf_z_vals, 0.0_r8, r8) + recon_dst_spl%fs(:,1) = dst_vals + recon_dst_spl%fs(:,2) = dst1_vals + recon_dst_spl%fs(:,3) = dst2_vals + recon_dst_spl%fs(:,4) = dst3_vals + CALL cspline_fit(recon_epf_spl, "extrap") + CALL cspline_fit(recon_dst_spl, "extrap") + CALL cspline_int(recon_epf_spl) + CALL cspline_int(recon_dst_spl) + DO iint = 1, nint_pts + epf_int_total_hr = REAL(recon_epf_spl%fsi(iint-1,1)) + epf_p_total_hr = REAL(recon_epf_spl%fsi(iint-1,2)) + epf_t_total_hr = REAL(recon_epf_spl%fsi(iint-1,3)) + epf_z_total_hr = REAL(recon_epf_spl%fsi(iint-1,4)) + dst_int_total_k_hr = recon_dst_spl%fsi(iint-1,1) + dst1_total_hr = recon_dst_spl%fsi(iint-1,2) + dst2_total_hr = recon_dst_spl%fsi(iint-1,3) + dst3_total_hr = recon_dst_spl%fsi(iint-1,4) + dw_cum = 0.5_r8 * + $ (CMPLX(epf_int_total_hr, 0.0_r8, r8) - + $ dst_int_total_k_hr) + IF (recon_out) WRITE(u_int,'(15(es17.8e3))') + $ psi_int_pts(iint), epf_vals(iint), + $ REAL(dst_vals(iint)), AIMAG(dst_vals(iint)), + $ epf_int_total_hr, REAL(dst_int_total_k_hr), + $ AIMAG(dst_int_total_k_hr), REAL(dw_cum), AIMAG(dw_cum), + $ epf_p_vals(iint), epf_t_vals(iint), epf_z_vals(iint), + $ REAL(dst1_vals(iint)), REAL(dst2_vals(iint)), + $ REAL(dst3_vals(iint)) + ENDDO + CALL cspline_dealloc(recon_epf_spl) + CALL cspline_dealloc(recon_dst_spl) + ELSE + dw_cum = CMPLX(0.0_r8, 0.0_r8, r8) + IF (recon_out) WRITE(u_int,'(15(es17.8e3))') + $ psi_int_pts(1), epf_vals(1), REAL(dst_vals(1)), + $ AIMAG(dst_vals(1)), epf_int_total_hr, + $ REAL(dst_int_total_k_hr), AIMAG(dst_int_total_k_hr), + $ REAL(dw_cum), AIMAG(dw_cum), epf_p_vals(1), epf_t_vals(1), + $ epf_z_vals(1), REAL(dst1_vals(1)), REAL(dst2_vals(1)), + $ REAL(dst3_vals(1)) + DO iint = 2, nint_pts + dpsi_local = psi_int_pts(iint) - psi_int_pts(iint-1) + epf_int_total_hr = epf_int_total_hr + + $ (epf_vals(iint-1) + epf_vals(iint)) * dpsi_local / 2.0_r8 + epf_p_total_hr = epf_p_total_hr + + $ (epf_p_vals(iint-1) + epf_p_vals(iint)) * dpsi_local + $ / 2.0_r8 + epf_t_total_hr = epf_t_total_hr + + $ (epf_t_vals(iint-1) + epf_t_vals(iint)) * dpsi_local + $ / 2.0_r8 + epf_z_total_hr = epf_z_total_hr + + $ (epf_z_vals(iint-1) + epf_z_vals(iint)) * dpsi_local + $ / 2.0_r8 + dst_int_total_k_hr = dst_int_total_k_hr + + $ (dst_vals(iint-1) + dst_vals(iint)) * dpsi_local / 2.0_r8 + dst1_total_hr = dst1_total_hr + + $ (dst1_vals(iint-1) + dst1_vals(iint)) * + $ dpsi_local / 2.0_r8 + dst2_total_hr = dst2_total_hr + + $ (dst2_vals(iint-1) + dst2_vals(iint)) * + $ dpsi_local / 2.0_r8 + dst3_total_hr = dst3_total_hr + + $ (dst3_vals(iint-1) + dst3_vals(iint)) * + $ dpsi_local / 2.0_r8 + dw_cum = 0.5_r8 * + $ (CMPLX(epf_int_total_hr, 0.0_r8, r8) - + $ dst_int_total_k_hr) + IF (recon_out) WRITE(u_int,'(15(es17.8e3))') + $ psi_int_pts(iint), epf_vals(iint), + $ REAL(dst_vals(iint)), AIMAG(dst_vals(iint)), + $ epf_int_total_hr, REAL(dst_int_total_k_hr), + $ AIMAG(dst_int_total_k_hr), REAL(dw_cum), AIMAG(dw_cum), + $ epf_p_vals(iint), epf_t_vals(iint), epf_z_vals(iint), + $ REAL(dst1_vals(iint)), REAL(dst2_vals(iint)), + $ REAL(dst3_vals(iint)) + ENDDO + ENDIF + psi_lo = psi_int_pts(1) + psi_hi = psi_int_pts(nint_pts) + DEALLOCATE(psi_int_pts, epf_vals, epf_p_vals, epf_t_vals, + $ epf_z_vals, dst_vals, dst1_vals, dst2_vals, dst3_vals) + dw_raw_k_hr = 0.5_r8 * + $ (CMPLX(epf_int_total_hr, 0.0_r8, r8) - dst_int_total_k_hr) + dw_dcon_k_hr = dw_raw_k_hr * + $ (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 + + IF (recon_out) THEN + WRITE(u_int,*) + WRITE(u_int,'(a)') "c Final psifac-grid integration results:" + WRITE(u_int,'(3(es17.8e3))') epf_int_total_hr, + $ REAL(dst_int_total_k_hr), AIMAG(dst_int_total_k_hr) + WRITE(u_int,'(a)') "c Final C2 component totals:" + WRITE(u_int,'(3(es17.8e3))') epf_p_total_hr, + $ epf_t_total_hr, epf_z_total_hr + WRITE(u_int,'(a)') "c Final K component totals:" + WRITE(u_int,'(3(es17.8e3))') REAL(dst1_total_hr), + $ REAL(dst2_total_hr), REAL(dst3_total_hr) + WRITE(u_int,'(a)') "c dW_gpec(K)" + WRITE(u_int,'(2(es17.8e3))') REAL(dw_raw_k_hr), + $ AIMAG(dw_raw_k_hr) + WRITE(u_int,'(a)') "c dW_dcon(K), normalized from dW_gpec(K)" + WRITE(u_int,'(2(es17.8e3))') REAL(dw_dcon_k_hr), + $ AIMAG(dw_dcon_k_hr) + ENDIF + + WRITE(*,'(a)') "GPEC_RECON C2 component totals:" + WRITE(*,'(a,es17.8e3)') " C2_psi/mu0 = ", + $ epf_p_total_hr + WRITE(*,'(a,es17.8e3)') " C2_theta/mu0 = ", + $ epf_t_total_hr + WRITE(*,'(a,es17.8e3)') " C2_zeta/mu0 = ", + $ epf_z_total_hr + WRITE(*,'(a)') "GPEC_RECON K component totals:" + WRITE(*,'(a,es17.8e3)') " K1_xin2 = ", + $ REAL(dst1_total_hr) + WRITE(*,'(a,es17.8e3)') " K2_xin2 = ", + $ REAL(dst2_total_hr) + WRITE(*,'(a,es17.8e3)') " K3_xin2 = ", + $ REAL(dst3_total_hr) + WRITE(*,'(a)') "GPEC_RECON final psifac-grid dW terms:" + WRITE(*,'(a,es17.8e3)') " int_J_C2_over_mu0 = ", + $ epf_int_total_hr + WRITE(*,'(a,es17.8e3)') " int_J_K_xin2 = ", + $ REAL(dst_int_total_k_hr) + WRITE(*,'(a,es17.8e3)') " dW_p(recon) = ", + $ REAL(dw_raw_k_hr) + WRITE(*,'(a,es17.8e3)') " dW_p(recon-normalize)= ", + $ REAL(dw_dcon_k_hr) + WRITE(*,'(a,a,a,es17.8e3)') " GPEC ep(", TRIM(smode), ") = ", + $ ep_selected + WRITE(*,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), ") = ", + $ ep_selected * (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 + WRITE(*,'(a,2es17.8e3)') " recon psi range = ", + $ psi_lo, psi_hi + WRITE(*,'(a,2es17.8e3)') " DCON psi range = ", + $ psilow, psilim + IF (curl_check_flag) THEN + cveri_stat = "PASS" + IF (cveri_count > 0) THEN + cveri_rms = SQRT(cveri_sumsq / REAL(cveri_count, r8)) + IF (cveri_scale_max > 0.0_r8) THEN + cveri_rel = cveri_max / cveri_scale_max + ELSE + cveri_rel = cveri_max + ENDIF + IF (cveri_rel > cveri_tol) cveri_stat = "WARN" + ELSE + cveri_stat = "SKIP" + ENDIF + WRITE(*,'(a,a)') "GPEC_RECON C verify: ", TRIM(cveri_stat) + WRITE(*,'(a,es17.8e3)') " max = ", cveri_max + WRITE(*,'(a,es17.8e3)') " rms = ", cveri_rms + WRITE(*,'(a,es17.8e3)') " scale = ", cveri_scale_max + WRITE(*,'(a,es17.8e3)') " rel = ", cveri_rel + WRITE(*,'(a,es17.8e3)') " tol = ", cveri_tol + ENDIF + + IF (recon_out) THEN + OPEN(NEWUNIT=u_log, FILE="gpec.log", STATUS="UNKNOWN", + $ POSITION="APPEND") + WRITE(u_log,'(a)') "GPEC_RECON final results:" + WRITE(u_log,'(a,I8)') " mode = ", mode + WRITE(u_log,'(a,L1)') " reg_flag = ", reg_flag + WRITE(u_log,'(a)') " C2 component totals:" + WRITE(u_log,'(a,es17.8e3)') " C2_psi/mu0 = ", + $ epf_p_total_hr + WRITE(u_log,'(a,es17.8e3)') " C2_theta/mu0 = ", + $ epf_t_total_hr + WRITE(u_log,'(a,es17.8e3)') " C2_zeta/mu0 = ", + $ epf_z_total_hr + WRITE(u_log,'(a)') " K component totals:" + WRITE(u_log,'(a,es17.8e3)') " K1_xin2 = ", + $ REAL(dst1_total_hr) + WRITE(u_log,'(a,es17.8e3)') " K2_xin2 = ", + $ REAL(dst2_total_hr) + WRITE(u_log,'(a,es17.8e3)') " K3_xin2 = ", + $ REAL(dst3_total_hr) + WRITE(u_log,'(a)') " psifac-grid dW terms:" + WRITE(u_log,'(a,es17.8e3)') " int_J_C2_over_mu0 = ", + $ epf_int_total_hr + WRITE(u_log,'(a,es17.8e3)') " int_J_K_xin2 = ", + $ REAL(dst_int_total_k_hr) + WRITE(u_log,'(a,es17.8e3)') " dW_p(recon) = ", + $ REAL(dw_raw_k_hr) + WRITE(u_log,'(a,es17.8e3)') " dW_p(recon-normalize)= ", + $ REAL(dw_dcon_k_hr) + WRITE(u_log,'(a,a,a,es17.8e3)') " GPEC ep(", TRIM(smode), + $ ") = ", ep_selected + WRITE(u_log,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), + $ ") = ", ep_selected * + $ (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 + WRITE(u_log,'(a,2es17.8e3)') " recon psi range = ", + $ psi_lo, psi_hi + WRITE(u_log,'(a,2es17.8e3)') " DCON psi range = ", + $ psilow, psilim + IF (curl_check_flag) THEN + WRITE(u_log,'(a,a)') " C verify: ", TRIM(cveri_stat) + WRITE(u_log,'(a,es17.8e3)') " max = ", cveri_max + WRITE(u_log,'(a,es17.8e3)') " rms = ", cveri_rms + WRITE(u_log,'(a,es17.8e3)') " scale = ", cveri_scale_max + WRITE(u_log,'(a,es17.8e3)') " rel = ", cveri_rel + WRITE(u_log,'(a,es17.8e3)') " tol = ", cveri_tol + ENDIF + WRITE(u_log,*) + CLOSE(u_log) + ENDIF + + CALL gpeq_dealloc + IF (recon_out) THEN + CLOSE(ushear_fun) + CLOSE(ucurv) + IF (curl_check_flag) CLOSE(ucveri) + CLOSE(uk) + CLOSE(uk_sigma) + CLOSE(u_int) + CLOSE(u_terms) + CLOSE(ucw_fun) + CLOSE(ucw_mn) + CLOSE(ucv_fun) + CLOSE(ucv_mn) + CLOSE(ucv2_fun) + CLOSE(ucv2_mn) + CLOSE(uqv_mn) + ENDIF +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gpout_recon +c----------------------------------------------------------------------- +c subprogram 22. gpout_recon2 +c first-form plasma energy diagnostic based on +c +c |Q|^2 - j.(Q x xi*) + mu0 (div xi)* (xi.grad p) +c +c with the gamma p |div xi|^2 term intentionally omitted. +c The pressure-divergence contribution is evaluated directly from +c div xi and xi.grad p rather than closed as a Bernstein residual. +c----------------------------------------------------------------------- + SUBROUTINE gpout_recon2(mode, xspmn) +c----------------------------------------------------------------------- +c declaration. +c----------------------------------------------------------------------- + INTEGER, INTENT(IN) :: mode + COMPLEX(r8), DIMENSION(:), INTENT(IN) :: xspmn + + INTEGER :: ipsi, itheta +c recon2 output units, assigned by OPEN(NEWUNIT=...). + INTEGER :: usurf, u_int, u_log + INTEGER :: istep, nint_pts, iint + REAL(r8) :: psi, eta, rfac, dpsi_local, q2_hr + REAL(r8), DIMENSION(0:mthsurf) :: rvals, zvals + COMPLEX(r8) :: jqx_hr, pdiv_hr, total_hr + COMPLEX(r8) :: dw_raw_hr, dw_dcon_hr + COMPLEX(r8), DIMENSION(0:mthsurf) :: q2_fun, jqx_fun, pdiv_fun, + $ total_fun + CHARACTER(8) :: smode + CHARACTER(128) :: surf_file, int_file + INTEGER :: ep_index + REAL(r8) :: ep_selected, ep_dcon, psi_lo, psi_hi + REAL(r8), DIMENSION(:), ALLOCATABLE :: psi_int_pts, q2_vals_r + COMPLEX(r8), DIMENSION(:), ALLOCATABLE :: q2_vals, jqx_vals, + $ pdiv_vals, total_vals + TYPE(cspline_type) :: recon2_spl + + IF(verbose) WRITE(*,*)"" + IF(verbose) WRITE(*,*)"GPOUT_RECON2: Starting first-form"// + $ " diagnostics" + IF(verbose) WRITE(*,*)"__________________________________________" + + WRITE(smode,'(I8)') mode + smode = ADJUSTL(smode) + surf_file = "gpec_recon2_terms_sol"//TRIM(smode)//".out" + int_file = "gpec_recon2_integration_sol"//TRIM(smode)//".out" + + IF (recon_out) THEN + OPEN(NEWUNIT=usurf, FILE=surf_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=u_int, FILE=int_file, STATUS="UNKNOWN") + WRITE(usurf,'(12(1x,a16))') + $ "psi","theta","r","z","q2_re","q2_im","jqx_re", + $ "jqx_im","pdiv_re","pdiv_im","total_re","total_im" + WRITE(u_int,'(10(1x,a16))') + $ "psi","q2_re","q2_im","jqx_re","jqx_im","pdiv_re", + $ "pdiv_im","total_re","total_im","dw_re" + ENDIF + +c fail fast on an out-of-range mode selection. + ep_index = mode + IF (ep_index < 1 .OR. ep_index > mpert) THEN + CALL gpec_stop("gpout_recon2 selected mode is out of range") + ENDIF + + CALL idcon_build(mode, xspmn) + CALL gpeq_alloc + + DO ipsi = 0, mpsi + psi = rzphi%xs(ipsi) + IF (psi > psilim) EXIT + CALL gpeq_firstform(psi, q2_hr, jqx_hr, pdiv_hr, total_hr, + $ q2_fun, jqx_fun, pdiv_fun, total_fun) + + IF (recon_out) THEN + DO itheta = 0, mthsurf + CALL bicube_eval(rzphi, psi, theta(itheta), 1) + rfac = SQRT(MAX(rzphi%f(1), 0.0_r8)) + eta = twopi*(theta(itheta) + rzphi%f(2)) + rvals(itheta) = ro + rfac*COS(eta) + zvals(itheta) = zo + rfac*SIN(eta) + WRITE(usurf,'(12(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), REAL(q2_fun(itheta)), + $ AIMAG(q2_fun(itheta)), REAL(jqx_fun(itheta)), + $ AIMAG(jqx_fun(itheta)), REAL(pdiv_fun(itheta)), + $ AIMAG(pdiv_fun(itheta)), REAL(total_fun(itheta)), + $ AIMAG(total_fun(itheta)) + ENDDO + WRITE(usurf,*) + ENDIF + ENDDO + + ep_selected = REAL(ep(ep_index)) + ep_dcon = ep_selected * + $ (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 + q2_hr = 0.0_r8 + jqx_hr = CMPLX(0.0_r8, 0.0_r8, r8) + pdiv_hr = CMPLX(0.0_r8, 0.0_r8, r8) + total_hr = CMPLX(0.0_r8, 0.0_r8, r8) + +c integrate on psifac points inside the spline domain; points +c below rzphi%xs(0) are skipped (gpout_recon clamps instead). + nint_pts = 0 + psi_lo = 0.0_r8 + psi_hi = 0.0_r8 + DO istep = 0, mstep + IF (psifac(istep) < rzphi%xs(0)) CYCLE + IF (psifac(istep) > rzphi%xs(mpsi)) EXIT + nint_pts = nint_pts + 1 + IF (nint_pts == 1) psi_lo = psifac(istep) + psi_hi = psifac(istep) + ENDDO + ALLOCATE(psi_int_pts(nint_pts), q2_vals_r(nint_pts), + $ q2_vals(nint_pts), jqx_vals(nint_pts), pdiv_vals(nint_pts), + $ total_vals(nint_pts)) + iint = 0 + DO istep = 0, mstep + psi = psifac(istep) + IF (psi < rzphi%xs(0)) CYCLE + IF (psi > rzphi%xs(mpsi)) EXIT + CALL gpeq_firstform(psi, q2_hr, jqx_hr, pdiv_hr, total_hr, + $ q2_fun, jqx_fun, pdiv_fun, total_fun) + iint = iint + 1 + psi_int_pts(iint) = psi + q2_vals_r(iint) = q2_hr + q2_vals(iint) = CMPLX(q2_hr, 0.0_r8, r8) + jqx_vals(iint) = jqx_hr + pdiv_vals(iint) = pdiv_hr + total_vals(iint) = total_hr + ENDDO + + IF (recon_out) THEN + WRITE(u_int,'(a)') "c first-form plasma-energy terms" + WRITE(u_int,'(a)') "c total = q2 - jqx + pdiv" + WRITE(u_int,'(a)') "c pdiv term is computed directly" + WRITE(u_int,'(a,a)') "c recon_int = ", TRIM(recon_int) + ENDIF + + IF (TRIM(recon_int) == "spline" .AND. nint_pts > 1) THEN + CALL cspline_alloc(recon2_spl, nint_pts-1, 4) + recon2_spl%xs = psi_int_pts + recon2_spl%fs(:,1) = q2_vals + recon2_spl%fs(:,2) = jqx_vals + recon2_spl%fs(:,3) = pdiv_vals + recon2_spl%fs(:,4) = total_vals + CALL cspline_fit(recon2_spl, "extrap") + CALL cspline_int(recon2_spl) + DO iint = 1, nint_pts + q2_hr = REAL(recon2_spl%fsi(iint-1,1), r8) + jqx_hr = recon2_spl%fsi(iint-1,2) + pdiv_hr = recon2_spl%fsi(iint-1,3) + total_hr = recon2_spl%fsi(iint-1,4) + dw_raw_hr = 0.5_r8 * total_hr + IF (recon_out) WRITE(u_int,'(10(es17.8e3))') + $ psi_int_pts(iint), q2_vals_r(iint), 0.0_r8, + $ REAL(jqx_vals(iint)), AIMAG(jqx_vals(iint)), + $ REAL(pdiv_vals(iint)), AIMAG(pdiv_vals(iint)), + $ REAL(total_vals(iint)), AIMAG(total_vals(iint)), + $ REAL(dw_raw_hr) + ENDDO + CALL cspline_dealloc(recon2_spl) + ELSE + q2_hr = 0.0_r8 + jqx_hr = CMPLX(0.0_r8, 0.0_r8, r8) + pdiv_hr = CMPLX(0.0_r8, 0.0_r8, r8) + total_hr = CMPLX(0.0_r8, 0.0_r8, r8) + DO iint = 1, nint_pts + IF (iint > 1) THEN + dpsi_local = psi_int_pts(iint) - psi_int_pts(iint-1) + q2_hr = q2_hr + (q2_vals_r(iint-1) + q2_vals_r(iint)) * + $ dpsi_local / 2.0_r8 + jqx_hr = jqx_hr + (jqx_vals(iint-1) + jqx_vals(iint)) * + $ dpsi_local / 2.0_r8 + pdiv_hr = pdiv_hr + (pdiv_vals(iint-1) + + $ pdiv_vals(iint)) * dpsi_local / 2.0_r8 + total_hr = total_hr + (total_vals(iint-1) + + $ total_vals(iint)) * dpsi_local / 2.0_r8 + ENDIF + dw_raw_hr = 0.5_r8 * total_hr + IF (recon_out) WRITE(u_int,'(10(es17.8e3))') + $ psi_int_pts(iint), q2_vals_r(iint), 0.0_r8, + $ REAL(jqx_vals(iint)), AIMAG(jqx_vals(iint)), + $ REAL(pdiv_vals(iint)), AIMAG(pdiv_vals(iint)), + $ REAL(total_vals(iint)), AIMAG(total_vals(iint)), + $ REAL(dw_raw_hr) + ENDDO + ENDIF + + dw_raw_hr = 0.5_r8 * total_hr + dw_dcon_hr = dw_raw_hr * + $ (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 + + IF (recon_out) THEN + WRITE(u_int,*) + WRITE(u_int,'(a)') "c Final first-form totals:" + WRITE(u_int,'(2(es17.8e3))') q2_hr, 0.0_r8 + WRITE(u_int,'(2(es17.8e3))') REAL(jqx_hr), AIMAG(jqx_hr) + WRITE(u_int,'(2(es17.8e3))') REAL(pdiv_hr), AIMAG(pdiv_hr) + WRITE(u_int,'(2(es17.8e3))') REAL(total_hr), AIMAG(total_hr) + WRITE(u_int,'(a)') "c dW_p(recon2)" + WRITE(u_int,'(2(es17.8e3))') REAL(dw_raw_hr), AIMAG(dw_raw_hr) + WRITE(u_int,'(a)') "c dW_p(recon2-normalize)" + WRITE(u_int,'(2(es17.8e3))') REAL(dw_dcon_hr), + $ AIMAG(dw_dcon_hr) + ENDIF + + WRITE(*,'(a)') "GPEC_RECON2 final first-form totals:" + WRITE(*,'(a,es17.8e3)') " int_J_Q2_over_mu0 = ", q2_hr + WRITE(*,'(a,2es17.8e3)') " int_J_jQxixi_over_mu0= ", + $ REAL(jqx_hr), AIMAG(jqx_hr) + WRITE(*,'(a,2es17.8e3)') " int_J_pdiv_term = ", + $ REAL(pdiv_hr), AIMAG(pdiv_hr) + WRITE(*,'(a,2es17.8e3)') " int_J_total = ", + $ REAL(total_hr), AIMAG(total_hr) + WRITE(*,'(a,es17.8e3)') " dW_p(recon2) = ", + $ REAL(dw_raw_hr) + WRITE(*,'(a,es17.8e3)') " dW_p(recon2-normalize)= ", + $ REAL(dw_dcon_hr) + WRITE(*,'(a,a,a,es17.8e3)') " GPEC ep(", TRIM(smode), ") = ", + $ ep_selected + WRITE(*,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), ") = ", + $ ep_dcon + WRITE(*,'(a,2es17.8e3)') " recon psi range = ", + $ psi_lo, psi_hi + WRITE(*,'(a,2es17.8e3)') " DCON psi range = ", + $ psilow, psilim + + IF (recon_out) THEN + OPEN(NEWUNIT=u_log, FILE="gpec.log", STATUS="UNKNOWN", + $ POSITION="APPEND") + WRITE(u_log,'(a)') "GPEC_RECON2 final results:" + WRITE(u_log,'(a,I8)') " mode = ", mode + WRITE(u_log,'(a,L1)') " reg_flag = ", reg_flag + WRITE(u_log,'(a,es17.8e3)') " int_J_Q2_over_mu0 = ", q2_hr + WRITE(u_log,'(a,2es17.8e3)') " int_J_jQxixi_over_mu0= ", + $ REAL(jqx_hr), AIMAG(jqx_hr) + WRITE(u_log,'(a,2es17.8e3)') " int_J_pdiv_term = ", + $ REAL(pdiv_hr), AIMAG(pdiv_hr) + WRITE(u_log,'(a,2es17.8e3)') " int_J_total = ", + $ REAL(total_hr), AIMAG(total_hr) + WRITE(u_log,'(a,es17.8e3)') " dW_p(recon2) = ", + $ REAL(dw_raw_hr) + WRITE(u_log,'(a,es17.8e3)') " dW_p(recon2-normalize)= ", + $ REAL(dw_dcon_hr) + WRITE(u_log,'(a,a,a,es17.8e3)') " GPEC ep(", TRIM(smode), + $ ") = ", ep_selected + WRITE(u_log,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), + $ ") = ", ep_dcon + WRITE(u_log,'(a,2es17.8e3)') " recon psi range = ", + $ psi_lo, psi_hi + WRITE(u_log,'(a,2es17.8e3)') " DCON psi range = ", + $ psilow, psilim + WRITE(u_log,*) + CLOSE(u_log) + ENDIF + + DEALLOCATE(psi_int_pts, q2_vals_r, q2_vals, jqx_vals, pdiv_vals, + $ total_vals) + CALL gpeq_dealloc + IF (recon_out) THEN + CLOSE(usurf) + CLOSE(u_int) + ENDIF +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gpout_recon2 +c----------------------------------------------------------------------- +c subprogram 23. gpout_recon3 +c Bernstein |C|^2 decomposition diagnostic. +c |C|^2 = |Q|^2 + 2 Re(V*.Q) + |V|^2 with V = xi_n mu0 j x n_hat. +c j is tangent to the flux surface (j.n=0) so +c |V|^2 = mu0^2 xi_n^2 |j|^2 = mu0^2 xi_n^2 (sigma^2 B^2 +c + p'^2 |grad psi|^2 / B^2). +c This routine writes ABS decomposition pieces: +c A = int J |Q|^2 / mu0 (perturbed B energy) +c Cpar = int J mu0 sigma^2 B^2 xi_n^2 (parallel current^2) +c Iperp = int J mu0 p'^2 |grad psi|^2 xi_n^2 / B^2 (perp current^2) +c B = int J 2 Re(V*.Q) / mu0 (cross term) +c epf = int J |C|^2 / mu0 (already in recon1) +c All five are computed independently (B from the equilibrium +c current, not as a remainder), so identity_res is a real check. +c Cpar should equal recon1's dst2; not compared here. +c----------------------------------------------------------------------- + SUBROUTINE gpout_recon3(mode, xspmn) +c----------------------------------------------------------------------- +c declaration. +c----------------------------------------------------------------------- + INTEGER, INTENT(IN) :: mode + COMPLEX(r8), DIMENSION(:), INTENT(IN) :: xspmn + + INTEGER :: ipsi, itheta +c recon3 output units, assigned by OPEN(NEWUNIT=...). + INTEGER :: usurf, u_int, u_log + INTEGER :: istep, nint_pts, iint + REAL(r8) :: psi, dpsi_local, rfac, eta + REAL(r8) :: A_hr, B_hr, Iperp_hr, Cpar_hr, epf_hr + REAL(r8) :: A_total, B_total, Iperp_total, Cpar_total, epf_total + REAL(r8) :: dst1_total, dst3_total + REAL(r8) :: identity_residual + REAL(r8) :: dw_raw3, dw_dcon3, ep_selected, ep_dcon + REAL(r8) :: psi_lo, psi_hi + INTEGER :: ep_index + COMPLEX(r8) :: dst_int_c, dst1_c, dst2_c, dst3_c + REAL(r8), DIMENSION(0:mthsurf) :: rvals, zvals + REAL(r8), DIMENSION(0:mthsurf) :: A_fun, B_fun, Iperp_fun + REAL(r8), DIMENSION(0:mthsurf) :: Cpar_fun + REAL(r8), DIMENSION(0:mthsurf,22) :: surf_extra + INTEGER :: kcol + CHARACTER(8) :: smode + CHARACTER(128) :: surf_file, int_file + REAL(r8), DIMENSION(:), ALLOCATABLE :: psi_int_pts + REAL(r8), DIMENSION(:), ALLOCATABLE :: A_vals, B_vals, + $ Iperp_vals, Cpar_vals, epf_vals, dst1_vals, dst3_vals + TYPE(cspline_type) :: recon3_spl + + IF(verbose) WRITE(*,*)"" + IF(verbose) WRITE(*,*)"GPOUT_RECON3: Bernstein |C|^2 decomp" + IF(verbose) WRITE(*,*)"__________________________________________" + + WRITE(smode,'(I8)') mode + smode = ADJUSTL(smode) + surf_file = "gpec_recon3_terms_sol"//TRIM(smode)//".out" + int_file = "gpec_recon3_integration_sol"//TRIM(smode)//".out" + + IF (recon_out) THEN + OPEN(NEWUNIT=usurf, FILE=surf_file, STATUS="UNKNOWN") + OPEN(NEWUNIT=u_int, FILE=int_file, STATUS="UNKNOWN") + WRITE(usurf,'(30(1x,a16))') + $ "psi","theta","r","z","A_den","B_den","Iperp_den", + $ "Cpar_den","VdotQ_re","VdotQ_im","Bcur_den","Bpre_den", + $ "Qp_re","Qp_im","Qt_re","Qt_im","Qz_re","Qz_im", + $ "Vt_re","Vt_im","Vz_re","Vz_im","jpar","jperp", + $ "xin_re","xin_im","delpsi","Bmod","dst1_den","dst3_den" + WRITE(u_int,'(12(1x,a16))') + $ "psi","A","B","Iperp","Cpar","epf","A_cum","B_cum", + $ "Iperp_cum","Cpar_cum","epf_cum","identity_res" + ENDIF + +c fail fast on an out-of-range mode selection. + ep_index = mode + IF (ep_index < 1 .OR. ep_index > mpert) THEN + CALL gpec_stop("gpout_recon3 selected mode is out of range") + ENDIF + + CALL idcon_build(mode, xspmn) + CALL gpeq_alloc + + DO ipsi = 0, mpsi + psi = rzphi%xs(ipsi) + IF (psi > psilim) EXIT + CALL gpeq_prep_c(psi) + CALL gpeq_epfterms(psi, A_hr, B_hr, Iperp_hr, Cpar_hr, epf_hr, + $ A_fun, B_fun, Iperp_fun, Cpar_fun, surf_extra) + IF (recon_out) THEN + DO itheta = 0, mthsurf + CALL bicube_eval(rzphi, psi, theta(itheta), 1) + rfac = SQRT(MAX(rzphi%f(1), 0.0_r8)) + eta = twopi*(theta(itheta) + rzphi%f(2)) + rvals(itheta) = ro + rfac*COS(eta) + zvals(itheta) = zo + rfac*SIN(eta) + WRITE(usurf,'(30(es17.8e3))') psi, theta(itheta), + $ rvals(itheta), zvals(itheta), A_fun(itheta), + $ B_fun(itheta), Iperp_fun(itheta), Cpar_fun(itheta), + $ (surf_extra(itheta,kcol), kcol=1,22) + ENDDO + WRITE(usurf,*) + ENDIF + ENDDO + +c integrate on psifac points inside the spline domain; points +c below rzphi%xs(0) are skipped (gpout_recon clamps instead). + nint_pts = 0 + psi_lo = 0.0_r8 + psi_hi = 0.0_r8 + DO istep = 0, mstep + IF (psifac(istep) < rzphi%xs(0)) CYCLE + IF (psifac(istep) > rzphi%xs(mpsi)) EXIT + nint_pts = nint_pts + 1 + IF (nint_pts == 1) psi_lo = psifac(istep) + psi_hi = psifac(istep) + ENDDO + ALLOCATE(psi_int_pts(nint_pts), A_vals(nint_pts), + $ B_vals(nint_pts), Iperp_vals(nint_pts), Cpar_vals(nint_pts), + $ epf_vals(nint_pts), dst1_vals(nint_pts), dst3_vals(nint_pts)) + iint = 0 + DO istep = 0, mstep + psi = psifac(istep) + IF (psi < rzphi%xs(0)) CYCLE + IF (psi > rzphi%xs(mpsi)) EXIT + CALL gpeq_prep_c(psi) + CALL gpeq_epfterms(psi, A_hr, B_hr, Iperp_hr, Cpar_hr, epf_hr) +c dst1 (=K1 xi_n^2) and dst3 (=K3 xi_n^2) for the C-free dW form +c 2 dW_p = A + B + Iperp - dst1 - dst3 (dst2 = Cpar cancels). + CALL gpeq_dst(psi, dst_int_c, dst1_c, dst2_c, dst3_c) + iint = iint + 1 + psi_int_pts(iint) = psi + A_vals(iint) = A_hr + B_vals(iint) = B_hr + Iperp_vals(iint) = Iperp_hr + Cpar_vals(iint) = Cpar_hr + epf_vals(iint) = epf_hr + dst1_vals(iint) = REAL(dst1_c, r8) + dst3_vals(iint) = REAL(dst3_c, r8) + ENDDO + + IF (recon_out) THEN + WRITE(u_int,'(a)') "c Bernstein |C|^2 decomposition (direct)" + WRITE(u_int,'(a)') "c identity: epf = A + B + Cpar + Iperp" + WRITE(u_int,'(a)') "c identity_res = epf - A - B - Cpar"// + $ " - Iperp (Fourier truncation error)" + WRITE(u_int,'(a)') "c Cpar should equal dst2 from recon1"// + $ " (compare against gpec_recon_integration_sol*.out)" + WRITE(u_int,'(a,a)') "c recon_int = ", TRIM(recon_int) + ENDIF + + A_total = 0.0_r8 + B_total = 0.0_r8 + Iperp_total = 0.0_r8 + Cpar_total = 0.0_r8 + epf_total = 0.0_r8 + dst1_total = 0.0_r8 + dst3_total = 0.0_r8 + + IF (TRIM(recon_int) == "spline" .AND. nint_pts > 1) THEN + CALL cspline_alloc(recon3_spl, nint_pts-1, 7) + recon3_spl%xs = psi_int_pts + recon3_spl%fs(:,1) = CMPLX(A_vals, 0.0_r8, r8) + recon3_spl%fs(:,2) = CMPLX(B_vals, 0.0_r8, r8) + recon3_spl%fs(:,3) = CMPLX(Iperp_vals, 0.0_r8, r8) + recon3_spl%fs(:,4) = CMPLX(Cpar_vals, 0.0_r8, r8) + recon3_spl%fs(:,5) = CMPLX(epf_vals, 0.0_r8, r8) + recon3_spl%fs(:,6) = CMPLX(dst1_vals, 0.0_r8, r8) + recon3_spl%fs(:,7) = CMPLX(dst3_vals, 0.0_r8, r8) + CALL cspline_fit(recon3_spl, "extrap") + CALL cspline_int(recon3_spl) + DO iint = 1, nint_pts + A_total = REAL(recon3_spl%fsi(iint-1,1), r8) + B_total = REAL(recon3_spl%fsi(iint-1,2), r8) + Iperp_total = REAL(recon3_spl%fsi(iint-1,3), r8) + Cpar_total = REAL(recon3_spl%fsi(iint-1,4), r8) + epf_total = REAL(recon3_spl%fsi(iint-1,5), r8) + dst1_total = REAL(recon3_spl%fsi(iint-1,6), r8) + dst3_total = REAL(recon3_spl%fsi(iint-1,7), r8) + identity_residual = epf_total - A_total - B_total - + $ Cpar_total - Iperp_total + IF (recon_out) WRITE(u_int,'(12(es17.8e3))') + $ psi_int_pts(iint), A_vals(iint), B_vals(iint), + $ Iperp_vals(iint), Cpar_vals(iint), epf_vals(iint), + $ A_total, B_total, Iperp_total, Cpar_total, epf_total, + $ identity_residual + ENDDO + CALL cspline_dealloc(recon3_spl) + ELSE + DO iint = 1, nint_pts + IF (iint > 1) THEN + dpsi_local = psi_int_pts(iint) - psi_int_pts(iint-1) + A_total = A_total + (A_vals(iint-1)+A_vals(iint)) * + $ dpsi_local / 2.0_r8 + B_total = B_total + (B_vals(iint-1)+B_vals(iint)) * + $ dpsi_local / 2.0_r8 + Iperp_total = Iperp_total + (Iperp_vals(iint-1) + + $ Iperp_vals(iint)) * dpsi_local / 2.0_r8 + Cpar_total = Cpar_total + (Cpar_vals(iint-1) + + $ Cpar_vals(iint)) * dpsi_local / 2.0_r8 + epf_total = epf_total + (epf_vals(iint-1) + + $ epf_vals(iint)) * dpsi_local / 2.0_r8 + dst1_total = dst1_total + (dst1_vals(iint-1) + + $ dst1_vals(iint)) * dpsi_local / 2.0_r8 + dst3_total = dst3_total + (dst3_vals(iint-1) + + $ dst3_vals(iint)) * dpsi_local / 2.0_r8 + ENDIF + identity_residual = epf_total - A_total - B_total - + $ Cpar_total - Iperp_total + IF (recon_out) WRITE(u_int,'(12(es17.8e3))') + $ psi_int_pts(iint), A_vals(iint), B_vals(iint), + $ Iperp_vals(iint), Cpar_vals(iint), epf_vals(iint), + $ A_total, B_total, Iperp_total, Cpar_total, epf_total, + $ identity_residual + ENDDO + ENDIF + + identity_residual = epf_total - A_total - B_total - Cpar_total + $ - Iperp_total + +c C-free reconstructed dW_p: 2 dW = A + B + Iperp - dst1 - dst3 +c (the parallel-current piece dst2 = Cpar cancels identically). + dw_raw3 = 0.5_r8 * (A_total + B_total + Iperp_total + $ - dst1_total - dst3_total) + dw_dcon3 = dw_raw3 * + $ (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 + ep_selected = REAL(ep(ep_index)) + ep_dcon = ep_selected * + $ (mu0*2.0_r8) / psio**2 / (chi1*1.0e-3_r8)**2 + + IF (recon_out) THEN + WRITE(u_int,*) + WRITE(u_int,'(a)') "c Final |C|^2 decomposition totals:" + WRITE(u_int,'(a)') "c A_total = int J |Q|^2 / mu0" + WRITE(u_int,'(a)') "c B_total = int J 2 Re(V*.Q) / mu0" + WRITE(u_int,'(a)') "c Cpar_total = int J mu0 sigma^2 B^2"// + $ " xi_n^2" + WRITE(u_int,'(a)') "c Iperp_total= int J mu0 p'^2 |gp|^2"// + $ " xi_n^2 / B^2" + WRITE(u_int,'(a)') "c epf_total = int J |C|^2 / mu0" + WRITE(u_int,'(a)') "c identity_res = epf - (A+B+Cpar+Iperp)" + WRITE(u_int,'(es17.8e3)') A_total + WRITE(u_int,'(es17.8e3)') B_total + WRITE(u_int,'(es17.8e3)') Cpar_total + WRITE(u_int,'(es17.8e3)') Iperp_total + WRITE(u_int,'(es17.8e3)') epf_total + WRITE(u_int,'(es17.8e3)') identity_residual + ENDIF + + WRITE(*,'(a)') "GPEC_RECON3 |C|^2 decomposition totals:" + WRITE(*,'(a,es17.8e3)') " A_total = ", A_total + WRITE(*,'(a,es17.8e3)') " B_total = ", B_total + WRITE(*,'(a,es17.8e3)') " Cpar_total = ", Cpar_total + WRITE(*,'(a,es17.8e3)') " Iperp_total = ", Iperp_total + WRITE(*,'(a,es17.8e3)') " epf_total = ", epf_total + WRITE(*,'(a,es17.8e3)') " identity_res = ", identity_residual + WRITE(*,'(a)') " (identity_res is the Fourier truncation"// + $ " error; it shrinks as harmonics are added.)" + WRITE(*,'(a,es17.8e3)') " dW_p(recon3) = ", dw_raw3 + WRITE(*,'(a,es17.8e3)') " dW_p(recon3-normalize)= ", dw_dcon3 + WRITE(*,'(a,a,a,es17.8e3)') " GPEC ep(", TRIM(smode), ") = ", + $ ep_selected + WRITE(*,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), ") = ", + $ ep_dcon + WRITE(*,'(a,2es17.8e3)') " recon psi range = ", + $ psi_lo, psi_hi + WRITE(*,'(a,2es17.8e3)') " DCON psi range = ", + $ psilow, psilim + + IF (recon_out) THEN + OPEN(NEWUNIT=u_log, FILE="gpec.log", STATUS="UNKNOWN", + $ POSITION="APPEND") + WRITE(u_log,'(a)') "GPEC_RECON3 final results:" + WRITE(u_log,'(a,I8)') " mode = ", mode + WRITE(u_log,'(a,L1)') " reg_flag = ", reg_flag + WRITE(u_log,'(a)') " Bernstein |C|^2 decomposition:" + WRITE(u_log,'(a,es17.8e3)') " A_total = ", A_total + WRITE(u_log,'(a,es17.8e3)') " B_total = ", B_total + WRITE(u_log,'(a,es17.8e3)') " Cpar_total = ", Cpar_total + WRITE(u_log,'(a,es17.8e3)') " Iperp_total = ", Iperp_total + WRITE(u_log,'(a,es17.8e3)') " epf_total = ", epf_total + WRITE(u_log,'(a,es17.8e3)') " identity_res = ", + $ identity_residual + WRITE(u_log,'(a)') " (identity_res is the Fourier"// + $ " truncation error; Cpar should match dst2.)" + WRITE(u_log,'(a,es17.8e3)') " dW_p(recon3) = ", + $ dw_raw3 + WRITE(u_log,'(a,es17.8e3)') " dW_p(recon3-normalize)= ", + $ dw_dcon3 + WRITE(u_log,'(a,a,a,es17.8e3)') " GPEC ep(", TRIM(smode), + $ ") = ", ep_selected + WRITE(u_log,'(a,a,a,es17.8e3)') " DCON ep(", TRIM(smode), + $ ") = ", ep_dcon + WRITE(u_log,'(a,2es17.8e3)') " recon psi range = ", + $ psi_lo, psi_hi + WRITE(u_log,'(a,2es17.8e3)') " DCON psi range = ", + $ psilow, psilim + WRITE(u_log,*) + CLOSE(u_log) + ENDIF + + DEALLOCATE(psi_int_pts, A_vals, B_vals, Iperp_vals, Cpar_vals, + $ epf_vals, dst1_vals, dst3_vals) + CALL gpeq_dealloc + IF (recon_out) THEN + CLOSE(usurf) + CLOSE(u_int) + ENDIF +c----------------------------------------------------------------------- +c terminate. +c----------------------------------------------------------------------- + RETURN + END SUBROUTINE gpout_recon3 + END MODULE gpout_mod diff --git a/input/gpec.in b/input/gpec.in index 7d36414d7..a4b43aff5 100644 --- a/input/gpec.in +++ b/input/gpec.in @@ -89,6 +89,14 @@ bin_2d_flag=f ! Write 2D binary outputs for use with xdraw verbose=t ! Print run log to terminal + + effective_field_flag=f ! Calculate each contribution of destabilizing term and effective perturbed field [Bernstein, Handbook of Plasma Physics, 1983]. + standard_form_flag=f ! Calculate the first-form plasma energy terms of Eq. (3.1) in docs/tex/recon/main.tex with the gamma|div xi|^2 term omitted. + effective_field_terms_flag=f ! Bernstein |C|^2 decomposition: |C|^2 = |Q|^2 + 2Re(V*.Q) + |V|^2, reported as A, B, Cpar, Iperp with the identity check epf = A+B+Cpar+Iperp. + curl_check_flag=f ! Under effective_field_flag, verify (curl C).grad(psi)=0 and write gpec_recon_cveri_sol*.out. + recon_out=t ! Under effective_field_flag/standard_form_flag/effective_field_terms_flag, write recon diagnostic files and gpec.log entries. If false, keep terminal summary only. + recon_int="spline" ! Radial quadrature of all three routes on the psifac grid: "spline" (default) or "trapezoid". The integrated psi range is printed beside the DCON psilow-psilim range. + ! effective_field_flag, standard_form_flag and effective_field_terms_flag require mode_flag=t (choose mode) with coil_flag, data_flag, harmonic_flag and reg_flag all f; GPEC stops at input otherwise. / &GPEC_DIAGNOSE timeit=f ! Print timer splits for major subroutines diff --git a/input/match.in b/input/match.in index e4b9e15ea..c35d54f61 100644 --- a/input/match.in +++ b/input/match.in @@ -5,6 +5,8 @@ ideal_flag=t contour_flag=t + sol_num=1 !DCON eigenfunction index for MATCH (1 = most unstable); sol_num=n>1 writes solutions.bin + res_flag=f ff0=1e6 ff1=1e6 diff --git a/install/DEPENDENCIES.inc b/install/DEPENDENCIES.inc index a7c0d3f46..d842459fa 100644 --- a/install/DEPENDENCIES.inc +++ b/install/DEPENDENCIES.inc @@ -197,7 +197,7 @@ ../gpec/gpec.o: ../coil/coil.o ../gpec/gpdiag.o ../gpec/gpout.o ../gpec/rdcon.o ../gpec/gpeq.o: ../gpec/idcon.o ../gpec/gpglobal.o: ../equil/bicube.o ../equil/fspline.o ../equil/local.o -../gpec/gpout.o: ../coil/field.o ../gpec/gpdiag.o ../gpec/gpresp.o ../gpec/gpvacuum.o ../gpec/idcon.o ../gpec/version.inc ../pentrc/inputs.o ../pentrc/pentrc_interface.o ../pentrc/utilities.o ../slayer/gslayer.o +../gpec/gpout.o: ../coil/field.o ../gpec/gpdiag.o ../gpec/gpglobal.o ../gpec/gpresp.o ../gpec/gpvacuum.o ../gpec/idcon.o ../gpec/version.inc ../pentrc/inputs.o ../pentrc/pentrc_interface.o ../pentrc/utilities.o ../slayer/gslayer.o ../gpec/gpresp.o: ../gpec/gpeq.o ../gpec/gpvacuum.o: ../gpec/gpeq.o ../vacuum/vacuum_ma.o ../gpec/idcon.o: ../equil/equil.o ../gpec/gpglobal.o ../gpec/ismath.o ../vacuum/vacuum_ma.o diff --git a/match/ideal.f b/match/ideal.f index deaaa3db3..033d3ad5b 100644 --- a/match/ideal.f +++ b/match/ideal.f @@ -305,21 +305,34 @@ END SUBROUTINE ideal_transform c----------------------------------------------------------------------- c declarations. c----------------------------------------------------------------------- - SUBROUTINE ideal_build + SUBROUTINE ideal_build(sol_num) + + INTEGER, INTENT(IN) :: sol_num INTEGER :: istep,ifix,jfix,kfix,ieq,info INTEGER, DIMENSION(mpert) :: ipiv COMPLEX(r8), DIMENSION(mpert) :: uedge,temp1 COMPLEX(r8), DIMENSION(mpert,mpert) :: temp2 + CHARACTER(128) :: message c----------------------------------------------------------------------- c construct uedge. c----------------------------------------------------------------------- WRITE(*,*)"Construct ideal eigenfunctions." IF(ripple_flag)THEN + IF(mripple < mlow .OR. mripple > mhigh)THEN + WRITE(message,'(a,i6,a,i6,a,i6)')"mripple = ",mripple, + $ " out of range ",mlow," to ",mhigh + CALL program_stop(message) + ENDIF uedge=0 uedge(mripple-mlow+1)=1 ELSE - uedge=wt(:,1) + IF(sol_num < 1 .OR. sol_num > mpert)THEN + WRITE(message,'(a,i6,a,i6)')"sol_num = ",sol_num, + $ " out of range 1 to ",mpert + CALL program_stop(message) + ENDIF + uedge=wt(:,sol_num) ENDIF temp2=soltype(mstep)%u(:,1:mpert,1) CALL zgetrf(mpert,mpert,temp2,mpert,ipiv,info) @@ -357,12 +370,15 @@ END SUBROUTINE ideal_build c----------------------------------------------------------------------- c declarations. c----------------------------------------------------------------------- - SUBROUTINE ideal_write + SUBROUTINE ideal_write(sol_num) + + INTEGER, INTENT(IN) :: sol_num LOGICAL, PARAMETER :: diagnose=.FALSE. INTEGER :: ipert,istep,m REAL(r8), DIMENSION(mpert) :: singfac COMPLEX(r8), DIMENSION(mpert,0:mstep) :: b + CHARACTER(128) :: outputname c----------------------------------------------------------------------- c compute normal perturbed magnetic field. c----------------------------------------------------------------------- @@ -375,7 +391,14 @@ SUBROUTINE ideal_write c write binary output for graphs. c----------------------------------------------------------------------- WRITE(*,*)"Write binary output for graphs." - CALL bin_open(bin_unit,"solutions.bin","UNKNOWN","REWIND","none") + + IF (sol_num == 1) THEN + outputname = "solutions.bin" + ELSE + WRITE(outputname, '(A,I0,A)') "solutions", sol_num, ".bin" + END IF + + CALL bin_open(bin_unit, outputname, "UNKNOWN", "REWIND", "none") DO ipert=1,mpert DO istep=0,mstep WRITE(bin_unit)REAL(psifac(istep),4),REAL(rho(istep),4), diff --git a/match/match.f b/match/match.f index e73e03f4c..5654ee673 100644 --- a/match/match.f +++ b/match/match.f @@ -29,7 +29,7 @@ PROGRAM match_main $ big,small,ns,root,scan_flag,root_flag,contour_flag,full, $ z_level_type,phi_level,transform_flag,poly_form_type, $ poly_test_type,scan0,scan1,nscan,delta0,delta1,condense, - $ ripple_flag,mripple + $ ripple_flag,mripple,sol_num c----------------------------------------------------------------------- c format statements. c----------------------------------------------------------------------- @@ -51,12 +51,18 @@ PROGRAM match_main CALL timer(0,out_unit) CALL ideal_read(filename) c----------------------------------------------------------------------- -c reconstruct most unstable ideal solution. +c reconstruct the ideal solution selected by sol_num. c----------------------------------------------------------------------- IF(ideal_flag)THEN CALL ideal_transform - CALL ideal_build - CALL ideal_write + CALL ideal_build(sol_num) +c ripple_flag builds a unit-vector uedge and ignores sol_num, so +c keep the default output name rather than a sol_num-labeled one. + IF(ripple_flag)THEN + CALL ideal_write(1) + ELSE + CALL ideal_write(sol_num) + ENDIF CALL ideal_chord IF(contour_flag)CALL ideal_contour ENDIF diff --git a/match/match_mod.f b/match/match_mod.f index 501e91bcc..5ff8d20c9 100644 --- a/match/match_mod.f +++ b/match/match_mod.f @@ -42,6 +42,7 @@ MODULE match_mod $ kin_flag=.FALSE.,con_flag=.FALSE. LOGICAL, DIMENSION(:), POINTER :: sing_flag INTEGER :: mfix,mhigh,mlow,mpert,mstep,nn,msing,mmatch,mbit,mterm + INTEGER :: sol_num = 1 INTEGER, DIMENSION(:), POINTER :: fixstep REAL(r8) :: sfac0=1 REAL(r8), DIMENSION(:), POINTER :: psifac,rho,q