Skip to content

GLM divergence cleaning ​

IDGroupMilestoneBackendsFigureRegression
MV-3, MV-10Ideal MHDM2 (#41); the MV-10 monitor M6 (#68, G1)CPU, FNLyesglm-pulse

Case and scope ​

MV-3, the GLM pulse. A Gaussian pulse in Bx along x on a fluid at rest is a pure divergence error. In 1-D the pair (Bx,ψ) of mixed GLM then obeys a (damped) telegraph equation decoupled from the fluid,

∂tBx+∂xψ=0,∂tψ+ch2∂xBx=−ch2cp2ψ,

with an exact solution: the error splits into two pulses travelling at ±ch and is damped at the rate ch2/cp2=αch/L (α = glm_alpha, L = glm_damping_length). The oracle computes it mode by mode (d'Alembert without damping).

What MV-3 catches:

  • a wrong GLM block in the face flux: L1 of Bx and ψ/ch against the exact solution under a bound, and the observed order;
  • a wrong damping source or rate: a wrong rate is an O(1) relative error (1.23 with no damping at N=128, against 9.2⋅10−5), and the min-cell damping length is checked on its own;
  • ch missing from the time step: ch=2 is above the fastest wave (1.29), so it sets Δt, and glm_ch_check = error must not trip;
  • a wrong wall parity of ψ: reflecting walls (Bx odd, ψ even);
  • a leaking none path: without cleaning Bx must stay frozen, bit for bit.

MV-10, the Dedner peak. A peak in Bx on a uniform magnetised flow (Dedner et al. 2002, section 5.1) has a non-zero ∇⋅B from t=0. Without cleaning the error is only advected; GLM transports it away at ch and damps it, so ∥∇⋅B∥1 must decay, on a uniform grid and across 2:1 AMR seams. The leg also checks the div(B) history, the derived output fields and the divb_tol monitor.

Models and numerics ​

ItemChoiceCode
Equationsideal MHD with mixed GLM, γ=5/3models; mhd_glm_flux, mhd_glm_eigenvectors, adam_flume_mhd_library.F90
SpaceWENO5 flux splitting on characteristic variables; the (Bn,ψ) pair is the 2×2 block of speeds ∓chmhd_glm_face_split_fluxes, adam_flume_mhd_library.F90; compute_residuals_weno, adam_flume_cpu_object.F90
Dampingsource −(ch2/cp2)ψ, ch2/cp2=αch/L; L a length or min-cell (the minimum cell size)add_glm_damping, adam_flume_cpu_mhd_glm_kernels.F90; set_glm_damping, adam_flume_mhd_object.F90
ch checkglm_ch_check = error (MV-3, MV-10)check_glm_ch, adam_flume_cpu_object.F90
div(B) history and monitormax, L1 and seam-local max of ∇⋅B (host FD, null directions weighted zero); divb_tol on the normalised Dflux, maxh|Dflux|/max|B|compute_divb_history, compute_dflux_norms, adam_flume_cpu_object.F90; save_divb_row, adam_flume_diagnostics_object.F90
TimeSSP Runge–Kutta, 3 stages, CFL 0.5adam_rk_object.F90
AMR (MV-10)2:1 seams: restriction, tricubic ghost fill, refluxinterp_seam_ghost, adam_field_object.F90; numerics
Boundariesperiodic; reflecting walls (wall-inviscid: Bx odd, ψ even) in the wall legadam_flume_bc_object.F90; boundary conditions
FNL backendthe same kernels on the deviceadam_flume_fnl_object.F90, adam_flume_fnl_mhd_glm_kernels.F90

Initial and boundary conditions ​

MV-3 (mhd/glm-pulse):

ItemValue
Domain[0,1] along x; y and z null
Gridthe Sod grid of V1, 4 blocks along x, blocks of N/4×2×2 cells
Ranks2
Initial conditionglm-pulse: background (ρ,u,v,w,p)=(1,0,0,0,1), B=0; pulse in Bx at x=0.5, width 0.1, amplitude 0.1
Cleaningch=2
LegNglm_alphaglm_damping_lengthx boundariest
d'Alembert64, 128, 25601.0periodic0.3 (the pulses cross the boundary)
damped64, 128, 2560.50.1 (ch2/cp2=10)periodic0.3
min-cell1280.1 (rate 25.6)min-cellperiodic0.3
walls12801.0wall-inviscid (both pulses reflected once)0.4
none128——periodic0.3

MV-10 (mhd/divb-peak):

ItemValue
Domain[−0.5,1.5]2, z null, periodic
Gridthe periodic grid of V3 (octree) at level 2: 642 cells, 162 per block, nk = 4 (the 2:1 seam fill needs 4 cells on every axis, null ones included)
AMR leg82 cells per base block (322), the blocks of [−0.5,0.5]2 refined once more (a 2:1 seam the peak crosses near t=0.375)
Ranks2
Initial conditiondivb-peak: ρ=1, u=v=1, w=0, p=6, B=(Bx,0,1/4π), Bx+=(1−(r/R)2)2/4π, R=1/8, at the origin
CleaningGLM, ch=5, glm_alpha = 0.4, damping length min-cell; and none
Final timet=0.5; the monitor legs 5 steps

Input files ​

FileRole
src/tests/flume/verification/mhd/glm-pulse/make_glm_pulse.pywrites the MV-3 input from sod/sod-x.ini
src/tests/flume/verification/mhd/glm-pulse/check.shruns the MV-3 legs (--np N, --divergence-control glm|eglm, --legs LIST: mv3-dalembert, mv3-damped, mv3-mincell, mv3-walls, mv3-none, the last GLM-only)
src/tests/flume/verification/mhd/glm-pulse/glm_pulse_oracle.pythe exact telegraph solution, the L1 bounds, the order, ∫Bx, the frozen none run
src/tests/flume/verification/mhd/divb-peak/make_divb_peak.pywrites the MV-10 input from conservation/amr-periodic.ini
src/tests/flume/verification/mhd/divb-peak/check.shruns the MV-10 legs and the monitor legs (--np N, --legs LIST: mv10-uniform, mv10-amr, mv10-monitor for the monitor runs)
src/tests/flume/verification/mhd/divb-peak/divb_peak_oracle.pythe decay, ∫B, max|ψ|, the derived fields, the seam column
src/tests/flume/verification/make_doc_figures.py fig_glm_pulsedraws glm-pulse.png from <runs>/glm-pulse (--only glm-pulse)
bash
cd src/tests/flume/verification/mhd
glm-pulse/check.sh                                                     # MV-3, CPU
FLUME_EXE=$PWD/../../../../../exe/adam_flume_fnl glm-pulse/check.sh    # MV-3, FNL
divb-peak/check.sh                                                     # MV-10

make_doc_runs.sh <runs> mhd writes the figure run (<runs>/glm-pulse: 128 cells, GLM, amplitude 0.1); make_doc_figures.py --runs <runs> draws it.

Results ​

GLM pulse

The undamped case (glm_alpha = 0, periodic, 128 cells, ch=2, t=0.3): Bx and ψ of FLUME on the exact solution, at t=0 and at the final time.

MV-3 (bounds: CPU baseline M2-P4a + 2%):

CheckBoundCPUFNL
d'Alembert, L1 of Bx and ψ/ch, N=64/128/2561.584⋅10−4 / 9.342⋅10−6 / 7.487⋅10−71.553⋅10−4 / 9.159⋅10−6 / 7.340⋅10−7pass
damped, L1, N=64/128/2563.393⋅10−5 / 1.962⋅10−6 / 1.572⋅10−73.327⋅10−5 / 1.923⋅10−6 / 1.541⋅10−7pass
observed order, both ladders≥3.0 (SSP-33 in time)4.1 and 3.6 (WENO5 in space still visible)pass
min-cell, L11.487⋅10−71.458⋅10−7pass
walls, L11.364⋅10−51.337⋅10−5pass
∫Bx drift (d'Alembert)10−14 (∫Bx≈1.8⋅10−2)≤5.8⋅10−16pass
none: Bx last against first checkpointbitwisebitwisebitwise

MV-10 (CPU baselines M2-P4b):

CheckBoundCPUFNL
GLM final / initial |∇⋅B|1, uniform 642 (never above the initial value)0.10.057pass
GLM / none final |∇⋅B|10.10.027pass
AMR: GLM final / initial0.20.091pass
∫Bx, ∫By, ∫Bz drift (∫Bz=1.13)10−11≤8.3⋅10−13pass
max|ψ|0.141 (chA/10)3.6⋅10−3 uniform, 1.3⋅10−2 AMRpass
saved pt, beta, bmag against their definitions; saved max|∇⋅B| against the history10−12exactpass
AMR seam column: zero at step 0, never above the global maximum, positive at the end—passpass
monitor, divb_tol = 0.01, divb_error = .false. (the peak starts near 0.2)warnings, run completespasspass
monitor, divb_error = .true.run stops with its messagepasspass
divb_tol > 0 without [diagnostics] divb_fluxrefused at initialisationpasspass

FNL ran every leg (work directories of both backends); the tables record the CPU values, and the backends agree to the printed digits.

Related cases: EGLM cleaning runs MV-3 under EGLM (EV-2); Orszag–Tang and field loop measure the seam div(B) of shocked and smooth flows; the seam div(B) studies of M6 are in divergence across seams.

Findings ​

  • The observed order is that of SSP-33 in time with the WENO5 spatial error still visible (4.1 and 3.6), above the asserted 3.0.
  • The ∥∇⋅B∥1 of the Dedner peak is not monotone under GLM: a few-percent transient while the peak spreads into outgoing waves. The leg therefore asserts "never above the initial value" and the final ratio, not a monotone decay.
  • On AMR the final ratio doubles (0.091 against 0.057): the 2:1 seam injects div(B) (the seam source of #29); GLM keeps it bounded.
  • Since #68 G1 the divb_tol monitor acts on the normalised Dflux, which needs [diagnostics] divb_flux and GLM; a divb_tol without divb_flux is refused at initialisation instead of monitoring nothing.