-
Notifications
You must be signed in to change notification settings - Fork 218
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
Changes from 15 commits
274a28b
73cfd2c
3bff4bb
1b0b91d
3fbb27c
ecbd37c
8584119
818abfe
324ffb0
f3900b1
f9d2b61
1123475
8e20e26
0da69be
8ec223c
ae97bf9
a2e8b1e
cd35d1d
cdb5afe
989ba5b
9dba00e
20833b9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
|
||
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)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -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"): | ||
|
@@ -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 ... | ||
|
@@ -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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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): | ||
|
||
|
@@ -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: | ||
|
@@ -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__): | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
@@ -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. | ||
|
||
|
@@ -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() | ||
|
@@ -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 | ||
|
||
|
@@ -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( | ||
|
@@ -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)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||
} |
There was a problem hiding this comment.
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.