Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
1e1eb33
transferred all changes related to mutating FS on VOMs
achanbour Jul 13, 2026
886b671
transferred all changes related to mutating FS on VOMs
achanbour Jul 13, 2026
c1c9579
Merge remote-tracking branch 'origin/main' into achanbour/mutable-fs-…
achanbour Jul 17, 2026
ec59875
Merge remote-tracking branch 'origin/achanbour/mutable-fs-on-vom' int…
achanbour Jul 17, 2026
3b97dce
generalise topology version to arbirary mesh topologies + fixed cachi…
achanbour Jul 17, 2026
88add8a
linting
achanbour Jul 17, 2026
852e255
improvements over the caching mechanism
achanbour Jul 17, 2026
bcab3f6
fixed mesh versioning
achanbour Jul 17, 2026
4c34822
added topology version on ExtrudedMesh which doesn't inherit from Abs…
achanbour Jul 17, 2026
723f032
transferred new functionalities related to mutating VOMs from persona…
achanbour Jul 24, 2026
6cdd99f
merged with main
achanbour Jul 24, 2026
f947410
review + tidy
achanbour Jul 24, 2026
01b18ae
minor fixes + added tests for vom mutation
achanbour Jul 27, 2026
5150cb5
added fs mutation test + fixed vom mutation test
achanbour Jul 28, 2026
fa299d0
added more tests and fixes
achanbour Jul 29, 2026
03defe0
linting
achanbour Jul 29, 2026
1c104a2
more linting + parametrized parent mesh type in VOM mutation tests
achanbour Jul 29, 2026
9b5f924
first set of changes
achanbour Jul 31, 2026
d8fa4f0
Merge remote-tracking branch 'origin/main' into achanbour/vom-mutation
achanbour Aug 3, 2026
54ad62c
fixes
achanbour Aug 3, 2026
f331138
fixes + changed the swarm array access pattern
achanbour Aug 3, 2026
a6e5742
fixed the caching
achanbour Aug 3, 2026
446ced1
added a topology version number for MeshSequence
achanbour Aug 3, 2026
c61fb93
fixed attribute name
achanbour Aug 3, 2026
223ccea
changed Function migration method to do a single collective broadcast
achanbour Aug 3, 2026
cf1065b
more fixes from the feedback
achanbour Aug 3, 2026
380ae71
added setter to dat
achanbour Aug 4, 2026
b56fb00
changes to function registration and eager migration methods
achanbour Aug 4, 2026
b340407
Merge branch 'main' into achanbour/vom-mutation
achanbour Aug 4, 2026
577eb25
fixed migration method
achanbour Aug 4, 2026
52ab5c3
moved migration logic to coordinateless function and duplicated into …
achanbour Aug 5, 2026
ba6f46c
split vom rebuild method into several sub-methods
achanbour Aug 5, 2026
876c420
moved FS mutation tests to separate file
achanbour Aug 5, 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
32 changes: 29 additions & 3 deletions firedrake/cofunction.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from firedrake.adjoint_utils.blocks.function import CofunctionAssignBlock
from firedrake.petsc import PETSc


__all__ = ["Cofunction", "RieszMap"]


