Core engine¶
The sequential simulation core: the event loop, the generic event, the
process driver, condition composition, and the error hierarchy. Every symbol
here is re-exported from the top-level llmsim package.
Simulation¶
Sim ¶
Sim(initial_time=0, *, seed=None, rng=None, debug=False)
A single-threaded discrete-event simulation.
Sim owns the event schedule, the current simulation time, and a
reproducible random stream. It and every event, process, and resource
attached to it belong to exactly one thread; parallelism comes from running
many independent Sim instances, never from locking one (share-nothing
architecture).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
initial_time
|
float
|
The simulation clock's starting value. |
0
|
seed
|
int | None
|
Seed for :attr: |
None
|
rng
|
Random | None
|
An explicit :class: |
None
|
debug
|
bool
|
When |
False
|
Initialize an empty schedule at initial_time.
debug
property
¶
debug
Whether debug mode is on (debug=True or LLMSIM_DEBUG=1).
Core owns this question; features that must "flag loudly in debug mode" (a non-strict offload, for example) ask here instead of inferring it from the thread-ownership guard.
active_process
property
¶
active_process
The process whose event is currently being resumed, if any.
schedule ¶
schedule(event, priority=NORMAL, delay=0)
Insert event into the schedule at now + delay with priority.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
in debug mode, if called from a thread other than the
one that constructed this |
step ¶
step()
Process the next scheduled event, advancing the clock to its time.
Raises:
| Type | Description |
|---|---|
EmptySchedule
|
if no events remain to process. |
delay ¶
delay(delay: float) -> Timeout[None]
delay(delay: float, value: T) -> Timeout[T]
delay(delay, value=None)
Return a :class:~llmsim.core.events.Timeout that fires after delay.
With no value the timeout resolves to None; otherwise it resolves
to value.
spawn ¶
spawn(process, *args, **kwargs)
Start a new process and return its :class:~llmsim.core.process.Process.
process may be:
- a process function -- called as
process(sim, *args, **kwargs)so theSimis injected as its first argument (design ergonomics); or - an already-created generator or coroutine, used as-is.
Both a def ... : yield generator and an async def coroutine are
accepted; the same unified driver advances either.
offload ¶
offload(fn, /, *args, delay=None, strict=True, **kwargs)
Run fn(*args, **kwargs) on the attached offload pool.
Returns an event that a process waits on like any other. In strict
mode (the default) the result is delivered exactly at the
deterministic completion slot now + delay, regardless of how long
the computation takes on the wall clock; with strict=False it is
delivered as soon as it is available (nondeterministic ordering), no
earlier than now + delay when delay is given.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fn
|
Callable[..., T]
|
An importable module-level callable (validated at submission); its positional and keyword arguments follow, except the reserved keywords delay and strict. |
required |
delay
|
float | None
|
The completion-slot offset, a pure function of model state
(required in strict mode); under |
None
|
strict
|
bool
|
Whether delivery is pinned to the deterministic slot. |
True
|
Raises:
| Type | Description |
|---|---|
SimulationError
|
if no offload pool is attached to this |
ValueError
|
if delay is negative, or omitted in strict mode. |
run ¶
run(until=None)
Advance the simulation until until is reached.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
until
|
float | Event[Any] | None
|
|
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
if until is a time at or before :attr: |
RuntimeError
|
if until is an event that can never trigger because the schedule emptied first. |
Events¶
Event ¶
Event(sim)
Bases: Generic[T]
Something that may happen at a point in simulation time.
An event moves through three states: it may happen (:attr:triggered is
False), it is going to happen (:attr:triggered is True once
:meth:succeed, :meth:fail, or :meth:trigger schedules it), and it
has happened (:attr:processed is True after the Sim invokes its
callbacks).
Every event belongs to exactly one :class:~llmsim.core.sim.Sim and, with
it, to exactly one thread. A failed event whose exception is never defused
crashes the simulation when processed, so faults are never silently lost.
Event is generic in the value it yields on success. Because
:meth:__await__ yields self exactly once, await event (in an
async def process) and yield event (in a generator process) resolve
through the very same scheduling path.
Create an untriggered event owned by sim.
ok
property
¶
ok
Whether the event succeeded.
Raises:
| Type | Description |
|---|---|
AttributeError
|
if accessed before the event is triggered. |
value
property
¶
value
The event's success value (or the exception, if it failed).
Raises:
| Type | Description |
|---|---|
AttributeError
|
if accessed before the event is triggered. |
trigger ¶
trigger(event)
Adopt event's outcome and schedule this event for processing.
Usable directly as a callback so one event's completion can drive a chain of dependent events.
Timeout ¶
Timeout(sim, delay, value=None)
Bases: Event[T]
An event that triggers automatically after a fixed delay elapses.
Constructing a timeout schedules it immediately; a process waits for the
delay by yielding (or awaiting) it. This is what
:meth:~llmsim.core.sim.Sim.delay returns.
Schedule a timeout that fires delay time units from now.
Raises:
| Type | Description |
|---|---|
ValueError
|
if delay is negative. |
Processes¶
Process ¶
Process(sim, runnable)
Bases: Event[T]
A generator or coroutine advanced by the simulation, and itself an event.
A process suspends by yielding (or awaiting) an event; the driver resumes it
with that event's value once it is processed, or throws the event's
exception into it. Process is itself an :class:~llmsim.core.events.Event
that is triggered when the body returns (its value becomes the return value)
or raises (its value becomes the exception), so other processes can wait on
a process the same way they wait on any event.
Schedule runnable to start at the next urgent step.
Raises:
| Type | Description |
|---|---|
ValueError
|
if runnable is neither a generator nor a coroutine
(it lacks a |
interrupt ¶
interrupt(cause=None)
Interrupt this process, throwing an :class:Interrupt into it.
The interrupt is delivered at the process's next resume, ahead of the event it is currently waiting on.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
if the process has already terminated, or if a process tries to interrupt itself. |
Condition composition¶
Condition ¶
Condition(sim, evaluate, events)
Bases: Event[ConditionValue]
An event triggered once evaluate holds over its member events.
The condition's value is a :class:ConditionValue giving access to the
members that had triggered by the time it was processed. If any member
fails, the condition fails too: with that member's exception, or -- when
several members have already failed together -- with a single
:exc:ExceptionGroup aggregating them (a deliberate clean-break divergence
from SimPy 3, which forwards only the first failure).
Watch events, triggering when evaluate returns True.
Raises:
| Type | Description |
|---|---|
ValueError
|
if the members do not all belong to sim. |
AllOf ¶
AllOf(sim, events)
Bases: Condition
A condition triggered once all events have succeeded.
Fails immediately if any member fails.
Wait for every event in events.
AnyOf ¶
AnyOf(sim, events)
Bases: Condition
A condition triggered once any of events has succeeded.
Fails immediately if any member fails.
Wait for at least one event in events.
Errors¶
SimulationError ¶
Bases: Exception
Base class for every exception the simulation engine raises.
Catching SimulationError distinguishes engine faults (a stopped
simulation, a misused event, an interrupt) from the domain exceptions a
model raises out of its own process code.
Interrupt ¶
Interrupt(cause=None)
Bases: SimulationError
Thrown into a process when another process interrupts it.
:attr:cause carries the reason for the interrupt, or None when the
interrupter supplied none. When a process is interrupted several times
concurrently, the interrupts are thrown in the order they were scheduled.
Store cause as the single exception argument.
EmptySchedule ¶
Bases: SimulationError
Raised by :meth:~llmsim.core.sim.Sim.step when no events remain.
A bare :meth:~llmsim.core.sim.Sim.run (until=None) catches this to
stop cleanly once the schedule drains; it surfaces to the caller only when
a run(until=event) can never be satisfied because the queue emptied
first.