Conversation
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Duplication | -2 |
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
1 task
Contributor
Author
|
The pre-existing 3-rank defect this PR deliberately leaves standing is now #496. |
gouarin
force-pushed
the
petsc-mpi-numbering-exchange
branch
from
September 2, 2026 08:09
dc5a07e to
6b246c3
Compare
…oth sides to agree The exchange that gives a ghost its petsc global index is positional: the n-th value received is the n-th value the neighbour sent. Both sides walked their own version of the sequence: - the sender pushed a value only for the cells **it** owned, the receiver read one only for the cells it believed **the neighbour** owned. Those are two ranks' opinions of ownership, and where they differ - which is exactly what `compute_cell_ownership`'s correction passes exist to repair, and cannot always - the sequences drift and every later ghost is handed another cell's global index; - the level range came from the sender's own mesh (`min_level..max_level`) on one side and from `0..max_level` on the other, and the intersection was built with the operands in the opposite order on each side. The corruption is silent. A shifted index is a valid-looking one in another rank's range, so nothing complains until petsc refuses a column it was not preallocated for - one rank's worth of rows away from the row, which is the signature I chased. Now: one value per shared unknown whether owned or not (UNSET for what the sender does not own), a level range derived from both meshes, and an operand order derived from the rank numbers. The sequences cannot drift, and an actual ownership disagreement is reported instead of being turned into a wrong index. `has_duplicates`, the check that catches this class of defect, was O(n^2), which is why it could only live inside an `assert` - and asserts are compiled out of every build CI runs. Sorting a copy makes it O(n log n), so it is now affordable enough that keeping it out of release builds is a choice rather than a necessity. Measured, `finite-volume-heat --init-sol crenel -pc_type lu --min-level 3 --max-level 8`, with asserts **on** (`-DCMAKE_CXX_FLAGS_RELEASE=-O1`): | | 1 rank | 2 | 3 | 4 | |---|---|---|---|---| | main | ok | ok | **duplicate indices** | ok | | main + this commit | ok | ok | **duplicate indices** | ok | | with the boundary rewrite's wider ghost band, before this commit | ok | ok | fails | **fails** | | with that band, after | ok | ok | fails | **ok** | So this repairs the case the wider band exposed, and it leaves a **pre-existing** defect standing: at 3 ranks the mapping already has duplicate global indices on main. That one is invisible in CI because the guard is an assert, and it deserves its own investigation - the exchange is no longer where it comes from.
…s identically The owner of a shared unknown was decided from local heuristics - "the minimum rank of the children *this rank* holds", "the closer of the two gravity centres, pairwise" - whose answers differ from rank to rank, and the disagreements were then negotiated over a bounded number of correction passes. Pairwise closeness is not transitive, so three ranks sharing a coarse cell could each name a different owner and the passes never converged. The wider prediction margin the coarse levels now carry shares more cells and made that failure the norm: `finite-volume-heat` at 3 ranks stopped with "Maximum number of correction passes reached". Ownership is now a function of data every holder has, evaluated identically everywhere, and the exchange only checks that neighbours agree. The principle: a row is assembled by the rank that classifies the cell - real cell, projection ghost, prediction ghost, boundary ghost - in its own mesh, since that classification is what the assembly visits and what guarantees the rank holds every cell the row reads. So the owner is the lowest rank among those that classify the cell, and a cell no rank's real cells give a meaning to goes to the holder whose gravity centre is the closest, ties to the lowest rank. A disagreement is now an error with the rule that fired on each side, not something to repair: it can only mean two ranks hold a common cell without being neighbours. Two more things the same runs turned up: - `Mesh_base::swap` did not swap the gravity centre, so a rank that had adapted kept the centre of its previous mesh while its neighbours held the centre of its current one - the one input to the ownership rule that could differ between ranks, and it did. - the projection rows were preallocated for half their children off-process; the owner is the lowest rank holding one child as a real cell, so every other child may be remote. The check exchange also walks a level range derived from both meshes and an intersection whose operands are ordered by rank, as the numbering exchange already does, since a positional exchange is only as good as the agreement on what is walked. The numbering report goes to stderr so that every rank is heard, the demos silencing stdout on ranks other than 0. Measured with the wider ghost band of the boundary rewrite (`finite-volume-heat --init-sol crenel --min-level 3 --max-level 8`): 1 to 3 ranks pass, with and without load balancing at 1 and 2 ranks, as do the lid-driven cavity at 1 and 2 ranks and the parallel ghost tests. Not fixed here: at 4 ranks, and at 3 with load balancing, petsc still refuses a column of a scheme row (`New nonzero at (335,21735)`), with the same row before and after this change, so it is a preallocation defect of the scheme rows and not an ownership one.
gouarin
force-pushed
the
petsc-mpi-numbering-exchange
branch
from
September 2, 2026 15:09
6b246c3 to
33019da
Compare
This was referenced Sep 2, 2026
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.
Description
The exchange that gives a ghost its petsc global index is positional: the n-th value received
is the n-th value the neighbour sent. Both sides walked their own version of the sequence:
cells it believed the neighbour owned. Those are two ranks' opinions of ownership, and where
they differ - which is what
compute_cell_ownership's correction passes exist to repair, andcannot always - the sequences drift and every later ghost is handed another cell's global index;
min_level..max_level) on one side and from0..max_levelon the other, and the intersection was built with the operands in the oppositeorder on each side.
The corruption is silent: a shifted index is a valid-looking index in another rank's range, so
nothing complains until petsc refuses a column it was not preallocated for, one rank's worth of
rows away from the row -
New nonzero at (455,13367),(456,13368), … which is the trail Ifollowed here.
Now: one value per shared unknown whether owned or not (
UNSETfor what the sender does not own),a level range derived from both meshes, and an operand order derived from the rank numbers. The
sequences cannot drift, and a real ownership disagreement is reported instead of being turned into
a wrong index.
has_duplicates, the check that catches exactly this class of defect, wasO(n^2)- which is whyit could only ever live inside an
assert, and asserts are compiled out of every build CI runs.Sorting a copy makes it
O(n log n), so keeping it out of release builds becomes a choice ratherthan a necessity.
Cell ownership decided by one rule (second commit)
The alignment above turned the corruption into a reported disagreement, and the report showed
where it came from. Ownership was decided from local heuristics - "the minimum rank of the
children this rank holds", "the closer of the two gravity centres, pairwise" - whose answers
differ from rank to rank, and the disagreements were then negotiated over a bounded number of
correction passes. Pairwise closeness is not transitive: three ranks sharing a coarse cell each
named a different owner and the passes never converged, which is the "Maximum number of
correction passes reached"
finite-volume-heatprinted at 3 ranks with #493's ghost band.Ownership is now a function of data every holder has, evaluated identically everywhere, and the
exchange only checks that neighbours agree. The principle: a row is assembled by the rank that
classifies the cell in its own mesh - real cell, projection ghost, prediction ghost, boundary
ghost - since that classification is what the assembly visits and what guarantees the rank
holds every cell the row reads. So the owner is the lowest rank among those that classify the
cell, and a cell no rank's real cells give a meaning to goes to the holder whose gravity centre
is the closest, ties to the lowest rank. A disagreement is an error naming the rule that fired
on each side, not something to repair: it can only mean two ranks hold a common cell without
being neighbours.
Two more things the same runs turned up, fixed here:
Mesh_base::swapdid not swap the gravity centre, so a rank that had adapted kept the centreof its previous mesh while its neighbours held the centre of its current one - the one input
to the rule that could differ between ranks, and it did;
lowest rank holding one child as a real cell, so every other child may be remote.
Measured with #493's ghost band on
finite-volume-heat --init-sol crenel --min-level 3 --max-level 8: 1 to 3 ranks pass, with load balancing at 1 and 2 ranks, as do the lid-drivencavity at 1 and 2 ranks,
finite-volume-advection-2dat 1 to 9 ranks and the parallel ghost andload-balancing tests. Not fixed here: at 4 ranks, and at 3 with load balancing, petsc still
refuses a column of a scheme row (
New nonzero at (335,21735)), the same row before and afterthis change, so that one is a preallocation defect of the scheme rows and not an ownership one.
Related issue
None. Found while closing the MPI gap of #493.
How has this been tested?
finite-volume-heat --init-sol crenel -pc_type lu --Tf 0.02 --min-level 3 --max-level 8, builtwith MPI + PETSc and with asserts on (
-DCMAKE_CXX_FLAGS_RELEASE=-O1, since the guard is anassert):
mainmain+ this PRSo this repairs the case the wider band exposed, and it changes nothing on
main- no regression,and no claim beyond what is measured.
It leaves a pre-existing defect standing, and that is worth stating plainly: at 3 ranks the
mapping already has duplicate global indices on
maintoday. It is invisible in CI because theguard is an assert, and it is not the exchange - the exchange is now provably aligned. It deserves
its own investigation, most likely into why the ownership correction passes do not converge for
that partition (
ci.ymlalready documents the neighbouring symptom next to a disabled 3-ranktest: "multiple ranks own the same ghost cell, which is not supported").
Code of Conduct