Skip to content

Commit 30026d3

Browse files
committed
add more notes
1 parent 03d5b7b commit 30026d3

File tree

1 file changed

+141
-13
lines changed
  • content/docs/reference/design-docs/running-time-calculation

1 file changed

+141
-13
lines changed

content/docs/reference/design-docs/running-time-calculation/_index.en.md

+141-13
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,51 @@ SimResults[simulation result curve]
137137
## Driving instructions
138138

139139
Driving instructions model what the train has to do, and under what conditions.
140-
It's a high-level primitive: an abstract order, not a curve.
140+
Driving instructions are generated using domain constraints such as:
141141

142+
- unsignaled line speed limits
143+
- permanent signaled speed limits
144+
- temporary speed limits
145+
- dynamic signaling:
146+
- block / moving block
147+
- dynamically signaled speed restrictions
148+
- neutral zones
149+
- stops
150+
151+
152+
There are two types of driving instructions:
153+
154+
- **Abstract driving instructions** model the high-level, rolling stock independant
155+
range of acceptable behavior: reach 30km/h at this location
156+
- **Concrete driving instructions** model the specific range of acceptable behavior
157+
for a specific rolling stock: follow this breaking curve and maintain 30km/h.
158+
159+
```mermaid
160+
flowchart TD
161+
Constraint[constraint]
162+
AbstractDrivingInstruction[abstract driving instruction]
163+
ConcreteDrivingInstruction[concrete driving instruction]
164+
RollingStockIntegrator[rolling stock integrator]
165+
Compiler([compiler])
166+
167+
Constraint -- generates one or more --> AbstractDrivingInstruction
168+
AbstractDrivingInstruction --> Compiler
169+
RollingStockIntegrator --> Compiler
170+
Compiler --> ConcreteDrivingInstruction
171+
```
172+
173+
174+
### Interpreting driving instructions
175+
176+
During the simulation, driving instructions are partitionned into 4 sets:
177+
178+
- `PENDING` instructions may apply at some point in the future
179+
- `RECEIVED` instructions aren't enforced yet, but will be unless overriden
180+
- `ENFORCED` instructions influence train behavior
181+
- `DISABLED` instructions don't ever have to be considered anymore. There are multiple ways instructions can be disabled:
182+
- `SKIPPED` instructions were not received
183+
- `RETIRED` instructions expired by themselves
184+
- `OVERRIDEN` instructions were removed by another instruction
142185

143186
```mermaid
144187
flowchart TD
@@ -149,6 +192,11 @@ subgraph disabled
149192
overriden
150193
end
151194
195+
subgraph active
196+
received
197+
enforced
198+
end
199+
152200
pending --> received
153201
pending --> skipped
154202
received --> enforced
@@ -157,10 +205,39 @@ enforced --> retired
157205
enforced --> overriden
158206
```
159207

208+
These sets evolve as follows:
160209

