Skip to content
Commits on Source (4)
......@@ -3,7 +3,8 @@ Contributing
============
Contributions are welcome, and they are greatly appreciated! Every little bit
helps, and credit will always be given.
helps, and credit will always be given. For issues that contributors have
experienced in the past, see the FAQ at the bottom of this document.
You can contribute in many ways:
......@@ -240,3 +241,17 @@ Please use concise descriptive commit messages and consider using
instead of ``my_dict.iteritems()`` use ``six.iteritems(my_dict)``
Thank you very much for contributing to cobrapy!
FAQs
----
Q1. Why do all of the tests that involve loading a pickled model fail on my branch?
A: Pickling is the standard method for serializing objects in python,
which is commonly done during operations like multiprocessing.
Because of this, we need to maintain tests that run on pickled
models, otherwise contributors may inadvertantly break
multiprocessing features. If changes you made to cobrapy
modify attributes of the ``cobra.Model`` class, the pickled
models stored in the repository won't contain those changes
and may fail tests that you add or modify. To resolve these
errors, just run ``cobra/test/data/update_pickles.py`` on your
branch, which will repickle the models.
language: python
python: 3.5
sudo: false
cache:
pip: true
python:
- "2.7"
- "3.5"
- "3.6"
- "3.7-dev"
git:
depth: 2
cache:
pip: true
branches:
only:
- master
......@@ -18,54 +24,18 @@ env:
matrix:
fast_finish: true
exclude:
- python: 3.5
include:
- os: linux
python: 3.5
env:
- TOXENV=pep8
- os: linux
python: 2.7
env: TOXENV=py27
- os: linux
python: 3.5
env: TOXENV=py35
- os: linux
python: 3.6
env: TOXENV=py36
- os: linux
python: 3.7-dev
env: TOXENV=py37
- os: linux
python: 3.5
env: TOXENV=sbml
- os: linux
python: 3.5
env: TOXENV=array
# - os: osx
# language: generic
# before_install:
# - brew update
# - brew install --without-readline --without-xz --without-gdbm --without-sqlite python3
# - virtualenv env -p python3
# - source env/bin/activate
# - os: osx
# language: generic
# before_install:
# - brew update
# - brew install --without-readline --without-xz --without-gdbm --without-sqlite python2
# - virtualenv env -p python2
# - source env/bin/activate
before_install:
- travis_retry pip install -U pip setuptools wheel tox
before_cache:
- set +e
install:
- pip install --upgrade pip setuptools wheel tox tox-travis
script:
- travis_wait tox
- tox -- --benchmark-skip --cov-report xml --cov-report term
- bash <(curl -s https://codecov.io/bash)
stages:
- test
- name: deploy
if: tag IS present
# N.B.: Currently, Travis mangles (escapes) the release tag body badly.
#before_deploy:
......@@ -75,27 +45,28 @@ deploy:
- provider: pypi
skip_cleanup: true
distributions: sdist bdist_wheel
install: skip
script:
- echo "Deploying COBRApy to PyPI."
on:
tags: true
repo: $GITHUB_REPO
python: '3.6'
condition: $TRAVIS_OS_NAME == "linux"
user: cobrapy
password: $PYPI_PASSWORD
- provider: script
skip_cleanup: true
install: skip
script: scripts/deploy_website.sh
on:
tags: true
repo: $GITHUB_REPO
python: '3.6'
condition: $TRAVIS_OS_NAME == "linux"
- provider: releases
skip_cleanup: true
install: skip
script:
- echo "Releasing COBRApy to GitHub."
api_key: $GITHUB_TOKEN
body: "Please see https://github.com/opencobra/cobrapy/tree/${TRAVIS_TAG}/release-notes/${TRAVIS_TAG}.md for the full release notes."
on:
tags: true
repo: $GITHUB_REPO
python: '3.6'
condition: $TRAVIS_OS_NAME == "linux"
This diff is collapsed.
......@@ -8,12 +8,14 @@ from os import name as _name
from os.path import abspath as _abspath
from os.path import dirname as _dirname
from cobra import flux_analysis, io
from cobra.core import (
DictList, Gene, Metabolite, Model, Object, Reaction, Species)
Configuration, DictList, Gene, Metabolite, Model, Object, Reaction,
Species)
from cobra import flux_analysis
from cobra import io
from cobra.util import show_versions
__version__ = "0.13.4"
__version__ = "0.14.1"
# set the warning format to be prettier and fit on one line
_cobra_path = _dirname(_abspath(__file__))
......
# -*- coding: utf-8 -*-
from __future__ import absolute_import
import logging
log = logging.getLogger(__name__)
non_zero_flux_threshold = 1e-6
ndecimals = 6
......@@ -2,11 +2,12 @@
from __future__ import absolute_import
from cobra.core.configuration import Configuration
from cobra.core.dictlist import DictList
from cobra.core.gene import Gene
from cobra.core.metabolite import Metabolite
from cobra.core.model import Model
from cobra.core.object import Object
from cobra.core.reaction import Reaction
from cobra.core.solution import Solution, LegacySolution, get_solution
from cobra.core.solution import LegacySolution, Solution, get_solution
from cobra.core.species import Species
# -*- coding: utf-8 -*-
"""Define the global configuration."""
from __future__ import absolute_import
import logging
import types
from multiprocessing import cpu_count
from warnings import warn
from six import string_types, with_metaclass
from cobra.core.singleton import Singleton
from cobra.exceptions import SolverNotFound
from cobra.util.solver import interface_to_str, solvers
__all__ = ("Configuration",)
LOGGER = logging.getLogger(__name__)
class BaseConfiguration(object):
"""
Define global configuration value that will be honored by cobra functions.
This object sets default values for the modifiable attributes like
default solver, reaction bounds etc.
Attributes
----------
solver : {"glpk", "cplex", "gurobi"}
The default solver for new models. The solver choices are the ones
provided by `optlang` and solvers installed in your environment.
lower_bound : float
The standard lower bound for reversible reactions (default -1000).
upper_bound : float
The standard upper bound for all reactions (default 1000).
bounds : tuple of floats
The default reaction bounds for newly created reactions. The bounds
are in the form of lower_bound, upper_bound (default -1000.0, 1000.0).
processes : int
A default number of processes to use where multiprocessing is
possible. The default number corresponds to the number of available
cores (hyperthreads).
"""
def __init__(self):
self._solver = None
self.lower_bound = None
self.upper_bound = None
# Set the default solver from a preferred order.
for name in ["gurobi", "cplex", "glpk"]:
try:
self.solver = name
except SolverNotFound:
continue
else:
break
self.bounds = -1000.0, 1000.0
try:
self.processes = cpu_count()
except NotImplementedError:
LOGGER.warning(
"The number of cores could not be detected - assuming 1.")
self.processes = 1
@property
def solver(self):
return self._solver
@solver.setter
def solver(self, value):
not_valid_interface = SolverNotFound(
"'{}' is not a valid solver interface. Pick one from {}.".format(
value, ", ".join(list(solvers))))
if isinstance(value, string_types):
if value not in solvers:
raise not_valid_interface
interface = solvers[value]
elif isinstance(value, types.ModuleType) and hasattr(value, 'Model'):
interface = value
else:
raise not_valid_interface
self._solver = interface
@property
def bounds(self):
return self.lower_bound, self.upper_bound
@bounds.setter
def bounds(self, bounds):
# TODO: We should consider allowing `None` for free bounds.
assert bounds[0] <= bounds[1]
self.lower_bound = bounds[0]
self.upper_bound = bounds[1]
def __repr__(self):
return "solver: {}\nlower_bound: {}\nupper_bound: {}\n" \
"processes: {}".format(
interface_to_str(self.solver), self.lower_bound,
self.upper_bound, self.processes)
class Configuration(with_metaclass(Singleton, BaseConfiguration)):
"""Define the configuration to be singleton based."""
pass
......@@ -7,6 +7,7 @@ from warnings import warn
from cobra.core.object import Object
# Numbers are not required because of the |(?=[A-Z])? block. See the
# discussion in https://github.com/opencobra/cobrapy/issues/128 for
# more details.
......
......@@ -3,9 +3,9 @@
from __future__ import absolute_import
import re
from ast import parse as ast_parse
from ast import (
And, BitAnd, BitOr, BoolOp, Expression, Name, NodeTransformer, Or)
from ast import parse as ast_parse
from keyword import kwlist
from warnings import warn
......@@ -13,6 +13,7 @@ from cobra.core.species import Species
from cobra.util import resettable
from cobra.util.util import format_long_string
keywords = list(kwlist)
keywords.remove("and")
keywords.remove("or")
......
......@@ -5,12 +5,12 @@ from __future__ import absolute_import
import re
from warnings import warn
from six import iteritems
from future.utils import raise_from, raise_with_traceback
from six import iteritems
from cobra.exceptions import OptimizationError
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
......@@ -47,7 +47,6 @@ class Metabolite(Species):
self.compartment = compartment
self.charge = charge
self._constraint_sense = 'E'
self._bound = 0.
def _set_id_with_model(self, value):
......
......@@ -2,30 +2,33 @@
from __future__ import absolute_import
import types
import logging
import types
from copy import copy, deepcopy
from functools import partial
from warnings import warn
import optlang
from optlang.symbolics import Basic, Zero
import six
from optlang.symbolics import Basic, Zero
from six import iteritems, string_types
from cobra.exceptions import SolverNotFound
from cobra.core.configuration import Configuration
from cobra.core.dictlist import DictList
from cobra.core.object import Object
from cobra.core.reaction import separate_forward_and_reverse_bounds, Reaction
from cobra.core.reaction import Reaction
from cobra.core.solution import get_solution
from cobra.util.context import HistoryManager, resettable, get_context
from cobra.exceptions import SolverNotFound
from cobra.medium import find_boundary_types, sbo_terms
from cobra.util.context import HistoryManager, get_context, resettable
from cobra.util.solver import (
get_solver_name, interface_to_str, set_objective, solvers,
add_cons_vars_to_problem, remove_cons_vars_from_problem, assert_optimal)
add_cons_vars_to_problem, assert_optimal, interface_to_str,
remove_cons_vars_from_problem, set_objective, solvers)
from cobra.util.util import AutoVivification, format_long_string
from cobra.medium import find_boundary_types
LOGGER = logging.getLogger(__name__)
CONFIGURATION = Configuration()
class Model(Object):
......@@ -36,7 +39,7 @@ class Model(Object):
id_or_model : Model, string
Either an existing Model object in which case a new model object is
instantiated with the same properties as the original model,
or a the identifier to associate with the model as a string.
or an identifier to associate with the model as a string.
name : string
Human readable name for the model
......@@ -56,8 +59,7 @@ class Model(Object):
"""
def __setstate__(self, state):
"""Make sure all cobra.Objects in the model point to the model.
"""
"""Make sure all cobra.Objects in the model point to the model."""
self.__dict__.update(state)
for y in ['reactions', 'genes', 'metabolites']:
for x in getattr(self, y):
......@@ -91,14 +93,14 @@ class Model(Object):
self.reactions = DictList() # A list of cobra.Reactions
self.metabolites = DictList() # A list of cobra.Metabolites
# genes based on their ids {Gene.id: Gene}
self._compartments = dict()
self._compartments = {}
self._contexts = []
# from cameo ...
# if not hasattr(self, '_solver'): # backwards compatibility
# with older cobrapy pickles?
interface = solvers[get_solver_name()]
interface = CONFIGURATION.solver
self._solver = interface.Model()
self._solver.objective = interface.Objective(Zero)
self._populate_solver(self.reactions, self.metabolites)
......@@ -443,12 +445,13 @@ class Model(Object):
self.add_reactions([reaction])
def add_boundary(self, metabolite, type="exchange", reaction_id=None,
lb=None, ub=1000.0):
"""Add a boundary reaction for a given metabolite.
lb=None, ub=None, sbo_term=None):
"""
Add a boundary reaction for a given metabolite.
There are three different types of pre-defined boundary reactions:
exchange, demand, and sink reactions.
An exchange reaction is a reversible, imbalanced reaction that adds
An exchange reaction is a reversible, unbalanced reaction that adds
to or removes an extracellular metabolite from the extracellular
compartment.
A demand reaction is an irreversible reaction that consumes an
......@@ -471,13 +474,17 @@ class Model(Object):
want to create your own kind of boundary reaction choose
any other string, e.g., 'my-boundary'.
reaction_id : str, optional
The ID of the resulting reaction. Only used for custom reactions.
The ID of the resulting reaction. This takes precedence over the
auto-generated identifiers but beware that it might make boundary
reactions harder to identify afterwards when using `model.boundary`
or specifically `model.exchanges` etc.
lb : float, optional
The lower bound of the resulting reaction. Only used for custom
reactions.
The lower bound of the resulting reaction.
ub : float, optional
The upper bound of the resulting reaction. For the pre-defined
reactions this default value determines all bounds.
The upper bound of the resulting reaction.
sbo_term : str, optional
A correct SBO term is set for the available types. If a custom
type is chosen, a suitable SBO term should also be set.
Returns
-------
......@@ -498,17 +505,34 @@ class Model(Object):
>>> demand.build_reaction_string()
'atp_c --> '
"""
types = dict(exchange=("EX", -ub, ub), demand=("DM", 0, ub),
sink=("SK", -ub, ub))
if ub is None:
ub = CONFIGURATION.upper_bound
if lb is None:
lb = CONFIGURATION.lower_bound
types = {
"exchange": ("EX", lb, ub, sbo_terms["exchange"]),
"demand": ("DM", 0, ub, sbo_terms["demand"]),
"sink": ("SK", lb, ub, sbo_terms["sink"])
}
if type in types:
prefix, lb, ub = types[type]
prefix, lb, ub, default_term = types[type]
if reaction_id is None:
reaction_id = "{}_{}".format(prefix, metabolite.id)
if sbo_term is None:
sbo_term = default_term
if reaction_id is None:
raise ValueError(
"Custom types of boundary reactions require a custom "
"identifier. Please set the `reaction_id`.")
if reaction_id in self.reactions:
raise ValueError('boundary %s already exists' % reaction_id)
raise ValueError(
"Boundary reaction '{}' already exists.".format(reaction_id))
name = "{} {}".format(metabolite.name, type)
rxn = Reaction(id=reaction_id, name=name, lower_bound=lb,
upper_bound=ub)
rxn.add_metabolites({metabolite: -1})
if sbo_term:
rxn.annotation["sbo"] = sbo_term
self.add_reactions([rxn])
return rxn
......@@ -617,6 +641,14 @@ class Model(Object):
reverse = reaction.reverse_variable
if context:
obj_coef = reaction.objective_coefficient
if obj_coef != 0:
context(partial(
self.solver.objective.set_linear_coefficients,
{forward: obj_coef, reverse: -obj_coef}))
context(partial(self._populate_solver, [reaction]))
context(partial(setattr, reaction, '_model', self))
context(partial(self.reactions.add, reaction))
......@@ -774,25 +806,14 @@ class Model(Object):
self.add_cons_vars(to_add)
for reaction in reaction_list:
if reaction.id not in self.variables:
reverse_lb, reverse_ub, forward_lb, forward_ub = \
separate_forward_and_reverse_bounds(*reaction.bounds)
forward_variable = self.problem.Variable(
reaction.id, lb=forward_lb, ub=forward_ub)
reverse_variable = self.problem.Variable(
reaction.reverse_id, lb=reverse_lb, ub=reverse_ub)
forward_variable = self.problem.Variable(reaction.id)
reverse_variable = self.problem.Variable(reaction.reverse_id)
self.add_cons_vars([forward_variable, reverse_variable])
else:
reaction = self.reactions.get_by_id(reaction.id)
forward_variable = reaction.forward_variable
reverse_variable = reaction.reverse_variable
for metabolite, coeff in six.iteritems(reaction.metabolites):
if metabolite.id in self.constraints:
constraint = self.constraints[metabolite.id]
......@@ -802,11 +823,13 @@ class Model(Object):
name=metabolite.id,
lb=0, ub=0)
self.add_cons_vars(constraint, sloppy=True)
constraint_terms[constraint][forward_variable] = coeff
constraint_terms[constraint][reverse_variable] = -coeff
self.solver.update()
for reaction in reaction_list:
reaction = self.reactions.get_by_id(reaction.id)
reaction.update_variable_bounds()
for constraint, terms in six.iteritems(constraint_terms):
constraint.set_linear_coefficients(terms)
......
......@@ -7,21 +7,26 @@ import re
from collections import defaultdict
from copy import copy, deepcopy
from functools import partial
from math import isinf
from operator import attrgetter
from warnings import warn
from six import iteritems, iterkeys, string_types
from future.utils import raise_from, raise_with_traceback
from six import iteritems, iterkeys, string_types
from cobra.exceptions import OptimizationError
from cobra.core.gene import Gene, ast2str, parse_gpr, eval_gpr
from cobra.core.configuration import Configuration
from cobra.core.gene import Gene, ast2str, eval_gpr, parse_gpr
from cobra.core.metabolite import Metabolite
from cobra.core.object import Object
from cobra.util.context import resettable, get_context
from cobra.exceptions import OptimizationError
from cobra.util.context import get_context, resettable
from cobra.util.solver import (
linear_reaction_coefficients, set_objective, check_solver_status)
check_solver_status, linear_reaction_coefficients, set_objective)
from cobra.util.util import format_long_string
CONFIGURATION = Configuration()
# precompiled regular expressions
# Matches and/or in a gene reaction rule
and_or_search = re.compile(r'\(| and| or|\+|\)', re.IGNORECASE)
......@@ -55,8 +60,8 @@ class Reaction(Object):
The upper flux bound
"""
def __init__(self, id=None, name='', subsystem='', lower_bound=0.,
upper_bound=1000., objective_coefficient=0.):
def __init__(self, id=None, name='', subsystem='', lower_bound=0.0,
upper_bound=None, objective_coefficient=0.0):
Object.__init__(self, id, name)
self._gene_reaction_rule = ''
self.subsystem = subsystem
......@@ -66,7 +71,7 @@ class Reaction(Object):
# A dictionary of metabolites and their stoichiometric coefficients in
# this reaction.
self._metabolites = dict()
self._metabolites = {}
# The set of compartments that partaking metabolites are in.
self._compartments = None
......@@ -81,17 +86,11 @@ class Reaction(Object):
'supported. Use the model.objective '
'setter')
# Used during optimization. Indicates whether the
# variable is modeled as continuous, integer, binary, semicontinous, or
# semiinteger.
self.variable_kind = 'continuous'
# from cameo ...
self._lower_bound = lower_bound
self._upper_bound = upper_bound
self._reverse_variable = None
self._forward_variable = None
self._lower_bound = lower_bound if lower_bound is not None else \
CONFIGURATION.lower_bound
self._upper_bound = upper_bound if upper_bound is not None else \
CONFIGURATION.upper_bound
def _set_id_with_model(self, value):
if value in self.model.reactions:
......@@ -185,6 +184,39 @@ class Reaction(Object):
cop = deepcopy(super(Reaction, self), memo)
return cop
@staticmethod
def _check_bounds(lb, ub):
if lb > ub:
raise ValueError(
"The lower bound must be less than or equal to the upper "
"bound ({} <= {}).".format(lb, ub))
def update_variable_bounds(self):
if self.model is None:
return
# We know that `lb <= ub`.
if self._lower_bound > 0:
self.forward_variable.set_bounds(
lb=None if isinf(self._lower_bound) else self._lower_bound,
ub=None if isinf(self._upper_bound) else self._upper_bound
)
self.reverse_variable.set_bounds(lb=0, ub=0)
elif self._upper_bound < 0:
self.forward_variable.set_bounds(lb=0, ub=0)
self.reverse_variable.set_bounds(
lb=None if isinf(self._upper_bound) else -self._upper_bound,
ub=None if isinf(self._lower_bound) else -self._lower_bound
)
else:
self.forward_variable.set_bounds(
lb=0,
ub=None if isinf(self._upper_bound) else self._upper_bound
)
self.reverse_variable.set_bounds(
lb=0,
ub=None if isinf(self._lower_bound) else -self._lower_bound
)
@property
def lower_bound(self):
"""Get or set the lower bound
......@@ -202,11 +234,18 @@ class Reaction(Object):
@lower_bound.setter
@resettable
def lower_bound(self, value):
if self.upper_bound < value:
if self._upper_bound < value:
warn("You are constraining the reaction '{}' to a fixed flux "
"value of {}. Did you intend to do this? We are planning to "
"remove this behavior in a future release. Please let us "
"know your opinion at "
"https://github.com/opencobra/cobrapy/issues/793."
"".format(self.id, value), DeprecationWarning)
self.upper_bound = value
# Validate bounds before setting them.
self._check_bounds(value, self._upper_bound)
self._lower_bound = value
update_forward_and_reverse_bounds(self, 'lower')
self.update_variable_bounds()
@property
def upper_bound(self):
......@@ -225,11 +264,18 @@ class Reaction(Object):
@upper_bound.setter
@resettable
def upper_bound(self, value):
if self.lower_bound > value:
if self._lower_bound > value:
warn("You are constraining the reaction '{}' to a fixed flux "
"value of {}. Did you intend to do this? We are planning to "
"remove this behavior in a future release. Please let us "
"know your opinion at "
"https://github.com/opencobra/cobrapy/issues/793."
"".format(self.id, value), DeprecationWarning)
self.lower_bound = value
# Validate bounds before setting them.
self._check_bounds(self._lower_bound, value)
self._upper_bound = value
update_forward_and_reverse_bounds(self, 'upper')
self.update_variable_bounds()
@property
def bounds(self):
......@@ -247,9 +293,12 @@ class Reaction(Object):
@bounds.setter
@resettable
def bounds(self, value):
assert value[0] <= value[1], "Invalid bounds: {}".format(value)
self._lower_bound, self._upper_bound = value
update_forward_and_reverse_bounds(self)
lower, upper = value
# Validate bounds before setting them.
self._check_bounds(lower, upper)
self._lower_bound = lower
self._upper_bound = upper
self.update_variable_bounds()
@property
def flux(self):
......@@ -1082,64 +1131,3 @@ class Reaction(Object):
self.build_reaction_string(True), 200),
gpr=format_long_string(self.gene_reaction_rule, 100),
lb=self.lower_bound, ub=self.upper_bound)
def separate_forward_and_reverse_bounds(lower_bound, upper_bound):
"""Split a given (lower_bound, upper_bound) interval into a negative
component and a positive component. Negative components are negated
(returns positive ranges) and flipped for usage with forward and reverse
reactions bounds
Parameters
----------
lower_bound : float
The lower flux bound
upper_bound : float
The upper flux bound
"""
assert lower_bound <= upper_bound, "lower bound is greater than upper"
bounds_list = [0, 0, lower_bound, upper_bound]
bounds_list.sort()
return -bounds_list[1], -bounds_list[0], bounds_list[2], bounds_list[3]
def update_forward_and_reverse_bounds(reaction, direction='both'):
"""For the given reaction, update the bounds in the forward and
reverse variable bounds.
Parameters
----------
reaction : cobra.Reaction
The reaction to operate on
direction : string
Either 'both', 'upper' or 'lower' for updating the corresponding flux
bounds.
"""
reverse_lb, reverse_ub, forward_lb, forward_ub = \
separate_forward_and_reverse_bounds(*reaction.bounds)
try:
# Clear the original bounds to avoid complaints
if direction == 'both':
reaction.forward_variable._ub = None
reaction.reverse_variable._lb = None
reaction.reverse_variable._ub = None
reaction.forward_variable._lb = None
reaction.forward_variable.set_bounds(lb=forward_lb, ub=forward_ub)
reaction.reverse_variable.set_bounds(lb=reverse_lb, ub=reverse_ub)
elif direction == 'upper':
reaction.forward_variable.ub = forward_ub
reaction.reverse_variable.lb = reverse_lb
elif direction == 'lower':
reaction.reverse_variable.ub = reverse_ub
reaction.forward_variable.lb = forward_lb
except AttributeError:
pass
# -*- coding: utf-8 -*-
"""Define the singleton meta class."""
from __future__ import absolute_import
class Singleton(type):
"""Implementation of the singleton pattern as a meta class."""
_instances = {}
def __call__(cls, *args, **kwargs):
"""Override an inheriting class' call."""
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args,
**kwargs)
return cls._instances[cls]
......@@ -10,10 +10,11 @@ from warnings import warn
from numpy import empty, nan
from optlang.interface import OPTIMAL
from pandas import Series, DataFrame, option_context
from pandas import DataFrame, Series, option_context
from cobra.util.solver import check_solver_status
__all__ = ("Solution", "LegacySolution", "get_solution")
LOGGER = logging.getLogger(__name__)
......@@ -40,19 +41,6 @@ class Solution(object):
Contains reaction reduced costs (dual values of variables).
shadow_prices : pandas.Series
Contains metabolite shadow prices (dual values of constraints).
Deprecated Attributes
---------------------
f : float
Use `objective_value` instead.
x : list
Use `fluxes.values` instead.
x_dict : pandas.Series
Use `fluxes` instead.
y : list
Use `reduced_costs.values` instead.
y_dict : pandas.Series
Use `reduced_costs` instead.
"""
def __init__(self, objective_value, status, fluxes, reduced_costs=None,
......@@ -98,16 +86,6 @@ class Solution(object):
html = '<strong><em>{}</em> solution</strong>'.format(self.status)
return html
def __dir__(self):
"""Hide deprecated attributes and methods from the public interface."""
fields = sorted(dir(type(self)) + list(self.__dict__))
fields.remove('f')
fields.remove('x')
fields.remove('y')
fields.remove('x_dict')
fields.remove('y_dict')
return fields
def __getitem__(self, reaction_id):
"""
Return the flux of a reaction.
......@@ -121,50 +99,6 @@ class Solution(object):
get_primal_by_id = __getitem__
@property
def f(self):
"""Deprecated property for getting the objective value."""
warn("use solution.objective_value instead", DeprecationWarning)
return self.objective_value
@property
def x_dict(self):
"""Deprecated property for getting fluxes."""
warn("use solution.fluxes instead", DeprecationWarning)
return self.fluxes
@x_dict.setter
def x_dict(self, fluxes):
"""Deprecated property for setting fluxes."""
warn("let Model.optimize create a solution instance,"
" don't update yourself", DeprecationWarning)
self.fluxes = fluxes
@property
def x(self):
"""Deprecated property for getting flux values."""
warn("use solution.fluxes.values() instead", DeprecationWarning)
return self.fluxes.values
@property
def y_dict(self):
"""Deprecated property for getting reduced costs."""
warn("use solution.reduced_costs instead", DeprecationWarning)
return self.reduced_costs
@y_dict.setter
def y_dict(self, costs):
"""Deprecated property for setting reduced costs."""
warn("let Model create a solution instance, don't update yourself",
DeprecationWarning)
self.reduced_costs = costs
@property
def y(self):
"""Deprecated property for getting reduced cost values."""
warn("use solution.reduced_costs.values() instead", DeprecationWarning)
return self.reduced_costs.values
def to_frame(self):
"""Return the fluxes and reduced costs as a data frame"""
return DataFrame({'fluxes': self.fluxes,
......
# -*- coding: utf-8 -*-
from cobra.flux_analysis.deletion import (
double_gene_deletion, double_reaction_deletion, single_gene_deletion,
single_reaction_deletion)
from cobra.flux_analysis.gapfilling import gapfill
from cobra.flux_analysis.geometric import geometric_fba
from cobra.flux_analysis.loopless import (
construct_loopless_model, loopless_solution, add_loopless)
from cobra.flux_analysis.loopless import (loopless_solution, add_loopless)
from cobra.flux_analysis.moma import add_moma, moma
from cobra.flux_analysis.parsimonious import pfba
from cobra.flux_analysis.moma import moma, add_moma
from cobra.flux_analysis.room import room, add_room
from cobra.flux_analysis.deletion import (
single_gene_deletion, single_reaction_deletion)
from cobra.flux_analysis.variability import (
find_blocked_reactions, flux_variability_analysis, find_essential_genes,
find_essential_reactions)
from cobra.flux_analysis.deletion import (
double_reaction_deletion, double_gene_deletion)
find_blocked_reactions, find_essential_genes, find_essential_reactions,
flux_variability_analysis)
from cobra.flux_analysis.phenotype_phase_plane import production_envelope
from cobra.flux_analysis.room import add_room, room
from cobra.flux_analysis.sampling import sample
# -*- coding: utf-8 -*-
import multiprocessing
import logging
import optlang
from warnings import warn
from itertools import product
import multiprocessing
from builtins import dict, map
from functools import partial
from builtins import (map, dict)
from itertools import product
import pandas as pd
from optlang.exceptions import SolverError
from cobra.manipulation.delete import find_gene_knockout_reactions
import cobra.util.solver as sutil
from cobra.core import Configuration
from cobra.flux_analysis.moma import add_moma
from cobra.flux_analysis.room import add_room
from cobra.manipulation.delete import find_gene_knockout_reactions
LOGGER = logging.getLogger(__name__)
CONFIGURATION = Configuration()
def _reactions_knockouts_with_restore(model, reactions):
......@@ -33,7 +35,7 @@ def _get_growth(model):
growth = model.solver.variables.moma_old_objective.primal
else:
growth = model.slim_optimize()
except optlang.exceptions.SolverError:
except SolverError:
growth = float('nan')
return growth
......@@ -117,11 +119,7 @@ def _multi_deletion(model, entity, element_lists, method="fba",
"Please choose a different solver or use FBA only.".format(solver))
if processes is None:
try:
processes = multiprocessing.cpu_count()
except NotImplementedError:
warn("Number of cores could not be detected - assuming 1.")
processes = 1
processes = CONFIGURATION.processes
with model:
if "moma" in method:
......
......@@ -2,9 +2,9 @@
from __future__ import absolute_import
from optlang.symbolics import add, Zero
from optlang.interface import OPTIMAL
from optlang.symbolics import Zero, add
from cobra.core import Model
from cobra.util import fix_objective_as_constraint
......@@ -118,8 +118,19 @@ class GapFiller(object):
"""
for rxn in self.universal.reactions:
rxn.gapfilling_type = 'universal'
new_metabolites = self.universal.metabolites.query(
lambda metabolite: metabolite not in self.model.metabolites
)
self.model.add_metabolites(new_metabolites)
existing_exchanges = []
for rxn in self.universal.boundary:
existing_exchanges = existing_exchanges + \
[met.id for met in list(rxn.metabolites)]
for met in self.model.metabolites:
if exchange_reactions:
# check for exchange reaction in model already
if met.id not in existing_exchanges:
rxn = self.universal.add_boundary(
met, type='exchange_smiley', lb=-1000, ub=0,
reaction_id='EX_{}'.format(met.id))
......@@ -158,7 +169,7 @@ class GapFiller(object):
for r in self.model.reactions)
prob = self.model.problem
for rxn in self.model.reactions:
if not hasattr(rxn, 'gapfilling_type') or rxn.id.startswith('DM_'):
if not hasattr(rxn, 'gapfilling_type'):
continue
indicator = prob.Variable(
name='indicator_{}'.format(rxn.id), lb=0, ub=1, type='binary')
......@@ -233,6 +244,9 @@ class GapFiller(object):
def validate(self, reactions):
with self.original_model as model:
mets = [x.metabolites for x in reactions]
all_keys = set().union(*(d.keys() for d in mets))
model.add_metabolites(all_keys)
model.add_reactions(reactions)
model.slim_optimize()
return (model.solver.status == OPTIMAL and
......
......@@ -11,6 +11,7 @@ from optlang.symbolics import Zero
from cobra.flux_analysis.parsimonious import add_pfba
from cobra.flux_analysis.variability import flux_variability_analysis
LOGGER = logging.getLogger(__name__)
......
......@@ -5,12 +5,10 @@
from __future__ import absolute_import
import numpy
from six import iteritems
from optlang.symbolics import Zero
from cobra.core import Metabolite, Reaction, get_solution
from cobra.core import get_solution
from cobra.util import create_stoichiometric_matrix, nullspace
from cobra.manipulation.modify import convert_to_irreversible
def add_loopless(model, zero_cutoff=1e-12):
......@@ -241,57 +239,3 @@ def loopless_fva_iter(model, reaction, solution=False, zero_cutoff=1e-6):
best = reaction.flux
model.objective.direction = objective_dir
return best
def construct_loopless_model(cobra_model):
"""Construct a loopless model.
This adds MILP constraints to prevent flux from proceeding in a loop, as
done in http://dx.doi.org/10.1016/j.bpj.2010.12.3707
Please see the documentation for an explanation of the algorithm.
This must be solved with an MILP capable solver.
"""
# copy the model and make it irreversible
model = cobra_model.copy()
convert_to_irreversible(model)
max_ub = max(model.reactions.list_attr("upper_bound"))
# a dict for storing S^T
thermo_stoic = {"thermo_var_" + metabolite.id: {}
for metabolite in model.metabolites}
# Slice operator is so that we don't get newly added metabolites
original_metabolites = model.metabolites[:]
for reaction in model.reactions[:]:
# Boundary reactions are not subjected to these constraints
if len(reaction._metabolites) == 1:
continue
# populate the S^T dict
bound_id = "thermo_bound_" + reaction.id
for met, stoic in iteritems(reaction._metabolites):
thermo_stoic["thermo_var_" + met.id][bound_id] = stoic
# I * 1000 > v --> I * 1000 - v > 0
reaction_ind = Reaction(reaction.id + "_indicator")
reaction_ind.variable_kind = "integer"
reaction_ind.upper_bound = 1
reaction_ub = Metabolite(reaction.id + "_ind_ub")
reaction_ub._constraint_sense = "G"
reaction.add_metabolites({reaction_ub: -1})
reaction_ind.add_metabolites({reaction_ub: max_ub})
# This adds a compensating term for 0 flux reactions, so we get
# S^T x - (1 - I) * 1001 < -1 which becomes
# S^T x < 1000 for 0 flux reactions and
# S^T x < -1 for reactions with nonzero flux.
reaction_bound = Metabolite(bound_id)
reaction_bound._constraint_sense = "L"
reaction_bound._bound = max_ub
reaction_ind.add_metabolites({reaction_bound: max_ub + 1})
model.add_reaction(reaction_ind)
for metabolite in original_metabolites:
metabolite_var = Reaction("thermo_var_" + metabolite.id)
metabolite_var.lower_bound = -max_ub
model.add_reaction(metabolite_var)
metabolite_var.add_metabolites(
{model.metabolites.get_by_id(k): v
for k, v in iteritems(thermo_stoic[metabolite_var.id])})
return model
......@@ -7,7 +7,6 @@ from __future__ import absolute_import
from optlang.symbolics import Zero, add
import cobra.util.solver as sutil
from cobra.flux_analysis.parsimonious import pfba
......