Skip to content
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
1 change: 1 addition & 0 deletions cobra/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
from cobra.core.model import Model
from cobra.core.object import Object
from cobra.core.reaction import Reaction
from cobra.core.group import Group
from cobra.core.solution import Solution, LegacySolution, get_solution
from cobra.core.species import Species
5 changes: 5 additions & 0 deletions cobra/core/gene.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,11 @@ def remove_from_model(self, model=None,
the_gene_re = re.compile('(^|(?<=( |\()))%s(?=( |\)|$))' %
re.escape(self.id))

# remove reference to the gene in all groups
associated_groups = self._model.get_associated_groups(self)
for group in associated_groups:
group.remove(self)

self._model.genes.remove(self)
self._model = None

Expand Down
108 changes: 108 additions & 0 deletions cobra/core/group.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-

"""Define the group class."""

from __future__ import absolute_import

from warnings import warn

from six import string_types

from cobra.core.object import Object


KIND_TYPES = ("collection", "classification", "partonomy")


class Group(Object):
"""
Manage groups via this implementation of the SBML group specification.

`Group` is a class for holding information regarding a pathways, subsystems,
or other custom groupings of objects within a cobra.Model object.

Parameters
----------
id : str
The identifier to associate with this group
name : str, optional
A human readable name for the group
members : iterable, optional
A list object containing references to cobra.Model-associated objects
that belong to the group.
kind : {"collection", "classification", "partonomy"}, optional
The kind of group, as specified for the Groups feature in the SBML level
3 package specification. Can be any of "classification", "partonomy", or
"collection". The default is "collection". Please consult the SBML level
3 package specification to ensure you are using the proper value for
kind. In short, members of a "classification" group should have an
"is-a" relationship to the group (e.g. member is-a polar compound, or
member is-a transporter). Members of a "partonomy" group should have a
"part-of" relationship (e.g. member is part-of glycolysis). Members of a
"collection" group do not have an implied relationship between the
members, so use this value for kind when in doubt (e.g. member is a
gap-filled reaction, or member is involved in a disease phenotype).

"""

def __init__(self, id, name='', members=None, kind=None):
Object.__init__(self, id, name)

self._members = set() if members is None else set(members)
self._kind = None
self.kind = "collection" if kind is None else kind
# self.model is None or refers to the cobra.Model that
# contains self
self._model = None

# read-only
@property
def members(self):
return self._members

@property
def kind(self):
return self._kind

@kind.setter
def kind(self, kind):
if kind in KIND_TYPES:
self._kind = kind
else:
raise ValueError(
"Kind can only by one of: {}.".format(", ".join(KIND_TYPES)))

def add_members(self, new_members):
"""
Add objects to the group.

Parameters
----------
new_members : list
A list of cobrapy objects to add to the group.

"""

if isinstance(new_members, string_types) or \
hasattr(new_members, "id"):
warn("need to pass in a list")
new_members = [new_members]

self._members.update(new_members)

def remove(self, to_remove):
"""
Remove objects from the group.

Parameters
----------
to_remove : list
A list of cobrapy objects to remove from the group
"""

if isinstance(to_remove, string_types) or \
hasattr(to_remove, "id"):
warn("need to pass in a list")
to_remove = [to_remove]

self._members.difference_update(to_remove)
138 changes: 136 additions & 2 deletions cobra/core/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
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.metabolite import Metabolite
from cobra.core.gene import Gene
from cobra.core.group import Group
from cobra.core.solution import get_solution
from cobra.util.context import HistoryManager, resettable, get_context
from cobra.util.solver import (
Expand Down Expand Up @@ -51,6 +54,9 @@ class Model(Object):
genes : DictList
A DictList where the key is the gene identifier and the value a
Gene
groups : DictList
A DictList where the key is the group identifier and the value a
Group
solution : Solution
The last obtained solution from optimizing the model.
"""
Expand Down Expand Up @@ -90,6 +96,7 @@ def __init__(self, id_or_model=None, name=None):
self.genes = DictList()
self.reactions = DictList() # A list of cobra.Reactions
self.metabolites = DictList() # A list of cobra.Metabolites
self.groups = DictList() # A list of cobra.Groups
# genes based on their ids {Gene.id: Gene}
self._compartments = dict()
self._contexts = []
Expand Down Expand Up @@ -278,7 +285,7 @@ def copy(self):
"""
new = self.__class__()
do_not_copy_by_ref = {"metabolites", "reactions", "genes", "notes",
"annotation"}
"annotation", "groups"}
for attr in self.__dict__:
if attr not in do_not_copy_by_ref:
new.__dict__[attr] = self.__dict__[attr]
Expand Down Expand Up @@ -324,6 +331,28 @@ def copy(self):
new_gene = new.genes.get_by_id(gene.id)
new_reaction._genes.add(new_gene)
new_gene._reaction.add(new_reaction)

new.groups = DictList()
do_not_copy_by_ref = {"_model", "_members"}
for group in self.groups:
new_group = group.__class__()
for attr, value in iteritems(group.__dict__):
if attr not in do_not_copy_by_ref:
new_group.__dict__[attr] = copy(value)
new_group._model = new
new.groups.append(new_group)
# update awareness, as in the reaction copies
new_objects = []
for member in group.members:
if isinstance(member, Metabolite):
new_object = new.metabolites.get_by_id(member.id)
elif isinstance(member, Reaction):
new_object = new.reactions.get_by_id(member.id)
elif isinstance(member, Gene):
new_objext = new.genes.get_by_id(member.id)
new_objects.append(new_object)
new_group.add_members(new_objects)

try:
new._solver = deepcopy(self.solver)
# Cplex has an issue with deep copies
Expand Down Expand Up @@ -406,6 +435,11 @@ def remove_metabolites(self, metabolite_list, destructive=False):
for x in metabolite_list:
x._model = None

# remove reference to the metabolite in all groups
associated_groups = self.get_associated_groups(x)
for group in associated_groups:
group.remove(x)

if not destructive:
for the_reaction in list(x._reaction):
the_coefficient = the_reaction._metabolites[x]
Expand Down Expand Up @@ -644,6 +678,100 @@ def remove_reactions(self, reactions, remove_orphans=False):
if context:
context(partial(self.genes.add, gene))

# remove reference to the reaction in all groups
associated_groups = self.get_associated_groups(reaction)
for group in associated_groups:
group.remove(reaction)

def add_groups(self, group_list):
"""Add groups to the model.

Groups with identifiers identical to a group already in the model are
ignored.

If any group contains members that are not in the model, these members
are added to the model as well. Only metabolites, reactions, and genes
can have groups.

Parameters
----------
group_list : list
A list of `cobra.Group` objects to add to the model.
"""

def existing_filter(group):
if group.id in self.groups:
LOGGER.warning(
"Ignoring group '%s' since it already exists.", group.id)
return False
return True

if isinstance(group_list, string_types) or \
hasattr(group_list, "id"):
warn("need to pass in a list")
group_list = [group_list]

pruned = DictList(filter(existing_filter, group_list))

for group in pruned:
group._model = self
for member in group.members:
# If the member is not associated with the model, add it
if isinstance(member, Metabolite):
if member not in self.metabolites:
self.add_metabolites([member])
if isinstance(member, Reaction):
if member not in self.reactions:
self.add_reactions([member])
# TODO(midnighter): `add_genes` method does not exist.
# if isinstance(member, Gene):
# if member not in self.genes:
# self.add_genes([member])

self.groups += [group]

def remove_groups(self, group_list):
"""Remove groups from the model.

Members of each group are not removed
from the model (i.e. metabolites, reactions, and genes in the group
stay in the model after any groups containing them are removed).

Parameters
----------
group_list : list
A list of `cobra.Group` objects to remove from the model.
"""

if isinstance(group_list, string_types) or \
hasattr(group_list, "id"):
warn("need to pass in a list")
group_list = [group_list]

for group in group_list:
# make sure the group is in the model
if group.id not in self.groups:
LOGGER.warning("%r not in %r. Ignored.", group, self)
else:
self.groups.remove(group)
group._model = None

def get_associated_groups(self, element):
"""Returns a list of groups that an element (reaction, metabolite, gene)
is associated with.

Parameters
----------
element: `cobra.Reaction`, `cobra.Metabolite`, or `cobra.Gene`

Returns
-------
list of `cobra.Group`
All groups that the provided object is a member of
"""
# check whether the element is associated with the model
return [g for g in self.groups if element in g.members]

def add_cons_vars(self, what, **kwargs):
"""Add constraints and variables to the model's mathematical problem.

Expand Down Expand Up @@ -891,6 +1019,7 @@ def repair(self, rebuild_index=True, rebuild_relationships=True):
self.reactions._generate_index()
self.metabolites._generate_index()
self.genes._generate_index()
self.groups._generate_index()
if rebuild_relationships:
for met in self.metabolites:
met._reaction.clear()
Expand All @@ -901,8 +1030,9 @@ def repair(self, rebuild_index=True, rebuild_relationships=True):
met._reaction.add(rxn)
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.groups):
for e in l:
e._model = self

Expand Down Expand Up @@ -1077,6 +1207,9 @@ def _repr_html_(self):
</tr><tr>
<td><strong>Number of reactions</strong></td>
<td>{num_reactions}</td>
</tr><tr>
<td><strong>Number of groups</strong></td>
<td>{num_groups}</td>
</tr><tr>
<td><strong>Objective expression</strong></td>
<td>{objective}</td>
Expand All @@ -1089,6 +1222,7 @@ def _repr_html_(self):
address='0x0%x' % id(self),
num_metabolites=len(self.metabolites),
num_reactions=len(self.reactions),
num_groups=len(self.groups),
objective=format_long_string(str(self.objective.expression), 100),
compartments=", ".join(
v if v else k for k, v in iteritems(self.compartments)
Expand Down
4 changes: 4 additions & 0 deletions cobra/manipulation/delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,4 +231,8 @@ def remove_genes(cobra_model, gene_list, remove_reactions=True):
reaction.gene_reaction_rule = new_rule
for gene in gene_set:
cobra_model.genes.remove(gene)
# remove reference to the gene in all groups
associated_groups = cobra_model.get_associated_groups(gene)
for group in associated_groups:
group.remove(gene)
cobra_model.remove_reactions(target_reactions)
Binary file modified cobra/test/data/iJO1366.pickle
Binary file not shown.
Binary file modified cobra/test/data/mini.mat
Binary file not shown.
Binary file modified cobra/test/data/mini.pickle
Binary file not shown.
Loading