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 15 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
45 changes: 45 additions & 0 deletions cobra/core/compartment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# -*- 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)
self._model = None
Copy link
Member

Choose a reason for hiding this comment

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

I don't think it needs a back-ref to the model. That just creates problems.


def copy(self):
return deepcopy(self)

@property
def model(self):
return (self._model)

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))
112 changes: 76 additions & 36 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 @@ -52,13 +53,17 @@ class Model(Object):
Gene
solution : Solution
The last obtained solution from optimizing the model.
compartment : DictList
A DictList where the key is the compartment identifier and the value a
Compartment
"""

def __setstate__(self, state):
"""Make sure all cobra.Objects in the model point to the model.
"""
self.__dict__.update(state)
for y in ['reactions', 'genes', 'metabolites']:
for y in ['reactions', 'genes', 'metabolites',
'compartments']:
for x in getattr(self, y):
x._model = self
if not hasattr(self, "name"):
Expand Down Expand Up @@ -90,7 +95,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() # A list of cobra.Compartments
self._contexts = []

# from cameo ...
Expand Down Expand Up @@ -161,32 +166,6 @@ def get_metabolite_compartments(self):
return {met.compartment for met in self.metabolites
if met.compartment is not None}

@property
def compartments(self):
Copy link
Member

Choose a reason for hiding this comment

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

I think this should not be removed see general comment.

return {met.compartment: self._compartments.get(met.compartment, '')
for met in self.metabolites if met.compartment is not None}

@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)

@property
def medium(self):

Expand Down Expand Up @@ -272,14 +251,24 @@ def copy(self):
than deepcopy
"""
new = self.__class__()
do_not_copy_by_ref = {"metabolites", "reactions", "genes", "notes",
"annotation"}
do_not_copy_by_ref = {"metabolites", "reactions", "genes",
"compartments", "notes", "annotation"}
for attr in self.__dict__:
if attr not in do_not_copy_by_ref:
new.__dict__[attr] = self.__dict__[attr]
new.notes = deepcopy(self.notes)
new.annotation = deepcopy(self.annotation)

new.compartment = DictList()
for compartment in self.compartments:
new_compartment = compartment.__class__(None)
for attr, value in iteritems(compartment.__dict__):
if attr not in do_not_copy_by_ref:
new_compartment.__dict__[attr] = copy(
value) if attr == "formula" else value
new_compartment._model = new
new.compartments.append(new_compartment)

new.metabolites = DictList()
do_not_copy_by_ref = {"_reaction", "_model"}
for metabolite in self.metabolites:
Expand All @@ -302,7 +291,8 @@ def copy(self):
new.genes.append(new_gene)

new.reactions = DictList()
do_not_copy_by_ref = {"_model", "_metabolites", "_genes"}
do_not_copy_by_ref = {"_model", "_metabolites", "_genes",
"_compartments"}
for reaction in self.reactions:
new_reaction = reaction.__class__()
for attr, value in iteritems(reaction.__dict__):
Expand Down Expand Up @@ -331,6 +321,41 @@ def copy(self):

return new

def add_compartments(self, compartment_list):
"""Will add a list of compartments to the model object

The change is reverted upon exit when using the model as a context.

Parameters
----------
compartment_list : A list of `cobra.core.Compartment` objects

"""
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]
if len(bad_ids) != 0:
raise ValueError('invalid identifiers in {}'.format(repr(bad_ids)))

for x in compartment_list:
x._model = self
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 @@ -377,6 +402,19 @@ def add_metabolites(self, metabolite_list):
# Do we care?
context(partial(setattr, x, '_model', None))

# add compartments to cobra.Model
for met in metabolite_list:
compartment = Compartment(met.compartment)
if not self.compartments.has_id(compartment.id):
self.compartments += [compartment]
compartment._model = self

if context:
# Remove the compartment later
context(partial(self.compartments.__isub__,
[compartment]))
context(partial(setattr, compartment, '_model', None))

def remove_metabolites(self, metabolite_list, destructive=False):
"""Remove a list of metabolites from the the object.

Expand Down Expand Up @@ -863,6 +901,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 All @@ -874,7 +913,8 @@ def repair(self, rebuild_index=True, rebuild_relationships=True):
for gene in rxn._genes:
gene._reaction.add(rxn)
# point _model to self
for l in (self.reactions, self.genes, self.metabolites):
for l in (self.reactions, self.genes, self.metabolites,
self.compartments):
for e in l:
e._model = self

Expand Down Expand Up @@ -1050,7 +1090,7 @@ 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(
Expand All @@ -1059,6 +1099,6 @@ def _repr_html_(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 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
17 changes: 13 additions & 4 deletions cobra/io/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,14 +229,23 @@ 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"},
},
"required": ["id", "reactions", "metabolites", "genes"],
"required": ["id", "reactions", "metabolites", "genes", "compartments"],
Copy link
Member

Choose a reason for hiding this comment

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

We need to maintain backwards compatibility so compartments should be optional for all input formats.

"additionalProperties": False,
}
Loading