Jets: one shared assignment check for dense and fine levels
Move surface_link_velocity (pure host code) from lbm/curved_links.py to body/jets.py, re-exported by curved_links, so refinement.py can reuse it without importing PyCUDA. prepare_fine_boundary now runs the same assignment validation as the steppers when jets are given, rejecting jet-vs-SurfaceActuation overlap (root's C(12.25,12.25) R2.15 case) as well as empty/overlapping jets. Test the overlap and a disjoint pair. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f7753be0e4
commit
038a759ff1
4 files changed
+70
-51
No files matched your search
@@ -11,6 +11,10 @@ One physical link (receiver, direction) may be driven by at most one jet.
|
||||
Edge rule: the lateral bound |s| < 1 is exclusive (the profile vanishes
|
||||
there for parabolic/quartic); Sector r_min/r_max and Tube 0 <= along <= length
|
||||
are inclusive. Jets are immutable; an amplitude change replaces the Jet.
|
||||
|
||||
``surface_link_velocity`` is the single host-only assignment/validation point
|
||||
for legacy apertures plus jets (no PyCUDA import), shared by dense and fine
|
||||
levels.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -181,3 +185,50 @@ def fine_hits_to_edge_frame(hits_local, fine_origin, dx):
|
||||
def edge_to_node_frame(points):
|
||||
"""Coarse cell-edge frame -> Simulation integer-node frame (shift by -1/2)."""
|
||||
return np.asarray(points, np.float64) - .5
|
||||
|
||||
|
||||
def surface_link_velocity(objects, cfg, flags, fluid_idx, dir, body_id, rx, ry,
|
||||
jets=(), hits=None):
|
||||
"""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 neither an object carries surface
|
||||
actuation nor a jet is given. Jets need true FP64 ``hits`` (n, 2) and may
|
||||
not share a link with each other or with a legacy aperture.
|
||||
"""
|
||||
enabled = [obj for obj in objects if obj.surface_actuation is not None]
|
||||
if not enabled and not jets:
|
||||
return None, None
|
||||
from ..lbm.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
|
||||
if jets:
|
||||
if hits is None or len(hits) != len(dir):
|
||||
raise ValueError("Jets require true hit points row-aligned with the cut links")
|
||||
if (cfg is None or not cfg.is_d2q9 or cfg.store_precision != "FP32"
|
||||
or cfg.streaming != "double_buffer" or cfg.ddf_shifting):
|
||||
raise ValueError("Jets require D2Q9/FP32/unshifted/double_buffer")
|
||||
shape, jet_id = jet_link_shapes(jets, hits)
|
||||
driven = jet_id >= 0
|
||||
owners = {obj.obj_id: obj for obj in objects}
|
||||
for bid in np.unique(body_id[driven]):
|
||||
state = vars(owners[int(bid)].state)
|
||||
if any(state.get(k, 0.) != 0 for k in ("vx", "vy", "omega")):
|
||||
raise ValueError("Jets may only drive links of stationary bodies")
|
||||
if np.any(surface_aperture & driven):
|
||||
raise ValueError("Jet overlaps a SurfaceActuation aperture")
|
||||
surface_velocity[driven] = jet_link_velocity(jets, shape, jet_id)[driven]
|
||||
surface_aperture |= driven
|
||||
return surface_velocity, surface_aperture
|
||||
Reference in new issue
Block a user