Skip to content

Evaluate interpolations on the reference cell in compute_form_data - #69

Closed
pbrubeck wants to merge 4 commits into
pbrubeck/pullback-apply-inversefrom
pbrubeck/interpolate-reference-lowering
Closed

pbrubeck wants to merge 4 commits into
pbrubeck/pullback-apply-inversefrom
pbrubeck/interpolate-reference-lowering

Conversation

@pbrubeck

@pbrubeck pbrubeck commented Sep 8, 2026

Copy link
Copy Markdown

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 (FEniCS#497), where Interpolate.ufl_element comes from.

Those are the same branches as FEniCS#511 and FEniCS#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
pbrubeck changed the base branch from pbrubeck/interpolate-holes to pbrubeck/pullback-apply-inverse-holes September 8, 2026 01:04
@pbrubeck
pbrubeck changed the base branch from pbrubeck/pullback-apply-inverse-holes to pbrubeck/pullback-apply-inverse September 8, 2026 01:13
@pbrubeck
pbrubeck force-pushed the pbrubeck/pullback-apply-inverse branch from 3ea4e9d to 6f6b47d Compare September 8, 2026 10:51
@pbrubeck
pbrubeck force-pushed the pbrubeck/interpolate-reference-lowering branch 2 times, most recently from 6931489 to 6cdee45 Compare September 10, 2026 23:09
pbrubeck and others added 4 commits September 11, 2026 00:37
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/pullback-apply-inverse branch from 3a43502 to fbdb7d7 Compare September 10, 2026 23:40
@pbrubeck
pbrubeck force-pushed the pbrubeck/interpolate-reference-lowering branch from 6cdee45 to ea2bddd Compare September 10, 2026 23:40
@jorgensd

Copy link
Copy Markdown

This is something I also experienced when trying to add interpolate to FFCx. Great to resolve it at the source.

@pbrubeck pbrubeck closed this Sep 11, 2026
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