neml2.es#
Python-native equation-system assembly for implicit updates.
Layered:
_helpers– raw-tensor helpers shared across the layer.axis_layout–AxisLayout.assembled–AssembledVector,AssembledMatrix,norm()/norm_sq()(forwarded toTensor).sparse–SparseVector,SparseMatrix(typed per-variable duals of the assembled forms; the symmetric API boundary surface).system–LinearSystem,NonlinearSystem,ModelNonlinearSystem.implicit– AOTI export wrappers for the implicit-segment Newton path: operator graphs (RHS,Jacobian,JacobianGiven,DrDParam) + solve graphs (LinearSolve,LinearSolveIFT,LinearSolveParam); the linear solve is un-baked from the operators.
- class neml2.es.AssembledMatrix(row_layout, col_layout, tensors=<factory>)[source]#
Bases:
object2D grid of per-(row_group, col_group) tensor blocks.
Per-block storage decision:
both row group AND col group dense -> shape
(*dyn, row_storage_with_sub_folded, col_storage_with_sub_folded);base_ndim=2,sub_batch_ndim=0.any side block -> that side’s sub_batch is preserved as intmd dims. Shape
(*dyn, *intmd_row, *intmd_col, row_storage, col_storage);base_ndim=2,sub_batch_ndim = len(intmd_row) + len(intmd_col).
matmul:
mm(aik, bkj)contracts the trailing two base axes.if
col_layout.structure[k] == "block"(the inner group is block), a sub_batch reduction (sumover the intmd dims contributed by group k) runs after the per-block mm. This is the v2-parity “block intmd_sum after mm” pattern.
- Parameters:
row_layout (AxisLayout)
col_layout (AxisLayout)
- col_layout: AxisLayout#
- disassemble()[source]#
Unpack per-block tensors into a
SparseMatrix.Each cell
cells[row_var][col_var]is a typed dynamic-baseTensorthat carries the block’sbatch_ndim/sub_batch_ndimverbatim. For"dense"blocks the sub_batch axes have been folded into base, so the slices are flat in row + col. Boundary callers (e.g. the pyzag interface) read the underlying dict-of-dict via.cellsand unwrap to raw.dataat their own framework boundary.- Return type:
- row_layout: AxisLayout#
- static select_blocks(row_layout, col_layout, blocks)[source]#
Build an
AssembledMatrixfrom per-(row_var, col_var) typed blocks.Inverse of
disassemble(). Eachblocks[row_name][col_name]is a typed dynamic-baseTensorwithbase_ndim=2andbase_shape == (row_storage, col_storage)sized to match_var_storage()for the given layout structure. Missing (row_var, col_var) pairs are zero-filled.Only the dense-x-dense case is supported (no intmd sub_batch dims on either side) – that’s the only path the pyzag interface and the round-trip test currently exercise. Block-structure layouts would need the corresponding sub_batch axes to live in each block’s storage and the cat axes shifted accordingly; raise so callers don’t silently get the wrong shape.
- Parameters:
row_layout (AxisLayout)
col_layout (AxisLayout)
- Return type:
- class neml2.es.AssembledVector(layout, tensors)[source]#
Bases:
objectPer-group dense vector blocks.
Each group’s tensor follows the
SubBatchStructureof the owningAxisLayout:"block"->(*dyn, *sub_batch, group_storage_size),base_ndim=1,sub_batch_ndim=len(sub_batch_shape)."dense"->(*dyn, group_storage_size_with_sub_folded),base_ndim=1,sub_batch_ndim=0.
Arithmetic and dot products forward to
Tensorops.- Parameters:
layout (AxisLayout)
- disassemble()[source]#
Unpack per-group tensors back into a
SparseVector.Inverse of
from_dict()– the per-group split point is the per-variable storage size; for"dense"groups the sub_batch is unfolded back to the declared shape before re-typing.Returns a
SparseVector(the typed dual of this object). The underlying name -> typed-wrapper dict is on.valuesfor callers that need the raw mapping.- Return type:
- classmethod from_dict(layout, values)[source]#
Pack typed-wrapper
values(one per variable) into per-group tensors.For a
"block"group, the per-variable wrappers are flattened to base then concatenated along base (last) axis – sub_batch axes stay as intermediate dims on the resulting Tensor.For a
"dense"group, each wrapper’s sub_batch is folded INTO the variable’s flat base storage before concat: per-variable contribution becomes(*dyn, sub_total * base_size), then all contributions concat to(*dyn, sum_var (sub_total * base_size)).Per CLAUDE.md rule 1:
valuesis strictly typed – rawtorch.Tensoris rejected. External boundaries wrap with the appropriateTensorWrappersubclass at the construction site.- Parameters:
layout (AxisLayout)
values (Mapping[str, TensorWrapper])
- Return type:
- layout: AxisLayout#
- class neml2.es.AxisLayout(groups, specs, sub_batch_shapes=None, structure=None)[source]#
Bases:
objectOrdered variable groups, their tensor types, per-variable sub-batch shapes, and per-group
SubBatchStructure.- Parameters:
- group_sub_batch_shape(index)[source]#
Common sub-batch shape across every variable in group
index.For a BLOCK group every variable must share the same sub_batch_shape (otherwise the block tensor can’t be a single rectangular tensor). Raises on disagreement. For a DENSE group the per-variable shapes may differ — they get folded into base on assembly — and this method returns the FIRST variable’s shape for shape inference only.
- specs: dict[str, type[TensorWrapper]]#
- sub_batch_shape(name)[source]#
Per-variable sub-batch shape (empty when the var is sub-batch-trivial).
- sub_layout(index)[source]#
Single-group sub-layout containing only
self.groups[index].- Parameters:
index (int)
- Return type:
- class neml2.es.DrDParam(system, linear_solver, param_names, selected_pairs=None)[source]#
Bases:
_ParamIFTBaseExportable operator graph for the implicit parameter sensitivity.
Contract:
(*u_groups, *g_groups, *params_per_batch) -> (A_dense, Bp)whereA = ∂r/∂uis the dense(*batch, R, U)residual Jacobian andBp = ∂r/∂θthe dense(*batch, R, P)parameter Jacobian, both formed by reverse-modetorch.autograd.gradover the flat residual (the only AD that lowers through AOTInductor – so this graph, like the oldParamIFT, is compiled strict +trace_autograd_ops). The solve is un-baked intoLinearSolveParam. Plain-batch only (the implicit-promotion guard rejects sub-batched unknowns / givens / params, soR == Uand the system is a single dense block).- Parameters:
- forward(*args)[source]#
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.
- class neml2.es.Jacobian(system, param_names=())[source]#
Bases:
_SystemModuleExportable residual-Jacobian operator graph.
Contract:
(*u_groups, *g_groups, *params) -> (*A_blocks, *b_groups)whereA_blocksis the row-major(residual_group × unknown_group)grid ofA = ∂r/∂uandb_groupsisb = -rat the current iterate. The linear solve is NOT baked here – it lives in the separateLinearSolvegraph, so the same operator can feed a direct solve today or a matrix-free iterative solver later. The C++ runtime chainsJacobian -> LinearSolvefor the Newton step (step) and reuses theb_groupstail for the line-search residual. The promoted-parameter tail is empty in the common case.- Parameters:
system (ModelNonlinearSystem)
- forward(*args)[source]#
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.
- class neml2.es.JacobianGiven(system, param_names=())[source]#
Bases:
_SystemModuleExportable
∂r/∂goperator graph – the IFT’s given-side Jacobian.Contract:
(*u_groups, *g_groups, *params) -> (*B_blocks)whereB_blocksis the row-major(residual_group × given_group)grid ofB = ∂r/∂gat the converged state. The un-baked given-side companion ofJacobian(which suppliesA = ∂r/∂u): the C++ runtime runsJacobian(forA) +JacobianGiven(forB) at the converged point and feeds both toLinearSolveIFTfor the implicit-function-theorem solvedu/dg = -A^{-1} B. Seeds the givens only. The promoted-parameter tail is the stored scalar (constant across the input Jacobian).- Parameters:
system (ModelNonlinearSystem)
- forward(*args)[source]#
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.
- class neml2.es.LinearSolve(system, linear_solver, param_names=())[source]#
Bases:
_SystemModuleExportable linear-solve graph:
(*A_blocks, *b_groups) -> (*du_groups).Reconstructs the typed
AssembledMatrixA(row_layout = residuals, col_layout = unknowns) andAssembledVectorbfrom the raw per-group blocks emitted byJacobian, then applies the configured Python linear solver (DenseLU/SchurComplement). Un-baked fromJacobianso the solver is a separate, swappable stage and the same operator can feed an iterative solver. Per-blocksub_batch_ndimis a structural constant of the layouts (group_block_sub_batch_ndim());batch_ndimfollows from the runtime tensor ndim, so this graph is batch-size agnostic. The promoted-parameter tail carries no state here (the solve is pure linear algebra over the assembled operators) but is accepted for a uniform signature.- Parameters:
system (ModelNonlinearSystem)
- forward(*args)[source]#
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.
- class neml2.es.LinearSolveIFT(system, linear_solver, selected_pairs=None, param_names=())[source]#
Bases:
_SystemModuleExportable IFT solve:
(*A_blocks, *B_blocks) -> *du_dg_pair_blocks.Reconstructs the typed
A = ∂r/∂u(blayout × ulayout, fromJacobian) andB = ∂r/∂g(blayout × glayout, fromJacobianGiven) from the raw per-group blocks, applies the configured linear solver (du/dg = -A^{-1} B, matrix RHS), then disassembles into per-(unknown, given)blocks inemitted_pairs()order – matching thejacobian_pairsmetadata. Shares the same solver as the forward Newton step (un-baked from the operators). The emitted pairs are plain-batch (guarded at compile time), butAitself may carry BLOCK unknown groups; per-blocksub_batch_ndimfollows the layout convention (group_block_sub_batch_ndim()).- Parameters:
- forward(*args)[source]#
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.
- class neml2.es.LinearSolveParam(system, linear_solver, param_names, selected_pairs=None)[source]#
Bases:
_ParamIFTBaseExportable parameter-sensitivity solve:
(A_dense, Bp) -> *du_dparam_blocks.Solves
A du/dθ = -∂r/∂θwith a densetorch.linalg.solve(exact for the plain-batch square systemDrDParamemits) and slices per-(unknown, param)block inemitted_param_pairs()order – matching theparam_jacobian_pairsmetadata. Un-baked fromDrDParam; pure linear algebra (plain compile).- Parameters:
- forward(A, Bp)[source]#
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.
- class neml2.es.LinearSystem[source]#
Bases:
objectBase class for systems with assembled operators.
- SECTION = 'EquationSystems'#
HIT section for
neml2-syntaxclassification – inherited by every registered subclass (ModelNonlinearSystemlives under[EquationSystems]in the input file).
- assemble(need_A, need_B, need_b)[source]#
- Parameters:
- Return type:
tuple[AssembledMatrix | None, AssembledMatrix | None, AssembledVector | None]
- property blayout: AxisLayout#
- property glayout: AxisLayout#
- set_g(g)[source]#
- Parameters:
g (AssembledVector | SparseVector)
- Return type:
None
- set_u(u)[source]#
- Parameters:
u (AssembledVector | SparseVector)
- Return type:
None
- property ulayout: AxisLayout#
- class neml2.es.Matvec(system, param_names=())[source]#
Bases:
_SystemModuleExportable matrix-free residual jvp:
(*u, *g, *params, *v) -> (*Jv).J.v = ∂r/∂u . vat fixed(u, g, params), wherevis a tangent in the unknown space (same per-group layout asu) andJvis in the residual space (blayout). Never assemblesA– it threads the single directionvthrough theforward(v=)chain rule (one forward pass + one pushforward), the matvec an iterative/Krylov linear solver calls each inner iteration. This is the matrix-free counterpart ofJacobian(which assembles the fullA):Matvec(u,g,v)equalsJacobian(u,g).A @ v, but at O(N^2) with no O(N^3) factorization downstream. The C++ Krylov runtime drives it (compiled loader on the AOTI route, callback on the eager route); both feed the sharedkrylov_solve.- Parameters:
system (ModelNonlinearSystem)
- forward(*args)[source]#
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.
- class neml2.es.ModelNonlinearSystem(model, unknowns, residuals=None, structure=None)[source]#
Bases:
NonlinearSystemA nonlinear system defined by a Model.
- Parameters:
- assemble(need_A, need_B, need_b)[source]#
- Parameters:
- Return type:
tuple[AssembledMatrix | None, AssembledMatrix | None, AssembledVector | None]
- classmethod from_hit(node, factory)[source]#
- Parameters:
node (nmhit.Node)
factory (_NativeInputFile)
- Return type:
- hit = <neml2.schema.HitSchema object>#
- initialize(*, u, g, dyn_shape=())[source]#
Set the state and per-variable layout from typed
SparseVectorinputs.uandgcarry their ownAxisLayout, which pins each variable’ssub_batch_shape. The system trusts these layouts directly – no separatesub_batch_ndimdict is needed because the wrappers + layout already encode the same information consistently.Each
SparseVector’svaluesmay pass typed wrappers (preferred per the wrapper-discipline rule) or rawtorch.Tensor(auto-wrapped with the input_spec’s type for caller convenience at construction sites that haven’t migrated yet – these wrap-on-entry, not wrap-on-exit, so no metadata is lost).Call sites that have raw dicts + a
sub_batch_ndimcount dict should funnel throughto_sparse()to construct the typedSparseVectorpair.- Parameters:
u (SparseVector)
g (SparseVector)
- Return type:
None
- set_g(g)[source]#
- Parameters:
g (AssembledVector | SparseVector)
- Return type:
None
- set_u(u)[source]#
- Parameters:
u (AssembledVector | SparseVector)
- Return type:
None
- set_u_from_group_raws(u_raws)[source]#
Commit per-group raw unknown tensors (the solver boundary).
Inverse of
_vector_to_per_group_raws(self.u()): each per-group raw is re-typed viawrap_group_raw()using the unknown layout’s structure, then committed throughset_u(). Used by the C++-backed Newton solver to write the converged iterate back into the system state.
- to(*args, **kwargs)[source]#
Move the underlying
Modeland any populated state to a new device / dtype.Matches torch’s
nn.Module.tosignature and convention: forwards*args/**kwargstoself.model.to(...)(coveringto(device='cuda'),to(dtype=torch.float32),to('cuda', non_blocking=True)etc.) and additionally walksself._state– the per-variable typed wrappers populated byinitialize()– moving each one throughTensorWrapper.to(). Returnsselfso call chains likesystem = neml2.load_nonlinear_system(...).to('cuda')work the same way they do fornn.Module.ModelNonlinearSystemis intentionally not annn.Module(it composes one rather than being one), so torch’sModule.tosemantics don’t reach the system automatically – this method is the bridge.- Return type:
- to_sparse(u, g, sub_batch_ndim=None)[source]#
Convert typed
(u_dict, g_dict, sub_batch_ndim)into(u_sv, g_sv)SparseVectorpair ready to pass toinitialize().Use this helper at internal call sites that already build per-variable typed-wrapper dicts + a per-variable
sub_batch_ndimcount dict (the classicImplicitUpdate/ pyzag adapter shape). Per-variablesub_batch_shapesare derived from each wrapper’s trailing batch dims so the resultingSparseVectorlayouts encode the same sub-batch structure that the wrappers carry.Per CLAUDE.md rule 1, raw
torch.Tensorinputs are rejected – wrap at the boundary first. For user-facing surfaces with no sub-batch (the typical test / notebook shape), constructSparseVector(system.ulayout, {name: typed_wrapper, ...})directly instead – no helper needed.- Parameters:
u (Mapping[str, TensorWrapper])
g (Mapping[str, TensorWrapper])
- Return type:
- class neml2.es.NonlinearSystem[source]#
Bases:
LinearSystemNonlinear system with C++-matching Newton sign convention.
- class neml2.es.RHS(system, param_names=())[source]#
Bases:
_SystemModuleExportable residual graph.
Contract:
(*u_groups, *g_groups, *params) -> (*b_groups)– per-group raw tensors.b_group = -r_groupfor each residual group; the C++ runtime computes a per-batch convergence norm by reducing each group tensor over its trailing sub_batch + base axes and summing across groups (no per-variable narrow on the hot path). The promoted-parameter tail (scalar; constant across the solve) is empty in the common case.- Parameters:
system (ModelNonlinearSystem)
- forward(*args)[source]#
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.
- class neml2.es.SparseMatrix(row_layout, col_layout, cells)[source]#
Bases:
objectPer-(row_var, col_var) cell map; the typed dual of
AssembledMatrix.cells[row_var][col_var]is the assembled per-cellTensorblock thatAssembledMatrix.tensors[i][j]holds – K has already been folded into trailing base via_tangent_block_to_trailing_k(). Construction validates that the outer keys cover every row variable inrow_layout; missing inner(row_var, col_var)entries are allowed and become zero blocks at assembly time (per-block sparsity is normal in chain-rule derivatives).- Parameters:
row_layout (AxisLayout)
col_layout (AxisLayout)
- assemble()[source]#
Walk row x col groups and pack into an
AssembledMatrix.Delegates to
AssembledMatrix.select_blocks(), which handles per-block sparsity (missing entries -> zero blocks).- Return type:
- col_layout: AxisLayout#
- row_layout: AxisLayout#
- class neml2.es.SparseVector(layout, values)[source]#
Bases:
objectPer-variable typed-wrapper vector; the typed dual of
AssembledVector.valuesmaps each variable name listed inlayout.vars()to its typed value (aTensorWrappersubclass instance). Construction validates that every layout variable is covered.Per CLAUDE.md rule 1:
valuesis strictly typed – rawtorch.Tensoris rejected. External boundaries that have raw tensors (the pyzag adapter, AOTI tracer fixtures, user code in notebooks / tests) wrap with the appropriateTensorWrappersubclass at the construction site, not by handing raw tensors to an internal neml2 helper.- Parameters:
layout (AxisLayout)
values (Mapping[str, TensorWrapper])
- items()[source]#
- Return type:
ItemsView[str, TensorWrapper]
- layout: AxisLayout#
- to(*args, **kwargs)[source]#
Move every value to a new device / dtype; returns a new SparseVector.
- Return type:
- values: Mapping[str, TensorWrapper]#
- neml2.es.norm(v)[source]#
Batched Euclidean norm over all assembled vector groups.
- Parameters:
v (AssembledVector)
- Return type:
- neml2.es.norm_sq(v)[source]#
Batched squared Euclidean norm over all assembled vector groups.
- Parameters:
v (AssembledVector)
- Return type: