def get_dgf_priors(mi: MaudInput) -> Tuple[pd.Series, pd.DataFrame]: """Given a Maud input, get a multivariate prior from equilibrator. Returns a pandas Series of prior means and a pandas DataFrame of covariances. Both are indexed by metabolite ids. :param mi: A MaudInput object """ cc = ComponentContribution() mu = [] sigmas_fin = [] sigmas_inf = [] external_ids = {m.id: m.inchi_key for m in mi.kinetic_model.metabolites} met_ix = pd.Index(mi.stan_coords.metabolites, name="metabolite") for met_id in met_ix: external_id = external_ids[met_id] if external_id is None: raise ValueError(f"metabolite {met_id} has no external id.") c = cc.get_compound(external_id) if isinstance(c, Compound): mu_c, sigma_fin_c, sigma_inf_c = cc.standard_dg_formation(c) mu.append(mu_c) sigmas_fin.append(sigma_fin_c) sigmas_inf.append(sigma_inf_c) else: raise ValueError(f"cannot find compound for metabolite {met_id}" f" with external id {external_id}.") sigmas_fin = np.array(sigmas_fin) sigmas_inf = np.array(sigmas_inf) cov = sigmas_fin @ sigmas_fin.T + 1e6 * sigmas_inf @ sigmas_inf.T return ( pd.Series(mu, index=met_ix, name="prior_mean_dgf").round(10), pd.DataFrame(cov, index=met_ix, columns=met_ix).round(10), )
def get_dgf_priors(mi: MaudInput) -> Tuple[pd.Series, pd.DataFrame]: """Given a Maud input, get a multivariate prior from equilibrator. Returns a pandas Series of prior means and a pandas DataFrame of covariances. Both are indexed by metabolite ids. :param mi: A MaudInput object """ cc = ComponentContribution() mu = [] sigmas_fin = [] sigmas_inf = [] met_ix = pd.Index(mi.stan_coords.metabolites, name="metabolite") met_order = [m.id for m in mi.kinetic_model.metabolites] for m in mi.kinetic_model.metabolites: external_id = m.id if m.inchi_key is None else m.inchi_key c = cc.get_compound(external_id) if isinstance(c, Compound): mu_c, sigma_fin_c, sigma_inf_c = cc.standard_dg_formation(c) mu_c += c.transform( cc.p_h, cc.ionic_strength, cc.temperature, cc.p_mg ).m_as("kJ/mol") mu.append(mu_c) sigmas_fin.append(sigma_fin_c) sigmas_inf.append(sigma_inf_c) else: raise ValueError( f"cannot find compound for metabolite {m.id}" f" with external id {external_id}." "\nConsider setting the field metabolite_inchi_key" " if you haven't already." ) sigmas_fin = np.array(sigmas_fin) sigmas_inf = np.array(sigmas_inf) cov = sigmas_fin @ sigmas_fin.T + 1e6 * sigmas_inf @ sigmas_inf.T cov = ( pd.DataFrame(cov, index=met_order, columns=met_order) .loc[met_ix, met_ix] .round(10) ) mu = ( pd.Series(mu, index=met_order, name="prior_mean_dgf") .loc[met_ix] .round(10) ) return mu, cov
def runThermo( pathway: rpPathway, cc: ComponentContribution=None, ph: float=DEFAULT_pH, ionic_strength: float=DEFAULT_ionic_strength, pMg: float=DEFAULT_pMg, compound_substitutes: Dict = None, logger: Logger = getLogger(__name__) ) -> Dict: """Given a tar input file, perform thermodynamics analysis for each rpSBML file. :param inFile: The path to the input file :param outFile: The path to the output file :param pathway_id: The id of the heterologous pathway of interest :param ph: The pH of the host organism (Default: 7.5) :param ionic_strength: Ionic strenght of the host organism (Default: 0.25M) :param pMg: The pMg of the host organism (Default: 3.0) :param temp_k: The temperature of the host organism in Kelvin (Default: 298.15) :param stdev_factor: The standard deviation factor to calculate MDF (Default: 1.96) :type pathway: Dict :type pathway_id: str :type ph: float :type ionic_strength: float :type pMg: float :type temp_k: float :type logger: Logger :rtype: Dict :return: Pathway updated with thermodynalics values """ print_title( txt='Pathway Reactions', logger=logger, waiting=False ) for rxn in pathway.get_list_of_reactions(): print_reaction( rxn=rxn, logger=logger ) ## INTERMEDIATE COMPOUNDS # Optimise the production of target # and remove (if possible) intermediate compounds reactions = remove_compounds( compounds=pathway.get_intermediate_species(), reactions=pathway.get_list_of_reactions(), rxn_target_id=pathway.get_target_rxn_id(), logger=logger ) ## eQuilibrator if cc is None: cc = initThermo( ph, ionic_strength, pMg, logger ) # Search for the key ID known by eQuilibrator cc_species = {} substituted_species = {} sep = '__64__' if compound_substitutes is None: compound_substitutes = read_compound_substitutes( os_path.join( os_path.dirname(os_path.realpath(__file__)), 'data', 'compound_substitutes.csv' ) ) for spe in pathway.get_species(): spe_split = spe.get_id().split(sep) if len(spe_split) > 1: _compound_substitutes = {k+sep+spe_split[1]: v for k, v in compound_substitutes.items()} else: _compound_substitutes = deepcopy(compound_substitutes) # If the specie is listed in substitutes file, then take search values from it # Check if starts with in case of compound names are like CMPD_NAME__64__COMPID if spe.get_id() in _compound_substitutes: cc_species[spe.get_id()] = search_equilibrator_compound( cc=cc, id=_compound_substitutes[spe.get_id()]['id'], inchikey=_compound_substitutes[spe.get_id()]['inchikey'], inchi=_compound_substitutes[spe.get_id()]['inchi'], logger=logger ) # Else, take search values from rpCompound else: cc_species[spe.get_id()] = search_equilibrator_compound( cc=cc, id=spe.get_id(), inchikey=spe.get_inchikey(), inchi=spe.get_inchi(), smiles=spe.get_smiles(), logger=logger ) if cc_species[spe.get_id()] != {}: if spe.get_id() != cc_species[spe.get_id()]['id']: substituted_species[spe.get_id()] = cc_species[spe.get_id()][cc_species[spe.get_id()]['cc_key']] else: logger.warning(f'Compound {spe.get_id()} has not been found within eQuilibrator cache') # Store thermo values for the net reactions # and for each of the reactions within the pathway results = { 'net_reaction': {}, 'optimized_net_reaction': Reaction.sum_stoichio(reactions), 'reactions': {}, 'optimized_reactions': { rxn.get_id(): rxn for rxn in reactions }, 'species': {}, 'substituted_species': substituted_species } # Get the formation energy for each compound for spe_id, cc_spe in cc_species.items(): try: value = cc.standard_dg_formation( cc.get_compound( cc_spe[cc_spe['cc_key']] ) )[0] # get .mu except Exception as e: value = None logger.debug(e) if value is None: value = 'NaN' results['species'][spe_id] = { 'standard_dg_formation': { 'value': value, 'units': 'kilojoule / mole' } } # Build the list of IDs known by eQuilibrator species_cc_ids = {} for spe_id, cc_spe in cc_species.items(): if cc_spe == {}: species_cc_ids[spe_id] = spe_id else: species_cc_ids[spe_id] = cc_spe[cc_spe['cc_key']] ## REACTIONS # Compute thermo for each reaction for rxn in pathway.get_list_of_reactions(): results['reactions'][rxn.get_id()] = eQuilibrator( species_stoichio=rxn.get_species(), species_ids=species_cc_ids, cc=cc, logger=logger ) ## THERMO print_title( txt='Computing thermodynamics (eQuilibrator)...', logger=logger, waiting=True ) results['net_reaction'] = eQuilibrator( species_stoichio=Reaction.sum_stoichio(reactions), species_ids=species_cc_ids, cc=cc, logger=logger ) print_OK(logger) # Write results into the pathway write_results_to_pathway(pathway, results, logger) return results
class Thermodynamics: """Class to calculate thermodynamics of Pickaxe runs. Thermodynamics allows for the calculation of: 1) Standard ∆G' of formation 2) Standard ∆G'o of reaction 3) Physiological ∆G'm of reaction 4) Adjusted ∆G' of reaction eQuilibrator objects can also be obtained from r_ids and c_ids. Parameters ---------- mongo_uri: str URI of the mongo database. client: MongoClient Connection to Mongo. CC: ComponentContribution eQuilibrator Component Contribution object to calculate ∆G with. lc: LocalCompoundCache The local compound cache to generate eQuilibrator compounds from. """ def __init__( self, ): # Mongo params self.mongo_uri = None self.client = None self._core = None # eQ params self.CC = ComponentContribution() self.lc = None self._water = None def load_mongo(self, mongo_uri: Union[str, None] = None): if mongo_uri: self.mongo_uri = mongo_uri self.client = MongoClient(mongo_uri) else: self.mongo_uri = "localhost:27017" self.client = MongoClient() self._core = self.client["core"] def _all_dbs_loaded(self): if self.client and self._core and self.lc: return True else: print("Load connection to Mongo and eQuilibrator local cache.") return False def _eq_loaded(self): if self.lc: return True else: print("Load eQulibrator local cache.") return False def _reset_CC(self): """reset CC back to defaults""" self.CC.p_h = default_physiological_p_h self.CC.p_mg = default_physiological_p_mg self.CC.temperature = default_physiological_temperature self.CC.ionic_strength = default_physiological_ionic_strength def load_thermo_from_postgres( self, postgres_uri: str = "postgresql:///eq_compounds" ) -> None: """Load a LocalCompoundCache from a postgres uri for equilibrator. Parameters ---------- postgres_uri : str, optional uri of the postgres DB to use, by default "postgresql:///eq_compounds" """ self.lc = LocalCompoundCache() self.lc.ccache = CompoundCache(create_engine(postgres_uri)) self._water = self.lc.get_compounds("O") def load_thermo_from_sqlite( self, sqlite_filename: str = "compounds.sqlite" ) -> None: """Load a LocalCompoundCache from a sqlite file for equilibrator. compounds.sqlite can be generated through LocalCompoundCache's method generate_local_cache_from_default_zenodo Parameters ---------- sqlite_filename: str filename of the sqlite file to load. """ self.lc = LocalCompoundCache() self.lc.load_cache(sqlite_filename) self._water = self.lc.get_compounds("O") def get_eQ_compound_from_cid( self, c_id: str, pickaxe: Pickaxe = None, db_name: str = None ) -> Union[Compound, None]: """Get an equilibrator compound for a given c_id from the core. Attempts to retrieve a compound from the core or a specified db_name. Parameters ---------- c_id : str compound ID for MongoDB lookup of a compound. pickaxe : Pickaxe pickaxe object to look for the compound in, by default None. db_name : str Database to look for compound in before core database, by default None. Returns ------- equilibrator_assets.compounds.Compound eQuilibrator Compound """ # Find locally in pickaxe compound_smiles = None if pickaxe: if c_id in pickaxe.compounds: compound_smiles = pickaxe.compounds[c_id]["SMILES"] else: return None # Find in mongo db elif self._all_dbs_loaded(): if db_name: compound = self.client[db_name].compounds.find_one( {"_id": c_id}, {"SMILES": 1} ) if compound: compound_smiles = compound["SMILES"] # No cpd smiles from database name if not compound_smiles: compound = self._core.compounds.find_one({"_id": c_id}, {"SMILES": 1}) if compound: compound_smiles = compound["SMILES"] # No compound_smiles at all if not compound_smiles or "*" in compound_smiles: return None else: eQ_compound = self.lc.get_compounds( compound_smiles, bypass_chemaxon=True, save_empty_compounds=True ) return eQ_compound def standard_dg_formation_from_cid( self, c_id: str, pickaxe: Pickaxe = None, db_name: str = None ) -> Union[float, None]: """Get standard ∆Gfo for a compound. Parameters ---------- c_id : str Compound ID to get the ∆Gf for. pickaxe : Pickaxe pickaxe object to look for the compound in, by default None. db_name : str Database to look for compound in before core database, by default None. Returns ------- Union[float, None] ∆Gf'o for a compound, or None if unavailable. """ eQ_cpd = self.get_eQ_compound_from_cid(c_id, pickaxe, db_name) if not eQ_cpd: return None dgf = self.CC.standard_dg_formation(eQ_cpd) dgf = dgf[0] return dgf def get_eQ_reaction_from_rid( self, r_id: str, pickaxe: Pickaxe = None, db_name: str = None ) -> Union[PhasedReaction, None]: """Get an eQuilibrator reaction object from an r_id. Parameters ---------- r_id : str Reaction id to get object for. pickaxe : Pickaxe pickaxe object to look for the compound in, by default None. db_name : str Database to look for reaction in. Returns ------- PhasedReaction eQuilibrator reactiono to calculate ∆Gr with. """ if pickaxe: if r_id in pickaxe.reactions: reaction_info = pickaxe.reactions[r_id] else: return None elif db_name: mine = self.client[db_name] reaction_info = mine.reactions.find_one({"_id": r_id}) if not reaction_info: return None else: return None reactants = reaction_info["Reactants"] products = reaction_info["Products"] lhs = " + ".join(f"{r[0]} {r[1]}" for r in reactants) rhs = " + ".join(f"{p[0]} {p[1]}" for p in products) reaction_string = " => ".join([lhs, rhs]) compounds = set([r[1] for r in reactants]) compounds.update(tuple(p[1] for p in products)) eQ_compound_dict = { c_id: self.get_eQ_compound_from_cid(c_id, pickaxe, db_name) for c_id in compounds } if not all(eQ_compound_dict.values()): return None if "X73bc8ef21db580aefe4dbc0af17d4013961d9d17" not in compounds: eQ_compound_dict["water"] = self._water eq_reaction = Reaction.parse_formula(eQ_compound_dict.get, reaction_string) return eq_reaction def physiological_dg_prime_from_rid( self, r_id: str, pickaxe: Pickaxe = None, db_name: str = None ) -> Union[pint.Measurement, None]: """Calculate the ∆Gm' of a reaction. Parameters ---------- r_id : str ID of the reaction to calculate. pickaxe : Pickaxe pickaxe object to look for the compound in, by default None. db_name : str MINE the reaction is found in. Returns ------- pint.Measurement The calculated ∆G'm. """ eQ_reaction = self.get_eQ_reaction_from_rid(r_id, pickaxe, db_name) if not eQ_reaction: return None dGm_prime = self.CC.physiological_dg_prime(eQ_reaction) return dGm_prime def standard_dg_prime_from_rid( self, r_id: str, pickaxe: Pickaxe = None, db_name: str = None ) -> Union[pint.Measurement, None]: """Calculate the ∆G'o of a reaction. Parameters ---------- r_id : str ID of the reaction to calculate. pickaxe : Pickaxe pickaxe object to look for the compound in, by default None. db_name : str MINE the reaction is found in. Returns ------- pint.Measurement The calculated ∆G'o. """ eQ_reaction = self.get_eQ_reaction_from_rid(r_id, pickaxe, db_name) if not eQ_reaction: return None dG0_prime = self.CC.standard_dg_prime(eQ_reaction) return dG0_prime def dg_prime_from_rid( self, r_id: str, pickaxe: Pickaxe = None, db_name: str = None, p_h: Q_ = default_physiological_p_h, p_mg: Q_ = default_physiological_p_mg, ionic_strength: Q_ = default_physiological_ionic_strength, ) -> Union[pint.Measurement, None]: """Calculate the ∆G' of a reaction. Parameters ---------- r_id : str ID of the reaction to calculate. pickaxe : Pickaxe pickaxe object to look for the compound in, by default None. db_name : str MINE the reaction is found in. p_h : Q_ pH of system. p_mg: Q_ pMg of the system. ionic_strength: Q_ ionic strength of the system. Returns ------- pint.Measurement The calculated ∆G'. """ eQ_reaction = self.get_eQ_reaction_from_rid(r_id, pickaxe, db_name) if not eQ_reaction: return None self.CC.p_h = p_h self.CC.p_mg = p_mg self.CC.ionic_strength = ionic_strength dG_prime = self.CC.dg_prime(eQ_reaction) self._reset_CC() return dG_prime