Expand Down Expand Up @@ -78,9 +77,36 @@ def __init__(self, function_space, val=None, name=None, dtype=ScalarType,

if isinstance(val, (op2.Dat, op2.DatView, op2.MixedDat, op2.Global)):
assert val.comm == self.comm
self.dat = val
self._dat = val
else:
self.dat = function_space.make_dat(val, dtype, self.name())
self._dat = V.make_dat(val, dtype, self.name())

# Record the mesh topology version
self._mesh_topology_version = self._mesh_topology._topology_version

# Register the function on the mesh
self._mesh_topology._register_function(self)

@property
def _mesh_topology(self):
"""Return the topology on which this cofunction is defined."""
return self._function_space.topological.mesh()

@property
def dat(self):
self._migrate_to_current_topology_version()
return self._dat

@dat.setter
def dat(self, value):
if value is self._dat:
return
raise AttributeError("A Cofunction's Dat cannot be replaced directly.")

def _migrate_to_current_topology_version(self) -> None:
"""Migrate this cofunction's data to the current topology version."""
from firedrake.function import _migrate_dg0_coefficient
_migrate_dg0_coefficient(self, self._function_space.topological)

@PETSc.Log.EventDecorator()
def copy(self, deepcopy=True):
Expand Down
8 changes: 8 additions & 0 deletions firedrake/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,11 @@ def __init__(self, domain, point):

def __str__(self):
return f"Domain {self.domain} does not contain point {self.point}"


class FunctionMigrationError(FiredrakeException):
"""Raised when automatic migration of Function data between mesh topology versions fails."""


class UnsupportedFunctionMigrationError(FiredrakeException):
"""Raised when automatic Function migration is not supported for the Function's mesh topology or function space."""
141 changes: 135 additions & 6 deletions firedrake/function.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.

It is not a requirement for this PR, but you can probably now see that Function and Cofunction share an awful lot of code. It is somewhere on my TODO list to build a parent FunctionSpaceData (when the old version of that class dies) class for the shared functionality. One day...

Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
from firedrake.cofunction import Cofunction, RieszMap
from firedrake.adjoint_utils import FunctionMixin
from firedrake.petsc import PETSc
from firedrake.mesh import MeshGeometry, VertexOnlyMesh
from firedrake.mesh import MeshGeometry, VertexOnlyMeshTopology, VertexOnlyMesh
from firedrake.functionspace import FunctionSpace, VectorFunctionSpace, TensorFunctionSpace
from firedrake.exceptions import PointNotInDomainError
from firedrake.exceptions import PointNotInDomainError, UnsupportedFunctionMigrationError, FunctionMigrationError

Check failure on line 30 in firedrake/function.py

View workflow job for this annotation

GitHub Actions / test / Lint codebase

F401

firedrake/function.py:30:1: F401 'firedrake.exceptions.FunctionMigrationError' imported but unused


__all__ = ['Function', 'CoordinatelessFunction', 'PointEvaluator']
Expand Down Expand Up @@ -79,15 +79,41 @@

if isinstance(val, (op2.Dat, op2.DatView, op2.MixedDat, op2.Global)):
assert val.comm == self.comm
self.dat = val
self._dat = val
else:
self.dat = function_space.make_dat(val, dtype, self.name())
self._dat = function_space.make_dat(val, dtype, self.name())

# Record the mesh topology version
self._mesh_topology_version = self._mesh_topology._topology_version

Check failure on line 88 in firedrake/function.py

View workflow job for this annotation

GitHub Actions / test / Lint codebase

W293

firedrake/function.py:88:1: W293 blank line contains whitespace
# Register the function on the mesh
self._mesh_topology._register_function(self)

@property
def topological(self):
r"""The underlying coordinateless function."""
return self

@property
def _mesh_topology(self):
"""Return the mesh topology on which this coordinateless function is defined."""
return self._function_space.topological.mesh()

@property
def dat(self):
self._migrate_to_current_topology_version()
return self._dat

@dat.setter
def dat(self, value):

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.

We should have a comment explaining things here because this is an extremely strange pattern.

if value is self._dat:
return
raise AttributeError("A Function's Dat cannot be replaced directly.")

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.

Suggested change
raise AttributeError("A Function's Dat cannot be replaced directly.")
raise AttributeError("The 'dat' of a function cannot be changed")

small thing but I think this is a bit clearer


def _migrate_to_current_topology_version(self) -> None:
"""Migrate this coordinateless function's data to the current topology version."""
_migrate_dg0_coefficient(self, self._function_space)

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.

This is the place where we should check that we are actually DG0 and fail appropriately (NotImplementedError)


@PETSc.Log.EventDecorator()
def copy(self, deepcopy=False):
r"""Return a copy of this CoordinatelessFunction.
Expand Down Expand Up @@ -281,6 +307,10 @@
r"""The underlying coordinateless function."""
return self._data

def _migrate_to_current_topology_version(self):
"""Migrate the underlying coordinateless data to the current topology."""
self._data._migrate_to_current_topology_version()

@PETSc.Log.EventDecorator()
@FunctionMixin._ad_annotate_copy
def copy(self, deepcopy=False):
Expand All @@ -299,8 +329,16 @@
return val

def __dir__(self):
current = super(Function, self).__dir__()
return list(dict.fromkeys(dir(self._data) + current))
current = super(Function, self).__dir__()

Check failure on line 332 in firedrake/function.py

View workflow job for this annotation

GitHub Actions / test / Lint codebase

E117

firedrake/function.py:332:13: E117 over-indented
return list(dict.fromkeys(dir(self._data) + current))

@property
def dat(self):
return self._data.dat

@dat.setter
def dat(self, value):
self._data.dat = value

@cached_property
@FunctionMixin._ad_annotate_subfunctions
Expand Down Expand Up @@ -870,3 +908,94 @@
comm=function.comm
)
return getattr(dll, c_name)

def migrate_dg0_dat(

Check failure on line 912 in firedrake/function.py

View workflow job for this annotation

GitHub Actions / test / Lint codebase

E302

firedrake/function.py:912:1: E302 expected 2 blank lines, found 1
old_cfunc: CoordinatelessFunction,
topological_function_space: functionspaceimpl.FunctionSpace,
step_sf: PETSc.SF
) -> CoordinatelessFunction:
"""Migrate DG0 data through a topology mapping.

Parameters
----------
old_cfunc
CoordinatelessFunction containing data on the source topology.
topological_function_space
Function space on the target topology.
step_sf
SF mapping points in the target topology to points in the source topology.

Returns
-------
CoordinatelessFunction
Coefficient containing the migrated data.
"""
from pyop2.mpi import MPI
from firedrake.halo import _get_mtype

old_dat = old_cfunc._dat
dim = old_dat.cdim

old_vals = np.ascontiguousarray(old_dat.data_ro).reshape((-1, dim))
old_space = old_cfunc.function_space()

assert old_space.cell_node_list.shape[1] == 1, \
"This Function migration method requires a DG0 Function with exactly one node per cell."

new_cfunc = CoordinatelessFunction(topological_function_space, val=None, dtype=old_dat.dtype, name=old_cfunc.name())

nroots, ilocal, remote = step_sf.getGraph()
nleaves = len(remote) if ilocal is None else len(ilocal)

new_vals = np.empty((nleaves, dim), dtype=old_dat.dtype)

mtype, _ = _get_mtype(old_dat)
step_sf.bcastBegin(mtype, old_vals, new_vals, MPI.REPLACE)
step_sf.bcastEnd(mtype, old_vals, new_vals, MPI.REPLACE)

cnl = topological_function_space.cell_node_list
new_data = new_cfunc.dat.data_with_halos.reshape((-1, dim))
new_data[cnl[:, 0], :] = new_vals

return new_cfunc

def _migrate_dg0_coefficient(

Check failure on line 962 in firedrake/function.py

View workflow job for this annotation

GitHub Actions / test / Lint codebase

E302

firedrake/function.py:962:1: E302 expected 2 blank lines, found 1
coefficient,
topological_function_space
) -> None:
"""Migrate a DG0 coefficient to the current topology version.

Parameters
----------
coefficient
Coefficient (CoordinatelessFunction or Cofunction) whose data should be migrated.
topological_function_space
Function space on the current topology.

Returns
-------
None

Raises
------
UnsupportedFunctionMigrationError
If the coefficient's topology does not support migration.
"""
topology = coefficient._mesh_topology
latest_topology_version = topology._topology_version

if latest_topology_version == coefficient._mesh_topology_version:
return

if not isinstance(topology, VertexOnlyMeshTopology):
raise UnsupportedFunctionMigrationError(
"The mesh topology has changed since this Function was created, \
and migration is currently only supported for Functions defined on VertexOnlyMeshes. \
Please re-create this Function on the updated mesh."
)

migration_sf = topology._get_migration_sf(coefficient._mesh_topology_version)
migrated_dat = migrate_dg0_dat(coefficient, topological_function_space, migration_sf)

coefficient._dat = migrated_dat._dat
coefficient._mesh_topology_version = latest_topology_version
Loading
Loading