Skip to content

Automated goal-oriented adaptivity - #5273

Open
pbrubeck wants to merge 5 commits into
mainfrom
pbrubeck/goal-adaptive-callback
Open

Automated goal-oriented adaptivity#5273
pbrubeck wants to merge 5 commits into
mainfrom
pbrubeck/goal-adaptive-callback

Conversation

@pbrubeck

@pbrubeck pbrubeck commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Description

Automates dual-weighted residual goal-oriented adaptive mesh refinement.

  • dwr_marking_callback(goal_functional) API.
  • Configuration through prefixed PETSc.Options() keys.
  • Enriched primal/dual solves via _SNESContext.solve_jacobian_transpose(), residual localization, and Dörfler marking.
  • Top-level solve(..., marking_callback=...) forwarding.
  • Demo from Rognes and Logg 2012

Comment thread firedrake/dwr.py Outdated
Comment thread firedrake/dmhooks.py
Comment thread firedrake/dmhooks.py Outdated
Comment thread demos/goal_oriented_adaptivity/goal_oriented_adaptivity.py.rst Outdated
Base automatically changed from pbrubeck/mg-redist to main July 31, 2026 10:37
Comment thread firedrake/dwr.py Outdated
Comment thread firedrake/dwr.py Outdated
Comment thread demos/goal_oriented_adaptivity/goal_oriented_adaptivity.py.rst Outdated
Comment thread firedrake/dwr.py Outdated
Comment on lines +69 to +71
f"dwr_{kind}_{key}": value
for kind in ("cell", "facet")
for key, value in {"ksp_type": "cg", "pc_type": "jacobi"}.items()

@pbrubeck pbrubeck Jul 31, 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.

don't use dict comprehensions or f-strings, these need to be human readable.

Comment on lines +52 to +54
geo = SplineGeometry()
geo.AddRectangle((0, 0), (1, 1), bc="boundary")
mesh = Mesh(geo.GenerateMesh(maxh=0.5))

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.

use UnitSquareMesh

@pytest.mark.parametrize(
("adapt_option", "criterion"),
(("snes_adapt_sequence", "refine"),
("snes_adapt_multigrid", "none")),

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 requires https://gitlab.com/petsc/petsc/-/merge_requests/9447

Suggested change
("snes_adapt_multigrid", "none")),
# ("snes_adapt_multigrid", "none")),

@pbrubeck
pbrubeck force-pushed the pbrubeck/goal-adaptive-callback branch from 145b3a8 to feae504 Compare July 31, 2026 16:49
Comment thread firedrake/dwr.py Outdated
Comment thread firedrake/dmhooks.py Outdated
@pbrubeck
pbrubeck force-pushed the pbrubeck/goal-adaptive-callback branch 2 times, most recently from 9abb66d to 0ca2b28 Compare July 31, 2026 17:44
Comment thread firedrake/dmhooks.py Outdated
Comment thread firedrake/solving_utils.py Outdated
Comment thread demos/goal_oriented_adaptivity/goal_oriented_adaptivity.py.rst
Comment thread demos/goal_oriented_adaptivity/goal_oriented_adaptivity.py.rst Outdated
Comment thread firedrake/variational_solver.py Outdated
Comment thread firedrake/variational_solver.py Outdated
Comment thread demos/goal_oriented_adaptivity/goal_oriented_adaptivity.py.rst Outdated
Comment thread firedrake/dwr.py Outdated
Comment thread demos/goal_oriented_adaptivity/goal_oriented_adaptivity.py.rst Outdated
Comment thread firedrake/dwr.py Outdated
Comment on lines +317 to +318
def solve_jacobian_transpose(self, rhs: Cofunction,
solution: Function) -> None:

@pbrubeck pbrubeck Aug 3, 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.

Where should this function live?

  • NonlinearVariationalSolver: exposes it to the user, needs adjoint
  • _SNESContext: too obscure to be useful to a user
  • DWRMarkingCallback: prevents code reusability

Comment thread tests/firedrake/multigrid/test_snes_adapt.py Outdated
Comment thread tests/firedrake/multigrid/test_snes_adapt.py
Comment thread firedrake/solving_utils.py
Comment thread firedrake/variational_solver.py Outdated
Comment thread firedrake/variational_solver.py Outdated
Comment thread firedrake/nullspace.py Outdated
Comment thread firedrake/nullspace.py Outdated
Comment on lines +335 to +337
if b.norm() == 0:
x.set(0)
return

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.

Suggested change
if b.norm() == 0:
x.set(0)
return

if self.snes.getDM() != solution_dm:
# DMAdaptorAdapt() consumed a reference to work, which the
# adapted-away DM keeps as its template global vector.
work.incRef()

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.

