Skip to content

feat(assemble): pass per-quadrature-point data to the element and node assemblers - #70

Merged
Shizheng-Wen merged 2 commits into
mainfrom
feat/quadrature-data
Oct 20, 2026
Merged

Shizheng-Wen merged 2 commits into
mainfrom
feat/quadrature-data

Conversation

@Shizheng-Wen

Copy link
Copy Markdown
Collaborator

What

ElementAssembler.__call__, ElementAssembler.energy and NodeAssembler.__call__ now take quadrature_data. Its tensors have shape [n_element, n_quadrature, ...], or {element_type: tensor} on meshes with several element types. Their value at the current quadrature point reaches forward by name, like any other data key:

class Tangent(ElementAssembler):          # K_ab = ∫ ∇N_a · C : ∇N_b
    def forward(self, gradu, gradv, C):
        return torch.einsum("j,ijkl,l->ik", gradu, C, gradv)

class InternalForce(NodeAssembler):       # f_a = ∫ σ ∇N_a
    def forward(self, gradv, sigma):
        return sigma @ gradv

K = Tangent.from_mesh(mesh)(quadrature_data={"C": C_alg})                 # C_alg: [E, Q, 3, 3, 3, 3]
f = InternalForce.from_mesh(mesh)(quadrature_data={"sigma": s}, batch_size=-1)

History-dependent materials need this for a Newton solve. Plasticity with a consistent tangent and a stress that depends on internal variables stored at the quadrature points is the case at hand. So far only energy() could see per-point data: it guessed the layout of element_data from shape[1] == n_quadrature, which is ambiguous when a per-element dimension equals the number of points. __call__ handed the whole [n_quadrature, ...] slice to forward, and NodeAssembler took no element data at all.

Details

  • Checks. Shapes are checked up front. A key that also appears in point_data, element_data or scalar_data, or that is a built-in name (u, v, x, …), is rejected.
  • Batching. The data is split together with the quadrature points when batch_size > 0.
  • Compiled path. A compiled NodeAssembler falls back to the vmap path for calls with quadrature data.
  • Bug fixed. energy() now slices per-point element_data along quadrature batches too. Before, batch_size > 0 with per-point history failed with a shape error.
  • Docs. The forms user guide (EN + zh) lists the new dict in the data-plumbing table and the feature matrix.

Tests

tests/assemble/test_quadrature_data.py (new) checks:

  • the element matrix and the node vector against manual einsum assembly from Transformation.shape_grad/JxW;
  • that a constant isotropic tangent reproduces LinearElasticityElementAssembler;
  • that batch sizes 1, 2 and 4 agree with the unbatched result;
  • energy() with quadrature_data and with per-point element_data in batches, including its gradient with respect to the data;
  • error messages;
  • CUDA.

Full suite on Clariden (aarch64) in a CPU conda env with gmsh: 548 passed, 18 skipped, 0 failed. Main gives 539 passed, 17 skipped; the extra skip is the CUDA test. The new tests also pass on a GH200 node, CUDA test included. The docs build adds no warnings (EN and zh: 21 → 21).

🤖 Generated with Claude Code

https://claude.ai/code/session_01Dst8zhPLnxVpZMBhR4Pvfs

Shizheng-Wen and others added 2 commits October 19, 2026 22:58
…e assemblers

ElementAssembler.__call__, ElementAssembler.energy and
NodeAssembler.__call__ take `quadrature_data`: tensors of shape
[n_element, n_quadrature, ...] (or {element_type: tensor} on mixed
meshes) whose value at the current point reaches `forward` by name.
This is what a consistent tangent or a stress of a history-dependent
material needs; until now only energy() could see per-point data, by
guessing the layout of element_data from shape[1] == n_quadrature.

Shapes and key clashes with other data are checked up front, the data
is split together with the quadrature points when batch_size > 0, and
a compiled NodeAssembler falls back to the vmap path for such calls.

energy() also slices per-point element_data along quadrature batches
now; with batch_size > 0 it was handed whole and the call failed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dst8zhPLnxVpZMBhR4Pvfs
Signed-off-by: Shizheng Wen <wenshizheng0710@gmail.com>
@Shizheng-Wen
Shizheng-Wen merged commit 1f3c375 into main Oct 20, 2026
4 checks passed
@Shizheng-Wen
Shizheng-Wen deleted the feat/quadrature-data branch October 20, 2026 08:28
Shizheng-Wen added a commit that referenced this pull request Oct 20, 2026
…#71)

A thick-walled cylinder under internal pressure (Hill 1950), in plane
strain, with J2 plasticity and linear isotropic hardening (perfect
plasticity by default). Every load step is solved by Newton's method
with the consistent tangent of the radial return, so the iterations
converge quadratically: three to five per plastic load step.

The plastic state lives at the quadrature points. The stress and the
tangent moduli reach an internal-force NodeAssembler and a tangent
ElementAssembler as quadrature_data (#70), and the pressure is a
FacetAssembler load along the outward normal n (#69). Displacement
control follows the collapse plateau; pressure control is an option.
The quarter ring of six-node triangles is built with NumPy, so the
example needs no gmsh.

Tests check the return map and its tangent against forward-mode
autodiff, the assembled stiffness against finite differences, the Lame
elastic response, first yield at the inner wall, the limit pressure
(2 / sqrt 3) sigma_y ln(b / a) of perfect plasticity (within 1 %), and
CUDA against CPU.

Docs: gallery page and solid-mechanics index entry, with zh_CN
translations.


Claude-Session: https://claude.ai/code/session_01Dst8zhPLnxVpZMBhR4Pvfs

Signed-off-by: Shizheng Wen <wenshizheng0710@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant