Wire Jets through ObjectManager and Simulation
ObjectManager.jets holds scene-level Jets. build_compact_lists_for collects true FP64 hits (SimObject.get_curved_list hits_out) row-aligned with the cut-link columns; BodySyncPlan carries them as curved_hits, and sync_to_gpu / apply_sync_plan / the pre-sync check pass jets+hits to assign_surface. Jets require D2Q9/FP32/unshifted/double_buffer and may only drive links of stationary bodies. Simulation.add_jet (before initialize) and set_jet_amplitude (velocity column re-upload only); save_checkpoint refuses scenes with jets. Fine-path wiring not included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
611a64c54a
commit
932702f779
6 files changed
+112
-11
No files matched your search
@@ -1,5 +1,6 @@
|
||||
"""Body-independent Jet: true-hit spatial selection, fields, profiles, overlap, amplitude."""
|
||||
import math
|
||||
from types import SimpleNamespace
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
@@ -8,6 +9,7 @@ from CelerisLab.body.jets import (Jet, Parallel, Radial, Sector, Tube,
|
||||
fine_hits_to_physical, jet_link_shapes)
|
||||
from CelerisLab.body.objects import SurfaceActuation
|
||||
from CelerisLab.lbm.curved_links import CurvedLinkSoA, surface_link_velocity
|
||||
from .test_state_regressions import device, make_field, manager
|
||||
from .test_surface_actuation import circle, projected
|
||||
|
||||
CENTER, RADIUS = (6., 5.), 2.999
|
||||
@@ -132,3 +134,46 @@ def test_legacy_adapter_measured_equivalence(spec):
|
||||
diff = np.abs(cl.surface_velocity - legacy.surface_velocity).max()
|
||||
print(f"legacy adapter max |dU| = {diff:.3e} (exact={diff == 0})")
|
||||
assert diff <= 4*np.finfo(np.float32).eps*abs(spec.normal_velocity)
|
||||
|
||||
|
||||
def jet_case(device, jets):
|
||||
field = make_field(device)
|
||||
bodies = manager(field, [circle(radius=2.)])
|
||||
bodies.jets.extend(jets)
|
||||
bodies.sync_to_gpu(field)
|
||||
return field, bodies
|
||||
|
||||
|
||||
def test_manager_passes_true_hits_and_reuploads_only_velocity(device):
|
||||
# CPU memory double only: checks host/device bookkeeping, not GPU numerics.
|
||||
jet = Jet(Sector(CENTER, 0., .6, 3.), Radial(CENTER, inward=False), "quartic", 2e-3)
|
||||
field, bodies = jet_case(device, [jet])
|
||||
cl = field.curved
|
||||
assert cl.jet_id is not None and (cl.jet_id == 0).sum() >= 2
|
||||
velocity_gpu = cl.surface_velocity_gpu
|
||||
np.testing.assert_array_equal(np.frombuffer(velocity_gpu.data, np.float32).reshape(-1, 2),
|
||||
cl.surface_velocity)
|
||||
geometry = {name: bytes(getattr(cl, name + "_gpu").data)
|
||||
for name in ("fluid_idx", "dir", "q", "body_id", "rx", "ry")}
|
||||
writes, before = device.writes, cl.surface_velocity.copy()
|
||||
cl.set_jet_amplitude(0, -1e-3, SimpleNamespace(synchronize=lambda: None))
|
||||
assert device.writes == writes + 1 and cl.surface_velocity_gpu is velocity_gpu
|
||||
np.testing.assert_array_equal(np.frombuffer(velocity_gpu.data, np.float32).reshape(-1, 2),
|
||||
cl.surface_velocity)
|
||||
assert all(bytes(getattr(cl, name + "_gpu").data) == data for name, data in geometry.items())
|
||||
np.testing.assert_allclose(cl.surface_velocity, -.5*before, rtol=1e-6, atol=0)
|
||||
np.testing.assert_array_equal(cl.surface_velocity[cl.jet_id < 0], 0.)
|
||||
|
||||
|
||||
def test_manager_rejects_overlapping_jets_and_moving_bodies(device):
|
||||
a = Jet(Sector(CENTER, 0., .6, 3.), Radial(CENTER))
|
||||
b = Jet(Tube(CENTER, 0., 3., .5), Parallel(0.))
|
||||
with pytest.raises(ValueError, match="overlaps"):
|
||||
jet_case(device, [a, b])
|
||||
field = make_field(device)
|
||||
moving = circle(radius=2.)
|
||||
moving.state.omega = 1e-3
|
||||
bodies = manager(field, [moving])
|
||||
bodies.jets.append(a)
|
||||
with pytest.raises(ValueError, match="stationary"):
|
||||
bodies.sync_to_gpu(field)
|
||||
Reference in new issue
Block a user