Nonlinear conjugate-gradient relaxation

Last changes: Documentation changelog

Scope and purpose

This page documents the source-backed nonlinear conjugate-gradient direct minimizer for normalized magnetization, including tangent directions, retraction, line search and completion semantics.

Scientific and numerical model

The method minimizes the resolved micromagnetic energy on the unit-spin manifold; rejected trials leave the accepted state and completion history unchanged.

Physical and numerical problem

nonlinear_cg is a direct constrained minimizer for the discrete micromagnetic energy. It searches \((\mathbb S^2)^N\) instead of integrating a physical-time LLG trajectory. Each active cell or finite-element node remains a unit vector. The tangent gradient is projected at the current state, trial states are retracted to the sphere, and accepted steps use the Polak–Ribière+ coefficient, an energy-metric product, periodic restart, and Armijo sufficient-decrease backtracking.

nonlinear_cg has no damping parameter, RK solver, adaptive timestep, physical-time duration, or LLG precession switch. Its line-search step is an internal numerical quantity, not a user supplied timestep.

Governing equations

For reduced magnetization \(\mathbf m_i\) and effective field \(\mathbf H_{\mathrm{eff},i}\), the tangent energy gradient is

(1)\[\mathbf g_i=-\left[\mathbf H_{\mathrm{eff},i} -\left(\mathbf m_i\cdot\mathbf H_{\mathrm{eff},i}\right)\mathbf m_i\right], \qquad \mathbf m_i\cdot\mathbf g_i=0.\]

The normalized sphere retraction for a trial state is

(2)\[\mathcal R_{\mathbf m_i}(\lambda\mathbf p_i) =\frac{\mathbf m_i+\lambda\mathbf p_i} {\left\lVert\mathbf m_i+\lambda\mathbf p_i\right\rVert_2}.\]

After an accepted step, the previous gradient and direction are transported into the new tangent plane. With the discretization energy metric \(\langle\cdot,\cdot\rangle_E\), the PR+ coefficient is

(3)\[\widetilde{\mathbf g}_{k-1}=P_{\mathbf m_k}\mathbf g_{k-1}, \qquad \beta_k^{\mathrm{PR+}} =\max\left(0, \frac{\left\langle\mathbf g_k, \mathbf g_k-\widetilde{\mathbf g}_{k-1}\right\rangle_E} {\left\langle\mathbf g_{k-1},\mathbf g_{k-1}\right\rangle_E} \right).\]

The next search direction is

(4)\[\mathbf p_k=-\mathbf g_k+\beta_k^{\mathrm{PR+}} P_{\mathbf m_k}\mathbf p_{k-1}, \qquad \left\langle\mathbf p_k,\mathbf g_k\right\rangle_E<0.\]

If this is not a descent direction, the implementation restarts from the negative current gradient. The shared FDM/reference policy forces a restart every 50 accepted steps. Native FEM implementations maintain their own backend state and must report the resolved policy in provenance.

The Armijo acceptance condition is

(5)\[E\!\left(\mathbf m(\lambda)\right) \leq E(\mathbf m_k)+c_1\lambda \left\langle\mathbf g_k,\mathbf p_k\right\rangle_E, \qquad c_1=10^{-4}.\]

Rejected trials halve \(\lambda\) and leave the accepted state unchanged. A lower energy or a successful line search alone is not convergence; the shared accepted-state stop contract must also be satisfied.

The implementation starts with \(\mathbf p_0=-\mathbf g_0\) and \(\lambda_0=\min(10^{-6},1/\lVert\mathbf p_0\rVert)\), with \(10^{-6}\) in \(\mathrm{m\,A^{-1}}\) and the norm computed by the unweighted vector product. Before each line search, a non-descent direction (\(\langle p,g\rangle_E\geq0\)) is replaced by \(-g\). The Polak–Ribière+ numerator uses the trial tangent projection of the previous gradient. The coefficient is clipped with \(\max(0,\cdot)\); invalid or non-positive previous-gradient metric norms give \(\beta=0\). The next direction transports the previous direction to the new tangent plane and is reset to \(-g\) if it is not a descent direction. Every 50th accepted step sets \(\beta=0\). Backtracking multiplies \(\lambda\) by \(1/2\) and allows at most 30 rejected trials.

