Skill Tracer
Skills › Scientific Agent Skills › simpy › real-time.md

real-time.md

180 lines · skills/simpy/references/real-time.md at 5f2bc03

Open on GitHub
1# Real-time simulation
2
3Reviewed 2026-10-01 against SimPy 4.1.2.
4
5`simpy.rt.RealtimeEnvironment` retains the Event/Process API but delays event
6processing so simulation time tracks wall-clock time.
7
8```python
9from simpy.rt import RealtimeEnvironment
11env = RealtimeEnvironment(
12 initial_time=0,
13 factor=0.1,
14 strict=True,
16```

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 17
18## Parameters
20- `initial_time`: starting simulation clock.
21- `factor`: wall-clock seconds per simulation time unit; validate it as finite and
22 strictly positive in model code (4.1.2 does not enforce this in its constructor).
23 - `1.0`: one simulation unit takes one second;
24 - `0.1`: one simulation unit takes 0.1 seconds;
25 - `60.0`: one simulation unit takes one minute.
26- `strict=True`: raise `RuntimeError` when processing falls more than the allotted
27 factor behind.
29`strict=False` suppresses deadline failure. It permits drift; it does not make an
30overloaded simulation accurately synchronized.
32If initialization takes a long time between construction and the first `run()`,
33call `env.sync()` immediately before that first run. It resets the real-time origin
34without changing simulation time. It is not a general checkpoint/resume mechanism:
35the mapping still uses the original `initial_time`.

Parameters

  • 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: 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.

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.

Raw lines 18 to 36
37## Minimal bounded example
39```python
40import time
41from simpy.rt import RealtimeEnvironment
43def ticker(env, count):
44 for index in range(count):
45 before = time.monotonic()
46 yield env.timeout(1)
47 elapsed = time.monotonic() - before
48 print(index, env.now, elapsed)
50env = RealtimeEnvironment(factor=0.05, strict=True)
51process = env.process(ticker(env, count=3))
52env.run(until=process)
53```
55Use `time.monotonic()` for elapsed wall time. Calendar time can jump.

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.

Raw lines 37 to 56
57## Strict-mode behavior
59Callbacks and generator code execute synchronously on the simulation thread. Slow
60computation, blocking I/O, logging, garbage collection, OS scheduling, and loaded
61CI hosts all consume the real-time budget.
63```python
64import time
65import simpy.rt
67def slow(env):
68 time.sleep(0.02)
69 yield env.timeout(1)
71env = simpy.rt.RealtimeEnvironment(factor=0.01, strict=True)
72env.process(slow(env))
73try:
74 env.run()
75except RuntimeError:
76 print("simulation missed its real-time budget")
77```
79This `sleep()` intentionally demonstrates a missed deadline. Do not put blocking
80sleep in ordinary SimPy process logic to represent simulated delay; use
81`env.timeout()`.

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().

Raw lines 57 to 82
83## Appropriate use
85Real-time execution is useful when wall-clock synchronization is intrinsic:
87- hardware- or software-in-the-loop tests;
88- human-paced demonstrations;
89- interactive controllers;
90- adapters to an external system with explicit timing contracts.
92It is usually inappropriate for Monte Carlo replications: normal `Environment`
93runs faster, is less affected by host load, and preserves the same model-time
94logic.

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.

Raw lines 83 to 95
96## External I/O boundary
98SimPy itself is single-threaded and does not make external I/O asynchronous.
99Integrating devices or services requires an explicit adapter and failure model.
100Document:
102- blocking/nonblocking behavior;
103- timeout and retry policy;
104- conversion between wall and simulation timestamps;
105- thread-safety and event handoff;
106- late/out-of-order data behavior;
107- shutdown and exception propagation.
109The bundled skill CLIs intentionally provide no device, network, plugin, or
110external-service integration.

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 111
112## Drift measurement
114Choose a real origin and compare expected elapsed wall time with actual monotonic
115elapsed time:
117```python
118import time
120origin_real = time.monotonic()
121origin_sim = env.now
123def drift_seconds(env, factor):
124 expected = (env.now - origin_sim) * factor
125 actual = time.monotonic() - origin_real
126 return actual - expected
127```
129Define sign, sampling instant, percentile, maximum allowed lag, and platform before
130testing. A mean near zero can hide large deadline misses.

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

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

Raw lines 112 to 131
132## Testing strategy
1341. Test model logic with normal `Environment`.
1352. Test deterministic ordering and interrupts independently of wall time.
1363. Keep real-time tests short and separately marked.
1374. Use `time.monotonic()` and broad platform-aware bounds.
1385. Test both `strict=True` deadline detection and intentional `strict=False` lag.
1396. Avoid exact-duration assertions.
1407. Record Python/SimPy version, OS, architecture, host load assumptions, and factor.
142Example tolerant assertion:
144```python
145start = time.monotonic()
146env = RealtimeEnvironment(factor=0.02, strict=False)
147env.run(until=env.timeout(2))
148elapsed = time.monotonic() - start
149assert elapsed >= 0.02
150assert elapsed < 1.0
151```
153The upper bound is deliberately generous; tune it for controlled hardware, not a
154busy shared runner.

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:

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.

Raw lines 132 to 155
156## Boundaries and stopping
158Real-time environments inherit `Environment.run()` semantics:
160- no `until` can run forever;
161- numeric `until` excludes normal events at the boundary;
162- Event `until` returns the Event value;
163- `peek()`/`step()` remain available.
165Always bound real-time runs. A tiny factor combined with a huge event count can
166still consume substantial CPU, and a huge factor can make a short model wait for a
167long wall duration.

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.

Raw lines 156 to 168
169## Limitations
171- Wall-clock results are not bit-for-bit timing reproducible across hosts.
172- Python/OS timer granularity and scheduling affect jitter.
173- One slow callback delays all later events.
174- Real-time synchronization does not make simulated processes parallel.
175- `strict=False` can accumulate unbounded lag.
176- Real-time agreement is not model validation.

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.
Raw lines 169 to 177
178## Sources
180See `sources.md` for the 4.1.2 real-time topical guide and `simpy.rt` API.

Sources

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

Raw lines 178 to 180