def initialze_cc(pH=7.0, pMg=3.0, temperature=298.15, ionic_strength=0.25): cc = ComponentContribution() cc.p_h = Q_(pH) cc.p_mg = Q_(pMg) cc.ionic_strength = Q_(str(ionic_strength) + "M") cc.temperature = Q_(str(temperature) + "K") return cc
def __init__( self, eq_uri=None, dg_max=0, p_h=7, ionic_strength=0, p_mg=3, physiological=False, generation_list=[], last_generation_only=False, ) -> None: self._filter_name = "Thermodynamic Filter" self.dg_max = Q_(f"{dg_max}kJ/mol") self.p_h = Q_(f"{p_h}") self.ionic_strength = Q_(f"{ionic_strength}M") self.p_mg = Q_(f"{p_mg}") self.physiological = physiological self.generation_list = generation_list self.last_generation_only = last_generation_only self.thermo = Thermodynamics() if not eq_uri: eq_uri = "" if "post" in eq_uri: self.thermo.load_thermo_from_postgres(eq_uri) elif "sql" in eq_uri: self.thermo.load_thermo_from_sqlite(eq_uri) else: self.thermo.load_thermo_from_sqlite()
def _choose_items_to_filter(self, pickaxe: Pickaxe, processes: int = 1) -> Set[str]: """ Check the compounds against the MW constraints and return compounds to filter. """ cpds_remove_set = set() rxns_remove_set = set() # TODO put these statements together # No reactions to filter for if len(pickaxe.reactions) == 0: print("No reactions to calculate ∆Gr for.") return cpds_remove_set, rxns_remove_set if self.last_generation_only and pickaxe.generation != self.generation: print("Not filtering for this generation using thermodynamics.") return cpds_remove_set, rxns_remove_set if self.generation_list and (self.generation - 1) not in self.generation_list: print("Not filtering for this generation using thermodynamics.") return cpds_remove_set, rxns_remove_set print(f"Filtering Generation {pickaxe.generation} " f"with ∆G <= {self.dg_max} at pH={self.p_h}, " f"I={self.ionic_strength}, pMg={self.p_mg}") reactions_to_check = [] for cpd in pickaxe.compounds.values(): # Compounds are in generation and correct type if cpd["Generation"] == pickaxe.generation and cpd["Type"] not in [ "Coreactant", "Target Compound", ]: reactions_to_check.extend(cpd["Product_of"]) reactions_to_check = set(reactions_to_check) for rxn_id in reactions_to_check: if self.physiological: rxn_dg = self.thermo.physiological_dg_prime_from_rid( r_id=rxn_id, pickaxe=pickaxe) else: rxn_dg = self.thermo.dg_prime_from_rid( r_id=rxn_id, pickaxe=pickaxe, p_h=Q_(f"{self.p_h}"), ionic_strength=Q_(f"{self.ionic_strength}"), p_mg=Q_(f"{self.p_mg}"), ) if rxn_dg >= self.dg_max: rxns_remove_set.add(rxn_id) return cpds_remove_set, rxns_remove_set
def major_ms(self, p_mg=14): try: return self._major_ms except AttributeError: self._major_ms = self.abundant_ms( pH=Q_(self.model.compartment_info["pH"][self.compartment]), I=Q_( str(self.model.compartment_info["I"][self.compartment]) + " M"), temperature=Q_(str(default_T) + " K"), pMg=Q_(self.model.compartment_info["pMg"][self.compartment]), ) return self._major_ms
def getFreeEnergyData(GEM, work_directory='', pH_i=None, Ionic_strength=None, dG0_uncertainty_threshold=None): # Load equilibrator data eq_api = ComponentContribution(p_h=Q_(str(pH_i)), ionic_strength=Q_(Ionic_strength)) # Get iJO1366/iML1515 reactions as KEGG reaction strings GEM2KEGG = pd.read_csv(work_directory + '/iJO1366_reactions.csv', index_col=0) # Prepare dictionary with dG0 def originallyIrreversible(reaction_id): return 'forward' not in reaction_id and 'backward' not in reaction_id dG0_data = {} GEM_rxns = np.array([rxn.id for rxn in GEM.reactions]) for rxn_id in GEM_rxns: try: if originallyIrreversible(rxn_id): id, direction = rxn_id, '' else: id, direction = re.split('_(forward|backward)', rxn_id)[:2] rxn = parse_reaction_formula(GEM2KEGG.loc[id.lower()]['formula']) dG0_prime = eq_api.standard_dg_prime( rxn).value.magnitude # kJ/mmol dG0_uncertainty = eq_api.standard_dg_prime( rxn).error.magnitude # kJ/mol if dG0_uncertainty < abs(dG0_uncertainty_threshold * dG0_prime): # remove uncertain dG0 data if 'backward' in direction: dG0_data[rxn_id] = { 'dG0': -dG0_prime, 'error': dG0_uncertainty } else: dG0_data[rxn_id] = { 'dG0': dG0_prime, 'error': dG0_uncertainty } except Exception: pass return dG0_data
def build_thermo_from_equilibrator(model, T=TEMPERATURE_0): """Build `thermo_data` structure from a cobra Model. The structure of the returned dictionary is specified in the pyTFA [documentation](https://pytfa.readthedocs.io/en/latest/thermoDB.html). :param model: cobra.Model :return thermo_data: dict to be passed as argument to initialize a `ThermoModel`. """ global ccache if ccache is None: ccache = create_compound_cache_from_quilt() logger.debug("eQuilibrator compound cache is loaded.") cc = ComponentContribution() cc.temperature = Q_(str(T) + "K") thermo_data = {"name": "eQuilibrator", "units": "kJ/mol", "cues": {}} met_to_comps = compat.map_cobra_metabolites(ccache, model.metabolites) thermo_data["metabolites"] = [ compound_to_entry(met, cc) for met in met_to_comps ] return thermo_data
def test_dgr_prime(pk_transformed): low_p_h = Q_(5) high_p_h = Q_(9) low_ph_dgp = thermo.dg_prime_from_rid( "R6036f02cd619e4515f5180a4d650c27b7a8b40b59dc130a6c8de526d5adaeb0e", pk_transformed, p_h=low_p_h, ) high_ph_dgp = thermo.dg_prime_from_rid( "R6036f02cd619e4515f5180a4d650c27b7a8b40b59dc130a6c8de526d5adaeb0e", pk_transformed, p_h=high_p_h, ) assert abs(high_ph_dgp.value.magnitude) == pytest.approx(4, 7) assert abs(high_ph_dgp.value.magnitude) == pytest.approx(21, 23)
def calculate_delG_f(self): """Calculates the standard transformed Gibbs formation energy of compound using component contribution method. pH, Ionic strength values are taken from model's compartment_info attribute Returns: float -- Transformed Gibbs energy of formation adjusted to pH, ionic strength of metabolite """ std_dG_f = self.compound_vector @ mu if self.compound_vector.any(): transform = self.equilibrator_accession.transform( p_h=Q_(self.model.compartment_info["pH"][self.compartment]), ionic_strength=Q_( str(self.model.compartment_info["I"][self.compartment]) + " M"), temperature=Q_(str(default_T) + " K"), ) return std_dG_f[0] + transform.to_base_units().magnitude * 1e-3 else: return std_dG_f[0]
def get_dGs(rxn_list: list, file_bigg_kegg_ids: str, pH: float = 7.0, ionic_strength: float = 0.1, digits: int = 2) -> dict: """ Given a plain text file with reactions in the form R_FBA: m_g3p_c + m_dhap_c <-> m_fdp_c and a file with a mapping between bigg and kegg ids, returns the standard gibbs energy and respective uncertainty for each reaction. It skips exchange reactions, which should start with 'R_EX_'. Args: file_rxns: path to file with plain text reactions. file_bigg_kegg_ids: path to file with mapping between bigg and kegg ids. pH: pH value to use to calculate standard Gibbs energies. ionic_strength: ionic strength value to use to calculate standard Gibbs energies. digits: number of digits to round standard gibbs energies and respective uncertainty. Returns: Dictionary with bigg reaction ids as keys and (standard Gibbs energy, uncertainty) as values. """ map_bigg_to_kegg_ids = pd.read_csv(file_bigg_kegg_ids, index_col=0) rxn_dict = convert_rxns_to_kegg(rxn_list, map_bigg_to_kegg_ids) rxn_dG_dict = {} eq_api = ComponentContribution(p_h=Q_(pH), ionic_strength=Q_(ionic_strength, 'M')) for rxn_id in rxn_dict.keys(): rxn = parse_reaction_formula(rxn_dict[rxn_id]) if not rxn.is_balanced(): print(f'{rxn_id} is not balanced.') res = eq_api.standard_dg_prime(rxn) dG0 = res.value.magnitude dG0_std = res.error.magnitude rxn_dG_dict[rxn_id] = (round(dG0, digits), round(dG0_std, digits)) return rxn_dG_dict
def __init__(self, rpsbml=None, ph=7.5, ionic_strength=200, pMg=10.0, temp_k=298.15): """Constructor class for rpEquilibrator :param rpsbml: rpSBML object (Default: None) :param ph: pH of the cell input from the rpSBML model input (Default: 7.5) :param ionic_strength: Ionic strength from the rpSBML model input (Default: 200) :param pMg: pMg value from the rpSBML model input (Default: 10.0) :param temp_k: Temperature from the rpSBML model input in Kelvin (Default: 298.15) :type rpsbml: rpSBML :type ph: float :type ionic_strength: float :type pMg: float :type temp_k: float .. document private functions .. automethod:: _makeSpeciesStr .. automethod:: _makeReactionStr .. automethod:: _speciesCmpQuery .. automethod:: _reactionCmpQuery .. automethod:: _reactionStrQuery """ self.logger = logging.getLogger(__name__) self.logger.debug('Started instance of rpEquilibrator') self.cc = ComponentContribution() self.cc.p_h = Q_(ph) self.cc.ionic_strength = Q_(str(ionic_strength)+' mM') self.cc.p_mg = Q_(pMg) self.cc.temperature = Q_(str(temp_k)+' K') self.ph = ph self.ionic_strength = ionic_strength self.pMg = pMg self.temp_k = temp_k #self.mnx_default_conc = json.load(open('data/mnx_default_conc.json', 'r')) self.mnx_default_conc = json.load(open(os.path.join(os.path.dirname(os.path.abspath( __file__ )), 'data', 'mnx_default_conc.json'), 'r')) self.rpsbml = rpsbml self.calc_cmp = {}
def initThermo( ph: float=DEFAULT_pH, ionic_strength: float=DEFAULT_ionic_strength, pMg: float=DEFAULT_pMg, logger: Logger=getLogger(__name__) ) -> ComponentContribution: print_title( txt='Initialising eQuilibrator...', logger=logger, waiting=True ) cc = ComponentContribution() cc.p_h = Q_(ph) cc.ionic_strength = Q_(f'{ionic_strength}M') cc.p_mg = Q_(pMg) # if temp_k is not None: # cc.temperature = Q_(str(temp_k)+' K') print_OK(logger) return cc
rxn_cpds_array = reactions_helper.parseStoich( reactions_dict[rxn]["stoichiometry"]) All_Mol = True Some_Mol = False for rgt in rxn_cpds_array: if (rgt['compound'] not in seed_mnx_structural_map): All_Mol = False Some_Mol = True if (All_Mol is True): complete_mol_rxns_dict[rxn] = 1 elif (Some_Mol is True): incomplete_mol_rxns_dict[rxn] = 1 equilibrator_calculator = ComponentContribution(p_h=Q_(7.0), ionic_strength=Q_("0.25M"), temperature=Q_("298.15K")) output_name = thermodynamics_root + 'eQuilibrator/MetaNetX_Reaction_Energies.tbl' output_handle = open(output_name, 'w') for rxn in reactions_dict: if (reactions_dict[rxn]['status'] == "EMPTY"): continue notes_list = reactions_dict[rxn]['notes'] if (not isinstance(notes_list, list)): notes_list = list() if (rxn not in complete_mol_rxns_dict): #'EQ' means equilibrator approach to calculating energies
def _reactionCmpQuery(self, libsbml_reaction, write_results=False, physio_param=1e-3): """This method makes a list of structure compounds and uses equilibrator to return the reaction dG :param libsbml_reaction: A libsbml reaction object :param write_results: Write the results to the rpSBML file (Default: False) :param physio_param: The physiological parameter, i.e. the concentration of the compounds to calculate the dG (Default: 1e-3) :type libsbml_reaction: libsbml.Reaction :type write_results: bool :type physio_param: float :rtype: tuple :return: Tuple of size three with dfG_prime_o, dfG_prime_m, uncertainty values in that order or False if fail """ mus = [] sigma_vecs = [] S = [] dfG_prime_o = None dfG_prime_m = None uncertainty = None for rea in libsbml_reaction.getListOfReactants(): self.logger.debug('------------------- '+str(rea.getSpecies())+' --------------') mu, sigma = self._speciesCmpQuery(self.rpsbml.model.getSpecies(rea.getSpecies())) self.logger.debug('mu: '+str(mu)) if not mu: self.logger.warning('Failed to calculate the reaction mu thermodynamics using compound query') if write_results: self.rpsbml.addUpdateBRSynth(libsbml_reaction, 'dfG_prime_o', 0.0, 'kj_per_mol') self.rpsbml.addUpdateBRSynth(libsbml_reaction, 'dfG_prime_m', 0.0, 'kj_per_mol') self.rpsbml.addUpdateBRSynth(libsbml_reaction, 'dfG_uncert', 0.0, 'kj_per_mol') return False elif mu=='h': #skipping the Hydrogen continue mus.append(mu) sigma_vecs.append(sigma) S.append([-rea.getStoichiometry()]) for pro in libsbml_reaction.getListOfProducts(): mu, sigma = self._speciesCmpQuery(self.rpsbml.model.getSpecies(pro.getSpecies())) if not mu: self.logger.warning('Failed to calculate the reaction mu thermodynamics using compound query') if write_results: self.rpsbml.addUpdateBRSynth(libsbml_reaction, 'dfG_prime_o', 0.0, 'kj_per_mol') self.rpsbml.addUpdateBRSynth(libsbml_reaction, 'dfG_prime_m', 0.0, 'kj_per_mol') self.rpsbml.addUpdateBRSynth(libsbml_reaction, 'dfG_uncert', 0.0, 'kj_per_mol') return False elif mu=='h': #skipping the Hydrogen continue mus.append(mu) sigma_vecs.append(sigma) S.append([pro.getStoichiometry()]) mus = Q_(mus, 'kJ/mol') sigma_vecs = Q_(sigma_vecs, 'kJ/mol') np_S = np.array(S) dfG_prime_o = np_S.T@mus dfG_prime_o = float(dfG_prime_o.m[0]) ###### adjust fot physio parameters to calculate the dGm' #TODO: check with Elad dfG_prime_m = float(dfG_prime_o)+float(self.cc.RT.m)*sum([float(sto[0])*float(np.log(co)) for sto, co in zip(S, [physio_param]*len(S))]) uncertainty = np_S.T@sigma_vecs uncertainty = [email protected] uncertainty = uncertainty.m[0][0] if write_results: self.rpsbml.addUpdateBRSynth(libsbml_reaction, 'dfG_prime_o', dfG_prime_o, 'kj_per_mol') self.rpsbml.addUpdateBRSynth(libsbml_reaction, 'dfG_prime_m', dfG_prime_m, 'kj_per_mol') self.rpsbml.addUpdateBRSynth(libsbml_reaction, 'dfG_uncert', uncertainty, 'kj_per_mol') return dfG_prime_o, dfG_prime_m, uncertainty
def create_reaction_id_to_dG0_mapping_json(model: cobra.Model, json_path: str) -> None: """Creates a reaction ID<->dG0 mapping using the Equilibrator API. This function uses the pregenerated BIGG ID to MetaNetX ID mapping, and lets the Equilibrator API calculate the dG0 values for each reaction using the MetaNetX IDs for the metabolites. This function is specifically written for the E. coli models used in CommModelPy's publication as its selection of dG0 values is based on BiGG identifiers and as it contains specific dG0 corrections for special reactions as referenced in the comments of this function. However, the general basis of this function can be easily adapted for other types of models. Arguments: * model: cobra.Model ~ The cobrapy model instance for which the dG0 mapping shall be created. * json_path: str ~ The path to the dG0 JSON that is going to be created. Output: * No variable but a JSON file with the mapping at json_path """ bigg_id_to_metanetx_id = json_load("./publication_runs/ecoli_models/bigg_id_to_metanetx_mapping_json/bigg_id_to_metanetx_id_mapping_from_iML1515.json") reaction_id_dG0_mapping: Dict[str, Dict[str, float]] = {} cc = ComponentContribution() cc.p_h = Q_(7.0) cc.ionic_strength = Q_("0.25M") cc.temperature = Q_("298.15K") cc.p_mg = Q_(3.0) reaction_ids_without_dG0: List[str] = [] for reaction in model.reactions: print("==dG0 GENERATION ATTEMPT FOR REACTION "+reaction.id+"==") # Exclude exchange reactions if reaction.id.startswith("EX_"): print("INFO: Reaction is identified as exchange reaction.") print(" No dG0 for this reaction!") reaction_ids_without_dG0.append(reaction.id) continue # Exclude special transport reactions if ("tpp_" in reaction.id+"_") or ("t1pp_" in reaction.id+"_") or ("tex_" in reaction.id+"_") or reaction.id.endswith("tex"): # tpp: Facilitated transport or (proton) symport # t1pp: Facilitated transport or (proton) symport # tex: "Via diffusion" print("INFO: Reaction is identified as special transport reaction.") print(" No dG0 for this reaction!") reaction_ids_without_dG0.append(reaction.id) continue # Exclude demand reactions if (reaction.id.startswith("DM_")): print("INFO: Reaction is identified as sink (demand) reaction.") print(" No dG0 for this reaction!") reaction_ids_without_dG0.append(reaction.id) continue educt_strings: List[str] = [] product_strings: List[str] = [] is_metanetx_id_missing = False for metabolite in reaction.metabolites.keys(): base_metabolite_id = "".join(metabolite.id.split("_")[:-1]) if base_metabolite_id.endswith("BIO"): base_metabolite_id = base_metabolite_id[:-len("BIO")] if base_metabolite_id not in bigg_id_to_metanetx_id.keys(): is_metanetx_id_missing = True break if reaction.metabolites[metabolite] < 0: educt_strings.append(str(-reaction.metabolites[metabolite]) + " " + bigg_id_to_metanetx_id[base_metabolite_id]) else: product_strings.append(str(reaction.metabolites[metabolite]) + " " + bigg_id_to_metanetx_id[base_metabolite_id]) if is_metanetx_id_missing: print("INFO: MetanetX ID missing for " + base_metabolite_id) print(" No dG0 can be given for this reaction!") reaction_ids_without_dG0.append(reaction.id) continue educt_string = " + ".join(educt_strings) product_string = " + ".join(product_strings) reaction_string = educt_string + " = " + product_string print("Reaction string with MetanetX IDs: " + reaction_string) try: parsed_reaction = cc.parse_reaction_formula(reaction_string) except Exception as e: print("INFO: Equilibrator reaction parsing error") print(e) print(" No dG0 can be given for this reaction!") reaction_ids_without_dG0.append(reaction.id) continue try: dG0 = cc.standard_dg_prime(parsed_reaction) if dG0.m.s > 10000: print("INFO: dG0 standard deviation too high") print(" No dG0 can be given for this reaction!") reaction_ids_without_dG0.append(reaction.id) continue else: reaction_id_dG0_mapping[reaction.id+"_ecoli1"] = {} reaction_id_dG0_mapping[reaction.id+"_ecoli1"]["dG0"] = dG0.m.n reaction_id_dG0_mapping[reaction.id+"_ecoli1"]["uncertainty"] = 0 reaction_id_dG0_mapping[reaction.id+"_ecoli2"] = {} reaction_id_dG0_mapping[reaction.id+"_ecoli2"]["dG0"] = dG0.m.n reaction_id_dG0_mapping[reaction.id+"_ecoli2"]["uncertainty"] = 0 reaction_id_dG0_mapping[reaction.id+"_ecoli3"] = {} reaction_id_dG0_mapping[reaction.id+"_ecoli3"]["dG0"] = dG0.m.n reaction_id_dG0_mapping[reaction.id+"_ecoli3"]["uncertainty"] = 0 print("dG0 calculation successful \\o/") print(f"dG0: {dG0}") except Exception as e: print("INFO:") print(e) print(" No dG0 can be given for this reaction!") reaction_ids_without_dG0.append(reaction.id) continue # Membrane-bound reaction corrections according to formula 9 in # Hamilton, J. J., Dwivedi, V., & Reed, J. L. (2013). # Quantitative assessment of thermodynamic constraints on # the solution space of genome-scale metabolic models. # Biophysical journal, 105(2), 512-522. # Formula is: # c_j * F * dPsi - 2.3 * h_j * R * T * dpH # c_j = net charge transported from outside to inside # h_j = Number of protons transported across membrane F = 0.10026 # kJ/mV/mol, or 0.02306; kcal/mV/mol dPsi = -130 # mV dpH = 0.4 # dimensionless R = 8.314e-3 # kJ⋅K⁻1⋅mol⁻1 T = 298.15 # K # c_j and h_j are taken from supplementary table 3 # of the mentioned publication (Hamilton et al., 2013) c_j = 0.0 h_j = 0.0 # ATP synthase if reaction.id.startswith("ATPS4"): c_j = 4.0 h_j = 4.0 # NADH dehydrogenases elif reaction.id.startswith("NADH16"): c_j = -3.5 h_j = -3.5 elif reaction.id.startswith("NADH17"): c_j = -2.0 h_j = -2.0 elif reaction.id.startswith("NADH18"): c_j = -2.8 h_j = -2.8 # Cytochrome dehydrogenases elif reaction.id.startswith("CYTBD"): c_j = -2.0 h_j = -2.0 elif reaction.id.startswith("CYTBO3"): c_j = -2.5 h_j = -2.5 if (c_j != 0) and (h_j != 0): print("Correcting dG0 for special membrane-bound reaction...") dG0_correction = c_j * F * dPsi - 2.3 * h_j * R * T * dpH print(f"Correction factor is {dG0_correction}") reaction_id_dG0_mapping[reaction.id+"_ecoli1"]["dG0"] += dG0_correction reaction_id_dG0_mapping[reaction.id+"_ecoli2"]["dG0"] += dG0_correction reaction_id_dG0_mapping[reaction.id+"_ecoli3"]["dG0"] += dG0_correction print("New dG0 is " + str(reaction_id_dG0_mapping[reaction.id+"_ecoli1"]["dG0"])) num_reactions_without_dG0 = len(reaction_ids_without_dG0) print("\n==FINAL STATISTICS==") print("No dG0 for the following reactions:") print("\n".join(reaction_ids_without_dG0)) print("Total number: ", str(num_reactions_without_dG0)) print("Fraction from all reactions: ", num_reactions_without_dG0/len(model.reactions)) json_write(json_path, reaction_id_dG0_mapping)