Fix periodic-seam cut-link addressing in map_cut_links

Source (write) and reverse-donor addresses are now the receiver's local
core address +/- (ey*local_nx + ex), i.e. the window image the next local
pull reads. block_cell preferred the in-window core image on a one-block
periodic axis, so BFL wrote a copy the pull never reads. Physical
neighbors are still formed only to reject non-periodic exteriors.
block_cell semantics unchanged; docstring notes the image ambiguity.
Adds an end-to-end synthetic seam test (x=0/x=nx-1/corner wraps) on
one-block-x, one-block-y and 1x1 periodic layouts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-15 02:38:13 +00:00
1 parent 2aadffe183
commit 2a68d7e7c7
2 files changed
+39 -12

No files matched your search

+27 -7
View File
@@ -39,6 +39,11 @@ def dense_pull(a, periodic):
def blocked_pull(layout, blocked):
"""Exchange, then pull every core cell from its own block storage only."""
layout.exchange(blocked)
return blocked_pull_core(layout, blocked)
def blocked_pull_core(layout, blocked):
"""Pull core cells with no halo copy (a copy would undo BFL halo writes)."""
out = np.full_like(blocked, np.nan)
(by, bx), h = layout.block, layout.halo
for q in range(NQ):
@@ -218,15 +223,30 @@ class BlockLayoutTests(unittest.TestCase):
layout = BlockLayout2D.build(16, 16, 8, 8)
blocked = layout.to_blocked(a) # gather halos
blocked_bfl(layout, blocked, links, layout.map_cut_links(links))
# Pull core cells without another halo copy (it would undo halo writes).
out = np.full_like(blocked, np.nan)
(by, bx), h = layout.block, layout.halo
for q in range(NQ):
ey, ex = D2Q9_EY[q], D2Q9_EX[q]
out[:, q, h:h + by, h:h + bx] = blocked[:, q, h - ey:h - ey + by, h - ex:h - ex + bx]
got = layout.to_dense(out)
got = layout.to_dense(blocked_pull_core(layout, blocked))
np.testing.assert_array_equal(got[:, ~solid], expected[:, ~solid])
def test_periodic_seam_links_follow_the_pulled_halo_image(self):
# Receivers on x=0 / x=nx-1 / a corner whose sources wrap physically.
links = [CutLink(fluid_idx=1 * 6 + 0, dir=2, q=.6, body_id=0),
CutLink(fluid_idx=2 * 6 + 0, dir=2, q=.3, body_id=0),
CutLink(fluid_idx=3 * 6 + 5, dir=1, q=.3, body_id=0),
CutLink(fluid_idx=0 * 6 + 0, dir=6, q=.4, body_id=0)]
sources = np.zeros((8, 6), dtype=bool)
for y, x in ((1, 5), (2, 5), (3, 0), (7, 5)):
sources[y, x] = True
a = tags(8, 6) / 7.0
expected = dense_pull(dense_bfl(a, links), (True, True))
# One block along x, along y, along both (periodic self-images).
for by, bx in ((4, 6), (8, 3), (8, 6)):
with self.subTest(block=(by, bx)):
layout = BlockLayout2D.build(8, 6, by, bx)
mapped = layout.map_cut_links(links)
blocked = layout.to_blocked(a)
blocked_bfl(layout, blocked, links, mapped)
got = layout.to_dense(blocked_pull_core(layout, blocked))
np.testing.assert_array_equal(got[:, ~sources], expected[:, ~sources])
def test_cut_link_rejections(self):
layout = BlockLayout2D.build(16, 16, 8, 8, periodic=(False, False))
link = CutLink(fluid_idx=5 * 16 + 5, dir=1, q=.6, body_id=0)