Skip to content

Commit

Permalink
More documentation in ecoli_master_sim
Browse files Browse the repository at this point in the history
  • Loading branch information
thalassemia committed Nov 1, 2023
1 parent 4d827fe commit b43b6d4
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 31 deletions.
2 changes: 1 addition & 1 deletion doc/reference/stores.rst
Original file line number Diff line number Diff line change
Expand Up @@ -290,7 +290,7 @@ molecule). All unique molecules will have the following named fields:

1. ``unique_index`` (:py:class:`int`): Unique identifier for each unique molecule
When processes add new unique molecules, the helper function
:py:func:`ecoli.library.schema.create_unqiue_indices` is used to generate
:py:func:`ecoli.library.schema.create_unqiue_indexes` is used to generate
unique indices for each molecule to be added.
2. ``_entryState`` (:py:attr:`numpy.int8`): 1 for active row, 0 for inactive row
When unique molecules are deleted (e.g. RNA degradation), all of their data,
Expand Down
10 changes: 5 additions & 5 deletions ecoli/composites/ecoli_master.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ def initial_state(self, config: dict[str, Any]=None) -> dict[str, Any]:
1. ``config['initial_state']``
2. Load the JSON file at ``f'data/{config['initial_state_file]}.json'``
2. Load the JSON file at ``f'data/{config['initial_state_file]}.json'``
using :py:func:`~ecoli.states.wcecoli_state.get_state_from_file`.
3. Generate initial state from simulation data object (see
:py:meth:`~ecoli.library.sim_data.LoadSimData.generate_initial_state`)
Expand Down Expand Up @@ -216,7 +217,7 @@ def generate_processes_and_steps(self, config: dict[str, Any]
This method is called when :py:class:`~ecoli.composites.ecoli_master.Ecoli`
is initialized and its return value is cached as the instance variable
:py:data:`~ecoli.composites.ecoli_master.Ecoli.processes_and_steps`. This
allows the :py:class:`~ecoli.composites.ecoil_master.Ecoli.initial_state`
allows the :py:class:`~ecoli.composites.ecoli_master.Ecoli.initial_state`
method to be run before calling
:py:meth:`~vivarium.core.composer.Composer.generate` on this composer.
Expand All @@ -231,8 +232,7 @@ def generate_processes_and_steps(self, config: dict[str, Any]
be loaded from the pickled simulation data object using
:py:meth:`~ecoli.library.sim_data.LoadSimData.get_config_by_name`,
or the string ``"default"`` to indicate that the
:py:data:`~vivarium.core.process.Process.defaults` attribute of
the process should be used as its config.
``defaults`` attribute of the process should be used as its config.
* ``processes``:
Mapping of all process names (:py:class:`str`)
Expand Down Expand Up @@ -497,7 +497,7 @@ def generate_topology(self, config: dict[str, Any]) -> dict[str, tuple[str]]:
Args:
config: Uses the same ``config`` supplied to this composer in
:py:meth:`~ecoli.composites.ecoli_master.Ecoli.__init__` that
:py:class:`~ecoli.composites.ecoli_master.Ecoli` that
was used to generate the processes and steps in
:py:meth:`~ecoli.composites.ecoli_master.Ecoli.generate_processes_and_steps`.
Important key-value pairs include:
Expand Down
170 changes: 146 additions & 24 deletions ecoli/experiments/ecoli_master_sim.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from typing import Optional, Dict, Any

from vivarium.core.engine import Engine
from vivarium.core.process import Process
from vivarium.core.serialize import deserialize_value, serialize_value
from vivarium.library.dict_utils import deep_merge
from vivarium.library.topology import assoc_path
Expand Down Expand Up @@ -334,6 +335,22 @@ def __init__(self, config: dict[str, Any]):
# in case multiple simulations are run with suffix_time = True.
self.experiment_id_base = config['experiment_id']
self.config = config
self.ecoli = None
"""vivarium.core.composer.Composite: Contains the fully instantiated
processes, steps, topologies, and flow necessary to run simulation.
Generated by
:py:meth:`~ecoli.experiments.ecoli_master_sim.EcoliSim.build_ecoli` and
cleared when :py:meth:`~ecoli.experiments.ecoli_master_sim.EcoliSim.run`
is called to potentially free up memory after division."""
self.generated_initial_state = None
"""dict: Fully populated initial state for simulation. Generated by
:py:meth:`~ecoli.experiments.ecoli_master_sim.EcoliSim.build_ecoli` and
cleared when :py:meth:`~ecoli.experiments.ecoli_master_sim.EcoliSim.run`
is called to potentially free up memory after division."""
self.ecoli_experiment = None
"""vivarium.core.engine.Engine: Engine that runs the simulation.
Instantiated by
:py:meth:`~ecoli.experiments.ecoli_master_sim.EcoliSim.run`."""

# Unpack config using Descriptor protocol:
# All of the entries in config are translated to properties
Expand Down Expand Up @@ -370,15 +387,15 @@ def __set__(self, sim, value):

@staticmethod
def from_file(filepath=CONFIG_DIR_PATH + 'default.json') -> 'EcoliSim':
f"""Used to instantiate
"""Used to instantiate
:py:class:`~ecoli.experiments.ecoli_master_sim.EcoliSim` with
a config loaded from the JSON at ``filepath`` by
:py:class:`~ecoli.experiments.ecoli_master_sim.SimConfig`.
Args:
filepath: String filepath of JSON file with config options to
apply on top of the options laid out in
{EcoliSim.default_file_path}
apply on top of the options laid out in the default JSON
located at the default value for ``filepath``.
"""
config = SimConfig()
config.update_from_json(filepath)
Expand All @@ -398,11 +415,25 @@ def from_cli() -> 'EcoliSim':


def _retrieve_processes(self,
processes,
add_processes,
exclude_processes,
swap_processes,
):
processes: list[str],
add_processes: list[str],
exclude_processes: list[str],
swap_processes: dict[str, str],
) -> dict[str, Process]:
"""
Retrieve process classes from :py:data:`~ecoli.processes.process_registry`.
Args:
processes: Base list of process names to retrieve classes for
add_processes: Additional process names to retrieve classes for
exclude_processes: Process names to not retrieve classes for
swap_processes: Mapping of process names to the names of the
processes they should be swapped for. It is assumed that
the swapped processes share the same topologies.
Returns:
Mapping of process names to process classes.
"""
result = {}
for process_name in list(processes.keys()) + list(add_processes):
if process_name in exclude_processes:
Expand All @@ -420,12 +451,31 @@ def _retrieve_processes(self,


def _retrieve_topology(self,
topology,
processes,
swap_processes,
log_updates,
divide,
):
topology: dict[str, dict[str, tuple[str]]],
processes: list[str],
swap_processes: dict[str, str],
log_updates: bool,
) -> dict[str, dict[str, tuple[str]]]:
"""
Retrieves topologies for processes from
:py:data:`~ecoli.processes.registries.topology_registry`.
Args:
topology: Mapping of process names to user-specified topologies.
Will be merged with topology from topology_registry, if exists.
processes: List of process names for which to retrive topologies.
swap_processes: Mapping of process names to the names of processes
to swap them for. It is assumed that swapped processes share
the same topologies. User-specified topologies in ``topology``
under either process name are merged into the topology
retrieved from the topology registry for the topology to be
swapped out.
log_updates: Whether to emit process updates. Adds topology for
``log_update`` port.
Returns:
Mapping of process names to process topologies.
"""
result = {}
original_processes = {v: k for k, v in swap_processes.items()}
for process in processes:
Expand Down Expand Up @@ -456,7 +506,21 @@ def _retrieve_topology(self,
return result


def _retrieve_process_configs(self, process_configs, processes):
def _retrieve_process_configs(self,
process_configs: dict[str, dict[str, Any]],
processes: list[str]) -> dict[str, Any]:
"""
Sets up process configs to be interpreted by
:py:meth:`~ecoli.composites.ecoli_master.Ecoli.generate_processes_and_steps`.
Args:
process_configs: Mapping of process names to user-specified process
configuration dictionaries.
processes: List of process names to set up process config for.
Returns:
Mapping of process names to process configs.
"""
result = {}
for process in processes:
result[process] = process_configs.get(process)
Expand All @@ -473,7 +537,8 @@ def build_ecoli(self):
For all processes in ``config['processes']``:
1. Retrieves process class from
:py:data:`~ecoli.processes.process_topology`
:py:data:`~vivarium.core.registry.process_registry`, which is
populated in ``ecoli/processes/__init__.py``.
2. Retrieves process topology from
:py:data:`~ecoli.processes.registries.topology_registry` and merge
Expand All @@ -488,14 +553,21 @@ def build_ecoli(self):
``True``. Spatial environment config options are loaded from
``config['spatial_environment_config]``. See
``ecoli/composites/ecoli_configs/spatial.json`` for an example.
.. note::
When loading from a saved state with a file name of the format
``vivecoli_t{save time}``, the simulation seed is automatically
set to ``config['seed'] + {save_time}`` to prevent
:py:func:`~ecoli.library.schema.create_unique_indexes` from
generating clashing indices.
"""
# build processes, topology, configs
self.processes = self._retrieve_processes(
self.processes, self.add_processes, self.exclude_processes,
self.swap_processes)
self.topology = self._retrieve_topology(
self.topology, self.processes, self.swap_processes,
self.log_updates, self.divide)
self.log_updates)
self.process_configs = self._retrieve_process_configs(
self.process_configs, self.processes)

Expand Down Expand Up @@ -544,7 +616,11 @@ def build_ecoli(self):
def save_states(self):
"""
Runs the simulation while saving the states of specific
timesteps to jsons.
timesteps to jsons. Automatically invoked by
:py:meth:`~ecoli.experiments.ecoli_master_sim.EcoliSim.run`
if ``config['save'] == True``. State is saved as a JSON that
can be reloaded into a simulation as described in
:py:meth:`~ecoli.composites.ecoli_master.Ecoli.initial_state`.
"""
for time in self.save_times:
if time > self.total_time:
Expand Down Expand Up @@ -590,7 +666,12 @@ def save_states(self):


def run(self):
"""Create and run an EcoliSim experiment. Must run build_ecoli first!"""
"""Create and run an EcoliSim experiment.
.. WARNING::
Run :py:meth:`~ecoli.experiments.ecoli_master_sim.EcoliSim.build_ecoli`
before calling :py:meth:`~ecoli.experiments.ecoli_master_sim.EcoliSim.run`!
"""
metadata = self.get_metadata()
# make the experiment
emitter_config = {'type': self.emitter}
Expand Down Expand Up @@ -652,23 +733,50 @@ def run(self):
report_profiling(self.ecoli_experiment.stats)


def query(self, query=None):
def query(self, query: list[tuple[str]]=None):
"""
Query emitted data.
Args:
query: List of tuple-style paths in the simulation state to
retrieve emitted values for. Returns all emitted data
if ``None``.
Returns:
Dictionary of emitted data in one of two forms.
* Raw data (if ``self.raw_output``): Data is keyed by time
(e.g. ``{0: {'data': ...}, 1: {'data': ...}, ...}``)
* Timeseries: Data is reorganized to match the structure of the
simulation state. Leaf values in the returned dictionary are
lists of the simulation state value over time (e.g.
``{'data': [..., ..., ...]}``).
"""
# Retrieve queried data (all if not specified)
if self.raw_output:
return self.ecoli_experiment.emitter.get_data(query)
else:
return self.ecoli_experiment.emitter.get_timeseries(query)


def merge(self, other):
def merge(self, other: 'EcoliSim'):
"""
Combine settings from this EcoliSim with another, overriding
current settings with those from the other EcoliSim.
Args:
other: Simulation with settings to override current simulation.
"""
deep_merge(self.config, other.config)


def get_metadata(self):
def get_metadata(self) -> dict[str, Any]:
"""
Compiles all simulation settings, git hash, and process list into a single
dictionary. Called by
:py:meth:`~ecoli.experiments.ecoli_master_sim.EcoliSim.to_json_string`.
"""
# create metadata of this experiment to be emitted,
# namely the config of this EcoliSim object
# with an additional key for the current git hash.
Expand All @@ -684,11 +792,25 @@ def get_metadata(self):
return metadata


def to_json_string(self):
def to_json_string(self) -> str:
"""
Serializes simulation setting dictionary along with git hash and
final list of process names. Called by
:py:meth:`~ecoli.experiments.ecoli_master_sim.EcoliSim.export_json`.
"""
return str(serialize_value(self.get_metadata()))


def export_json(self, filename=CONFIG_DIR_PATH + "export.json"):
def export_json(self, filename: str=CONFIG_DIR_PATH + "export.json"):
"""
Saves current simulation settings along with git hash and final list
of process names as a JSON that can be reloaded using
:py:meth:`~ecoli.experiments.ecoli_master_sim.EcoliSim.from_file`.
Args:
filename: Filepath and name for saved JSON (include ``.json``).
"""
with open(filename, 'w') as f:
f.write(self.to_json_string())

Expand Down
2 changes: 1 addition & 1 deletion ecoli/library/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ class UniqueNumpyUpdater:
signal to apply these updates is given by a special process (
:py:class:`ecoli.processes.unique_update.UniqueUpdate`) that is
automatically added to the simulation by
:py:meth:`ecoli.composites.ecoli_master.Ecoli._generate_processes_and_steps`
:py:meth:`ecoli.composites.ecoli_master.Ecoli.generate_processes_and_steps`
"""
def __init__(self):
"""Sets up instance attributes to accumulate updates.
Expand Down

0 comments on commit b43b6d4

Please sign in to comment.