One nonlinear-CG iteration

The accepted-step loop is:

  1. Assemble the current effective field, tangent gradient, energy and torque. Reject non-finite quantities and classify an exactly degenerate gradient as numerical stagnation.

  2. Transport the previous gradient and direction into the current tangent plane. Compute PR+ with the energy metric, force \(\beta=0\) on the 50-step restart boundary, and replace any non-descent direction by \(-\mathbf g_k\).

  3. Start the line search at the bounded initial proposal, retract \(\mathcal R_{\mathbf m}(\lambda\mathbf p_k)\), and evaluate the complete trial energy. Apply the Armijo inequality; halve and retry on rejection. The FDM/shared loop permits 30 rejected trials. Native FEM recovery paths may consume additional restart trials, which are recorded as rejected attempts rather than accepted steps.

  4. Commit only the accepted trial, preserve its field/energy snapshot for the next direction, and increment accepted_step. Rejected trials never modify the accepted state.

  5. Feed the accepted torque and energy into the shared completion controller. The line search proves sufficient decrease for one step; it does not prove equilibrium or a global minimum.

Native FEM CPU and GPU implementations additionally evaluate a representable direct-energy difference and its roundoff bound before accepting a step. This is an implementation-level acceptance proof and must be reported separately from the shared torque/energy stop result.

Symbols and SI units

Symbol

Meaning

SI unit

\(\mathbf m_i\)

reduced magnetization at active cell/node \(i\)

\(1\)

\(\mathcal R_{\mathbf m_i}\)

normalized sphere retraction

\(1\)

\(\mathbf H_{\mathrm{eff},i}\)

effective magnetic field

\(\mathrm{A\,m^{-1}}\)

\(\mathbf g_i\)

tangent energy gradient

\(\mathrm{A\,m^{-1}}\)

\(\mathbf p_i\)

tangent search direction

\(\mathrm{A\,m^{-1}}\)

\(P_{\mathbf m_i}\)

tangent-plane projection

\(1\)

\(\widetilde{\mathbf g}_{k-1}\)

transported previous gradient

\(\mathrm{A\,m^{-1}}\)

\(\beta_k^{\mathrm{PR+}}\)

Polak–Ribière+ coefficient

\(1\)

\(\lambda\)

direct-minimizer line-search step

\(\mathrm{m\,A^{-1}}\)

\(E\)

total micromagnetic energy

\(\mathrm{J}\)

\(\langle\cdot,\cdot\rangle_E\)

energy-metric inner product

dimension-dependent metric

\(c_1\)

Armijo constant

\(1\)

\(N\)

active cells or finite-element nodes

\(1\)

\(k\)

accepted minimizer iteration

\(1\)

\(\tau_{\max}\)

maximum accepted-state torque

\(\mathrm{A\,m^{-1}}\)

\(\varepsilon_\tau\)

canonical torque threshold

\(\mathrm{A\,m^{-1}}\)

\(\mu_0\)

vacuum permeability

\(\mathrm{N\,A^{-2}}\)

\(M_{s,i}\)

local saturation magnetization

\(\mathrm{A\,m^{-1}}\)

\(V_i\)

cell or nodal volume weight

\(\mathrm{m^3}\)

\(a_i\)

first vector in the energy metric

\(\mathrm{A\,m^{-1}}\)

\(b_i\)

second vector in the energy metric

\(\mathrm{A\,m^{-1}}\)

The metric used by the FDM/shared implementation is

(6)\[\langle a,b\rangle_E =\sum_i \mu_0 M_{s,i}V_i\,a_i\cdot b_i, \qquad [\langle a,b\rangle_E] =\mathrm{J\,A^{-1}}[a][b].\]

The metric weight \(\mu_0M_{s,i}V_i\) is \(\mathrm{J\,A^{-1}}\). Thus the Armijo slope \(\langle g,p\rangle_E\) is \(\mathrm{J\,A\,m^{-1}}\) for field-valued \(g\) and \(p\), and \(\lambda\langle g,p\rangle_E\) is an energy. The displacement vectors \(s\) are dimensionless, so the BB/PR products have different operand-dependent units; they must not be treated as a single unitless dot product.

