"""
Simulation data for external state
This base class includes all data associated with states external to the cells.
Initializes the environment using conditions and time series from raw_data.
- saved_timelines: a dictionary of all timelines.
- current_timeline_id: a string specifying the timelines
used for the current simulation.
- current_media: a dictionary of molecules (keys) and
their concentrations (values).
- saved_media: a dictionary of all media, each entry
itself a dictionary molecules (keys) and their concentrations (values).
"""
import numpy as np
from wholecell.utils import units
from wholecell.utils.make_media import Media
from typing import Any
# threshold (units.mmol / units.L) separates concentrations that are import constrained with
# max flux = 0 from unconstrained molecules.
IMPORT_CONSTRAINT_THRESHOLD = 1e-5
[docs]
class ExternalState(object):
"""External State"""
def __init__(self, raw_data, sim_data):
# make media object
self.make_media = Media(raw_data)
self.carbon_sources = sim_data.molecule_groups.carbon_sources
self._initialize_environment(raw_data)
self.all_external_exchange_molecules = (
self._get_all_external_exchange_molecules(raw_data)
)
self.secretion_exchange_molecules = self._get_secretion_exchange_molecules(
raw_data
)
[docs]
def _get_all_external_exchange_molecules(self, raw_data):
"""
Returns:
list[str]: all external exchange molecules
"""
externalExchangeData = []
# initiate all molecules with 0 concentrations
for row in raw_data.condition.environment_molecules:
externalExchangeData.append(
row["molecule id"] + row["exchange molecule location"]
)
return externalExchangeData
[docs]
def _get_secretion_exchange_molecules(self, raw_data):
"""
Returns:
set[str]: all secretion exchange molecules
"""
secretionExchangeMolecules = []
for secretion in raw_data.secretions:
if secretion["lower bound"] and secretion["upper bound"]:
# "non-growth associated maintenance", not included in our metabolic model
continue
else:
secretionExchangeMolecules.append(secretion["molecule id"])
return set(secretionExchangeMolecules)
[docs]
def _initialize_environment(self, raw_data):
self.import_constraint_threshold = IMPORT_CONSTRAINT_THRESHOLD
# create a dictionary with all saved timelines
self.saved_timelines = {}
for row in raw_data.condition.timelines_def:
timeline_id = row["timeline"]
timeline_str = row["events"]
new_timeline = self.make_media.make_timeline(timeline_str)
self.saved_timelines[timeline_id] = new_timeline
# set default current_timeline_id to None, this can be overwritten by the timelines variant
self.current_timeline_id = None
# make a dictionary with all media conditions specified by media_recipes
self.saved_media = self.make_media.make_saved_media()
# make mapping from external molecule to exchange molecule
self.env_to_exchange_map = {
mol["molecule id"]: mol["molecule id"] + mol["exchange molecule location"]
for mol_index, mol in enumerate(raw_data.condition.environment_molecules)
}
self.exchange_to_env_map = {v: k for k, v in self.env_to_exchange_map.items()}
# make dict with exchange molecules for all saved environments, using env_to_exchange_map
self.exchange_dict = {}
for media, concentrations in self.saved_media.items():
self.exchange_dict[media] = {
self.env_to_exchange_map[mol]: conc
for mol, conc in concentrations.items()
}
[docs]
def exchange_data_from_concentrations(
self, molecules: dict[str, float]
) -> dict[str, Any]:
"""
Update importExchangeMolecules for FBA based on current nutrient concentrations.
This provides a simple type of transport to accommodate changing nutrient
concentrations in the environment. Transport is modeled as a binary switch:
When there is a high concentrations of environment nutrients, transporters
are unconstrained and nutrients are transported as needed by metabolism.
When concentrations fall below the threshold, that nutrient's transport
is constrained to max flux of 0.
Args:
molecules: external molecules (no location tag) with external concentration,
concentration can be inf
Returns dict with the following keys:
externalExchangeMolecules (set[str]): all exchange molecules (with
location tag), includes both import and secretion exchanged molecules
importExchangeMolecules (set[str]): molecules (with location tag) that
can be imported from the environment into the cell
importConstrainedExchangeMolecules (dict[str, float with mol/mass/time units]):
constrained molecules (with location tag) with upper bound flux constraints
importUnconstrainedExchangeMolecules (set[str]): exchange molecules
(with location tag) that do not have an upper bound on their flux
secretionExchangeMolecules (set[str]): molecules (with location tag)
that can be secreted by the cell into the environment
"""
externalExchangeMolecules = set()
importExchangeMolecules = set()
secretionExchangeMolecules = self.secretion_exchange_molecules
oxygen_id = "OXYGEN-MOLECULE[p]"
exchange_molecules = {
self.env_to_exchange_map[mol]: conc for mol, conc in molecules.items()
}
# Unconstrained uptake if greater than import threshold
importUnconstrainedExchangeMolecules = {
molecule_id
for molecule_id, concentration in exchange_molecules.items()
if concentration >= self.import_constraint_threshold
}
importExchangeMolecules.update(importUnconstrainedExchangeMolecules)
externalExchangeMolecules.update(importUnconstrainedExchangeMolecules)
# TODO: functionalize limits based on concentrations of transporters and environment
# Limit carbon uptake if present depending on the presence of oxygen
importConstrainedExchangeMolecules = {}
for carbon_source_id in self.carbon_sources:
if carbon_source_id in importUnconstrainedExchangeMolecules:
if oxygen_id in importUnconstrainedExchangeMolecules:
importConstrainedExchangeMolecules[carbon_source_id] = 20.0 * (
units.mmol / units.g / units.h
)
else:
importConstrainedExchangeMolecules[carbon_source_id] = 100.0 * (
units.mmol / units.g / units.h
)
importUnconstrainedExchangeMolecules.remove(carbon_source_id)
externalExchangeMolecules.update(secretionExchangeMolecules)
return {
"externalExchangeMolecules": externalExchangeMolecules,
"importExchangeMolecules": importExchangeMolecules,
"importConstrainedExchangeMolecules": importConstrainedExchangeMolecules,
"importUnconstrainedExchangeMolecules": importUnconstrainedExchangeMolecules,
"secretionExchangeMolecules": secretionExchangeMolecules,
}
[docs]
def get_import_constraints(self, unconstrained, constrained, units):
"""
Returns:
unconstrained_molecules (list[bool]): the indices of all
importUnconstrainedExchangeMolecules in
self.all_external_exchange_molecules are true, the rest as false
constrained_molecules (list[bool]): the indices of all
importConstrainedExchangeMolecules in
self.all_external_exchange_molecules are true, the rest as false
constraints (list[float]): uptake constraints for each molecule
that is constrained, nan for no constraint
"""
# molecules from all_external_exchange_molecules set to 'true' if they are current importExchangeMolecules.
unconstrained_molecules = [
molecule_id in unconstrained
for molecule_id in self.all_external_exchange_molecules
]
# molecules from all_external_exchange_molecules set to 'true' if they are current importConstrainedExchangeMolecules.
constrained_molecules = [
molecule_id in constrained
for molecule_id in self.all_external_exchange_molecules
]
constraints = [
constrained.get(molecule_id, np.nan * units).asNumber(units)
for molecule_id in self.all_external_exchange_molecules
]
return unconstrained_molecules, constrained_molecules, constraints