110 lines
4.2 KiB
Markdown
110 lines
4.2 KiB
Markdown
# The simulation layer
|
|
|
|
English | [简体中文](./sim.zh.md)
|
|
|
|
This file records the cross-crate decisions of the simulation layer. The
|
|
design inside each crate is in its module documentation.
|
|
|
|
## Composition and dependencies
|
|
|
|
```text
|
|
apsis-event the event type and the event queue
|
|
apsis-sim-engine the discrete event engine
|
|
apsis-scenario scenario definition and text loading
|
|
apsis-output simulation output recording
|
|
apsis-sim the facade, which joins the four above
|
|
```
|
|
|
|
The dependency direction:
|
|
|
|
```text
|
|
apsis-event -> apsis-sim-engine -> apsis-sim -> apsis-rs
|
|
apsis-scenario -----------------> apsis-sim
|
|
apsis-output -------------------> apsis-sim
|
|
```
|
|
|
|
`apsis-sim` also depends on the ops layer (`apsis-scheduler`,
|
|
`apsis-schedule`, `apsis-task`, `apsis-resource`), because it plans a
|
|
scenario and reads the plan. The remaining crates depend on foundation alone.
|
|
|
|
## The order of events
|
|
|
|
Several events at the same instant must be delivered in a reproducible
|
|
order, or the same input would give a different output and there would be
|
|
nothing to cross-check against a reference implementation. The convention:
|
|
|
|
- Every event takes an increasing sequence number when it is scheduled.
|
|
- Events are ordered by time, and events at the same time by that number,
|
|
which is the order they were scheduled in.
|
|
- An event that a handler schedules while handling another goes behind the
|
|
events already queued for that instant.
|
|
|
|
`apsis-event` provides this order and the engine and the facade carry it
|
|
through. The queue assigns the numbers, so a caller cannot create two events
|
|
with the same one.
|
|
|
|
## The origin of time
|
|
|
|
An event time is a number of seconds from the origin of the simulation and
|
|
has nothing to do with a calendar; a scenario that needs one converts it
|
|
above this layer.
|
|
|
|
The `epoch` of a scenario is where the clock starts. The times in a timeline
|
|
are relative to that origin and `apsis-sim` adds the `epoch` before
|
|
delivering. Without the offset the engine would move the clock backwards when
|
|
it delivered the first event.
|
|
|
|
A time limit is compared on the same absolute axis: an event later than the
|
|
limit stays in the queue instead of running, so a caller can see what
|
|
remained when the run ended. The clock stops at the last event it executed
|
|
rather than at the limit.
|
|
|
|
## The event limit is cumulative
|
|
|
|
The event limit in `apsis-sim-engine` bounds the total for the life of the
|
|
engine, not the count of a single `run`. Once the limit is reached a further
|
|
`run` executes nothing. The limit is therefore a bound that cannot be
|
|
circumvented, and a caller who wants the remaining events to run has to
|
|
raise it explicitly.
|
|
|
|
## What a timeline means
|
|
|
|
`apsis-sim` expands a plan into start and end events on a timeline: every
|
|
placed task contributes one start and one end, as separate events, so the
|
|
engine can order them in time with everything else and the order of
|
|
overlapping tasks follows from the clock.
|
|
|
|
The timeline is the plan, not a physical model. What physical computation a
|
|
handler runs when a start or end event fires is up to the handler; the
|
|
facade only orders and records.
|
|
|
|
## The scenario text format
|
|
|
|
`apsis-scenario` carries its own line oriented text format with no
|
|
dependency, for storing a scenario and loading it back. Parsing runs in three
|
|
passes, over the resources, the tasks and the precedence constraints, so a
|
|
`require` may name a resource defined later in the file and a `precedence`
|
|
may name tasks in any order. Every failure carries the line it came from.
|
|
|
|
The output of `Scenario::to_text` must parse back with `Scenario::parse` to
|
|
an equal scenario, so the text form is stable under a round trip, which a
|
|
test asserts.
|
|
|
|
A task with no window, whose two ends are infinite, cannot be written as
|
|
text, so it is written as a finite interval that contains the task and is
|
|
still usable once read back.
|
|
|
|
## Determinism
|
|
|
|
A simulation result must be reproducible, because that is what makes a
|
|
cross-check possible. Three things give it:
|
|
|
|
- the same-instant ordering rule of the event queue;
|
|
- the stable ordering the planner applies to tasks of equal weight, described
|
|
in the ops layer;
|
|
- the recorder, which keeps the order of insertion, where
|
|
`Trace::in_time_order` is a stable sort.
|
|
|
|
The `integrate_sim` example asserts that running one prepared simulation
|
|
twice produces the same trace.
|