Esempio n. 1
0
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
Esempio n. 3
0
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
Esempio n. 4
0
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