Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
67f0753
TSFC: support same-mesh ufl.Interpolate in Form kernels
pbrubeck Jul 16, 2026
d46250b
FormAssembler: handle same-mesh interpolation
pbrubeck Jul 16, 2026
acd5b64
Unify Interpolate and Form assembly
pbrubeck Aug 27, 2026
56419dd
Match boundary conditions to a space without function_space_match
pbrubeck Aug 10, 2026
c24ffb4
TSFC: resolve an unsplit mixed argument's integral type
pbrubeck Aug 10, 2026
a537887
Interpolate onto a point cloud through the integral kernel builder
pbrubeck Aug 26, 2026
ec47cee
Compare boundary conditions against primal spaces
pbrubeck Aug 27, 2026
1be4e86
Leave a Coargument out of the Real mangling
pbrubeck Aug 27, 2026
fc48c40
Count the coordinates among an interpolation's inputs
pbrubeck Aug 27, 2026
e2582f5
Compile an interpolation kernel when the callable is asked for
pbrubeck Aug 29, 2026
181bb8d
Address code-review findings on #5258
pbrubeck Aug 29, 2026
d327e02
fix tests
pbrubeck Aug 29, 2026
eb85165
Remove dead interpolation/BC code, share form-assembler dispatch
pbrubeck Sep 6, 2026
378f5e8
Trim numpydoc on thin helpers
pbrubeck Sep 6, 2026
fa4d9ee
Split BCBase.function_space(parent=True) into parent_function_space
pbrubeck Sep 6, 2026
b8691cf
Reuse UFL's build_coefficient_split in compile_interpolate
pbrubeck Sep 6, 2026
ac2a257
Use join_domains to collapse compile_interpolate's target MeshSequence
pbrubeck Sep 7, 2026
ffe8e08
Contract each dual argument component on its own sub-element
pbrubeck Sep 7, 2026
26aa5d5
shorten code
pbrubeck Sep 7, 2026
1ae908b
Compile interpolation and form kernels through one pipeline
pbrubeck Sep 7, 2026
86bddcd
Dispatch to a form assembler in one place
pbrubeck Sep 7, 2026
a86a8b6
Name interpolation members after what they hold
pbrubeck Sep 7, 2026
6c4c54b
Keep function_space_index rather than inlining it
pbrubeck Sep 7, 2026
f53c1e7
Match main's continuation indentation
pbrubeck Sep 7, 2026
56f0099
Apply batched suggestions from code review
pbrubeck Sep 7, 2026
cf5d8e0
Apply suggestion from @pbrubeck
pbrubeck Sep 7, 2026
ce3c742
Apply batched suggestions from code review
pbrubeck Sep 7, 2026
02e8928
Fixes
pbrubeck Sep 7, 2026
1a26dc3
Just forward kwargs to OneFormAssembler
pbrubeck Sep 7, 2026
8b28e99
fix logic
pbrubeck Sep 7, 2026
dc0fd3c
Apply suggestion from @pbrubeck
pbrubeck Sep 7, 2026
330d366
Restore the assembly options that get_form_assembler dropped
pbrubeck Sep 7, 2026
351e7c1
Address review comments
pbrubeck Sep 7, 2026
e978886
Apply suggestion from @pbrubeck
pbrubeck Sep 7, 2026
e030e28
Rename FunctionSpaceMismatchError to MismatchingFunctionSpaceError
pbrubeck Sep 7, 2026
0fbf83a
Treat a fusable Form(Interpolate) as a leaf of the BaseForm DAG
pbrubeck Sep 8, 2026
fcc301f
Lower interpolation through UFL's compute_form_data
pbrubeck Sep 8, 2026
f04505b
Dispatch _is_compilable on the type of the expression
pbrubeck Sep 9, 2026
9eb405c
Traverse Slate operands in _is_compilable
pbrubeck Sep 9, 2026
4e0fa28
Lower nested interpolations in preprocess_expression
pbrubeck Sep 9, 2026
266ded0
Filter fused interpolations out of base_form_operands
pbrubeck Sep 9, 2026
98e1f37
DROP BEFORE MERGE: install the FIAT and UFL branches
pbrubeck Sep 9, 2026
74d5802
Move Slate interpolation tests to Slate tests
pbrubeck Sep 9, 2026
4e705ca
Preserve interpolation indices for sum factorisation
pbrubeck Sep 10, 2026
9ee29a0
Factorise direct-sum dual interpolation
pbrubeck Sep 10, 2026
837828b
Tighten mixed interpolation factorisation test
pbrubeck Sep 10, 2026
061b52b
style
pbrubeck Sep 10, 2026
e17e2a3
Consolidate interpolation factorisation tests
pbrubeck Sep 10, 2026
bdbc5e9
Share interpolation driver setup
pbrubeck Sep 10, 2026
51f61d7
Share interpolation driver setup
pbrubeck Sep 10, 2026
ced5430
Unify interpolation code generation
pbrubeck Sep 10, 2026
cc9d662
DROP BEFORE MERGE: remove merged UFL CI override
pbrubeck Sep 11, 2026
aead293
cleanup
pbrubeck Sep 11, 2026
00e27cf
Fix TSFC cleanup import cycle
pbrubeck Sep 11, 2026
e93677b
Unify dual interpolation kernel builders
pbrubeck Sep 12, 2026
f03ab0c
Simplify boolean assertions
pbrubeck Sep 12, 2026
e7ab640
Preserve aliased interpolation accumulators
pbrubeck Sep 12, 2026
b93be6b
Shorten interpolation test diff
pbrubeck Sep 12, 2026
298edae
Fix aliased interpolation access handling
pbrubeck Sep 12, 2026
cee390d
Remove duplicate interpolation reduction cases
pbrubeck Sep 12, 2026
c22ed33
Document TSFC argument elision
pbrubeck Sep 12, 2026
77a5dfc
add missing ExpressionKernel attributes back to Kernel
pbrubeck Sep 12, 2026
83560ba
Use shorter external coordinates property name
pbrubeck Sep 12, 2026
de8fb78
Sum factorise an interpolation in a form
pbrubeck Sep 13, 2026
74b1bfd
Do not fuse a partly covered interpolation into a Form kernel
pbrubeck Sep 13, 2026
4fc805e
Refactor same-mesh interpolation checks
pbrubeck Sep 13, 2026
5a3bc00
Check interpolation domains in TSFC and share the assembly helpers
pbrubeck Sep 14, 2026
a5f89a0
Update tsfc/fem.py
pbrubeck Sep 14, 2026
b57122d
Address interpolation review comments
pbrubeck Sep 14, 2026
8b8e263
Remove interpolation domain helpers
pbrubeck Sep 15, 2026
052d651
inline _get_tensor inside VomOntoVomInterpolator
pbrubeck Sep 16, 2026
d3aa435
Filter unrelated BCs before interpolation assembly
pbrubeck Sep 17, 2026
243649b
Revert "Filter unrelated BCs before interpolation assembly"
pbrubeck Sep 17, 2026
f7d6c3b
Apply suggestion from @pbrubeck
pbrubeck Sep 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/actions/install/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,14 @@ runs:
EXTRA_PIP_FLAGS='--no-build-isolation'
elif [ ${{ inputs.base_ref }} = 'release' ]; then
EXTRA_PIP_FLAGS=''
export PIP_BUILD_CONSTRAINT=constraints.txt
else
echo "Unrecognised 'base_ref' input: '${{ inputs.base_ref }}"
exit 1
fi

