Source code for cobra.core.metabolite

# -*- coding: utf-8 -*-

"""Define the Metabolite class."""

from __future__ import absolute_import

import re
from warnings import warn

from future.utils import raise_from, raise_with_traceback

from cobra.core.formula import elements_and_molecular_weights
from cobra.core.species import Species
from cobra.exceptions import OptimizationError
from cobra.util.solver import check_solver_status
from cobra.util.util import format_long_string


# Numbers are not required because of the |(?=[A-Z])? block. See the
# discussion in https://github.com/opencobra/cobrapy/issues/128 for
# more details.
[docs]element_re = re.compile("([A-Z][a-z]?)([0-9.]+[0-9.]?|(?=[A-Z])?)")
[docs]class Metabolite(Species): """Metabolite is a class for holding information regarding a metabolite in a cobra.Reaction object. Parameters ---------- id : str the identifier to associate with the metabolite formula : str Chemical formula (e.g. H2O) name : str A human readable name. charge : float The charge number of the metabolite compartment: str or None Compartment of the metabolite. """ def __init__(self, id=None, formula=None, name="", charge=None, compartment=None): Species.__init__(self, id, name) self.formula = formula # because in a Model a metabolite may participate in multiple Reactions self.compartment = compartment self.charge = charge self._bound = 0.0
[docs] def _set_id_with_model(self, value): if value in self.model.metabolites: raise ValueError( "The model already contains a metabolite with " "the id:", value ) self.model.constraints[self.id].name = value self._id = value self.model.metabolites._generate_index()
@property
[docs] def constraint(self): """Get the constraints associated with this metabolite from the solve Returns ------- optlang.<interface>.Constraint the optlang constraint for this metabolite """ if self.model is not None: return self.model.constraints[self.id]
@property
[docs] def elements(self): """Dictionary of elements as keys and their count in the metabolite as integer. When set, the `formula` property is update accordingly""" tmp_formula = self.formula if tmp_formula is None: return {} # necessary for some old pickles which use the deprecated # Formula class tmp_formula = str(self.formula) # commonly occurring characters in incorrectly constructed formulas if "*" in tmp_formula: warn("invalid character '*' found in formula '%s'" % self.formula) tmp_formula = tmp_formula.replace("*", "") if "(" in tmp_formula or ")" in tmp_formula: warn("invalid formula (has parenthesis) in '%s'" % self.formula) return None composition = {} parsed = element_re.findall(tmp_formula) for (element, count) in parsed: if count == "": count = 1 else: try: count = float(count) int_count = int(count) if count == int_count: count = int_count else: warn( "%s is not an integer (in formula %s)" % (count, self.formula) ) except ValueError: warn("failed to parse %s (in formula %s)" % (count, self.formula)) return None if element in composition: composition[element] += count else: composition[element] = count return composition
@elements.setter def elements(self, elements_dict): def stringify(element, number): return element if number == 1 else element + str(number) self.formula = "".join( stringify(e, n) for e, n in sorted(elements_dict.items()) ) @property
[docs] def formula_weight(self): """Calculate the formula weight""" try: return sum( [ count * elements_and_molecular_weights[element] for element, count in self.elements.items() ] ) except KeyError as e: warn("The element %s does not appear in the periodic table" % e)
@property
[docs] def y(self): """The shadow price for the metabolite in the most recent solution Shadow prices are computed from the dual values of the bounds in the solution. """ warn("Please use metabolite.shadow_price instead.", DeprecationWarning) return self.shadow_price
@property
[docs] def shadow_price(self): """ The shadow price in the most recent solution. Shadow price is the dual value of the corresponding constraint in the model. Warnings -------- * Accessing shadow prices through a `Solution` object is the safer, preferred, and only guaranteed to be correct way. You can see how to do so easily in the examples. * Shadow price is retrieved from the currently defined `self._model.solver`. The solver status is checked but there are no guarantees that the current solver state is the one you are looking for. * If you modify the underlying model after an optimization, you will retrieve the old optimization values. Raises ------ RuntimeError If the underlying model was never optimized beforehand or the metabolite is not part of a model. OptimizationError If the solver status is anything other than 'optimal'. Examples -------- >>> import cobra >>> import cobra.test >>> model = cobra.test.create_test_model("textbook") >>> solution = model.optimize() >>> model.metabolites.glc__D_e.shadow_price -0.09166474637510488 >>> solution.shadow_prices.glc__D_e -0.091664746375104883 """ try: check_solver_status(self._model.solver.status) return self._model.constraints[self.id].dual except AttributeError: raise RuntimeError("metabolite '{}' is not part of a model".format(self.id)) # Due to below all-catch, which sucks, need to reraise these. except (RuntimeError, OptimizationError) as err: raise_with_traceback(err) # Would love to catch CplexSolverError and GurobiError here. except Exception as err: raise_from( OptimizationError( "Likely no solution exists. Original solver message: {}." "".format(str(err)) ), err,
)
[docs] def remove_from_model(self, destructive=False): """Removes the association from self.model The change is reverted upon exit when using the model as a context. Parameters ---------- destructive : bool If False then the metabolite is removed from all associated reactions. If True then all associated reactions are removed from the Model. """ self._model.remove_metabolites(self, destructive)
[docs] def summary(self, solution=None, fva=None): """ Create a summary of the producing and consuming fluxes. Parameters ---------- solution : cobra.Solution, optional A previous model solution to use for generating the summary. If ``None``, the summary method will generate a parsimonious flux distribution (default None). fva : pandas.DataFrame or float, optional Whether or not to include flux variability analysis in the output. If given, `fva` should either be a previous FVA solution matching the model or a float between 0 and 1 representing the fraction of the optimum objective to be searched (default None). Returns ------- cobra.summary.MetaboliteSummary See Also -------- Reaction.summary Model.summary """ from cobra.summary import MetaboliteSummary return MetaboliteSummary( metabolite=self, model=self._model, solution=solution, fva=fva,
)
[docs] def _repr_html_(self): return """ <table> <tr> <td><strong>Metabolite identifier</strong></td><td>{id}</td> </tr><tr> <td><strong>Name</strong></td><td>{name}</td> </tr><tr> <td><strong>Memory address</strong></td> <td>{address}</td> </tr><tr> <td><strong>Formula</strong></td><td>{formula}</td> </tr><tr> <td><strong>Compartment</strong></td><td>{compartment}</td> </tr><tr> <td><strong>In {n_reactions} reaction(s)</strong></td><td> {reactions}</td> </tr> </table>""".format( id=self.id, name=format_long_string(self.name), formula=self.formula, address="0x0%x" % id(self), compartment=self.compartment, n_reactions=len(self.reactions), reactions=format_long_string(", ".join(r.id for r in self.reactions), 200),
)