real-time.md
180 lines · skills/simpy/references/real-time.md at 5f2bc03
Real-time simulation
Reviewed 2026-10-01 against SimPy 4.1.2.
simpy.rt.RealtimeEnvironment retains the Event/Process API but delays event processing so simulation time tracks wall-clock time.
from simpy.rt import RealtimeEnvironment
env = RealtimeEnvironment(
initial_time=0,
factor=0.1,
strict=True,
)Raw lines 1 to 17Parameters
initial_time: starting simulation clock.factor: wall-clock seconds per simulation time unit; validate it as finite and strictly positive in model code (4.1.2 does not enforce this in its constructor).1.0: one simulation unit takes one second;0.1: one simulation unit takes 0.1 seconds;60.0: one simulation unit takes one minute.strict=True: raiseRuntimeErrorwhen processing falls more than the allotted factor behind.
strict=False suppresses deadline failure. It permits drift; it does not make an overloaded simulation accurately synchronized.
If initialization takes a long time between construction and the first run(), call env.sync() immediately before that first run. It resets the real-time origin without changing simulation time. It is not a general checkpoint/resume mechanism: the mapping still uses the original initial_time.
Minimal bounded example
import time
from simpy.rt import RealtimeEnvironment
def ticker(env, count):
for index in range(count):
before = time.monotonic()
yield env.timeout(1)
elapsed = time.monotonic() - before
print(index, env.now, elapsed)
env = RealtimeEnvironment(factor=0.05, strict=True)
process = env.process(ticker(env, count=3))
env.run(until=process)Use time.monotonic() for elapsed wall time. Calendar time can jump.
Strict-mode behavior
Callbacks and generator code execute synchronously on the simulation thread. Slow computation, blocking I/O, logging, garbage collection, OS scheduling, and loaded CI hosts all consume the real-time budget.
import time
import simpy.rt
def slow(env):
time.sleep(0.02)
yield env.timeout(1)
env = simpy.rt.RealtimeEnvironment(factor=0.01, strict=True)
env.process(slow(env))
try:
env.run()
except RuntimeError:
print("simulation missed its real-time budget")This sleep() intentionally demonstrates a missed deadline. Do not put blocking sleep in ordinary SimPy process logic to represent simulated delay; use env.timeout().
Appropriate use
Real-time execution is useful when wall-clock synchronization is intrinsic:
- hardware- or software-in-the-loop tests;
- human-paced demonstrations;
- interactive controllers;
- adapters to an external system with explicit timing contracts.
It is usually inappropriate for Monte Carlo replications: normal Environment runs faster, is less affected by host load, and preserves the same model-time logic.
External I/O boundary
SimPy itself is single-threaded and does not make external I/O asynchronous. Integrating devices or services requires an explicit adapter and failure model. Document:
- blocking/nonblocking behavior;
- timeout and retry policy;
- conversion between wall and simulation timestamps;
- thread-safety and event handoff;
- late/out-of-order data behavior;
- shutdown and exception propagation.
The bundled skill CLIs intentionally provide no device, network, plugin, or external-service integration.
Raw lines 96 to 111Drift measurement
Choose a real origin and compare expected elapsed wall time with actual monotonic elapsed time:
import time
origin_real = time.monotonic()
origin_sim = env.now
def drift_seconds(env, factor):
expected = (env.now - origin_sim) * factor
actual = time.monotonic() - origin_real
return actual - expectedDefine sign, sampling instant, percentile, maximum allowed lag, and platform before testing. A mean near zero can hide large deadline misses.
Raw lines 112 to 131Testing strategy
- Test model logic with normal
Environment. - Test deterministic ordering and interrupts independently of wall time.
- Keep real-time tests short and separately marked.
- Use
time.monotonic()and broad platform-aware bounds. - Test both
strict=Truedeadline detection and intentionalstrict=Falselag. - Avoid exact-duration assertions.
- Record Python/SimPy version, OS, architecture, host load assumptions, and factor.
Example tolerant assertion:
start = time.monotonic()
env = RealtimeEnvironment(factor=0.02, strict=False)
env.run(until=env.timeout(2))
elapsed = time.monotonic() - start
assert elapsed >= 0.02
assert elapsed < 1.0The upper bound is deliberately generous; tune it for controlled hardware, not a busy shared runner.
Raw lines 132 to 155Boundaries and stopping
Real-time environments inherit Environment.run() semantics:
- no
untilcan run forever; - numeric
untilexcludes normal events at the boundary; - Event
untilreturns the Event value; peek()/step()remain available.
Always bound real-time runs. A tiny factor combined with a huge event count can still consume substantial CPU, and a huge factor can make a short model wait for a long wall duration.
Raw lines 156 to 168Limitations
- Wall-clock results are not bit-for-bit timing reproducible across hosts.
- Python/OS timer granularity and scheduling affect jitter.
- One slow callback delays all later events.
- Real-time synchronization does not make simulated processes parallel.
strict=Falsecan accumulate unbounded lag.- Real-time agreement is not model validation.