ecoli.processes.engine_process
EngineProcess
Tunnel Ports
Sometimes, a process inside the EngineProcess might need to be wired to a store outside the EngineProcess, or an outside process might need to be wired to an inside store. We handle this with _tunnels_.
Here is a state hierarchy showing how a tunnel connects an outside
process (A
) to an inside store (store1
). We call this a “tunnel
in” because the exterior process is tunneling into EngineProcess to see
an internal store. Essentially, EngineProcess has a port for the tunnel
in that connects to some store in the outer simulation here called
dummy_store
. The process A
connects to that same store. When
the next_update
method of EngineProcess runs, the value of store1
in the inner simulation is read and copied through the tunnel in port
to dummy_store
, where it can be read by process A
. Conversely,
if process A
modifies the value of dummy_store
, the next_update
method of EngineProcess ensures that store1
reflects that change the
next time it is run. It is the user’s responsibility to define the topology
of EngineProcess such that the tunnel in port points to the store where
the outer simulation process (here, process A
) expects the data to be
(here, dummy_store
).
________________
/ | \ EngineProcess
dummy_store +---+ +-------------------------+
: :.....| A | | |
: +---+ | /\ |
: | / \ |
: | +---+ store1 |
: | | B | ^ |
: | +---+ | |
: | | |
:.............tunnel_in <-- next_update |
| |
+-------------------------+
Here is another example where a tunnel connects an inside process
(B
) to an outside store (store2
). We call this a “tunnel out”
because the interior process is tunneling outside of EngineProcess to
see an external store. In this case, EngineProcess has a port for the
tunnel out that connects to store2
in the outer simulation. When
the next_update
method of EngineProcess is called, the value of
store2
is read and copied to a store in the inner simulation
(here, dummy_store
) where it is read by process B
. Unlike
tunnels in, all tunnels out are automatically created and wired
because EngineProcess only needs to have the complete inner simulation
topology to know exactly what inner process ports connect to stores in
the outer simulation and what paths those outer simulation stores have.
/\
/ \ EngineProcess
/ +-------------------------+
store2 | |
: | /\ |
: | / \ |
: | +---+ dummy_store |
...: | | B |.......: ^ |
: | +---+ | |
: | | |
:..tunnel_out <----- next_update |
| |
+-------------------------+
In these diagrams, processes are boxes, stores are labeled nodes in the
tree, solid lines show the state hierarchy, dotted lines show the
topology wiring, and tunnel_out
/tunnel_in
are ports for
EngineProcess.
These tunnels are the only way that the EngineProcess exchanges information with the outside simulation.
- class ecoli.processes.engine_process.EngineProcess(parameters=None)[source]
Bases:
Process
Process that wraps a Vivarium simulation by taking the following options in its parameter dictionary:
inner_composer
: a composer for the inner simulationinner_composer_config
: a configuration dictionary for that composerouter_composer
: a composer that can be used upon division to create a new EngineProcess (including a new inner simulation using the inner composer) plus as any other processes that make up a single simulationouter_composer_config
: a configuration dictionary for that composertunnels_in
: a mapping from port names to paths of stores in the inner simulation that processes in the outer simulation would like access to.EcoliEngineProcess
has a customgenerate_topology()
method to ensure that each port name is wired to a user-specified store in the outer simulation. Then, every time theEngineProcess()
is run, the value from the outer simulation store is copied to the store located at the path in the inner simulation, the inner simulation is incremented, and the final value of the store in the inner simulation is copied to the store that the port points to in the outer simulation. This allows outer simulation processes to indirectly read and modify the value of stores in the inner simulation.stub_schemas
: a mapping from process names to a mapping from paths in the inner simulation to the schema to use for the store at that path. SeeStubSchemas
for more details.tunnel_out_schemas
: a mapping from names of ports for tunnels out to schemas to use for the stores that those ports point to. Helpful for ensuring consistency in schemas specified for these stores.
These options allow EngineProcess to create a full inner simulation, increment it in sync with the process time step and the rest of the outer simulation, and read/modify values in both the inner and outer simulations over time.
- calculate_timestep(states)[source]
The time step for EngineProcess is always set to be the shortest time step for an inner simulation process. This ensures that we never accidentally skip over a scheduled update for a Process/Step.
- create_emitter()[source]
Since EngineProcesses are often parallelized with multiprocessing, we wait to create an emitter for each EngineProcess instance until multiprocessing is initiated to avoid unusual locking or other issues.
This is the first thing called by
next_update()
the first time it is run.
-
defaults: Dict[str, Any]
{ 'agent_id': '0', 'divide': False, 'division_threshold': None, 'division_variable': None, 'emit_paths': (), 'experiment_id': '', 'inner_composer': None, 'inner_composer_config': {}, 'inner_emitter': 'null', 'inner_same_timestep': False, 'outer_composer': None, 'outer_composer_config': {}, 'seed': 0, 'start_time': 0, 'stub_schemas': {}, 'tunnel_out_schemas': {}, 'tunnels_in': {}}
- initial_state(config=None)[source]
To ensure that the stores pointed to by the tunnel in ports accurately reflect the values of the stores they tunnel to in the inner simulation, we define a custom
initial_state()
method for EngineProcess that populates those stores at the start of the simulation with their corresponding inner simulation store values.
- next_update(timestep, states)[source]
Update the inner simulation store values for tunnels in and out then emit the inner simulation state. We emit at the start of next_update() because the inner simulation is in-sync with the outer simulation only after the stores in the internal state that tunnel out have been synchronized with their corresponding stores in the outer simulation.
Increment the inner simulation by the shortest time possible to guarantee at least one Process/Step ran. If division is enabled, check to see if the division variable has reached the threshold. If so, divide the cell state into two daughter states and create two new inner simulations using the inner composer configuration, the outer composer, and the outer composer configuration.
Finally, figure out what updates to apply to the outer simulation stores for tunnels in/out in order to mutate them the same way that their corresponding stores in the inner simulation were mutated (if at all) over the course of the increment in inner simulation time.
- class ecoli.processes.engine_process.SchemaStub(parameters=None)[source]
Bases:
Step
Stub process for providing schemas to an inner simulation.
When using
ecoli.processes.engine_process.EngineProcess
, there may be processes in the outer simulation whose schemas you are expecting to affect variables in the inner simulation. You can include this stub process in your inner simulation to provide those schemas from the outer simulation.The process takes a single parameter,
ports_schema
, whose value is the process’s ports schema to be provided to the inner simulation.We run these as Steps otherwise they could influence when other Steps run. For example, if a SchemaStub was a Process with timestep 1 while all other Processes had timestep 2, Steps like mass listener would run after every second instead of every 2 seconds, which may not be desired.
- Parameters:
parameters (dict | None)
- class ecoli.processes.engine_process._InnerComposer(config=None)[source]
Bases:
Composer
Base class for composer classes.
Composers generate composites.
All composer classes must inherit from this class.
- Parameters:
config (dict | None) – Dictionary of configuration options that can override the class defaults.
- class ecoli.processes.engine_process._OuterComposer(config=None)[source]
Bases:
Composer
Base class for composer classes.
Composers generate composites.
All composer classes must inherit from this class.
- Parameters:
config (dict | None) – Dictionary of configuration options that can override the class defaults.
- class ecoli.processes.engine_process._ProcA(parameters=None)[source]
Bases:
Process
- Parameters:
parameters (dict | None)
- class ecoli.processes.engine_process._ProcB(parameters=None)[source]
Bases:
Process
- Parameters:
parameters (dict | None)
- class ecoli.processes.engine_process._ProcC(parameters=None)[source]
Bases:
Process
- Parameters:
parameters (dict | None)
- ecoli.processes.engine_process._get_path_net_depth(path)[source]
Resolve a tuple path to figure out its depth, subtracting one for every “..” encountered.
- ecoli.processes.engine_process._inverse_update(initial_state, final_state, store, updater_registry_reverse)[source]
Given a dictionary containing the current values contained inside a potentially nested store and a dictionary containing the final values inside that same store, calculate an update that can be passed such that calling the updater of said store with the calculated update causes the values in the store to change from their current values to the provided final values.
- Parameters:
initial_state (Any) – Current values (potentially nested) in store
final_state (Any) – Final values (potentially nested) in store that we desire
store (Store) – Store (potentially nested) that we are trying to mutate
updater_registry_reverse (dict[Callable, str]) – A mapping from updater functions to the string names they are registered as in
updater_registry
- Returns:
Update dictionary that when used to update
store
by calling its (or its sub-stores) updaters, causes its values to change frominitial_state
tofinal_state
- ecoli.processes.engine_process.cap_tunneling_paths(topology, outer=())[source]
For ports in the inner simulation that point to stores outside, this function caps those stores to point to a dummy store that is populated with the value of the actual store in the outer simulation in the
next_update()
method ofEngineProcess
.- Parameters:
topology (dict[str, Any]) – Topology of inner simulation in
EngineProcess
. Mutated in place such that ports pointing to stores located outside the inner simulation are capped and instead point to top-level stores called “port_name_tunnel”outer (tuple[str, ...]) – Current port path inside topology (for recursively travelling topology with this function)
- Returns:
Mapping from paths to ports that were capped as described above to the names of the new top-level stores they point to.
- Return type: