Skip to content

Fix assembly and solves with the Hessian of an interpolation - #5551

Open
pbrubeck wants to merge 34 commits into
mainfrom
danshapero/interp-hessian
Open

pbrubeck wants to merge 34 commits into
mainfrom
danshapero/interp-hessian

Conversation

@pbrubeck

@pbrubeck pbrubeck commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Resolves #5477: solving derivative(J, u) == 0 failed for a functional J containing interpolate(u, P) with P on a VertexOnlyMesh. The Hessian of the data term is Iᵀ M I.

Depends on FEniCS/ufl#532, which computes that Hessian symbolically. The DROP BEFORE MERGE commit installs that branch in CI.

Derivative expansion

  • preprocess_base_form expands the derivatives of a Form only if it has base form operators, whatever its mat_type. Plain Forms and Slate tensors are left to TSFC. Before, the "matfree" residual assembler skipped the expansion, so the chain rule through the interpolation produced a FormSum that OneFormAssembler rejects.
  • A "matfree" 2-form is still expanded only when its action is taken. MatrixFreeAssembler accepts any 2-form BaseForm, such as a FormSum, and keeps the tree intact so that a single implicit matrix assembles its action.

Dual slot of base form operators

UFL now simplifies Action(N(u; v̂), F) to N(u; F) for any base form operator and 1-form F, so the assembler can no longer split an operator from its dual slot by building an Action. Restructure case (5) and the Interpolate(v, 2-form) split are removed. Instead, an operator that cannot consume its assembled dual slot is assembled as N(u; v̂) and contracted with it:

  • an Interpolate whose dual slot assembles to a Matrix;
  • an ExternalOperator with a Cofunction or Matrix in its dual slot and no assembly method registered for it (assembly_method()).

The numerics of the Action branch move to contract, which both paths use.

Vertex-only meshes

Only the output matrix uses allocation_integral_types. An inner matrix may live on a mesh without them, for example a vertex-only mesh.

Tests

  • test_solve_interp_hessian: a Newton solve with the Hessian of an interpolation onto a vertex-only mesh.
  • test_matfree_form_sum_uses_implicit_matrix and test_matfree_slate_sum_product_uses_implicit_matrix.
  • test_reduced_functional.py::test_interpolate runs a second-order Taylor test with the Hessian.

🤖 Generated with Claude Code

Moved from fork PR #5478 to the upstream branch. Prepared with Codex.

danshapero and others added 30 commits September 22, 2026 14:28
Solving derivative(J, u) == 0 for a functional J that contains
interpolate(u, P), with P a space on a vertex-only mesh, failed in two
places in the base form assembler:

- The residual assembler of the solver uses mat_type="matfree", which
  skipped the expansion of derivatives. The chain rule through the
  interpolation turns the residual into a FormSum, which the 1-form
  assembler rejects. The mat_type only applies to 2-forms, so
  derivatives of 0-forms and 1-forms are now always expanded.
- Every inner matrix of the DAG was allocated with the integral types of
  the output matrix. The mass matrix on the vertex-only mesh then asked
  for interior facets, which that mesh does not have. Only the output
  matrix now uses those integral types.

The test also needs FEniCS/ufl#525, which fixes the symbolic Hessian.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Install UFL from danshapero/fix-bfo-hessian (FEniCS/ufl#525) in the
shared and documentation CI environments, so that CI tests this PR
against the matching UFL fix. Drop this commit once #525 merges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…late

Reconstructing an Interpolate now accepts the argument_slots keyword, and
replaces a plain UFL Coargument in the dual slot with a Firedrake Argument
on the dual of the target space. Also expand derivatives of matfree 0- and
1-forms that contain base form operators, so that the DAG traversal can
assemble them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Expanding the derivatives of a Form with base form operators may turn it
into another BaseForm, which determines how it is assembled. Plain Forms
and Slate tensors keep their type, so TSFC expands their derivatives.
A 'matfree' matrix is still expanded when its action is taken.

`preprocess_base_form` used `mat_type` to decide, so a 1-form with base
form operators, whose default `mat_type` is 'matfree', was not expanded.
`ufl.replace` then expanded it inside the assembly visitor, turning it
into a FormSum. Expand it in preprocessing instead, and drop the second
traversal in the visitor.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FEniCS/ufl#525 was split into #530, #531 and #532.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UFL now simplifies Action(N(u; v*), F) to N(u; F) for any base form
operator N and 1-form F, so the assembler can no longer split an operator
from its dual slot by building an Action: restructure case (5) and the
Interpolate split folded straight back.

Instead, when an operator cannot consume its assembled dual slot, assemble
N(u; vhat) with a coargument vhat and contract it with the assembled dual
slot:

- An Interpolate whose dual slot assembles to a Matrix.
- An ExternalOperator with a Cofunction or Matrix in its dual slot and no
  assembly method registered for it. `assembly_method()` looks this up.

The numerics of the Action branch move to `contract`, which both paths
use.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An assembled Function has no arguments, so the contraction of an external
operator with its assembled dual slot failed. Use the arguments of
N(u; vhat) instead. Rename dual_slot_coargument to
replace_dual_slot_by_coargument, which is what it does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s integrals

UFL returns a ZeroBaseForm for a vanishing derivative, and a derivative
with base form operators may be a FormSum, neither of which has
integrals(). The Hessian of a solve whose residual is linear in its
solution raised AttributeError in _assemble_soa_eq_rhs. is_zero checks
for a ZeroBaseForm or an empty Form, as AssembleBlock already does.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UFL no longer simplifies Actions and Adjoints when they are constructed.
preprocess_base_form calls ufl's restructure_base_form after expanding
derivatives, and the Interpolators restructure the Actions they build
when splitting a Cofunction dual argument.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
UFL now splits a rank 2 dual slot off a base form operator into an
Action, so an Interpolate or ExternalOperator never sees an assembled
Matrix in its dual slot.

An ExternalOperator without an assembly method for its assembled
Cofunction dual slot is now reconstructed once, with a coargument in
place of the Cofunction, and its result is contracted with the
Cofunction. This removes replace_dual_slot_by_coargument and
contracted_arguments. contract becomes a static method of
BaseFormAssembler.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It now sits next to _matrix_free_assembler, the assembler it selects.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@pbrubeck pbrubeck added the LLM used An LLM was used in the production of this PR label Oct 23, 2026
Comment thread firedrake/assemble.py Outdated
Comment thread firedrake/assemble.py Outdated
Co-authored-by: Pablo Brubeck <brubeck@protonmail.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

LLM used An LLM was used in the production of this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

interpolation inside a system matrix doesn't work

2 participants