Skip to content

Restrict star patches to the adaptively refined region - #5329

Draft
pbrubeck wants to merge 3 commits into
mainfrom
pbrubeck/adaptive-patch
Draft

Restrict star patches to the adaptively refined region#5329
pbrubeck wants to merge 3 commits into
mainfrom
pbrubeck/adaptive-patch

Conversation

@pbrubeck

@pbrubeck pbrubeck commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Depends on PETSc petsc/petsc!9481, the tip of the stack !9480!9414 → !9481. The last commit points CI at that branch and must be dropped before merging.

ASMStarPC built its index sets in Python, taking each seed's star through getTransitiveClosure and gathering the dofs point by point. Every entity of the stratum got a patch, whether or not it lay anywhere near a refinement, so a smoother on an adaptively refined level relaxed the whole mesh to fix a corner of it.

ASMStarPC now has a single IS-construction mechanism, in firedrake/cython/patchimpl.pyx, used for every case rather than only the colored one: create_star_points() takes the star of each owned seed and create_patch_ises() gathers the dofs those points carry. The seeds may be restricted to the region that was genuinely refined, and the coloring then colors only those, so the patch count follows the size of the refined region instead of the size of the mesh.

Changes

  • create_star_points() and create_patch_ises() replace build_star_indices, which is gone. They reproduce the dof ordering the Python path produced, so a reordering requested with pc_star_mat_ordering_type still applies per star.
  • order_points() no longer reverses its input, since both remaining callers pass the star already reversed. A reordering applies to each star on its own, so it survives a coloring that grouped several stars into one patch.
  • adapt.mark_refined_entities() labels the cells whose parent was split into more than one child, plus their closure — exactly the entities whose star meets the refined region. It returns None for a mesh with no adaptive parent, which both preconditioners read as relaxing everywhere.
  • get_colors() inserts its MatColoring type with petsctools.inserted_options under the dm_plex_coloring_ prefix, matching how _adapt_marked_cells drives the transform type. The previous code set a bare mat_coloring_type globally and never removed it.
  • A demo, demos/patch/adaptive_patches.py.rst, solves a cubic Poisson problem over a hierarchy that starts with two uniform refinements of a single square and grows three adaptive levels on top, and draws the patch seeds of each level colored by their color.

Options

Option Effect
pc_star_adaptive Restricts ASMStarPC to the entities whose star meets the refined region, building the label from mesh.adaptive_cell_maps.
patch_pc_patch_adaptive The same restriction for PatchPC, through PCPatchSetConstructLabel().
pc_star_construct_label / _value Restricts ASMStarPC to any DMLabel already on the mesh's DMPlex, for a region the adaptive machinery did not produce.
pc_star_use_coloring Unchanged in meaning, but colors only the selected entities when combined with either of the above.

Both adaptive options are correct on a uniformly refined level as well as an adaptive one: every cell of a uniform level was split, so the restriction is a no-op there and the smoother relaxes everywhere. Nothing in the solver parameters has to know which kind of level it is looking at. Adding -dm_plex_coloring_local colors each process's own vertices without communicating, and usually with fewer colors.

Patches across a hierarchy

The first two levels below are uniform refinements, so every vertex carries a patch. From the third level the patches sit only where the mesh was refined. Each disc is drawn half the way to the nearest vertex, so discs shrink with the mesh and never touch; within a level no two discs of the same color are adjacent, which is what makes the grouped patch operator block diagonal.

Patch seeds and their colors on each level

Tests

Added to tests/firedrake/regression/test_star_pc.py: ASMStarPC and PatchPC take equal iteration counts on a corner-refined mesh, coloring does not change that count, restriction leaves strictly fewer patches, and the adaptive option on a mesh with no adaptive parent matches the unrestricted one. They run at 1 and 3 processes. The demo is registered in tests/firedrake/demos/test_demos_run.py.

AI was used to draft this change (Claude Code, Opus 5).

…tion

ASMStarPC now has a single IS-construction mechanism, in
firedrake/cython/patchimpl.pyx, used for every case rather than only the
colored one: create_star_points() takes the star of each owned seed and
create_patch_ises() gathers the dofs those points carry. This reproduces
what the Python path built, so a reordering requested with
pc_star_mat_ordering_type still applies per star, and order_points() no
longer reverses its input since its callers now do.

The seeds may be restricted to the entities marked by a DMLabel, named by
pc_star_construct_label, and the coloring then colors only those. With
pc_star_adaptive the label comes from adapt.mark_refined_entities(), which
marks the cells whose parent was genuinely split along with their closure:
that is the set of entities whose star meets the refined region, which is
what a smoother on an adaptively refined level should relax.

