Multipatch: fine_objects and jets on several patches (level-wide projection)
prepare_fine_boundary accepts a FixedPatchSet2D: objects are projected once onto the level-wide fine grid (FixedPatchSet2D.fine_origin = (0, 0), same coarse cell-edge -> fine cell transform), each circle must lie inside one patch, coarse transfer cells are checked against the covered union, and body ids stay object positions, so the existing jet one-body-per-jet, overlap and (receiver, dir) writer checks run once over the whole level (caller obj_id cannot bypass them). The stepper no longer rejects fine_objects/jets with several patches; blocked link tables, BFL and the jet amplitude cache are reused unchanged. No kernel change. README/docstring scope updated. Tests: CPU translation/ids/rejections (outside one patch, one jet over two bodies at zero amplitude); GPU one body via the new path == single-patch path (both modes), two bodies (+jets, amplitude switch) == tiled single (bitwise). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f1dcf329ff
commit
64665319a4
5 files changed
+152
-10
No files matched your search
@@ -227,6 +227,11 @@ class FixedPatchSet2D:
|
||||
def bounds(self) -> tuple:
|
||||
return tuple(p.bounds for p in self.patches)
|
||||
|
||||
@property
|
||||
def fine_origin(self) -> tuple[float, float]:
|
||||
"""Lower edge of the level-wide fine grid in coarse physical units."""
|
||||
return 0.0, 0.0
|
||||
|
||||
@property
|
||||
def covered(self) -> np.ndarray:
|
||||
mask = np.zeros(self.coarse_shape, bool)
|
||||
@@ -351,6 +356,10 @@ def prepare_fine_boundary(cfg, patch, objects, *, wall_aware=False, jets=()):
|
||||
SurfaceActuation retains its lattice-velocity meaning: input coarse units
|
||||
are converted into independent fine-local objects with dx=dt=.5.
|
||||
Original objects are not retained or mutated. No runtime geometry updates.
|
||||
``patch`` may be a FixedPatchSet2D: objects are then projected once onto
|
||||
the level-wide fine grid (fine_origin (0, 0)); each circle must lie inside
|
||||
one patch, body ids are the positions in ``objects`` (never patch ids), and
|
||||
jets and the (receiver, dir) writer checks run once over the whole level.
|
||||
``jets`` use the same coarse cell-edge frame as the objects; their
|
||||
selection is checked against true fine hits here. Jet amplitudes need no
|
||||
rescaling (lattice velocity scales by dt/dx = 1 under acoustic 2:1).
|
||||
@@ -375,7 +384,8 @@ def prepare_fine_boundary(cfg, patch, objects, *, wall_aware=False, jets=()):
|
||||
flags = np.full(nx * ny, FLUID, np.uint16)
|
||||
active = patch.fine_kind.ravel() == ACTIVE
|
||||
local, links = [], []
|
||||
x0, y0, x1, y1 = patch.bounds
|
||||
rects = patch.bounds if isinstance(patch, FixedPatchSet2D) else (patch.bounds,)
|
||||
covered = patch.covered
|
||||
dx = dt = .5
|
||||
for bid, obj in enumerate(objects):
|
||||
if (type(obj.geometry) is not CircleGeometry or obj.is_sensor or obj.is_force_region
|
||||
@@ -385,8 +395,9 @@ def prepare_fine_boundary(cfg, patch, objects, *, wall_aware=False, jets=()):
|
||||
cx, cy = obj.geometry.center
|
||||
radius = obj.geometry._radius
|
||||
if (not np.isfinite([cx, cy, radius]).all() or radius <= 0
|
||||
or cx-radius < x0 or cx+radius > x1 or cy-radius < y0 or cy+radius > y1):
|
||||
raise ValueError("Entire fine circle must lie inside the physical patch")
|
||||
or not any(x0 <= cx-radius and cx+radius <= x1 and y0 <= cy-radius
|
||||
and cy+radius <= y1 for x0, y0, x1, y1 in rects)):
|
||||
raise ValueError("Entire fine circle must lie inside one physical patch")
|
||||
center = tuple((v-o)/dx-.5 for v, o in zip((cx, cy), patch.fine_origin))
|
||||
spec = obj.surface_actuation
|
||||
if spec is not None:
|
||||
@@ -446,7 +457,7 @@ def prepare_fine_boundary(cfg, patch, objects, *, wall_aware=False, jets=()):
|
||||
raise ValueError("Boundary stencil overlaps the two-hop transfer footprint")
|
||||
for k in coarse:
|
||||
y, x = divmod(k, cfg.nx)
|
||||
if x0 <= x < x1 and y0 <= y < y1:
|
||||
if covered[y, x]:
|
||||
raise ValueError("Transfer coarse donor/target is not an uncovered bulk cell")
|
||||
# Rows 0/ny-1 are never computed and rows 1/ny-2 are reconstructed after
|
||||
# coalescence; rows 2/ny-3 are ordinary bulk cells only read as donors.
|
||||
|
||||
@@ -134,7 +134,9 @@ class FixedPatchStepper:
|
||||
|
||||
Several patches: pass ``bounds`` as a sequence of (x0, y0, x1, y1)
|
||||
rectangles (FixedPatchSet2D, >= 4 coarse cells apart). This requires
|
||||
fine_block and base_block, is body-free (no fine_objects/jets yet), and
|
||||
fine_block and base_block; fine_objects/jets are projected once onto the
|
||||
level-wide fine grid (each circle inside one patch, body ids = object
|
||||
positions, jet and writer checks level-wide), and
|
||||
uses one level-wide fine grid (2*ny, 2*nx): public fine arrays have that
|
||||
shape, NaN outside the patches' ACTIVE cells. The step sequence is the
|
||||
blocked one above, with every patch's transfers in one launch each.
|
||||
@@ -150,8 +152,6 @@ class FixedPatchStepper:
|
||||
if multipatch:
|
||||
if fine_block is None or base_block is None:
|
||||
raise ValueError("several patches require fine_block and base_block")
|
||||
if tuple(fine_objects) or tuple(jets):
|
||||
raise ValueError("several patches do not support fine_objects or jets yet")
|
||||
self.patch = FixedPatchSet2D.from_config(cfg, bounds)
|
||||
else:
|
||||
self.patch = FixedPatch2D.from_config(cfg, bounds)
|
||||
|
||||
Reference in new issue
Block a user