Skip to content

Delta-f variational MHD: second-order, stable time stepping - #906

Merged
spossann merged 42 commits into
develfrom
variational-delta-f-time-stepping
Nov 10, 2026
Merged

spossann merged 42 commits into
develfrom
variational-delta-f-time-stepping

Conversation

@nicolominichelli

@nicolominichelli nicolominichelli commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Follows #900 (merged).

Summary

Second-order and stable time stepping of the perturbative (delta-f) variational MHD model VariationalPerturbativeMHD. The changes came out of a comparison with PerturbativeMHDWithQ (exact nonlinear slab solutions, screw pinch). Each change keeps the discrete equations of the variational principle and the exact energy conservation; only the grouping of terms into substeps and the nonlinear solvers change.

  1. Sound step with the total pressure as transport weight (VariationalLinearSoundEvolve(total_weight=True), set by the perturbative model). The pressure perturbation is transported with p0 + p~^{n+1/2} in the implicit sound step, instead of p0 there plus a separate transport with weight p~ in the nonlinear substep. The pressure enters the energy only linearly (int p_hat / (gamma-1)), so the separate substep had no force: the nonlinear part of the sound coupling was split across two substeps, i.e. explicit. It was unstable for the grid-scale sound waves (omega dt >~ 2) as soon as p~ is a finite fraction of p0 (growth x1.1 per step at 5 %, x1.25 at 10 %, and the blow-up with a constant offset p0 -> p0 + pc, p~ -> p~ - pc). Now the step depends only on the total pressure. Picard iteration on the weight, one Schur solve per iteration (4-6 iterations).
  2. VariationalNonlinearPBEvolve keeps only the magnetic terms and is solved by a Picard iteration on the weight b^{n+1/2} with one SPD solve of (M_rho + dt^2/4 K) du = ... per iteration. The previous Picard iteration on all of (u, b) was an explicit fixed point on the wave operator and diverged for dt k_max |b~| >~ 2 (measured contraction (dt |b~| k_max / 2)^2); it also stopped after one iteration when b~ = 0 (explicit Euler for p~).
  3. VariationalDensityEvolve: midpoint density weight (Options(weights=...), default "midpoint" for model="perturbative", "frozen" otherwise): the frozen weight made the step non-symmetric and the model first order in time. New option jacobian_solver ("pbicgstab" default, "rpbicgstab", "gmres"); a breakdown of the Newton linear solve (BiCGStab at flow CFL numbers dt |u| k_max ~ 10) now raises an error instead of continuing with NaN.

Evidence

Scripts and data: delta_f_docs/var_vs_q/data (not in the repo).

  • Slab, exact nonlinear solutions (circularly polarized Alfven wave 10 %/20 %, simple sound wave 10 %/30 %): second order on all dt ladders (dt = 0.2 ... 0.006), energy exact to <= 3e-10. Before: sound wave first order; Alfven wave failed for dt >= 0.1 (10 %) and dt >= 0.025 (20 %).
  • Seeded stability (grid noise 1e-8): uniform p~ of +-10 %, +20 %, and offsets pc = 0.5, 1.0 up to dt = 0.2: stable (before: blow-up after 30-60 steps).
  • Screw pinch (R0 = 20, 12x12x4): stage pc = 0 / 0.1 / 1.0 gives the same velocity to 3e-11 (before: 0.9 % at pc = 0.1, blow-up at pc = 1); agreement with the previous scheme where it was stable (5e-4); finite b~ (10 %, 20 %): same evolution as PerturbativeMHDWithQ; long run at b~ 20 %, dt = 0.2 (4x the q time step): bounded |u| and b~ as with q and energy to 3e-12 for 28 steps, then the density Newton solve broke down at |u| ~ 1.7 (flow CFL ~10, see Open; q at dt = 0.05 itself lost energy at the 1e-4 level and failed at t = 39).
  • Vanishing edge pressure (p~ proportional to p0, p0 = 1e-8 at the edge, 10 %/20 %, positive total pressure): same evolution as PerturbativeMHDWithQ (max |u| agrees to 3 digits), energy exact to 1e-12.
  • Cost (pinch): +1 to +5 % per time step (the sound step is 2-4x more expensive, the nonlinear substep cheaper; both are 11-15 % of the step).

Tests

  • New src/struphy/propagators/tests/test_variational_sound_total_weight.py: the sound step with total weight is symmetric in time and conserves the energy; the Strang group at a large time step gives the same velocity for p0 + pc, p~ - pc as for pc = 0 (< 1e-7; 1.39 without the total weight).
  • test_variational_nonlinear_pb_evolve.py updated (no pressure in the nonlinear substep).
  • All variational propagator tests pass on 1 and 2 ranks, as do the variational model regression tests.
  • Baselines VariationalPerturbativeMHD-u_space=Hdiv and VariationalPerturbativeMHD (default, u in H1vec) updated (1 and 2 ranks); in both the merged sound step changes the second-order diagnostic bt2 by 7e-3 and div_u by 7e-4 (relative), the splitting difference between two second-order schemes for the same equations (verified: switching the total weight off removes the nonlinear pressure transport and changes it by 10 %).

