Solver algorithms¶
This chapter documents the timestep driver and acceptance logic of the stereographic dynamic
integrator implemented in
Z_sim_dynamic_stereo_base
(demag and nodemag drivers). Configuration parameters are listed in Configuration reference.
Notation¶
The table lists symbols common to the timestep driver and to phases (A)–(C).
Symbol |
Definition |
|---|---|
\(t^n\), \(\Delta t^\ast\) |
Physical time and non-dimensional timestep of the current attempt |
\(\mathbf{u}^n=(\mathbf{q}_A^n,\mathbf{q}_B^n)\); demag adds \(\varphi^n\) |
Accepted state at the beginning of the step (backup) |
\(\mathbf{u}^{\mathrm{trial}}\) |
State returned by the nonlinear solve prior to acceptance |
\(\mathbf{F}(\mathbf{u})\) |
\(\text{SNES}\) residual vector of the BDF-discretized LLG weak form (Stereographic formulation) |
\(\|\mathbf{F}\|\) |
Euclidean norm of the \(\text{SNES}\) residual vector |
\(E_{\mathrm{split}}\), \(E_{\mathrm{phys}}\) |
Split stored energy ((143)) and physical Cartesian energy ((145)) |
\(E_{\mathrm{gate}}\) |
Energy functional monitored by the thermodynamic gate ((146)) |
Energy functionals¶
Each acceptance criterion in phases (C)–(F) compares a scalar energy or its increment
step-over-step. The solver maintains two such scalars on every trial state:
\(E_{\mathrm{split}}\) (compute_energy) and \(E_{\mathrm{phys}}`\)
(_compute_energy_physical_cartesian). Both integrate the same micromagnetic energy
densities over \(\Omega_{\mathrm{m}}\); they differ in how the magnetization field
entering those densities is constructed from the dual-chart state
\((\mathbf{q}_A,\mathbf{q}_B)\).
Split stored energy¶
\(E_{\mathrm{split}}\) is assembled directly from the stereographic chart fields. For each chart \(\chi\in\{A,B\}\), let \(\mathbf{m}_\chi = \mathbf{m}_\chi(\mathbf{q}_\chi)\) denote the Cartesian magnetization recovered from that chart’s inverse map, and let \(W_\chi\) denote the energy density evaluated on \(\mathbf{q}_\chi\) (for exchange, which uses stereographic gradients \(\nabla\mathbf{q}_\chi\)) or on \(\mathbf{m}_\chi\) (for anisotropy, Zeeman, and demagnetization). With element-wise atlas weights \(w_A\), \(w_B\),
This is the functional whose Gateaux variation generates the \(\text{SNES}\) residual
\(\mathbf{F}\) ((120), Stereographic formulation). Because exchange
in \(W_\chi\) is expressed through stereographic gradients \(\nabla\mathbf{q}_\chi\)
rather than \(\nabla\mathbf{m}\), \(E_{\mathrm{split}}\) depends on the full
chart representation \((\mathbf{q}_A,\mathbf{q}_B)\) and not solely on the
magnetization direction. compute_energy returns component integrals (exchange,
uniaxial anisotropy, cubic anisotropy, demagnetization, Zeeman) followed by
\(E_{\mathrm{split}}\).
Physical Cartesian energy¶
\(E_{\mathrm{phys}}\) evaluates the same energy densities on a single Cartesian field \(\mathbf{m}\) constructed by normalizing the atlas-weighted blend of the two chart reconstructions:
Exchange in \(E_{\mathrm{phys}}\) uses \(|\nabla\mathbf{m}|^2\) of this blended field; all other terms are evaluated identically to their counterparts in \(E_{\mathrm{split}}\). Formally,
computed by _compute_energy_physical_cartesian. Because \(\mathbf{m}\) is constructed
from the blend without reference to individual chart gradients, \(E_{\mathrm{phys}}\) is a
functional of the magnetization direction alone: it is insensitive to how the state
\((\mathbf{q}_A,\mathbf{q}_B)\) is distributed between the two charts, provided the
blended \(\mathbf{m}\) is the same.
Comparison and usage of the two functionals¶
When the two functionals agree. In regions where a single chart dominates, both charts reconstruct essentially the same magnetization direction, and the stereographic exchange gradient reduces to the physical one via the conformal factor. In that regime \(E_{\mathrm{split}}\) and \(E_{\mathrm{phys}}\) are numerically close.
When they differ. The two values are not equal in two situations. First, in overlap regions where both atlas weights are non-zero, each chart contributes independent stereographic gradients to \(E_{\mathrm{split}}\); those gradients do not need to match the gradient of the blended \(\mathbf{m}\) used by \(E_{\mathrm{phys}}\). Second, atlas closure (Atlas closure (phase F)) adjusts the chart fields \((\mathbf{q}_A,\mathbf{q}_B)\) to restore representation consistency without changing the physical magnetization, so \(E_{\mathrm{phys}}\) is unaffected while \(E_{\mathrm{split}}\) may shift.
Functional assignment by criterion. Each acceptance criterion uses exactly one of the two functionals:
Criterion |
Functional |
Rationale |
|---|---|---|
\(\text{SNES}\) residual / \(F\) |
\(E_{\mathrm{split}}\) |
Gateaux derivative of (143). |
Thermodynamic gate (\(E_{\mathrm{gate}}\), default) |
\(E_{\mathrm{split}}\) |
Form-consistent with the dynamical variables \(\mathbf{q}_{A,B}\) solved by \(\text{SNES}\). |
Physical guard (optional, |
\(E_{\mathrm{phys}}\) |
Secondary check that the blended-field energy has not grown; insensitive to chart redistribution inside the atlas. |
Atlas closure jump |
\(E_{\mathrm{gate}}\) (split or phys, per |
Monitors the energy change caused by the representational repair in phase (F); a large jump signals an inconsistent closure. |
Steady-state stop |
\(E_{\mathrm{phys}}\) |
Atlas closure can shift \(E_{\mathrm{split}}\) without any physical change; \(E_{\mathrm{phys}}\) avoids spurious early termination near equilibrium. |
Gate energy definition.
The thermodynamic gate (phase E, Thermodynamic gate (phase E)) tests the increment
\(\Delta E_{\mathrm{gate}} = E_{\mathrm{gate}}^{\mathrm{trial}} - E_{\mathrm{gate}}^{n}\)
between the backup state \(\mathbf{u}^n\) and the trial state
\(\mathbf{u}^{\mathrm{trial}}\). Flag thermo_use_split_energy (default True)
selects which functional plays the role of \(E_{\mathrm{gate}}\).
Let \(\chi\) denote that flag; then:
Split stored energy (default,
thermo_use_split_energy = True). The gate monitors \(E_{\mathrm{split}}\), the same functional whose Gateaux variation drives the \(\text{SNES}\) solve ((143), (120)). This keeps the energy test fully consistent with the dynamical degrees of freedom \((\mathbf{q}_A,\mathbf{q}_B)\) and with the functional differentiated in the nonlinear solve.Physical Cartesian energy (
thermo_use_split_energy = False). The gate monitors \(E_{\mathrm{phys}}\), which depends only on the blended magnetization \(\mathbf{m}\) ((145)). Because \(E_{\mathrm{phys}}\) is insensitive to how the state is distributed between charts, this option eliminates representational variation from the energy test. It is appropriate when the thermodynamic criterion must be stated strictly in terms of the physical magnetization direction rather than the atlas decomposition.
Structure of one timestep¶
At step index \(n\), solve_problem advances from the last accepted state
\(\mathbf{u}^n\) at time \(t^n\) to an accepted state at \(t^{n+1}=t^n+\Delta t^\ast\).
The routine nests several control levels; Figure 1 (Structure of one timestep) summarises the hierarchy.
Timestep loop.
Outer driver for step in range(step_start, step_end) over integer indices \(n\). Physical
time advances once per iteration, and only after a successful accept. Each iteration comprises
three sequential blocks:
Preamble (
_open_timestep,_run_timestep_preamble) — runs once before any retry at this \(n\). Updates demagnetization and Zeeman continuation ramps; when a ramp completes, transition guards may reduce \(\Delta t^\ast\), tighten growth caps, and rebase thermodynamic baselines (Continuation ramps).Δt-retry loop (
while not accepted) — repeated acceptance attempts at the current \(\Delta t^\ast\). The state \(\mathbf{u}^n\) is backed up before each attempt. If an attempt fails, the backup is restored and \(\Delta t^\ast\) may be reduced before retrying; the clock does not advance (Rejection and recovery). Each pass through this loop executes phases (A)–(F) on a trial state \(\mathbf{u}^{\mathrm{trial}}\) via_run_accept_attempt(Acceptance phases).Post-acceptance update (
_finalize_accepted_step) — runs once after accept. Advances \(t^n\to t^{n+1}\), updates BDF history, selects the next \(\Delta t^\ast\) ((176)), and writes I/O.
Operator-split loop (demag driver only). Inside phase (A) of a single acceptance attempt, a fixed-point iteration couples \(\text{SNES}\) for \((\mathbf{q}_A,\mathbf{q}_B,\varphi)\) with magnetostatic updates of \(\mathbf{H}_{\mathrm{d}}\) (Nonlinear solve (phase A)). This loop is nested within one acceptance attempt; it is not part of the timestep loop or the Δt-retry loop.
Acceptance phases¶
Within one iteration of the Δt-retry loop, the trial state passes through six phases (A)–(F) in order. Phases (A)–(E) operate on the dynamical chart fields; phase (F) repairs the stereographic representation for storage and I/O.
(A) Nonlinear solve (Nonlinear solve (phase A)). Advance the LLG equation over \([t^n,t^{n+1}]\) with a BDF formula on the chart variables \((\mathbf{q}_A,\mathbf{q}_B)\) (and \(\varphi\) in the demag driver), producing a trial state \(\mathbf{u}^{\mathrm{trial}}\) and a \(\text{SNES}\) residual \(F\). In the demag driver, magnetostatics and magnetization are coupled through an operator-split loop nested inside this phase.
(B) Algebraic validity (Algebraic validity (phase B)). Confirm that the nonlinear and linear algebra completed successfully: \(\text{SNES}\) and \(\text{KSP}\) converged, and in the demag driver the operator-split fixed point reached its tolerance. This phase does not evaluate physics; it only excludes diverged or incomplete solves.
(C) Atlas radius guard (Atlas guard (phase C)). Verify that the stereographic coordinates \(\mathbf{q}_A\), \(\mathbf{q}_B\) remain within a numerically safe range, and snapshot \(E_{\mathrm{split}}\) and \(E_{\mathrm{phys}}\) for the downstream gates. The energy snapshot is also refreshed whenever backtracking in phase (D) or (E) modifies the chart fields.
(D) Residual gate (Residual gate (phase D)). Compare \(\|\mathbf{F}\|\) against a history-adaptive threshold \(g_{\mathrm{res}}\). If the test fails, three substages are tried in order: residual backtracking along the chord between \(\mathbf{u}^n\) and \(\mathbf{u}^{\mathrm{trial}}\) (D1), merit acceptance based on the contraction of \(\|\mathbf{F}\|\) relative to the previous step (D2), or continuation with a soft violation that caps subsequent timestep growth (D3).
(E) Thermodynamic gate (Thermodynamic gate (phase E)). When active, verify that the energy increment \(\Delta E_{\mathrm{gate}}\) is consistent with Gilbert damping and external work ((167)). The gate is skipped during field ramps, when the energy landscape itself is changing. On failure, optional backtracking (E1) searches for a damped intermediate state that satisfies the inequality.
(F) Atlas closure (Atlas closure (phase F)). After all dynamical gates pass, reconcile the inactive chart degrees of freedom with the blended magnetization \(\mathbf{m}\) so the stored representation is atlas-consistent. A large jump in \(E_{\mathrm{gate}}\) across this purely representational step indicates an inconsistent closure and triggers rejection.
The six labels map directly to private methods of
Z_sim_dynamic_stereo_base,
called in sequence by
_run_accept_attempt():
Phase |
Method |
Role |
|---|---|---|
|
BDF weak form, operator-split loop (demag); saves trial \(\mathbf{q}_A\), \(\mathbf{q}_B\) |
|
|
\(\text{SNES}\)/\(\text{KSP}\) convergence reasons; outer-split defect check (demag) |
|
|
Atlas radius guard; \(E_{\mathrm{split}}\) and \(E_{\mathrm{phys}}\) snapshot |
|
|
Substages D1 ( |
|
|
Dissipation inequality ((167)); E1 via |
|
|
Inactive-chart reconciliation; \(|\Delta E_{\mathrm{gate}}|\) jump check |
A RuntimeError from any phase is caught by
_handle_step_rejection(),
which restores the step backup \(\mathbf{u}^n\) and may reduce \(\Delta t^\ast\).
On success,
_finalize_accepted_step()
advances time, updates the BDF history, selects the next \(\Delta t^\ast\), and writes I/O.
Figure 1 below shows the full timestep hierarchy — preamble, Δt-retry loop with phases (A)–(F), and post-acceptance update. The outcome labels beside each phase box summarise what can happen; full detail for each phase follows in the sections below.
Figure 1. Timestep driver for step index \(n\).
Preamble → \(\Delta t^\ast\)-retry loop (phases (A)–(F)) → post-accept update.
Badge key: PASS / SKIP / RETRY (green) — attempt continues.
WARN / SOFT (amber) — continues with a diagnostic flag.
REJECT (red) — \(\mathbf{u}^n\) restored, \(\Delta t^\ast\) adjusted, retry.
Phase (D) never rejects. See Figure 8 for full per-gate detail.
Nonlinear solve (phase A)¶
Figure 2. Phase (A): nonlinear solve, operator-split loop, and trial state.
Phase (A) (_run_nonlinear_solve) solves one implicit timestep: find chart fields at
\(t^{n+1}\) such that the BDF-discretized LLG weak form (Stereographic formulation,
(125)) is satisfied over \([t^n,t^{n+1}]\). The result is the trial state
\(\mathbf{u}^{\mathrm{trial}}\) for the acceptance gates. Phase (A) does not judge acceptance;
in the demag driver it returns split-convergence metadata in split_info.
Demag driver — hybrid FEM–BEM operator-split loop. solve_weak_form advances one implicit
BDF step at \(t^{n+1}\) for the mixed fields \((\mathbf{q}_A,\mathbf{q}_B,\varphi)\) while
coupling open-boundary magnetostatics through the Fredkin–Koehler FEM–BEM procedure
(Magnetostatics). A fully coupled Newton solve would require differentiating the dense
BEM operators with respect to the chart fields at every Newton step, which is prohibitively expensive.
The demag driver avoids that cost by a two-level scheme at each timestep: an outer
fixed-point loop over the magnetization–demagnetization split, and an inner \(\text{SNES}\)
Newton–Krylov solve at each outer iterate.
Fields and notation. The mixed vector u_mixed holds \((\mathbf{q}_A,\mathbf{q}_B,\varphi)\).
The scalar \(\varphi\) is the FEM magnetostatic unknown: \(\text{SNES}\) updates it, and the LLG
residuals use \(\mathbf{H}_{\mathrm{d}}=-\nabla\varphi\). The BEM closure
\(\varphi_{\mathrm{BEM}}\) is the magnetostatic potential returned by demag_helper.compute_phi
from blended \(\mathbf{m}(\mathbf{q}_A,\mathbf{q}_B)\); it is written to phi_computed_mixed.
During outer iterate \(k\), the closure evaluated at the opening charts is frozen as
\(\varphi_{\mathrm{BEM}}^{k}\) (pass-local copy phi_outer_ref) and appears in the
\(\varphi\) residual as \(\nabla(\varphi-\varphi_{\mathrm{BEM}}^{k})\) without being
refreshed until the next outer iterate.
Timestep setup. At the entry of solve_weak_form, accepted charts at \(t^n\) and
\(t^{n-1}\) are copied into qA_prev / qB_prev (and *_prev_prev for BDF2). They
enter the BDF time derivative and remain fixed for the entire outer loop. The outer loop updates only
the trial charts at \(t^{n+1}\) in qA / qB. The first outer iterate uses
\(\mathbf{q}^{0}=(\mathbf{q}_A^{0},\mathbf{q}_B^{0})\) as an initial guess for
\(t^{n+1}\)—typically the accepted charts at \(t^n\) already present in qA / qB after
the history shift.
Outer fixed-point loop. Outer iterates are indexed \(k=0,1,\ldots\) up to
outer_split_max_iter. At iterate \(k\) the opening charts \(\mathbf{q}^{k}\) are the
fields in qA / qB before the pass; the inner \(\text{SNES}\) returns updated charts
\(\mathbf{q}^{k+1}\). The goal of the outer loop is to reach a state where the BEM closure
computed from \(\mathbf{q}^{k+1}\) agrees with the closure \(\varphi_{\mathrm{BEM}}^{k}\)
that was frozen during the solve—so that the magnetization and the demagnetizing field are mutually
consistent at \(t^{n+1}\). Each pass executes:
Atlas update.
_atlas_update_masks_from_qArebuilds the element weightswA/wBfrom \(\mathbf{q}^{k}\).Magnetostatic freeze.
_refresh_demag_state_from_current_qforms \(\mathbf{m}(\mathbf{q}^{k})\), callsdemag_helper.compute_phito evaluate the BEM closure \(\varphi_{\mathrm{BEM}}^{k}\), stores it inphi_computed_mixed, and copies it tophi_outer_ref. The opening charts are saved asqA_outer_before/qB_outer_before.Inner |SNES| solve. \(\text{SNES}\) advances the BDF–LLG equations for \((\mathbf{q}_A,\mathbf{q}_B,\varphi)\) with \(\varphi_{\mathrm{BEM}}^{k}\) held constant (see the inner-solve subsection below). The resulting charts written back to
qA/qBare \(\mathbf{q}^{k+1}\); the resulting potential inu_mixedis \(\varphi^{k+1}\).Split-defect check and loop decision.
_refresh_demag_state_from_current_qre-evaluates the BEM closure from \(\mathbf{q}^{k+1}\), giving \(\varphi_{\mathrm{BEM}}^{k+1}\). Two defect norms (defined below) then measure how much the charts and the BEM closure changed across this pass. If both are within tolerance and the minimum pass count is met (outer_split_min_iter_with_accelwhen Anderson is enabled), the outer loop stops. Otherwise iterate \(k+1\) opens from \(\mathbf{q}^{k+1}\)—optionally modified by Anderson acceleration—and returns to step 1.
Inner |SNES| solve. The key simplification of the two-level scheme is that, with
\(\varphi_{\mathrm{BEM}}^{k}\) frozen, the Jacobian of \(\mathbf{F}\) with respect to
\((\mathbf{q}_A,\mathbf{q}_B,\varphi)\) is sparse and can be assembled and factored without
touching the dense BEM operators. Concretely, \(\text{SNES}\) drives
\(\mathbf{F}(\mathbf{u})=\mathbf{0}\) for
\(\mathbf{u}=(\mathbf{q}_A,\mathbf{q}_B,\varphi)\), initialized from \(\mathbf{q}^{k}\)
(_build_snes_forms). The mixed residual
\(\mathbf{F}=(\mathbf{F}_{\mathbf{q}_A},\mathbf{F}_{\mathbf{q}_B},F_\varphi)\) is assembled
from the weak forms (Stereographic formulation). The chart residual blocks are
where \(\dot{\mathbf{q}}_\chi\) is the BDF approximation (125), \(\mathcal{L}_\chi\) is the chart-specific stereographic damping matrix (Stereographic formulation), and \(\mathbf{F}_\chi^{\mathrm{ex}},\mathbf{F}_\chi^{\mathrm{ani}},\mathbf{F}_\chi^{\mathrm{zee}},\mathbf{F}_\chi^{\mathrm{d}}\) collect the exchange, anisotropy, Zeeman, and demagnetization Gateaux variations. The demagnetization block is
where \(\mathcal{A}_\chi=\partial\mathbf{m}_\chi/\partial\mathbf{q}_\chi\) is the
chart Jacobian A_mu_i_chart. This term couples \(\mathbf{q}_\chi\) to \(\varphi\)
through a sparse local operator.
The third block of the monolithic residual enforces the magnetostatic constraint \(\varphi=\varphi_{\mathrm{BEM}}(\mathbf{q}_A,\mathbf{q}_B)\). In the full coupled system this block depends on the chart fields through \(\varphi_{\mathrm{BEM}}\); freezing the closure as \(\varphi_{\mathrm{BEM}}^{k}\) at the start of the outer iterate makes it a constant, so the constraint reduces to a standard Poisson equation for \(\varphi\):
with \(\varphi_{\mathrm{BEM}}^{k}\) held constant for every Newton step inside the call. Its unique minimum is \(\varphi=\varphi_{\mathrm{BEM}}^{k}\), so the \(\text{SNES}\) solution satisfies \(\varphi^{k+1}=\varphi_{\mathrm{BEM}}^{k}\) up to boundary conditions.
Because \(F_\varphi\) is linear in \(\varphi\) and independent of \((\mathbf{q}_A,\mathbf{q}_B)\) once \(\varphi_{\mathrm{BEM}}^{k}\) is frozen, the inner Jacobian \(\mathbf{J}^{\mathrm{inner}}=\partial\mathbf{F}/\partial\mathbf{u}\) has the block structure
The zero blocks in the bottom row reflect the decoupling of \(F_\varphi\) from the chart
fields when \(\varphi_{\mathrm{BEM}}^{k}\) is frozen. The diagonal chart blocks contain
atlas-weighted contributions from damping, exchange, anisotropy, and Zeeman terms.
The off-diagonal blocks \(\mathbf{J}_{\mathbf{q}_A,\mathbf{q}_B}\) and
\(\mathbf{J}_{\mathbf{q}_B,\mathbf{q}_A}\) are typically small because atlas weighting
suppresses simultaneous chart activity in the same region.
The right-column blocks \(\mathbf{J}_{\mathbf{q}_\chi,\varphi}\) capture the sensitivity
of the LLG dynamics to the demagnetizing field \(\mathbf{H}_{\mathrm{d}}=-\nabla\varphi\).
The Jacobian is computed by automatic differentiation (UFL ufl.derivative). Each Newton
step solves \(\mathbf{J}^{\mathrm{inner}}\delta\mathbf{u}=-\mathbf{F}\) with GMRES and
a block-Jacobi preconditioner. Intermediate Newton iterates are internal to \(\text{SNES}\);
only \(\mathbf{q}^{k}\) and the final \(\mathbf{q}^{k+1}\) enter the split-defect
tests.
Split-defect tests. Let \(\Delta\mathbf{q}^{k}=\mathbf{q}^{k+1}-\mathbf{q}^{k}\). The chart defects are
with \(N_q\) the global chart-DOF count. After \(\text{SNES}\), _refresh_demag_state_from_current_q
builds \(\varphi_{\mathrm{BEM}}^{k+1}\) from \(\mathbf{q}^{k+1}\). The magnetostatic defects
are
The outer iterate passes when
using outer_split_tol_q, outer_split_tol_q_abs, outer_split_tol_phi, and
outer_split_tol_phi_abs. The relative norms divide by the opening chart magnitude and by
\(\|\varphi_{\mathrm{BEM}}^{k}\|\); when those scales are small, the absolute norms supply a
per-DOF floor on the increments.
Note that \(\mathbf{q}^{k+1}\neq\mathbf{q}^{k}\) in general even when the defects are small: \(\delta_q^k\) measures how much the charts changed compared to the opening guess at iterate \(k\), not compared to the accepted charts at \(t^n\). A small \(\delta_q^k\) does not mean the magnetization barely moved over the timestep; it means that freezing \(\varphi_{\mathrm{BEM}}\) from \(\mathbf{q}^{k}\) and then solving the inner BDF–LLG system led back to essentially the same charts \(\mathbf{q}^{k+1}\approx\mathbf{q}^{k}\). Similarly, a small \(\delta_\varphi^k\) confirms that re-evaluating the BEM closure at those new charts gives \(\varphi_{\mathrm{BEM}}^{k+1}\approx\varphi_{\mathrm{BEM}}^{k}\). Together the two tests certify that \(\mathbf{q}^{k+1}\) satisfies the BDF–LLG equations and that the demagnetizing field in those equations is the one the BEM assigns to \(\mathbf{q}^{k+1}\) itself.
Anderson acceleration (outer_split_anderson_enable). When the defects fail, the charts for
outer iterate \(k+1\) can be extrapolated from a short history of packed pairs
\((\mathbf{x}^{i},\mathbf{x}^{i+1})\) with \(\mathbf{x}=(\mathbf{q}_A,\mathbf{q}_B)\),
\(i=0,\ldots,k\) (history depth outer_split_anderson_depth, starting from iterate
outer_split_anderson_start_iter). The accelerated point \(\mathbf{x}_{\mathrm{acc}}\) is
obtained by combining the stored iterates with coefficients \(\alpha_i\) that minimize the
residual norm:
computed by _outer_split_anderson_candidate. The next opening state damps the accelerated point
against the last \(\text{SNES}\) result:
with \(\omega\) from outer_split_anderson_damping. Only \((\mathbf{q}_A,\mathbf{q}_B)\) are
overwritten; \(\varphi\) is set by the next \(\text{SNES}\) call.
Convergence metadata (iters, q_defect, phi_defect, converged, …) is returned in
split_info for phase (B).
Nodemag driver.
Only \((\mathbf{q}_A,\mathbf{q}_B)\) are advanced; there is no \(\varphi\) unknown,
no BEM step, and no \(\delta_\varphi\). The outer loop still runs the same
\(\delta_q\) / \(\delta_q^{\mathrm{abs}}\) defect check, acting as a convergence
guard on the atlas-weight update (step 1) rather than on a magnetostatic split.
In the typical nodemag case outer_split_max_iter is set to 1, so the loop executes
a single SNES solve with no iteration.
After solve_weak_form returns, _run_nonlinear_solve copies the chart trial
(\(\mathbf{q}_A^{\mathrm{trial}}\), \(\mathbf{q}_B^{\mathrm{trial}}\)) into the attempt
record and calls _prepare_state_for_acceptance(apply_closure=False) to refresh \(\dot{\mathbf{q}}\),
the mixed finite-element vector, and (demag) \(\varphi\) without atlas closure. The \(\text{SNES}\)
residual \(\mathbf{F}(\mathbf{u})\) and \(\|\mathbf{F}\|\) are available for phases (D) and (E) and are
recomputed whenever backtracking modifies \(\mathbf{u}\).
Completion:
solve_weak_formreturns; the trial charts andsplit_infoare passed to phase (B). Algebraic validity is not checked in phase (A).Outer split not converged: the loop exhausted all allowed outer iterations but
split_info["converged"]is false because \(\delta_q\) or \(\delta_\varphi\) still exceed their tolerances. Phase (A) returns the last iterate regardless. Whenenforce_outer_split_acceptanceis true, phase (B) raises aRuntimeErrorand_handle_step_rejectiontakes over. Before cutting \(\Delta t^\ast\), the handler checks whetherretry.outer_retry_countis still withinouter_split_retry_before_dt_cutand whetherretry.outer_iter_caphas not yet reachedouter_split_max_iter_hard. If both conditions hold, it incrementsretry.outer_iter_capbyouter_split_iter_escalation, rolls back to the step-start backup \(\mathbf{u}^n\), and re-runs the entire outer loop with the higher iteration budget—keeping \(\Delta t^\ast\) unchanged. Only once the retry budget is exhausted or the hard cap is reached does the handler halve \(\Delta t^\ast\) and continue the Δt-retry loop.
Algebraic validity (phase B)¶
Figure 3. Phase (B): algebraic validity — SNES/KSP convergence and outer-split check.
Phase (B) (_validate_algebraic_solve) confirms that the nonlinear and linear algebra completed
successfully. It does not evaluate physics—only whether \(\text{SNES}\) and \(\text{KSP}\) terminated without
divergence and, in the demag driver, whether the operator-split loop converged.
Checks performed. _validate_algebraic_solve reads the \(\text{SNES}\) and \(\text{KSP}\) converged-reason
codes and the split_info dict returned by phase (A).
|SNES|/|KSP| reason check. Both \(\text{SNES}\) and \(\text{KSP}\) must report non-negative converged reasons. A negative reason indicates divergence of the nonlinear or linear solve. The method takes
min(snes_reason, ksp_reason); any negative value immediately raisesRuntimeError.Outer-split check (demag driver, when
enforce_outer_split_acceptanceis true).split_info["converged"]must be true, i.e. the final \(\delta_q\) (and \(\delta_\varphi\) in the demag driver) from phase (A) must satisfy their paired tolerances ((150)–(151)). Whenenforce_outer_split_acceptanceis false an unconverged split is allowed through; later gates may still reject the step.
Success: both checks pass.
_validate_algebraic_solvecomputes \(\|\mathbf{F}\|\) (_compute_residual_norm), stores \(\|\mathbf{F}\|\), \(\text{SNES}\) iteration count, and \(\text{KSP}\) iteration count on the attempt record, and the trial proceeds to phase (C).Failure (|SNES|/|KSP| divergence): a negative converged reason raises
RuntimeError;_handle_step_rejectionrestores \(\mathbf{u}^n\), switches betweennewtonls(Newton with line search, the default) andnewtontr(Newton with trust-region globalisation) according to a reason-aware policy (_set_snes_globalization_mode), and halves \(\Delta t^\ast\) on the nonthermo rejection path.Failure (outer split):
split_info["converged"]is false withenforce_outer_split_acceptancetrue; raisesRuntimeError(Outer split not converged). Recovery proceeds as described in the phase (A) outcomes: the handler first attempts to escalateretry.outer_iter_capbefore cutting \(\Delta t^\ast\).
Atlas guard (phase C)¶
Figure 4. Phase (C): atlas radius guard and energy snapshot.
Phase (C) (_refresh_attempt_energies) performs two operations on the \(\text{SNES}\) trial state: it
enforces the atlas radius guard, and it snapshots the energies required by the later gates.
Atlas radius guard (_enforce_atlas_radius_guard).
Each stereographic chart \(\chi\in\{A,B\}\) maps a unit vector \(\mathbf{m}\) to a planar
coordinate \(\mathbf{q}_\chi\in\mathbb{R}^2\). The conformal factor
\(\sigma_\chi^2=(2/(1+|\mathbf{q}_\chi|^2))^2\) ((77)) decreases as
\(|\mathbf{q}_\chi|\) grows and vanishes as \(\mathbf{m}\) approaches the projection pole
of chart \(\chi\); large \(|\mathbf{q}_\chi|\) therefore signals numerical
ill-conditioning in that chart. For an exact point \(\mathbf{m}\in\mathcal{S}^2\), the
two-chart atlas with antipodal poles guarantees that at least one chart radius remains bounded.
This guarantee, however, applies only to geometrically consistent states. During the \(\text{SNES}\)
solve, \(\mathbf{q}_A\) and \(\mathbf{q}_B\) are independent finite-element DOFs;
Newton iterates do not enforce mutual chart consistency—that is deferred to atlas closure in phase
(F). A Newton step that overshoots significantly in chart space can therefore drive both
\(|\mathbf{q}_{A,i}|\) and \(|\mathbf{q}_{B,i}|\) large at the same node
simultaneously—even when \(\text{SNES}\) reports a positive converged reason—producing a trial state that
is algebraically valid in the weak form but not geometrically consistent on \(\mathcal{S}^2\).
Phase (C) detects this on the mesh.
check_q_magnitude evaluates, at each node \(i\), the minimum chart radius
\(r_i=\min(|\mathbf{q}_{A,i}|,\,|\mathbf{q}_{B,i}|)\) and MPI-reduces the mesh-wide maximum:
When \(r_{\max}\) is large, the worst-placed node exceeds the acceptable radius even in its
complementary chart. The guard defines a soft threshold \(r_{\mathrm{guard}}\) (atlas_qmax_guard)
and a hard threshold
with \(f_{\mathrm{hard}}=\) _atlas_qmax_guard_soft_factor.
Energy snapshot (_compute_energy_quiet, _compute_energy_physical_cartesian).
When the radius guard does not raise, _compute_energy_quiet and
_compute_energy_physical_cartesian evaluate \(E_{\mathrm{split}}\) and
\(E_{\mathrm{phys}}\) together with their component integrals (exchange, anisotropy,
demagnetization, Zeeman) on the current trial state. The values are stored in the attempt record
and carry no acceptance decision; they are consumed by the residual and thermodynamic gates in
phases (D) and (E) and written to the acceptance log on commit.
Success (\(r_{\max} \le r_{\mathrm{guard}}\)): the guard returns normally; the energy snapshot is taken and the attempt proceeds to phase (D).
Soft warning (\(r_{\mathrm{guard}} < r_{\max} \le g_{\mathrm{hard}}\)): the radius is elevated but below the hard limit; a diagnostic line is emitted when
_debug_logsis enabled. The guard returns normally; the energy snapshot is taken and the attempt proceeds to phase (D).Failure (\(r_{\max} > g_{\mathrm{hard}}\)):
RuntimeErroris raised before the energy snapshot is taken;_handle_step_rejectionrestores \(\mathbf{u}^n\) and reduces \(\Delta t^\ast\) on the nonthermo rejection path.
Residual gate (phase D)¶
Figure 5. Phase (D): residual gate — substages D1 (backtracking), D2 (merit accept), D3 (soft continue).
Phase (D) (_apply_residual_gate) examines the algebraic residual \(\|\mathbf{F}\|\) of the
BDF-discretized LLG system on the trial state. \(\text{SNES}\) converges under its own absolute and relative
tolerances on \(\|\mathbf{F}\|\) and step norms; those tolerances do not track the scale of
\(\|\mathbf{F}\|\) recorded at previously accepted steps. A trial can therefore satisfy \(\text{SNES}\)
termination while \(\|\mathbf{F}\|\) remains large relative to the integrator history—for example
after an incomplete operator-split loop in the demag driver. Phase (D) compares \(\|\mathbf{F}\|\)
to a history-adaptive threshold \(g_{\mathrm{res}}\) assembled by
_compute_effective_residual_gate.
Threshold assembly (_compute_effective_residual_gate).
During the first nonlinear_residual_gate_warmup_steps accepted steps the routine returns
None and the residual test is inactive. After warmup the threshold is assembled in four stages;
the running value is denoted \(g_{\mathrm{ema}}\).
Baseline. At each accepted step, two exponential moving averages of \(\|\mathbf{F}\|\) are maintained: a fast EMA \(\bar{r}_{\mathrm{fast}}\) (decay
nonlinear_residual_ema_beta) stored inaccepted_residual_ema, and a slow EMA \(\bar{r}_{\mathrm{slow}}`\) (decay_nonlinear_residual_slow_ema_beta) stored inaccepted_residual_ema_slow. With floor \(r_{\mathrm{floor}}\) (nonlinear_residual_gate_floor) and scale factor \(\alpha\) (nonlinear_residual_ema_factor), the baseline is:(156)¶\[g_{\mathrm{ema}} = \max\bigl(r_{\mathrm{floor}},\; \alpha\,\bar{r}_{\mathrm{fast}}\bigr).\]The floor prevents \(g_{\mathrm{ema}}\) from collapsing toward zero; the factor \(\alpha\) leaves headroom above the recent typical residual.
Slow-EMA clamp (when \(\bar{r}_{\mathrm{slow}}\) is available). The fast EMA can shift abruptly after a single stiff step. The baseline from stage 1 is replaced by
\[g_{\mathrm{ema}} \leftarrow \min\!\Bigl( \max\!\bigl(g_{\mathrm{ema}},\; g_{\mathrm{slow}} / c_{\mathrm{defl}}\bigr),\; \alpha\,c_{\mathrm{infl}}\,\bar{r}_{\mathrm{slow}} \Bigr),\]where \(g_{\mathrm{slow}}=\max(r_{\mathrm{floor}},\,\alpha\,\bar{r}_{\mathrm{slow}})\), \(c_{\mathrm{defl}}=\)
_nonlinear_residual_gate_deflation_cap, and \(c_{\mathrm{infl}}=\)_nonlinear_residual_gate_inflation_cap. This limits how far a single acceptance can move \(g_{\mathrm{ema}}\) relative to the long-term residual level.Absolute cap (when
nonlinear_residual_max_acceptis finite). Let \(r_{\mathrm{abs}}=\)nonlinear_residual_max_accept:\[g_{\mathrm{ema}} \leftarrow \min(g_{\mathrm{ema}},\; r_{\mathrm{abs}}).\]This enforces a hard upper bound on the threshold independent of the EMA history.
Relaxation near \(\Delta t_{\min}\). Let \(R_{\mathrm{dt}}=\Delta t^\ast/\Delta t_{\min}\). When \(\Delta t^\ast\) is close to
dt_minfurther timestep reduction is costly, yet the EMA-based baseline can be tight. With maximum relaxation factor \(\rho_{\max}=\)nonlinear_residual_dt_relax_max, the code applies a piecewise linear ramp:(157)¶\[\begin{split}\rho = \begin{cases} 1, & R_{\mathrm{dt}} \ge 5, \\[0.35em] 1 + \dfrac{5 - R_{\mathrm{dt}}}{4}\,(\rho_{\max} - 1), & 1 \le R_{\mathrm{dt}} < 5. \end{cases}\end{split}\]The implementation sets \(g_{\mathrm{ema}} \leftarrow \rho\,g_{\mathrm{ema}}\), so the same \(\|\mathbf{F}\|\) is compared against a larger threshold as \(\Delta t^\ast\) approaches
dt_min. At \(R_{\mathrm{dt}}\ge 5\) the factor is unity and \(g_{\mathrm{ema}}\) is unchanged; at \(R_{\mathrm{dt}}=1\) (right atdt_min) it equals \(\rho_{\max}\). The threshold \(R_{\mathrm{dt}}=5\) means the relaxation activates once \(\Delta t^\ast\) falls within five timesdt_min; below that the ramp widens the gate linearly toward \(\rho_{\max}\). The value \(5\) and the resulting denominator \(4=5-1\) are hard-coded implementation constants, not user parameters.
The residual acceptance threshold is the final value \(g_{\mathrm{res}} = g_{\mathrm{ema}}\).
SNES-reason filter (_enforce_snes_reason_on_residual; runs between phases C and D).
\(\text{SNES}\) reports a converged-reason code on termination. When strict_snes_reason_check is enabled,
reason codes listed in disallowed_snes_reasons are treated as weak certificates of convergence.
For such a reason, _enforce_snes_reason_on_residual compares \(\|\mathbf{F}\|\) to a tolerance
derived from \(g_{\mathrm{res}}\) (scaled by snes_disallowed_residual_factor, and additionally
by snes_disallowed_dtmin_relax when \(R_{\mathrm{dt}}\le 2\)). If \(\|\mathbf{F}\|\)
exceeds that tolerance the attempt raises RuntimeError and \(\Delta t^\ast\) is reduced.
When strict_snes_reason_check is disabled, every positive reason passes this filter.
Residual test and substages. The trial passes immediately when
or when \(g_{\mathrm{res}}\) is None (warmup period). When the test fails, three substages
are tried in order. Each operates at fixed \(\Delta t^\ast\) without restoring
\(\mathbf{u}^n\) or calling \(\text{SNES}\) again.
D1 — Residual backtracking (_residual_backtracking_search; when
_residual_backtracking_enable is true). D1 searches along the chord in chart space between the
backup state \(\mathbf{u}^n\) and the \(\text{SNES}\) trial \(\mathbf{u}^{\mathrm{trial}}\) using a
blend parameter \(\lambda\in(0,1)\):
Each candidate applies (159), leaves \(\Delta t^\ast\) and the BDF stencil
unchanged, and re-evaluates \(\|\mathbf{F}\|\) without calling \(\text{SNES}\). Candidates take
\(\lambda\in\{\sigma,\sigma^2,\ldots\}\) with \(\sigma=\) _residual_backtracking_shrink,
starting at \(\lambda=\sigma\) (never retesting \(\lambda=1\)), until
_residual_backtracking_min_lambda or _residual_backtracking_max_iter is reached.
Success (\(\|\mathbf{F}\|\le g_{\mathrm{res}}\) for some \(\lambda\)): the blended state is retained; energies and the atlas guard are refreshed on the new state and the attempt proceeds to phase (E).
Failure: the full \(\text{SNES}\) trial (\(\lambda=1\)) is restored; energies are refreshed on that state and D2 is evaluated.
A damped D1 acceptance introduces a temporal inconsistency. The BDF stencil and the output clock
both refer to the full interval \([t^n,t^{n+1}]\) of length \(\Delta t^\ast\), but the
accepted state lies partway along the chord at \(\lambda<1\). _finalize_accepted_step
nevertheless labels it \(\mathbf{m}^{n+1}\) at \(t^{n+1}=t^n+\Delta t^\ast\) and advances
the BDF history as for a full step. The discrete trajectory can therefore show a smaller
magnetization increment over \([t^n,t^{n+1}]\) than the \(\text{SNES}\) solution at \(\lambda=1\)
would imply. D1 accepts this trade-off because a state-space correction along the chord is
often sufficient to bring \(\|\mathbf{F}\|\) below \(g_{\mathrm{res}}\) when the trial
overshot only modestly.
D2 — Merit acceptance (_evaluate_residual_merit; when _residual_merit_enable is true).
When no chord point satisfies (158), the \(\text{SNES}\) solve may still have reduced
\(\|\mathbf{F}\|\) substantially relative to the opening residual. D2 accepts the full trial
(\(\lambda=1\)) when the contraction is sufficient. Let \(\|\mathbf{F}\|_{\mathrm{before}}\)
be the residual evaluated on \(\mathbf{u}^n\) at the start of phase (A), and
\(\|\mathbf{F}\|_{\mathrm{after}}\) the residual on the restored trial:
where \(\varepsilon_0\) is a small positive regularisation constant. \(\chi\) measures the contraction of \(\|\mathbf{F}\|\) across the solve; \(\rho_{\mathrm{m}}\) measures what fraction of the gap to \(g_{\mathrm{res}}\) was closed. Merit acceptance requires all three conditions:
with \(g_{\mathrm{merit}} = \max(\kappa_g\, g_{\mathrm{res}},\, \kappa_f\, r_{\mathrm{floor}})\),
\(\kappa_g=\) _residual_merit_gate_relax, \(\kappa_f=\) _residual_merit_floor_mult,
\(\chi_{\mathrm{req}}=\) _residual_merit_required_contraction, and
\(\rho_{\mathrm{req}}=\) _residual_merit_required_rho.
Success: the full trial (\(\lambda=1\)) is retained and the attempt proceeds to phase (E).
Failure: D3 is entered.
D3 — Soft continuation. When neither D1 nor D2 recovers (158), a hard rejection
would reduce \(\Delta t^\ast\) even though the magnetization update may be physically admissible.
D3 records residual_soft_violation on the attempt and continues with the full \(\text{SNES}\) trial at
\(\lambda=1\), leaving phases (E) and (F) to accept or reject the step.
_apply_residual_soft_accept_policy is called after phases (E) and (F) regardless of which
substage was taken; when residual_soft_violation is set it tightens \(\Delta t^\ast\) growth
caps and may schedule BDF1 recovery on subsequent steps as a penalty for the algebraic looseness.
Thermodynamic gate (phase E)¶
Figure 6. Phase (E): thermodynamic gate — substage E1 (backtracking; only substage that rejects).
Phase (E) (_apply_thermodynamic_gate) tests whether the accepted trial is consistent with
Gilbert damping and with any external work injected during the step. The test compares an energy
increment to a dissipation budget augmented by numerical tolerances. When the preamble sets
thermo_guard_active=False, phase (E) is not evaluated: this occurs while the demagnetization ramp
is active (_thermo_check_active) and during the short continuation-reconcile window that follows
a completed demag or Zeeman ramp, when the energy landscape itself is still being rescaled and the
stored baseline is rebased.
The monitored scalar is \(E_{\mathrm{gate}}\) ((146)). Flag
thermo_use_split_energy selects whether it coincides with \(E_{\mathrm{split}}\) or
\(E_{\mathrm{phys}}\); the default monitors \(E_{\mathrm{split}}\) so the inequality is
form-consistent with the chart variables advanced by \(\text{SNES}\). Let \(E^n_{\mathrm{gate}}\) denote
the value stored at the last acceptance. For the current trial,
Gilbert damping dissipates magnetic energy. In the stereographic weak form the associated loss rate is expressed through the chart velocities \(\dot{\mathbf{q}}_{A,B}\) carried by the BDF multistep history and through the regularised conformal factors \(\sigma_{A,B}^2\). The form-consistent dissipation over the step is
If the Zeeman field changed between \(t^n\) and the trial at \(t^{n+1}\), the external work on the magnetization is
During an active Zeeman ramp phase (E) remains active; the changing field enters through \(W_{\mathrm{ext}}\) rather than through suspension of the test.
A discrete Gilbert balance reads
The thermodynamic residual is
With \(D_q \ge 0\), exact closure implies \(\Delta E_{\mathrm{gate}} \le \max(0,\,-W_{\mathrm{ext}})\). \(\text{SNES}\) tolerance, quadrature, and floating-point arithmetic prevent exact closure in general. The step is accepted when
with nonnegative budget \(B\) given by (168). The left-hand side is the energy increment; the term \(\max(0,-W_{\mathrm{ext}})\) is the Gilbert bound rearranged from (165). The budget absorbs solver defect, roundoff, demag-dominated scales, and continuation-induced shifts in \(E_{\mathrm{gate}}\); the EMA contribution uses \(|R_{\mathrm{th}}|\) from (166).
Write \(\varepsilon_{\mathrm{mach}}\) for the IEEE-754 double-precision machine epsilon (\(\varepsilon_{\mathrm{mach}}\approx 2.2\times 10^{-16}\)).
Floating-point floor \(B_{\mathrm{fp}}\). \(E_{\mathrm{gate}}\) is obtained by volume quadrature and many accumulated operations. Even when \(R_{\mathrm{th}}\) would be zero in exact arithmetic, roundoff produces a nonzero defect of order \(\varepsilon_{\mathrm{mach}}\) times a representative energy magnitude. The hard-coded factor \(C_{\mathrm{fp}}=128\) (\(2^7\), not a user parameter) multiplies that estimate so phase (E) does not reject steps for roundoff alone.
Solver-noise term \(\Delta t^\ast\|\mathbf{F}\|\). The trial state is intended to satisfy the implicit BDF equation, but \(\text{SNES}\) may terminate with \(\|\mathbf{F}\|>0\). A nonzero residual means the discrete balance is not closed exactly; the associated energy increment \(\Delta E_{\mathrm{gate}}\) can then deviate from the damped balance \(-D_q-W_{\mathrm{ext}}\) even when the physics is sound. Scaling the allowance by \(\Delta t^\ast\|\mathbf{F}\|\) widens \(B\) in proportion to how far the algebraic solve sits from its root.
Defect EMA \(\eta\,\mathrm{EMA}(|R_{\mathrm{th}}|)\).
After thermo_defect_ema_warmup_steps acceptances, the code tracks an exponential moving average of
past values of \(|R_{\mathrm{th}}|\). The budget adds \(\eta\) times that average
(thermo_defect_ema_factor). The budget therefore permits a positive \(\Delta E_{\mathrm{gate}}\)
comparable to recent thermodynamic slack rather than demanding exact closure on every step.
Demag relative floor \(B_{\mathrm{dem}}\).
In the demag driver, \(|E_{\mathrm{gate}}|\) is dominated by magnetostatic self-energy, so
\(\Delta E_{\mathrm{gate}}\) is computed as a small difference of large terms. Quadrature,
the operator-split loop, and \(\text{SNES}\) tolerance can then leave
\(|\Delta E_{\mathrm{gate}}|=\mathcal{O}(\varepsilon_{\mathrm{dem}}|E_{\mathrm{gate}}|)\) on an
otherwise acceptable step, with \(\varepsilon_{\mathrm{dem}}\gg\varepsilon_{\mathrm{mach}}\).
The existing budget pieces do not cover that scale: \(B_{\mathrm{fp}}\) is only
\(\mathcal{O}(\varepsilon_{\mathrm{mach}}|E_{\mathrm{gate}}|)\) and \(\Delta t^\ast\|\mathbf{F}\|\)
tracks \(\text{SNES}\) defect on the LLG residual, not relative error in differencing demag integrals.
When demag is in energy_terms, the code adds
\(B_{\mathrm{dem}}=\varepsilon_{\mathrm{dem}}\max(1,|E^n_{\mathrm{gate}}|,|E^{n+1}_{\mathrm{gate}}|)\)
(thermo_demag_energy_rel_floor). The nodemag driver omits this term.
Ramp credit \(B_{\mathrm{ramp}}\).
Demagnetization terms in \(E_{\mathrm{gate}}\) are multiplied by \(\lambda_{\mathrm{dem}}\)
(demag_ramp_constant). A change in \(\lambda_{\mathrm{dem}}\) between accepts rescales the
demag contribution to \(\Delta E_{\mathrm{gate}}\) without changing
\((\mathbf{m},\mathbf{H}_{\mathrm{d}})\)—not dissipation and not Zeeman work. The evaluator
would credit
\(B_{\mathrm{ramp}}=|\lambda_{\mathrm{dem}}^{\mathrm{curr}}-\lambda_{\mathrm{dem}}^{n}|\,
|E_{\mathrm{dem}}^{n}|/|\lambda_{\mathrm{dem}}^{n}|\) (\(E_{\mathrm{dem}}^{n}\) the stored demag
component at the previous accept). In the current driver this term does not affect acceptance:
while \(\lambda_{\mathrm{dem}}\) is still ramping, phase (E) is suspended
(Continuation ramps); when the ramp completes, _run_timestep_preamble rebases
\(\lambda_{\mathrm{dem}}^{n}\) to \(\lambda_{\mathrm{dem}}^{\mathrm{curr}}\) before any gate
runs (transition_rebased) and keeps phase (E) off for the reconcile window. After that,
\(\lambda_{\mathrm{dem}}=1\) at every accept, so \(B_{\mathrm{ramp}}=0\) whenever
(167) is tested. The branch is retained in _evaluate_thermodynamic_residual
for bookkeeping consistency only.
When thermo_enable_physical_guard is true, a parallel inequality is imposed on
\(\Delta E_{\mathrm{phys}} = E^{n+1}_{\mathrm{phys}} - E^n_{\mathrm{phys}}\) with allowed
increase \(\max(0,-W_{\mathrm{ext}}) + B\) plus relative and EMA-based floors
(thermo_physical_guard_rel_floor, thermo_physical_guard_abs_floor,
thermo_physical_guard_ema_factor after thermo_physical_guard_warmup_steps). The guard
bounds growth of the blended-field energy when \(E_{\mathrm{split}}\) is the primary monitored
functional.
If (167) holds and the physical guard passes when enabled, phase (E) is complete.
If either test fails, _apply_thermodynamic_gate invokes substage E1 when
thermo_backtracking_enable is true; otherwise it raises RuntimeError.
E1 — Thermodynamic backtracking (_thermo_backtracking_search; when
thermo_backtracking_enable is true). The full trial violates (167) (or the
physical guard when enabled). Cutting \(\Delta t^\ast\) and re-solving would repeat \(\text{SNES}\); E1
searches instead along the chord in chart space between the backup \(\mathbf{u}^n\) and the trial
\(\mathbf{u}^{\mathrm{trial}}\). The search uses a blend parameter \(\lambda\in(0,1]\), with
\(\lambda=1\) at the trial and smaller values closer to the backup:
Each trial applies this update, leaves \(\Delta t^\ast\) and the BDF stencil on
\([t^n,t^{n+1}]\) unchanged, refreshes \(\dot{\mathbf{q}}\), and re-evaluates \(\|\mathbf{F}\|\),
\(E_{\mathrm{gate}}\), \(D_q\), \(W_{\mathrm{ext}}\), and \(B\) without calling \(\text{SNES}\).
Trials take \(\lambda \in \{\sigma,\,\sigma^2,\,\ldots\}\) with
\(\sigma=\) thermo_backtracking_shrink, starting at \(\lambda=\sigma\) and never retesting
\(\lambda=1\), until thermo_backtracking_min_lambda or thermo_backtracking_max_iter. At each
\(\lambda\) the implementation applies the tests in order:
If \(g_{\mathrm{res}}\) is defined and \(\|\mathbf{F}\|>g_{\mathrm{res}}\), discard this \(\lambda\) and continue the search. E1 is a thermodynamic search, but a blend that fails the residual gate from phase (D) is not eligible for acceptance.
If \(\Delta E_{\mathrm{gate}} > \max(0,-W_{\mathrm{ext}})+B\), discard this \(\lambda\) and continue.
If the physical guard is enabled and \(\Delta E_{\mathrm{phys}}\) exceeds its allowed increase, discard this \(\lambda\) and continue.
Otherwise retain this blend and stop.
Success (some \(\lambda\) passes all applicable tests above):
_commit_thermo_backtrackretains the blended state, updates energies, and refreshes the atlas guard.Failure (no \(\lambda\) in the search passes): the attempt raises
RuntimeError, the driver restores \(\mathbf{u}^n\), and reduces \(\Delta t^\ast\) with an overshoot-aware shrink factor. E1 is the only backtracking substage that triggers this hard rejection.
Temporal inconsistency when \(\lambda<1\) is accepted after E1.
E1 uses the same state-space line search as D1: the BDF operators and the output clock refer to the
full interval \([t^n,t^{n+1}]\) of length \(\Delta t^\ast\), while a damped accept stores a
configuration on the chord at \(\lambda<1\). _finalize_accepted_step labels that state as
\(\mathbf{m}^{n+1}\) at \(t^{n+1}=t^n+\Delta t^\ast\). The discrete trajectory therefore
records a magnetization increment over \([t^n,t^{n+1}]\) that is smaller than the \(\text{SNES}\) predictor
at \(\lambda=1\) would imply, and the BDF multistep history is advanced as after any full step.
The temporal mismatch is the same in structure as for damped D1 acceptance: neither substage assigns
an intermediate time label \(t^n+\lambda\Delta t^\ast\) to the stored state.
The two substages are not equally strict in what they certify on a damped accept. D1 requires only \(\|\mathbf{F}\|\le g_{\mathrm{res}}\); a damped D1 state can still violate (167). E1 requires (167) directly, so a damped E1 accept is tied to the dissipation inequality even though the time labelling remains approximate. That physical certification does not remove the temporal artefact: the BDF history still advances with a state that is not the implicit root at \(\lambda=1\) over the full step. A damped E1 accept is not temporally more faithful than a damped D1 accept; it is energetically more constrained. E1 accepts the same time-labelling compromise when damping restores the dissipation budget without cutting \(\Delta t^\ast\).
Atlas closure (phase F)¶
Figure 7. Phase (F): atlas closure and energy-jump gate.
Phase (F) (_apply_closure_gate) runs after phases (B)–(E) on a trial that passed the dynamical
gates. Phases (A)–(E) advance and test the chart fields returned by \(\text{SNES}\) or backtracking with
apply_closure=False; the inactive chart can then remain inconsistent with the blended direction
\(\mathbf{m}\) ((144)) even though \(\mathbf{m}\) itself is acceptable.
Before storage and I/O the representation must be repaired: _prepare_state_for_acceptance with
apply_closure=True updates atlas masks, reconstructs the inactive chart from the active one
(_reconstruct_inactive_chart_from_active), and refreshes auxiliary fields. This step does not
change \(\mathbf{m}\); it aligns the stereographic coordinates so the two-chart atlas is
internally consistent.
When thermo_use_split_energy is true, the jump is monitored on \(E_{\mathrm{split}}\), which
depends on how energy is distributed between charts (Energy functionals). A consistent repair
should produce only a small representational shift; a large jump means the inactive chart could not be
reconciled with the accepted dynamic state without materially altering the split energy. Let
\(E_{\mathrm{pre}}=E_{\mathrm{gate}}\) before closure and \(E_{\mathrm{post}}\) the same
functional afterward:
The tolerance scale combines an absolute floor with a term proportional to the energy magnitude:
with \(B_{\mathrm{abs}}=\) _atlas_closure_jump_abs_floor and
\(\tau_{\mathrm{rel}}=\) _atlas_closure_jump_rel_tol. The absolute floor prevents the test
from collapsing toward zero when energies are small; the relative term tracks the scale of
\(E_{\mathrm{gate}}\) when demag or other contributions make \(|E_{\mathrm{pre}}|\) large.
The attempt is rejected if
with \(f_{\mathrm{reject}}=\) _atlas_closure_jump_reject_factor.
Success (\(|\Delta E_{\mathrm{closure}}| \le f_{\mathrm{reject}}\,\tau_{\mathrm{jump}}\), or
transition_reconcile_active):_prepare_state_for_acceptanceleaves the atlas-closed charts in the attempt state;attenergies are refreshed from the post-closure configuration, \(\Delta E_{\mathrm{closure}}\) is stored inaccepted_closure_jump_energyandlast_closure_jump_energy, and the attempt proceeds to commit. After a full accept,_finalize_accepted_stepruns_apply_post_accept_atlas_closurewithapply_closure=Falseto update closure-monitor telemetry only.Soft warning (\(\tau_{\mathrm{jump}} < |\Delta E_{\mathrm{closure}}| \le f_{\mathrm{reject}}\,\tau_{\mathrm{jump}}\) and not
transition_reconcile_active): the closed state is retained as for success;last_closure_jump_warnis set and a diagnostic line is printed when_debug_logsis enabled. The attempt is not rejected.Failure (\(|\Delta E_{\mathrm{closure}}| > f_{\mathrm{reject}}\,\tau_{\mathrm{jump}}\) and not
transition_reconcile_active):_apply_closure_gateraisesRuntimeError;_handle_step_rejectionrestores \(\mathbf{u}^n\), halves \(\Delta t^\ast\) (nonthermo rejection path), and the Δt-retry loop continues. Closure is not committed—the pre-closure trial from phases (A)–(E) is discarded.
Rejection and recovery¶
Not every gate failure leads to a rejected step. Some gates have three possible outcomes —
pass, soft warning, or hard failure — and only a hard failure raises a RuntimeError that
triggers the rejection handler. Others allow the attempt to continue even when their
criterion is not fully met (substages D2 and D3 in phase D). The table in the next section
maps each phase to its possible outcomes; this section explains what the rejection handler
does when a RuntimeError is raised.
Every RuntimeError from gates (A)–(F) is caught by solve_problem and routed to
_handle_step_rejection, which always begins by calling _rollback_to_step_backup
to restore \(\mathbf{u}^n\). What happens next depends on which gate failed.
Failures handled without reducing \(\Delta t^\ast\). Two error classes are resolved by widening the solve rather than shrinking the step:
Disallowed \(\text{SNES}\) reason — \(\text{SNES}\) converged but for a reason classified as a weak certificate (e.g. reason 4). On the first such occurrence per step, the driver switches globalization mode (line-search ↔ trust-region) and retries at the same \(\Delta t^\ast\). A second occurrence within the same step falls through to the solver-divergence path and \(\Delta t^\ast\) is halved.
Outer-split non-convergence — the fixed-point loop between \(\text{SNES}\) and the BEM closure did not reach its defect tolerance. The driver widens the iteration budget by
outer_split_iter_escalationand retries at the same \(\Delta t^\ast\). Once the hard budget ceilingouter_split_max_iter_hardis reached, \(\Delta t^\ast\) is halved.
Solver-divergence failures — \(\text{SNES}\)/\(\text{KSP}\) divergence (B), atlas radius hard limit
(C), or closure energy jump (F). The driver halves \(\Delta t^\ast\) (using the
min_shrink factor), switches \(\text{SNES}\) globalization mode based on the SNES convergence
reason (reason −6 prefers line-search on first failure; reason −11 reverts to line-search;
unknown reasons alternate to avoid mode lock-in), and falls back from BDF2 to BDF1. A
growth cap on \(\Delta t^\ast\) is set for a cooldown period to prevent immediate
regrowth to the value that was just rejected.
Thermodynamic failure — E1 backtracking found no damped state that satisfies
(167). The driver cuts \(\Delta t^\ast\) by a factor proportional
to how badly the energy inequality was violated: if the trial energy overshoot is
\(r\) times the allowed budget, the shrink factor is approximately \(0.9/r\),
clamped to the interval [0.05, 0.95], so a two-fold overshoot produces roughly a
factor-2 cut while a ten-fold overshoot produces a much steeper cut. On the first
thermodynamic rejection at a given step, the driver keeps BDF2 — cutting the timestep
alone is often sufficient to satisfy the budget at the next attempt, and discarding the
BDF2 history prematurely would add unnecessary perturbation. BDF2 falls back to BDF1 on
a second thermodynamic rejection at the same step, or after any solver-divergence
failure. A growth cap (thermo_dt_cap) and a BDF1 recovery window
(be_recovery_steps_on_thermo) are set to stabilise the subsequent trajectory.
If \(\Delta t^\ast\) has reached its minimum dt_min, the handler allows a bounded
number of same-step retries by varying \(\text{SNES}\) globalization mode or BDF order before
giving up. Retry counters are maintained separately for thermodynamic
(max_retry_thermo), outer-split, and other failures (max_retry); the simulation
aborts by re-raising the RuntimeError when any counter exceeds its limit.
Phase outcomes summary¶
The figure below shows every possible outcome of one pass through phases (A)–(F). The left
spine lists the phases in order; outcomes for each phase are annotated to the right.
Phase (D) is the only phase that never raises RuntimeError: D1 backtracking failure
falls through to D2 (merit accept), D2 failure falls through to D3 (soft continue), and
D3 always continues to phase (E) without rejecting. Hard failures in (B), (C), and (F)
and E1 backtracking failure in (E) are the only paths that raise RuntimeError and
terminate the attempt.
Figure 8. Phase outcomes for one acceptance attempt. Left spine: phases (A)–(F) in order, connected by arrows. Outcome badges to the right list all possible outcomes per phase; D1 → D2 → D3 show the residual-gate fallthrough chain and E1 the thermodynamic backtracking substage.
Badge key: PASS / SKIP / RETRY (green) — the attempt continues to the next
phase or commits the step. WARN / SOFT (amber) — the attempt continues but a
diagnostic flag is set. REJECT (red) — RuntimeError is raised, \(\mathbf{u}^n\)
is restored, and the rejection handler described above is invoked.
Timestep control¶
BDF1 and BDF2 are A-stable and impose no stability ceiling on \(\Delta t^\ast\).
Accuracy constraints remain: a step that advances the magnetization by many mesh elements
in a single iteration fails to resolve mesh-scale features such as domain walls or vortex
cores, and may cause the nonlinear solver to diverge or the energy budget to be violated.
_update_timestep_cfl assembles five independent upper bounds, takes their minimum, and
applies exponential smoothing to select the next \(\Delta t^\ast\).
Upper bounds on \(\Delta t^\ast\):
\(\Delta t_{\mathrm{CFL}}\) bounds the physical displacement per step. Let \(v_{q99}\) be the 99th-percentile of the chart-invariant rate \(|\mathrm{d}\mathbf{m}/\mathrm{d}t|\) evaluated from the current BDF velocities \(\dot{\mathbf{q}}_{A,B}\), and \(h_{\min}\) the minimum element size:
(172)¶\[\Delta t_{\mathrm{CFL}} = \mathrm{CFL}\,\frac{h_{\min}}{\max(v_{q99},\varepsilon)}.\]\(\Delta t_{\mathrm{error}}\) controls the local truncation error. When BDF2 is active, the accepted history holds three blended-magnetization levels \(\mathbf{m}^{n+1},\mathbf{m}^{n},\mathbf{m}^{n-1}\). The temporal defect \(\mathbf{e}_{\mathrm{temp}}\) ((127) in Stereographic formulation) is the difference between the BDF2 and backward-Euler approximations of \(\dot{\mathbf{m}}\) at \(t^{n+1}\), evaluated on those three levels without any additional solve. Since BDF2 is second-order and backward Euler is first-order, this difference is \(O(\Delta t^2)\) and provides a local estimate of how much the second-order method corrects the first-order one over the current step: a large \(e\) signals that the trajectory is curving significantly on the timescale of \(\Delta t^\ast\) and the step should be reduced; a small \(e\) indicates that the step could be increased without incurring large truncation error. With BDF order \(p\in\{1,2\}\) and safety headroom \(\gamma=\)
error_headroom,(173)¶\[\Delta t_{\mathrm{error}} = \mathrm{clamp}\!\left( \Delta t_{\mathrm{old}}\,\gamma\, e^{-1/(p+1)},\; \bigl[\sigma_{\min}\,\Delta t_{\mathrm{old}},\; \sigma_{\max}\,\Delta t_{\mathrm{old}}\bigr] \right),\]with \(\sigma_{\min}=\)
min_shrinkand \(\sigma_{\max}=\)max_growth. The inner clamp bounds the step-size ratio to \([\sigma_{\min},\sigma_{\max}]\) in a single step. This bound is inactive when BDF1 is active, since only two history levels are available and the defect cannot be formed.\(\Delta t_{\mathrm{thermo}}\), \(\Delta t_{\mathrm{nonthermo}}\), and \(\Delta t_{\mathrm{cont}}\) are history caps set by the rejection handler after a thermodynamic failure (E1), a soft residual acceptance (D3), or a continuation-ramp completion, respectively. Each cap relaxes back toward
dt_maxover a cooldown window; maintaining them separately allows each failure mode to constrain \(\Delta t^\ast\) independently of the others.
The five bounds are combined as
The candidate is then smoothed against the previous accepted step with weight
\(\omega=\) smooth_factor:
A value \(\omega<1\) damps the response to momentary spikes in any individual bound; \(\omega=1\) recovers the unsmoothed candidate. A per-step ratio constraint is then applied:
followed by clamping to \([\Delta t_{\min},\Delta t_{\max}]\). The ratio constraint
limits growth to max_growth and shrink to min_shrink per step regardless of the
bounds. After nonlinear retries, max_growth is further reduced for a cooldown window
to prevent \(\Delta t^\ast\) from immediately recovering to the value that required
retries.
Continuation ramps¶
Solving the fully coupled LLG system from the first step can be numerically challenging: the initial magnetization configuration may be far from equilibrium under the full demagnetizing or applied field, causing the nonlinear solver to diverge or the energy budget to be violated on early steps. Continuation ramps address this by introducing a scalar prefactor \(\rho\in[0,1]\) that scales a given contribution from a prescribed starting value \(\rho_0\) to its full value \(\rho=1\) over \(N\) accepted steps.
Smoothstep profile. Both ramps share the same piecewise schedule. Let \(s\) denote the current step index and \(d\) an optional delay, so that \(\rho\) is held at \(\rho_0\) for the first \(d\) steps before any ramping begins. The ramp step index \(k=s-d\) and the normalised index \(x=k/(N-1)\) define the cubic smoothstep \(S(x)\) and the ramp factor:
so that \(\rho(0)=\rho_0\) and \(\rho(N-1)=1\). The full piecewise schedule is
The cubic \(S\) satisfies \(S'(0)=S'(1)=0\), so the prefactor enters and exits the active ramp with zero rate of change. This avoids a discontinuous jump in the assembled residual at the first and last ramp steps, which would otherwise appear as a sudden change in effective field and perturb the nonlinear solve.
Figure 10 Ramp factor \(\rho(k)\) as a function of the normalised index \(x=k/(N-1)\) for \(\rho_0=0\). The shaded region to the left represents the optional delay phase, during which \(\rho\) is held at \(\rho_0\). The zero-derivative conditions at \(x=0\) and \(x=1\) ensure a smooth entry into and exit from the active ramp.¶
Demagnetization ramp. When demag_ramp_N is set (demag driver), the demagnetization
energy density \(W_{\mathrm{d}}\) ((124)) is multiplied by a prefactor
\(\rho_{\mathrm{dem}}(s)\), which follows schedule (178) with
\(\rho_0=\) demag_start_fraction, \(d=\) demag_ramp_delay, and
\(N=\) demag_ramp_N:
The prefactor is updated each step preamble by update_demag_ramp_factor and copied
into the PETSc coefficient demag_ramp_constant before each weak-form assembly.
While \(\rho_{\mathrm{dem}}<1\), the thermodynamic gate (phase E) is disabled:
the energy baseline is shifting step by step, so a dissipation inequality on
\(E_{\mathrm{gate}}\) is not meaningful until the full model is reached.
Zeeman ramp. When zeeman_ramp_N is set, the applied field is scaled as
following schedule (178) with \(\rho_0=\) zeeman_start_fraction,
\(d=\) zeeman_ramp_delay, and \(N=\) zeeman_ramp_N. Unlike the demag
ramp, the thermodynamic gate remains active during the Zeeman ramp: the applied-field
change is accounted for by the external-work term \(W_{\mathrm{ext}}\) ((164)),
which tracks the change in \(\mathbf{H}_{\mathrm{zee}}^\ast\) between the previous
and current accepted steps.
Transition guard. When \(\rho\) reaches 1, _mark_continuation_transition
records a transition event. On the next step preamble, before the \(\Delta t\)-retry
loop, the solver applies a stabilization bundle to allow the system to equilibrate under
the now fully-coupled model:
\(\Delta t^\ast\) is reduced by a factor
_continuation_transition_dt_shrink_factor.The history caps
continuation_transition_dt_cap,thermo_dt_cap, andnonthermo_dt_capare tightened to prevent immediate step-size recovery._bdf_active_orderis reset to 1 for_continuation_transition_be_recovery_stepsaccepted steps, discarding the BDF2 history that was built under the partial model.The thermodynamic baseline \(E^n_{\mathrm{gate}}\) and \(E^n_{\mathrm{phys}}\) are rebased to the current state, so the gate does not compare against pre-transition energies.
A reconcile window of
_continuation_transition_reconcile_stepsaccepted steps begins, during which the thermodynamic gate and the atlas closure jump check remain suspended. The window decrements on each accept and the gates re-engage once it expires.
Physical steady-state stopping¶
When convergence_settings is provided, _check_physical_convergence tests for
steady state after each accepted step. The two monitored quantities are:
Atlas-blended magnetization \(\mathbf{m}\) ((144)) — the Cartesian direction field reconstructed from both charts.
Physical energy \(E_{\mathrm{phys}}\) ((145)) — the total energy evaluated on the blended state.
These are used instead of the chart fields \((\mathbf{q}_A,\mathbf{q}_B)\) or \(E_{\mathrm{split}}\) because atlas closure (phase F) redistributes energy between the two chart representations without altering \(\mathbf{m}\), which would otherwise produce a spurious nonzero \(\Delta E_{\mathrm{split}}\) at a true steady state and reset the counter.
The first criterion measures the RMS rate of change of the magnetization direction. Let \(\Delta\mathbf{m}^n = \mathbf{m}^{n+1} - \mathbf{m}^n\) be the nodewise difference of the blended Cartesian fields at two consecutive accepted steps. The RMS rate is
The second criterion measures the relative rate of change of \(E_{\mathrm{phys}}\):
Both criteria must be satisfied simultaneously at a given step. The steady-state counter
increments when both hold and resets to zero when either fails. Termination is triggered
once the counter reaches steady_steps consecutive accepted steps:
with \(\tau_{\mathrm{m}}=\) physical_tol_mdot_rms and
\(\tau_{E}=\) physical_tol_edot.
A separate stagnation warning is emitted when the relative change in \(\|\mathbf{F}\|\)
between consecutive accepted steps falls below stagnation_tol for stagnation_steps
consecutive steps. This indicates that the algebraic residual has plateaued, which may or
may not coincide with physical steady state; the warning is informational and does not
trigger termination.
Configuration parameters¶
The table below groups configuration keys by subsystem. Keys marked internal are assigned in
init_dynamic on the driver class and are not normally set in user input files. Default values
refer to the demag/nodemag drivers unless noted; see Configuration reference for the full schema.
Subsystem |
Keys |
Role and typical defaults |
|---|---|---|
Timestep bounds |
|
(176)–(174); defaults in
|
Retry limits |
|
Δt-retry loop at fixed \(\Delta t^\ast\); thermo and operator-split counters are separate. |
Residual gate |
|
Build \(g_{\mathrm{res}}\) ((156)–(157)); EMA factor default \(8\), floor default \(10^{-6}\). |
Residual backtrack (D1) |
|
Enabled by default; \(\sigma=1/2\), \(\lambda_{\min}=1/64\), at most six trials. |
Merit / soft residual (D2/D3) |
|
Merit thresholds; D3 always caps |
Thermodynamic gate (E) |
|
(168); split energy selected by default (\(\chi_{\mathrm{split}}=\mathrm{true}\)). |
Thermodynamic backtrack (E1) |
|
Default \(\sigma=1/2\), eight trials, \(\lambda_{\min}=1/256\); failure rejects. |
Demag operator split |
|
Phase (B), demag only; default \(\tau_q=5\times 10^{-3}\), \(\tau_\varphi=10^{-3}\). |
Atlas radius |
|
Phase (C); hard limit \(g_{\mathrm{hard}} = f_{\mathrm{hard}}\, r_{\mathrm{guard}}\); re-evaluated after backtracking. |
Closure jump |
|
|
Continuation ramps |
|
Thermodynamic gate inactive while \(\lambda_{\mathrm{dem}}<1\); reconcile window after ramp completion. |
Recovery after rejection |
|
BDF1 windows; caps limit \(\Delta t^\ast\) regrowth after marginal failures. |
Diagnostics¶
The dictionary state_monitor records:
Key |
Quantity |
|---|---|
|
\(E^n_{\mathrm{gate}}\) at last acceptance |
|
\(E^n_{\mathrm{phys}}\) at last acceptance |
|
\(g_{\mathrm{res}}\) in (158) |
|
\(\delta_q\) in (150) |
|
\(\delta_\varphi\) in (151) (demag) |
|
\(\Delta E_{\mathrm{closure}}\) in (169) |
|
\(\lambda\) when thermodynamic backtracking accepted the step |
|
\(e\) in (173) |
|
Components of (176) |
Detailed thermodynamic logging is enabled by enable_thermo_diagnostics and
thermo_diag_log_accepted in args_init_dyn.