Jets: explicit cell-edge/node frame conversion, same-physical-hit test
Replace fine_hits_to_physical with fine_hits_to_edge_frame (patch cell-edge frame, coarse centers at I+1/2, as FixedPatchStepper uses) and edge_to_node_frame (Simulation integer-node frame, shift -1/2). Test one physical circle through both frames: fine hits converted to node frame lie on the Simulation circle and one node-frame Jet selects the same arc on coarse and fine links; relabelling without conversion is detected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b1cd555b0f
commit
da8bfb0d4a
@@ -156,6 +156,17 @@ def jet_link_velocity(jets, shape, jet_id):
|
||||
return (amplitude[jet_id][:, None]*shape).astype(np.float32)
|
||||
|
||||
|
||||
def fine_hits_to_physical(hits_local, fine_origin, dx):
|
||||
"""Fine-local cell-index hits -> physical frame: origin + dx*(hit + 1/2)."""
|
||||
# Two coarse-unit frames coexist (see FixedPatchStepper docs): the patch
|
||||
# cell-edge frame puts coarse cell I's center at I + 1/2, while Simulation
|
||||
# geometry uses the integer-node frame with that center at I. Jets are
|
||||
# evaluated in the frame their region/field were written in; convert hits
|
||||
# explicitly, never by relabelling.
|
||||
|
||||
def fine_hits_to_edge_frame(hits_local, fine_origin, dx):
|
||||
"""Fine-local cell-index hits -> coarse cell-edge frame: origin + dx*(hit + 1/2)."""
|
||||
return np.asarray(fine_origin, np.float64) + dx*(np.asarray(hits_local, np.float64) + .5)
|
||||
|
||||
|
||||
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
|
||||
|
||||
+32
-3
@@ -6,7 +6,8 @@ import numpy as np
|
||||
import pytest
|
||||
|
||||
from CelerisLab.body.jets import (Jet, Parallel, Radial, Sector, Tube,
|
||||
fine_hits_to_physical, jet_link_shapes)
|
||||
edge_to_node_frame, fine_hits_to_edge_frame,
|
||||
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
|
||||
@@ -85,8 +86,36 @@ def test_selection_uses_true_hit_not_clipped_q():
|
||||
|
||||
|
||||
def test_fine_half_cell_frame():
|
||||
np.testing.assert_allclose(fine_hits_to_physical([[0., 0.], [3., 1.5]], (4., 6.), .5),
|
||||
[[4.25, 6.25], [5.75, 7.]], rtol=0, atol=0)
|
||||
edge = fine_hits_to_edge_frame([[0., 0.], [3., 1.5]], (4., 6.), .5)
|
||||
np.testing.assert_array_equal(edge, [[4.25, 6.25], [5.75, 7.]])
|
||||
np.testing.assert_array_equal(edge_to_node_frame(edge), [[3.75, 5.75], [5.25, 6.5]])
|
||||
|
||||
|
||||
def test_same_physical_hit_in_simulation_and_patch_frames():
|
||||
# One physical circle: Simulation node-frame centre C; patch edge-frame
|
||||
# centre C + 1/2; fine-local centre (C + 1/2 - origin)/dx - 1/2 as in
|
||||
# prepare_fine_boundary. Fine hits mapped edge -> node must lie on the
|
||||
# Simulation circle, and one node-frame Jet selects the same arc on both.
|
||||
from CelerisLab.body.geometry.circle import CircleGeometry
|
||||
C, R, origin, dx = np.array([10.3, 9.6]), 2.15, np.array([4., 3.]), .5
|
||||
local = (C + .5 - origin)/dx - .5
|
||||
fine = CircleGeometry(*local, R/dx).build_cut_links(40, 40)
|
||||
coarse = CircleGeometry(*C, R).build_cut_links(24, 20)
|
||||
fine_hits = edge_to_node_frame(fine_hits_to_edge_frame(
|
||||
[(l.hit_x, l.hit_y) for l in fine], origin, dx))
|
||||
coarse_hits = np.array([(l.hit_x, l.hit_y) for l in coarse])
|
||||
np.testing.assert_allclose(np.hypot(*(fine_hits - C).T), R, rtol=0, atol=1e-12)
|
||||
np.testing.assert_allclose(np.hypot(*(coarse_hits - C).T), R, rtol=0, atol=1e-12)
|
||||
jet = Jet(Sector(tuple(C), .4, .5, R + .01, R - .01), Radial(tuple(C), inward=False))
|
||||
for hits in (fine_hits, coarse_hits):
|
||||
shape, jet_id = jet_link_shapes([jet], hits)
|
||||
d = hits - C
|
||||
angle = np.arctan2(d[:, 1], d[:, 0])
|
||||
assert np.array_equal(jet_id == 0, np.abs(angle - .4) < .5)
|
||||
np.testing.assert_allclose(shape[jet_id == 0], d[jet_id == 0]/R, atol=1e-12)
|
||||
# Relabelling instead of converting moves every hit off the circle.
|
||||
wrong = fine_hits_to_edge_frame([(l.hit_x, l.hit_y) for l in fine], origin, dx)
|
||||
assert np.abs(np.hypot(*(wrong - C).T) - R).max() > .3
|
||||
|
||||
|
||||
def test_links_are_distinct_by_receiver_and_direction_and_overlap_rejected():
|
||||
|
||||
Reference in New Issue
Block a user