Rework PMGBase.coarsen() and add NonlinearVariationalProblem.rediscretise() - #5324
Merged
Conversation
…method NonlinearVariationalProblem.reconstruct(function_space) rebuilds the problem's solution, forms, and bcs on a new function space living on the same mesh, substituting the coefficient mapping symbolically through an optional form_transform hook (defaulting to ufl.replace). VectorSpaceBasis.reconstruct and MixedVectorSpaceBasis.reconstruct do the analogous thing for a nullspace basis, interpolating each basis Function onto the new space and re-orthonormalizing. Neither caller is wired in yet; PMGBase.coarsen() picks these up next.
…ct() coarsen() used to hand-roll the coarse problem (coarsening J/Jp/F/bcs/ appctx itself, then constructing a fresh NonlinearVariationalProblem and _SNESContext) and separately special-case bc coarsening per PMGPC vs PMGSNES via coarsen_bcs/coarsen_bc_value and a bespoke per-bc cache. None of that is necessary now: a bc's value is just a UFL expression, and NonlinearVariationalProblem.reconstruct() already builds the coefficient_mapping needed to substitute it symbolically, the same way it already does for forms, so PMGPC and PMGSNES coarsen F, J, Jp, and bcs identically. _SNESContext gets the analogous treatment via its own reconstruct(), and nullspace coarsening goes through the new VectorSpaceBasis/MixedVectorSpaceBasis.reconstruct() instead of a hand-rolled interpolation + cache. Also drop the PMGPC-only "cproblem.F = action(J, cu)" workaround now that geometric multigrid supports non-Lagrange elements (the mixed-block bug it worked around no longer applies), so PMGPC and PMGSNES coarsen F identically too. Drop PMGBase.reconstruct_degree's element-degree-shift tree-walk; its callers move to the element's own reconstruct(degree=...) next.
…econstruct_degree finat.ufl's FiniteElement subclasses now support reconstruct(degree=...) natively (firedrakeproject/fiat#273), including the element-degree-shift tree-walk through EnrichedElement/TensorProductElement/MixedElement/etc. that PMGBase.reconstruct_degree used to carry. P1PC/P1SNES/LORPC. coarsen_element and the p-multigrid tests call the element's own reconstruct() directly. test_p_multigrid_mixed's iteration count moves from <=7 to <=9: PMGPC's coarse-grid Jacobian is no longer forced through action(J, cu) (see previous commit), which slightly changes the coarse operator for this problem.
Pin firedrake-fiat to pbrubeck/element-reconstruct-degree (firedrakeproject/fiat#273) so CI exercises the composite-element reconstruct(degree=...) fix this branch depends on. Revert to @main once that fiat PR merges. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
connorjward
reviewed
Aug 4, 2026
pbrubeck
commented
Aug 4, 2026
Covers PMGBase.coarsen_element/coarsen/coarsen_quadrature, NonlinearVariationalProblem.reconstruct, VectorSpaceBasis.reconstruct, MixedVectorSpaceBasis.reconstruct, and _SNESContext.reconstruct. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ufl.replace can't move a Cofunction's implicit test argument to the coarse space (it isn't a literal node in the expression tree), so coarsening F = action(J, u) - L for a LinearVariationalProblem left L on the fine space, giving F two distinct arguments and tripping the "residual is not a linear form" check. PC-only coarse levels never assemble their own residual, so give them F = action(cJ, cu) instead of coarsening fproblem.F. SNES/FAS levels do need a real residual: fall back to only replacing coefficients and re-pairing the result with the coarse test function via interpolation, mirroring the DirichletBC-restriction code path. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
action(cJ, cu) worked but redundantly coarsened J a second time (reconstruct() already does it for J itself). Use a bare ZeroBaseForm with the coarse test argument instead: cheaper, and the recently-fixed ZeroBaseForm._ufl_expr_reconstruct_ (upstream UFL) makes it safe to carry through reconstruction. This needs two supporting fixes: - reconstruct_nlvp in mg/ufl_utils.py looked up the coarsened solution via coefficient_mapping[problem.u_restrict], relying on it having been populated as a side effect of walking F for coefficients. A coefficient-free F doesn't trigger that, so look it up explicitly instead. - test_p_fas_nonlinear_scalar's check_coarsen_quadrature inspected p.F.integrals() directly; skip non-Form entries since PC-only levels no longer carry a real Form there (J alone is sufficient to verify the quadrature-degree coarsening). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Pin fenics-ufl to pbrubeck/zero-base-form-holes (FEniCS/ufl#494) so CI exercises the ZeroBaseForm.extract_domains and _ufl_expr_reconstruct_ fixes PMG coarsening now depends on. Revert to @main once that ufl PR merges. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
pbrubeck
commented
Aug 5, 2026
pbrubeck
commented
Aug 5, 2026
pbrubeck
commented
Aug 5, 2026
Co-authored-by: Pablo Brubeck <brubeck@protonmail.com>
Covers PMGBase.coarsen_element/coarsen/coarsen_quadrature, NonlinearVariationalProblem.reconstruct, VectorSpaceBasis.reconstruct, MixedVectorSpaceBasis.reconstruct, and _SNESContext.reconstruct. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ufl.replace can't move a Cofunction's implicit test argument to the coarse space (it isn't a literal node in the expression tree), so coarsening F = action(J, u) - L for a LinearVariationalProblem left L on the fine space, giving F two distinct arguments and tripping the "residual is not a linear form" check. PC-only coarse levels never assemble their own residual, so give them F = action(cJ, cu) instead of coarsening fproblem.F. SNES/FAS levels do need a real residual: fall back to only replacing coefficients and re-pairing the result with the coarse test function via interpolation, mirroring the DirichletBC-restriction code path. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
action(cJ, cu) worked but redundantly coarsened J a second time (reconstruct() already does it for J itself). Use a bare ZeroBaseForm with the coarse test argument instead: cheaper, and the recently-fixed ZeroBaseForm._ufl_expr_reconstruct_ (upstream UFL) makes it safe to carry through reconstruction. This needs two supporting fixes: - reconstruct_nlvp in mg/ufl_utils.py looked up the coarsened solution via coefficient_mapping[problem.u_restrict], relying on it having been populated as a side effect of walking F for coefficients. A coefficient-free F doesn't trigger that, so look it up explicitly instead. - test_p_fas_nonlinear_scalar's check_coarsen_quadrature inspected p.F.integrals() directly; skip non-Form entries since PC-only levels no longer carry a real Form there (J alone is sufficient to verify the quadrature-degree coarsening). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Pin firedrake-fiat to pbrubeck/element-reconstruct-degree (firedrakeproject/fiat#273) so CI exercises the composite-element reconstruct(degree=...) fix this branch depends on. Revert to @main once that fiat PR merges. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Pin fenics-ufl to pbrubeck/zero-base-form-holes (FEniCS/ufl#494) so CI exercises the ZeroBaseForm.extract_domains and _ufl_expr_reconstruct_ fixes PMG coarsening now depends on. Revert to @main once that ufl PR merges. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Drop the fiat/ufl pins that leaked back in through the merge; they belong in the DROP BEFORE MERGE action.yml commits only. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
pbrubeck
commented
Aug 5, 2026
pbrubeck
force-pushed
the
pbrubeck/pmg-reconstruct-rework
branch
from
August 5, 2026 10:31
a820726 to
e07c9b4
Compare
pbrubeck
commented
Aug 5, 2026
connorjward
requested changes
Aug 5, 2026
pbrubeck
commented
Aug 5, 2026
pbrubeck
commented
Aug 5, 2026
pbrubeck
commented
Aug 5, 2026
Co-authored-by: Pablo Brubeck <brubeck@protonmail.com>
pbrubeck
force-pushed
the
pbrubeck/pmg-reconstruct-rework
branch
2 times, most recently
from
August 5, 2026 15:06
adad029 to
2972461
Compare
pbrubeck
force-pushed
the
pbrubeck/pmg-reconstruct-rework
branch
from
August 5, 2026 16:52
2972461 to
83b5017
Compare
Contributor
Author
|
CI is green here |
connorjward
approved these changes
Aug 6, 2026
leo-collins
pushed a commit
that referenced
this pull request
Aug 6, 2026
…tise() (#5324) * Give NonlinearVariationalProblem and nullspace bases a rediscretise() method * Rework PMGBase.coarsen() to use NonlinearVariationalProblem.rediscretise()
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Gives
NonlinearVariationalProblemand the nullspacebasis classes a generic
reediscretise()method, and reworksPMGBase.coarsen()to build the coarse problem,_SNESContext, andnullspaces through those
reconstruct()/rediscretise()methods instead of hand-rollingeach piece and special-casing bc coarsening per
PMGPC/PMGSNES.Also replaces
PMGBase.reconstruct_degree's element-degree-shifttree-walk with
finat.ufl's own nativeFiniteElement.reconstruct(degree=...)support (firedrakeproject/fiat#273).
Before / after
PMGBase.coarsen()hand-built the coarseNonlinearVariationalProblemand
_SNESContext, and coarsened bcs via bespokecoarsen_bcs/coarsen_bc_valuemethods (with a separate per-bc cache) that special-casedPMGPC(bc value always 0) vsPMGSNES(eagerFunction.interpolate()).NonlinearVariationalProblem.redisctretise()and_SNESContext.reconstruct()do this generically for any caller, so
PMGPCandPMGSNEScoarsen bcs (and F)identically, and the PMGPC-only
cproblem.F = action(J, cu)simplification is replaced withZeroBaseForm.PMGBase.reconstruct_degree's manual tree-walk overEnrichedElement/TensorProductElement/MixedElement/etc.reconstruct(degree=...).