161-
```rust
210+
- when an integration steps overlaps a `PENDING` instruction's received condition, it is `RECEIVED` and becomes a candidate to execution
211+
- existing instructions may be `OVERRIDEN` due to an `override_on_received` operation
212+
- if an instruction cannot ever be received at any future simulation state, it transitions to the `SKIPPED` state
213+
- when simulation state exceeds an instruction's enforcement position, it becomes `ENFORCED`. Only enforced instructions influence train behavior.
214+
- existing instructions may be `OVERRIDEN` due to an `override_on_enforced` operation
215+
- when simulation state exceeds an instruction's retirement position, it becomes `RETIRED`
162216

163217

218+
### Overrides
219+
220+
When an instruction transitions to the `RECEIVED` or `ENFORCED` state, it can disable active instructions
221+
which match some metadata predicate. There are two metadata attributes which can be relied on for overrides:
222+
223+
- the `kind` allows overriding previous instructions for a given domain, such as spacing or block signaled speed limits
224+
- the `rank` can be used as a "freshness" or "priority" field. If two instructions overriding each other are received
225+
(such as when a train sees two signals), the rank allows deciding which instruction should be prioritized.
226+
227+
This is required to implement a number of signaling features, as well as stops, where the stop instruction is overriden
228+
by the restart instruction.
229+
230+
231+
### Data model
232+
233+
234+
235+
```rust
236+
struct ReceivedCond {
237+
position_in: Option<PosRange>,
238+
time_in: Option<TimeRange>,
239+
}
240+
164241
struct InstructionMetadata {
165242
// state transitions
166243
received_when: ReceivedCond,
@@ -181,7 +258,6 @@ struct InstructionMetadata {
181258
enum AbstractInstruction {
182259
NeutralZone,
183260
SpeedTarget {
184-
enforcement: Enforcement,
185261
at: Position,
186262
speed: Speed,
187263
}
@@ -190,16 +266,10 @@ enum AbstractInstruction {
190266
enum ConcreteInstruction {
191267
NeutralZone,
192268
SpeedTarget {
193-
enforcement: Enforcement,
194269
braking_curve: SpeedPosCurve,
195270
},
196271
}
197272

198-
struct ReceivedCond {
199-
position_in: Option<PosRange>,
200-
time_in: Option<TimeRange>,
201-
}
202-
203273
struct OverrideFilter {
204274
kind: InstructionKindId,
205275
rank: Option<(RankRelation, usize)>,
@@ -213,7 +283,65 @@ enum RankRelation {
213283

214284
# Design decisions
215285

216-
## Driving instruction overrides
286+
## Lowering constraints to an intermediate representation
287+
288+
Early on, we started making lists of what domain constraints can have an impact on train behavior.
289+
Meanwhile, to simulate train behavior, we figured out that we need to know which constraints apply at any given time.
290+
291+
There's a fundamental tension between these two design constraints, which can be resolved in one of two ways:
292+
293+
- either treat each type of constraint as its own thing during the simulation
294+
- abstract away constraints into a common representation, and then simulate that
295+
296+
### {{< rejected >}} Distinct constraint types
297+
298+
When we first started drafting architecture diagrams, the train simulation API directly took
299+
a bunch of constraint types as an input. It brought up a number of issues:
300+
301+
- the high diversity of constraint types makes it almost impossible to describe all interactions between all constraint types
302+
- the domain of some of these interactions is very complex (block signaling)
303+
- when simulating, it does not seem to matter why a constraint is there, only what to do about it
304+
305+
We couldn't find clear benefits to dragging distinctions between constraint types deep into the implementation
306+
307+
### {{< rejected >}} Internal constraint types abstraction
308+
309+
We then realized that abstracting over constraint types during simulation had immense benefits:
310+
311+
- it allows expressing requirements on what constraints need to be enforceable
312+
- it greatly simplifies the process of validating constraint semantics: instead of having to validate interactions between
313+
every possible type of constraints, we only have to validate that constraint semantics can be expressed by the abstract
314+
constraint type
315+
316+
We decided the explore the possibility of keeping constraint types distinct in the external API, but lowering these constraints into an intermediary representation internaly. We found a number of downsides:
317+
318+
- the public simulation API would still bear the complexity of dealing with many constraint types
319+
- there would be a need to incrementaly generate internal abstracted constraints to support the incremental API
320+
321+
### {{< adopted >}} External constraint types abstraction
322+
323+
We tried to improve over the previous proposal by moving the burden of converting many constraints into a common abstraction out of the simulation API. We found that doing so:
324+
325+
- reduces the API surface of the train simulation module
326+
- decouples behavior from constraint types: if a new constraint type needs to be added, the simulation
327+
API only needs expansion if the expected behavior expected for this constraint isn't part of the API.
328+
329+
330+
## Interpreting driving instructions
331+
332+
As the train progresses through the simulation, it reacts according to driving instructions
333+
which depend on more than the bare train physics state (position, time, and speed):
334+
335+
- the behavior of a train on each block depends on the state of the last passed block signal
336+
- when a train stops at a red signal, then restarts, it may have to keep applying the driving
337+
instruction from the previous signal until the formerly red light is passed
338+
339+
Thus, given:
340+
341+
- set of all possible driving instructions (alongside applicability metadata)
342+
- the result of previous integration steps (which may be extended to hold metadata)
343+
344+
There is a need to know what driving instructions are applicable to the current integration step.
217345

218346
Overrides are a way of modelling instructions which disable previous ones. Here are some examples:
219347

@@ -227,6 +355,6 @@ We identified multiple filtering needs:
227355

228356
We quickly settled on adding a kind field, but had a lengthy discussion over how to discriminate upstream and downstream signals. We explored the following options:
229357

230-
- adding `source` metadata, which was rejected as it does not address the issue of upstream / downstream
231-
- adding identifiers to instructions, and overriding specific instructions, which was rejected as it makes instruction generation and processing more complex
232-
- adding some kind of priority / rank field, which was adopted
358+
- {{< rejected >}} adding `source` metadata, which was rejected as it does not address the issue of upstream / downstream
359+
- {{< rejected >}} adding identifiers to instructions, and overriding specific instructions, which was rejected as it makes instruction generation and processing more complex
360+
- {{< adopted >}} adding some kind of priority / rank field, which was adopted

0 commit comments

Comments
 (0)