Blocked fine storage: velocity-only surface actuation on fixed circles
- curved_links.surface_link_velocity: pure host helper extracted from CurvedLinkSoA.assign_surface (which now calls it; dense path unchanged). Rows follow physical cut-link order. - blocks.cu BlockCurvedBoundary: one nullable trailing surface_velocity argument (2 floats/link), same convention as CurvedBoundaryKernel; no exchange output. Both call sites pass it explicitly (PeriodicBlockStepper passes null; single-level behaviour unchanged). - FixedPatchStepper blocked mode accepts actuated stationary circles, validated against the physical fine_cfg; uploads the velocity table only when any actuation spec exists and fails before BFL if that buffer is missing (even at zero amplitude). Blocked exchange diagnostics are unavailable; dense exchange behaviour unchanged. No force aggregation, no motion, no new numerical formula. Tests: 1x1 seam (0,0), receivers in 4 blocks, all q<.5 links fallback 0; dense SoA never built in blocked mode; launch order incl. null vs velocity pointer; missing-buffer guard at 0 and 0.002; helper == assign_surface; aperture crosses a seam; GPU: periodic and slot_reservoir, +/-0.002, 8 coarse steps, dense vs 1x1/2x2/4x2 exact values + NaN masks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
2bbf82121e
commit
7bf6101460
5 files changed
+181
-50
No files matched your search
@@ -210,7 +210,8 @@ class PeriodicBlockStepper:
|
||||
if self._curved is not None:
|
||||
t = self._tables
|
||||
self._curved(self._current, t["block"], t["cell"], t["dir"], t["q"],
|
||||
t["fallback"], np.uint32(self._links), grid=self._link_grid, **kw)
|
||||
t["fallback"], np.uint32(self._links), np.uintp(0), # no actuation
|
||||
grid=self._link_grid, **kw)
|
||||
self._step(self._current, self._spare, np.uint32(self.layout.halo),
|
||||
self._tables.get("flags", np.uintp(0)), np.uintp(0), # no kind
|
||||
grid=self._grid, **kw)
|
||||
|
||||
@@ -42,6 +42,33 @@ def _upload_columns(columns, stream=None):
|
||||
stream.synchronize()
|
||||
|
||||
|
||||
def surface_link_velocity(objects, cfg, flags, fluid_idx, dir, body_id, rx, ry):
|
||||
"""Per-link prescribed surface velocity (n, 2) FP32 and aperture mask.
|
||||
|
||||
Host-only and storage-neutral: rows follow the given physical cut-link
|
||||
order. Returns (None, None) when no object carries surface actuation.
|
||||
"""
|
||||
enabled = [obj for obj in objects if obj.surface_actuation is not None]
|
||||
if not enabled:
|
||||
return None, None
|
||||
from .descriptors import FLUID, SOLID
|
||||
if np.any((flags[fluid_idx] & FLUID) == 0) or np.any(flags[fluid_idx] & SOLID):
|
||||
raise ValueError("Surface cut links must have active fluid receivers")
|
||||
if len(np.unique(fluid_idx.astype(np.uint64)*cfg.nq+dir)) != len(dir):
|
||||
raise ValueError("Surface boundary requires unique cut-link writers")
|
||||
surface_velocity = np.zeros((len(dir), 2), np.float32)
|
||||
surface_aperture = np.zeros(len(dir), bool)
|
||||
for obj in enabled:
|
||||
obj.validate_surface_actuation(cfg)
|
||||
selected = body_id == obj.obj_id
|
||||
velocity, aperture = obj.surface_actuation.project(rx[selected], ry[selected])
|
||||
if not aperture.any():
|
||||
raise ValueError("Surface aperture hits no resolved cut links")
|
||||
surface_velocity[selected] = velocity
|
||||
surface_aperture[selected] = aperture
|
||||
return surface_velocity, surface_aperture
|
||||
|
||||
|
||||
@dataclass
|
||||
class CurvedLinkSoA:
|
||||
"""Cut-link columns on host with matching ``*_gpu`` device buffers."""
|
||||
@@ -83,25 +110,9 @@ class CurvedLinkSoA:
|
||||
|
||||
def assign_surface(self, objects, cfg, flags):
|
||||
"""Keep the original eight-column geometry/checkpoint contract unchanged."""
|
||||
enabled = [obj for obj in objects if obj.surface_actuation is not None]
|
||||
self.surface_velocity = self.aperture = None
|
||||
if not enabled:
|
||||
return
|
||||
from .descriptors import FLUID, SOLID
|
||||
if np.any((flags[self.fluid_idx] & FLUID) == 0) or np.any(flags[self.fluid_idx] & SOLID):
|
||||
raise ValueError("Surface cut links must have active fluid receivers")
|
||||
if len(np.unique(self.fluid_idx.astype(np.uint64)*cfg.nq+self.dir)) != len(self.dir):
|
||||
raise ValueError("Surface boundary requires unique cut-link writers")
|
||||
self.surface_velocity = np.zeros((len(self.dir), 2), np.float32)
|
||||
self.aperture = np.zeros(len(self.dir), bool)
|
||||
for obj in enabled:
|
||||
obj.validate_surface_actuation(cfg)
|
||||
selected = self.body_id == obj.obj_id
|
||||
velocity, aperture = obj.surface_actuation.project(self.rx[selected], self.ry[selected])
|
||||
if not aperture.any():
|
||||
raise ValueError("Surface aperture hits no resolved cut links")
|
||||
self.surface_velocity[selected] = velocity
|
||||
self.aperture[selected] = aperture
|
||||
self.surface_velocity, self.aperture = surface_link_velocity(
|
||||
objects, cfg, flags, self.fluid_idx, self.dir, self.body_id, self.rx, self.ry)
|
||||
|
||||
def surface_exchange(self, body_id):
|
||||
"""Latest boundary step's (Fx,Fy,Tz), split by aperture; not nozzle thrust.
|
||||
|
||||
@@ -54,14 +54,22 @@ extern "C" __global__ void BlockExplode(
|
||||
// Static cut links, receiver-local staging: cell is the receiver's local core
|
||||
// cell, so the existing BFL writes the solid-source window image that this
|
||||
// block's next pull reads (no halo copy between this kernel and BlockStep).
|
||||
// surface_velocity (nullable, 2 floats per link): prescribed fluid velocity on
|
||||
// fixed geometry, same convention as CurvedBoundaryKernel. No exchange output.
|
||||
extern "C" __global__ void BlockCurvedBoundary(
|
||||
fpxx* f, const uint32_t* block, const uint32_t* cell, const unsigned char* dir,
|
||||
const float* q, const unsigned char* fallback, unsigned int count)
|
||||
const float* q, const unsigned char* fallback, unsigned int count,
|
||||
const float* surface_velocity)
|
||||
{
|
||||
unsigned int row = blockIdx.x * blockDim.x + threadIdx.x;
|
||||
if (row >= count) return;
|
||||
float Uw = 0.0f, Vw = 0.0f;
|
||||
if (surface_velocity != nullptr) {
|
||||
const float u = surface_velocity[2 * row], v = surface_velocity[2 * row + 1];
|
||||
if (u != 0.0f || v != 0.0f) { Uw = u; Vw = v; }
|
||||
}
|
||||
apply_bouzidi_link(dir[row], q[row], cell[row], f + (unsigned long)block[row] * NQ * TOTAL_CELLS,
|
||||
0.0f, 0.0f, 0.0f, 0.0f, fallback[row], nullptr, -1);
|
||||
Uw, Vw, 0.0f, 0.0f, fallback[row], nullptr, -1);
|
||||
}
|
||||
|
||||
// Pull/collide/store core cells only; halo cells of out are left untouched.
|
||||
|
||||
@@ -15,6 +15,7 @@ import pycuda.driver as cuda
|
||||
|
||||
from ..cuda import compiler_v2 as compiler
|
||||
from .blocks import BlockLayout2D, blocked_link_tables
|
||||
from .curved_links import surface_link_velocity
|
||||
from .block_stepper import MAX_GRID_DIM
|
||||
from .refinement import ACTIVE, HALO, OUTER_GHOST, FixedPatch2D, prepare_fine_boundary
|
||||
from .descriptors import SOLID
|
||||
@@ -71,9 +72,11 @@ class FixedPatchStepper:
|
||||
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):
|
||||
Blocked fine_objects are stationary circles, optionally surface-actuated:
|
||||
prepare_fine_boundary decides legality on the dense fine grid, then cut
|
||||
links are staged receiver-local per hop: halo -> BFL -> step, same buffer.
|
||||
Blocked mode applies prescribed surface velocity only; its per-link
|
||||
exchange diagnostic is unavailable (rows keep physical cut-link order).
|
||||
"""
|
||||
def __init__(self, cfg, bounds, *, context, coarse_boundary="periodic", fine_objects=(),
|
||||
fine_block=None):
|
||||
@@ -93,8 +96,6 @@ class FixedPatchStepper:
|
||||
fine_objects = tuple(fine_objects)
|
||||
self.fine_layout = None
|
||||
if fine_block is not None:
|
||||
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
|
||||
@@ -104,7 +105,7 @@ 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
|
||||
self._fine_tables, self._actuated = None, False
|
||||
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(
|
||||
@@ -128,6 +129,14 @@ class FixedPatchStepper:
|
||||
if self._wall_aware:
|
||||
fine_cfg = replace(fine_cfg, y_wall_bc="bounce_back", top_wall_bc="velocity",
|
||||
wall_slot_width=0, wall_normal_velocity=0.0, velocity=0.0)
|
||||
if self._fine_tables is not None:
|
||||
# Any actuation spec (even zero amplitude) requires the velocity buffer.
|
||||
b = self.fine_boundary
|
||||
self._actuated = any(obj.surface_actuation is not None for obj in b.objects)
|
||||
if self._actuated:
|
||||
fluid_idx, dirs, _, body_id, rx, ry, _, _ = b.curved_host
|
||||
self._fine_tables["surface_velocity"], _ = surface_link_velocity(
|
||||
b.objects, fine_cfg, b.flags, fluid_idx, dirs, body_id, rx, ry)
|
||||
with self.ctx.activate():
|
||||
try:
|
||||
self._stream = cuda.Stream()
|
||||
@@ -286,8 +295,12 @@ class FixedPatchStepper:
|
||||
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"])
|
||||
velocity = t.get("surface_velocity")
|
||||
if self._actuated and velocity is None:
|
||||
raise RuntimeError("Surface velocity device buffer is missing")
|
||||
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)
|
||||
np.uint32(count), velocity if velocity is not None else np.uintp(0),
|
||||
grid=((count + self._block[0] - 1) // self._block[0], 1), **kw)
|
||||
|
||||
def _fine_halo(self, buffer, **kw):
|
||||
pairs = len(self.fine_layout.halo_dst)
|
||||
|
||||
Reference in new issue
Block a user