Open

  • VariationalDensityEvolve: at flow CFL numbers of order ten the BiCGStab solve of the Newton system breaks down (now an error); unpreconditioned GMRES works but needs many iterations in 3D.
  • Inner-wall mode of the linear model (Pi^1, coarse radial grid), unchanged.

🤖 Generated with Claude Code

nicolominichelli and others added 30 commits November 4, 2026 17:04
HdivBracketOperator: bracket of the variational momentum advection for a velocity
2-form, with the vector fields acting on the auxiliary spline space W = S_p^{p-2}
(double knots), which contains all components of the 2-forms. The bracket
B = Pi^T K^T - K Pi is skew-symmetric for any momentum (exact conservation of the
kinetic energy) and applied matrix-free with the quadrature of the mass matrices.

- Derham(multiplicity=...) builds the auxiliary sequence with double knots
  (without commuting projectors)
- DoubleKnotSpace: evaluation, integration and the mass matrix M^W[sqrt g] of W
  (matrix-free: the feectools stencil kernels need shifts 1), with a Kronecker
  preconditioner scaled by the diagonal
- VariationalMomentumAdvection: velocity in Hdiv with the density-weighted mass
  matrix of VelocityMass, momentum of the bracket at t^n (one linear solve) or at
  t^{n+1/2} (Picard or Newton, option bracket_momentum)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
M^W[sqrt g] is banded (|a_d - b_d| <= p_d); its entries are extracted once from
the matrix-free product with colored probe vectors and stored per owned row. The
product is the new struphy kernel banded_matvec_3d (feectools unchanged). One
product is 2.7x faster; on the HollowTorus (16x16x8, p=3) the bracket is 1.9x and
the momentum advection step 1.7x faster.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- VariationalDensityEvolve (model="perturbative"), VariationalPBEvolve ("perturbative") and
  VariationalViscosity ("full_p") with a velocity 2-form: 2-form mass matrix of the total density
  (HdivDensityOperators / VelocityMass), transport operators with u_space="Hdiv", kinetic energy
  evaluator with u_space="Hdiv"; the viscous heating goes into the pressure (exact energy balance)
- VariationalPerturbativeMHD(u_space="Hdiv") for the nonlinear model; kinetic energy with the mass
  matrix of the total density; the default parameter file sets degree (2, 2, 1)
- schur_precond="StiffnessPreconditioner" (built from the equilibrium) also for model="perturbative"
- DoubleKnotSpace: simple knots in periodic directions with one element (reduced directions); per
  direction multiplicity in Derham
- tests: energy conservation (Cuboid, HollowTorus), viscous energy balance, agreement with H1vec under
  refinement; regression baseline VariationalPerturbativeMHD-u_space=Hdiv

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the projection on the auxiliary space W of double knots by the
auxiliary space W' = V0 + Z + Z/sqrt(g), which is never built: the hat map
is the identity on the velocities and, after an integration by parts,

    B[y] = D^T N[y/sqrt g] - N^T D - R[curl y/sqrt g]

with standard weighted mass matrices (no mapping derivatives, pointwise
antisymmetric integrand, so energy is conserved with inexact quadrature).
The momentum derivative for the Newton iteration is applied matrix-free.

Same convergence of the bracket (about 4th order), but about 8.5x faster
(HollowTorus 16x16x8, p=3: 14.6 s vs 125 s per step with the momentum at
t^n; with the momentum at t^{n+1/2} the double-knot version did not finish
3 steps in 3000 s).

Removes DoubleKnotSpace, the banded_matvec_3d kernel and the knot
multiplicity in Derham.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: the double-knot changes of this branch (Derham multiplicity per
direction, DoubleKnotSpace in reduced directions) are dropped with the
double-knot space; the H(div) support of KineticEnergyEvaluator is kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…m representer

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test relied on basis_ops left over from an earlier test and failed when
run alone (or first, as selected by testmon).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rrent of the dual complex in VariationalPBEvolve

The terms of model="perturbative" with the perturbation as weight now use the midpoint values b^{n+1/2}, p^{n+1/2}
(new option weights, default "midpoint"; "frozen" keeps t^n). With weights at t^n the pressure force and the force of
the equilibrium current were explicit, so the sound waves grew for dt omega_s > 1 (one-step map of the R0=20 screw
pinch at dt=0.5: |lambda| up to 44).

- second-order magnetic term as int J0 . A-hat with the current of the dual complex (EquilibriumCurrentTerm, moved
  from VariationalLinearAlfvenEvolve to propagators/utils.py), instead of the weak curl of B0