: # DROP BEFORE MERGE
pip install -v --no-deps --ignore-installed git+https://github.com/firedrakeproject/fiat.git@pbrubeck/fix/dual-enriched-again
pip install --verbose $EXTRA_PIP_FLAGS \
--no-binary h5py \
--extra-index-url https://download.pytorch.org/whl/cpu \
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/core.yml
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,9 @@ jobs:
- name: Install Firedrake
id: install
run: |
: # DROP BEFORE MERGE
pip install -v --no-deps --ignore-installed git+https://github.com/firedrakeproject/fiat.git@pbrubeck/fix/dual-enriched-again
pip install -v --no-deps --ignore-installed git+https://github.com/firedrakeproject/ufl.git@pbrubeck/interpolate-reference-lowering
pip install --verbose -r ./firedrake-repo/requirements-build.txt
CC=mpicc CXX=mpicxx \
pip install --verbose --no-build-isolation './firedrake-repo[docs]'
Expand Down
1 change: 1 addition & 0 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@
('py:class', 'pyop2.caching.Cached'),
('py:class', 'pyop2.op2.Kernel'),
('py:class', 'pyop2.types.mat.Mat'),
('py:class', 'pyop2.types.access.Access'),
# Ignore mission docs from Firedrake internal "private" code
# Any "Base" class eg:
# firedrake.adjoint.checkpointing.CheckpointBase
Expand Down
3 changes: 2 additions & 1 deletion docs/source/interpolation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,8 @@ each block given by
\end{pmatrix}
The off-diagonal blocks are zero since the dofs are applied component-wise. Firedrake's form
compiler recognises this and avoids assembling the zero blocks.
compiler recognises this and avoids assembling the zero blocks, which the nest
still allocates.

