feax.assembler
Assembler functions that work with Problem and TracedParams.
This module provides the main assembler API for finite element analysis with separated internal variables. It handles the assembly of residual vectors and Jacobian matrices for both volume and surface integrals, supporting various physics kernels (Laplace, mass, surface, and universal).
classify_volume_var
def classify_volume_var(problem: 'Problem', var: np.ndarray)
Classify a volume internal variable by its storage kind.
The decision is made from the original global array shape (before per-cell gathering), so it is unambiguous and resolved statically at trace-build time.
Returns a (kind, fe_idx) pair where kind is one of
_VAR_NODE/_VAR_CELL/_VAR_QUAD (or None for an unrecognized
layout → caller falls back to legacy sniffing), and fe_idx is the index
of the finite-element variable whose shape functions interpolate a
node-based variable. fe_idx matters only for _VAR_NODE (node-based
fields are interpolated with that variable's shape functions); it is 0
otherwise. This is what makes mixed element-type multi-variable problems
interpolate each internal variable on its own mesh rather than always on
variable 0's.
Operator Objects
class Operator()
Element-level operator for quadrature-point computations.
Provides methods for interpolating solutions, computing gradients, interpolating internal variables, and integrating over quadrature points. Used internally by kernel functions to eliminate code duplication.
Parameters
- problem (Problem): The finite element problem.
- fe_index (int): Index of the finite element variable (default 0).
eval
def eval(cell_sol: np.ndarray, shape_vals: np.ndarray = None) -> np.ndarray
Interpolate nodal solution to quadrature points.
Parameters
- cell_sol (np.ndarray): Nodal solution values, shape (num_nodes, vec).
- shape_vals (np.ndarray, optional): Shape function values. Uses volume shape functions if None.
Returns
np.ndarray Solution at quadrature points, shape (num_quads, vec).
grad
def grad(cell_sol: np.ndarray, cell_shape_grads: np.ndarray) -> np.ndarray
Compute solution gradient at quadrature points.
Parameters
- cell_sol (np.ndarray): Nodal solution values, shape (num_nodes, vec).
- cell_shape_grads (np.ndarray): Shape function gradients, shape (num_quads, num_nodes, dim).
Returns
np.ndarray Gradient at quadrature points, shape (num_quads, vec, dim).
hess
def hess(cell_sol: np.ndarray, cell_shape_hessians: np.ndarray) -> np.ndarray
Compute solution Hessian (second spatial derivatives) at quadrature points.
Parameters
- cell_sol (np.ndarray): Nodal solution values, shape (num_nodes, vec).
- cell_shape_hessians (np.ndarray): Shape function second derivatives in physical coordinates, shape (num_quads, num_nodes, dim, dim).
Returns
np.ndarray Hessian at quadrature points, shape (num_quads, vec, dim, dim). H[q, i, K, L] = sum_a u[a, i] * d²h_a/(dX_K dX_L) at quad point q.
interpolate_var
def interpolate_var(var: np.ndarray,
kind: str = None,
fe_idx: int = 0) -> np.ndarray
Interpolate a single internal variable to quadrature points.
Handles node-based (shape function interpolation), cell-based (broadcast), and quad-based (pass-through) variables.
Parameters
- var (np.ndarray): Internal variable for a single element (post-gather, post-slice).
- kind (str, optional): Static storage tag (
_VAR_NODE/_VAR_CELL/_VAR_QUAD) classified from the original global shape via :func:classify_volume_var. When provided, interpolation dispatches on the tag with no runtime shape inspection. WhenNone, falls back to the legacy shape-sniffing path (kept for backward compatibility / unclassifiable shapes). - fe_idx (int, default 0): For
_VAR_NODEvariables, the finite-element variable whose shape functions interpolate this field. Lets a node-based internal var tied to variableibe interpolated on variablei's mesh even when this Operator belongs to a different variable (mixed element types). Ignored for cell/quad layouts.
Returns
np.ndarray Values at quadrature points, shape (num_quads,).
interpolate_vars
def interpolate_vars(cell_internal_vars: Tuple[np.ndarray, ...],
kinds: Tuple = None) -> List[np.ndarray]
Interpolate all internal variables to quadrature points.
Parameters
- cell_internal_vars (tuple of np.ndarray): Internal variables for a single element.
- kinds (tuple of (str, int), optional): Static
(kind, fe_idx)descriptors, one per variable, from :func:classify_volume_var(see :meth:interpolate_var). WhenNone, every variable uses the legacy sniffing path.
Returns
list of np.ndarray Interpolated values at quadrature points.
integrate_grad
def integrate_grad(quad_values: np.ndarray,
cell_v_grads_JxW: np.ndarray) -> np.ndarray
Integrate tensor quad-point values against test function gradients.
Computes: sum_q sigma(q) : grad_v(q) * JxW(q)
Parameters
- quad_values (np.ndarray): Physics values at quad points, shape (num_quads, vec, dim).
- cell_v_grads_JxW (np.ndarray): Pre-multiplied test function gradients × JxW, shape (num_quads, num_nodes, vec, dim).
Returns
np.ndarray Element contribution, shape (num_nodes, vec).
integrate_val
def integrate_val(quad_values: np.ndarray,
cell_JxW: np.ndarray,
shape_vals: np.ndarray = None) -> np.ndarray
Integrate scalar/vector quad-point values with shape functions.
Computes: sum_q f(q) * N(q) * JxW(q)
Parameters
- quad_values (np.ndarray): Physics values at quad points, shape (num_quads, vec).
- cell_JxW (np.ndarray): Jacobian determinant × quadrature weights, shape (num_quads,).
- shape_vals (np.ndarray, optional): Shape function values. Uses volume shape functions if None.
Returns
np.ndarray Element contribution, shape (num_nodes, vec).
gather_internal_vars
@staticmethod
def gather_internal_vars(problem: 'Problem',
traced_params: Tuple[np.ndarray, ...],
ts=None) -> List[np.ndarray]
Gather global internal variables to per-cell format.
Transforms node-based variables from global (num_nodes,) arrays to per-cell (num_cells, num_nodes_per_elem) arrays using element connectivity. Cell-based and quad-based variables are passed through.
Parameters
- problem (Problem): The finite element problem with connectivity information.
- traced_params (tuple of np.ndarray): Global internal variables.
Returns
list of np.ndarray Per-cell internal variables ready for vmapped kernels.
interpolate_to_quad_points
def interpolate_to_quad_points(var: np.ndarray, shape_vals: np.ndarray,
num_cells: int, num_quads: int) -> np.ndarray
Interpolate node-based or cell-based values to quadrature points.
.. deprecated::
Use :meth:Operator.interpolate_var instead.
This function handles three cases:
- Node-based: shape (num_nodes,) -> interpolate using shape functions
- Cell-based: shape (num_cells,) -> broadcast to all quad points in cell
- Quad-based: shape (num_cells, num_quads) -> pass through (legacy)
Parameters
- var (np.ndarray): Variable to interpolate. Can be: - (num_nodes,) for node-based - (num_cells,) for cell-based - (num_cells, num_quads) for quad-based (legacy)
- shape_vals (np.ndarray): Shape function values at quadrature points, shape (num_quads, num_nodes)
- num_cells (int): Number of cells/elements
- num_quads (int): Number of quadrature points per cell
Returns
np.ndarray Values at quadrature points, shape (num_quads,)
get_laplace_kernel
def get_laplace_kernel(problem: 'Problem',
tensor_map: Callable,
var_kinds: Tuple[str, ...] = None) -> Callable
Create Laplace kernel function for gradient-based physics.
The Laplace kernel handles gradient-based terms in the weak form, such as those arising in elasticity, heat conduction, and diffusion problems. It implements the integral term: ∫ σ(∇u) : ∇v dΩ where σ is the stress/flux tensor computed from the gradient.
Parameters
- problem (Problem): The finite element problem containing mesh and element information.
- tensor_map (Callable): Function that maps gradient tensor to stress/flux tensor. Signature: (u_grad: ndarray, *traced_params) -> ndarray where u_grad has shape (vec, dim) and returns (vec, dim).
Returns
Callable Laplace kernel function that computes the contribution to the weak form from gradient-based physics.
get_mass_kernel
def get_mass_kernel(problem: 'Problem',
mass_map: Callable,
var_kinds: Tuple[str, ...] = None) -> Callable
Create mass kernel function for non-gradient terms.
The mass kernel handles terms without derivatives in the weak form, such as mass matrices, reaction terms, or body forces. It implements the integral term: ∫ m(u, x) · v dΩ where m is a mass-like term.
Parameters
- problem (Problem): The finite element problem containing mesh and element information.
- mass_map (Callable): Function that computes the mass term. Signature: (u: ndarray, x: ndarray, *traced_params) -> ndarray where u has shape (vec,), x has shape (dim,), and returns (vec,).
Returns
Callable Mass kernel function that computes the contribution to the weak form from non-gradient physics.
get_surface_kernel
def get_surface_kernel(problem: 'Problem', surface_map: Callable) -> Callable
Create surface kernel function for boundary integrals.
The surface kernel handles boundary integrals in the weak form, such as surface tractions, pressures, or fluxes. It implements the integral term: ∫ t(u, x) · v dΓ where t is the surface load/flux.
Parameters
- problem (Problem): The finite element problem containing mesh and element information.
- surface_map (Callable): Function that computes the surface traction/flux. Signature: (u: ndarray, x: ndarray, *traced_params) -> ndarray where u has shape (vec,), x has shape (dim,), and returns (vec,).
Returns
Callable Surface kernel function that computes the contribution to the weak form from boundary loads/fluxes.
create_volume_kernel
def create_volume_kernel(problem: 'Problem',
var_kinds: Tuple = None) -> Callable
Create the unified volume kernel for residual / Jacobian assembly.
Composition rules (consistent for single- and multi-variable problems):
-
Base element residual (full physics, chosen exclusively):
get_universal_kernel()— if defined, it is the complete element residual (full low-level control); the standard pieces below are not added. This is the same "escape hatch" meaning in both single- and multi-variable problems.- otherwise, for multi-variable problems:
get_weak_form()(raises if neither is defined). - otherwise, for single-variable problems:
get_tensor_map()(orget_energy_density()differentiated viajax.grad) plusget_mass_map()— these are complementary standard pieces and are summed.
-
Extra additive kernel (optional):
get_extra_kernel()is always added on top of the base, regardless of which base was chosen. Use it for complementary low-level terms (e.g. a regularization or stabilization term layered on standard physics) — the additive counterpart of the full-replacementget_universal_kernel().
Parameters
- problem (Problem): Defines some subset of
get_tensor_map/get_energy_density/get_mass_map/get_weak_form/get_universal_kernel/get_extra_kernel.
Returns
Callable
Element kernel base(...) + extra(...).
create_surface_kernel
def create_surface_kernel(problem: 'Problem', surface_index: int) -> Callable
Create unified surface kernel for a specific boundary.
This function creates a kernel that combines contributions from standard surface maps and universal surface kernels for a specific boundary surface identified by surface_index.
Parameters
- problem (Problem): The finite element problem that may define get_surface_maps() and/or get_universal_kernels_surface() methods.
- surface_index (int): Index identifying which boundary surface this kernel is for. Corresponds to the index in problem.location_fns.
Returns
Callable Combined surface kernel function for the specified boundary.
Notes
Multiple boundaries can have different physics. The surface_index parameter selects which surface map and universal kernel to use.
For multi-variable problems, only universal_kernels_surface should be used, as get_surface_maps() only supports single-variable problems.
split_and_compute_cell
def split_and_compute_cell(problem: 'Problem',
cells_sol_flat: np.ndarray,
jac_flag: bool,
internal_vars_volume: Tuple[np.ndarray, ...],
ts=None) -> Any
Compute volume integrals for residual or Jacobian assembly.
This function evaluates volume integrals over all elements, optionally computing the Jacobian via forward-mode automatic differentiation. It uses batching to manage memory for large meshes.
Parameters
- problem (Problem): The finite element problem containing mesh and quadrature data.
- cells_sol_flat (np.ndarray): Flattened solution values at element nodes. Shape: (num_cells, num_nodes * vec).
- jac_flag (bool): If True, compute both values and Jacobian. If False, compute only values.
- internal_vars_volume (tuple of np.ndarray): Material properties at quadrature points for each variable. Each array has shape (num_cells, num_quads).
Returns
np.ndarray or tuple of np.ndarray If jac_flag is False: weak form values with shape (num_cells, num_dofs). If jac_flag is True: tuple of (values, jacobian) where jacobian has shape (num_cells, num_dofs, num_dofs).
Notes
The function splits computation into batches (default 20) to avoid memory issues with large meshes. This is particularly important for 3D problems.
compute_face
def compute_face(problem: 'Problem',
cells_sol_flat: np.ndarray,
jac_flag: bool,
internal_vars_surfaces: List[Tuple[np.ndarray, ...]],
ts=None) -> Any
Compute surface integrals for residual or Jacobian assembly.
This function evaluates surface integrals over all boundary faces, optionally computing the Jacobian via forward-mode automatic differentiation.
Parameters
- problem (Problem): The finite element problem containing boundary information.
- cells_sol_flat (np.ndarray): Flattened solution values at element nodes. Shape: (num_cells, num_nodes * vec).
- jac_flag (bool): If True, compute both values and Jacobian. If False, compute only values.
- internal_vars_surfaces (list of tuple of np.ndarray): Surface variables for each boundary. Each entry corresponds to one boundary surface and contains arrays with shape (num_surface_faces, num_face_quads).
Returns
list of np.ndarray or list of tuple If jac_flag is False: list of weak form values for each boundary. If jac_flag is True: list of (values, jacobian) tuples for each boundary.
Notes
Each boundary surface can have different loading conditions or physics, handled through separate surface kernels and internal variables.
compute_residual_vars_helper
def compute_residual_vars_helper(problem: 'Problem',
weak_form_flat: np.ndarray,
weak_form_face_flat: List[np.ndarray],
ts=None) -> List[np.ndarray]
Assemble residual from element and face contributions.
This helper function assembles the global residual vector by accumulating contributions from volume and surface integrals at the appropriate nodes.
Parameters
- problem (Problem): The finite element problem containing connectivity information.
- weak_form_flat (np.ndarray): Flattened weak form values from volume integrals. Shape: (num_cells, num_dofs_per_cell).
- weak_form_face_flat (list of np.ndarray): Weak form values from surface integrals for each boundary. Each array has shape (num_boundary_faces, num_dofs_per_face).
Returns
list of np.ndarray Global residual for each solution variable. Each array has shape (num_total_nodes, vec).
Notes
Volume and all boundary contributions are concatenated into one value array
and reduced with a single sorted segment_sum over the precomputed DOF
scatter map (problem.res_perm / res_sorted_dofs). This replaces the
1 + num_boundaries scatter-adds with one deterministic reduction (no
atomics), matching the CSR-direct Jacobian assembly.
get_jacobian
def get_jacobian(problem: 'Problem',
sol_list: List[np.ndarray],
traced_params: TracedParams,
ts=None) -> 'CSRMatrix'
Assemble the global tangent (Jacobian) as a :class:feax.csr.CSRMatrix.
Companion to :func:get_res (which assembles the global residual): this
assembles the element tangents into the global Jacobian without applying
Dirichlet boundary conditions, returned as the deduplicated CSR triple
(data, indptr, indices) wrapped in a :class:~feax.csr.CSRMatrix. It is
the entry point for callers that need the raw assembled operator — e.g.
building the material/geometric stiffness pair (K, K_g) for the
linear-buckling eigensolver
(:func:feax.solvers.eigen.create_linear_buckling_solver).
The assembly is already CSR-native (:func:_get_J_csr); returning the CSR
matrix directly avoids the redundant BCOO round-trip (stack to COO-style
indices here, re-sort back to CSR in the consumer). CSRMatrix supports
@ (mat-vec), .todense(), .T, etc.
For the solver stack, prefer the CSR-direct, BC-applied assembly
(:func:get_res9); for cheap statistics without
materializing the matrix, use :func:``0.
Parameters
- problem (Problem): The finite element problem containing mesh and physics definitions.
- sol_list (list of np.ndarray): Solution arrays for each variable.
- traced_params (TracedParams): Container with material properties and loading parameters.
- ts (TracedStructure, optional): When given, assembles on the TracedStructure path (avoids the no-TracedStructure host slot maps / deprecation warning). Omit it on a problem whose host scratch is still alive (
TracedStructure.from_problem(problem, free_scratch=False)).
Returns
feax.csr.CSRMatrix
The assembled global Jacobian (no BCs applied), shape
(num_total_dofs_all_vars, num_total_dofs_all_vars), with nse
deduplicated nonzeros.
get_jacobian_info
def get_jacobian_info(problem: 'Problem', sol_list: List[np.ndarray],
traced_params: TracedParams) -> dict
Get Jacobian matrix information without full matrix construction.
This function provides safe access to Jacobian statistics that works
correctly with JIT-compiled solvers and cuDSS backend. Unlike the
full assembly in :func:get_jacobian, this does not cause GPU memory conflicts.
Parameters
- problem (Problem): The finite element problem definition.
- sol_list (list of np.ndarray): Solution arrays for each variable.
- traced_params (TracedParams): Internal variables container.
Returns
dict Dictionary containing:
- 'nnz': Number of non-zero entries (int)
- 'shape': Matrix shape (tuple)
- 'matrix_view': Matrix storage format (MatrixView enum)
Examples
>>> info = get_jacobian_info(problem, sol_list, traced_params)
>>> print(f"Jacobian NNZ: {`info['nnz']:,`}")
>>> print(f"Matrix view: {`info['matrix_view'].name`}")
Notes
This function is safe to call from user code and does not interfere with JIT-compiled solvers using cuDSS backend.
get_res
def get_res(problem: 'Problem',
sol_list: List[np.ndarray],
traced_params: TracedParams,
ts=None) -> List[np.ndarray]
Compute residual vector with separated internal variables.
Assembles the global residual vector by evaluating the weak form at the current solution state. Includes contributions from both volume and surface integrals.
Parameters
- problem (Problem): The finite element problem containing mesh and physics definitions.
- sol_list (list of np.ndarray): Solution arrays for each variable. Each array has shape (num_total_nodes, vec).
- traced_params (TracedParams): Container with material properties and loading parameters.
Returns
list of np.ndarray Residual arrays for each solution variable. Each array has shape (num_total_nodes, vec).
Examples
>>> residual = get_res(problem, [solution], traced_params)
>>> res_norm = np.linalg.norm(jax.flatten_util.ravel_pytree(residual)[0])
>>> print(f"Residual norm: {`res_norm`}")
Notes
The residual represents the imbalance in the weak form equations. For converged solutions, the residual should be near zero.
create_J_bc_csr_function
def create_J_bc_csr_function(
problem: 'Problem',
bc: 'DirichletBC',
symmetric: bool = True
) -> Callable[[np.ndarray, TracedParams], 'CSRMatrix']
Assemble the BC-applied Jacobian directly as a deduplicated CSRMatrix.
Returns (sol_flat, traced_params) -> CSRMatrix that assembles the
BC-applied Jacobian straight into deduplicated CSR form — no BCOO, no
per-solve sum_duplicates sort — using the slot map precomputed in
:meth:Problem._build_csr_assembly_structure, ready for direct backends
(cuDSS/cholmod/umfpack/spsolve) without conversion.
create_J_bc_csr_parametric
def create_J_bc_csr_parametric(problem: 'Problem',
symmetric: bool = True) -> Callable
Parametric form of :func:create_J_bc_csr_function.
Like :func:create_J_bc_csr_function but takes bc as an explicit third
argument so it traces through the BC pytree (vmap / per-step BC values).
create_res_J_bc_csr_parametric
def create_res_J_bc_csr_parametric(problem: 'Problem',
symmetric: bool = True) -> Callable
Fused BC-applied residual + CSR Jacobian, bc as an explicit argument.
Returns (sol_flat, traced_params, bc) -> (res_bc, J_bc_csr) computed from
a single element-kernel pass (see :func:_get_res_J_csr) — used by the
Newton step so it does not evaluate the volume kernel twice (once for the
residual, once for the Jacobian).
create_matfree_res_J_parametric
def create_matfree_res_J_parametric(problem: 'Problem',
symmetric: bool = True) -> Callable
Matrix-free counterpart of :func:create_res_J_bc_csr_parametric (Krylov).
Returns (sol_flat, traced_params, bc) -> (res_bc, J_matvec) where
J_matvec(v) applies the BC-eliminated tangent without assembling it:
the bulk action K @ v is a forward-mode jax.jvp of the residual, and
the Dirichlet rows/columns are handled by masking. It reproduces
apply_boundary_to_J_csr(_get_J_csr(...)) @ v exactly. For symmetric BC the
operator is symmetric (Jᵀ = J), so the same matvec serves the adjoint.
This is the operator the Krylov (cg/bicgstab/gmres) solvers consume — they never need the matrix entries, only this matvec, so the CSR assembly is skipped entirely on the iterative path.
create_matfree_Kt_parametric
def create_matfree_Kt_parametric(problem: 'Problem') -> Callable
Matrix-free K_bulk^T (un-eliminated residual transpose) for the
symmetric-BC adjoint correction.
Returns (sol_flat, traced_params) -> (w -> K_bulk^T @ w) via reverse-mode
jax.vjp of the residual — used to recover the correct bc_vals
gradient without assembling the bulk Jacobian.
create_res_bc_function
def create_res_bc_function(
problem: 'Problem',
bc: 'DirichletBC') -> Callable[[np.ndarray, TracedParams], np.ndarray]
Create residual function with Dirichlet BC applied.
Returns a function that computes the residual vector with Dirichlet boundary conditions enforced. The BC application zeros out residuals at constrained DOFs.
Parameters
- problem (Problem): The finite element problem definition.
- bc (DirichletBC): Dirichlet boundary condition specifications.
Returns
Callable Function with signature (sol_flat, traced_params) -> np.ndarray that returns the BC-modified residual vector.
Notes
The returned function is used in Newton solvers to find solutions that satisfy both the weak form equations and boundary conditions.
create_res_bc_parametric
def create_res_bc_parametric(problem: 'Problem') -> Callable
Create a residual function that takes bc as an explicit argument.
Unlike :func:create_res_bc_function which captures bc in a closure,
this version accepts bc as a third argument. This enables a single
JIT-compiled function to be reused across time steps where only the
prescribed BC values change (same DOF locations, different values).
Parameters
- problem (Problem): The finite element problem definition.
Returns
Callable
Function with signature (sol_flat, traced_params, bc) -> np.ndarray.
create_energy_fn
def create_energy_fn(problem) -> Callable
Create a total-energy integration function from a feax Problem.
Builds a pure JAX function that integrates the problem's energy density over the domain::
E(u) = ∫ ψ(∇u, *traced_params) dΩ
The energy density is obtained from problem.get_energy_density(). This
is the same density the residual assembler differentiates (tensor_map = jax.grad(energy_density)) — :func:create_energy_fn exposes the scalar
energy itself, which is useful for objective evaluation (e.g. compliance /
stored energy) and energy-based post-processing.
Parameters
- problem (feax.Problem): A problem defining
get_energy_density()(must return non-None).
Returns
- energy (callable):
energy(u_flat)orenergy(u_flat, traced_params)returning a scalar. Withouttraced_paramsthe density receives only∇u; with it, each volume variable is interpolated to quadrature points (node-based via shape functions, cell-based by broadcast) and passed as extra argumentsψ(∇u, var0_q, var1_q, …).
Raises
ValueError
If problem.get_energy_density() returns None.