For FEM the same physical metric is realized by the MFEM mass/lumped-mass operators rather than Cartesian cell volumes. The line-search parameter has units \(\mathrm{m\,A^{-1}}\) because it multiplies a field-valued direction.

Assumptions and validity

  • Every committed magnetization is normalized. A zero or non-finite retraction is a failure.

  • Gradient norms, metric products and energy differences must be finite. Degenerate gradients produce numerical stagnation, not a false converged result.

  • Armijo rejection rolls back the accepted magnetization and the backend field/energy state needed for the next trial.

  • PR+ is clipped at zero. A non-descent direction is replaced with the negative tangent gradient.

  • Direct minimizers have no physical time. max_relaxation_time_s, solver, dt, max_error, max_err, dt_min, dt_max, dt_initial, adaptive_timestep, field_refresh, and relax_alpha are invalid for nonlinear_cg.

Python API

This is the copyable stage-first pattern. It does not use fm.Problem(...).

# %% Configure nonlinear-CG relaxation
import fullmag as fm

nm = 1.0e-9
study = fm.study("nonlinear_cg_relaxation")
study.engine("fem")
study.device("cpu", precision="double")
study.mode("strict")
study.universe(mode="manual", size=(700 * nm, 250 * nm, 250 * nm))
film = study.geometry(
    fm.Box(size=(500 * nm, 125 * nm, 3 * nm), name="film"),
    name="film",
)
film.Ms = 8.0e5
film.Aex = 1.3e-11
film.m = fm.init.UniformMagnetization((1.0, 0.1, 0.0))
study.exchange()
study.stages.add_relax(
    stage_id="relax",
    algorithm="nonlinear_cg",
    tolT=1.0e-6,
    max_steps=50_000,
)

Python parameter

Type

Default

SI unit

Validation

Meaning

Backend support

ProblemIR

StudyStagesBuilder.add_relax(algorithm="nonlinear_cg")

str

“llg_overdamped”

\(1\)

supported identifier

selects NCG

FDM reference; FEM CPU/GPU native lanes

study.algorithm

StudyStagesBuilder.add_relax(tolT=...)

float

\(10^{-6}\)

\(\mathrm{T}\)

finite, positive, exclusive with tolA

public torque threshold converted to A/m

FDM/FEM

study.stop.torque_tolerance_apm

StudyStagesBuilder.add_relax(tolA=...)

float

equivalent

\(\mathrm{A\,m^{-1}}\)

finite, positive, exclusive with tolT

canonical torque threshold

FDM/FEM

study.stop.torque_tolerance_apm

StudyStagesBuilder.add_relax(max_steps=...)

int

\(50{,}000\)

\(1\)

positive integer

accepted-step budget

FDM/FEM

study.stop.max_steps

StudyStagesBuilder.add_relax(energy_tolerance=...)

float or None

None

\(\mathrm{J}\)

positive when set

accepted-energy plateau threshold

FDM/FEM

study.stop.energy_tolerance_j

StudyStagesBuilder.add_relax(max_relaxation_time_s=...)

float or None

None

\(\mathrm{s}\)

rejected for NCG

LLG-only time ceiling

none for NCG

study.stop.max_relaxation_time_s

StudyStagesBuilder.add_relax(solver=...)

str or None

None

\(1\)

rejected for NCG

LLG integrator selector

none for NCG

study.dynamics.integrator

StudyStagesBuilder.add_relax(dt=...)

float or auto or None

None

\(\mathrm{s}\)

rejected for NCG

LLG timestep

none for NCG

study.dynamics

StudyStagesBuilder.add_relax(max_error=...)

float or None

None

\(1\)

rejected for NCG

LLG adaptive error control

none for NCG

study.dynamics.adaptive_timestep.atol

StudyStagesBuilder.add_relax(max_err=...)

float or None

None

\(1\)

rejected for NCG

LLG error alias

none for NCG

study.dynamics.adaptive_timestep.atol

StudyStagesBuilder.add_relax(dt_min=...)

float or None

None

\(\mathrm{s}\)

rejected for NCG

LLG lower timestep bound

none for NCG

study.dynamics.adaptive_timestep.dt_min

StudyStagesBuilder.add_relax(dt_max=...)

float or None

None

\(\mathrm{s}\)

rejected for NCG

LLG upper timestep bound

