Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#652[Feature request] Provide compartments as objects #696

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cobra/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from cobra import flux_analysis, io
from cobra.core import (
DictList, Gene, Metabolite, Model, Object, Reaction, Species)
DictList, Gene, Metabolite, Model, Object, Reaction, Species, Compartment)
from cobra.util.version_info import show_versions

__version__ = "0.11.3"
Expand Down
1 change: 1 addition & 0 deletions cobra/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
from cobra.core.reaction import Reaction
from cobra.core.solution import Solution, LegacySolution, get_solution
from cobra.core.species import Species
from cobra.core.compartment import Compartment
40 changes: 40 additions & 0 deletions cobra/core/compartment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-

from __future__ import absolute_import

from copy import deepcopy
from cobra.util.util import format_long_string

from cobra.core.object import Object


class Compartment(Object):
"""Compartment is a class for holding information regarding
a compartment in a cobra.Model object

Parameters
----------
id : string
An identifier for the compartment
name : string
A human readable name.
"""
def __init__(self, id=None, name=""):
Object.__init__(self, id, name)

def copy(self):
return deepcopy(self)

def _repr_html_(self):
return """
<table>
<tr>
<td><strong>Compartment 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>
</table>""".format(id=self.id, name=format_long_string(self.name),
address='0x0%x' % id(self))
75 changes: 48 additions & 27 deletions cobra/core/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from cobra.exceptions import SolverNotFound
from cobra.core.dictlist import DictList
from cobra.core.object import Object
from cobra.core.compartment import Compartment
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
Expand Down Expand Up @@ -90,7 +91,7 @@ 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 = DictList()
self._contexts = []

# from cameo ...
Expand Down Expand Up @@ -163,29 +164,17 @@ def get_metabolite_compartments(self):

@property
def compartments(self):
return {met.compartment: self._compartments.get(met.compartment, '')
for met in self.metabolites if met.compartment is not None}
for met in self.metabolites:
if met.compartment is not None:
try:
self._compartments.append(Compartment(met.compartment))
except Exception:
pass
return self._compartments

@compartments.setter
def compartments(self, value):
"""Get or set the dictionary of current compartment descriptions.

Assigning a dictionary to this property updates the model's
dictionary of compartment descriptions with the new values.

Parameters
----------
value : dict
Dictionary mapping compartments abbreviations to full names.

Examples
--------
>>> import cobra.test
>>> model = cobra.test.create_test_model("textbook")
>>> model.compartments = {'c': 'the cytosol'}
{'c': 'the cytosol', 'e': 'extracellular'}
"""
self._compartments.update(value)
def compartments(self, compartments):
self._compartments = compartments

@property
def medium(self):
Expand Down Expand Up @@ -325,12 +314,42 @@ def copy(self):
except Exception: # pragma: no cover
new._solver = copy(self.solver) # pragma: no cover

try:
new.compartments = self.compartments
except Exception:
pass

# it doesn't make sense to retain the context of a copied model so
# assign a new empty context
new._contexts = list()

return new

def add_compartments(self, compartment_list):
if not hasattr(compartment_list, '__iter__'):
compartment_list = [compartment_list]
if len(compartment_list) == 0:
return None

# First check whether the compartments exist in the model
compartment_list = [x for x in compartment_list
if x.id not in self.compartments]

bad_ids = [m for m in compartment_list
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spaces should probably be disallowed as well since we will get problems with optlang otherwise.

if not isinstance(m.id, string_types) or len(m.id) < 1 or
m is ' ']
if len(bad_ids) != 0:
raise ValueError('invalid identifiers in {}'.format(repr(bad_ids)))

self._compartments += compartment_list

context = get_context(self)
if context:
context(partial(self._compartments.__isub__, compartment_list))
for x in compartment_list:
# Do we care?
context(partial(setattr, x, '_model', None))

def add_metabolites(self, metabolite_list):
"""Will add a list of metabolites to the model object and add new
constraints accordingly.
Expand Down Expand Up @@ -863,6 +882,7 @@ def repair(self, rebuild_index=True, rebuild_relationships=True):
self.reactions._generate_index()
self.metabolites._generate_index()
self.genes._generate_index()
self._compartments._generate_index()
if rebuild_relationships:
for met in self.metabolites:
met._reaction.clear()
Expand Down Expand Up @@ -1050,15 +1070,16 @@ def _repr_html_(self):
<td><strong>Objective expression</strong></td>
<td>{objective}</td>
</tr><tr>
<td><strong>Compartments</strong></td>
<td><strong>{n_compartments} compartment(s)</strong></td>
<td>{compartments}</td>
</tr>
</table>""".format(
</table>""".format(
name=self.id,
address='0x0%x' % id(self),
num_metabolites=len(self.metabolites),
num_reactions=len(self.reactions),
objective=format_long_string(str(self.objective.expression), 100),
compartments=", ".join(
v if v else k for k, v in iteritems(self.compartments)
))
n_compartments=len(self.compartments),
compartments=format_long_string(
', '.join((r.id + " : " + r.name) for r in self._compartments),
200))
35 changes: 31 additions & 4 deletions cobra/io/dict.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from numpy import bool_, float_
from six import iteritems, string_types

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

_REQUIRED_REACTION_ATTRIBUTES = [
Expand Down Expand Up @@ -44,11 +44,17 @@
"annotation": {},
}

_ORDERED_OPTIONAL_MODEL_KEYS = ["name", "compartments", "notes", "annotation"]
_REQUIRED_COMPARTMENT_ATTRIBUTES = ["id", "name"]
_ORDERED_OPTIONAL_COMPARTMENT_KEYS = ["notes", "annotation"]
_OPTIONAL_COMPARTMENT_ATTRIBUTES = {
"notes": {},
"annotation": {},
}

_ORDERED_OPTIONAL_MODEL_KEYS = ["name", "notes", "annotation"]
_OPTIONAL_MODEL_ATTRIBUTES = {
"name": None,
# "description": None, should not actually be included
"compartments": [],
"notes": {},
"annotation": {},
}
Expand Down Expand Up @@ -118,6 +124,23 @@ def gene_from_dict(gene):
return new_gene


def compartment_to_dict(compartment):
new_compartment = OrderedDict()
for key in _REQUIRED_COMPARTMENT_ATTRIBUTES:
new_compartment[key] = _fix_type(getattr(compartment, key))
_update_optional(compartment, new_compartment,
_OPTIONAL_COMPARTMENT_ATTRIBUTES,
_ORDERED_OPTIONAL_COMPARTMENT_KEYS)
return new_compartment


def compartment_from_dict(compartment):
new_compartment = Compartment(compartment["id"])
for k, v in iteritems(compartment):
setattr(new_compartment, k, v)
return new_compartment


def reaction_to_dict(reaction):
new_reaction = OrderedDict()
for key in _REQUIRED_REACTION_ATTRIBUTES:
Expand Down Expand Up @@ -174,6 +197,7 @@ def model_to_dict(model, sort=False):
obj["metabolites"] = list(map(metabolite_to_dict, model.metabolites))
obj["reactions"] = list(map(reaction_to_dict, model.reactions))
obj["genes"] = list(map(gene_to_dict, model.genes))
obj["compartments"] = list(map(compartment_to_dict, model.compartments))
obj["id"] = model.id
_update_optional(model, obj, _OPTIONAL_MODEL_ATTRIBUTES,
_ORDERED_OPTIONAL_MODEL_KEYS)
Expand All @@ -182,6 +206,7 @@ def model_to_dict(model, sort=False):
obj["metabolites"].sort(key=get_id)
obj["reactions"].sort(key=get_id)
obj["genes"].sort(key=get_id)
obj["compartments"].sort(key=get_id)
return obj


Expand Down Expand Up @@ -211,6 +236,8 @@ def model_from_dict(obj):
if 'reactions' not in obj:
raise ValueError('Object has no reactions attribute. Cannot load.')
model = Model()
model.add_compartments([compartment_from_dict(compartment) for
compartment in obj['compartments']])
model.add_metabolites(
[metabolite_from_dict(metabolite) for metabolite in obj['metabolites']]
)
Expand All @@ -225,6 +252,6 @@ def model_from_dict(obj):
rxn in objective_reactions}
set_objective(model, coefficients)
for k, v in iteritems(obj):
if k in {'id', 'name', 'notes', 'compartments', 'annotation'}:
if k in {'id', 'name', 'notes', 'annotation'}:
setattr(model, k, v)
return model
15 changes: 12 additions & 3 deletions cobra/io/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,19 @@ def load_json_model(filename):

},
"compartments": {
"type": "object",
"patternProperties": {
"[a-z]{1,2}": {"type": "string"}
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {"type": "string"},
"name": {"type": "string"},
"notes": {"type": "object"},
"annotation": {"type": "object"},
},
"required": ["id", "name"],
"additionalProperties": False,
}

},
"notes": {"type": "object"},
"annotation": {"type": "object"},
Expand Down
16 changes: 10 additions & 6 deletions cobra/io/mat.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from numpy import array, inf, isinf
from six import string_types

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

Expand Down Expand Up @@ -120,7 +120,8 @@ def create_mat_metabolite_id(model):
for met in model.metabolites:
if not _get_id_compartment(met.id) and met.compartment:
yield '{}[{}]'.format(met.id,
model.compartments[met.compartment].lower())
model.compartments.get_by_id(
met.compartment).name.lower())
else:
yield met.id

Expand Down Expand Up @@ -204,12 +205,15 @@ def from_mat_struct(mat_struct, model_id=None, inf=inf):
new_metabolite.compartment = m['comps'][0, 0][comp_index][0][0]
if new_metabolite.compartment not in model.compartments:
comp_name = m['compNames'][0, 0][comp_index][0][0]
model.compartments[new_metabolite.compartment] = comp_name
model.add_compartments([
Compartment(new_metabolite.compartment, comp_name)])
else:
new_metabolite.compartment = _get_id_compartment(new_metabolite.id)
if new_metabolite.compartment not in model.compartments:
model.compartments[
new_metabolite.compartment] = new_metabolite.compartment
if new_metabolite.compartment not in model.compartments and \
new_metabolite.compartment is not None:
model.add_compartments([
Compartment(new_metabolite.compartment,
new_metabolite.compartment)])
try:
new_metabolite.name = str(m["metNames"][0, 0][i][0][0])
except (IndexError, ValueError):
Expand Down
23 changes: 12 additions & 11 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, Compartment
from cobra.util.solver import set_objective

try:
Expand Down Expand Up @@ -123,6 +123,10 @@ def create_cobra_model_from_sbml_file(sbml_filename, old_sbml=False,
[(v, k) for k, v in iteritems(compartment_dict)])

cobra_model = Model(sbml_model_id)
# Populate the compartment list - This will be done based on
# cobra.Metabolites in cobra.Reactions in the future.
compartments = [Compartment(k, v) for k, v in iteritems(compartment_dict)]
cobra_model.add_compartments(compartments)
metabolites = []
metabolite_dict = {}
# Convert sbml_metabolites to cobra.Metabolites
Expand Down Expand Up @@ -344,9 +348,6 @@ def create_cobra_model_from_sbml_file(sbml_filename, old_sbml=False,

# Now, add all of the reactions to the model.
cobra_model.id = sbml_model.getId()
# Populate the compartment list - This will be done based on
# cobra.Metabolites in cobra.Reactions in the future.
cobra_model.compartments = compartment_dict

cobra_model.add_reactions(cobra_reaction_list)
set_objective(cobra_model, coefficients)
Expand Down Expand Up @@ -460,14 +461,14 @@ def get_libsbml_document(cobra_model,

# Add in the common compartment abbreviations. If there are additional
# compartments they also need to be added.
if not cobra_model.compartments:
cobra_model.compartments = {'c': 'cytosol',
'p': 'periplasm',
'e': 'extracellular'}
for the_key in cobra_model.compartments.keys():
if len(cobra_model.compartments) == 0:
cobra_model.add_compartments([Compartment('c', 'cytosol'),
Compartment('p', 'periplasm'),
Compartment('e', 'extracellular')])
for compartment in cobra_model.compartments:
sbml_comp = sbml_model.createCompartment()
sbml_comp.setId(the_key)
sbml_comp.setName(cobra_model.compartments[the_key])
sbml_comp.setId(compartment.id)
sbml_comp.setName(compartment.name)
sbml_comp.setSize(1) # Just to get rid of warnings

if print_time:
Expand Down
Loading