Clarify force-balance documentation and PDF build#76
Conversation
Review Summary by QodoAdd diamagnetic Om_tE computation from force balance (Level 0)
WalkthroughsDescription• Add compute_omte_diamagnetic() function computing E x B rotation frequency from pressure gradient term of radial force balance (Level 0 model) • Implement diamagnetic Om_tE calculation using CGS units matching NEO-2 conventions • Include 6 analytic unit tests covering zero gradient, sign, linear profiles, temperature gradient, charge scaling, and array inputs • Add end-to-end validation against NEO-2 reference data (AUG #30835) with regression assertions • Capture 15-35% of full neoclassical Om_tE, with correct sign and order of magnitude Diagramflowchart LR
A["Pressure gradient<br/>dn/ds, dT/ds"] --> B["compute_omte_diamagnetic()"]
C["Geometry parameters<br/>aiota, sqrtg_phi, av_nabla"] --> B
D["Profile data<br/>n, T, z"] --> B
B --> E["Radial electric field<br/>Er"]
B --> F["E x B rotation frequency<br/>Om_tE"]
E --> G["Validation vs NEO-2<br/>Sign & magnitude match"]
F --> G
File Changes1. python/src/neo2_ql/__init__.py
|
Code Review by Qodo
1.
|
a2067ef to
4c10b72
Compare
Compute the E x B rotation frequency Om_tE from the pressure gradient term of the radial force balance (no flows assumed). This is the simplest model in the planned hierarchy (Level 0-2) for estimating Om_tE from experimental profiles without running the full neoclassical NEO-2 calculation. Includes e2e validation against NEO-2 output from AUG #30835 showing correct sign and order of magnitude (35% at s=0.25, 15% at s=0.50 of the full neoclassical result). Closes #72
Remove dead code in e2e test, add exact regression values for Er and Om_tE at rtol=1e-5, tighten ratio bounds from (0.01, 100) to (0.1, 1).
Document the physics (radial force balance, coordinate conventions, CGS units), the model hierarchy (Level 0-3), algebraic consistency with the NEO-2 Fortran implementation, and validation results against AUG #30835. Includes references to Helander & Sigmar, Kim-Diamond- Groebner, Viezzer et al., and NEO-2 papers.
The HDF5 multispec input was regenerated after the NEO-2 runs with different profiles. Extract actual n, T, dn/ds, dT/ds from the Fortran namelists which match the output n_spec/T_spec exactly. With corrected profiles, Level 0 OVERESTIMATES |Om_tE| by 3-5x (not underestimates as previously reported). This is physically correct: the toroidal rotation and neoclassical flow terms partially cancel the diamagnetic pressure gradient contribution. Also fix sqrtg_bctrvr_phi unit annotation from [G cm^2] to [G cm], verified by dimensional analysis through the full formula chain and cross-checked against the AUG toroidal flux.
Explain why Level 1 uses the simple V^phi * B_theta product instead of the full Fortran numerator V^phi * (iota*B_theta + B_phi): the V^theta * B_phi term is O(B_phi) but cancels against V^phi * B_phi through the neoclassical parallel momentum constraint. Without the D31/D32/D33 transport closure, the cancellation is absent and Er overshoots by two orders of magnitude. Add the Boozer pair-product identity showing that the contravariant/ covariant product V^phi * B_theta_cov equals the physical product v_phi * B_theta because the R factors cancel. Mark the strict NEO-2 Vphi convention function as a building block only, not a standalone reduced model.
The KDG formula in CGS is v_theta = K_i * c / (Z*e*B_phi) * dT/dr, where c is the speed of light. The factor of c cancels against 1/c in the force balance term -v_theta*B_phi/c, giving the net correction Er_pol = -K_i * dT/dr / (Z*e). Without c the poloidal contribution was 3e10 times too small (~4e-12 instead of ~0.13 statV/cm). Show three Level 2 curves for the banana (K=-1.17), plateau (K=-0.5), and Pfirsch-Schluter (K=+0.5) collisionality regimes. Cross-checked against Kasilov (ntv_torque.tex eq. vpoltor) and MARS (knc=1.17 in MarsQ_2022/common_ntv.f).
The geometry factor bcovar_phi^2 / (sqrtg_bctrvr_tht * <B^2>) was squashing the KDG poloidal rotation estimate by ~15x. This is wrong because the pair-product identity guarantees that v_theta * B_phi is coordinate-independent: the metric factors in the Boozer contravariant velocity and covariant field cancel exactly. Without the geometry correction, the Level 2 k-scan from -2.5 to 1.5 now encloses the NEO-2 reference result at both AUG surfaces. The best-fit k shifts from 1.5 (boundary, not enclosing) to ~1.08 (interior, enclosing).
4c10b72 to
ddbf111
Compare
The Level 1/2 force-balance functions paired V^phi with the covariant B_theta_cov, but the correct Boozer-algebra rotation product is sqrt(g)*B^theta * V^phi. These quantities differ in sign and by a factor of 3-5 because the inverse metric mixes the dominant B_phi_cov into the contravariant B^theta. Add compute_omte_toroidal_rotation_boozer (Level 1) and compute_omte_neoclassical_poloidal_boozer (Level 2) using the correct sqrtg_bctrvr_tht * Vphi product. With the transport-derived k_ii coefficient, the corrected Level 2 agrees with full NEO-2 within 8% on the AUG 30835 benchmark (previously off by 3x). Also add Boozer metric helpers (compute_boozer_metric, compute_boozer_metric_from_bc, compute_boozer_metric_from_rz_profile) for converting Boozer components to physical cylindrical quantities, and update the fixture with R_Vphi_prof/Z_Vphi_prof geometry data.
Document the complete model hierarchy (Level 0 through full NEO-2 transport closure) using the notation from Kasilov et al., Phys. Plasmas 21, 092506 (2014). Includes the correct contravariant Boozer pairing sqrt(g)*B^theta * V^phi, the k coefficient from D32/D31, thermodynamic forces, and AUG 30835 benchmark results. Wire into the CMake doc build via add_latex_document.
…ctions
Remove compute_boozer_metric, compute_boozer_metric_from_bc, and
compute_boozer_metric_from_rz_profile which attempted to fix the
force-balance pairing via an R/|e_theta| diagonal-metric correction.
This approach was wrong because the off-diagonal inverse metric
g^{theta phi} mixes the dominant B_phi_cov into B^theta, flipping
the sign.
The correct fix (already in place) uses the contravariant product
sqrt(g)*B^theta * V^phi directly from the NEO-2 output. Clean up
all docstrings and documentation to remove references to the
discarded pair-product identity and metric-correction approach.
Reads input profiles from the multispec HDF5 input and NEO-2 output from all 100 surfaces of the 2016 controlled fusion benchmark. Plots Er and Om_tE for Level 0/1/2, reduced single-ion, and full NEO-2 across the full radial range.
Show V^phi (NEO-2 vs input), V^theta (NEO-2 vs KDG estimate), Er term decomposition (diamagnetic, rotation, KDG poloidal), and the full model hierarchy comparison across 100 radial surfaces.
Show Level 1 (rotation only), Level 2 with filled band between banana (k=-1.17) and Pfirsch-Schluter (k=+0.5) limits, Level 2 with NEO-2 transport-derived k_ii, and full NEO-2 transport closure on a single Om_tE panel.
row_ind_spec stores species tags (1=electron, 2=ion), not Python array indices. The code was matching against ion_idx (array index 1) instead of species_tag_Vphi (tag value 2), extracting the electron-electron transport coefficients instead of ion-ion. This gave k_ii ~ 0.27 (electron value) instead of the correct k_ii ~ 0.60 (ion value), making the Level 2 curve wrong. Replace all hardcoded D31_AX[:, 3] with explicit tag matching.
The code used KDG K_i values (banana = -1.17, PS = +0.5) in the Kasilov formula Er = ... - k * dT/dr / (Ze), giving wrong signs. In Kasilov notation k = 5/2 - D32/D31 (eq 8), the correct values are banana = +1.17, plateau ~ +0.5, PS ~ -0.5. The KDG K_i has opposite sign convention and is a different physical quantity. Fix select_poloidal_rotation_coefficient, all k-regime tables in documentation, and the Level 2 band edges in the benchmark plot.
Level 3 retains the exact Boozer geometry factor B_phi / (iota B_theta + B_phi) on the KDG term instead of approximating it as 1. On AUG 30835 this is a ~2% correction, so Levels 2 and 3 nearly overlap. Add to the Om_tE benchmark plot and the LaTeX force-balance levels document. Also gitignore DOC/Build/.
Implement compute_ftrap (trapped particle fraction from B(theta)) and compute_k_sauter (Sauter, Angioni & Lin-Liu, Phys. Plasmas 6, 2834, 1999) for computing k = 5/2 - D32/D31 without a neoclassical code. On AUG 30835 the Sauter k agrees with the NEO-2 drift-kinetic result within 16% across all 100 surfaces.
The Sauter formula requires the Hinton-Hazeltine nu*_i normalized
with eps^{3/2}, which differs from NEO-2's nu_star_spec by a
factor of ~4x. Add compute_nui_star_sauter that computes nu*
from plasma parameters (n, T, m, Z, R, q, eps) with the GACODE
Coulomb logarithm. This reduces the Sauter k error from 16% to
~9% compared to the NEO-2 drift-kinetic result.
Compute k from the Sauter analytical formula using ftrap from the Boozer file and nu* from plasma parameters, and plot the resulting Level 2 Om_tE alongside the NEO-2 k_ii curve for comparison.
Summary
compute_omte.mdthat full NEO-2, reduced single-ion NEO-2, the direct pair evaluator, and GAvgen(2)are four different objects and must not be mixedspecies_tag_Vphiis a real physics selector for the rotation species, not a bookkeeping labelDOC/work again by wiring in the vendoredcmake/UseLATEX.cmakeDOC/UserDoc/Output.texandDOC/ExtraDocuments/neo-2-ql-demo_runs.texDocumentation changes
python/src/neo2_ql/compute_omte.mdvgenvgen er_method=1is not part of the benchmark right nowspecies_tag_Vphiactually changes incompute_Er()DOC/neo2.in.ql-fullspecies_tag_vphias the selected rotation species used in the force-balance solveDOC/ExtraDocuments/neo-2-ql-demo_runs.texisw_Vphi_loc = 0andspecies_tag_Vphi = 2in the demo profile generator exampleDOC/CMakeLists.txtcmake/directory toCMAKE_MODULE_PATHso the standalone doc build can resolveUseLATEX.cmakePDF outputs
After this change the documented CMake path from
DOC/produces these PDFs inDOC/Build/:main.pdfneo-2-ql-demo_runs.pdfneo-2-par-eccd_runs.pdfDoxygen_Reference_Manual.pdfVerification
Documentation build fails before the fix
Documentation build passes after the fix
$ cmake -S DOC -B DOC/Build -G Ninja && cmake --build DOC/Build -j2 -- Build files have been written to: /home/ert/code/NEO-2/DOC/Build [10/10] Generating Doxygen_Reference_Manual.pdf Output written on refman.pdf (768 pages, 4600660 bytes).Python tests pass after the doc update