We can assemble more general interpolation matrices between mixed function spaces by interpolating
vector expressions with arguments. For example, by doing
Expand Down
1 change: 1 addition & 0 deletions firedrake/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def init_petsc():
FiredrakeException, ConvergenceError, MismatchingDomainError,
VertexOnlyMeshMissingPointsError, DofNotDefinedError, DofTypeError,
SerialExecutionOnlyError, PointNotInDomainError,
MismatchingFunctionSpaceError,
)
from firedrake.function import ( # noqa: F401
Function, CoordinatelessFunction, PointEvaluator
Expand Down
261 changes: 186 additions & 75 deletions firedrake/assemble.py

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions firedrake/bcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,14 @@
V = V.parent
return V

@property
def parent_function_space(self):

Check failure on line 102 in firedrake/bcs.py

View workflow job for this annotation

GitHub Actions / test / Lint codebase

F811

firedrake/bcs.py:102:5: F811 redefinition of unused 'parent_function_space' from line 94
"""The top-level function space, walking up through indexed or component subspaces."""
V = self._function_space
while V.parent is not None:
V = V.parent
return V
Comment thread
pbrubeck marked this conversation as resolved.

@cached_property
def domain_args(self):
r"""The sub_domain the BC applies to."""
Expand Down
6 changes: 6 additions & 0 deletions firedrake/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ def __str__(self):
)


class MismatchingFunctionSpaceError(FiredrakeException):
"""Raised when a function space does not match the one expected, such as a
boundary condition applied to a form whose arguments live elsewhere.
"""