- pressure force in weak form, int p^w div v, with the lumped update of p-hat (WeakPressureTerm, moved from
  VariationalLinearSoundEvolve), as in the linear model
- Newton matrix with the derivatives of the linear terms (sound waves, J0 force), solver rpbicgstab; the pressure is
  recomputed from the current velocity in every iteration
- VariationalPerturbativeMHD: diagnostic at1 also for the nonlinear model, en_mag_2 = int J0 . A-hat
- test of the large-dt sound wave (frozen grows, midpoint bounded); stale assertion in test_hdiv_needs_linear_cn
  (the nonlinear p model accepts u_space="Hdiv" since #892); regression baseline updated

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ar Lagrangian, no auxiliary potential

The second-order terms of the linear and the perturbative (delta-f) variational MHD models are now the direct
variation of the discrete Lagrangian, with the commuting projections Pi^1/Pi^2 as interior products and the Galerkin
Hodge star, without integration by parts or auxiliary variables:

- magnetic term b0^T M2 b-hat: force T[b^w]^T M2 b0, the adjoint of the transport T[b] = C Pi^1[b x .] of the
  second-order field b-hat (bt2); the auxiliary vector potential at1 (int J0 . A-hat, #884) is removed from
  VariationalPerturbativeMHD and VariationalPerturbativeMHDWithQ;
- pressure term int p-hat / (gamma - 1): force S[p^w]^T l, the adjoint of the transport of p-hat, in the linear
  model as in VariationalPBEvolve (WeakPressureTerm removed);
- the linear model uses the equilibrium transports of VariationalPBEvolve (Hdiv0_transport_operator with b0,
  Pressure_transport_operator with p0), so that the perturbative model reduces to it at small amplitude;
- with midpoint weights the linear Alfven and sound steps solve the (linear) midpoint system by a Newton iteration
  with the exact residual; the pointwise matrix W of int v . (j0 x B) (dual-complex j0) and the weak pressure form
  are used only in the Newton matrices (EquilibriumCurrentTerm reduced to W).

Regression baselines updated: second-order diagnostics bt2/pt3 of the linear CN cases, and the state of the
nonlinear Hdiv and default cases (different second-order forces at finite amplitude).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e for the perturbative p model

Since #892 VariationalPBEvolve builds the Kronecker Schur preconditioner also for model="perturbative" (as an
approximation of the Newton matrix). The perturbative p model moves from test_fallback to test_same_solution
(active, same solution as the mass-matrix preconditioner); test_fallback keeps the split q model.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… linear solve in the linear Alfven step

The force T[b]^T l of the second-order magnetic term (l = -M2 b0, T[b] = C Pi^1[b x .]) is linear in b, but was only
available matrix-free. New TransportAdjointMatrix (feec/variational_utilities.py) assembles it once: with
Pi^1 = (I^1)^{-1} sigma^1 and z = (I^1)^{-T} C^T l, it is a bilinear form in the velocity and 2-form bases integrated
with the degrees of freedom of Pi^1 (point values and the Gauss points of the histopolation cells), weighted by z.
It uses the existing kernel_3d_mat on the projection point sets; each rank takes the cells of its own elements and
the ghost contributions are accumulated as for the mass matrices.

- VariationalLinearAlfvenEvolve: the matrix in the Schur system makes the midpoint step one linear solve again (the
  Newton iteration with W is removed). HollowTorus 16x16x8: 2.2 s/step instead of 8.5 s (4 solves).
- VariationalPBEvolve: the matrix is the exact derivative of the second-order magnetic force in the Newton matrix
  (instead of W).
- EquilibriumCurrentTerm (W, dual-complex j0) removed.
- Docstrings: why the weak pressure form may replace the derivative of the pressure force in the Newton matrices
  (divergence part of S drops for zero normal flux, Pi^3 preserves integrals: equal up to the quadrature of the
  metric factor).
- Tests: TransportAdjointMatrix against the matrix-free operator (Cuboid, HollowCylinder, HollowTorus; H(div) and
  H1vec; serial and 2 ranks), and the one-solve midpoint step against the exact midpoint equations.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…weak form; one solve in the linear sound step

- ProjectionAdjointMatrix (feec/variational_utilities.py): the matrix of f -> P[f]^T y for a basis projection operator
  whose weight is linear in a field, assembled on the projection points (generalizes the assembly of
  TransportAdjointMatrix, which is now a subclass).
- PressureTransportAdjointMatrix: the matrix of p -> S[p]^T l for Pressure_transport_operator,
  P2[p]^T D^T l + (D U)^T P3[p]^T l, without using the weak form or any identity.
- VariationalLinearSoundEvolve (linear_p): the force S[p^w]^T l is this matrix applied to p^w; with midpoint weights
  the step is one exact linear solve (no Newton iteration, no weak form). HollowTorus 16x16x8: 0.24 s/step instead
  of 0.80 s.
- VariationalPBEvolve: the sound block of the Newton matrix is the exact derivative -A S[p0] (instead of the weak
  form). The sign is placed inside the composition, since the outer ScaledLinearOperator absorbs an outer scalar and
  its scalar is overwritten with dt^2/4.
- Test: PressureTransportAdjointMatrix against the matrix-free Pressure_transport_operator (Cuboid, HollowCylinder,
  HollowTorus; H(div), H1vec; serial and 2 ranks).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…agators plus a nonlinear substep

- New model VariationalLinearMHD (linear variational p model): one linear solve per
  Alfven and sound substep with the exact force matrices; the old Picard linear path
  (crank_nicolson=False) is removed.
- VariationalPerturbativeMHD inherits from it: Strang block linear Alfven, linear sound,
  and the new VariationalNonlinearPBEvolve (nonlinear terms, implicit midpoint with
  Picard iteration, conserves kinetic + first-order magnetic energy).
- Example lowmode_tae moved to examples/VariationalLinearMHD; docs, tests and
  regression baselines updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-mhd-midpoint

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rce-matrix

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…atio 0.1998 was at the old bound 0.2)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ham.prepare_eval_tp_fixed

