Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d5b13d7f31 | ||
|
|
353a7b03c4 | ||
|
|
0d6df6e6b8 |
@@ -99,7 +99,12 @@ class MovingCircle:
|
|||||||
return self.centre(self.applied), self.centre(offset), velocity
|
return self.centre(self.applied), self.centre(offset), velocity
|
||||||
|
|
||||||
def advance(self, upload: Callable, step: Callable, synchronize: Callable) -> None:
|
def advance(self, upload: Callable, step: Callable, synchronize: Callable) -> None:
|
||||||
"""Upload the record, run one step and commit only after a successful sync.
|
"""Single-circle transaction helper (unit tests): upload, step, sync, commit.
|
||||||
|
|
||||||
|
Not the production path: ``Simulation._run_moving`` batches all circles,
|
||||||
|
adds the ``no_donor`` gate and uses ``commit``/``fail`` directly.
|
||||||
|
|
||||||
|
Upload the record, run one step and commit only after a successful sync.
|
||||||
|
|
||||||
Any exception after the upload is attempted leaves device geometry
|
Any exception after the upload is attempted leaves device geometry
|
||||||
unknown: the circle is marked failed, never reports applied state again,
|
unknown: the circle is marked failed, never reports applied state again,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class LBMStepper:
|
|||||||
|
|
||||||
self._esopull = (cfg.streaming == "esopull")
|
self._esopull = (cfg.streaming == "esopull")
|
||||||
# Set by the owner when a step would be wrong (translating circles
|
# Set by the owner when a step would be wrong (translating circles
|
||||||
# excluded from the packed links before the moving kernels exist).
|
# without attached moving tables, a pending target, or a failed step).
|
||||||
self.blocked: str | None = None
|
self.blocked: str | None = None
|
||||||
self.moving = None # MovingTables attached by the owner (translating circles)
|
self.moving = None # MovingTables attached by the owner (translating circles)
|
||||||
|
|
||||||
@@ -39,8 +39,10 @@ class LBMStepper:
|
|||||||
self.apply_esopull_curved_fn = module.get_function("ApplyEsoPullCurvedKernel")
|
self.apply_esopull_curved_fn = module.get_function("ApplyEsoPullCurvedKernel")
|
||||||
self.sensor_fn = module.get_function("SensorKernel")
|
self.sensor_fn = module.get_function("SensorKernel")
|
||||||
self.force_region_fn = module.get_function("ForceRegionKernel")
|
self.force_region_fn = module.get_function("ForceRegionKernel")
|
||||||
self.moving_fn = None if self._esopull else module.get_function("MovingCircleLinks")
|
# Moving-circle kernels exist only in D2Q9 double-buffer modules.
|
||||||
self.moving_cells_fn = None if self._esopull else module.get_function("MovingCircleCells")
|
moving = not self._esopull and cfg.is_d2q9
|
||||||
|
self.moving_fn = module.get_function("MovingCircleLinks") if moving else None
|
||||||
|
self.moving_cells_fn = module.get_function("MovingCircleCells") if moving else None
|
||||||
|
|
||||||
tpb = cfg.threads_per_block
|
tpb = cfg.threads_per_block
|
||||||
self.block = (tpb, 1, 1)
|
self.block = (tpb, 1, 1)
|
||||||
|
|||||||
@@ -242,7 +242,8 @@ class Simulation:
|
|||||||
self._require_usable()
|
self._require_usable()
|
||||||
tables = self.stepper.moving
|
tables = self.stepper.moving
|
||||||
if tables is None:
|
if tables is None:
|
||||||
return np.zeros((0, 6), np.float32)
|
from .body.motion import MovingTables
|
||||||
|
return np.zeros((0, MovingTables.DIAG_FLOATS), np.float32)
|
||||||
self.stream.synchronize()
|
self.stream.synchronize()
|
||||||
return tables.download_diagnostics()
|
return tables.download_diagnostics()
|
||||||
|
|
||||||
@@ -785,6 +786,7 @@ class Simulation:
|
|||||||
(docs/moving_bodies.md 4a, 5). Any failure leaves the Simulation unusable."""
|
(docs/moving_bodies.md 4a, 5). Any failure leaves the Simulation unusable."""
|
||||||
tables = self.stepper.moving
|
tables = self.stepper.moving
|
||||||
circles = [(obj.motion, obj.obj_id) for obj in self._moving_bodies()]
|
circles = [(obj.motion, obj.obj_id) for obj in self._moving_bodies()]
|
||||||
|
pending = any(motion.pending is not None for motion, _ in circles)
|
||||||
try:
|
try:
|
||||||
advancing = tables.upload_records(circles, stream)
|
advancing = tables.upload_records(circles, stream)
|
||||||
self.stepper._step(steps, action_gpu=self.bodies.action_gpu,
|
self.stepper._step(steps, action_gpu=self.bodies.action_gpu,
|
||||||
@@ -797,10 +799,12 @@ class Simulation:
|
|||||||
no_donor = tables.download_diagnostics()[:, tables.DIAG.index("no_donor")]
|
no_donor = tables.download_diagnostics()[:, tables.DIAG.index("no_donor")]
|
||||||
if np.any(no_donor > 0):
|
if np.any(no_donor > 0):
|
||||||
raise RuntimeError("translating circle refill found no donor cell")
|
raise RuntimeError("translating circle refill found no donor cell")
|
||||||
|
if pending: # a target equal to the applied offset is consumed as a hold step
|
||||||
for motion, _ in circles:
|
for motion, _ in circles:
|
||||||
motion.commit()
|
motion.commit()
|
||||||
tables.upload_records(circles, stream) # first hold: old = new = applied, u = 0
|
if advancing:
|
||||||
stream.synchronize()
|
tables.upload_records(circles, stream) # first hold: old = new = applied, u = 0
|
||||||
|
stream.synchronize()
|
||||||
self._attach_moving()
|
self._attach_moving()
|
||||||
except BaseException as exc:
|
except BaseException as exc:
|
||||||
for motion, _ in circles: # device geometry/state unknown after any failure
|
for motion, _ in circles: # device geometry/state unknown after any failure
|
||||||
|
|||||||
@@ -297,6 +297,44 @@ def test_run_one_advances_and_commits_after_sync(monkeypatch):
|
|||||||
assert ("step", 3, False) in writes and motion.applied == (0., .25)
|
assert ("step", 3, False) in writes and motion.applied == (0., .25)
|
||||||
|
|
||||||
|
|
||||||
|
def test_zero_motion_target_is_consumed_by_run_one(monkeypatch):
|
||||||
|
sim, writes, motion = advancing_simulation(monkeypatch)
|
||||||
|
motion.set_target((0., 0.), (0., 0.)) # equal to applied: a hold target
|
||||||
|
sim._attach_moving()
|
||||||
|
assert sim.stepper.blocked == module.PENDING_BLOCKED
|
||||||
|
sim.run(1, stream=STREAM)
|
||||||
|
assert motion.pending is None and motion.applied == (0., 0.)
|
||||||
|
assert sim.stepper.blocked is None and ("step", 1, False) in writes
|
||||||
|
writes.clear()
|
||||||
|
sim.run(3, stream=STREAM) # no stale latch
|
||||||
|
assert ("step", 3, False) in writes
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("streaming,d2q9,bound", [("double_buffer", True, True),
|
||||||
|
("double_buffer", False, False),
|
||||||
|
("esopull", True, False)])
|
||||||
|
def test_moving_kernels_bound_only_for_d2q9_double_buffer(monkeypatch, streaming, d2q9, bound):
|
||||||
|
from CelerisLab.lbm import stepper as stepper_module
|
||||||
|
monkeypatch.setattr(stepper_module.cuda, "mem_alloc", lambda n: object())
|
||||||
|
moving = {"MovingCircleLinks", "MovingCircleCells"}
|
||||||
|
|
||||||
|
def get_function(name):
|
||||||
|
if name in moving and not (d2q9 and streaming == "double_buffer"):
|
||||||
|
raise KeyError(name) # absent from D3Q19 / EsoPull modules
|
||||||
|
return lambda *a, **k: None
|
||||||
|
cfg = SimpleNamespace(streaming=streaming, threads_per_block=128, is_d2q9=d2q9,
|
||||||
|
nx=16, ny=8, nz=4, nq=9 if d2q9 else 19)
|
||||||
|
stepper = stepper_module.LBMStepper(SimpleNamespace(), SimpleNamespace(get_function=get_function), cfg)
|
||||||
|
assert (stepper.moving_fn is not None) == bound and (stepper.moving_cells_fn is not None) == bound
|
||||||
|
|
||||||
|
|
||||||
|
def test_moving_diagnostics_empty_shape_matches_layout(monkeypatch):
|
||||||
|
from CelerisLab.body.motion import MovingTables
|
||||||
|
sim = make_simulation(monkeypatch)
|
||||||
|
sim._failed, sim.stepper = None, SimpleNamespace(moving=None)
|
||||||
|
assert sim.moving_diagnostics().shape == (0, MovingTables.DIAG_FLOATS)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("failure", ("upload", "first_sync", "no_donor", "hold_upload",
|
@pytest.mark.parametrize("failure", ("upload", "first_sync", "no_donor", "hold_upload",
|
||||||
"hold_sync"))
|
"hold_sync"))
|
||||||
def test_failed_advance_fails_simulation(monkeypatch, failure):
|
def test_failed_advance_fails_simulation(monkeypatch, failure):
|
||||||
|
|||||||
Reference in New Issue
Block a user