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 six import iteritems
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.
[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(iteritems(elements_dict)))
@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,
threshold=0.01,
fva=None,
names=False,
float_format="{:.3g}".format
):
"""
Create a summary of the producing and consuming fluxes.
This method requires the model for which this metabolite is a part
to be solved.
Parameters
----------
solution : cobra.Solution, optional
A previous model solution to use for generating the summary. If
None, the summary method will resolve the model. Note that the
solution object must match the model, i.e., changes to the model
such as changed bounds, added or removed reactions are not taken
into account by this method (default None).
threshold : float, optional
Threshold below which fluxes are not reported. May not be smaller
than the model tolerance (default 0.01).
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).
names : bool, optional
Emit reaction and metabolite names rather than identifiers (default
False).
float_format : callable, optional
Format string for floats (default ``'{:3G}'.format``).
Returns
-------
cobra.MetaboliteSummary
See Also
--------
Reaction.summary
Model.summary
"""
from cobra.core.summary import MetaboliteSummary
return MetaboliteSummary(
metabolite=self,
model=self._model,
solution=solution,
threshold=threshold,
fva=fva,
names=names,
float_format=float_format
)
[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))