Back to Claude Scientific Skills

Real-time simulation

skills/simpy/references/real-time.md

2.55.05.0 KB
Original Source

Real-time simulation

Verified 2026-07-23 against SimPy 4.1.2.

simpy.rt.RealtimeEnvironment retains the Event/Process API but delays event processing so simulation time tracks wall-clock time.

python
from simpy.rt import RealtimeEnvironment

env = RealtimeEnvironment(
    initial_time=0,
    factor=0.1,
    strict=True,
)

Parameters

  • initial_time: starting simulation clock.
  • factor: wall-clock seconds per simulation time unit; must be positive.
    • 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: raise RuntimeError when 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.

Minimal bounded example

python
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.

python
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.

Drift measurement

Choose a real origin and compare expected elapsed wall time with actual monotonic elapsed time:

python
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 - expected

Define sign, sampling instant, percentile, maximum allowed lag, and platform before testing. A mean near zero can hide large deadline misses.

Testing strategy

  1. Test model logic with normal Environment.
  2. Test deterministic ordering and interrupts independently of wall time.
  3. Keep real-time tests short and separately marked.
  4. Use time.monotonic() and broad platform-aware bounds.
  5. Test both strict=True deadline detection and intentional strict=False lag.
  6. Avoid exact-duration assertions.
  7. Record Python/SimPy version, OS, architecture, host load assumptions, and factor.

Example tolerant assertion:

python
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.0

The upper bound is deliberately generous; tune it for controlled hardware, not a busy shared runner.

Boundaries and stopping

Real-time environments inherit Environment.run() semantics:

  • no until can run forever;
  • numeric until excludes normal events at the boundary;
  • Event until returns 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.

Limitations

  • 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=False can accumulate unbounded lag.
  • Real-time agreement is not model validation.

Sources

See sources.md for the 4.1.2 real-time topical guide and simpy.rt API.