Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
ff94938
Adaptive Multigrid
pbrubeck Nov 17, 2025
b585c34
Merge branch 'main' into pbrubeck/adaptive-mg
pbrubeck Nov 26, 2025
cf0b36e
Fix complex tests
pbrubeck Dec 1, 2025
8312598
complex
pbrubeck Dec 1, 2025
94a0fd0
merge conflict
pbrubeck Dec 22, 2025
6b69bf9
Apply suggestions from code review
pbrubeck Jan 19, 2026
fa2cce8
Merge branch 'main' into pbrubeck/adaptive-mg
pbrubeck Jan 19, 2026
18de9b8
WIP
pbrubeck Jan 19, 2026
5c23e5e
Remove Submesh
pbrubeck Jan 19, 2026
433a4b0
Fix tests
pbrubeck Jan 19, 2026
706e303
Apply suggestions from code review
pbrubeck Jan 20, 2026
8dd0e66
fixup
pbrubeck Jan 20, 2026
c40a36b
Apply suggestions from code review
pbrubeck Jan 26, 2026
76c2d59
enable tests
leo-collins Dec 19, 2025
67a5a0c
remove check
leo-collins Dec 21, 2025
e637d6f
add test
leo-collins Dec 21, 2025
4d23dc2
update test
leo-collins Dec 22, 2025
5c84b12
update tests
leo-collins Jan 5, 2026
d8a1ac3
WIP
leo-collins Jan 5, 2026
8c255e2
fix test
leo-collins Jan 20, 2026
40c1252
WIP
leo-collins Jan 21, 2026
b5ebf32
fix target_space
leo-collins Jan 21, 2026
a2789da
two-form assembly working
leo-collins Jan 21, 2026
986ce11
Apply suggestions from code review
pbrubeck Jan 26, 2026
e1dba6b
docs
pbrubeck Jan 26, 2026
10f8516
forward and adjoint one-form
leo-collins Jan 26, 2026
fc17045
fixes
leo-collins Jan 26, 2026
cb92d81
working
leo-collins Jan 26, 2026
cf1d747
add zero-form test
leo-collins Jan 27, 2026
170bcb4
fix tests
leo-collins Jan 27, 2026
76a9323
docs
leo-collins Jan 27, 2026
5979f71
fixes
leo-collins Jan 27, 2026
1625b3b
Added demo for performing adaptive multigrid (#4535)
AnuragRao1 Jan 27, 2026
76f3942
Edits
pefarrell Jan 27, 2026
eedf0ad
fix test
leo-collins Jan 28, 2026
139ec33
MG iteration counts
pbrubeck Jan 29, 2026
862b943
fixes
pbrubeck Jan 29, 2026
f3352c3
review suggestions
leo-collins Jan 29, 2026
0ea4dc7
fix latex
pbrubeck Jan 29, 2026
86aa644
Merge branch 'leo/cross-mesh-non-lagrange' into pbrubeck/adaptive-mg
pbrubeck Jan 29, 2026
9b46f42
Use native interpolation
pbrubeck Jan 29, 2026
b7289f3
merge conflict
pbrubeck Feb 11, 2026
c643c52
merge conflict
pbrubeck Feb 11, 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: 2 additions & 1 deletion firedrake/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ def init_petsc():
HierarchyBase, MeshHierarchy, ExtrudedMeshHierarchy,
NonNestedHierarchy, SemiCoarsenedExtrudedHierarchy,
prolong, restrict, inject, TransferManager,
OpenCascadeMeshHierarchy
OpenCascadeMeshHierarchy, AdaptiveMeshHierarchy,
AdaptiveTransferManager
)
from firedrake.norms import errornorm, norm # noqa: F401
from firedrake.nullspace import VectorSpaceBasis, MixedVectorSpaceBasis # noqa: F401
Expand Down
2 changes: 2 additions & 0 deletions firedrake/mg/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@
)
from firedrake.mg.embedded import TransferManager # noqa F401
from firedrake.mg.opencascade_mh import OpenCascadeMeshHierarchy # noqa F401
from firedrake.mg.adaptive_hierarchy import AdaptiveMeshHierarchy # noqa F401
from firedrake.mg.adaptive_transfer_manager import AdaptiveTransferManager # noqa: F401
79 changes: 79 additions & 0 deletions firedrake/mg/adaptive_hierarchy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"""
This module contains the class for the AdaptiveMeshHierarchy and
related helper functions
"""

from collections import defaultdict

from firedrake.cofunction import Cofunction
from firedrake.function import Function
from firedrake.mg import HierarchyBase
from firedrake.mg.utils import set_level

__all__ = ["AdaptiveMeshHierarchy"]


class AdaptiveMeshHierarchy(HierarchyBase):
"""
HierarchyBase for hierarchies of adaptively refined meshes
"""
def __init__(self, base_mesh, refinements_per_level=1, nested=True):
self.meshes = []
self._meshes = []
self.refinements_per_level = refinements_per_level
self.nested = nested
self.add_mesh(base_mesh)

