Skip to content

Evaluate interpolations on the reference cell in compute_form_data - #512

Merged
jorgensd merged 5 commits into
FEniCS:mainfrom
firedrakeproject:pbrubeck/interpolate-reference-lowering
Sep 11, 2026
Merged

Evaluate interpolations on the reference cell in compute_form_data#512
jorgensd merged 5 commits into
FEniCS:mainfrom
firedrakeproject:pbrubeck/interpolate-reference-lowering

Conversation

@pbrubeck

@pbrubeck pbrubeck commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Description

A form compiler evaluates an Interpolate on the reference cell of its target element. The operand therefore has to be mapped to that reference cell, and the result pulled back to the physical cell for the integrand that holds it. TSFC did that in a pass of its own, run just before it called compute_form_data, and carried its own copy of the inverse Piola maps to do it.

This moves the step into UFL:

  • apply_inverse_pullback(expr, element, domain) maps a physical expression onto an element's reference cell, checking the shape on the way in and out. It is the inverse of what FunctionPullbackApplier does to a form argument, and it is TSFC's apply_mapping with the maps themselves replaced by pullback.apply_inverse.
  • InterpolatePullbackApplier rewrites each Interpolate in an integrand, and apply_interpolate_pullbacks maps it over a form.
  • compute_form_data runs it at the start, under do_apply_function_pullbacks, so the operand a form compiler sees is the one the target element dual-evaluates. It runs before the other lowering, which is where TSFC's own pass ran, so the estimated degrees and everything downstream see the same form they see today.

The node is built as a ufl.Interpolate rather than by reconstructing the one in the integrand, because the mapped operand no longer has the physical value shape that a subclass may check.

Stacking

Based on #71, which gives the pull backs their apply_inverse, which in turn is based on pbrubeck/interpolate-holes (#497), where Interpolate.ufl_element comes from.

Those are the same branches as #511 and #512, which propose the same two commits upstream against main.

Firedrake's side of the move — deleting apply_mapping, InterpolatePullbackApplier and the pre-pass in tsfc/driver.py, 200 lines in all — is on pbrubeck/form-interp-tsfc. With this branch in place, tests/tsfc and the interpolation, assembly, multigrid and vertex-only suites are green.

🤖 Generated with Claude Code

https://claude.ai/code/session_0155wQMTMSGiF9a6TbyTVK5P

pbrubeck added a commit to firedrakeproject/firedrake that referenced this pull request Sep 8, 2026
TSFC mapped an interpolation's operand onto the target element's reference
cell in a pass of its own, run before compute_form_data, and carried its own
copy of the inverse Piola maps to do it. UFL does both now, so this deletes
apply_mapping, InterpolatePullbackApplier and the pre-pass in the driver, and
preprocess_interpolate calls ufl.apply_inverse_pullback for the standalone
case.

Needs FEniCS/ufl#511 and FEniCS/ufl#512.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155wQMTMSGiF9a6TbyTVK5P
pbrubeck added a commit to firedrakeproject/firedrake that referenced this pull request Sep 8, 2026
pbrubeck/interpolate-reference-lowering is pbrubeck/interpolate-holes plus
FEniCS/ufl#511 and FEniCS/ufl#512. Revert to the plain companion branch once
those merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155wQMTMSGiF9a6TbyTVK5P
@pbrubeck
pbrubeck force-pushed the pbrubeck/interpolate-reference-lowering branch from df1414a to 336fa7a Compare September 8, 2026 10:51
pbrubeck added a commit to firedrakeproject/firedrake that referenced this pull request Sep 9, 2026
TSFC mapped an interpolation's operand onto the target element's reference
cell in a pass of its own, run before compute_form_data, and carried its own
copy of the inverse Piola maps to do it. UFL does both now, so this deletes
apply_mapping, InterpolatePullbackApplier and the pre-pass in the driver, and
preprocess_interpolate calls ufl.apply_inverse_pullback for the standalone
case.

Needs FEniCS/ufl#511 and FEniCS/ufl#512.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155wQMTMSGiF9a6TbyTVK5P
pbrubeck added a commit to firedrakeproject/firedrake that referenced this pull request Sep 9, 2026
This PR needs firedrakeproject/fiat#261 and the UFL work in FEniCS/ufl#511 and
FEniCS/ufl#512, none of which has merged, so CI installs both branches over the
pins in pyproject.toml.  Drop this commit once they land; nothing else on the
branch touches .github.