none for NCG

study.dynamics.adaptive_timestep.dt_max

StudyStagesBuilder.add_relax(dt_initial=...)

float or None

None

\(\mathrm{s}\)

rejected for NCG

LLG initial timestep

none for NCG

study.dynamics.adaptive_timestep.dt_initial

StudyStagesBuilder.add_relax(adaptive_timestep=...)

AdaptiveTimestep or None

None

mixed

rejected for NCG

LLG adaptive policy

none for NCG

study.dynamics.adaptive_timestep

StudyStagesBuilder.add_relax(field_refresh=...)

FieldRefreshPolicy or None

None

\(1\)

rejected for NCG

LLG field-refresh policy

none for NCG

study.dynamics.field_refresh

StudyStagesBuilder.add_relax(relax_alpha=...)

float or None

None

\(1\)

rejected for NCG

LLG damping override

none for NCG

resolved LLG provenance

legacy tol=…

float

unavailable

legacy

always rejected

use tolT or tolA

none

none

StudyStagesBuilder.add_relax(max_pseudotime_s=...)

float or None

None

\(\mathrm{s}\)

rejected for NCG

LLG-only alias

none

none

StudyStagesBuilder.add_relax(max_physical_time_s=...)

float or None

None

\(\mathrm{s}\)

rejected for NCG

LLG-only alias

none

none

StudyStagesBuilder.add_relax(stop=...)

RelaxStop or None

None

mixed

grouped stop; scalar conflicts rejected

canonical stopping object

FDM/FEM

study.stop

Line-search constants, restart interval, curvature guards and backend recovery limits are implementation policy, not user parameters. They must be included in resolved provenance when they affect qualification.

Parameters

The executable controls are the algorithm identifier, torque and optional energy stop criteria, and the accepted-step budget. LLG timestep, adaptive and damping controls are explicitly rejected for this direct minimizer, as shown by the Python-to-ProblemIR table.

ProblemIR

The request lowers to this direct-minimizer payload:

{
  "kind": "relaxation",
  "algorithm": "nonlinear_cg",
  "stop": {
    "torque_tolerance_apm": 0.7957747154594767,
    "max_steps": 50000
  },
  "sampling": {
    "outputs": []
  }
}

This is an explanatory projection of the serializer, not a hand-written replacement for study.stages.add_relax(...)). There is intentionally no dynamics object for NCG. The planner adds the requested FEM/FDM lane, CPU/GPU device, precision, field realization and resolved policy as execution/provenance data.

Diagnostics and failure semantics

Record tangent-gradient norms, direction updates, Armijo trials and backtracks, accepted-step energy, torque completion, failure or cancellation status and resolved lane. A non-finite metric, invalid curvature or failed line search is not convergence.

Round-trip and failure semantics

The requested intent is preserved separately from the resolved execution record.

The exporter preserves the requested algorithm, stop fields, requested engine/device and absence of LLG dynamics. tolT is retained as authored intent while the canonical stop field is A/m. The resolved execution record separately identifies solver, device, precision, mesh and backend policy. Validation errors include unknown algorithm, legacy tol, simultaneous tolT and tolA, non-positive threshold or step budget, any LLG-only parameter, non-finite gradient or energy, unrecoverable non-descent direction, exhausted line search, failed field/energy evaluation, and an unnormalizable magnetization. Unsupported combinations are rejected by the planner; there is no silent fallback to another algorithm or device.

Discrete realization

Solver

Device

Status

Realization and evidence boundary

FDM

CPU

source-backed

cellwise tangent gradient, energy-weighted products, retraction and reference Armijo loop

FDM

GPU

source-backed with planner boundary

native CUDA direct-minimizer dispatcher exists; public multilayer planner currently permits only llg_overdamped

FEM

CPU

source-backed

native MFEM nodal gradient, mass metric, direct-energy Armijo decision, rollback and persistent direction

FEM

GPU

source-backed

native CUDA device-resident gradients, reductions, retraction, rollback and persistent direction; executed-device qualification is separate

The continuum algorithm is shared, but the discrete metric is not. FDM uses cell-volume/material weights. FEM uses its finite-element mass/lumped-mass realization and native field operators. CPU/GPU comparison requires the same problem, mesh, precision, stop contract and resolved provenance.