def add_mesh(self, mesh):
"""
Adds newly refined mesh into hierarchy.
"""
self._meshes.append(mesh)
self.meshes.append(mesh)
level = len(self.meshes)
set_level(self.meshes[-1], self, level - 1)
self._shared_data_cache = defaultdict(dict)

def adapt(self, eta: Function | Cofunction, theta: float):
"""
Add a refinement level to the hierarchy by local refinement
with a simplified variant of Dorfler marking.

Parameters
----------
eta
A DG0 :class:`~firedrake.function.Function` with the local error estimator.
theta
The threshold for marking as a fraction of the maximum error.

Note
----
Dorfler marking involves sorting all of the elements by decreasing
error estimator and taking the minimal set that exceeds some fixed
fraction of the total error. What this code implements is the simpler
variant that doesn't have a proof of convergence (as far as I know)
but works as well in practice.

"""
if not isinstance(eta, (Function, Cofunction)):
raise TypeError(f"eta must be a Function or Cofunction, not a {type(eta).__name__}")
M = eta.function_space()
if M.finat_element.space_dimension() != 1:
raise ValueError("eta must be a Function or Cofunction in DG0")
mesh = self.meshes[-1]
if M.mesh() is not mesh:
raise ValueError("eta must be defined on the finest mesh of the hierarchy")

# Take the maximum over all processes
with eta.dat.vec_ro as eta_:
Comment thread
pbrubeck marked this conversation as resolved.
Outdated
eta_max = eta_.max()[1]
Comment thread
pbrubeck marked this conversation as resolved.
Outdated

threshold = theta * eta_max
should_refine = eta.dat.data_ro > threshold

markers = Function(M)
markers.dat.data_wo[should_refine] = 1

refined_mesh = mesh.refine_marked_elements(markers)
self.add_mesh(refined_mesh)
return refined_mesh
94 changes: 94 additions & 0 deletions firedrake/mg/adaptive_transfer_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""
This module contains the AdaptiveTransferManager used to perform
transfer operations on AdaptiveMeshHierarchies
"""
from firedrake.mg.embedded import TransferManager
from firedrake.ufl_expr import action, TrialFunction
from firedrake.functionspace import FunctionSpace, TensorFunctionSpace
from firedrake.interpolation import interpolate
from firedrake.preconditioners.bddc import is_lagrange
from finat.quadrature import QuadratureRule
from functools import partial

import numpy


__all__ = ("AdaptiveTransferManager",)


class AdaptiveTransferManager(TransferManager):
Comment thread
pbrubeck marked this conversation as resolved.
"""
TransferManager for adaptively refined mesh hierarchies
"""
def __init__(self, *, native_transfers=None, use_averaging=True):
super().__init__(native_transfers=native_transfers, use_averaging=use_averaging)
self.cache = {}

def get_operators(self, Vc, Vf):
key = (Vc, Vf)
try:
return self.cache[key]
except KeyError:
ops = get_mg_interpolator(Vc, Vf)
return self.cache.setdefault(key, ops)

def forward(self, uc, uf):
from firedrake.assemble import assemble
Vc = uc.function_space()
Vf = uf.function_space()
ops = self.get_operators(Vc, Vf)

expr = uc
for op in ops:
expr = action(op, expr)
return assemble(expr, tensor=uf)

def adjoint(self, uf, uc):
from firedrake.assemble import assemble
Vc = uc.function_space().dual()
Vf = uf.function_space().dual()
ops = self.get_operators(Vc, Vf)

expr = uf
for op in reversed(ops):
expr = action(expr, op)
return assemble(expr, tensor=uc)

def prolong(self, uf, uc):
return self.forward(uf, uc)

def inject(self, uc, uf):
return self.forward(uc, uf)

def restrict(self, uc, uf):
return self.adjoint(uc, uf)


def make_quadrature_space(V):
fe = V.finat_element
_, ps = fe.dual_basis
wts = numpy.full(len(ps.points), numpy.nan)
scheme = QuadratureRule(ps, wts, ref_el=fe.cell)
if V.value_shape == ():
make_space = FunctionSpace
else:
make_space = partial(TensorFunctionSpace, shape=V.value_shape)
return make_space(V.mesh(), "Quadrature", degree=fe.degree, quad_scheme=scheme)


def get_mg_interpolator(V1, V2):
from firedrake.assemble import assemble
if is_lagrange(V2.finat_element):
spaces = (V1, V2)
else:
Q2 = make_quadrature_space(V2)
spaces = (V1, Q2, V2)

ops = []
for i in range(len(spaces)-1):
Vsrc = spaces[i]
Vdest = spaces[i+1]
Iexpr = interpolate(TrialFunction(Vsrc), Vdest)
op = assemble(Iexpr, mat_type="aij")
ops.append(op)
return ops
Loading
Loading