isn't better to destroy the work Vec?

second_goal = assemble(solver.get_goal_functional())

assert first_dim > old_dim
assert isinstance(first_goal, float)

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.

pointless test

@pbrubeck
pbrubeck force-pushed the pbrubeck/goal-adaptive-callback branch from 64c97c5 to 4a95b41 Compare August 4, 2026 08:45
@pbrubeck
pbrubeck force-pushed the pbrubeck/goal-adaptive-callback branch from 4a95b41 to de30e69 Compare August 4, 2026 14:33
@pbrubeck
pbrubeck changed the base branch from main to pbrubeck/pmg-reconstruct-rework August 4, 2026 14:34
@pbrubeck pbrubeck added the base:main Run this PR using a main (dev) build label Aug 4, 2026
Comment on lines +61 to +62
+ dot(div(sigma), v)
+ dot(u, div(tau))

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.

Suggested change
+ dot(div(sigma), v)
+ dot(u, div(tau))
+ inner(div(sigma), v)
+ inner(u, div(tau))

Comment on lines +63 to +65
+ (sigma[0, 1] - sigma[1, 0])*eta
+ gamma*(tau[0, 1] - tau[1, 0])
)*dx - dot(body_force, v)*dx - dot(u0, dot(tau, n))*ds

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.

Suggested change
+ (sigma[0, 1] - sigma[1, 0])*eta
+ gamma*(tau[0, 1] - tau[1, 0])
)*dx - dot(body_force, v)*dx - dot(u0, dot(tau, n))*ds
+ inner(sigma[0, 1] - sigma[1, 0], eta)
+ inner(gamma, tau[0, 1] - tau[1, 0])
)*dx - inner(body_force, v)*dx - inner(u0, dot(tau, n))*ds

Base automatically changed from pbrubeck/pmg-reconstruct-rework to main August 6, 2026 09:19
DWRMarkingCallback estimates the dual-weighted-residual error indicator
for a user-supplied goal functional and turns it into the DG0 marker
Function that NonlinearVariationalSolver's existing marking-callback
adaptive-refinement machinery expects, so goal-oriented mesh adaptivity
is driven the same way as any other marking callback.

Estimating the DWR indicator needs a dual (adjoint) solve on an enriched
space, and a primal solve's Jacobian is symmetric only in specific cases,
so _SNESContext grows solve_jacobian_transpose() to solve the dual
problem against the transposed primal Jacobian. Finding "the" ksp behind
solve_jacobian_transpose() and PMG's/adaptive-refinement's reconstructed
_SNESContexts is unreliable via a weakref carried across every
reconstruct() call (PETSc returns a fresh Python wrapper on every
getKSP()/getDM(), and the weakref has to be explicitly re-attached each
time); composing it directly on the DM instead (dm.setAttr("_ksp", ksp))
survives reconstruction for free since PETSc's attribute compose/query
operates on the underlying PetscObject. _refine_adaptive() carries the
composed ksp forward onto each newly-refined DM, alongside the
parent/ctx-coarsener/appctx propagation it already does.

NonlinearVariationalSolver.set_marking_callback() now recognises a
DWRMarkingCallback and runs its setup() against the primal solution and
options prefix; get_goal_functional() exposes the (possibly-adapted)
goal functional for inspection after solve().

Also along the way:

- add_hooks/SetupHooks now record the appctx a saved hook stack was
 built for and only replay it when the current appctx matches. Adaptive
 refinement replaces the appctx on every adapted solve, so replaying a
 stale hook stack anchored the DM chain on an already-torn-down root DM
 and surfaced as PETSc error 101 out of DMRefine() on a solver's second
 solve().

- NonlinearVariationalSolver.solve() no longer caches its PETSc work
 vector across calls (the problem may have been reconstructed onto an
 adapted mesh since the last solve) and incRef()s it whenever
 DMAdaptorAdapt() has swapped the solution DM out from under the solve,
 since DMAdaptorAdapt() steals a reference to it as the adapted-away
 DM's template global vector; without the extra ref a second collection
 of the same vector double-frees it and segfaults the interpreter.

- _refine_adaptive() snaps to solution_mesh.unique() and, for a
 MeshSequenceGeometry, calls set_hierarchy() after adding the refined
 mesh, so goal-oriented adaptivity works on mesh sequences too.
Walks through DWRMarkingCallback on a Poisson problem: setting a goal
functional, attaching the callback to a solver via
set_marking_callback(), and inspecting the adapted mesh and goal value
after solve().
@pbrubeck
pbrubeck force-pushed the pbrubeck/goal-adaptive-callback branch from 378d836 to 44b9ce1 Compare August 6, 2026 09:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

base:main Run this PR using a main (dev) build

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant