Source code for wholecell.utils.make_media

"""
Functions for making media

# Example use of make_media

### Create media object
> media_obj = Media(raw_data)

### Retrieve stock media
> base_media = media_obj.stock_media['M9_GLC']
> base_media2 = media_obj.stock_media['5X_supplement_EZ']

### Define a dict of ingredients
Ingredients is a dict with molecule ids as the keys.
Each ingredient's value is a dict with {'weight': value * (units.g), 'counts': value * (units.mmol), 'volume': value *  (units.L)}.
Only one of 'weights' (in units.g) or 'counts' (in units.mmol) is required; if both are specified, it will use weight.
If weight or counts is Infinity, it sets the final concentration to inf.
If weight or counts is -Infinity, it sets the final concentration to 0.

Example:
> ingredients = {
        'L-ALPHA-ALANINE': {'weight': 1.78 * units.g, 'volume': 0.025 * units.L},
        'ARG': {'weight': 8.44 * units.g, 'volume': 0.1 * units.L},
        'UREA': {'counts': 102.0 * units.mmol, 'volume': 1.0 * units.L},
        'LEU': {'weight': float("inf") * units.g, 'volume': 0 * units.L},
        'OXYGEN-MOLECULE': {'weight': float("-inf") * units.g, 'volume': 0 * units.L},
    }

### Add ingredients directly into an existing media
> new_media1 = media_obj.add_ingredients(base_media, 0.8 * units.L, ingredients)

### Combine two medias
> new_media2 = media_obj.combine_media(base_media, 0.8 * units.L, base_media2, 0.2 * units.L)

"""

from wholecell.utils import units

INF = float("inf")
NEG_INF = float("-inf")

COUNTS_UNITS = units.mmol
VOLUME_UNITS = units.L
MASS_UNITS = units.g
CONC_UNITS = COUNTS_UNITS / VOLUME_UNITS


