Registry and Live Parameters
Every run has one SimulationRegistry, at env.registry. It maps dot-notation paths, such as Line_A.resources.lathe.current_cap, to the values the simulation reads. An ingress connector, a scenario or a process changes a parameter by updating its path, and the simulation follows the change without restarting.
Registry Paths
run() flattens the configuration into the registry, and every path an ingress message, a scenario or registry.get names has one of these forms:
| Builder call | Paths |
|---|---|
add_arrival(name, ...), add_service(name, ...) |
<sim_id>.arrival.<name>.rate and <sim_id>.service.<name>.rate for an exponential distribution, otherwise .mean and .std |
add_resource(name, ...), add_container(name, ...) |
<sim_id>.resources.<name>.current_cap and .max_cap, <sim_id>.containers.<name>.current_cap and .max_cap |
add_variable(name, value) |
<sim_id>.variables.<name> |
Stores, registered through SimParameter(stores=...) on the low-level API, take <sim_id>.stores.<name>.current_cap and .max_cap. An update to a path that does not exist is logged as a warning and ignored.
compile_parameters() on a SimulationContext returns the SimParameter that run() registers, so the paths of a configuration can be checked before the run.
How an Update Is Applied
env.registry.update(path, value) applies one change:
- A path that does not exist is logged as a warning, and the update is ignored.
- A value of a different type is converted to the type the path holds, so the string
"5"sent to an integer path becomes5. The conversion is Python's own, so2.5sent to an integer path becomes2. A value that cannot be converted is logged as an error, and the update is ignored. - A value equal to the current one changes nothing.
- Otherwise the value is replaced, and the configuration object the path belongs to is updated too, such as the
rateof aDistributionConfigor an entry of the variables.
Ingress connectors run on a background thread, so they do not call update themselves. Each puts a (path, value) pair on a queue, and a SimPy process started by setup_ingress applies everything queued every 0.1 simulation seconds. At factor=1.0 an update therefore lands within 0.1 seconds of arriving. The simulation time it lands at depends on when it arrives, so a change that must happen at an exact simulation time is a scenario.
What Follows a Change
- Arrival and service distributions. A
Samplerreads the configuration object each time it draws, so the next draw uses the new value.context.wait_for_arrivaldraws when it is called, so the arrival already being waited for keeps its old gap.@app.taskdraws the service time once the resource is acquired, so a task still in the queue uses the new value. - Resource capacity. A
DynamicResourcefollowscurrent_cap, rounded down to a whole number and limited to between 0 and themax_capthe registry holds. A change tomax_capappliescurrent_capagain under the new limit. Resources and Containers describes what happens when the capacity shrinks below the number of tasks in progress. - Container capacity. A
DynamicContainerfollowscurrent_cap, fractions included even when the starting value is a whole number, limited to between 0 and themax_capthe registry holds. A change tomax_capappliescurrent_capagain under the new limit. ADynamicStoredoes the same with whole numbers. - Variables. The new value is stored. A process reads it with
env.registry.get(path).value.
Sources of Updates
- Ingress connectors: Kafka, Redis, PostgreSQL and
LocalIngress. Connectors gives the message each one expects. - A scenario in a YAML blueprint, compared with
LocalIngressbelow. - A process, which calls
env.registry.update(path, value)directly, as the maintenance process in Advanced YAML does.
Scenarios versus LocalIngress
A YAML blueprint can carry a scenario: a list of registry changes, each with the simulation time it applies at, such as {at: 10, path: Line_A.resources.lathe.current_cap, value: 3}. Script an experiment shows a complete file.
A scenario is not a connector. It is compiled into a SimPy process that waits on the simulation clock, so each change lands at exactly its at, on every run and at any factor, including factor=0. Every path is checked against the registry when the file is loaded, so a misspelt path stops the load with its line number. LocalIngress waits on the wall clock instead, and an unknown path is only logged as a warning when it is due.
A scenario and an ingress connector can be used together. Both write to the same registry, so a scripted baseline can run while an operator steers over Kafka. When both change the same path, the later write wins.
Waiting for a Change
env.registry.get(path).wait_for_change() returns a SimPy event that fires when the value at that path changes. Each value has one change signal, and the first process waiting takes it. DynamicResource, DynamicContainer and DynamicStore already wait on their current_cap and max_cap, so wait only on paths that nothing else waits on, such as variables.