From a454f023555dcf8c70af9a31aeda0826aed96b97 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 21 Oct 2026 05:57:11 +0000 Subject: [PATCH] Jets: compute jet link shapes once per boundary preparation surface_link_velocity already runs jet_link_shapes; CurvedLinkSoA. assign_surface and _prepare_blocked_boundary then ran it again with the same jets and hits to fill the host jet cache. A private _surface_links returns (velocity, aperture, shape, jet_id) from the one call; both callers use it. surface_link_velocity keeps its two-value public contract (now a thin wrapper). Same values, same validation order, same cache contents; host geometry preparation only. Co-Authored-By: Claude Opus 5.5 --- src/CelerisLab/body/jets.py | 17 ++++++++++++++--- src/CelerisLab/lbm/curved_links.py | 6 ++---- src/CelerisLab/lbm/refinement_tables.py | 10 ++++++---- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/CelerisLab/body/jets.py b/src/CelerisLab/body/jets.py index e75ecb7..bbd8774 100644 --- a/src/CelerisLab/body/jets.py +++ b/src/CelerisLab/body/jets.py @@ -207,19 +207,30 @@ def surface_link_velocity(objects, cfg, flags, fluid_idx, dir, body_id, rx, ry, jets=(), hits=None, *, _shifted_blocked=False): """Per-link prescribed surface velocity (n, 2) FP32 and aperture mask. + See _surface_links, which also returns the unit-amplitude jet shapes. + """ + return _surface_links(objects, cfg, flags, fluid_idx, dir, body_id, rx, ry, jets, hits, + _shifted_blocked=_shifted_blocked)[:2] + + +def _surface_links(objects, cfg, flags, fluid_idx, dir, body_id, rx, ry, + jets=(), hits=None, *, _shifted_blocked=False): + """(velocity, aperture, jet shape, jet id); the last two are None without jets. + Host-only and storage-neutral: rows follow the given physical cut-link - order. Returns (None, None) when neither an object carries surface + order. Returns all 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. Shifted storage is privately enabled only by the all-blocked stepper. """ enabled = [obj for obj in objects if obj.surface_actuation is not None] if not enabled and not jets: - return None, None + return None, None, None, None from .preprocess.flag_overlay import validate_cut_link_ownership validate_cut_link_ownership(flags, fluid_idx, dir, cfg.nq) surface_velocity = np.zeros((len(dir), 2), np.float32) surface_aperture = np.zeros(len(dir), bool) + shape = jet_id = None for obj in enabled: obj.validate_surface_actuation(cfg) selected = body_id == obj.obj_id @@ -252,4 +263,4 @@ def surface_link_velocity(objects, cfg, flags, fluid_idx, dir, body_id, rx, ry, 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 + return surface_velocity, surface_aperture, shape, jet_id diff --git a/src/CelerisLab/lbm/curved_links.py b/src/CelerisLab/lbm/curved_links.py index 1d67d10..d26e6b8 100644 --- a/src/CelerisLab/lbm/curved_links.py +++ b/src/CelerisLab/lbm/curved_links.py @@ -94,12 +94,10 @@ class CurvedLinkSoA: """ self.surface_velocity = self.aperture = None self.jets, self.jet_shape, self.jet_id = tuple(jets), None, None - self.surface_velocity, self.aperture = surface_link_velocity( + from ..body.jets import _surface_links + self.surface_velocity, self.aperture, self.jet_shape, self.jet_id = _surface_links( objects, cfg, flags, self.fluid_idx, self.dir, self.body_id, self.rx, self.ry, jets=self.jets, hits=hits) - if self.jets: - from ..body.jets import jet_link_shapes - self.jet_shape, self.jet_id = jet_link_shapes(self.jets, hits) def set_jet_amplitude(self, index, amplitude, stream=None): """Rewrite jet rows from cached shapes; re-upload only the velocity column. diff --git a/src/CelerisLab/lbm/refinement_tables.py b/src/CelerisLab/lbm/refinement_tables.py index b2dd7eb..b4b5b73 100644 --- a/src/CelerisLab/lbm/refinement_tables.py +++ b/src/CelerisLab/lbm/refinement_tables.py @@ -8,7 +8,7 @@ from dataclasses import dataclass, replace import numpy as np from .blocks import blocked_link_tables -from ..body.jets import jet_link_shapes, surface_link_velocity +from ..body.jets import _surface_links from .descriptors import D2Q9_EX, D2Q9_EY from .refinement import ACTIVE, OUTER_GHOST @@ -142,11 +142,13 @@ def _prepare_blocked_boundary(boundary, layout, cfg, jets, *, _shifted_blocked=F tables.update(body_id=np.asarray(body_id, np.int32), rx=np.asarray(rx, np.float32), ry=np.asarray(ry, np.float32), aperture=np.zeros(len(dirs), bool)) actuated = bool(jets) or any(o.surface_actuation is not None for o in boundary.objects) + jet_cache = None if actuated: cfg = replace(cfg) cfg.validate() - tables["surface_velocity"], tables["aperture"] = surface_link_velocity( + tables["surface_velocity"], tables["aperture"], shape, jet_id = _surface_links( boundary.objects, cfg, boundary.flags, fluid_idx, dirs, body_id, rx, ry, jets=jets, hits=boundary.curved_hits, _shifted_blocked=_shifted_blocked) - return _BlockedBoundary(boundary, tables, jets, actuated, - jet_link_shapes(jets, boundary.curved_hits) if jets else None) + if jets: + jet_cache = (shape, jet_id) + return _BlockedBoundary(boundary, tables, jets, actuated, jet_cache)