Skip to content

Commit

Permalink
feat: introduce global configuration instance (#758)
Browse files Browse the repository at this point in the history
  • Loading branch information
synchon authored and Midnighter committed Oct 2, 2018
1 parent 757b961 commit df9c561
Show file tree
Hide file tree
Showing 11 changed files with 630 additions and 27 deletions.
3 changes: 2 additions & 1 deletion cobra/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@

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.util import show_versions

__version__ = "0.13.4"
Expand Down
1 change: 1 addition & 0 deletions cobra/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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
Expand Down
93 changes: 93 additions & 0 deletions cobra/core/configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-

"""Define the global configuration."""

from __future__ import absolute_import

import types

from six import with_metaclass, string_types

from cobra.exceptions import SolverNotFound
from cobra.core.singleton import Singleton
from cobra.util.solver import interface_to_str, solvers


__all__ = ("Configuration",)


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).
"""

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

@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: {}".format(
interface_to_str(self.solver), self.lower_bound, self.upper_bound)


class Configuration(with_metaclass(Singleton, BaseConfiguration)):
"""Define the configuration to be singleton based."""

pass
14 changes: 8 additions & 6 deletions cobra/core/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,21 @@
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.solution import get_solution
from cobra.util.context import HistoryManager, resettable, get_context
from cobra.util.solver import (
get_solver_name, interface_to_str, set_objective, solvers,
interface_to_str, set_objective, solvers,
add_cons_vars_to_problem, remove_cons_vars_from_problem, assert_optimal)
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):
Expand All @@ -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
Expand All @@ -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):
Expand Down Expand Up @@ -91,14 +93,14 @@ def __init__(self, id_or_model=None, name=None):
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)
Expand Down
19 changes: 12 additions & 7 deletions cobra/core/reaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from future.utils import raise_from, raise_with_traceback

from cobra.exceptions import OptimizationError
from cobra.core.configuration import Configuration
from cobra.core.gene import Gene, ast2str, parse_gpr, eval_gpr
from cobra.core.metabolite import Metabolite
from cobra.core.object import Object
Expand All @@ -22,6 +23,9 @@
linear_reaction_coefficients, set_objective, check_solver_status)
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)
Expand Down Expand Up @@ -55,8 +59,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
Expand All @@ -66,7 +70,7 @@ def __init__(self, id=None, name='', subsystem='', lower_bound=0.,

# 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
Expand All @@ -82,13 +86,14 @@ def __init__(self, id=None, name='', subsystem='', lower_bound=0.,
'setter')

# Used during optimization. Indicates whether the
# variable is modeled as continuous, integer, binary, semicontinous, or
# semiinteger.
# variable is modeled as continuous, integer, or binary.
self.variable_kind = 'continuous'

# from cameo ...
self._lower_bound = lower_bound
self._upper_bound = upper_bound
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

self._reverse_variable = None
self._forward_variable = None
Expand Down
18 changes: 18 additions & 0 deletions cobra/core/singleton.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# -*- 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]
21 changes: 11 additions & 10 deletions cobra/io/sbml.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from six import iteritems

from cobra.core import Metabolite, Model, Reaction
from cobra.core import Metabolite, Model, Reaction, Configuration
from cobra.util.solver import set_objective

try:
Expand All @@ -18,6 +18,9 @@
libsbml = None


CONFIGURATION = Configuration()


def parse_legacy_id(the_id, the_compartment=None, the_type='metabolite',
use_hyphens=False):
"""Deals with a bunch of problems due to bigg.ucsd.edu not following SBML
Expand Down Expand Up @@ -81,8 +84,6 @@ def create_cobra_model_from_sbml_file(sbml_filename, old_sbml=False,
raise ImportError('create_cobra_model_from_sbml_file '
'requires python-libsbml')

__default_lower_bound = -1000
__default_upper_bound = 1000
__default_objective_coefficient = 0
# Ensure that the file exists
if not isfile(sbml_filename):
Expand Down Expand Up @@ -255,13 +256,13 @@ def create_cobra_model_from_sbml_file(sbml_filename, old_sbml=False,
if not sbml_reaction.getKineticLaw():

if sbml_reaction.getReversible():
parameter_dict['lower_bound'] = __default_lower_bound
parameter_dict['upper_bound'] = __default_upper_bound
parameter_dict['lower_bound'] = CONFIGURATION.lower_bound
parameter_dict['upper_bound'] = CONFIGURATION.upper_bound
else:
# Assume that irreversible reactions only proceed from left to
# right.
parameter_dict['lower_bound'] = 0
parameter_dict['upper_bound'] = __default_upper_bound
parameter_dict['upper_bound'] = CONFIGURATION.upper_bound

parameter_dict[
'objective_coefficient'] = __default_objective_coefficient
Expand All @@ -276,7 +277,7 @@ def create_cobra_model_from_sbml_file(sbml_filename, old_sbml=False,
elif 'lower bound' in parameter_dict:
reaction.lower_bound = parameter_dict['lower bound']
elif sbml_reaction.getReversible():
reaction.lower_bound = __default_lower_bound
reaction.lower_bound = CONFIGURATION.lower_bound
else:
reaction.lower_bound = 0

Expand All @@ -285,7 +286,7 @@ def create_cobra_model_from_sbml_file(sbml_filename, old_sbml=False,
elif 'upper bound' in parameter_dict:
reaction.upper_bound = parameter_dict['upper bound']
else:
reaction.upper_bound = __default_upper_bound
reaction.upper_bound = CONFIGURATION.upper_bound

objective_coefficient = parameter_dict.get(
'objective_coefficient', parameter_dict.get(
Expand All @@ -295,9 +296,9 @@ def create_cobra_model_from_sbml_file(sbml_filename, old_sbml=False,

# ensure values are not set to nan or inf
if isnan(reaction.lower_bound) or isinf(reaction.lower_bound):
reaction.lower_bound = __default_lower_bound
reaction.lower_bound = CONFIGURATION.lower_bound
if isnan(reaction.upper_bound) or isinf(reaction.upper_bound):
reaction.upper_bound = __default_upper_bound
reaction.upper_bound = CONFIGURATION.upper_bound

reaction_note_dict = parse_legacy_sbml_notes(
sbml_reaction.getNotesString())
Expand Down
9 changes: 6 additions & 3 deletions cobra/io/sbml3.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

from six import iteritems, string_types

from cobra.core import Gene, Metabolite, Model, Reaction
from cobra.core import Gene, Metabolite, Model, Reaction, Configuration
from cobra.core.gene import parse_gpr
from cobra.manipulation.modify import _renames
from cobra.manipulation.validate import check_metabolite_compartment_formula
Expand Down Expand Up @@ -53,6 +53,9 @@
class Basic:
pass


CONFIGURATION = Configuration()

# deal with namespaces
namespaces = {"fbc": "http://www.sbml.org/sbml/level3/version1/fbc/version2",
"sbml": "http://www.sbml.org/sbml/level3/version1/core",
Expand Down Expand Up @@ -436,8 +439,8 @@ def model_to_xml(cobra_model, units=True):
min_value = min(cobra_model.reactions.list_attr("lower_bound"))
max_value = max(cobra_model.reactions.list_attr("upper_bound"))
else:
min_value = -1000
max_value = 1000
min_value = CONFIGURATION.lower_bound
max_value = CONFIGURATION.upper_bound

SubElement(parameter_list, "parameter", value=strnum(min_value),
id="cobra_default_lb", sboTerm="SBO:0000626", **param_attr)
Expand Down
34 changes: 34 additions & 0 deletions cobra/test/test_core/test_configuration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-

"""Test functions of configuration.py"""

from __future__ import absolute_import

from cobra.core import Configuration
from cobra.util.solver import interface_to_str


def test_default_bounds():
"""Verify the default bounds."""
config = Configuration()
assert config.bounds == (-1000.0, 1000.0)


def test_bounds():
"""Test changing bounds."""
config = Configuration()
config.bounds = 100.0, 10000.0
assert config.bounds == (100.0, 10000.0)
# Restore default values.
config.bounds = -1000.0, 1000.0


def test_solver():
"""Test assignment of different solvers."""
config = Configuration()
config.solver = "glpk"
assert interface_to_str(config.solver) == "glpk"
config.solver = "glpk_exact"
assert interface_to_str(config.solver) == "glpk_exact"
# Restore default solver.
config.solver = "glpk"

0 comments on commit df9c561

Please sign in to comment.