PatchPC gains the same option as patch_pc_patch_adaptive, so that PCPatch
and ASMStarPC build the same restricted patches and take the same number of
iterations.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@pbrubeck pbrubeck added the base:main Run this PR using a main (dev) build label Aug 6, 2026
@pbrubeck
pbrubeck force-pushed the pbrubeck/adaptive-patch branch from 1e916b4 to 06872ac Compare August 6, 2026 08:38
pbrubeck and others added 2 commits August 6, 2026 09:40
… refined

mark_refined_entities() raised on a mesh with no adaptive parent, so
patch_pc_patch_adaptive failed on the coarsest level of a hierarchy and on
every uniformly refined one. PCMG applies mg_levels_ to level 0 as well, so
a single-level hierarchy hit this on its first solve.

Such a level has no refined region to single out: every cell of a uniformly
refined level was split, and a mesh with no parent is new in its entirety.
mark_refined_entities() now returns None there, which both preconditioners
already read as relaxing everywhere, so the same option is correct on a
uniform level and an adaptive one.

The demo solves a cubic Poisson problem over a hierarchy that starts with
two uniform refinements of a single square and grows three adaptive levels
on top of it, and draws the patch seeds of each level colored by their
color. The discs are drawn half the way to the nearest vertex, so they
shrink with the mesh and never touch: the first two levels carry a patch on
every vertex, and the adaptive levels only over the refined region.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The star patches this branch builds need -pc_patch_construct_label and
DMPlexTransformCreateSplitCellLabel(), which are not in petsc/petsc main
yet. Clone the tip of the stack !9480 -> !9414 -> !9481 instead, and drop
this commit once those merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@pbrubeck
pbrubeck force-pushed the pbrubeck/adaptive-patch branch from 06872ac to 5352181 Compare August 6, 2026 08:40

@pefarrell pefarrell left a comment

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.

Looks really good

Contributed by `Pablo Brubeck <https://www.maths.ox.ac.uk/people/pablo.brubeckmartinez/>`_.

The demo :doc:`Using patch relaxation for multigrid <poisson_mg_patches.py>` builds a
vertex star patch around every vertex of every level. That is the right thing to do when

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.

Clarify level means mesh refinement level


An adaptively refined level is different. Only the cells near the marked region were
split; everywhere else the level is a copy of its parent, already resolved by the coarser
levels, and relaxing there costs work without improving the error. This demo restricts

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.

without substantially improving

(it is effectively doing more relaxation sweeps, which is probably helping, but not much)

that reaches a whole vertex star, which is what the ``distribution_parameters`` request. ::

import numpy
from scipy.spatial import cKDTree

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.

I wouldn't call this one of the usual imports. What is it for? Maybe import it later nearer the plotting?

A :func:`~.MeshHierarchy` records parent child relations however its levels were made, so
uniform and adaptive levels stack in the same object. We start from a single square cut
into two triangles and refine it uniformly twice, which gives a 4x4 hierarchy of three
levels, and the adaptive levels will grow on top of those: ::

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.

Break up the sentence: "... three levels. The adaptive levels will grow on top of these"

mh = MeshHierarchy(base, 2)
mesh = mh[-1]

This composition is worth doing. Uniform levels are cheap to build and cheap to apply, and

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 composition of uniform and adaptive refinement is often useful"

"mg_levels_0": {
"mat_type": "aij",
"ksp_type": "preonly",
"pc_type": "lu",

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.

cholesky?

params = {
"mat_type": "aij",
"snes_adapt_sequence": 3,
"ksp_type": "cg",

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.

add the dm_plex_local_coloring?

print(f"Solved on {len(hierarchy)} levels, {solution.function_space().dim()} degrees of freedom")

:class:`~.ASMStarPC` offers the same thing under ``pc_star_adaptive``, building the index
sets itself rather than going through PCPatch.

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 doesn't usefully summarise the difference with PCPatch. Maybe say "extracting the submatrices for the vertex stars from the assembled matrix, instead of building them matrix-free?

Looking at the patches
----------------------

The restriction and the coloring are both visible from Python. ``mark_refined_entities``

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.

The mark_refined_entities function from firedrake.adapt

The restriction and the coloring are both visible from Python. ``mark_refined_entities``
returns the label that ``patch_pc_patch_adaptive`` passes to PCPatch, and returns `None`
for a level with no adaptive parent, which is how a uniformly refined level ends up
relaxing everywhere. ``createColoringLabel`` then colors the marked vertices, under the

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.

plex.createColoringLabel instead of createColoringLabel

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.

2 participants