test: integrate pinned Re100 and jet control-volume validation
This commit is contained in:
1 parent
d79e3f6f0a
commit
69e9b78de9
12 files changed
+2418
No files matched your search
@@ -0,0 +1,139 @@
|
||||
"""CPU-only science, geometry, units, and source tests; no PyCUDA import."""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
import unittest
|
||||
import numpy as np
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
REPO = ROOT
|
||||
sys.path.insert(0, str(ROOT / "tests/validation"))
|
||||
sys.path.insert(0, str(REPO / "src"))
|
||||
from run_jet_circle_cv import (
|
||||
CX,
|
||||
CY,
|
||||
W,
|
||||
case,
|
||||
state,
|
||||
cv,
|
||||
aperture_probe,
|
||||
measured_aperture_flux,
|
||||
verify,
|
||||
)
|
||||
|
||||
|
||||
def eq(rho, u, v):
|
||||
cu = CX[:, None, None] * u + CY[:, None, None] * v
|
||||
return W[:, None, None] * rho * (1 + 3 * cu + 4.5 * cu**2 - 1.5 * (u * u + v * v))
|
||||
|
||||
|
||||
class Host(unittest.TestCase):
|
||||
def test_descriptor_and_uniform_closed_flux(self):
|
||||
from CelerisLab.lbm.descriptors import D2Q9_EX, D2Q9_EY
|
||||
|
||||
np.testing.assert_array_equal(CX, D2Q9_EX)
|
||||
np.testing.assert_array_equal(CY, D2Q9_EY)
|
||||
rho = np.full((48, 64), 1.01)
|
||||
g = eq(rho, 0.023, -0.017)
|
||||
inventory, flux = cv(g, np.ones((48, 64), bool), (0, 0), (5, 4, 25, 20), 0.0072)
|
||||
np.testing.assert_allclose(
|
||||
inventory, 320 * 1.01 * np.array([1, 0.023, -0.017]), atol=1e-12
|
||||
)
|
||||
np.testing.assert_allclose(flux, 0, atol=1e-14)
|
||||
|
||||
def test_affine_velocity_independent_divergence(self):
|
||||
y, x = np.mgrid[:48, :64]
|
||||
x = (x + 0.5) * 0.5
|
||||
y = (y + 0.5) * 0.5
|
||||
a = 0.001
|
||||
g = eq(np.ones_like(x), a * x, -a * y)
|
||||
_, flux = cv(g, np.ones(x.shape, bool), (0, 0), (5, 4, 25, 20), 0.0072)
|
||||
np.testing.assert_allclose(
|
||||
flux, [0, a * a * 320 * 15, a * a * 320 * 12], atol=1e-13
|
||||
)
|
||||
|
||||
def test_quadratic_viscous_flux_sign_and_units(self):
|
||||
y, x = np.mgrid[:48, :64]
|
||||
y = (y + 0.5) * 0.5
|
||||
a = 0.0001
|
||||
nu = 0.0072
|
||||
g = eq(np.ones_like(y), a * y * y, np.zeros_like(y))
|
||||
_, flux = cv(g, np.ones(y.shape, bool), (0, 0), (5, 4, 25, 20), nu)
|
||||
np.testing.assert_allclose(flux, [0, -2 * nu * a * 320, 0], atol=2e-14)
|
||||
|
||||
def test_geometry_adapter_match_and_apertures(self):
|
||||
from CelerisLab.config import LBMConfig
|
||||
from CelerisLab.body.geometry.circle import CircleGeometry
|
||||
from CelerisLab.body.objects import SimObject
|
||||
from CelerisLab.body.jets import Jet, Sector, Radial, jet_link_shapes
|
||||
from CelerisLab.lbm.refinement import FixedPatch2D, prepare_fine_boundary
|
||||
from CelerisLab.lbm.descriptors import SOLID
|
||||
|
||||
c = case()
|
||||
center = np.array(c["center"])
|
||||
r = c["diameter"] / 2
|
||||
cfg = LBMConfig(nx=c["nx"], ny=c["ny"], viscosity=c["nu"])
|
||||
cfg.validate()
|
||||
patch = FixedPatch2D.from_config(cfg, c["bounds"])
|
||||
obj = SimObject(0, CircleGeometry(*center, r), center=tuple(center), radius=r)
|
||||
jets = [
|
||||
Jet(
|
||||
Sector(tuple(center), a, np.pi / 12, r + 0.1),
|
||||
Radial(tuple(center), inward=False),
|
||||
"uniform",
|
||||
s * c["jet_speed"],
|
||||
)
|
||||
for a, s in [(np.pi / 2, 1), (3 * np.pi / 2, -1)]
|
||||
]
|
||||
host = prepare_fine_boundary(cfg, patch, [obj], jets=jets)
|
||||
shape, ids = jet_link_shapes(jets, host.curved_hits)
|
||||
self.assertTrue((ids == 0).any() and (ids == 1).any() and (ids < 0).any())
|
||||
self.assertGreater(host.curved_host[2].min(), 0.010001)
|
||||
self.assertFalse(np.any(host.curved_host[-1]))
|
||||
links = CircleGeometry(*(2 * center - 0.5), 2 * r).build_cut_links(
|
||||
2 * c["nx"], 2 * c["ny"]
|
||||
)
|
||||
np.testing.assert_array_equal([v.dir for v in links], host.curved_host[1])
|
||||
np.testing.assert_allclose(
|
||||
[v.q for v in links], host.curved_host[2], atol=2e-7, rtol=0
|
||||
)
|
||||
np.testing.assert_allclose(
|
||||
np.array([[v.hit_x, v.hit_y] for v in links]) * 0.5 + 0.25,
|
||||
host.curved_hits,
|
||||
atol=2e-12,
|
||||
)
|
||||
fluid = (host.flags.reshape(patch.fine_shape) & SOLID) == 0
|
||||
g = state((9, *patch.fine_shape), c["u"])
|
||||
g[:, ~fluid] = np.nan
|
||||
for contour in c["contours"]:
|
||||
cv(g, fluid, patch.fine_origin, contour, c["nu"])
|
||||
probe = aperture_probe(fluid, patch.fine_origin, center, r)
|
||||
measured = measured_aperture_flux(g, probe)
|
||||
np.testing.assert_allclose(
|
||||
measured, 0, atol=1e-14
|
||||
) # x-directed flow has zero flux on symmetric top/bottom arcs
|
||||
print(
|
||||
"GEOMETRY",
|
||||
dict(
|
||||
links=len(links),
|
||||
aperture_counts=[int((ids == i).sum()) for i in [0, 1]],
|
||||
q_min=float(host.curved_host[2].min()),
|
||||
q_max=float(host.curved_host[2].max()),
|
||||
fallbacks=int(np.count_nonzero(host.curved_host[-1])),
|
||||
sample_distances=[p[-1] for p in probe],
|
||||
),
|
||||
)
|
||||
|
||||
def test_diffusive_scaling(self):
|
||||
a, b = case(1), case(2)
|
||||
self.assertEqual(a["re_d"], b["re_d"])
|
||||
self.assertEqual(a["nu"], b["nu"])
|
||||
self.assertEqual(a["final_t_star"], b["final_t_star"])
|
||||
self.assertEqual(a["steps"] * 4, b["steps"])
|
||||
|
||||
def test_source_pin(self):
|
||||
verify(REPO)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,103 @@
|
||||
import sys, json, hashlib
|
||||
from pathlib import Path
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT / "tests/validation"))
|
||||
import re100_helpers as h
|
||||
import periodic as p
|
||||
|
||||
|
||||
@pytest.mark.parametrize("n", [20, 40, 80])
|
||||
def test_units(n):
|
||||
x = h.parameters(n, 0.01)
|
||||
assert x["viscosity"] * x["dx"] ** 2 / x["dt"] == pytest.approx(0.001)
|
||||
assert x["mean_speed"] * x["dx"] / x["dt"] == pytest.approx(1.0)
|
||||
assert x["mean_speed"] * n / x["viscosity"] == pytest.approx(100)
|
||||
assert x["peak_mach"] == pytest.approx(0.015 * np.sqrt(3))
|
||||
assert (x["top"] - x["bottom"]) * x["dx"] == pytest.approx(0.41)
|
||||
assert (x["nx"] - 1) * x["dx"] == pytest.approx(2.2)
|
||||
assert (np.array(x["center"]) - [0, 0.5]) * x["dx"] == pytest.approx([0.2, 0.2])
|
||||
cfg, _, _ = h.make_config(n, 0.01, "MRT", "neq_extrap", "zou_he_local")
|
||||
assert cfg["method"]["inlet"]["scheme"] == "zou_he_local"
|
||||
|
||||
|
||||
def test_pressure_units():
|
||||
x = h.parameters(40, 0.01)
|
||||
ny, nx = x["ny"], x["nx"]
|
||||
xx = np.arange(nx)[None, :]
|
||||
rho = np.broadcast_to(1 + xx * 1e-6, (ny, nx))
|
||||
fluid = np.ones_like(rho, dtype=bool)
|
||||
assert h.pressure_probes(rho, fluid, x)["dp"] == pytest.approx(
|
||||
-40e-6 / 0.01**2 / 3, abs=1e-10
|
||||
)
|
||||
|
||||
|
||||
def rows(amplitude=1.0, drift=0.0):
|
||||
out = []
|
||||
for t in np.arange(240, 300.001, 0.02):
|
||||
out.append(
|
||||
dict(
|
||||
tstar=t,
|
||||
tstar_force=t,
|
||||
cl=amplitude * np.sin(2 * np.pi * t / 3.3),
|
||||
cd=3.2 + 0.03 * np.cos(4 * np.pi * t / 3.3) + drift * t,
|
||||
dp=2.46 + 0.04 * np.sin(4 * np.pi * t / 3.3),
|
||||
mass=1.0,
|
||||
flux_in=1.0,
|
||||
flux_out=1.0,
|
||||
)
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def test_periodic_synthetic():
|
||||
a = p.assess(rows())
|
||||
assert a["periodic"]
|
||||
assert len(a["cycles"]) > 5
|
||||
assert a["cycles"][-1]["st"] == pytest.approx(1 / 3.3, rel=1e-5)
|
||||
assert not p.assess(rows(amplitude=0.01))["periodic"]
|
||||
assert not p.assess(rows(drift=0.01))["periodic"]
|
||||
assert not p.assess(rows()[:100])["periodic"]
|
||||
|
||||
|
||||
def test_production_unmodified():
|
||||
m = json.loads(
|
||||
(ROOT / "tests/validation/data/dfg_re100/source_manifest.json").read_text()
|
||||
)
|
||||
assert m["commit"] == h.PIN
|
||||
for name, digest in m["production_sha256"].items():
|
||||
assert hashlib.sha256((ROOT / name).read_bytes()).hexdigest() == digest, name
|
||||
|
||||
|
||||
def test_reference():
|
||||
r = json.loads(
|
||||
(ROOT / "tests/validation/data/dfg_re100/reference_cycle.json").read_text()
|
||||
)
|
||||
assert 0.301 < r["st"] < 0.303
|
||||
assert 3.22 < r["cd_max"] < 3.23
|
||||
assert 2.5 < r["dp_max"] < 2.51
|
||||
assert p.compare(r, r)["reference_agreement"]
|
||||
|
||||
|
||||
def test_stale_cycles_rejected():
|
||||
r = rows()
|
||||
level = next(x["cl"] for x in r if x["tstar"] >= 284.5)
|
||||
for x in r:
|
||||
if x["tstar"] >= 284.5:
|
||||
x["cl"] = level
|
||||
assert not p.assess(r)["periodic"]
|
||||
|
||||
|
||||
def test_min_time_is_used(monkeypatch):
|
||||
monkeypatch.setitem(p.GATES, "min_tstar", 290.0)
|
||||
assert not p.assess(rows())["periodic"]
|
||||
|
||||
|
||||
def test_nonconsecutive_cycles_rejected(monkeypatch):
|
||||
cycles = p.extract(rows())
|
||||
bad = cycles[-6:]
|
||||
bad.pop(2)
|
||||
monkeypatch.setattr(p, "extract", lambda *a, **k: bad)
|
||||
assert not p.assess(rows())["periodic"]
|
||||
Reference in new issue
Block a user