FDM CPU calls the reference grid loop; FDM GPU calls the native CUDA direct-minimizer dispatcher when the resolved plan permits direct minimization. The public multilayer FDM planner currently rejects PG/NCG for that runner, so “CUDA source exists” and “this multilayer script executes NCG on GPU” are different claims. FEM CPU performs native MFEM field/energy recovery and rollback; FEM GPU keeps direction, accepted field and reduction workspaces device-resident. Runtime qualification must identify the actual device and precision.

Implementation mapping

The Python stage is lowered by relax_stage and validated by Relaxation. The FDM reference loop is execute_nonlinear_cg. Shared direction, transport, restart and line-search helpers live in direct_minimizer.rs. Native FEM CPU and GPU are separate implementations: CPU owns MFEM state and direct-energy recovery; GPU owns device-resident vectors, reductions and rollback. Source presence and serialization do not prove executed GPU behavior.

Validation

Validation covers tangent orthogonality, unit-norm retraction, PR+ and restart, Armijo halving and rollback, line-search exhaustion, accepted-state torque/energy completion, stage-to-ProblemIR round-trip, and separate FDM CPU, FDM GPU, FEM CPU and FEM GPU planner/runtime gates. Python to_ir() proves authoring/lowering only; a source-map match proves traceability only.

Limitations

nonlinear_cg is a local constrained minimizer and may end in a metastable equilibrium. It does not guarantee a global minimum, fixed iteration count, identical FDM/FEM trajectories, monotone energy across every backend recovery policy, or universal GPU qualification. Its line-search step has no physical-time interpretation.

Scientific bibliography

Control Room workflow

Use the stage editor to select nonlinear_cg and configure only direct-minimizer stop controls. Inspect the resolved algorithm and completion telemetry; LLG integrator and physical-time controls are not valid controls for this stage.

Control Room crosswalk

Use Model Explorer -> Stages -> Add stage -> <stage kind> for stage-level controls when the terminal page identifies a matching field. The current editor is partial: only fields surfaced by the stage draft are authorable. Numerical parameters without a matching control are not implemented in the frontend. Do not infer frontend support from Python or backend availability. See {doc}/frontend/capability-register for the current register and exact source owner.

Where this is implemented

The source-code index below records the Python contract, reference loop, direction and line-search policy, native FEM lanes and completion policy used by this page.

Source-code index

Claim

Repository path

Stable symbol

Responsibility

Lane

Evidence

Public algorithm contract

packages/fullmag-py/src/fullmag/model/study.py

class Relaxation

validates algorithms, rejects LLG dynamics for direct minimizers, serializes ProblemIR

public API

Python contract tests

Stop fields

packages/fullmag-py/src/fullmag/model/study.py

class RelaxStop

validates torque, energy and step/time criteria

public API

Python contract tests

Stage lowering

packages/fullmag-py/src/fullmag/world.py

relax_stage

maps stage arguments into the canonical payload

public API

stage export tests

NCG direction policy

crates/fullmag-runner/src/relaxation/direct_minimizer.rs

nonlinear_cg_next_direction

tangent transport, PR+, restart and descent recovery

shared/FDM policy

Rust unit tests

NCG line search

crates/fullmag-runner/src/relaxation/direct_minimizer.rs

nonlinear_cg_armijo_accepts

Armijo acceptance predicate

shared/FDM policy

Rust unit tests

FDM reference loop

crates/fullmag-runner/src/relaxation/direct_minimizer_reference.rs

execute_nonlinear_cg

cellwise NCG implementation

FDM CPU/reference

Rust unit tests

FDM CUDA loop

crates/fullmag-runner/src/fdm/gpu/cuda/direct_minimizer.rs

execute_direct_minimizer

CUDA PG/NCG state, line search and metrics

FDM GPU

device-gated tests

FEM CPU step

backends/fem/cpu/mfem/relaxation/nonlinear_cg.cpp

run_nonlinear_cg_step

MFEM gradient, energy decision, recovery and state update

FEM CPU

native source contract tests

FEM GPU step

backends/fem/gpu/cuda/relaxation/nonlinear_cg.cpp

gpu_relax_nonlinear_cg_step

device-resident reductions, direction, retraction and rollback

FEM GPU

native source contract tests