Blocked fine storage: passive stationary circles via receiver-local BFL
FixedPatchStepper(fine_block=..., fine_objects=[circle]) now accepts passive stationary circles; any surface actuation is rejected before CUDA (dense fine path unchanged). prepare_fine_boundary still decides legality on the dense fine grid; its SoA is mapped by the new array-based storage adapter blocks.blocked_link_tables (map_cut_links: unique receiver owner and local writer, row order kept; blocked flags sized nblocks*L), which PeriodicBlockStepper._static_circles now also uses (single-level legality builder unchanged). Per fine hop on the same buffer: halo -> existing BlockCurvedBoundary -> BlockStep(flags, kind). No CUDA changes. Tests: fixture 76 links / 68 q<.5 and 8 seam writes + 8 seam donors per decomposed layout (both modes); adapter flags sizing; mocked launch-order contract; GPU: periodic and slot_reservoir, 8 coarse steps, dense fine body vs 1x1/2x2/4x2 fine blocks, exact values + NaN masks on masked downloads and raw final arrays, ACTIVE fluid finite, solid/OUTER NaN. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
59921a0181
commit
2bbf82121e
@@ -19,7 +19,7 @@ import numpy as np
|
||||
import pycuda.driver as cuda
|
||||
|
||||
from ..cuda import compiler_v2 as compiler
|
||||
from .blocks import NQ, BlockLayout2D
|
||||
from .blocks import NQ, BlockLayout2D, blocked_link_tables
|
||||
from .descriptors import D2Q9_EX, D2Q9_EY, FLUID, SOLID
|
||||
|
||||
MAX_GRID_DIM = 65535 # CUDA gridDim.y/z limit (rows, blocks)
|
||||
@@ -64,15 +64,12 @@ def _static_circles(layout, objects):
|
||||
for q in range(1, NQ) if solid[source(r, q)]}
|
||||
if len(receivers) != len(links) or receivers != required:
|
||||
raise ValueError("Cut links must cover every fluid pull from a solid exactly once")
|
||||
mapped = layout.map_cut_links(links) # unique receiver owner and local writer
|
||||
cells = int(np.prod(layout.local_shape))
|
||||
blocked_flags = np.zeros(layout.nblocks * cells, np.uint16) # halo entries unused
|
||||
blocked_flags[layout.owner.ravel().astype(np.intp)] = flags
|
||||
return dict(solid=solid, flags=blocked_flags, block=mapped.block,
|
||||
cell=(mapped.receiver % cells).astype(np.uint32),
|
||||
dir=np.array([link.dir for link in links], np.uint8),
|
||||
q=np.array([link.q for link in links], np.float32),
|
||||
fallback=np.array([link.fallback for link in links], np.uint8))
|
||||
tables, _ = blocked_link_tables(
|
||||
layout, flags, np.array([link.fluid_idx for link in links], np.uint32),
|
||||
np.array([link.dir for link in links], np.uint8),
|
||||
np.array([link.q for link in links], np.float32),
|
||||
np.array([link.fallback for link in links], np.uint8))
|
||||
return dict(solid=solid, **tables)
|
||||
|
||||
|
||||
class PeriodicBlockStepper:
|
||||
|
||||
@@ -27,6 +27,7 @@ No moving geometry is implied.
|
||||
"""
|
||||
from dataclasses import dataclass
|
||||
from numbers import Integral
|
||||
from types import SimpleNamespace
|
||||
|
||||
import numpy as np
|
||||
|
||||
@@ -266,3 +267,30 @@ class BlockLayout2D:
|
||||
or not blocked.flags.c_contiguous:
|
||||
raise ValueError(f"expected C-contiguous blocked array {self.blocked_shape}")
|
||||
return blocked
|
||||
|
||||
|
||||
def blocked_link_tables(layout, flags, fluid_idx, dir, q, fallback):
|
||||
"""Storage adapter: dense static cut-link SoA + dense flags -> blocked tables.
|
||||
|
||||
Legality (geometry, coverage, transfer isolation) stays with the caller's
|
||||
own builder; this only maps storage via map_cut_links (unique receiver
|
||||
owner and local writer, input row order kept). Returns (tables, mapped):
|
||||
tables holds device-ready arrays, with flags sized nblocks*L (unused halo
|
||||
entries 0) and cell = the receiver's local core cell.
|
||||
"""
|
||||
flags = np.asarray(flags)
|
||||
if flags.shape != (int(np.prod(layout.shape)),) or flags.dtype != np.uint16:
|
||||
raise ValueError("flags must be dense uint16 with one entry per grid cell")
|
||||
columns = [np.asarray(fluid_idx), np.asarray(dir), np.asarray(q), np.asarray(fallback)]
|
||||
if len({len(c) for c in columns}) != 1:
|
||||
raise ValueError("cut-link columns must have equal length")
|
||||
mapped = layout.map_cut_links(SimpleNamespace(fluid_idx=r, dir=d, q=v, fallback=f)
|
||||
for r, d, v, f in zip(*columns))
|
||||
cells = int(np.prod(layout.local_shape))
|
||||
blocked_flags = np.zeros(layout.nblocks * cells, np.uint16)
|
||||
blocked_flags[layout.owner.ravel().astype(np.intp)] = flags
|
||||
tables = dict(flags=blocked_flags, block=mapped.block,
|
||||
cell=(mapped.receiver % cells).astype(np.uint32),
|
||||
dir=columns[1].astype(np.uint8), q=columns[2].astype(np.float32),
|
||||
fallback=columns[3].astype(np.uint8))
|
||||
return tables, mapped
|
||||
|
||||
@@ -14,7 +14,7 @@ import numpy as np
|
||||
import pycuda.driver as cuda
|
||||
|
||||
from ..cuda import compiler_v2 as compiler
|
||||
from .blocks import BlockLayout2D
|
||||
from .blocks import BlockLayout2D, blocked_link_tables
|
||||
from .block_stepper import MAX_GRID_DIM
|
||||
from .refinement import ACTIVE, HALO, OUTER_GHOST, FixedPatch2D, prepare_fine_boundary
|
||||
from .descriptors import SOLID
|
||||
@@ -65,12 +65,15 @@ class FixedPatchStepper:
|
||||
On a device/launch failure the instance becomes unusable: close and recreate
|
||||
it, rather than pretending the partially advanced fine buffer rolled back.
|
||||
|
||||
``fine_block=(by, bx)`` (opt-in, body-free only) stores the fine level in
|
||||
``fine_block=(by, bx)`` (opt-in) stores the fine level in
|
||||
blocks.cu storage: the ENTIRE fine_shape, including the four-cell patch
|
||||
ghost band, is partitioned by a non-periodic BlockLayout2D with one-cell
|
||||
same-level halos. Patch kinds stay per cell (OUTER invalid, INNER stream
|
||||
only, ACTIVE collide); halos are gathered before each fine hop. Public
|
||||
arrays keep the dense (9, ny, nx) fine shape; only the allocation differs.
|
||||
Blocked fine_objects must be passive stationary circles (no actuation):
|
||||
prepare_fine_boundary decides legality on the dense fine grid, then cut
|
||||
links are staged receiver-local per hop: halo -> BFL -> step, same buffer.
|
||||
"""
|
||||
def __init__(self, cfg, bounds, *, context, coarse_boundary="periodic", fine_objects=(),
|
||||
fine_block=None):
|
||||
@@ -90,8 +93,8 @@ class FixedPatchStepper:
|
||||
fine_objects = tuple(fine_objects)
|
||||
self.fine_layout = None
|
||||
if fine_block is not None:
|
||||
if fine_objects:
|
||||
raise ValueError("fine_block storage is body-free; fine_objects need the dense fine level")
|
||||
if any(getattr(obj, "surface_actuation", None) is not None for obj in fine_objects):
|
||||
raise ValueError("fine_block supports passive circles only; actuation needs the dense fine level")
|
||||
if len(fine_block) != 2:
|
||||
raise ValueError("fine_block must be (by, bx)")
|
||||
fy, fx = self.patch.fine_shape
|
||||
@@ -101,6 +104,11 @@ class FixedPatchStepper:
|
||||
raise ValueError("fine blocks exceed launch limits")
|
||||
self.fine_boundary = prepare_fine_boundary(
|
||||
cfg, self.patch, fine_objects, wall_aware=self._wall_aware)
|
||||
self._fine_tables = None
|
||||
if self.fine_layout is not None and self.fine_boundary is not None:
|
||||
fluid_idx, dirs, q, _, _, _, _, fallback = self.fine_boundary.curved_host
|
||||
self._fine_tables, _ = blocked_link_tables(
|
||||
self.fine_layout, self.fine_boundary.flags, fluid_idx, dirs, q, fallback)
|
||||
self._fine_leaf = self.patch.fine_kind == ACTIVE
|
||||
if self.fine_boundary is not None:
|
||||
self._fine_leaf &= (self.fine_boundary.flags.reshape(self.patch.fine_shape) & SOLID) == 0
|
||||
@@ -113,6 +121,7 @@ class FixedPatchStepper:
|
||||
self._stream = None
|
||||
self._fine_flags = self._fine_curved = self._curved = None
|
||||
self._kind = self._halo_copy = self._halo_dst = self._halo_src = None
|
||||
self._link_tables = {}
|
||||
self._block = (cfg.threads_per_block, 1, 1)
|
||||
fine_cfg = replace(cfg, nx=self.patch.fine_shape[1], ny=self.patch.fine_shape[0],
|
||||
viscosity=2 * cfg.viscosity)
|
||||
@@ -173,7 +182,12 @@ class FixedPatchStepper:
|
||||
module = self._levels[1].module
|
||||
self._explode = module.get_function("BlockExplode")
|
||||
self._halo_copy = module.get_function("BlockHaloCopy")
|
||||
if self.fine_boundary is not None:
|
||||
if self._fine_tables is not None:
|
||||
self._fine_flags = self._upload(self._fine_tables["flags"])
|
||||
self._link_tables = {k: self._upload(v) for k, v in self._fine_tables.items()
|
||||
if k != "flags"}
|
||||
self._curved = self._levels[1].module.get_function("BlockCurvedBoundary")
|
||||
elif self.fine_boundary is not None:
|
||||
from .curved_links import CurvedLinkSoA
|
||||
boundary = self.fine_boundary
|
||||
self._fine_flags = self._upload(boundary.flags)
|
||||
@@ -266,6 +280,15 @@ class FixedPatchStepper:
|
||||
cl.link_exchange_gpu if cl.link_exchange_gpu is not None else np.uintp(0),
|
||||
grid=((cl.count + self._block[0] - 1) // self._block[0], 1), **kw)
|
||||
|
||||
def _blocked_fine_boundary(self, buffer, **kw):
|
||||
if self._fine_tables is None:
|
||||
return
|
||||
if self._curved is None or self._fine_flags is None:
|
||||
raise RuntimeError("Fine boundary device buffers are missing")
|
||||
t, count = self._link_tables, len(self._fine_tables["cell"])
|
||||
self._curved(buffer, t["block"], t["cell"], t["dir"], t["q"], t["fallback"],
|
||||
np.uint32(count), grid=((count + self._block[0] - 1) // self._block[0], 1), **kw)
|
||||
|
||||
def _fine_halo(self, buffer, **kw):
|
||||
pairs = len(self.fine_layout.halo_dst)
|
||||
if pairs:
|
||||
@@ -290,9 +313,12 @@ class FixedPatchStepper:
|
||||
**kw)
|
||||
if self.fine_layout is not None:
|
||||
# Gather one-cell block halos before EACH fine hop.
|
||||
# Same buffer per hop: halo -> receiver-local BFL -> step.
|
||||
flags = self._fine_flags if self._fine_flags is not None else np.uintp(0)
|
||||
for src, dst in ((f.current, f.spare), (f.spare, f.current)):
|
||||
self._fine_halo(src, **kw)
|
||||
f.kernel(src, dst, np.uint32(self.fine_layout.halo), np.uintp(0),
|
||||
self._blocked_fine_boundary(src, **kw)
|
||||
f.kernel(src, dst, np.uint32(self.fine_layout.halo), flags,
|
||||
self._kind, grid=f.grid, **kw)
|
||||
else:
|
||||
flags = self._fine_flags if self._fine_flags is not None else np.uintp(0)
|
||||
@@ -355,6 +381,7 @@ class FixedPatchStepper:
|
||||
error = error or exc
|
||||
self._fine_curved = None
|
||||
self._curved = self._fine_flags = None
|
||||
self._link_tables = {}
|
||||
for pointer in self._allocations:
|
||||
try:
|
||||
pointer.free()
|
||||
|
||||
Reference in New Issue
Block a user