diff --git a/docs/lifecycle.md b/docs/lifecycle.md index 17940f8..bbb8ea8 100644 --- a/docs/lifecycle.md +++ b/docs/lifecycle.md @@ -6,8 +6,8 @@ it is; it adds no framework. Line references are to candidate 0f8fdbc3. | Class | Contents | How it changes | Owner rule | |---|---|---|---| | 1. Module and its globals | Compile-time (visible to the compiler, fixed per module): `config.h` macros incl. `NX`/`NY`, `COLLISION_MODEL`, `BLOCK_ROOT_BC`, `WALL_*`. Runtime per-module globals (not visible to the compiler; written by host code): `__constant__` `d_params`, `d_w`, `d_cx`, `d_cy`, `d_block_channel`, `d_block_wall`; plus bound kernel handles | Globals are written once by the owner right after `load_module`. Uniform grid: `Simulation._recompile` (construction, body-topology sync) loads a new module and re-uploads; `update_runtime_params` rewrites `d_params` (omega, force, n_objects) between runs. Steppers: never rewritten after construction, so they are immutable for the instance's lifetime although they are runtime values. The blocked slot/reservoir root reads its slot from the `d_block_wall` constant (the former dense route used `WALL_*` macros; both were bit-identical in the parity gates). | Every owner loads its own module (`compiler.load_module` = `module_from_file`, no cache). A loaded module is never shared between live owners: globals are per module, so sharing would share mutable `d_params`. | -| 2. Geometry, layout, tables | flags, curved-link tables, block layouts and halo pairs, transfer (explode/coalesce) tables, block kinds | Immutable for the life of an instance. Uniform grid: rebuilt by body sync. `FixedPatchStepper`: a geometry change is a new instance (`remap_finest`), committed only after a successful transfer. | No in-place table edits. | -| 3. Controls and dependent boundary columns | Jet amplitude -> per-link `surface_velocity`; body actions (uniform grid) | Between steps, without a table or module rebuild (`set_jet_amplitude`). | Input (index, level, amplitude) is validated before any device write; invalid input changes nothing and the instance stays usable. The host copy commits only after the upload returned. Any error after validation leaves the device column undefined and fails the instance: `FixedPatchStepper` status `failed`, `Simulation._failed`; every later device-touching public method (`Simulation`: `stream`, `set_jet_amplitude`, `initialize`, `sync_bodies`, `run`/`step`, `read_surface_exchange`, `read_body(s)`, `get_macroscopic`, `get_ddf`, `update_runtime_params`, `snapshot`, `restore`, save/load checkpoint) raises; cached host readers and host-side staging (e.g. `get_flags`, `add_body`/`remove_body` before a sync) do not touch the device and stay callable. `close()` still releases resources and stays retryable. | +| 2. Geometry, layout, tables | flags, curved-link tables, block layouts and halo pairs, transfer (explode/coalesce) tables, block kinds | Immutable for the life of an instance, except translating circles on the uniform grid: their swept-band flags and cut links are rewritten on the device every advancing step (`MovingCircleCells`/`MovingCircleLinks`, docs/moving_bodies.md); the host flag mirror is then stale for those cells. Uniform grid otherwise: rebuilt by body sync. `FixedPatchStepper`: a geometry change is a new instance (`remap_finest`), committed only after a successful transfer. | No in-place table edits. | +| 3. Controls and dependent boundary columns | Jet amplitude -> per-link `surface_velocity`; body actions (uniform grid) | Between steps, without a table or module rebuild (`set_jet_amplitude`). | Input (index, level, amplitude) is validated before any device write; invalid input changes nothing and the instance stays usable. The host copy commits only after the upload returned. Any error after validation leaves the device column undefined and fails the instance: `FixedPatchStepper` status `failed`, `Simulation._failed`; every later device-touching public method (`Simulation`: `stream`, `set_jet_amplitude`, `initialize`, `sync_bodies`, `run`/`step`, `read_surface_exchange`, `read_body(s)`, `get_macroscopic`, `get_ddf`, `update_runtime_params`, `snapshot`, `restore`, save/load checkpoint) raises; cached host readers and host-side staging (e.g. `get_flags` without translating circles, `add_body`/`remove_body` before a sync) do not touch the device and stay callable; with translating circles `get_flags` reads the device flags back and refuses after a failure. Translating-circle targets follow the same rule: `set_body` validates before mutation, the applied offset commits only after a successful synchronized step, and a failed moving step fails the instance (docs/moving_bodies.md). `close()` still releases resources and stays retryable. | | 4. Per-step state | population double buffers and their parity, raw-exchange ledgers, observations | Device-only during `step(n)` / `run(n)`; buffer swaps are host-handle swaps. | Valid on the host only after the single wait at the end of the call. | Storage routes are separate concerns: diff --git a/docs/supported_settings.md b/docs/supported_settings.md index 60bbe31..ce37f73 100644 --- a/docs/supported_settings.md +++ b/docs/supported_settings.md @@ -82,6 +82,13 @@ Tests: `tests/unit/test_jets.py`, `test_jets_review.py`, `test_shifted_jets.py`, - DFG 2D-1 Re 20 (V4, SRT, t* 60, pressure-NEQ outlet and local Zou-He inlet): refined and uniform runs were NOT_STEADY under the frozen steady rule, so no interval verdict was given. +## Moving circles (dense `Simulation`, uniform grid only) + +Translating circles with optional spin are supported on the dense double +buffer D2Q9 FP32 path only (not on `FixedPatchStepper` or refined levels). +API, supported/rejected modes and limits: `docs/moving_bodies.md`; stage-1 +evidence: `docs/validation/moving_bodies_stage1.md`. + ## Not supported in this release 3D, dynamic/automatic AMR (tagging, time-varying hierarchies beyond `remap_finest`), diff --git a/docs/validation/moving_bodies_stage1.md b/docs/validation/moving_bodies_stage1.md index 999ab76..285097f 100644 --- a/docs/validation/moving_bodies_stage1.md +++ b/docs/validation/moving_bodies_stage1.md @@ -29,8 +29,8 @@ and the Mac evidence directory (not in git). ## Free transverse VIV (Kolahdouz §4.1.1 setup, Qin §4.5 target) -- Predeclaration: `docs/validation/leontini_viv_predeclare.md` (agreed at - `1abfe954`); run at `fb507aa1`, single case, D = 40, 75 D × 60 D. +- Predeclaration: `docs/validation/viv_re150_predeclare.md` (agreed at + `1abfe954` under its former name `leontini_viv_predeclare.md`); run at `fb507aa1`, single case, D = 40, 75 D × 60 D. - Result: status `scored`, 336,384 steps (t U/D = 336.4); A_max = .5464 D vs .520 D (+5.1 %, inside the declared ±10 % band); mean half peak-to-peak .5448 D; f/f_n = .955; block drifts .78/.29/.18 %, last-five spreads diff --git a/docs/validation/leontini_viv_predeclare.md b/docs/validation/viv_re150_predeclare.md similarity index 97% rename from docs/validation/leontini_viv_predeclare.md rename to docs/validation/viv_re150_predeclare.md index 379d0be..59e47cf 100644 --- a/docs/validation/leontini_viv_predeclare.md +++ b/docs/validation/viv_re150_predeclare.md @@ -1,4 +1,6 @@ -# Single-cylinder free transverse VIV — PREDECLARATION (agreed at 1abfe954) +# Single-cylinder free transverse VIV, Re 150 (Kolahdouz/Qin) — PREDECLARATION (agreed at 1abfe954) + +(File renamed from `leontini_viv_predeclare.md`; the Leontini Re 200 case is only the queued second case below.) Stage goal (Yanqi via TRON): prescribed Dütsch case + one VIV case both "reasonably close" to a predeclared reference = development-stage success, then diff --git a/research/viv_re150/run.py b/research/viv_re150/run.py index 629de5f..4e96a63 100644 --- a/research/viv_re150/run.py +++ b/research/viv_re150/run.py @@ -1,4 +1,4 @@ -"""One-shot predeclared VIV run (docs/validation/leontini_viv_predeclare.md). +"""One-shot predeclared VIV run (docs/validation/viv_re150_predeclare.md). Re 150, U* = 5, m/(rho D^2) = 2, zeta = 0, D = 40 on a 75 D x 60 D native channel (uniform inlet, NEQ outlet, free-slip y walls), cylinder at diff --git a/research/viv_re150/viv.py b/research/viv_re150/viv.py index c8b8d01..3f65972 100644 --- a/research/viv_re150/viv.py +++ b/research/viv_re150/viv.py @@ -1,6 +1,6 @@ """Pure logic of the predeclared Re 150 free transverse VIV case (stdlib only). -Contract: docs/validation/leontini_viv_predeclare.md. Oscillator update with a +Contract: docs/validation/viv_re150_predeclare.md. Oscillator update with a constant force over one lattice step, response-cycle tracking with fixed y = 0 crossings, eligibility, 10-cycle blocks, settling/scoring with deterministic restart, and the conditional seed. diff --git a/tests/unit/test_duetsch_protocol.py b/tests/unit/test_duetsch_protocol.py index cdde4fe..d70b924 100644 --- a/tests/unit/test_duetsch_protocol.py +++ b/tests/unit/test_duetsch_protocol.py @@ -11,6 +11,20 @@ _spec = importlib.util.spec_from_file_location( p = importlib.util.module_from_spec(_spec) _spec.loader.exec_module(p) +def _load(rel, name): + """Load a research driver module under a unique name (no bare ``import run``).""" + import sys + path = Path(__file__).resolve().parents[2] / rel + sys.path.insert(0, str(path.parent)) # for the driver's own sibling imports + try: + spec = importlib.util.spec_from_file_location(name, path) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + finally: + sys.path.remove(str(path.parent)) + return mod + + T = 2520 @@ -93,12 +107,7 @@ def test_exact_point_inside_body_is_missing_even_with_fluid_corners(): def test_generated_config_passes_the_real_loader(tmp_path): import json - import sys - sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "research/duetsch_re100_kc5")) - try: - import run - finally: - sys.path.pop(0) + run = _load("research/duetsch_re100_kc5/run.py", "duetsch_run") from CelerisLab.config import load_lbm_config cfg_dict, u_max, n = run.config(20, 10.0, 2520) path = tmp_path / "config_lbm.json" @@ -111,13 +120,7 @@ def test_generated_config_passes_the_real_loader(tmp_path): def _analyze(): - import sys - sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "research/duetsch_re100_kc5")) - try: - import analyze - finally: - sys.path.pop(0) - return analyze + return _load("research/duetsch_re100_kc5/analyze.py", "duetsch_analyze") def test_hf_energy_is_parseval_weighted(): diff --git a/tests/unit/test_viv_logic.py b/tests/unit/test_viv_logic.py index 0b4feb5..dfb5849 100644 --- a/tests/unit/test_viv_logic.py +++ b/tests/unit/test_viv_logic.py @@ -8,6 +8,20 @@ _spec = importlib.util.spec_from_file_location( viv = importlib.util.module_from_spec(_spec) _spec.loader.exec_module(viv) +def _load(rel, name): + """Load a research driver module under a unique name (no bare ``import run``).""" + import sys + path = Path(__file__).resolve().parents[2] / rel + sys.path.insert(0, str(path.parent)) # for the driver's own sibling imports + try: + spec = importlib.util.spec_from_file_location(name, path) + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) + finally: + sys.path.remove(str(path.parent)) + return mod + + D, U = 40, 0.04 @@ -72,12 +86,7 @@ def test_seed_reset_discards_incomplete_cycle_and_proof(): def test_viv_config_passes_the_real_loader(tmp_path): import json - import sys - sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "research/viv_re150")) - try: - import run - finally: - sys.path.pop(0) + run = _load("research/viv_re150/run.py", "viv_run") from CelerisLab.config import load_lbm_config cfg_dict, nx, ny = run.config() path = tmp_path / "config_lbm.json"