Skip to content

Commit f8a56dd

Browse files
authored
Merge pull request #4 from benjym/copilot/add-test-cases-inertia-model
Add inertial-physics regression tests for single-cell fall, directional streaming, walls, and τ relaxation
2 parents ea62836 + 991bbc5 commit f8a56dd

3 files changed

Lines changed: 97 additions & 0 deletions

File tree

HGD/motion/core.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include <cmath>
44
#include <algorithm>
55
#include <array>
6+
#include <stdexcept>
67

78
double inf = std::numeric_limits<double>::infinity();
89

test/test_move_particles_core.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,28 @@ def test_move_particles_core_respects_nu_cs_cap():
9292
# The source particle should remain if destination is capped.
9393
assert not np.isnan(s_out[src])
9494
assert np.isnan(s_out[dst[0], dst[1], src[2]])
95+
96+
97+
def test_single_particle_falls_at_gravity_rate_with_inertia():
98+
p = _make_particle_params(nx=3, ny=7, nm=1, nu_cs=1.0)
99+
p.g = 1.0
100+
p.dt = 1.0
101+
p.dx = 1.0
102+
p.dy = 1.0
103+
104+
# Constrain to 1-D vertical motion so gravity-only physics is testable.
105+
p.boundary_mask[0, :] = True
106+
p.boundary_mask[2, :] = True
107+
108+
s = np.full((p.nx, p.ny, p.nm), np.nan, dtype=np.float64)
109+
u = np.zeros((p.nx, p.ny, p.nm), dtype=np.float64)
110+
v = np.zeros((p.nx, p.ny, p.nm), dtype=np.float64)
111+
s[1, 6, 0] = 1.0
112+
113+
expected_speed_increment = np.sqrt(p.g * p.dy)
114+
for step in range(1, 6):
115+
u, v, s = _run_move_particles(u, v, s, p)
116+
i, j, k = np.argwhere(~np.isnan(s))[0]
117+
assert (int(i), int(j), int(k)) == (1, 6 - step, 0)
118+
assert u[i, j, k] == pytest.approx(0.0, abs=1e-12)
119+
assert v[i, j, k] == pytest.approx(-step * expected_speed_increment, abs=1e-12)

test/test_stream_lbm_zero_eq.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
from io import StringIO
12
from types import SimpleNamespace
23

34
import numpy as np
45
import pytest
6+
from HGD import params as hgd_params
57

68
try:
79
from HGD.motion import d2q4_cpp
@@ -289,3 +291,72 @@ def test_tau_relaxes_when_particle_exceeds_local_pore_size():
289291
assert not np.isnan(s_out[i0, j0, k])
290292
assert u_out[i0, j0, k] == pytest.approx(1.0 * relax_factor, abs=1e-10)
291293
assert v_out[i0, j0, k] == pytest.approx(-0.5 * relax_factor, abs=1e-10)
294+
295+
296+
def test_inertia_true_can_be_loaded_from_json5():
297+
_, p = hgd_params.load_file(StringIO("{ inertia: true, nx: 3, ny: 4, nm: 2 }"))
298+
assert p.inertia is True
299+
300+
301+
@pytest.mark.parametrize(
302+
"vx, vy, expected_ij",
303+
[
304+
(1.0, 0.0, (5, 4)),
305+
(-1.0, 0.0, (3, 4)),
306+
(0.0, 1.0, (4, 5)),
307+
(0.0, -1.0, (4, 3)),
308+
],
309+
)
310+
def test_directional_streaming_without_gravity(vx, vy, expected_ij):
311+
p = _make_params(nx=9, ny=9, nm=10)
312+
p.g = 0.0
313+
start = (4, 4, 0)
314+
u, v, s = _single_particle_state(start=start, vx=vx, vy=vy, nx=p.nx, ny=p.ny, nm=p.nm)
315+
316+
u_out, v_out, s_out = d2q4_cpp.stream(u, v, s, p)
317+
i, j, k = _particle_pos(s_out)
318+
assert (i, j) == expected_ij
319+
assert u_out[i, j, k] == pytest.approx(vx, abs=1e-12)
320+
assert v_out[i, j, k] == pytest.approx(vy, abs=1e-12)
321+
322+
323+
def test_particle_does_not_penetrate_masked_wall():
324+
p = _make_params(nx=9, ny=9, nm=10)
325+
start = (4, 4, 0)
326+
p.boundary_mask[start[0] + 1, start[1]] = True
327+
328+
u, v, s = _single_particle_state(start=start, vx=1.0, vy=0.0, nx=p.nx, ny=p.ny, nm=p.nm)
329+
u_out, v_out, s_out = d2q4_cpp.stream(u, v, s, p)
330+
i, j, k = _particle_pos(s_out)
331+
332+
assert (i, j, k) == start
333+
assert u_out[i, j, k] == pytest.approx(1.0, abs=1e-12)
334+
assert v_out[i, j, k] == pytest.approx(0.0, abs=1e-12)
335+
336+
337+
def test_tau_relaxation_matches_exponential_decay_over_time():
338+
p = _make_params(nx=7, ny=7, nm=10)
339+
p.tau = 2.0
340+
p.nu_cs = 0.5
341+
342+
s = np.full((p.nx, p.ny, p.nm), np.nan, dtype=np.float64)
343+
u = np.zeros((p.nx, p.ny, p.nm), dtype=np.float64)
344+
v = np.zeros((p.nx, p.ny, p.nm), dtype=np.float64)
345+
346+
i0, j0 = 3, 3
347+
for k in range(6):
348+
s[i0, j0, k] = 1.0
349+
u[i0, j0, k] = 1.0
350+
v[i0, j0, k] = -0.5
351+
352+
p.boundary_mask[2, 3] = True
353+
p.boundary_mask[4, 3] = True
354+
p.boundary_mask[3, 2] = True
355+
p.boundary_mask[3, 4] = True
356+
357+
for step in range(1, 4):
358+
u, v, s = d2q4_cpp.stream(u, v, s, p)
359+
expected_factor = np.exp(-step * p.dt / p.tau)
360+
for k in range(6):
361+
assert u[i0, j0, k] == pytest.approx(expected_factor, abs=1e-10)
362+
assert v[i0, j0, k] == pytest.approx(-0.5 * expected_factor, abs=1e-10)

0 commit comments

Comments
 (0)