Jets: freeze origins, guard set_body, fine-boundary hit helper
Sector/Tube/Radial store origin as a validated float 2-tuple, so caller lists/arrays cannot change a registered definition. Simulation.set_body rejects nonzero motion for bodies with Jet-driven links once initialized (before initialize the sync-time stationary check applies). prepare_fine_boundary accepts jets in the same coarse cell-edge frame as fine objects, records read-only FP64 edge-frame hits (FineBoundary2D .curved_hits, default None) and rejects empty or overlapping selections before CUDA. FixedPatchStepper wiring is not included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
da8bfb0d4a
commit
62c525e735
4 files changed
+82
-5
No files matched your search
@@ -32,6 +32,15 @@ def _finite(*values):
|
||||
raise ValueError("Jet parameters must be finite")
|
||||
|
||||
|
||||
def _freeze_origin(spec):
|
||||
"""Store ``origin`` as a validated float 2-tuple (no caller-owned list/array)."""
|
||||
origin = tuple(float(v) for v in spec.origin)
|
||||
if len(origin) != 2:
|
||||
raise ValueError("Jet origin must be a 2D point")
|
||||
_finite(*origin)
|
||||
object.__setattr__(spec, "origin", origin)
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Sector:
|
||||
"""Annular sector about ``origin``: |angle offset| < half_width, r_min <= r <= r_max."""
|
||||
@@ -42,7 +51,8 @@ class Sector:
|
||||
r_min: float = 0.0
|
||||
|
||||
def __post_init__(self):
|
||||
_finite(*self.origin, self.angle, self.half_width, self.r_max, self.r_min)
|
||||
_freeze_origin(self)
|
||||
_finite(self.angle, self.half_width, self.r_max, self.r_min)
|
||||
if not (0 < self.half_width <= math.pi and 0 <= self.r_min <= self.r_max):
|
||||
raise ValueError("Sector requires 0 < half_width <= pi and 0 <= r_min <= r_max")
|
||||
|
||||
@@ -63,7 +73,8 @@ class Tube:
|
||||
half_width: float
|
||||
|
||||
def __post_init__(self):
|
||||
_finite(*self.origin, self.axis_angle, self.length, self.half_width)
|
||||
_freeze_origin(self)
|
||||
_finite(self.axis_angle, self.length, self.half_width)
|
||||
if self.length <= 0 or self.half_width <= 0:
|
||||
raise ValueError("Tube requires positive length and half_width")
|
||||
|
||||
@@ -82,7 +93,7 @@ class Radial:
|
||||
inward: bool = True
|
||||
|
||||
def __post_init__(self):
|
||||
_finite(*self.origin)
|
||||
_freeze_origin(self)
|
||||
|
||||
def direction(self, hits):
|
||||
d = hits - np.asarray(self.origin, np.float64)
|
||||
|
||||
@@ -153,6 +153,7 @@ class FineBoundary2D:
|
||||
transfer_fine_cells: np.ndarray
|
||||
transfer_coarse_cells: np.ndarray
|
||||
transfer_reads: np.ndarray
|
||||
curved_hits: np.ndarray | None = None # FP64 cell-edge-frame hits, row-aligned
|
||||
|
||||
|
||||
def _transfer_footprint(patch):
|
||||
@@ -183,15 +184,20 @@ def _transfer_footprint(patch):
|
||||
return fine, coarse, _readonly(sorted(reads), np.int64)
|
||||
|
||||
|
||||
def prepare_fine_boundary(cfg, patch, objects, *, wall_aware=False):
|
||||
def prepare_fine_boundary(cfg, patch, objects, *, wall_aware=False, jets=()):
|
||||
"""Project fixed circles and reject boundary/transfer overlap before CUDA.
|
||||
|
||||
Input centers/radii are in coarse physical coordinates (dx=dt=1).
|
||||
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.
|
||||
``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).
|
||||
"""
|
||||
if not objects:
|
||||
if jets:
|
||||
raise ValueError("Fine jets need fine boundary objects")
|
||||
return None
|
||||
checked = replace(cfg)
|
||||
checked.validate()
|
||||
@@ -286,8 +292,14 @@ def prepare_fine_boundary(cfg, patch, objects, *, wall_aware=False):
|
||||
# coalescence; rows 2/ny-3 are ordinary bulk cells only read as donors.
|
||||
if wall_aware and (y <= 1 or y >= cfg.ny-2):
|
||||
raise ValueError("Transfer footprint touches the coarse wall reconstruction stencil")
|
||||
from ..body.jets import fine_hits_to_edge_frame, jet_link_shapes
|
||||
hits = fine_hits_to_edge_frame([(link.hit_x, link.hit_y) for link in links],
|
||||
patch.fine_origin, dx)
|
||||
if jets:
|
||||
jet_link_shapes(jets, hits) # empty selection/overlap rejected before CUDA
|
||||
return FineBoundary2D(tuple(local), _readonly(flags, np.uint16),
|
||||
tuple(_readonly(a, a.dtype) for a in pack_cut_links_to_soa(links)),
|
||||
_readonly(sorted(boundary), np.uint32),
|
||||
_readonly(sorted(fine), np.uint32),
|
||||
_readonly(sorted(coarse), np.uint32), reads)
|
||||
_readonly(sorted(coarse), np.uint32), reads,
|
||||
_readonly(hits, np.float64))
|
||||
@@ -260,6 +260,11 @@ class Simulation:
|
||||
proposed.update({k: float(v) for k, v in (("omega", omega), ("vx", vx), ("vy", vy))
|
||||
if v is not None})
|
||||
obj.validate_surface_actuation(self.lbm_cfg, proposed)
|
||||
cl = self.field.curved
|
||||
if (self._initialized and cl.jet_id is not None
|
||||
and np.any(cl.body_id[cl.jet_id >= 0] == id)
|
||||
and any(proposed.get(k, 0.) != 0 for k in ("vx", "vy", "omega"))):
|
||||
raise ValueError("Jet-driven bodies must stay stationary")
|
||||
changed = False
|
||||
if omega is not None:
|
||||
obj.state.omega = float(omega)
|
||||
|
||||
Reference in new issue
Block a user