The step runs after Firedrake is installed, so that resolving its dependencies
cannot pull fenics-ufl back from FEniCS/ufl@main over the branch, and calls
firedrake-clean so no kernel cached against the released UFL survives.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pbrubeck added a commit to firedrakeproject/firedrake that referenced this pull request Sep 9, 2026
This PR needs firedrakeproject/fiat#261 and the UFL work in FEniCS/ufl#511 and
FEniCS/ufl#512, none of which has merged, so CI installs both branches over the
pins in pyproject.toml.  Drop this commit once they land; nothing else on the
branch touches .github.

The step runs after Firedrake is installed, so that resolving its dependencies
cannot pull fenics-ufl back from FEniCS/ufl@main over the branch, and calls
firedrake-clean so no kernel cached against the released UFL survives.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pbrubeck added a commit to firedrakeproject/firedrake that referenced this pull request Sep 9, 2026
This PR needs firedrakeproject/fiat#261 and the UFL work in FEniCS/ufl#511 and
FEniCS/ufl#512, none of which has merged, so CI installs both branches over the
pins in pyproject.toml.  Drop this commit once they land; nothing else on the
branch touches .github.

Both installs sit inside the 'Install Firedrake' step, ahead of the Firedrake
install rather than after it: that step ends with firedrake-clean, which
imports Firedrake, and Firedrake cannot be imported against the released UFL.
A step of its own after the install therefore never gets to run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
pbrubeck added a commit to firedrakeproject/firedrake that referenced this pull request Sep 10, 2026
This PR needs firedrakeproject/fiat#294 and the UFL work in FEniCS/ufl#511 and FEniCS/ufl#512, none of which has merged, so CI installs both branches over the pins in pyproject.toml. Drop this commit once they land; nothing else on the branch touches .github.

Both installs sit inside the Install Firedrake step, ahead of the Firedrake install rather than after it: that step ends with firedrake-clean, which imports Firedrake, and Firedrake cannot be imported against the released dependencies. A step of its own after the install therefore never gets to run.
@pbrubeck
pbrubeck force-pushed the pbrubeck/interpolate-reference-lowering branch 2 times, most recently from 6cdee45 to ea2bddd Compare September 10, 2026 23:40
pbrubeck and others added 5 commits September 11, 2026 09:30
A form compiler evaluates an Interpolate on the reference cell of its target
element, so the operand has to be mapped there and the result pulled back for
the integrand that holds it. TSFC did this in a pass of its own, before it
called compute_form_data, and carried its own copy of the inverse maps.

apply_interpolate_pullbacks does it here, at the start of compute_form_data
and under the same switch as the other pull backs, on top of the pull backs'
apply_inverse.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0155wQMTMSGiF9a6TbyTVK5P
Both interpolation pull back passes were the older MultiFunction style, while
the rest of the algorithms package has moved to DAGTraverser.

InterpolatePullbackApplier becomes a DAGTraverser, with the Interpolate rule a
postorder handler, and apply_interpolate_pullbacks maps it over the integrands
directly, as the traverser walks the DAG itself.  apply_inverse_pullback keeps
its signature for TSFC and now wraps InversePullbackApplier, which carries the
element and the fallback domain as traverser state.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BP8aGcL37egqaMX9zXAYgX
FunctionPullbackApplier was the last MultiFunction in the module, so it joins
the interpolation pass as a DAGTraverser: the form argument rule dispatches on
FormArgument and the traverser's own memoisation replaces memoized_handler.

apply_inverse_pullback goes back to being a plain function.  It is a rule on a
single node, not a traversal: it must not recurse into its argument, or the
interpolations there would be lowered a second time by the pass that runs
after it.  Wrapping it in a traverser only hid that.

The two traversers are separate passes because compute_form_data runs them at
different points, with derivative expansion and degree estimation in between.

Neither pass had tests.  test_apply_function_pullbacks covers the pull back of
form arguments in a form, the round trip of apply_inverse_pullback against
each pull back (through cancel_jacobian_products, which cancels the Jacobian
factors the two maps insert), its shape and domain checks, and the lowering of
plain, nested and in-form interpolations.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
FormArgument itself declares neither ufl_function_space nor ufl_element, so
annotating the handler with it fails mypy.  Its subclasses Argument and
Coefficient are what actually reach the rule, and both carry them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@pbrubeck
pbrubeck force-pushed the pbrubeck/interpolate-reference-lowering branch from 6ad84df to fcd6122 Compare September 11, 2026 08:35
@jorgensd
jorgensd added this pull request to the merge queue Sep 11, 2026
Merged via the queue into FEniCS:main with commit 80629e9 Sep 11, 2026
11 checks passed
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.

2 participants