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]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),
)