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
|
||||
|
||||
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
|
||||
unknown: the circle is marked failed, never reports applied state again,
|
||||
|
||||
@@ -21,7 +21,7 @@ class LBMStepper:
|
||||
|
||||
self._esopull = (cfg.streaming == "esopull")
|
||||
# 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.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.sensor_fn = module.get_function("SensorKernel")
|
||||
self.force_region_fn = module.get_function("ForceRegionKernel")
|
||||
self.moving_fn = None if self._esopull else module.get_function("MovingCircleLinks")
|
||||
self.moving_cells_fn = None if self._esopull else module.get_function("MovingCircleCells")
|
||||
# Moving-circle kernels exist only in D2Q9 double-buffer modules.
|
||||
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
|
||||
self.block = (tpb, 1, 1)
|
||||
|
||||
@@ -242,7 +242,8 @@ class Simulation:
|
||||
self._require_usable()
|
||||
tables = self.stepper.moving
|
||||
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()
|
||||
return tables.download_diagnostics()
|
||||
|
||||
@@ -785,6 +786,7 @@ class Simulation:
|
||||
(docs/moving_bodies.md 4a, 5). Any failure leaves the Simulation unusable."""
|
||||
tables = self.stepper.moving
|
||||
circles = [(obj.motion, obj.obj_id) for obj in self._moving_bodies()]
|
||||
pending = any(motion.pending is not None for motion, _ in circles)
|
||||
try:
|
||||
advancing = tables.upload_records(circles, stream)
|
||||
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")]
|
||||
if np.any(no_donor > 0):
|
||||
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:
|
||||
motion.commit()
|
||||
tables.upload_records(circles, stream) # first hold: old = new = applied, u = 0
|
||||
stream.synchronize()
|
||||
if advancing:
|
||||
tables.upload_records(circles, stream) # first hold: old = new = applied, u = 0
|
||||
stream.synchronize()
|
||||
self._attach_moving()
|
||||
except BaseException as exc:
|
||||
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)
|
||||
|
||||
|
||||
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",
|
||||
"hold_sync"))
|
||||
def test_failed_advance_fails_simulation(monkeypatch, failure):
|
||||
|
||||
Reference in New Issue
Block a user