Review of #891 (step A), no change of results:
- QuadratureIntegrator moved unchanged to the new module struphy.feec.quadrature, with a clear argument
  (as in L2Projector.get_dofs). With clear=False the owned data is kept and added after
  exchange_assembly_data, since the ghost regions may hold copies of it after update_ghost_regions.
- Derham.prepare_eval_tp_fixed(..., derivatives=True) (and _get_span_and_basis_for_eval_mpi) return the
  first derivatives of the N- and D-splines, computed in the existing per-point loop
  (b_der_splines_slim; basis_funs_1st_der at degree p - 1 with the D-spline scaling). The default return
  is unchanged. eval_bases_with_derivatives is removed; HdivBracketOperator uses the new option.
- Tests: test_derham.py::test_prepare_eval_tp_fixed_derivatives (p = 1, 2, 3; clamped and periodic),
  replacing the finite-difference test of test_hdiv_bracket.py; test_quadrature.py (H1, L2, Hdiv against
  L2Projector.get_dofs, clear=False accumulation).
- dev-feec.rst documents the new module.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…erification/test_verif_VariationalPerturbativeMHD.py

models/tests/test_variational_perturbative_hdiv.py was not run by CI (models/tests is not collected by folder and the
file was not registered). As a verification file it is collected by folder: the scheduled CI runs all of it, pull
requests run the energy conservation on the Cuboid (marked verif_pr, 1.6 s on 2 ranks) and the whole file when it
changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…or the nonlinear model with u in H(div)

test_hdiv_needs_linear_cn: the nonlinear VariationalPerturbativeMHD accepts u_space="Hdiv" (only the q model and
the linear models without crank_nicolson reject it). test_variational_schur_precond: the stiffness preconditioner is
now active for model="perturbative" of VariationalPBEvolve (test_same_solution, perturbative_p); the fallback test
keeps the perturbative q model. Both tests failed in CI on this branch; the updates were in the stacked #898.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- VariationalDensityEvolve: midpoint density weight (option weights, default midpoint for model=perturbative),
  option jacobian_solver, error instead of NaN when the Newton linear solve breaks down
- VariationalLinearSoundEvolve(total_weight=True) for the perturbative model: pressure transported with the total
  pressure p0 + p~ as weight (Picard on the weight), closing the nonlinear sound coupling inside one implicit step
- VariationalNonlinearPBEvolve: only the magnetic terms; Picard iteration on the weight b^{n+1/2} with one SPD solve
  per iteration (no explicit-wave limit dt k |b~| < 2)
- tests: symmetry, energy and split invariance of the sound step with total weight; baseline of
  VariationalPerturbativeMHD-u_space=Hdiv updated (split -> merged sound step)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…solver breakdown

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-perturbative-mhd-midpoint

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nicolominichelli and others added 8 commits November 10, 2026 09:43
…riational-perturbative-mhd-midpoint

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… into variational-exact-force-matrix

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…) into variational-exact-force-matrix

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…near-mhd-model

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…riational-linear-mhd-model

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Base automatically changed from variational-linear-mhd-model to devel November 10, 2026 10:55
nicolominichelli and others added 3 commits November 10, 2026 11:59
…a-f-time-stepping

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…riational-delta-f-time-stepping

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@spossann spossann left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good - please update with current devel.

…docstring of VariationalDensityEvolve.Options

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@spossann
spossann merged commit bb4664b into devel Nov 10, 2026
36 checks passed
@spossann
spossann deleted the variational-delta-f-time-stepping branch November 10, 2026 13:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants