research: Dütsch driver — exact-point body mask, periodic geometry from step mod T

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Claude
2026-10-30 10:59:55 +00:00
co-authored by Claude Opus 5.5
parent 73c2f507f4
commit 04a33246a1
3 changed files with 43 additions and 5 deletions
+18 -3
View File
@@ -25,12 +25,15 @@ def amplitude(D):
def position(s, T, A): def position(s, T, A):
"""Centre position relative to the mean: x = A cos(2 pi s / T).""" """Centre position relative to the mean: x = A cos(2 pi s / T), evaluated from the
return A * math.cos(2.0 * math.pi * s / T) native step modulo T so every cycle repeats the same geometry bit for bit."""
return A * math.cos(2.0 * math.pi * (int(s) % T) / T)
def phase(s, T): def phase(s, T):
return (360.0 * s / T - 90.0) % 360.0 """Paper phase in degrees from the native step modulo T (the unwrapped cycle
index is kept separately by ``cycle``)."""
return (360.0 * (int(s) % T) / T - 90.0) % 360.0
def cycle(s, T): def cycle(s, T):
@@ -63,6 +66,18 @@ def morison_fit(f_norm, b1, b2):
return float(c_d), float(c_i) return float(c_d), float(c_i)
def inside_circle(x, y, centre, radius):
return (x - centre[0]) ** 2 + (y - centre[1]) ** 2 < radius * radius
def sample(field, fluid, x, y, centre, radius):
"""Profile sample: MISSING if the exact point is inside the body (checked before
any corner interpolation), else ``bilinear_fluid``."""
if inside_circle(x, y, centre, radius):
return None, (), 0.0
return bilinear_fluid(field, fluid, x, y)
def bilinear_fluid(field, fluid, x, y): def bilinear_fluid(field, fluid, x, y):
"""Bilinear value at (x, y) in cell coordinates from fluid cells only. """Bilinear value at (x, y) in cell coordinates from fluid cells only.
+3 -2
View File
@@ -70,11 +70,12 @@ def main():
fields = sim.get_macroscopic() fields = sim.get_macroscopic()
fluid = (sim.get_flags().reshape(n, n) & 1) != 0 fluid = (sim.get_flags().reshape(n, n) & 1) != 0
ux, uy = (np.asarray(fields[k]).reshape(n, n) for k in ("ux", "uy")) ux, uy = (np.asarray(fields[k]).reshape(n, n) for k in ("ux", "uy"))
centre = (xm + p.position(s, T, A), ym) # analytic centre after the step
for xs in p.STATIONS: for xs in p.STATIONS:
for x2 in p.X2: for x2 in p.X2:
x, y = xm + xs * D, ym + x2 * D x, y = xm + xs * D, ym + x2 * D
u, wu, wsum = p.bilinear_fluid(ux, fluid, x, y) u, wu, wsum = p.sample(ux, fluid, x, y, centre, R)
v, _, _ = p.bilinear_fluid(uy, fluid, x, y) v, _, _ = p.sample(uy, fluid, x, y, centre, R)
samples.append((s, p.cycle(s, T), ph, xs, x2, samples.append((s, p.cycle(s, T), ph, xs, x2,
math.nan if u is None else u, math.nan if v is None else v, math.nan if u is None else u, math.nan if v is None else v,
wsum, json.dumps(wu))) wsum, json.dumps(wu)))
+22
View File
@@ -62,3 +62,25 @@ def test_interval_bases_and_morison_fit_recover_known_coefficients():
assert np.allclose(b[:, 1], np.sin(phi), atol=1e-5) # midpoint ~ interval mean assert np.allclose(b[:, 1], np.sin(phi), atol=1e-5) # midpoint ~ interval mean
f = 2.09 * b[:, 0] - (math.pi ** 2 * 1.45 / p.KC) * b[:, 1] f = 2.09 * b[:, 0] - (math.pi ** 2 * 1.45 / p.KC) * b[:, 1]
assert p.morison_fit(f, b[:, 0], b[:, 1]) == pytest.approx((2.09, 1.45), abs=1e-9) assert p.morison_fit(f, b[:, 0], b[:, 1]) == pytest.approx((2.09, 1.45), abs=1e-9)
def test_geometry_repeats_exactly_across_cycles():
A = p.amplitude(20)
for m in (0, 1, T // 4, T // 2 - 1, 7 * T // 12):
for c in (1, 5, 11):
assert p.position(m + c * T, T, A) == p.position(m, T, A)
assert p.phase(m + c * T, T) == p.phase(m, T)
v0 = p.position(m + 1, T, A) - p.position(m, T, A)
assert p.position(m + 1 + c * T, T, A) - p.position(m + c * T, T, A) == v0
assert p.cycle(T // 4 + 5 * T, T) == 6 # unwrapped index stays separate
def test_exact_point_inside_body_is_missing_even_with_fluid_corners():
field = np.zeros((6, 6))
fluid = np.ones((6, 6), bool)
fluid[3, 3] = False # only one corner solid
centre, radius = (3.0, 3.0), 0.3
value, used, total = p.sample(field, fluid, 3.2, 3.1, centre, radius) # inside the circle
assert value is None and used == () and total == 0.0
value, used, total = p.sample(field, fluid, 3.35, 3.0, centre, radius) # just outside
assert value is not None and total > 0.0