class NonUniqueMeshSequenceError(FiredrakeException):
"""Raised when calling `.unique()` on a MeshSequence which contains
non-unique meshes.
Expand Down
67 changes: 55 additions & 12 deletions firedrake/formmanipulation.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the changes to split_form for?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is needed in order to stop using MixedInterpolator for same mesh interpolator. assemble(Form(Interpolate)|Interpolate) needs to split an Interpolate differently depending on whether the Interpolate is outermost. I should definitely be adding a comment explaining this.

@pbrubeck pbrubeck Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment at both places. The short version: an outermost Interpolate splits into a smaller Interpolate, but one sitting inside an integrand has to keep the value shape its neighbours expect, so the selected block is padded back out with zeros. _splitting_interpolate is what tells the two cases apart.


Written by Claude (Claude Code), posted from @pbrubeck's account.

Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@

import numpy
import collections
from collections.abc import Iterable

from ufl import as_tensor, as_vector, split
from ufl.classes import Form, Zero, FixedIndex, ListTensor, ZeroBaseForm
from ufl.classes import Expr, Form, Interpolate, Zero, FixedIndex, ListTensor, ZeroBaseForm
from ufl.algorithms.map_integrands import map_integrand_dags
from ufl.algorithms import expand_derivatives
from ufl.corealg.map_dag import MultiFunction, map_expr_dags
Expand All @@ -13,6 +14,7 @@

from firedrake.petsc import PETSc
from firedrake.functionspace import MixedFunctionSpace
from firedrake.functionspaceimpl import WithGeometry
from firedrake.cofunction import Cofunction
from firedrake.ufl_expr import Coargument

Expand All @@ -30,6 +32,12 @@ class ExtractSubBlock(MultiFunction):

"""Extract a sub-block from a form."""

def __init__(self):
super().__init__()
self._arg_cache = {}
self.blocks = {}
self._splitting_interpolate = False

class IndexInliner(MultiFunction):
"""Inline fixed index of list tensors"""
expr = MultiFunction.reuse_if_untouched
Expand Down Expand Up @@ -82,6 +90,9 @@ def split(self, form, argument_indices):
args = form.arguments()
self._arg_cache = {}
self.blocks = dict(enumerate(map(as_tuple, argument_indices)))
# An outermost Interpolate splits into a smaller Interpolate, while one
# inside an integrand must keep the value shape its neighbours expect.
self._splitting_interpolate = isinstance(form, Interpolate)
if len(args) == 0:
# Functional can't be split
return form
Expand Down Expand Up @@ -229,9 +240,38 @@ def matrix(self, o):
def zero_base_form(self, o):
return ZeroBaseForm(tuple(map(self, o.arguments())))

def _zero_interpolate(self, o: Interpolate) -> ZeroBaseForm | Zero:
"""Result of an Interpolate whose operand or target block is Zero."""
if self._splitting_interpolate:
return self(ZeroBaseForm(o.arguments()))
return Zero(o.ufl_shape)

@staticmethod
def _select_components(V: WithGeometry, indices: tuple, operand: Expr) -> list:
"""Flatten the sub-blocks of ``operand`` whose subspace is in ``indices``."""
components = []
cur = 0
for i, Vi in enumerate(V):
if i in indices:
components.extend(operand[k] for k in range(cur, cur + Vi.value_size))
cur += Vi.value_size
return components

@staticmethod
def _embed_components(V: WithGeometry, indices: tuple, values: Iterable) -> list:
"""Embed ``values`` into V's full shape, zero outside ``indices``."""
values = iter(values)
components = []
for i, Vi in enumerate(V):
if i in indices:
components.extend(next(values) for _ in range(Vi.value_size))
else:
components.extend(Zero() for _ in range(Vi.value_size))
return components

def interpolate(self, o, operand):
if isinstance(operand, Zero):
return self(ZeroBaseForm(o.arguments()))
return self._zero_interpolate(o)

dual_arg, _ = o.argument_slots()
if len(dual_arg.arguments()) == 1 or len(dual_arg.arguments()[-1].function_space()) == 1:
Expand All @@ -249,18 +289,21 @@ def interpolate(self, o, operand):
W = sub_dual_arg.function_space()

# Unflatten the expression into the target shape
cur = 0
components = []
for i, Vi in enumerate(V):
if i in indices:
components.extend(operand[i] for i in range(cur, cur+Vi.value_size))
cur += Vi.value_size

components = self._select_components(V, indices, operand)
operand = as_tensor(numpy.reshape(components, W.value_shape))
if isinstance(operand, Zero):
return self(ZeroBaseForm(o.arguments()))

return o._ufl_expr_reconstruct_(operand, sub_dual_arg)
return self._zero_interpolate(o)

interpolation = o._ufl_expr_reconstruct_(operand, sub_dual_arg)
if self._splitting_interpolate:
return interpolation

# Inside an integrand the block is one part of a wider expression, so
# pad it back out to V's value shape with zeros.
interpolation_components = ((interpolation[j] for j in numpy.ndindex(interpolation.ufl_shape))
if interpolation.ufl_shape else iter((interpolation,)))
components = self._embed_components(V, indices, interpolation_components)
return as_tensor(numpy.reshape(components, V.value_shape))


SplitForm = collections.namedtuple("SplitForm", ["indices", "form"])
Expand Down
Loading
Loading