Generalize synchronized remap to multiple pinned body rectangles

This commit is contained in:
2026-10-16 11:32:42 -07:00
parent 864a220dbe
commit 60ad6b8b0d
6 changed files with 375 additions and 32 deletions
+5 -5
View File
@@ -499,9 +499,9 @@ class FixedPatchStepper:
Periodic three-level set children only; L0/L1 and block sizes stay fixed.
A body run requires its original root-edge-frame ``child_objects``:
one stationary circle pinned in one of exactly two L2 rectangles.
The complete body rectangle stays identical; only the other fluid
rectangle may move. Current L2 Jets (including updated amplitudes)
stationary circles pinned in unchanged complete L2 rectangles.
Separated fluid-only rectangles may be added, moved or removed.
Current L2 Jets (including updated amplitudes)
are preserved. Local projected boundary objects are not valid inputs.
The lock and stream synchronization certify
current postcollision G and the completed 1:2:4 counts internally.
@@ -538,10 +538,10 @@ class FixedPatchStepper:
raise ValueError("child_objects changed boundary ownership or surface velocity")
new = RemapTopology.from_config(cfg, self.patch, rectangles,
child_objects=child_objects, child_jets=jets)
if (old.pinned_rectangle != new.pinned_rectangle
if (old.pinned_rectangles != new.pinned_rectangles
or not same_physical_boundary(old.boundary, new.boundary)
or not np.array_equal(old.surface_velocity, new.surface_velocity)):
raise ValueError("remap requires the identical pinned body rectangle and boundary")
raise ValueError("remap requires the identical pinned body rectangles and boundary")
base_block, fine_block, child_block = self._remap_blocks
# Check sparse block alignment/launch size before any device read/allocation.
layout = new.child.fine_layout(child_block)
+20 -15
View File
@@ -1,7 +1,7 @@
"""CPU-only whole-volume G transfer, not a runtime adaptive solver.
Periodic three-level grids with fixed L0/L1 topology are supported, including
one stationary circle pinned inside an unchanged L2 rectangle. The caller
stationary circles pinned inside unchanged L2 rectangles. The caller
certifies that current owned leaves are quiescent,
unshifted D2Q9 postcollision G at the supplied completed base interval.
No device buffers, device boundary state, ghosts, collision or time advance
@@ -25,12 +25,14 @@ class RemapTopology:
Child rectangles are in the parent's allocated L1 coordinates, including
its window offset/halo when the parent is a scalar patch. Dense masks are
rebuilt from geometry, never inferred from finite values in input arrays.
``pinned_rectangles`` canonically lists each body-containing rectangle once;
fluid-only layouts have no pins.
"""
parent: object
child: FixedPatchSet2D
leaf_masks: tuple
boundary: FineBoundary2D | None = None
pinned_rectangle: tuple | None = None
pinned_rectangles: tuple = ()
surface_velocity: np.ndarray | None = None
@classmethod
@@ -43,24 +45,24 @@ class RemapTopology:
parent = type(parent).from_config(cfg, parent.bounds)
child, middle_kind = nest_patch_set(cfg, parent, child_rectangles)
child_objects, child_jets = tuple(child_objects), tuple(child_jets)
if child_objects and (len(child_objects) != 1 or len(child.bounds) != 2):
raise ValueError("body remap requires one stationary circle and two L2 rectangles")
fine_cfg = replace(cfg, nx=parent.fine_shape[1], ny=parent.fine_shape[0],
viscosity=2 * cfg.viscosity)
boundary = prepare_fine_boundary(
fine_cfg, child, child_objects, jets=child_jets,
parent_origin=parent.fine_origin, parent_spacing=.5)
pinned, velocity = None, None
pinned, velocity = (), None
child_leaf = child.fine_kind == ACTIVE
if boundary is not None:
child_leaf &= (boundary.flags.reshape(child.fine_shape) & SOLID) == 0
# Whole circle containment, not merely the resolved solid cells.
obj = child_objects[0]
cx, cy = (np.asarray(obj.geometry.center) - parent.fine_origin) * 2
radius = obj.geometry._radius * 2
pinned = next(r for r in child.bounds if
r[0] <= cx-radius and cx+radius <= r[2] and
r[1] <= cy-radius and cy+radius <= r[3])
containing = set()
for obj in child_objects:
cx, cy = (np.asarray(obj.geometry.center) - parent.fine_origin) * 2
radius = obj.geometry._radius * 2
containing.add(next(r for r in child.bounds if
r[0] <= cx-radius and cx+radius <= r[2] and
r[1] <= cy-radius and cy+radius <= r[3]))
pinned = tuple(sorted(containing))
velocity = boundary_velocity(cfg, child, boundary, child_jets)
if velocity is not None:
velocity.setflags(write=False)
@@ -88,8 +90,11 @@ def same_physical_boundary(a, b):
return a is b
if not isinstance(a, FineBoundary2D) or not isinstance(b, FineBoundary2D):
return False
if (tuple(o.surface_actuation for o in a.objects)
!= tuple(o.surface_actuation for o in b.objects)):
# Exact ordered geometry also catches changes hidden by cut-table rounding.
def signatures(boundary):
return tuple((o.geometry.center, o.geometry._radius, o.surface_actuation)
for o in boundary.objects)
if signatures(a) != signatures(b):
return False
return all(np.array_equal(x, y) for x, y in zip(
(a.flags, a.boundary_cells, a.curved_hits, *a.curved_host),
@@ -162,10 +167,10 @@ def remap_synchronized(old, new, old_g, *, base_count, level_steps, phase,
or not _same_parent(old.parent, new.parent)
or old.child.tau_fine != new.child.tau_fine):
raise ValueError("remap requires unchanged L0/L1 geometry and relaxation")
if (old.pinned_rectangle != new.pinned_rectangle
if (old.pinned_rectangles != new.pinned_rectangles
or not same_physical_boundary(old.boundary, new.boundary)
or not np.array_equal(old.surface_velocity, new.surface_velocity)):
raise ValueError("remap requires the identical pinned body rectangle and physical boundary")
raise ValueError("remap requires the identical pinned body rectangles and physical boundary")
if not isinstance(level_steps, (tuple, list)):
raise ValueError("level_steps must be three completed integer counts")
counts = (base_count, *level_steps)