test: integrate pinned Re100 and jet control-volume validation

This commit is contained in:
2026-10-17 23:38:53 -07:00
parent d79e3f6f0a
commit 69e9b78de9
12 changed files with 2418 additions and 0 deletions
+7
View File
@@ -663,6 +663,13 @@ for step in range(steps):
streak.render("streakline.png")
```
## Validation campaigns
- [DFG 2D-2 Re100](docs/validation/dfg_re100.md): frozen periodicity and official-reference comparisons, with a declared noncanonical outlet.
- [Jet-circle control volume](docs/validation/jet_circle_cv.md): bounded AMR versus uniform-grid conservation diagnostic.
The runners are in `tests/validation`, their CPU tests in `tests/unit`, and compact reference/source guards in `tests/validation/data`. These campaigns preserve pinned production code; successful CPU checks alone do not establish GPU or physical validation. Keep generated results outside tracked files.
## Citation
```bibtex
+52
View File
@@ -0,0 +1,52 @@
# DFG 2D-2 Re100 GPU campaign (validation-only)
## Scope and locked definitions
Unchanged d79e3f6 production sources. Uniform D2Q9/MRT, shifted FP32, double buffer, no LES, stationary cylinder and no-slip walls. This is not an AMR, jet, or full canonical-outlet certification.
Physical geometry: [0,2.2] × [0,0.41], center (0.2,0.2), radius .05, rho=1, nu=.001, inlet mean=1 and peak=1.5. D is lattice diameter; dx=.1/D, dt=U_lattice*dx, nu_lattice=U_lattice*D/100. U_lattice=.01 gives peak inlet Mach .02598. Existing local Zou–He inlet is selected in validation config only. Existing pressure-NEQ outlet is unclamped: **it does not impose the canonical vector do-nothing condition nu*d_n(u)-p*n=0**. The existing production channel-flow initial condition is used, not zero velocity. Periodic eligibility is delayed to t*=250 (25 physical seconds), with fixed end t*=300 (30 seconds).
Official source: https://wwwold.mathematik.tu-dortmund.de/~featflow/en/benchmarks/cfdbenchmarking/flow/dfg_benchmark2_re100.html
The 20141124 official newer reference archive has higher-order line-integration force data and supersedes the older example table for this comparison. The compact tests/validation/data/dfg_re100/reference_cycle.json preserves the last complete lift-minimum cycle from lv6/dt4. Reference Cd=[3.164266,3.227392], Cl=[-1.021286,.986571], pressure difference=[2.420371,2.507013], St=.301841. These are derived sampled extrema, not exact analytical values. Official mean=(min+max)/2 and amplitude=max-min; separate time averages are also recorded.
## Exact commands and startup ownership
Use the existing CUDA/PyCUDA environment; no package installation or production edits required. From the repository root, use a new task-owned output path:
export PYTHONPATH="$PWD/src${PYTHONPATH:+:$PYTHONPATH}"
python -m pytest -q tests/unit/test_re100.py
CUDA_VISIBLE_DEVICES=0 python tests/validation/run_dfg_re100.py --diameter 20 --speed .01 --mode pilot --output output/validation/pilot_d20_u001 > pilot.log 2>&1
Pilot is t*=5 (10,000 steps), with dense .02-t* sampling to calibrate diagnostic cost. It cannot pass periodicity or external benchmark validation. Root launch owner reviews health, memory, timing and stdout before full runs; the full pair is not launched automatically.
CUDA_VISIBLE_DEVICES=0 python tests/validation/run_dfg_re100.py --diameter 40 --speed .01 --mode full --output output/validation/full_d40_u001 > d40.log 2>&1
CUDA_VISIBLE_DEVICES=1 python tests/validation/run_dfg_re100.py --diameter 80 --speed .01 --mode full --output output/validation/full_d80_u001 > d80.log 2>&1
These are separate invocations, not a parallel shell script. A single root launch owner serializes startup/compilation across all Horsea work. Do not launch the next process until the previous log contains an explicit `READY {...}` line. READY comes after the first physical sample, including Simulation initialize, sim.run, read_force and get_macroscopic, so lazy runtime/diagnostic paths are exercised. That sample remains in the physical trajectory/history. No hidden warmup/reset, no later new diagnostic kernel. Do not treat a process merely existing as READY. Keep separate output directories and retain exit codes/logs. GPU assignment is chosen by root after checking existing workloads; shown indices are examples of physical device selection.
## Measurement, frozen criteria and outcomes
Force uses advancing link-momentum exchange, accumulated in batches <=100 steps, combined in FP64; Cd/Cl=2F/(U_lattice² D). Force timestamp is the batch midpoint. Pressure uses fluid-only h/2h extrapolation to cylinder front/back points and physical conversion 1/(3 U_lattice²), at each sample endpoint. For phase comparisons endpoint pressure is interpolated to the same cycle phase. Both raw times are saved.
Before t*=240 sample width=.5; afterwards .02 (about 166 samples per lift cycle). Pilot uses dense sampling throughout. Extract complete lift-minimum-to-minimum cycles using quadratic local minimum locations, accepting periods 2–5 t*. Latest five complete cycles must be consecutive with the last ending no more than 5 t* before the final observation, and must start at or after t*=250. Eligibility requires lift amplitude >=.2, period range/mean <=.005, Cd/Cl/pressure min and max ranges <=.005 absolute, cycle-mean mass range/mean <=1e-4, and each cycle's mean mass-flux imbalance <=.005. Instantaneous flux equality is not required in this unsteady compressible LBM diagnostic.
Reference gates: Cd extrema relative errors <=2%, Cl extrema absolute errors <=.03, pressure extrema relative errors <=2%, St relative error <=2%. These are predeclared engineering tolerances, not official uncertainty intervals. Phase-aligned waveform RMS errors, extrema, midpoint means, time means, amplitudes, outlet residual, sampled density bounds and Mach are all reported. Waveform RMS is diagnostic (no retroactive gate). No threshold changes after GPU results. At cap, failed periodicity stays incomplete. Invalid/nonfinite state, rho outside [.5,1.5], or sampled max Mach>.2 causes a safety stop; this is a gross-health guard, not a Mach-independence claim.
A case_reference_pass requires both periodicity and reference gates. Every raw case keeps suite_validated=false. The two-grid comparison must report each case and errors; matched-Mach D40/D80 is evidence of resolution sensitivity only, not an asymptotic convergence order. If either fails or finer errors increase materially, report the failure instead of adjusting thresholds. No full canonical/AMR/Mach-independent validation claim is allowed.
## Resource and calibration plan
D20: 441×84=37,044 nodes, pilot 10,000 steps. D40: 881×166=146,246 nodes, 1,200,000 full steps. D80: 1761×330=581,130 nodes, 2,400,000 full steps. Main double-buffer DDF arrays alone cost 72 bytes/node: 2.7/10.5/41.8 MB decimal. Flags, masks, macro host arrays, body buffers and CUDA context/JIT memory add overhead; root verifies actual free GPU/host memory and disk before launch. Conservative per-process reservation: 1 GiB VRAM and 2 GiB host RAM, excluding shared/compiler overhead; measurements take precedence.
Historical Re20 D80 U=.01 step throughput was ~6082 MLUPS (45.9 seconds/480k steps). At equal throughput D80 Re100 full advancing steps would take ~229 seconds, excluding dense full-field downloads/reduction and compile overhead. D40 estimate must be calibrated, not assumed proportional. Pilot records startup_seconds, step_seconds, measurement_seconds, mlups. For each full grid, calibrate a same-grid pilot if D20 cannot predict cost adequately. Full has about 3480 records (480 coarse+3000 dense); use measured seconds/step × full steps plus seconds/record ×3480 plus startup. D20-to-D80 extrapolation is only a rough planning upper/lower guide, not an ETA commitment.
Only geometry.npz, one final_field.npz, history.csv, summary.json and stdout/stderr are persisted. No per-step or repeated full-field snapshots. D80 final float64 rho/ux/uy plus mask are <15 MB uncompressed; history/summary several MB, compact versioned reference/source guards <50 KiB. Budget <100 MB per case and <500 MB whole campaign including logs and optional externally retained archives, safely below 2 GiB; root still checks baseline Horsea usage. Retain sparse evidence only.
## CPU validation and remaining execution
Ten tests cover geometry/units/config, pressure scaling, periodic and nonperiodic synthetic histories, immutable production hashes, and self-reference comparison. CUDA execution is not available in the preparation environment and has not been claimed. Run pilot first on Horsea; do not confuse successful CPU tests with GPU readiness.
## Reference provenance and reproduction
Only the compact derived cycle is versioned. Its source URL, official ZIP SHA-256, column selection, and cycle-selection description are embedded in reference_cycle.json. Raw archives and GPU results are not repository inputs. To reproduce it, obtain the exact archive identified there, verify its SHA-256, and extract logs/results_cc2d_bench2_referencenew/bdforces_q2_lv6_dt4 and pointvalues_q2_lv6_dt4. Use physical time column 2 multiplied by 10, drag/lift columns 4/5, and pressure columns 7 minus 12 (one-based columns). Build rows with mass=flux_in=flux_out=1, then call periodic.extract(rows, min_time=0) and select the last complete cycle. The source guard is tests/validation/data/dfg_re100/source_manifest.json; it deliberately verifies unchanged d79 production content rather than the validation branch HEAD.
+58
View File
@@ -0,0 +1,58 @@
# Jet-circle closed-control-volume pilot
Status: CPU preparation only. No GPU run or production change. Pinned unchanged production source: d79e3f6f0a424f5bcc3a1a6d610313e49daf97a2. tests/validation/data/jet_circle_cv/source_manifest.json checks all tracked source file hashes before importing GPU code.
## Execute only after root-owned GPU serialization
From the repository root, with REPO set to the repository root and an absent output directory:
```sh
PYTHONPATH=src python -m pytest -q tests/unit/test_jet_circle_cv.py
python tests/validation/run_jet_circle_cv.py --repo "$REPO" --output "$OUTPUT" --plan
CUDA_VISIBLE_DEVICES="$VERIFIED_IDLE_GPU" python tests/validation/run_jet_circle_cv.py --repo "$REPO" --output "$OUTPUT"
```
The runner does not select, reserve, or contact a GPU machine. It requires one explicit CUDA_VISIBLE_DEVICES value, at least 1 GiB free, and refuses an existing output directory. It imports existing PyCUDA; no installation. Default pilot: 800 base steps, 8 output windows, at most 1800 seconds after READY. READY is printed only after both steppers compile, execute one base/two fine warmup hops, exercise download/raw paths, and reset initial state/time. A pre-READY silence may be compilation, not stepping progress. Root owns any overall startup timeout and all launch serialization.
## Physical/numerical case
Periodic root box 192 by 128, center (96,64) in root cell-edge coordinates, D=24.084, U0=.03, nu=.0072252, Re_D=100, beta=Vj/U0=.1, rho0=1. Unforced D2Q9 SRT FP32, no DDF shifting, no LES. The slightly noninteger diameter was selected solely to avoid q clipping at all three planned scales. Circle is stationary. Two uniform radial sectors at pi/2 and 3pi/2, half-width pi/12: top blows outward +.003, bottom sucks inward -.003; remaining wall has zero velocity. This periodic transient is NOT DFG, infinite-stream drag, or statistical high-Re validation. End time is t*=t U0/D approximately .9965.
AMR bounds (48,32,144,96). Finest dx=dt=.5 in root lattice units, nu_fine=2 nu, finest D=48.168. Full-domain fine reference is 384 by 256 with exactly the same finest spacing, timestep, viscosity, collision and storage. Root tau=.5216756, fine tau=.5433512, Mach approximately .05196.
AMR uses actual public FixedPatchStepper Jet construction and raw observation. The uniform blocked reference reuses the existing test_raw_exchange_gpu.py optional-input validation adapter: production Jet-derived velocity/force-arm arrays feed the unchanged blocked curved kernel. Its normal public PeriodicBlockStepper API is passive-only. This does not claim there is no uniform Jet route anywhere: Simulation.add_jet exists, but the same-blocked-kernel adapter removes a backend/storage confound for this comparison. No new public backend is introduced. Runtime asserts matching directions, q, fallback and true hit coordinates after frame conversion; both sectors must resolve links. Unclamped q/no fallback is required.
## Independent measurements and signs
Two closed rectangles: (72,44,120,84) and (64,40,128,88), wholly within the finest patch. Every contour face has two active-fluid stencil layers. Capture physical postcollision populations at t=0 and every completed base interval. Collision conserves density/momentum; these are valid endpoint moments, NOT precollision stress estimates.
For each contour, M and P sum fluid-cell rho and rho*u times h^2, excluding the staircase solid. Face midpoint quadrature integrates rho*u dot n and [rho*u*u+p*I-mu*(grad u+grad u.T)] dot n. Velocity gradients use centered differences in root-length units, mu=rho*nu, p=(rho-1)/3. Constant pressure subtraction integrates to zero on a closed rectangle. No raw exchange enters these field quantities.
Trapezoidal time integration samples every base step over the SAME reported [step_start,step_end] as cumulative raw impulses/masses. For each window:
- mass into fluid through body surface = delta M + integral outer mass flux
- raw boundary reaction target = -delta P - integral outer momentum-minus-traction flux
Raw rows are [Jx,Jy,angular impulse,mass into fluid]; each uniform fine hop scales by [.25,.25,.125,.25], exactly as the AMR interval ledger. Both fine hops are accumulated. AMR observation time endpoints and the public raw reduction are checked. Reports separate total, wall, aperture, blowing and suction raw rows. Coefficients divide force by .5*rho0*U0^2*D. These are momentum reactions including aperture transport; neither raw aperture nor total raw reaction is labelled solid-only hydrodynamic traction.
Independent aperture mass estimates integrate measured rho*u dot e_r over true arcs using 64-point Gauss quadrature and nearest active-fluid samples. No target velocity or raw row enters this estimate. Reports include maximum sampling distance, actual signed raw mass rates and nominal signed volume flux ±2*(pi/12)*R*Vj. Near-wall nearest-node sampling is only a first-order diagnostic, not certified wall extrapolation. Nominal volume flux is not asserted to equal measured mass flow at finite density variation.
Inventory near the circle is a staircase-cell approximation; its geometric error and face/time quadrature errors can appear in CV residuals. Two-contour spread and grid refinement are essential. No exact continuum force value, solid traction recovery, jet hardware load, or physics acceptance is claimed. Output status is deliberately PILOT_DIAGNOSTIC_ONLY even on successful execution.
## Scope, refinement, resources
Default one pilot only. Estimated device envelope 128 MiB, required free 1 GiB; bounded JSON only, conservatively under 2 MiB, no population snapshots or per-step logs. Every base step downloads both fields for independent quadrature, so PCIe/CPU diagnostic cost is intentional. Six CPU tests cover descriptor/source identity, uniform flux cancellation, affine convective divergence, quadratic viscous sign/coefficient, matched geometry/Jet selection and diffusive scaling.
Optional --scale 2 or 4 doubles/quadruples all lengths and divides U,V by that scale, holds lattice nu fixed, and scales steps/windows by scale^2: physical Re and t* match, Mach decreases. Source and geometry guards repeat. Device estimate scales as scale^2; memory gate requires twice the estimate or 1 GiB, whichever is greater. Scale 4 is not part of default authorization/readiness. --re 400 requires --allow-re400 and a separate reviewed root launch decision after Re100 evidence; no automatic promotion.
Compare AMR/reference velocity error, signed reaction coefficients, each contour's independently measured reaction and residual, contour spread, signed mass budgets, and measured aperture flux. Freeze numerical tolerances only after agreeing a scientifically appropriate convergence criterion; this preparation imposes only structural/finite-state guards. A completed pilot cannot establish convergence.
Follow-on design retained: porous Couette crossflow with u=U*expm1(Re_V*y/H)/expm1(Re_V), v=V and unequal exact wall shear. It requires validation-only planar geometry and is intentionally not implemented here.
### Amplitude, geometry, and high-Re limits
Both Jet amplitudes are constant throughout the run and every fine substep; geometry is fixed. There is no runtime switch, per-step forcing schedule, or moving-wall velocity in this campaign. Construction/packing happens once, with identical uploaded amplitudes in the uniform adapter. This avoids any substep timing ambiguity from amplitude changes.
Every case records actual tau_root and tau_fine. For this fixed s=1 geometry and speed, Re400 would give nu=.0018063, tau_root=.5054189 and tau_fine=.5108378, close to the SRT limit .5. The implemented Re choices are a bounded campaign menu, not a universal physical Reynolds-number ceiling. Higher-Re evidence may require a larger initial resolved geometry or a different justified grid/relaxation design before launch. Diffusive refinement at constant lattice nu improves spatial/Mach resolution but does not move tau farther from .5; simply selecting scale=2 does not cure a poor tau. No all-Re, LES or three-dimensional realism claim follows from this pilot.
The source guard checks production file content against d79, not validation-branch HEAD. This runner is validation-only; generated results and frozen execution-package archives are not committed.
+139
View File
@@ -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()
+103
View File
@@ -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"]
@@ -0,0 +1,818 @@
{
"start": 96.60907453162486,
"end": 99.92207699254307,
"period": 3.313002460918213,
"st": 0.301841007302737,
"cd_min": 3.164265915836695,
"cd_max": 3.2273924509469625,
"cd_midrange": 3.1958291833918286,
"cd_amplitude": 0.06312653511026767,
"cd_time_mean": 3.1958873825129652,
"cd_waveform": [
3.2192714249444543,
3.2202803099943376,
3.2212299416471293,
3.222118601683268,
3.2229440924252426,
3.2237043728228487,
3.224397563502949,
3.225021951759953,
3.2255759960759045,
3.226058329556411,
3.2264677639287034,
3.226803291773589,
3.2270640893003435,
3.227249518358305,
3.227359127231524,
3.2273924509469625,
3.2273485621938347,
3.22722863562996,
3.227032979281687,
3.2267620869419815,
3.2264166368706584,
3.2259974896059336,
3.225505684975984,
3.2249424394952255,
3.224309142817072,
3.2236073535652885,
3.222838795285557,
3.222005351549716,
3.2211090610907003,
3.220152110736951,
3.21913591342886,
3.2180639425025355,
3.2169388049876204,
3.2157632320581224,
3.2145400725256823,
3.213272285099224,
3.2119629314217737,
3.210615168117056,
3.209232239485073,
3.2078174691606125,
3.2063742523121657,
3.204906047188526,
3.203416367201774,
3.201908772307293,
3.20038671156229,
3.1988540415270714,
3.197314448828512,
3.1957715956753288,
3.194229147910405,
3.1926907667041973,
3.19116010031245,
3.1896407755313154,
3.188136389507111,
3.1866505018003437,
3.18518662592634,
3.1837482219382216,
3.1823386881615674,
3.1809613539268415,
3.1796199070610616,
3.178317365693067,
3.1770566177141566,
3.1758406383588778,
3.1746722981220814,
3.1735543560753845,
3.1724894540381894,
3.171480110448416,
3.1705287147383436,
3.169637521817997,
3.168808647409722,
3.168044062789466,
3.16734559035941,
3.166714899466848,
3.1661541138486857,
3.165664557595598,
3.1652468672004876,
3.1649020564911114,
3.164630969966524,
3.164434280442736,
3.1643124874918267,
3.164265915836695,
3.164294714155108,
3.164398854572734,
3.164578132207916,
3.164832165115698,
3.1651603948311298,
3.165562087003554,
3.166036763487302,
3.166583632347454,
3.1672006737836313,
3.1678864585242907,
3.1686393915089255,
3.1694577152711365,
3.17033951339357,
3.171282714587482,
3.1722850969178604,
3.1733442928081175,
3.17445779387909,
3.175622956745662,
3.176837008657004,
3.178097054026256,
3.1794002164290207,
3.1807436978583974,
3.182123709232136,
3.1835369281843153,
3.1849799460334114,
3.1864492760653347,
3.1879413618349814,
3.1894525859099345,
3.19097927879319,
3.1925177278192263,
3.1940641867753663,
3.195614884812199,
3.197166036575152,
3.198713851202913,
3.2002545337987547,
3.2017840862384728,
3.2032988764817527,
3.2047951919540822,
3.2062693641104913,
3.207717777711432,
3.2091368805426974,
3.2105231925781985,
3.2118733148571565,
3.2131839390335397,
3.2144518552407257,
3.2156739612615417,
3.216847269859638,
3.217968917317378,
3.219036170327386,
3.220045562724451,
3.220995334928343,
3.2218832986850447,
3.2227073170066918,
3.2234654153623845,
3.224155785778181,
3.2247767917974066,
3.2253269721252744,
3.2258050437055585,
3.2262099052701587,
3.2265406390922298,
3.226796513255351,
3.2269769825521033,
3.227081689768787,
3.22710955220279,
3.227061168669443,
3.2269370924239436,
3.2267377237295465,
3.2264636466881744,
3.2261156276975886,
3.2256946121982577,
3.2252017217835,
3.224638250833764,
3.2240056622744246,
3.223305583413101,
3.222539801329067,
3.2217102575995633,
3.2208190428742154,
3.219867854353387,
3.218859259026148,
3.2177961858992568,
3.2166812675225214,
3.2155172535532928,
3.2143070040629325,
3.2130534817570826,
3.211759744984257,
3.210428939916766,
3.2090642928856465,
3.207669102594001,
3.2062467320151002,
3.2048006003874154,
3.2033341751083237,
3.2018508739555798,
3.2003542779095873,
3.1988481127850616,
3.1973359573002664,
3.1958213973977445,
3.194308017670146,
3.19279939346063,
3.1912990827323733,
3.189810618404239,
3.188337500433207,
3.1868831881313007,
3.185451092560893,
3.1840445691293837,
3.1826669104651053,
3.1813214859380303,
3.1800117901097846,
3.178740483661364,
3.177510533708781,
3.1763248101985377,
3.1751860802710605,
3.1740970016898973,
3.173060117741568,
3.172077851413086,
3.171152500485684,
3.1702862322665037,
3.169481079259431,
3.1687389344676062,
3.1680615476209555,
3.1674506253282964,
3.1669085513260424,
3.1664356220663086,
3.1660329701689696,
3.165701568133256,
3.165442226438076,
3.165255590934966,
3.16514214162142,
3.1651021912290123,
3.1651358837399672,
3.1652431944171915,
3.165423928768434,
3.165677722868927,
3.166004043509568,
3.1664021891744154,
3.1668723687065503,
3.16741250732336,
3.168021321234566,
3.1686974411192694,
3.1694393380619967,
3.170245326209458,
3.171113566351732,
3.172042068956519,
3.173028698759894,
3.1740711784468334,
3.1751670940119223,
3.176313898883396,
3.177508920042188,
3.17874936360477,
3.1800328259079818,
3.181355841861699,
3.1827151303301267,
3.1841074729782854,
3.1855295642750754,
3.186978019262098,
3.1884493811374868,
3.1899401298427543,
3.191446689954596,
3.192965439405816,
3.1944927184101823,
3.1960248382013705,
3.1975580901006118,
3.199088754756803,
3.200613009960425,
3.20212708603014,
3.2036273295775386,
3.205110074007536,
3.2065716893821072,
3.208008592293136,
3.209417254433106,
3.210794212466389,
3.212136076499309,
3.213439539354214,
3.214701385159765,
3.215918497504247,
3.2170878683639637,
3.2182066051468428,
3.2192715160336944
],
"cl_min": -1.021285623433455,
"cl_max": 0.9865706078511706,
"cl_midrange": -0.017357507791142257,
"cl_amplitude": 2.0078562312846255,
"cl_time_mean": -0.018055778744564696,
"cl_waveform": [
-1.0212846006341447,
-1.0209838590222233,
-1.0200727383007768,
-1.0185590327265965,
-1.0164441167317841,
-1.01372972461554,
-1.010417946688192,
-1.0065112246383783,
-1.0020123475697247,
-0.9969244470282416,
-0.9912509924392147,
-0.984995786253974,
-0.9781629589771562,
-0.9707569640900402,
-0.9627825730042762,
-0.9542433948273606,
-0.9451386764809715,
-0.9354819238811002,
-0.925279142068636,
-0.9145366279059466,
-0.9032609650239106,
-0.8914590187491076,
-0.8791379311317419,
-0.8663051160407256,
-0.8529682543310683,
-0.8391352890960927,
-0.8248144210074706,
-0.8100141037255332,
-0.7947430394336983,
-0.7790101644090639,
-0.7628173731216689,
-0.7461816969118068,
-0.7291127993693902,
-0.7116205711659391,
-0.6937151260047051,
-0.6754067966482753,
-0.6567061309580705,
-0.637623888008147,
-0.6181710341714578,
-0.5983587392102484,
-0.5781983723659854,
-0.5577014984612264,
-0.5368798738456273,
-0.5157454424421091,
-0.4943063234695201,
-0.4725784485709182,
-0.4505750883115271,
-0.4283089024165666,
-0.405792716370432,
-0.38303951669758823,
-0.36006244613658056,
-0.3368747985998418,
-0.3134900140010978,
-0.28992167285852,
-0.2661834907138685,
-0.24228931233131965,
-0.21825310567974987,
-0.19408895564887746,
-0.16980993582635662,
-0.14543133426087812,
-0.12096814315114233,
-0.09643485175457375,
-0.0718460310133605,
-0.04721632579948139,
-0.022560446976959612,
0.0021068368194982773,
0.02677070764406316,
0.05141630692334945,
0.07602874438036183,
0.10059310719281325,
0.1250944693656024,
0.1495179012836364,
0.17384754771042515,
0.19806822588832526,
0.22216578848678734,
0.2461253934945549,
0.2699322466441472,
0.2935716120906063,
0.3170288231832102,
0.34028929328125385,
0.36333852677889195,
0.3861621301373943,
0.40874582307035934,
0.43107544954464694,
0.4531369891575652,
0.47491656822324385,
0.4963986071823978,
0.5175678949127962,
0.5384141735804333,
0.5589242775445442,
0.5790852440767277,
0.5988843242125788,
0.6183089934932646,
0.6373469625245218,
0.6559861873413382,
0.674214879706298,
0.6920215170336557,
0.7093948521976959,
0.7263239230232448,
0.742798061573706,
0.7588052844335239,
0.7743307471351503,
0.7893708273667098,
0.803916135973999,
0.8179576199407215,
0.8314865695778171,
0.8444946252982337,
0.8569737840555796,
0.8689164052636061,
0.8803152163390694,
0.8911633177499939,
0.9014541876864335,
0.9111816861332408,
0.9203400586319765,
0.9289234860093117,
0.936918266184497,
0.9443288660701289,
0.9511511188545412,
0.9573812545471582,
0.9630159006719111,
0.9680520825648866,
0.972487223197885,
0.9763191426551175,
0.9795460569396854,
0.9821665765670635,
0.9841797046166384,
0.9855848344669915,
0.986381747045911,
0.9865706078511706,
0.9861430751758404,
0.9851078843149434,
0.9834674674373464,
0.9812235079135473,
0.978378053362095,
0.974933510845897,
0.9708926418403944,
0.9662585570602749,
0.9610347110820011,
0.9552248968839854,
0.9488332402249186,
0.941864193965974,
0.9343225322984421,
0.9262133449652685,
0.9175353367316478,
0.9082985884427632,
0.8985116225127334,
0.888180745849068,
0.8773125515396398,
0.8659139131624679,
0.8539919791562397,
0.841554167318266,
0.8286081593376501,
0.8151618954310706,
0.8012235691354387,
0.7868016221216644,
0.7719047392049175,
0.7565418433711996,
0.7407178806800201,
0.7244435174512682,
0.707731607286953,
0.6905919887305398,
0.6730347151255153,
0.6550700503410322,
0.6367084643889164,
0.617960629252382,
0.5988374147135249,
0.5793498841758384,
0.5595092905790806,
0.5393270722633181,
0.5188148488816408,
0.4979844172354143,
0.4768457513845936,
0.45541040875158884,
0.43369366814580723,
0.41170800001396685,
0.38946603027749815,
0.3669805357724072,
0.3442644395643103,
0.3213308061849796,
0.2981928366787376,
0.2748638635506233,
0.2513573454944852,
0.22768686201881655,
0.20386610779915632,
0.17990888695762292,
0.15582865541317795,
0.13163881464575702,
0.10735496845190624,
0.08299130047733835,
0.05856207148298293,
0.034081612308419704,
0.009564316642990896,
-0.014975366427826247,
-0.039522940080061256,
-0.06406386762101547,
-0.08858358056301793,
-0.11306748690571801,
-0.1375009796129617,
-0.16186944527282868,
-0.186158127286089,
-0.21035090571304071,
-0.23443440343427951,
-0.2583940736125971,
-0.28221540953832974,
-0.30588395428926807,
-0.3293853105136673,
-0.35270515033803884,
-0.37582922531739726,
-0.39874337658360604,
-0.4214335449640186,
-0.44388578123531364,
-0.4660862563718654,
-0.4880212718764244,
-0.509677270114379,
-0.531036136112504,
-0.5520885997128909,
-0.5728219120147833,
-0.5932231908464959,
-0.6132797468099019,
-0.6329790933854313,
-0.652308956917678,
-0.6712572864574565,
-0.6898122635200815,
-0.7079623116377989,
-0.7256961057871241,
-0.7430025816000796,
-0.7598709443367393,
-0.7762906777176882,
-0.7922455571624138,
-0.8077296357950401,
-0.8227349965022713,
-0.8372523342423263,
-0.8512726627071948,
-0.8647873213967461,
-0.8777879823088283,
-0.8902666562070795,
-0.902215698680155,
-0.9136278156586564,
-0.9244960686140175,
-0.9348138793173668,
-0.9445750342635408,
-0.9537736885083689,
-0.9623985419578303,
-0.9704465786953729,
-0.977916689291491,
-0.9848045517658248,
-0.99110622613045,
-0.9968181555444723,
-1.0019371672305317,
-1.0064604728414646,
-1.0103856687137327,
-1.0137107350135544,
-1.0164340351998324,
-1.01855431505123,
-1.0200707006187215,
-1.0209826964966897,
-1.021285623433455
],
"dp_min": 2.4203712005726827,
"dp_max": 2.507012902468643,
"dp_midrange": 2.463692051520663,
"dp_amplitude": 0.08664170189596021,
"dp_time_mean": 2.4639514695178963,
"dp_waveform": [
2.485558537878651,
2.487354668330821,
2.489094038859844,
2.4907730268211923,
2.4923875927671357,
2.493933872161179,
2.4954081841697953,
2.4968070398569093,
2.498127149841111,
2.4993654310604727,
2.5005190130830766,
2.5015852434313093,
2.502561692495173,
2.50344615769302,
2.5042366667973424,
2.504931223854115,
2.5055272123906085,
2.5060247031878675,
2.5064226784653965,
2.5067203585292273,
2.506917201491131,
2.507012902468643,
2.507007392300157,
2.5069008355614204,
2.506693628251957,
2.5063863949686396,
2.5059799860275485,
2.505475473290949,
2.504874146643661,
2.5041775076417783,
2.503385760083982,
2.5025023981520067,
2.501529545753141,
2.500469519417466,
2.4993248229208986,
2.49809814052029,
2.4967923311909144,
2.495410422113925,
2.493955601552369,
2.492431212574658,
2.490840745393346,
2.489187830493204,
2.4874762310352,
2.4857098352761477,
2.483891999686845,
2.4820274911095415,
2.480120662304518,
2.4781758269143728,
2.476197383057849,
2.474189804509867,
2.47215763257422,
2.47010546680718,
2.4680379564006794,
2.465959791179922,
2.4638756921667406,
2.46179040254991,
2.4597086782017046,
2.457635278124787,
2.455575155334104,
2.4535330651884157,
2.451513647441801,
2.4495215742730494,
2.447561471628255,
2.4456379085376456,
2.443755387861672,
2.4419183356738783,
2.4401310914614514,
2.438397897721916,
2.436722890392385,
2.4351100884293455,
2.4335633840618365,
2.432086533067437,
2.4306838103185973,
2.4293586878640068,
2.428113869269998,
2.4269524606913686,
2.4258773845443202,
2.424891371376568,
2.423996951340259,
2.4231964462177444,
2.4224919620804686,
2.4218853823843163,
2.421378360808599,
2.4209723155762863,
2.420668423809868,
2.4204676161372016,
2.4203712005726827,
2.420380068704988,
2.420493289903554,
2.420710759315692,
2.4210321100509016,
2.421456711924972,
2.4219836715842935,
2.4226118326498916,
2.423339777335187,
2.424165828339519,
2.4250880518614637,
2.4261042616582924,
2.4272120230151923,
2.4284086587121188,
2.4296915368281606,
2.4310582510675482,
2.432504302921731,
2.4340261048245524,
2.435619866098322,
2.4372816029434006,
2.4390071492364305,
2.440792167926676,
2.442632162995385,
2.444522492162555,
2.4464583798729533,
2.448434930859836,
2.450447144296273,
2.4524899276311465,
2.4545581275894874,
2.4566467249172272,
2.4587500444816213,
2.460862775988026,
2.462979593476627,
2.4650951703383326,
2.4672041949249874,
2.469301385189656,
2.4713815036357163,
2.4734393717724394,
2.475469884333746,
2.477468023387656,
2.4794288713654966,
2.4813476246082624,
2.483219605457596,
2.485039458918412,
2.4868033804993956,
2.4885072430697144,
2.4901469959998734,
2.4917187690881257,
2.4932188815730543,
2.4946438498431105,
2.49599039508062,
2.4972554500082174,
2.498436164504503,
2.4995299110592493,
2.5005342891380167,
2.501447129364351,
2.502266495991754,
2.5029895274516014,
2.5036154604052916,
2.5041435094498383,
2.5045727041834747,
2.504902313989728,
2.5051318472519433,
2.505261050325141,
2.5052899056087403,
2.5052186294050998,
2.5050476691328045,
2.5047776999609392,
2.504409621696276,
2.5039445544003622,
2.503383834013878,
2.5027281389852822,
2.5019794867872425,
2.501140507540692,
2.500213360279151,
2.4992003931281483,
2.498104137409639,
2.4969273011438373,
2.495672762685901,
2.494343563852958,
2.492942903234649,
2.491474128676318,
2.4899407305330525,
2.488346334061092,
2.486694691617208,
2.484989340356667,
2.4832342121680133,
2.4814339161814924,
2.479592639951934,
2.4777146583088454,
2.4758043245876524,
2.473866062448869,
2.471904357327426,
2.4699237475913485,
2.4679288158413883,
2.46592417988613,
2.4639144839871556,
2.461904389322368,
2.459898565346157,
2.4579017367770137,
2.455918764942065,
2.453954173148493,
2.4520125671214457,
2.4500985125535784,
2.448216525842054,
2.4463710641219887,
2.444566515822548,
2.4428071908357323,
2.4410973107465668,
2.439440999504861,
2.4378422733498937,
2.4363050317847414,
2.434833047645694,
2.43343006971473,
2.432100619437783,
2.430846995094142,
2.429672363555455,
2.428579719706815,
2.427571878080977,
2.426651464455842,
2.4258209081907864,
2.425082434921927,
2.4244380590290993,
2.423889577520045,
2.4234385632168207,
2.423086359460975,
2.4228340742719734,
2.4226825762746023,
2.4226340587576405,
2.4226874437485133,
2.422842736063271,
2.4230997975159894,
2.4234582347462745,
2.4239173992440146,
2.424476386499562,
2.4251340367353267,
2.425888936234961,
2.4267394193027876,
2.427683571153293,
2.428719231244257,
2.429843998149402,
2.431055234079425,
2.432351101512926,
2.4337276833933763,
2.4351813534956155,
2.43670858740822,
2.4383056624813797,
2.4399686671928897,
2.4416935114716893,
2.4434759376701103,
2.4453115320216026,
2.447195736733967,
2.4491238624275873,
2.451091101018784,
2.4530925394872574,
2.4551231733170757,
2.4571781139809588,
2.4592520095527344,
2.4613395134290164,
2.4634353978388575,
2.46553441673564,
2.467631320555607,
2.469720870921841,
2.4717978552593056,
2.4738571012373916,
2.4758934909645545,
2.477901974977891,
2.479877585675147,
2.481815450926397,
2.4837108064957207,
2.485558618881201
],
"mass_min": 1.0,
"mass_max": 1.0,
"mass_midrange": 1.0,
"mass_amplitude": 0.0,
"mass_time_mean": 1.0,
"flux_in_min": 1.0,
"flux_in_max": 1.0,
"flux_in_midrange": 1.0,
"flux_in_amplitude": 0.0,
"flux_in_time_mean": 1.0,
"flux_out_min": 1.0,
"flux_out_max": 1.0,
"flux_out_midrange": 1.0,
"flux_out_amplitude": 0.0,
"flux_out_time_mean": 1.0,
"flux_relative": 0.0,
"source": "https://wwwold.mathematik.tu-dortmund.de/~featflow/media/dfg_bench2new_2d/20141124_dfg_bench2_official.zip",
"archive_sha256": "08db59605b2f60efc512d2bbc79628ea5d1cc720c0986938a16920884a0215be",
"selection": "lv6 dt4 last complete lift-minimum-to-minimum cycle; force columns 2,4,5; pressure columns 2,7-12; physical time multiplied by 10",
"note": "Newer official high-order line-integration data; do not mix older website table with this reference."
}
@@ -0,0 +1,104 @@
{
"commit": "d79e3f6f0a424f5bcc3a1a6d610313e49daf97a2",
"production_sha256": {
"src/CelerisLab/__init__.py": "21cc469bd6e970a1f9ff5970c8eea4602065e41fc881854759ee565ee55c10c6",
"src/CelerisLab/body/__init__.py": "e4e0e8f0769a94428d5b02fac790443997556cfc5515a5f1ed1595d2c694d69d",
"src/CelerisLab/body/action_smoother.py": "b1a23ee6bbd4850b172cb8738c5c3546ee465a54189e6af9d8044f023d4687dc",
"src/CelerisLab/body/coupling/__init__.py": "c931f7eb318d48408c3e2febcc6917c88c8dcc1697cbf4329b1f340d94c35763",
"src/CelerisLab/body/coupling/soa_packer.py": "8ec26fd55ef74801bbd5ab6bd3d0a7dabc750cbd498aaac2ca4c61a0108ad3c9",
"src/CelerisLab/body/ddf_patch.py": "60fcfb7441e73bf290bcff41ca3a83da3e7ddad765d7247464e8f02e3043b4e2",
"src/CelerisLab/body/geometry/__init__.py": "ada88980ab672959aca9aa3a4c4aade3219524b2ab68110cd2a2f41e3e0ca90a",
"src/CelerisLab/body/geometry/base.py": "cceca14361ca2f34d34a3e43730e1a79b8ca81be00f0dd62e884db32b479419f",
"src/CelerisLab/body/geometry/circle.py": "7605ca7c21103f1d801ac73ab8dd18dffbdac425b2dee7b8858b283628a9e436",
"src/CelerisLab/body/jets.py": "4104c24af4651f4bb4f8d8fd36fb6d88f41c6e4469822f36e22e74c9ede31dac",
"src/CelerisLab/body/manager.py": "01b653d25bd9b4333379c8bfd12383596b686d2029f2e91b1c91b618540ec6b3",
"src/CelerisLab/body/objects.py": "8e63cee8faa9cc6243bcff428be628dfad73a02a9a3b8ece7d78c0a63406a08d",
"src/CelerisLab/body/preprocess/__init__.py": "dfd9d58bc07f626ab23a70053e31b88a6d9165d2e4c76a4e9f48abe674d3d2ec",
"src/CelerisLab/body/preprocess/flag_overlay.py": "831fc9e9736b758847759a085ff133eb49ec91318ace4fda76f85960138ee300",
"src/CelerisLab/body/registry.py": "fbb7327780adbdc6da15ebdb799980daf32df22ee974af0893d7409f177bbb68",
"src/CelerisLab/body/sync_plan.py": "3a15bfe9ebeb91482dbfc68850761ef39c872cc12d2efc795babd0c01e392323",
"src/CelerisLab/common/__init__.py": "d4dbde78bc97fccc9eabac9abae8f0f15c112c10ff8e9b45bd9401d4723df1ef",
"src/CelerisLab/common/_types.py": "aad2ff1a9033d4fd68783ad7123afb47201ddc58785cb6dc0246947037072a5f",
"src/CelerisLab/common/checkpoint.py": "b2f960aecd5ac27a3a7130b809ffad4709c44d522edef0359ab952654625410b",
"src/CelerisLab/common/pathline.py": "a1b6efcc0a076d0f1dc8d49a595779426d7d5626b00072a59bac710ff02ad046",
"src/CelerisLab/common/preprocess.py": "7fc78a08857cf8e98519500b094a1d896af189b6b15658ff9b388ed51bed4515",
"src/CelerisLab/common/render.py": "0e055476257eb2bcd11e733766e52195de02f8fad228f570a3511a7c831bdc29",
"src/CelerisLab/common/streakline/__init__.py": "13ff1cfa416fdd721a08a659e76b5a5a99b8d3201add10a83b7b0bc654bc1eec",
"src/CelerisLab/common/streakline/_config.py": "a9185ef68e9c7b5d5a1dc0b04a28341e2ad91d13c145a7c106636a878cf0a605",
"src/CelerisLab/common/streakline/_integrate.py": "3fbd4368b2c8d0e7fda40dc65aac5df02b085008b4c68acd83cb7ae45d33a13b",
"src/CelerisLab/common/streakline/_render.py": "236cd637414ee318c4d66f9aed32110d8dc42a100d65fde06172f7cd0ba62c65",
"src/CelerisLab/common/streakline/_streakline.py": "1a3e08daa706b5f83cdeb337e9a945a36ddf7583c40af1d0bb9d825ac954df37",
"src/CelerisLab/config.py": "2f1bba7515f5bc08cb3a8d140bcafe7fa0689e01b3b06494697bb2956a71530e",
"src/CelerisLab/configs/CONFIG.md": "41cafdbe525366c08aa9d0416ea026ccf0026ba7123b8a9b65f968a80d928e3a",
"src/CelerisLab/configs/config_body.json": "055f261f0828ca1d4f2bb694bd15b72bbcabe31d552326208f28e37f761adab1",
"src/CelerisLab/configs/config_lbm.json": "17e4311f1f250c6ece59177a8b6ff856b69a016c7c10267e774b030a0a390865",
"src/CelerisLab/configs/config_lbm_flume_mrt_zouhe.json": "8f593321db05a563a42931603a228d61cbcba06dfbe113262d5cd0ad93c0fe79",
"src/CelerisLab/configs/config_lbm_flume_trt_regularized.json": "f4bba8e6a4339fc3c4eb14f6e2f57c4b22993c73703c773d853edfbe34736e56",
"src/CelerisLab/configs/config_lbm_three_cylinder_triangle.json": "eeb8e7149dbe4aa6b3a9562802735aa950750dfbe2d972a0727e788573afa817",
"src/CelerisLab/cuda/__init__.py": "0d01dff9f0399c8baba822aab5ca0122a135f011008ed8a499ebd9f7e5b06aea",
"src/CelerisLab/cuda/compiler_v2.py": "c4cc3ec4445c8d75e6a253dd87963877117a722bc73b9d4811806fc6045d08c1",
"src/CelerisLab/cuda/context.py": "013dd4ec6d01d2fbd11afdfb103eac6a273bd9f798d663d761b851eeb27885e9",
"src/CelerisLab/cuda/obs_layout.py": "5ffb2c3aeeaaaaeb64f2cd8febfbcf4ad9919e5414020ad753cfade0683b21c2",
"src/CelerisLab/lbm/__init__.py": "7f60fb3aaae81501b510d08c2fedff44fcb05657260eb10b6f5cc91e7204306b",
"src/CelerisLab/lbm/block_stepper.py": "00b313daddc6e63d8ab8423c9190ca7e92ad291de20a2f6b80eb53e3f2d85ec7",
"src/CelerisLab/lbm/blocks.py": "1e644c5719993f418185ad4a10b6950a30636fc912befd9eab41ff48b4cf85cd",
"src/CelerisLab/lbm/curved_links.py": "b6957fa13d13f864dba4f7364e93a39905d062dbb3ee9b3f5191add81fff0732",
"src/CelerisLab/lbm/descriptors.py": "c9f025dfc14da43b989047b84cc22bd05d5cc92c9ec695c1da68fdfb788e3f2b",
"src/CelerisLab/lbm/equilibrium.py": "2fb2f2bb7ff326d23abce80fc2a4c0907b17740ea3e29fe6ae079c5044783826",
"src/CelerisLab/lbm/field.py": "0e727eacc3706add4d8d15c418e9b631050871f831698d3ac689dd5f2c701b9b",
"src/CelerisLab/lbm/hierarchy.py": "f234975074f6b6d289d573ff6ea6832feae0e27cb04f80b37fe81792b13a12e7",
"src/CelerisLab/lbm/initializers.py": "dacf9586963e862d6d0a98ef1a066036be6f91e9d423720661d8ef2c1fb2df6a",
"src/CelerisLab/lbm/kernels/blocks.cu": "72d7803c859c974c24ed16b36df43d9e5799828be0fe589cd5bf7a0a5e5cf485",
"src/CelerisLab/lbm/kernels/boundary/bounce_back.cuh": "aba3748c564ed91b161d268ef8b737b7c7be96a823063d201f3da684dd845233",
"src/CelerisLab/lbm/kernels/boundary/curved_boundary.cuh": "aeb16bfd483514fb03e15a2fed437d673c3b8b2fe68ab83edf4244ef593878ea",
"src/CelerisLab/lbm/kernels/boundary/esopull_curved_helpers.cuh": "b9b474bc4edaff05681c5443235472d04072d0d775fc0eb9c5967faef52b0258",
"src/CelerisLab/lbm/kernels/boundary/ibm_kernels.cuh": "e6909b547dc8d64cdd8a76a90660243a8f5fee535caa161b52b4dd8b7c29934b",
"src/CelerisLab/lbm/kernels/boundary/inlet/channel_stabilized.cuh": "efe077afcbb6b731256a987c2fd79a1dbc470c902f87070d60421ea6cb13d4e2",
"src/CelerisLab/lbm/kernels/boundary/inlet/common.cuh": "f3cec54c8873c65b3dca4375605d2c89a8727765884d3954c235242fdff9c6ba",
"src/CelerisLab/lbm/kernels/boundary/inlet/equilibrium.cuh": "f0964c811b2c41a5aaa1c6d99c490b06e850c634eb8749f1f05ef61f93c2de1d",
"src/CelerisLab/lbm/kernels/boundary/inlet/regularized.cuh": "943d4a571818bacb89c70bccfaac202691121e3e385803ce6309123a0a03ddaf",
"src/CelerisLab/lbm/kernels/boundary/inlet/zou_he_local.cuh": "e73e66b853b1e4e0e30d8f4185d832aa414ce670a1813ce2b5459f2887f71f89",
"src/CelerisLab/lbm/kernels/boundary/inlet_outlet.cuh": "de8b213a6da42c2e5e9f8966199fc66ea4541b85144a90f3f5dbad3c6ab63b97",
"src/CelerisLab/lbm/kernels/boundary/outlet/pressure_neq.cuh": "c8b2adc66d12fe602076ddc9693c5c208711fcaddb58aa9a31401a269f51c4c4",
"src/CelerisLab/lbm/kernels/boundary/planar_velocity.cuh": "501b2759d0e2e4280cb6fdd127d66a50fa89f6de48fc2cae9a7eafb11ae90bed",
"src/CelerisLab/lbm/kernels/config/config_grid.h": "1db3e030ebdc116cd7f02362d1fd077daac55e8f0b4329f6285948c224d3ca5d",
"src/CelerisLab/lbm/kernels/config/config_method.h": "4374e04a130f90f97d17cc20c40d2f9ea0ed0dd8603817d07f215d02266e247c",
"src/CelerisLab/lbm/kernels/config/config_objects.h": "db793af052c756561a190a3ff2cb93ee5fe28462ee4f97c6af80882b4dc8b072",
"src/CelerisLab/lbm/kernels/config/config_obs.h": "c11e2249359145b5c4cbaaf0487506fa77c1f4b82c7c169ddc574aa0babcd727",
"src/CelerisLab/lbm/kernels/config/config_physics.h": "8ac0990b168e1058588676cc05c80df8634cfd671dbc92a89246c3c372d83a4e",
"src/CelerisLab/lbm/kernels/config.h": "7d1124125e0b354558297934460e64c760fb68cdd7bf6d01a1238bb2eaab8c37",
"src/CelerisLab/lbm/kernels/core/descriptors.cuh": "8440bc14857da9007982650f828a23608eec9f7cc4bf772e8c8dc1239cb45356",
"src/CelerisLab/lbm/kernels/core/flags.cuh": "0025705f24710330d9d9195f713879bfb650a85a3449baef0e196b831af104ab",
"src/CelerisLab/lbm/kernels/core/layout.cuh": "1fbd820021c89ac3efcde24145996cb0147769e607e498e61f308ed3e0b13706",
"src/CelerisLab/lbm/kernels/core/obs.cuh": "b742b77c9f61e1f020fe9922c3838ae9d855e17b381154958b58f10a22938922",
"src/CelerisLab/lbm/kernels/core/params.cuh": "6e18bf730d936d940e3d85452f0714a2eaae31bf41002f9fd520ec5b372878dd",
"src/CelerisLab/lbm/kernels/core/precision.cuh": "849df14b4cce50af52399e4b8f3e997196f3ae5f1163f68e28df3e6b2507a8c4",
"src/CelerisLab/lbm/kernels/kernel_v2.cu": "c7bd3fb705b77261ba2441c52a24a7e6a7865c165cba088a1b75201c4d29c408",
"src/CelerisLab/lbm/kernels/operators/collision_mrt.cuh": "aee444eb60c47a3fd518dca975d1af05425a0818a0ec6c3fb3f6150f60c2cb44",
"src/CelerisLab/lbm/kernels/operators/collision_srt.cuh": "6b7ff842f392c8f853d2d1a97cc69bfe50f21283f73e659404da07cf6c95f5e1",
"src/CelerisLab/lbm/kernels/operators/collision_trt.cuh": "9725b9840b9ad8b4ec7be61ff5d06d3151e86d1a87585c13f2fbf934d0d2c5f6",
"src/CelerisLab/lbm/kernels/operators/forcing_guo.cuh": "969a430c1af03f32e3bd0df95d805e64f56334717c44dd4ba208ba503062a24b",
"src/CelerisLab/lbm/kernels/operators/helpers.cuh": "0b3a4d95d7d19f5fec09e5423b8f4cc99f8a226b541967425967330da5a051ec",
"src/CelerisLab/lbm/kernels/operators/macro.cuh": "2be20eb2b7d7f5522daaad7f4b57b1ff8cdf883efe4a67846a96783b2fe846a3",
"src/CelerisLab/lbm/kernels/operators/turbulence_smag.cuh": "ff7f000297ee0817e255ad9fcb6a13b647e3ddd5b206518d35f38d2895a9f501",
"src/CelerisLab/lbm/kernels/refinement.cu": "129f3d91da93b4324f88a0da73a4da1e5390f72c14cce574ee63306712da9895",
"src/CelerisLab/lbm/kernels/step/aux_kernels.cu": "578a5b20a157241ee678547059abb1372dd5a589ad06bb2c15b987eb01ca1013",
"src/CelerisLab/lbm/kernels/step/curved_double.cu": "bfa7a1df1e939020314d49755045f7a85fe4a19cc1c68dbe62653c66c5e7c2b3",
"src/CelerisLab/lbm/kernels/step/esopull_macro.cu": "c73116020ca217ede1e4de27c8269c31f9d91e33433ac8252a21e9b14187c50f",
"src/CelerisLab/lbm/kernels/step/esopull_sync.cu": "c0ff3e72900c3ab89a05dd03684e96d3b740cf1672d565727fc2af165933d087",
"src/CelerisLab/lbm/kernels/step/init_flow.cu": "305be3bb12fc0d039acec1701baabcf0e8a35e683c49c34f2af8ca9867d2b29c",
"src/CelerisLab/lbm/kernels/step/one_step_double.cu": "f9f8242b38e8e0cf8425b75e2b0215bc15363c62eaa0fb43c761b8e0931bfdc9",
"src/CelerisLab/lbm/kernels/step/one_step_esopull.cu": "aa223b2744ae6c9125673df311a96ce588032c1ba58bebb11fd33ecb990f8ed4",
"src/CelerisLab/lbm/kernels/streaming/esopull_semantic_helpers.cuh": "dd5a9091628b079efd97e53fd49e52d09dcee88529235db77a5648e02937af6e",
"src/CelerisLab/lbm/kernels/streaming/esopull_single_buffer.cuh": "77ed4ab9a0ebc5ff62a91a7c39066ef395391ae36e9040a69fb2bd8b1626dc79",
"src/CelerisLab/lbm/kernels/streaming/pull_double_buffer.cuh": "b65d741ed78b7b62f99531ba0c0ce1936663d8dc9640e7d7d16c707f1baf6549",
"src/CelerisLab/lbm/population_storage.py": "a6a86c29c52c892edfe4802fb773c387212e3f4cc86708d80e154986ddf88044",
"src/CelerisLab/lbm/raw_exchange.py": "8694202466eba848563e298656353b0f5db94e21a01fa05f51aad6dc2b9fd311",
"src/CelerisLab/lbm/refinement.py": "08c2a0557276a98cbcaa459d9bb7a9735d8bbbf64a6114c57af2fda61481a687",
"src/CelerisLab/lbm/refinement_stepper.py": "16f778522c8e4dbb444b84d6381e94c047e0e05299541e589f0f365fb9a89c4e",
"src/CelerisLab/lbm/refinement_tables.py": "fdd344024b89df3a83174dc810ca5e32db5e19eb8b1609f8f73b0821f56546b6",
"src/CelerisLab/lbm/stepper.py": "0dcb7cd7ae87e33e3dc67a38b6aa43042fbe673e37d9ad165342ee1d695933ce",
"src/CelerisLab/lbm/synchronized_remap.py": "7400fc4beaffd4649d6a9c524d38ce5fd6fa7eeb32eb5332e4030a4b6648f197",
"src/CelerisLab/simulation.py": "0a03904dc6de88667d7af699005a289adf8b09f0976cb0d099b041f4830a8f4c"
}
}
@@ -0,0 +1,104 @@
{
"revision": "d79e3f6f0a424f5bcc3a1a6d610313e49daf97a2",
"files": {
"src/CelerisLab/__init__.py": "21cc469bd6e970a1f9ff5970c8eea4602065e41fc881854759ee565ee55c10c6",
"src/CelerisLab/body/__init__.py": "e4e0e8f0769a94428d5b02fac790443997556cfc5515a5f1ed1595d2c694d69d",
"src/CelerisLab/body/action_smoother.py": "b1a23ee6bbd4850b172cb8738c5c3546ee465a54189e6af9d8044f023d4687dc",
"src/CelerisLab/body/coupling/__init__.py": "c931f7eb318d48408c3e2febcc6917c88c8dcc1697cbf4329b1f340d94c35763",
"src/CelerisLab/body/coupling/soa_packer.py": "8ec26fd55ef74801bbd5ab6bd3d0a7dabc750cbd498aaac2ca4c61a0108ad3c9",
"src/CelerisLab/body/ddf_patch.py": "60fcfb7441e73bf290bcff41ca3a83da3e7ddad765d7247464e8f02e3043b4e2",
"src/CelerisLab/body/geometry/__init__.py": "ada88980ab672959aca9aa3a4c4aade3219524b2ab68110cd2a2f41e3e0ca90a",
"src/CelerisLab/body/geometry/base.py": "cceca14361ca2f34d34a3e43730e1a79b8ca81be00f0dd62e884db32b479419f",
"src/CelerisLab/body/geometry/circle.py": "7605ca7c21103f1d801ac73ab8dd18dffbdac425b2dee7b8858b283628a9e436",
"src/CelerisLab/body/jets.py": "4104c24af4651f4bb4f8d8fd36fb6d88f41c6e4469822f36e22e74c9ede31dac",
"src/CelerisLab/body/manager.py": "01b653d25bd9b4333379c8bfd12383596b686d2029f2e91b1c91b618540ec6b3",
"src/CelerisLab/body/objects.py": "8e63cee8faa9cc6243bcff428be628dfad73a02a9a3b8ece7d78c0a63406a08d",
"src/CelerisLab/body/preprocess/__init__.py": "dfd9d58bc07f626ab23a70053e31b88a6d9165d2e4c76a4e9f48abe674d3d2ec",
"src/CelerisLab/body/preprocess/flag_overlay.py": "831fc9e9736b758847759a085ff133eb49ec91318ace4fda76f85960138ee300",
"src/CelerisLab/body/registry.py": "fbb7327780adbdc6da15ebdb799980daf32df22ee974af0893d7409f177bbb68",
"src/CelerisLab/body/sync_plan.py": "3a15bfe9ebeb91482dbfc68850761ef39c872cc12d2efc795babd0c01e392323",
"src/CelerisLab/common/__init__.py": "d4dbde78bc97fccc9eabac9abae8f0f15c112c10ff8e9b45bd9401d4723df1ef",
"src/CelerisLab/common/_types.py": "aad2ff1a9033d4fd68783ad7123afb47201ddc58785cb6dc0246947037072a5f",
"src/CelerisLab/common/checkpoint.py": "b2f960aecd5ac27a3a7130b809ffad4709c44d522edef0359ab952654625410b",
"src/CelerisLab/common/pathline.py": "a1b6efcc0a076d0f1dc8d49a595779426d7d5626b00072a59bac710ff02ad046",
"src/CelerisLab/common/preprocess.py": "7fc78a08857cf8e98519500b094a1d896af189b6b15658ff9b388ed51bed4515",
"src/CelerisLab/common/render.py": "0e055476257eb2bcd11e733766e52195de02f8fad228f570a3511a7c831bdc29",
"src/CelerisLab/common/streakline/__init__.py": "13ff1cfa416fdd721a08a659e76b5a5a99b8d3201add10a83b7b0bc654bc1eec",
"src/CelerisLab/common/streakline/_config.py": "a9185ef68e9c7b5d5a1dc0b04a28341e2ad91d13c145a7c106636a878cf0a605",
"src/CelerisLab/common/streakline/_integrate.py": "3fbd4368b2c8d0e7fda40dc65aac5df02b085008b4c68acd83cb7ae45d33a13b",
"src/CelerisLab/common/streakline/_render.py": "236cd637414ee318c4d66f9aed32110d8dc42a100d65fde06172f7cd0ba62c65",
"src/CelerisLab/common/streakline/_streakline.py": "1a3e08daa706b5f83cdeb337e9a945a36ddf7583c40af1d0bb9d825ac954df37",
"src/CelerisLab/config.py": "2f1bba7515f5bc08cb3a8d140bcafe7fa0689e01b3b06494697bb2956a71530e",
"src/CelerisLab/configs/CONFIG.md": "41cafdbe525366c08aa9d0416ea026ccf0026ba7123b8a9b65f968a80d928e3a",
"src/CelerisLab/configs/config_body.json": "055f261f0828ca1d4f2bb694bd15b72bbcabe31d552326208f28e37f761adab1",
"src/CelerisLab/configs/config_lbm.json": "17e4311f1f250c6ece59177a8b6ff856b69a016c7c10267e774b030a0a390865",
"src/CelerisLab/configs/config_lbm_flume_mrt_zouhe.json": "8f593321db05a563a42931603a228d61cbcba06dfbe113262d5cd0ad93c0fe79",
"src/CelerisLab/configs/config_lbm_flume_trt_regularized.json": "f4bba8e6a4339fc3c4eb14f6e2f57c4b22993c73703c773d853edfbe34736e56",
"src/CelerisLab/configs/config_lbm_three_cylinder_triangle.json": "eeb8e7149dbe4aa6b3a9562802735aa950750dfbe2d972a0727e788573afa817",
"src/CelerisLab/cuda/__init__.py": "0d01dff9f0399c8baba822aab5ca0122a135f011008ed8a499ebd9f7e5b06aea",
"src/CelerisLab/cuda/compiler_v2.py": "c4cc3ec4445c8d75e6a253dd87963877117a722bc73b9d4811806fc6045d08c1",
"src/CelerisLab/cuda/context.py": "013dd4ec6d01d2fbd11afdfb103eac6a273bd9f798d663d761b851eeb27885e9",
"src/CelerisLab/cuda/obs_layout.py": "5ffb2c3aeeaaaaeb64f2cd8febfbcf4ad9919e5414020ad753cfade0683b21c2",
"src/CelerisLab/lbm/__init__.py": "7f60fb3aaae81501b510d08c2fedff44fcb05657260eb10b6f5cc91e7204306b",
"src/CelerisLab/lbm/block_stepper.py": "00b313daddc6e63d8ab8423c9190ca7e92ad291de20a2f6b80eb53e3f2d85ec7",
"src/CelerisLab/lbm/blocks.py": "1e644c5719993f418185ad4a10b6950a30636fc912befd9eab41ff48b4cf85cd",
"src/CelerisLab/lbm/curved_links.py": "b6957fa13d13f864dba4f7364e93a39905d062dbb3ee9b3f5191add81fff0732",
"src/CelerisLab/lbm/descriptors.py": "c9f025dfc14da43b989047b84cc22bd05d5cc92c9ec695c1da68fdfb788e3f2b",
"src/CelerisLab/lbm/equilibrium.py": "2fb2f2bb7ff326d23abce80fc2a4c0907b17740ea3e29fe6ae079c5044783826",
"src/CelerisLab/lbm/field.py": "0e727eacc3706add4d8d15c418e9b631050871f831698d3ac689dd5f2c701b9b",
"src/CelerisLab/lbm/hierarchy.py": "f234975074f6b6d289d573ff6ea6832feae0e27cb04f80b37fe81792b13a12e7",
"src/CelerisLab/lbm/initializers.py": "dacf9586963e862d6d0a98ef1a066036be6f91e9d423720661d8ef2c1fb2df6a",
"src/CelerisLab/lbm/kernels/blocks.cu": "72d7803c859c974c24ed16b36df43d9e5799828be0fe589cd5bf7a0a5e5cf485",
"src/CelerisLab/lbm/kernels/boundary/bounce_back.cuh": "aba3748c564ed91b161d268ef8b737b7c7be96a823063d201f3da684dd845233",
"src/CelerisLab/lbm/kernels/boundary/curved_boundary.cuh": "aeb16bfd483514fb03e15a2fed437d673c3b8b2fe68ab83edf4244ef593878ea",
"src/CelerisLab/lbm/kernels/boundary/esopull_curved_helpers.cuh": "b9b474bc4edaff05681c5443235472d04072d0d775fc0eb9c5967faef52b0258",
"src/CelerisLab/lbm/kernels/boundary/ibm_kernels.cuh": "e6909b547dc8d64cdd8a76a90660243a8f5fee535caa161b52b4dd8b7c29934b",
"src/CelerisLab/lbm/kernels/boundary/inlet/channel_stabilized.cuh": "efe077afcbb6b731256a987c2fd79a1dbc470c902f87070d60421ea6cb13d4e2",
"src/CelerisLab/lbm/kernels/boundary/inlet/common.cuh": "f3cec54c8873c65b3dca4375605d2c89a8727765884d3954c235242fdff9c6ba",
"src/CelerisLab/lbm/kernels/boundary/inlet/equilibrium.cuh": "f0964c811b2c41a5aaa1c6d99c490b06e850c634eb8749f1f05ef61f93c2de1d",
"src/CelerisLab/lbm/kernels/boundary/inlet/regularized.cuh": "943d4a571818bacb89c70bccfaac202691121e3e385803ce6309123a0a03ddaf",
"src/CelerisLab/lbm/kernels/boundary/inlet/zou_he_local.cuh": "e73e66b853b1e4e0e30d8f4185d832aa414ce670a1813ce2b5459f2887f71f89",
"src/CelerisLab/lbm/kernels/boundary/inlet_outlet.cuh": "de8b213a6da42c2e5e9f8966199fc66ea4541b85144a90f3f5dbad3c6ab63b97",
"src/CelerisLab/lbm/kernels/boundary/outlet/pressure_neq.cuh": "c8b2adc66d12fe602076ddc9693c5c208711fcaddb58aa9a31401a269f51c4c4",
"src/CelerisLab/lbm/kernels/boundary/planar_velocity.cuh": "501b2759d0e2e4280cb6fdd127d66a50fa89f6de48fc2cae9a7eafb11ae90bed",
"src/CelerisLab/lbm/kernels/config.h": "7d1124125e0b354558297934460e64c760fb68cdd7bf6d01a1238bb2eaab8c37",
"src/CelerisLab/lbm/kernels/config/config_grid.h": "1db3e030ebdc116cd7f02362d1fd077daac55e8f0b4329f6285948c224d3ca5d",
"src/CelerisLab/lbm/kernels/config/config_method.h": "4374e04a130f90f97d17cc20c40d2f9ea0ed0dd8603817d07f215d02266e247c",
"src/CelerisLab/lbm/kernels/config/config_objects.h": "db793af052c756561a190a3ff2cb93ee5fe28462ee4f97c6af80882b4dc8b072",
"src/CelerisLab/lbm/kernels/config/config_obs.h": "c11e2249359145b5c4cbaaf0487506fa77c1f4b82c7c169ddc574aa0babcd727",
"src/CelerisLab/lbm/kernels/config/config_physics.h": "8ac0990b168e1058588676cc05c80df8634cfd671dbc92a89246c3c372d83a4e",
"src/CelerisLab/lbm/kernels/core/descriptors.cuh": "8440bc14857da9007982650f828a23608eec9f7cc4bf772e8c8dc1239cb45356",
"src/CelerisLab/lbm/kernels/core/flags.cuh": "0025705f24710330d9d9195f713879bfb650a85a3449baef0e196b831af104ab",
"src/CelerisLab/lbm/kernels/core/layout.cuh": "1fbd820021c89ac3efcde24145996cb0147769e607e498e61f308ed3e0b13706",
"src/CelerisLab/lbm/kernels/core/obs.cuh": "b742b77c9f61e1f020fe9922c3838ae9d855e17b381154958b58f10a22938922",
"src/CelerisLab/lbm/kernels/core/params.cuh": "6e18bf730d936d940e3d85452f0714a2eaae31bf41002f9fd520ec5b372878dd",
"src/CelerisLab/lbm/kernels/core/precision.cuh": "849df14b4cce50af52399e4b8f3e997196f3ae5f1163f68e28df3e6b2507a8c4",
"src/CelerisLab/lbm/kernels/kernel_v2.cu": "c7bd3fb705b77261ba2441c52a24a7e6a7865c165cba088a1b75201c4d29c408",
"src/CelerisLab/lbm/kernels/operators/collision_mrt.cuh": "aee444eb60c47a3fd518dca975d1af05425a0818a0ec6c3fb3f6150f60c2cb44",
"src/CelerisLab/lbm/kernels/operators/collision_srt.cuh": "6b7ff842f392c8f853d2d1a97cc69bfe50f21283f73e659404da07cf6c95f5e1",
"src/CelerisLab/lbm/kernels/operators/collision_trt.cuh": "9725b9840b9ad8b4ec7be61ff5d06d3151e86d1a87585c13f2fbf934d0d2c5f6",
"src/CelerisLab/lbm/kernels/operators/forcing_guo.cuh": "969a430c1af03f32e3bd0df95d805e64f56334717c44dd4ba208ba503062a24b",
"src/CelerisLab/lbm/kernels/operators/helpers.cuh": "0b3a4d95d7d19f5fec09e5423b8f4cc99f8a226b541967425967330da5a051ec",
"src/CelerisLab/lbm/kernels/operators/macro.cuh": "2be20eb2b7d7f5522daaad7f4b57b1ff8cdf883efe4a67846a96783b2fe846a3",
"src/CelerisLab/lbm/kernels/operators/turbulence_smag.cuh": "ff7f000297ee0817e255ad9fcb6a13b647e3ddd5b206518d35f38d2895a9f501",
"src/CelerisLab/lbm/kernels/refinement.cu": "129f3d91da93b4324f88a0da73a4da1e5390f72c14cce574ee63306712da9895",
"src/CelerisLab/lbm/kernels/step/aux_kernels.cu": "578a5b20a157241ee678547059abb1372dd5a589ad06bb2c15b987eb01ca1013",
"src/CelerisLab/lbm/kernels/step/curved_double.cu": "bfa7a1df1e939020314d49755045f7a85fe4a19cc1c68dbe62653c66c5e7c2b3",
"src/CelerisLab/lbm/kernels/step/esopull_macro.cu": "c73116020ca217ede1e4de27c8269c31f9d91e33433ac8252a21e9b14187c50f",
"src/CelerisLab/lbm/kernels/step/esopull_sync.cu": "c0ff3e72900c3ab89a05dd03684e96d3b740cf1672d565727fc2af165933d087",
"src/CelerisLab/lbm/kernels/step/init_flow.cu": "305be3bb12fc0d039acec1701baabcf0e8a35e683c49c34f2af8ca9867d2b29c",
"src/CelerisLab/lbm/kernels/step/one_step_double.cu": "f9f8242b38e8e0cf8425b75e2b0215bc15363c62eaa0fb43c761b8e0931bfdc9",
"src/CelerisLab/lbm/kernels/step/one_step_esopull.cu": "aa223b2744ae6c9125673df311a96ce588032c1ba58bebb11fd33ecb990f8ed4",
"src/CelerisLab/lbm/kernels/streaming/esopull_semantic_helpers.cuh": "dd5a9091628b079efd97e53fd49e52d09dcee88529235db77a5648e02937af6e",
"src/CelerisLab/lbm/kernels/streaming/esopull_single_buffer.cuh": "77ed4ab9a0ebc5ff62a91a7c39066ef395391ae36e9040a69fb2bd8b1626dc79",
"src/CelerisLab/lbm/kernels/streaming/pull_double_buffer.cuh": "b65d741ed78b7b62f99531ba0c0ce1936663d8dc9640e7d7d16c707f1baf6549",
"src/CelerisLab/lbm/population_storage.py": "a6a86c29c52c892edfe4802fb773c387212e3f4cc86708d80e154986ddf88044",
"src/CelerisLab/lbm/raw_exchange.py": "8694202466eba848563e298656353b0f5db94e21a01fa05f51aad6dc2b9fd311",
"src/CelerisLab/lbm/refinement.py": "08c2a0557276a98cbcaa459d9bb7a9735d8bbbf64a6114c57af2fda61481a687",
"src/CelerisLab/lbm/refinement_stepper.py": "16f778522c8e4dbb444b84d6381e94c047e0e05299541e589f0f365fb9a89c4e",
"src/CelerisLab/lbm/refinement_tables.py": "fdd344024b89df3a83174dc810ca5e32db5e19eb8b1609f8f73b0821f56546b6",
"src/CelerisLab/lbm/stepper.py": "0dcb7cd7ae87e33e3dc67a38b6aa43042fbe673e37d9ad165342ee1d695933ce",
"src/CelerisLab/lbm/synchronized_remap.py": "7400fc4beaffd4649d6a9c524d38ce5fd6fa7eeb32eb5332e4030a4b6648f197",
"src/CelerisLab/simulation.py": "0a03904dc6de88667d7af699005a289adf8b09f0976cb0d099b041f4830a8f4c"
}
}
+130
View File
@@ -0,0 +1,130 @@
"""CPU-only cycle extraction. All criteria frozen before any Re100 GPU result."""
import numpy as np
GATES = dict(
min_tstar=250.0,
cycles=5,
min_period=2.0,
max_period=5.0,
min_lift_amplitude=0.2,
period_relative_range=0.005,
extrema_absolute_range=0.005,
mass_relative_range=1e-4,
cycle_mean_flux_relative=0.005,
)
REFERENCE_GATES = dict(
cd_relative=0.02, cl_absolute=0.03, dp_relative=0.02, st_relative=0.02
)
def minima(t, y):
out = []
for i in range(1, len(y) - 1):
if y[i] < y[i - 1] and y[i] <= y[i + 1]:
# Quadratic sub-sample location; only separated physical cycles accepted.
coef = np.polyfit(t[i - 1 : i + 2] - t[i], y[i - 1 : i + 2], 2)
v = t[i] - coef[1] / (2 * coef[0]) if coef[0] > 0 else t[i]
if t[i - 1] <= v <= t[i + 1] and (not out or v - out[-1] > 1.0):
out.append(float(v))
return out
def extract(rows, min_time=250.0):
if len(rows) < 3:
return []
t = np.array([r["tstar_force"] for r in rows])
cl = np.array([r["cl"] for r in rows])
ends = minima(t, cl)
cycles = []
for a, b in zip(ends, ends[1:]):
if a < min_time or not GATES["min_period"] < b - a < GATES["max_period"]:
continue
grid = np.linspace(a, b, 257)
c = dict(start=a, end=b, period=b - a, st=1 / (b - a))
for key in ("cd", "cl", "dp", "mass", "flux_in", "flux_out"):
times = np.array(
[r["tstar" if key not in ("cd", "cl") else "tstar_force"] for r in rows]
)
# No endpoint extrapolation: pressure/mass must cover the whole cycle.
if a < times[0] or b > times[-1]:
break
v = np.interp(grid, times, [r[key] for r in rows])
c[key + "_min"] = float(v.min())
c[key + "_max"] = float(v.max())
c[key + "_midrange"] = float((v.min() + v.max()) / 2)
c[key + "_amplitude"] = float(v.max() - v.min())
c[key + "_time_mean"] = float(
np.sum((v[1:] + v[:-1]) * np.diff(grid) / 2) / (b - a)
)
if key in ("cd", "cl", "dp"):
c[key + "_waveform"] = v.tolist()
else:
c["flux_relative"] = abs(
c["flux_in_time_mean"] - c["flux_out_time_mean"]
) / max(abs(c["flux_in_time_mean"]), 1e-30)
cycles.append(c)
return cycles
def assess(rows):
cycles = extract(rows, min_time=GATES["min_tstar"])
tail = cycles[-GATES["cycles"] :]
checks = {}
checks["enough_cycles"] = len(tail) == GATES["cycles"]
if checks["enough_cycles"]:
checks["consecutive"] = all(
abs(a["end"] - b["start"]) < 1e-8 for a, b in zip(tail, tail[1:])
)
checks["recent"] = (
0 <= rows[-1]["tstar_force"] - tail[-1]["end"] <= GATES["max_period"]
)
checks["shedding"] = (
min(c["cl_amplitude"] for c in tail) >= GATES["min_lift_amplitude"]
)
periods = [c["period"] for c in tail]
checks["period"] = (
np.ptp(periods) / np.mean(periods) <= GATES["period_relative_range"]
)
checks["extrema"] = all(
np.ptp([c[k + "_" + e] for c in tail]) <= GATES["extrema_absolute_range"]
for k in ("cd", "cl", "dp")
for e in ("min", "max")
)
mass = [c["mass_time_mean"] for c in tail]
checks["mass"] = (
np.ptp(mass) / abs(np.mean(mass)) <= GATES["mass_relative_range"]
)
checks["flux"] = (
max(c["flux_relative"] for c in tail) <= GATES["cycle_mean_flux_relative"]
)
return dict(
periodic=bool(checks["enough_cycles"] and all(checks.values())),
checks={k: bool(v) for k, v in checks.items()},
cycles=cycles,
)
def compare(cycle, ref):
errors = {}
passed = True
for key in ("cd", "cl", "dp"):
for ext in ("min", "max"):
k = key + "_" + ext
e = abs(cycle[k] - ref[k])
e = e if key == "cl" else e / abs(ref[k])
errors[k] = e
passed &= (
e
<= REFERENCE_GATES[key + ("_absolute" if key == "cl" else "_relative")]
)
errors[key + "_waveform_rms"] = float(
np.sqrt(
np.mean(
(np.array(cycle[key + "_waveform"]) - ref[key + "_waveform"]) ** 2
)
)
)
errors["st_relative"] = abs(cycle["st"] - ref["st"]) / ref["st"]
passed &= errors["st_relative"] <= REFERENCE_GATES["st_relative"]
return dict(reference_agreement=bool(passed), errors=errors)
+127
View File
@@ -0,0 +1,127 @@
"""DFG Re100 periodic reference comparison with declared pressure-NEQ outlet, not exact do-nothing.
CPU helpers import without CUDA. GPU execution uses unchanged d79e3f6 production
sources. Default pilot is deliberately too short to certify periodic convergence.
"""
import argparse
import csv
import hashlib
import json
import math
import os
from pathlib import Path
import time
from tempfile import TemporaryDirectory
import numpy as np
ROOT = Path(__file__).resolve().parents[2]
PIN = "d79e3f6f0a424f5bcc3a1a6d610313e49daf97a2"
def geometry(n):
if isinstance(n, bool) or n < 10 or n % 10:
raise ValueError("Diameter must be a positive multiple of 10, at least 10")
return dict(
diameter=n,
nx=22 * n + 1,
ny=41 * n // 10 + 2,
center=[2 * n, 2 * n + 0.5],
radius=n / 2,
dx=0.1 / n,
bottom=0.5,
top=41 * n / 10 + 0.5,
inlet_source_x=0.0,
outlet_source_x=22.0 * n,
)
def parameters(n, speed):
g = geometry(n)
if not 0 < speed <= 0.04:
raise ValueError("Require 0 < lattice mean speed <= .04")
return dict(
**g,
mean_speed=speed,
peak_speed=1.5 * speed,
viscosity=speed * n / 100,
dt=speed * g["dx"],
peak_mach=1.5 * speed * math.sqrt(3),
re=100.0,
)
def make_config(n, speed, collision, outlet, inlet="regularized"):
p = parameters(n, speed)
cfg = json.loads((ROOT / "src/CelerisLab/configs/config_lbm.json").read_text())
cfg["grid"].update(nx=p["nx"], ny=p["ny"], nz=1, lattice_model="D2Q9")
cfg["physics"].update(velocity=speed, viscosity=p["viscosity"], rho=1.0)
cfg["method"].update(
collision=collision,
streaming="double_buffer",
store_precision="FP32",
ddf_shifting=True,
y_wall_bc="bounce_back",
)
cfg["method"]["les"]["enabled"] = False
cfg["method"]["inlet"].update(profile="parabolic", scheme=inlet)
cfg["method"]["outlet"].update(mode=outlet, backflow_clamp=False, blend_alpha=0.7)
body = {
"objects": [
dict(type="cylinder", center=p["center"], radius=p["radius"], omega=0.0)
]
}
return cfg, body, p
def bilinear_fluid(a, fluid, x, y):
ix, iy = math.floor(x), math.floor(y)
tx, ty = x - ix, y - iy
result = 0.0
for xx, yy, w in (
(ix, iy, (1 - tx) * (1 - ty)),
(ix + 1, iy, tx * (1 - ty)),
(ix, iy + 1, (1 - tx) * ty),
(ix + 1, iy + 1, tx * ty),
):
if w == 0:
continue
if not (0 <= yy < a.shape[0] and 0 <= xx < a.shape[1]) or not fluid[yy, xx]:
raise ValueError("Pressure interpolation touched a non-fluid/outside node")
result += w * float(a[yy, xx])
return result
def pressure_probes(rho, fluid, p):
# Linear extrapolation from distances h and 2h outside the cylinder to
# each exact wall probe, after bilinear interpolation using only fluid.
cx, cy = p["center"]
r = p["radius"]
values = []
for sign in (-1, 1):
wall = cx + sign * r
near = bilinear_fluid(rho, fluid, wall + sign, cy)
far = bilinear_fluid(rho, fluid, wall + 2 * sign, cy)
values.append(2 * near - far)
scale = (1.0 / p["mean_speed"]) ** 2 / 3
return dict(
rho_front=values[0], rho_back=values[1], dp=(values[0] - values[1]) * scale
)
def outlet_diagnostic(rho, ux, uy, p):
# Extrapolate fluid values to the declared x=NX-1 source plane. Diagnostic
# excludes two rows beside each wall; not an exact discrete BC identity.
rows = slice(3, -3)
i = -2
dudx = (5 * ux[rows, i] - 8 * ux[rows, i - 1] + 3 * ux[rows, i - 2]) / 2
dvdx = (5 * uy[rows, i] - 8 * uy[rows, i - 1] + 3 * uy[rows, i - 2]) / 2
rho_plane = 2 * rho[rows, i] - rho[rows, i - 1]
pressure = (rho_plane - 1) / 3
scale = p["mean_speed"] ** 2
residual = np.stack((p["viscosity"] * dudx - pressure, p["viscosity"] * dvdx))
return dict(
rms_over_rho_U2=float(np.sqrt(np.mean(residual**2)) / scale),
max_over_rho_U2=float(np.max(abs(residual)) / scale),
definition="fluid-side one-sided estimate of nu*d_x(u)-p*n at declared outlet",
)
+218
View File
@@ -0,0 +1,218 @@
"""Uniform DFG 2D-2 Re100, existing Zou-He inlet / noncanonical pressure-NEQ outlet."""
import argparse, csv, hashlib, json, math, os, time
from pathlib import Path
from tempfile import TemporaryDirectory
import numpy as np
from re100_helpers import ROOT, PIN, make_config, pressure_probes, outlet_diagnostic
from periodic import assess, compare, GATES, REFERENCE_GATES
def run(a):
import CelerisLab
from CelerisLab.simulation import Simulation
from CelerisLab.lbm.descriptors import FLUID
if Path(CelerisLab.__file__).resolve() != ROOT / "src/CelerisLab/__init__.py":
raise RuntimeError("Wrong source checkout")
manifest = json.loads(
(ROOT / "tests/validation/data/dfg_re100/source_manifest.json").read_text()
)
for rel, digest in manifest["production_sha256"].items():
if hashlib.sha256((ROOT / rel).read_bytes()).hexdigest() != digest:
raise RuntimeError("Production mismatch: " + rel)
if a.output.exists() and any(a.output.iterdir()):
raise ValueError("Output must be empty/new")
a.output.mkdir(parents=True, exist_ok=True)
cfg, body, p = make_config(a.diameter, a.speed, "MRT", "neq_extrap", "zou_he_local")
cap = 5.0 if a.mode == "pilot" else 300.0
max_steps = math.ceil(cap * p["diameter"] / p["mean_speed"])
result = dict(
case="DFG 2D-2 Re100, pressure-NEQ outlet deviation",
pin=PIN,
production_verified=True,
parameters=p,
mode=a.mode,
cap_tstar=cap,
inlet=cfg["method"]["inlet"],
outlet=cfg["method"]["outlet"],
canonical_outlet="nu*d_n(u)-p*n=0; NOT imposed exactly",
initialization="unchanged production channel-flow initialization, not zero initial velocity",
force_phase="batch-averaged link momentum exchange, timestamp at batch midpoint",
pressure_phase="postcollision end-sample; interpolated onto force-cycle times for comparison",
gates=GATES,
reference_gates=REFERENCE_GATES,
status="running",
history=[],
suite_validated=False,
)
reference = json.loads(
(ROOT / "tests/validation/data/dfg_re100/reference_cycle.json").read_text()
)
started = time.monotonic()
step_seconds = 0.0
healthy = True
with TemporaryDirectory() as tmp:
cp, bp = Path(tmp) / "lbm.json", Path(tmp) / "body.json"
cp.write_text(json.dumps(cfg))
bp.write_text(json.dumps(body))
with Simulation(str(cp), str(bp), device_id=0) as sim:
sim.initialize()
result["startup_seconds"] = time.monotonic() - started
fluid = (sim.field.flag.reshape(p["ny"], p["nx"]) & FLUID) != 0
np.savez_compressed(
a.output / "geometry.npz",
flag=sim.field.flag.reshape(p["ny"], p["nx"]),
x=np.arange(p["nx"]) * p["dx"],
y=(np.arange(p["ny"]) - 0.5) * p["dx"],
)
with (a.output / "history.csv").open("w") as f:
writer = None
while sim.stepper.step_count < max_steps:
begin = sim.stepper.step_count
tstar = begin * p["mean_speed"] / p["diameter"]
# Pilot exercises the expensive dense measurement path too.
width = 0.02 if a.mode == "pilot" or tstar >= 240 else 0.5
n = min(
max(1, round(width * p["diameter"] / p["mean_speed"])),
max_steps - begin,
)
impulse = np.zeros(2)
timer = time.monotonic()
done = 0
while done < n:
batch = min(100, n - done)
sim.run(batch)
impulse += sim.read_force(0).astype(np.float64) * batch
done += batch
step_seconds += time.monotonic() - timer
macro = sim.get_macroscopic()
rho, ux, uy = [
np.asarray(macro[k], np.float64).reshape(p["ny"], p["nx"])
for k in ("rho", "ux", "uy")
]
healthy = bool(
all(np.isfinite(v[fluid]).all() for v in (rho, ux, uy))
and np.isfinite(impulse).all()
and (rho[fluid] > 0).all()
)
if not healthy:
break
den = p["mean_speed"] ** 2 * p["diameter"] / 2
row = dict(
step=sim.stepper.step_count,
tstar=sim.stepper.step_count * p["mean_speed"] / p["diameter"],
tstar_force=(begin + n / 2) * p["mean_speed"] / p["diameter"],
cd=float(impulse[0] / n / den),
cl=float(impulse[1] / n / den),
mass=float(rho[fluid].sum()),
rho_min=float(rho[fluid].min()),
rho_max=float(rho[fluid].max()),
max_mach=float(
np.sqrt(3 * (ux[fluid] ** 2 + uy[fluid] ** 2)).max()
),
flux_in=float(np.sum(rho[1:-1, 2] * ux[1:-1, 2])),
flux_out=float(np.sum(rho[1:-1, -3] * ux[1:-1, -3])),
**pressure_probes(rho, fluid, p),
)
result["history"].append(row)
if (
row["max_mach"] > 0.2
or row["rho_min"] < 0.5
or row["rho_max"] > 1.5
):
healthy = False
result["safety_stop"] = "Mach > .2 or density outside [.5,1.5]"
if writer is None:
writer = csv.DictWriter(f, fieldnames=row)
writer.writeheader()
writer.writerow(row)
if not healthy:
f.flush()
break
if len(result["history"]) == 1:
print(
"READY "
+ json.dumps(
dict(
pid=os.getpid(),
step=row["step"],
tstar=row["tstar"],
all_runtime_and_macro_paths_exercised=True,
)
),
flush=True,
)
if len(result["history"]) % 100 == 0:
f.flush()
print(json.dumps(row), flush=True)
result.update(
steps=sim.stepper.step_count,
step_seconds=step_seconds,
wall_seconds=time.monotonic() - started,
)
result["measurement_seconds"] = max(
0.0,
result["wall_seconds"] - result["startup_seconds"] - step_seconds,
)
result["mlups"] = (
p["nx"] * p["ny"] * result["steps"] / max(step_seconds, 1e-30) / 1e6
)
result.update(assess(result["history"]))
if not healthy:
result["periodic"] = False
result["status"] = (
"invalid_state"
if not healthy
else (
"pilot_complete"
if a.mode == "pilot"
else (
"periodic"
if result["periodic"]
else "incomplete_periodicity"
)
)
)
if healthy:
np.savez_compressed(
a.output / "final_field.npz", rho=rho, ux=ux, uy=uy, fluid=fluid
)
result["outlet_residual"] = outlet_diagnostic(rho, ux, uy, p)
if result["cycles"]:
result["reference_comparison"] = compare(
result["cycles"][-1], reference
)
result["case_reference_pass"] = bool(
healthy
and result["periodic"]
and result.get("reference_comparison", {}).get(
"reference_agreement", False
)
)
(a.output / "summary.json").write_text(
json.dumps(result, indent=2, allow_nan=False)
)
print(
json.dumps({k: v for k, v in result.items() if k not in ("history", "cycles")}),
flush=True,
)
return (
1 if not healthy else (2 if a.mode == "full" and not result["periodic"] else 0)
)
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--diameter", type=int, choices=[20, 40, 80], default=20)
parser.add_argument("--speed", type=float, choices=[0.01, 0.02], default=0.01)
parser.add_argument("--mode", choices=["pilot", "full"], default="pilot")
parser.add_argument("--output", type=Path, required=True)
a = parser.parse_args()
if not os.environ.get("CUDA_VISIBLE_DEVICES"):
raise SystemExit("Select physical GPU with CUDA_VISIBLE_DEVICES")
return run(a)
if __name__ == "__main__":
raise SystemExit(main())
+558
View File
@@ -0,0 +1,558 @@
#!/usr/bin/env python3
"""Bounded jet-on periodic cylinder: actual AMR Jet API vs blocked validation adapter."""
import argparse
import hashlib
import json
import os
from pathlib import Path
import sys
import time
import numpy as np
PIN = "d79e3f6f0a424f5bcc3a1a6d610313e49daf97a2"
HERE = Path(__file__).resolve().parent
CX = np.array([0, 1, -1, 0, 0, 1, -1, 1, -1])
CY = np.array([0, 0, 0, 1, -1, 1, -1, -1, 1])
W = np.array([4 / 9] + [1 / 9] * 4 + [1 / 36] * 4)
def case(scale=1, re=100, steps=800):
u = 0.03 / scale
d = 24.084 * scale
return dict(
scale=scale,
re_d=re,
beta=0.1,
nx=192 * scale,
ny=128 * scale,
diameter=d,
center=[96 * scale, 64 * scale],
u=u,
jet_speed=0.1 * u,
nu=u * d / re,
tau_root=0.5 + 3 * u * d / re,
tau_fine=0.5 + 6 * u * d / re,
bounds=[48 * scale, 32 * scale, 144 * scale, 96 * scale],
contours=[
[72 * scale, 44 * scale, 120 * scale, 84 * scale],
[64 * scale, 40 * scale, 128 * scale, 88 * scale],
],
steps=steps * scale**2,
window=100 * scale**2,
dx_root=1 / scale,
dt_root=1 / scale**2,
fine_dx_root_units=0.5,
fine_dt_root_units=0.5,
mach=np.sqrt(3) * u,
final_t_star=steps * scale**2 * u / d,
)
def state(shape, u):
cu = CX[:, None, None] * u
return (
np.broadcast_to(
W[:, None, None] * (1 + 3 * cu + 4.5 * cu**2 - 1.5 * u * u), shape
)
.astype(np.float32)
.copy()
)
def moments(g):
f = np.asarray(g, np.float64)
rho = f.sum(axis=0)
momentum = np.stack([np.einsum("q,qyx->yx", v, f) for v in (CX, CY)])
return rho, momentum, momentum / rho
def cv(g, fluid, origin, rectangle, nu, h=0.5):
"""Cell-centred inventory and midpoint face quadrature, physical root units.
Cauchy stress from centred velocity gradients, NOT postcollision non-equilibrium.
Rectangle faces lie on cell edges; fluid mask excludes the staircase cylinder.
"""
rho, mom, vel = moments(g)
x0, y0, x1, y1 = np.rint((np.array(rectangle) - np.tile(origin, 2)) / h).astype(int)
np.testing.assert_allclose(
np.array([x0, y0, x1, y1]) * h + np.tile(origin, 2), rectangle, atol=1e-12
)
ny, nx = fluid.shape
if not (2 <= x0 < x1 <= nx - 2 and 2 <= y0 < y1 <= ny - 2):
raise ValueError("Contour needs two fluid-node stencil layers")
# Every face and its velocity-gradient stencil must be active fluid.
for ys, xs in [
(slice(y0 - 2, y1 + 2), slice(x0 - 2, x0 + 2)),
(slice(y0 - 2, y1 + 2), slice(x1 - 2, x1 + 2)),
(slice(y0 - 2, y0 + 2), slice(x0 - 2, x1 + 2)),
(slice(y1 - 2, y1 + 2), slice(x0 - 2, x1 + 2)),
]:
if not fluid[ys, xs].all():
raise ValueError("Contour stencil intersects solid/inactive region")
use = fluid[y0:y1, x0:x1]
inventory = (
np.array(
[
rho[y0:y1, x0:x1][use].sum(),
mom[0, y0:y1, x0:x1][use].sum(),
mom[1, y0:y1, x0:x1][use].sum(),
]
)
* h
* h
)
duy, dux = np.gradient(vel[0], h)
dvy, dvx = np.gradient(vel[1], h)
mu = rho * nu
# Subtract constant reference pressure, whose closed-contour integral is zero.
p = (rho - 1) / 3
tensor = np.array(
[
[
rho * vel[0] ** 2 + p - 2 * mu * dux,
rho * vel[0] * vel[1] - mu * (duy + dvx),
],
[
rho * vel[0] * vel[1] - mu * (duy + dvx),
rho * vel[1] ** 2 + p - 2 * mu * dvy,
],
]
)
transport = np.concatenate(
[mom[None], tensor], axis=0
) # [mass,Px,Py] x normal axis
flux = np.zeros(3)
for i, sign in [(x0, -1), (x1, 1)]:
flux += (
sign
* 0.5
* (transport[:, 0, y0:y1, i - 1] + transport[:, 0, y0:y1, i]).sum(axis=1)
* h
)
for j, sign in [(y0, -1), (y1, 1)]:
flux += (
sign
* 0.5
* (transport[:, 1, j - 1, x0:x1] + transport[:, 1, j, x0:x1]).sum(axis=1)
* h
)
if not np.isfinite(np.r_[inventory, flux]).all():
raise RuntimeError("Nonfinite CV quantities")
return inventory, flux
def aperture_probe(fluid, origin, center, radius, h=0.5):
"""Independent true-arc quadrature with nearest active-fluid sampling.
This is a first-order near-wall diagnostic, not certified wall reconstruction.
No target velocity or raw-population ledger is used to measure the flux.
"""
z, w = np.polynomial.legendre.leggauss(64)
result = []
for angle in (np.pi / 2, 3 * np.pi / 2):
theta = angle + np.pi / 12 * z
er = np.column_stack((np.cos(theta), np.sin(theta)))
points = np.asarray(center) + radius * er
selected = []
distance = []
for point in points:
ix, iy = np.rint((point - np.asarray(origin)) / h - 0.5).astype(int)
candidates = [
(y, x)
for y in range(iy - 3, iy + 4)
for x in range(ix - 3, ix + 4)
if 0 <= y < fluid.shape[0] and 0 <= x < fluid.shape[1] and fluid[y, x]
]
if not candidates:
raise ValueError("No active near-wall sampling node")
yy, xx = np.array(candidates).T
dist = np.linalg.norm(
np.column_stack((xx, yy)) * h + np.asarray(origin) + h / 2 - point,
axis=1,
)
k = np.argmin(dist)
selected.append(candidates[k])
distance.append(dist[k])
yy, xx = np.array(selected).T
result.append((yy, xx, er, w * radius * np.pi / 12, float(max(distance))))
return result
def measured_aperture_flux(g, probe):
_, momentum, _ = moments(g)
return np.array(
[
np.sum(np.einsum("in,ni->n", momentum[:, y, x], normal) * ds)
for y, x, normal, ds, _ in probe
]
)
def verify(repo):
manifest = json.loads(
(HERE / "data/jet_circle_cv/source_manifest.json").read_text()
)
if manifest["revision"] != PIN:
raise RuntimeError("Wrong pinned revision")
for path, sha in manifest["files"].items():
if hashlib.sha256((repo / path).read_bytes()).hexdigest() != sha:
raise RuntimeError("Source hash mismatch: " + path)
return manifest
def run_case(c, ctx, output):
from CelerisLab.body.geometry.circle import CircleGeometry
from CelerisLab.body.objects import SimObject
from CelerisLab.body.jets import Jet, Sector, Radial
from CelerisLab.config import LBMConfig
from CelerisLab.lbm.blocks import BlockLayout2D
from CelerisLab.lbm.block_stepper import PeriodicBlockStepper
from CelerisLab.lbm.refinement_stepper import FixedPatchStepper
from CelerisLab.lbm.descriptors import D2Q9_EX, D2Q9_EY
import pycuda.driver as cuda
np.testing.assert_array_equal(CX, D2Q9_EX)
np.testing.assert_array_equal(CY, D2Q9_EY)
nx, ny, u, nu = c["nx"], c["ny"], c["u"], c["nu"]
center = np.array(c["center"], float)
radius = c["diameter"] / 2
cfg = LBMConfig(
nx=nx,
ny=ny,
viscosity=nu,
velocity=0.0,
collision="SRT",
streaming="double_buffer",
store_precision="FP32",
ddf_shifting=False,
les_enabled=False,
)
fine_cfg = LBMConfig(
nx=2 * nx,
ny=2 * ny,
viscosity=2 * nu,
velocity=0.0,
collision="SRT",
streaming="double_buffer",
store_precision="FP32",
ddf_shifting=False,
les_enabled=False,
)
obj = SimObject(
0, CircleGeometry(*center, radius), center=tuple(center), radius=radius
)
fc = 2 * center - 0.5
fobj = SimObject(
0, CircleGeometry(*fc, 2 * radius), center=tuple(fc), radius=2 * radius
)
jets = [
Jet(
Sector(tuple(center), angle, np.pi / 12, radius + 0.1),
Radial(tuple(center), inward=False),
"uniform",
sign * c["jet_speed"],
)
for angle, sign in [(np.pi / 2, 1), (3 * np.pi / 2, -1)]
]
layout = BlockLayout2D.build(2 * ny, 2 * nx, 8, 8)
with (
PeriodicBlockStepper(fine_cfg, layout, context=ctx, objects=[fobj]) as uniform,
FixedPatchStepper(
cfg,
c["bounds"],
context=ctx,
base_block=(8, 8),
fine_block=(8, 8),
fine_objects=[obj],
jets=jets,
observe_raw_exchange=True,
) as amr,
):
b = amr._level_boundaries[1]
count = len(b.tables["cell"])
if np.any(b.tables["q"] <= 0.010001) or np.any(b.tables["fallback"]):
raise RuntimeError("Pilot requires unclamped q and no fallback")
links = fobj.geometry.build_cut_links(2 * nx, 2 * ny)
np.testing.assert_array_equal([v.dir for v in links], b.tables["dir"])
np.testing.assert_allclose(
[v.q for v in links], b.tables["q"], atol=2e-7, rtol=0
)
np.testing.assert_array_equal([v.fallback for v in links], b.tables["fallback"])
np.testing.assert_allclose(
np.array([[v.hit_x, v.hit_y] for v in links]) * 0.5 + 0.25,
b.host.curved_hits,
atol=2e-12,
rtol=0,
)
# Same pre-existing optional-input adapter as test_raw_exchange_gpu.py.
velocity = uniform._upload(b.tables["surface_velocity"])
rx = uniform._upload(b.tables["rx"])
ry = uniform._upload(b.tables["ry"])
raw = uniform._allocate(count * 16)
original = uniform._curved
samples = []
def boundary(*args, **kw):
original(*args[:7], velocity, rx, ry, raw, **kw)
uniform._stream.synchronize()
a = np.empty((count, 4), np.float32)
cuda.memcpy_dtoh(a, raw)
samples.append(a)
uniform._curved = boundary
def initialize():
uniform.initialize(state((9, 2 * ny, 2 * nx), u))
amr.initialize(*(state(l.shape, u) for l in amr._levels))
initialize()
# Compile/launch all stepping and diagnostic paths, then reset physical time.
amr.step()
uniform.step(2)
uniform.download()
amr.download()
amr.read_raw_exchange(0)
initialize()
samples.clear()
jet_id = b.jet_cache[1]
masks = {
"wall": jet_id < 0,
"aperture": jet_id >= 0,
"total": np.ones(count, bool),
"blowing": jet_id == 0,
"suction": jet_id == 1,
}
if not masks["blowing"].any() or not masks["suction"].any():
raise RuntimeError("Both apertures must resolve cut links")
fluid_u = ~uniform.boundary["solid"]
fluid_a = amr._leaf[1]
origin_a = np.array(amr.patch.fine_origin)
probes = {
"uniform_adapter": aperture_probe(fluid_u, (0.0, 0.0), center, radius),
"amr_api": aperture_probe(fluid_a, origin_a, center, radius),
}
def capture():
ug = uniform.download()
ag = amr.download()[1]
data = {}
aperture = {}
for name, g, fluid, origin in [
("uniform_adapter", ug, fluid_u, (0.0, 0.0)),
("amr_api", ag, fluid_a, origin_a),
]:
data[name] = [cv(g, fluid, origin, r, nu) for r in c["contours"]]
aperture[name] = measured_aperture_flux(g, probes[name])
rho, _, _ = moments(g)
if not np.isfinite(rho[fluid]).all() or rho[fluid].min() <= 0:
raise RuntimeError("Nonfinite/nonpositive fluid density")
ox, oy = np.rint(2 * origin_a).astype(int)
ay, ax = fluid_a.shape
_, _, uv = moments(ug[:, oy : oy + ay, ox : ox + ax])
_, _, av = moments(ag)
error = float(
np.linalg.norm((av - uv)[:, fluid_a])
/ max(np.linalg.norm(uv[:, fluid_a]), 1e-30)
)
return data, error, aperture
previous, _, previous_aperture = capture()
initial = previous
integrated = {name: np.zeros((2, 3)) for name in previous}
integrated_aperture = {name: np.zeros(2) for name in previous}
accumulated = {name: {k: np.zeros(4) for k in masks} for name in previous}
rows = []
start = time.monotonic()
print(
json.dumps(
dict(
status="READY",
case=c,
links=count,
actual_amr_jet_api=True,
uniform_reference="private optional-input validation adapter",
q_range=[float(b.tables["q"].min()), float(b.tables["q"].max())],
fallback_count=int(np.count_nonzero(b.tables["fallback"])),
)
),
flush=True,
)
for step in range(1, c["steps"] + 1):
if time.monotonic() - start > 1800:
raise RuntimeError(
"1800-second post-READY pilot budget exceeded; partial report retained"
)
samples.clear()
amr.step()
uniform.step(2)
if len(samples) != 2:
raise RuntimeError("Uniform interval must contain two fine hops")
ur = sum(a.astype(float) * [0.25, 0.25, 0.125, 0.25] for a in samples)
ar = np.empty((count, 4), np.float64)
with ctx.activate():
cuda.memcpy_dtoh(ar, b.interval_integral)
obs = amr.read_raw_exchange(0)
if obs["time_start"] != step - 1 or obs["time_end"] != step:
raise RuntimeError("Raw interval time mismatch")
np.testing.assert_allclose(
ar.sum(axis=0)[:2], obs["total"]["impulse"], atol=1e-13, rtol=1e-13
)
current, error, current_aperture = capture()
for name, rr in [("uniform_adapter", ur), ("amr_api", ar)]:
for k, mask in masks.items():
accumulated[name][k] += rr[mask].sum(axis=0)
integrated_aperture[name] += 0.5 * (
previous_aperture[name] + current_aperture[name]
)
integrated[name] += 0.5 * (
np.array([x[1] for x in previous[name]])
+ np.array([x[1] for x in current[name]])
)
previous = current
previous_aperture = current_aperture
if step % c["window"] == 0 or step == c["steps"]:
end0 = rows[-1]["step_end"] if rows else 0
duration = step - end0
row = dict(
step_start=end0,
step_end=step,
t_star=step * u / c["diameter"],
fine_velocity_relative_l2=error,
elapsed_seconds=time.monotonic() - start,
measurements={},
)
for name in current:
delta = np.array([x[0] for x in current[name]]) - np.array(
[x[0] for x in initial[name]]
)
mass_cv = delta[:, 0] + integrated[name][:, 0]
reaction_cv = -delta[:, 1:] - integrated[name][:, 1:]
ledger = accumulated[name]
total = ledger["total"]
normal = 0.5 * u * u * c["diameter"]
row["measurements"][name] = dict(
raw_interval={k: v.tolist() for k, v in ledger.items()},
cv_mass_into_fluid=mass_cv.tolist(),
cv_reaction_impulse=reaction_cv.tolist(),
mass_balance_residual=(mass_cv - total[3]).tolist(),
reaction_balance_residual=(reaction_cv - total[:2]).tolist(),
reaction_residual_coefficient=(
(reaction_cv - total[:2]) / duration / normal
).tolist(),
raw_reaction_coefficient=(
total[:2] / duration / normal
).tolist(),
cv_reaction_coefficient=(
reaction_cv / duration / normal
).tolist(),
contour_reaction_spread_coefficient=(
(reaction_cv[1] - reaction_cv[0]) / duration / normal
).tolist(),
measured_arc_mass_rates=(
integrated_aperture[name] / duration
).tolist(),
arc_max_sample_distance=[v[-1] for v in probes[name]],
nominal_volume_fluxes=[
2 * np.pi / 12 * radius * c["jet_speed"],
-2 * np.pi / 12 * radius * c["jet_speed"],
],
aperture_mass_rates={
k: float(ledger[k][3] / duration)
for k in ("blowing", "suction", "wall", "total")
},
)
integrated[name].fill(0)
integrated_aperture[name].fill(0)
for v in accumulated[name].values():
v.fill(0)
rows.append(row)
initial = current
payload = dict(
case=c,
source_revision=PIN,
status="PILOT_DIAGNOSTIC_ONLY",
windows=rows,
physics_validation_pass=False,
precision="FP32 evolution, FP64 host diagnostics",
)
(output / f"re{c['re_d']}_s{c['scale']}.json").write_text(
json.dumps(payload, indent=2) + "\n"
)
print(
json.dumps(
dict(
status="PROGRESS",
step=step,
steps=c["steps"],
elapsed_seconds=row["elapsed_seconds"],
fine_velocity_relative_l2=error,
)
),
flush=True,
)
return payload
def main():
p = argparse.ArgumentParser(description=__doc__)
p.add_argument("--repo", type=Path, required=True)
p.add_argument("--output", type=Path, required=True)
p.add_argument("--scale", type=int, choices=[1, 2, 4], default=1)
p.add_argument("--re", type=int, choices=[100, 400], default=100)
p.add_argument(
"--steps", type=int, default=800, help="Base s=1 steps; scales as s squared"
)
p.add_argument("--allow-re400", action="store_true")
p.add_argument("--plan", action="store_true")
a = p.parse_args()
if a.steps <= 0 or a.steps > 3200:
p.error("steps must be 1..3200")
if a.re == 400 and not a.allow_re400:
p.error("Re400 requires explicit conditional-campaign opt-in")
c = case(a.scale, a.re, a.steps)
plan = dict(
case=c,
required_revision=PIN,
required_free_device_mib=1024,
estimated_peak_device_mib=128 * a.scale**2,
estimated_output_mib=2,
max_steps=c["steps"],
gpu_launch_owner="root; this runner does not select or reserve a GPU",
)
if a.plan:
print(json.dumps(plan, indent=2))
return
if (
not os.environ.get("CUDA_VISIBLE_DEVICES")
or "," in os.environ["CUDA_VISIBLE_DEVICES"]
):
p.error("Root must select one verified idle GPU via CUDA_VISIBLE_DEVICES")
repo = a.repo.resolve()
verify(repo)
sys.path.insert(0, str(repo / "src"))
import CelerisLab
if Path(CelerisLab.__file__).resolve() != repo / "src/CelerisLab/__init__.py":
raise RuntimeError("Wrong source import")
from CelerisLab.cuda.context import CudaContext
import pycuda.driver as cuda
a.output.mkdir(parents=True, exist_ok=False)
(a.output / "plan.json").write_text(json.dumps(plan, indent=2) + "\n")
with CudaContext(0) as ctx:
with ctx.activate():
free, total = cuda.mem_get_info()
if free < max(1024, 2 * plan["estimated_peak_device_mib"]) * 1024**2:
raise RuntimeError("Insufficient free device memory")
run_case(c, ctx, a.output)
print(
json.dumps(
dict(status="COMPLETE_DIAGNOSTIC_ONLY", physics_validation_pass=False)
),
flush=True,
)
if __name__ == "__main__":
main()