[docs] class AddIngredientsError(Exception): pass
[docs] class Media(object): """ A media object is a factory for making new media by either combining two saved media at different volumes (with self.combine_media(), or adding ingredients to a saved media (with self.add_ingredients()). Ingredients can be added by either specifying their weight (in grams) or the counts (in mmol) in addition to the volume. The new media dicts are returned to the caller, and are not saved in this object. A media object holds dicts about stock media in ```self.stock_media``` and the formula weight of environmental molecules in ```self.environment_molecules_fw```, which is needed for mixing in ingredients at weights. """ def __init__(self, raw_data): # get dicts from knowledge base self.environment_molecules_fw = self._get_environment_molecules_fw(raw_data) self.stock_media = self._get_stock_media(raw_data) self.recipes = self._get_recipes(raw_data)
[docs] def _get_environment_molecules_fw(self, raw_data): """get formula weight (units.g / units.mol) for all environmental molecules""" environment_molecules_fw = {} for row in raw_data.condition.environment_molecules: mol = row["molecule id"] fw = row["formula weight"] if fw == "None": environment_molecules_fw[mol] = None else: environment_molecules_fw[mol] = float(fw) * (units.g / units.mol) return environment_molecules_fw
[docs] def _get_stock_media(self, raw_data): """load all stock media""" stock_media = {} for label in vars(raw_data.condition.media): # initiate all molecules with 0 concentrations stock_media[label] = { row["molecule id"]: 0.0 * CONC_UNITS for row in raw_data.condition.environment_molecules } # get non-zero concentrations (assuming units.mmol / units.L) molecule_concentrations = getattr(raw_data.condition.media, label) environment_non_zero_dict = { row["molecule id"]: row["concentration"] for row in molecule_concentrations } # update saved_media with non zero concentrations stock_media[label].update(environment_non_zero_dict) return stock_media
[docs] def _get_recipes(self, raw_data): """load recipes""" recipes = {} for row in raw_data.condition.media_recipes: new_media_id = row["media id"] recipe = {} recipe["base media"] = row["base media"] recipe["added media"] = row.get("added media", None) recipe["ingredients"] = row.get("ingredients", None) recipe["base media volume"] = row.get("base media volume", 0 * units.L) recipe["added media volume"] = row.get("added media volume", 0 * units.L) recipe["ingredients weight"] = row.get("ingredients weight", None) recipe["ingredients counts"] = row.get("ingredients counts", None) recipe["ingredients volume"] = row.get("ingredients volume", 0 * units.L) recipes[new_media_id] = recipe return recipes
[docs] def make_saved_media(self): """make all the media recipes in self.recipes""" self.saved_media = {} for new_media_id in self.recipes: new_media = self.make_recipe(new_media_id) self.saved_media[new_media_id] = new_media return self.saved_media
[docs] def make_recipe(self, media_id): """make a single media recipe from self.recipes""" recipe = self.recipes[media_id] base_id = recipe["base media"] added_media_id = recipe["added media"] ingredient_ids = recipe["ingredients"] base_media = self.stock_media[base_id] base_vol = recipe["base media volume"] if added_media_id: added_media = self.stock_media[added_media_id] added_vol = recipe["added media volume"] new_media = self.combine_media(base_media, base_vol, added_media, added_vol) base_media = new_media base_vol += added_vol if ingredient_ids: added_weight = recipe.get("ingredients weight", []) added_counts = recipe.get("ingredients counts", []) added_vol = recipe.get( "ingredients volume" ) # the row is a list with units.L, even an empty list is read. ingredients = {ingred_id: {} for ingred_id in ingredient_ids} for index, ingred_id in enumerate(ingredient_ids): if len(added_weight): ingredients[ingred_id]["weight"] = added_weight[index] if len(added_counts): ingredients[ingred_id]["counts"] = added_counts[index] if len(added_vol): ingredients[ingred_id]["volume"] = added_vol[index] else: ingredients[ingred_id]["volume"] = 0 * units.L new_media = self.add_ingredients(base_media, base_vol, ingredients) if not added_media_id and not ingredient_ids: new_media = base_media # remove concentration units, setting at CONC_UNITS unitless_new_media = { mol: conc.asNumber(CONC_UNITS) for mol, conc in new_media.items() } return unitless_new_media
[docs] def combine_media(self, base_media, base_media_volume, mix_media, mix_media_volume): """ Combines two medias and returns a new media Args: base_media, mix_media (dict): dicts with {molecule_id: concentration} base_media_volume, mix_media_volume (unum): the volumes of base_media and mix_media (floats) with a volume units (i.e. units.L) Returns: new_media (dict): {molecule_id: concentrations} """ # intialize new_media new_media = {mol_id: 0.0 * CONC_UNITS for mol_id, conc in base_media.items()} # get new_media volume new_volume = base_media_volume + mix_media_volume for mol_id, base_conc in base_media.items(): mix_conc = mix_media[mol_id] if base_conc.asNumber() == INF or mix_conc.asNumber() == INF: new_media[mol_id] = INF * CONC_UNITS else: base_counts = base_conc * base_media_volume mix_counts = mix_conc * mix_media_volume new_counts = base_counts + mix_counts new_conc = new_counts / new_volume # update media new_media[mol_id] = new_conc return new_media
[docs] def add_ingredients(self, base_media, base_media_volume, ingredients): """ Combines ingredients to existing media to make new media. Args: base_media (dict): {molecule_id: concentrations} base_media_volume: ingredients (dict): keys are ingredient ids, values are dicts with weight, counts, volume. Only one of weights (in g) or counts (in mmol) is needed; if both are specified, it will use weight. If weight or counts is Infinity, the new concentration is set to inf. If the weight or counts is -Infinity, the new concentration is set to 0. Example format of ingredients: {mol_id_1: {'weight': 1.78 * units.g, 'volume': 0.025 * units.L), mol_id_2: {'counts': 0.2 * units.mmol, 'volume': 0.1 * units.L), } Returns: new_media (dict): {molecule_id: concentrations} """ # intialize new_media new_media = {mol_id: 0.0 * CONC_UNITS for mol_id, conc in base_media.items()} # get new_media volume ingredients_volume = 0 * VOLUME_UNITS for quantities in ingredients.values(): ingredients_volume += quantities["volume"] new_volume = base_media_volume + ingredients_volume # get new_media concentrations from mixing ingredients for mol_id, base_conc in base_media.items(): if mol_id in ingredients: base_counts = base_conc * base_media_volume quantities = ingredients[mol_id] weight = quantities.get("weight", None) mix_counts = quantities.get("counts", None) # calculate mix_counts from weight. # This will overwrite added counts if those were specified if weight is not None: if weight.asNumber() == INF: mix_counts = INF * COUNTS_UNITS elif weight.asNumber() == NEG_INF: mix_counts = NEG_INF * COUNTS_UNITS elif weight.asNumber() >= 0: if self.environment_molecules_fw[mol_id] is not None: fw = self.environment_molecules_fw[mol_id] mix_counts = weight / fw else: raise AddIngredientsError( "No fw defined for {} in environment_molecules.tsv".format( mol_id ) ) else: raise AddIngredientsError( "Negative weight given for {}".format(mol_id) ) elif mix_counts is None: raise AddIngredientsError( "No added added weight or counts for {}".format(mol_id) ) # get new concentration # make infinite concentration of ingredient if mix_counts is Infinity if mix_counts.asNumber() == INF: new_media[mol_id] = INF * CONC_UNITS # remove ingredient from media if mix_counts is -Infinity # this will override infinite concentrations in base_media elif mix_counts.asNumber() == NEG_INF: new_media[mol_id] = 0.0 * CONC_UNITS else: new_counts = base_counts + mix_counts new_conc = new_counts / new_volume new_media[mol_id] = new_conc # if mol_id is not in ingredients, dilute its concentration in new_media else: base_counts = base_conc * base_media_volume new_conc = base_counts / new_volume new_media[mol_id] = new_conc return new_media
[docs] def make_timeline(self, timeline_str): """ Make a timeline from a string Args: timeline_str (str): 'time1 media_id1, time2 media_id2' Returns: timeline (list[tuple]): a list of tuples with (time (float), media_id (str)) TODO (Eran) make a parsing expression grammar for this: https://github.com/erikrose/parsimonious TODO (Eran) expand capabilities to also pass in ingredients to be added from the prior event """ timeline = [] events_str = timeline_str.split(", ") for event in events_str: time, media = event.split() timeline.append((float(time), media)) return timeline