diff --git a/cobra/core/__init__.py b/cobra/core/__init__.py
index f23d109a3..f9147b5f6 100644
--- a/cobra/core/__init__.py
+++ b/cobra/core/__init__.py
@@ -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
diff --git a/cobra/core/gene.py b/cobra/core/gene.py
index b869b06c4..921ff0d6e 100644
--- a/cobra/core/gene.py
+++ b/cobra/core/gene.py
@@ -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
diff --git a/cobra/core/group.py b/cobra/core/group.py
new file mode 100644
index 000000000..079eada4a
--- /dev/null
+++ b/cobra/core/group.py
@@ -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)
diff --git a/cobra/core/model.py b/cobra/core/model.py
index 27e6a9f46..64d9b652c 100644
--- a/cobra/core/model.py
+++ b/cobra/core/model.py
@@ -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 (
@@ -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.
"""
@@ -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 = []
@@ -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]
@@ -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
@@ -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]
@@ -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.
@@ -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()
@@ -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
@@ -1077,6 +1207,9 @@ def _repr_html_(self):
Number of reactions |
{num_reactions} |
+
+ Number of groups |
+ {num_groups} |
Objective expression |
{objective} |
@@ -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)
diff --git a/cobra/manipulation/delete.py b/cobra/manipulation/delete.py
index ea940e21f..4a5deec06 100644
--- a/cobra/manipulation/delete.py
+++ b/cobra/manipulation/delete.py
@@ -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)
diff --git a/cobra/test/data/iJO1366.pickle b/cobra/test/data/iJO1366.pickle
index 2e89067fa..f90e88ee8 100644
Binary files a/cobra/test/data/iJO1366.pickle and b/cobra/test/data/iJO1366.pickle differ
diff --git a/cobra/test/data/mini.mat b/cobra/test/data/mini.mat
index 6726342d3..7653038e3 100644
Binary files a/cobra/test/data/mini.mat and b/cobra/test/data/mini.mat differ
diff --git a/cobra/test/data/mini.pickle b/cobra/test/data/mini.pickle
index af9ef9ccc..c90dd6c5e 100644
Binary files a/cobra/test/data/mini.pickle and b/cobra/test/data/mini.pickle differ
diff --git a/cobra/test/data/mini_cobra.xml b/cobra/test/data/mini_cobra.xml
index 48b02294b..4e7486177 100644
--- a/cobra/test/data/mini_cobra.xml
+++ b/cobra/test/data/mini_cobra.xml
@@ -205,8 +205,8 @@
-
+
@@ -216,20 +216,20 @@
-
+
- GENE_ASSOCIATION: b3603 or b2975
+ GENE ASSOCIATION: b3603 or b2975
-
+
@@ -242,23 +242,23 @@
-
+
- GENE_ASSOCIATION: b2779
+ GENE ASSOCIATION: b2779
-
+
@@ -286,8 +286,8 @@
-
+
@@ -305,8 +305,8 @@
-
+
@@ -324,23 +324,23 @@
-
+
- GENE_ASSOCIATION: b1773 or b2097 or b2925
+ GENE ASSOCIATION: b1773 or b2097 or b2925
-
+
- GENE_ASSOCIATION: b1779
+ GENE ASSOCIATION: b1779
-
+
-
-
+
+
- GENE_ASSOCIATION: ( b2417 and b1621 and b2415 and b2416 ) or ( b2417 and b1101 and b2415 and b2416 ) or ( b1817 and b1818 and b1819 and b2415 and b2416 )
+ GENE ASSOCIATION: ( b2417 and b1621 and b2415 and b2416 ) or ( b2417 and b1101 and b2415 and b2416 ) or ( b1817 and b1818 and b1819 and b2415 and b2416 )
-
+
@@ -403,15 +403,15 @@
-
+
- GENE_ASSOCIATION: b0875 or s0001
+ GENE ASSOCIATION: b0875 or s0001
@@ -427,20 +427,20 @@
-
+
- GENE_ASSOCIATION: b2133 or b1380
+ GENE ASSOCIATION: b2133 or b1380
-
+
@@ -454,25 +454,25 @@
-
+
- GENE_ASSOCIATION: b3916 or b1723
+ GENE ASSOCIATION: b3916 or b1723
-
+
-
+
- GENE_ASSOCIATION: b4025
+ GENE ASSOCIATION: b4025
@@ -505,15 +505,15 @@
-
+
- GENE_ASSOCIATION: b2926
+ GENE ASSOCIATION: b2926
@@ -521,8 +521,8 @@
-
+
- GENE_ASSOCIATION: b4395 or b3612 or b0755
+ GENE ASSOCIATION: b4395 or b3612 or b0755
@@ -555,15 +555,15 @@
-
+
- GENE_ASSOCIATION: b2987 or b3493
+ GENE ASSOCIATION: b2987 or b3493
@@ -581,25 +581,25 @@
-
+
- GENE_ASSOCIATION: b1854 or b1676
+ GENE ASSOCIATION: b1854 or b1676
-
+
-
+
- GENE_ASSOCIATION: b3919
+ GENE ASSOCIATION: b3919
@@ -632,8 +632,8 @@
-
+
diff --git a/cobra/test/data/mini_fbc1.xml b/cobra/test/data/mini_fbc1.xml
index 58fb4bc9c..c286c4478 100644
--- a/cobra/test/data/mini_fbc1.xml
+++ b/cobra/test/data/mini_fbc1.xml
@@ -312,20 +312,20 @@
-
+
- GENE_ASSOCIATION: b3603 or b2975
+ GENE ASSOCIATION: b3603 or b2975
-
+
@@ -335,15 +335,15 @@
- GENE_ASSOCIATION: b2779
+ GENE ASSOCIATION: b2779
-
+
@@ -373,43 +373,43 @@
- GENE_ASSOCIATION: b1773 or b2097 or b2925
+ GENE ASSOCIATION: b1773 or b2097 or b2925
-
+
- GENE_ASSOCIATION: b1779
+ GENE ASSOCIATION: b1779
-
+
-
-
+
+
- GENE_ASSOCIATION: ( b2417 and b1621 and b2415 and b2416 ) or ( b2417 and b1101 and b2415 and b2416 ) or ( b1817 and b1818 and b1819 and b2415 and b2416 )
+ GENE ASSOCIATION: ( b2417 and b1621 and b2415 and b2416 ) or ( b2417 and b1101 and b2415 and b2416 ) or ( b1817 and b1818 and b1819 and b2415 and b2416 )
-
+
@@ -419,7 +419,7 @@
- GENE_ASSOCIATION: b0875 or s0001
+ GENE ASSOCIATION: b0875 or s0001
@@ -432,12 +432,12 @@
- GENE_ASSOCIATION: b2133 or b1380
+ GENE ASSOCIATION: b2133 or b1380
-
+
@@ -448,23 +448,23 @@
- GENE_ASSOCIATION: b3916 or b1723
+ GENE ASSOCIATION: b3916 or b1723
-
+
-
+
- GENE_ASSOCIATION: b4025
+ GENE ASSOCIATION: b4025
@@ -477,7 +477,7 @@
- GENE_ASSOCIATION: b2926
+ GENE ASSOCIATION: b2926
@@ -485,14 +485,14 @@
-
+
- GENE_ASSOCIATION: b4395 or b3612 or b0755
+ GENE ASSOCIATION: b4395 or b3612 or b0755
@@ -505,7 +505,7 @@
- GENE_ASSOCIATION: b2987 or b3493
+ GENE ASSOCIATION: b2987 or b3493
@@ -520,23 +520,23 @@
- GENE_ASSOCIATION: b1854 or b1676
+ GENE ASSOCIATION: b1854 or b1676
-
+
-
+
- GENE_ASSOCIATION: b3919
+ GENE ASSOCIATION: b3919
diff --git a/cobra/test/data/mini_fbc2.xml.gz b/cobra/test/data/mini_fbc2.xml.gz
index 16d383c00..0fcd44c55 100644
Binary files a/cobra/test/data/mini_fbc2.xml.gz and b/cobra/test/data/mini_fbc2.xml.gz differ
diff --git a/cobra/test/data/raven.pickle b/cobra/test/data/raven.pickle
index adfaf9b65..c5e250b55 100644
Binary files a/cobra/test/data/raven.pickle and b/cobra/test/data/raven.pickle differ
diff --git a/cobra/test/data/salmonella.pickle b/cobra/test/data/salmonella.pickle
index ba0568c82..ae44cc013 100644
Binary files a/cobra/test/data/salmonella.pickle and b/cobra/test/data/salmonella.pickle differ
diff --git a/cobra/test/data/textbook_fva.json b/cobra/test/data/textbook_fva.json
index f31092a84..3ab5fe4c2 100644
--- a/cobra/test/data/textbook_fva.json
+++ b/cobra/test/data/textbook_fva.json
@@ -1 +1 @@
-{"maximum": {"EX_fum_e": 0.0, "ACALDt": 0.0, "EX_glc__D_e": -10.0, "EX_mal__L_e": -0.0, "ADK1": -0.0, "ICL": -0.0, "TALA": 1.49698, "EX_ac_e": -0.0, "PGI": 4.86086, "ACKr": 0.0, "NADTRHD": -0.0, "SUCCt2_2": -0.0, "O2t": 21.79949, "EX_co2_e": 22.80983, "PTAr": -0.0, "EX_h2o_e": 29.17583, "GLUDy": -4.54186, "ACONTa": 6.00725, "GLCpts": 10.0, "GAPD": 16.02353, "TKT1": 1.49698, "TKT2": 1.1815, "NADH16": 38.53461, "EX_etoh_e": -0.0, "ME1": -0.0, "FBP": -0.0, "GLUt2r": 0.0, "SUCDi": 1000.0, "EX_h_e": 17.53087, "ACt2r": 0.0, "GLUSy": -0.0, "TPI": 7.47738, "PYRt2": 0.0, "PGM": -14.71614, "Biomass_Ecoli_core": 0.87392, "PFL": -0.0, "RPE": 2.67848, "RPI": -2.2815, "EX_succ_e": -0.0, "ACONTb": 6.00725, "EX_lac__D_e": -0.0, "PPC": 2.50431, "ALCD2x": 0.0, "AKGDH": 5.06438, "EX_acald_e": -0.0, "EX_nh4_e": -4.76532, "GLUN": -0.0, "EX_gln__L_e": 0.0, "EX_glu__L_e": -0.0, "GND": 4.95998, "PGL": 4.95998, "PPCK": -0.0, "ENO": 14.71614, "EX_fru_e": -0.0, "AKGt2r": 0.0, "SUCCt3": -0.0, "PDH": 9.28253, "EX_pyr_e": -0.0, "EX_o2_e": -21.79949, "PPS": -0.0, "H2Ot": -29.17583, "GLNabc": 0.0, "MDH": 5.06438, "EX_akg_e": -0.0, "ME2": -0.0, "FORt2": -0.0, "EX_for_e": -0.0, "SUCOAS": -5.06438, "PIt2r": 3.2149, "CS": 6.00725, "MALS": -0.0, "FBA": 7.47738, "FRUpts2": 0.0, "PYK": 1.75818, "ATPM": 8.39, "LDH_D": 0.0, "CYTBD": 43.59899, "NH4t": 4.76532, "CO2t": -22.80983, "THD2": -0.0, "ATPS4r": 45.51401, "D_LACt2": 0.0, "FRD7": 994.93562, "GLNS": 0.22346, "G6PDH2r": 4.95998, "MALt2_2": 0.0, "FORti": -0.0, "PFK": 7.47738, "ETOHt2r": 0.0, "ICDHyr": 6.00725, "PGK": -16.02353, "ACALD": 0.0, "FUMt2_2": 0.0, "FUM": 5.06438, "EX_pi_e": -3.2149}, "minimum": {"EX_fum_e": 0.0, "ACALDt": 0.0, "EX_glc__D_e": -10.0, "EX_mal__L_e": 0.0, "ADK1": 0.0, "ICL": 0.0, "TALA": 1.49698, "EX_ac_e": 0.0, "PGI": 4.86086, "ACKr": 0.0, "NADTRHD": 0.0, "SUCCt2_2": 0.0, "O2t": 21.79949, "EX_co2_e": 22.80983, "PTAr": 0.0, "EX_h2o_e": 29.17583, "GLUDy": -4.54186, "ACONTa": 6.00725, "GLCpts": 10.0, "GAPD": 16.02353, "TKT1": 1.49698, "TKT2": 1.1815, "NADH16": 38.53461, "EX_etoh_e": 0.0, "ME1": 0.0, "FBP": 0.0, "GLUt2r": 0.0, "SUCDi": 5.06438, "EX_h_e": 17.53087, "ACt2r": 0.0, "GLUSy": 0.0, "TPI": 7.47738, "PYRt2": 0.0, "PGM": -14.71614, "Biomass_Ecoli_core": 0.87392, "PFL": 0.0, "RPE": 2.67848, "RPI": -2.2815, "EX_succ_e": 0.0, "ACONTb": 6.00725, "EX_lac__D_e": 0.0, "PPC": 2.50431, "ALCD2x": 0.0, "AKGDH": 5.06438, "EX_acald_e": 0.0, "EX_nh4_e": -4.76532, "GLUN": 0.0, "EX_gln__L_e": 0.0, "EX_glu__L_e": 0.0, "GND": 4.95998, "PGL": 4.95998, "PPCK": 0.0, "ENO": 14.71614, "EX_fru_e": 0.0, "AKGt2r": 0.0, "SUCCt3": 0.0, "PDH": 9.28253, "EX_pyr_e": 0.0, "EX_o2_e": -21.79949, "PPS": 0.0, "H2Ot": -29.17583, "GLNabc": 0.0, "MDH": 5.06438, "EX_akg_e": 0.0, "ME2": 0.0, "FORt2": 0.0, "EX_for_e": 0.0, "SUCOAS": -5.06438, "PIt2r": 3.2149, "CS": 6.00725, "MALS": 0.0, "FBA": 7.47738, "FRUpts2": 0.0, "PYK": 1.75818, "ATPM": 8.39, "LDH_D": 0.0, "CYTBD": 43.59899, "NH4t": 4.76532, "CO2t": -22.80983, "THD2": 0.0, "ATPS4r": 45.51401, "D_LACt2": 0.0, "FRD7": 0.0, "GLNS": 0.22346, "G6PDH2r": 4.95998, "MALt2_2": 0.0, "FORti": -0.0, "PFK": 7.47738, "ETOHt2r": 0.0, "ICDHyr": 6.00725, "PGK": -16.02353, "ACALD": 0.0, "FUMt2_2": 0.0, "FUM": 5.06438, "EX_pi_e": -3.2149}}
\ No newline at end of file
+{"maximum": {"PTAr": -0.0, "ACONTb": 6.00725, "ACALDt": 0.0, "CYTBD": 43.59899, "FORt2": -0.0, "SUCDi": 1000.0, "GLCpts": 10.0, "EX_etoh_e": -0.0, "ACONTa": 6.00725, "EX_nh4_e": -4.76532, "FBA": 7.47738, "CS": 6.00725, "FRD7": 994.93562, "MDH": 5.06438, "GLNS": 0.22346, "ADK1": -0.0, "PPCK": -0.0, "EX_glu__L_e": -0.0, "FRUpts2": 0.0, "EX_co2_e": 22.80983, "EX_acald_e": -0.0, "EX_pyr_e": -0.0, "FUMt2_2": 0.0, "THD2": -0.0, "O2t": 21.79949, "RPE": 2.67848, "GAPD": 16.02353, "GND": 4.95998, "ICDHyr": 6.00725, "NH4t": 4.76532, "PGK": -16.02353, "EX_fru_e": -0.0, "PGM": -14.71614, "EX_glc__D_e": -10.0, "CO2t": -22.80983, "MALt2_2": 0.0, "EX_mal__L_e": -0.0, "ATPS4r": 45.51401, "FBP": -0.0, "EX_succ_e": -0.0, "D_LACt2": 0.0, "PYRt2": 0.0, "NADTRHD": -0.0, "TALA": 1.49698, "ATPM": 8.39, "GLUN": -0.0, "AKGDH": 5.06438, "PPC": 2.50431, "PIt2r": 3.2149, "G6PDH2r": 4.95998, "ETOHt2r": 0.0, "EX_ac_e": -0.0, "GLUDy": -4.54186, "ACALD": 0.0, "EX_pi_e": -3.2149, "TKT1": 1.49698, "ACKr": 0.0, "PDH": 9.28253, "EX_for_e": -0.0, "ME2": -0.0, "PGL": 4.95998, "ENO": 14.71614, "TKT2": 1.1815, "EX_h_e": 17.53087, "GLUt2r": 0.0, "SUCOAS": -5.06438, "FUM": 5.06438, "PGI": 4.86086, "ALCD2x": 0.0, "EX_gln__L_e": 0.0, "FORti": -0.0, "ICL": -0.0, "ME1": -0.0, "GLNabc": 0.0, "PFK": 7.47738, "EX_akg_e": -0.0, "NADH16": 38.53461, "PYK": 1.75818, "AKGt2r": 0.0, "MALS": -0.0, "EX_o2_e": -21.79949, "SUCCt3": -0.0, "RPI": -2.2815, "EX_lac__D_e": -0.0, "PFL": -0.0, "ACt2r": 0.0, "H2Ot": -29.17583, "LDH_D": 0.0, "SUCCt2_2": -0.0, "TPI": 7.47738, "GLUSy": -0.0, "Biomass_Ecoli_core": 0.87392, "PPS": -0.0, "EX_h2o_e": 29.17583, "EX_fum_e": 0.0}, "minimum": {"PTAr": 0.0, "ACONTb": 6.00725, "ACALDt": 0.0, "CYTBD": 43.59899, "FORt2": 0.0, "SUCDi": 5.06438, "GLCpts": 10.0, "EX_etoh_e": 0.0, "ACONTa": 6.00725, "EX_nh4_e": -4.76532, "FBA": 7.47738, "CS": 6.00725, "FRD7": 0.0, "MDH": 5.06438, "GLNS": 0.22346, "ADK1": 0.0, "PPCK": 0.0, "EX_glu__L_e": 0.0, "FRUpts2": 0.0, "EX_co2_e": 22.80983, "EX_acald_e": 0.0, "EX_pyr_e": 0.0, "FUMt2_2": 0.0, "THD2": 0.0, "O2t": 21.79949, "RPE": 2.67848, "GAPD": 16.02353, "GND": 4.95998, "ICDHyr": 6.00725, "NH4t": 4.76532, "PGK": -16.02353, "EX_fru_e": 0.0, "PGM": -14.71614, "EX_glc__D_e": -10.0, "CO2t": -22.80983, "MALt2_2": 0.0, "EX_mal__L_e": 0.0, "ATPS4r": 45.51401, "FBP": 0.0, "EX_succ_e": 0.0, "D_LACt2": 0.0, "PYRt2": 0.0, "NADTRHD": 0.0, "TALA": 1.49698, "ATPM": 8.39, "GLUN": 0.0, "AKGDH": 5.06438, "PPC": 2.50431, "PIt2r": 3.2149, "G6PDH2r": 4.95998, "ETOHt2r": 0.0, "EX_ac_e": 0.0, "GLUDy": -4.54186, "ACALD": 0.0, "EX_pi_e": -3.2149, "TKT1": 1.49698, "ACKr": 0.0, "PDH": 9.28253, "EX_for_e": 0.0, "ME2": 0.0, "PGL": 4.95998, "ENO": 14.71614, "TKT2": 1.1815, "EX_h_e": 17.53087, "GLUt2r": 0.0, "SUCOAS": -5.06438, "FUM": 5.06438, "PGI": 4.86086, "ALCD2x": 0.0, "EX_gln__L_e": 0.0, "FORti": 0.0, "ICL": 0.0, "ME1": 0.0, "GLNabc": 0.0, "PFK": 7.47738, "EX_akg_e": 0.0, "NADH16": 38.53461, "PYK": 1.75818, "AKGt2r": 0.0, "MALS": 0.0, "EX_o2_e": -21.79949, "SUCCt3": 0.0, "RPI": -2.2815, "EX_lac__D_e": 0.0, "PFL": 0.0, "ACt2r": 0.0, "H2Ot": -29.17583, "LDH_D": 0.0, "SUCCt2_2": 0.0, "TPI": 7.47738, "GLUSy": 0.0, "Biomass_Ecoli_core": 0.87392, "PPS": 0.0, "EX_h2o_e": 29.17583, "EX_fum_e": 0.0}}
\ No newline at end of file
diff --git a/cobra/test/data/textbook_pfba_fva.json b/cobra/test/data/textbook_pfba_fva.json
index c8acb0281..d97e0177e 100644
--- a/cobra/test/data/textbook_pfba_fva.json
+++ b/cobra/test/data/textbook_pfba_fva.json
@@ -1 +1 @@
-{"maximum": {"ACALD": 0.0, "ACALDt": 0.0, "ACKr": 0.0, "ACONTa": 6.00725, "ACONTb": 6.00725, "ACt2r": 0.0, "ADK1": -0.0, "AKGDH": 5.06438, "AKGt2r": 0.0, "ALCD2x": -0.0, "ATPM": 8.39, "ATPS4r": 45.51401, "Biomass_Ecoli_core": 0.87392, "CO2t": -22.80983, "CS": 6.00725, "CYTBD": 43.59899, "D_LACt2": 0.0, "ENO": 14.71614, "ETOHt2r": -0.0, "EX_ac_e": -0.0, "EX_acald_e": -0.0, "EX_akg_e": -0.0, "EX_co2_e": 22.80983, "EX_etoh_e": -0.0, "EX_for_e": -0.0, "EX_fru_e": -0.0, "EX_fum_e": 0.0, "EX_glc__D_e": -10.0, "EX_gln__L_e": 0.0, "EX_glu__L_e": -0.0, "EX_h2o_e": 29.17583, "EX_h_e": 17.53087, "EX_lac__D_e": -0.0, "EX_mal__L_e": -0.0, "EX_nh4_e": -4.76532, "EX_o2_e": -21.79949, "EX_pi_e": -3.2149, "EX_pyr_e": -0.0, "EX_succ_e": -0.0, "FBA": 7.47738, "FBP": -0.0, "FORt2": -0.0, "FORti": -0.0, "FRD7": 25.9211, "FRUpts2": 0.0, "FUM": 5.06438, "FUMt2_2": 0.0, "G6PDH2r": 4.95998, "GAPD": 16.02353, "GLCpts": 10.0, "GLNS": 0.22346, "GLNabc": 0.0, "GLUDy": -4.54186, "GLUN": -0.0, "GLUSy": -0.0, "GLUt2r": 0.0, "GND": 4.95998, "H2Ot": -29.17583, "ICDHyr": 6.00725, "ICL": -0.0, "LDH_D": 0.0, "MALS": -0.0, "MALt2_2": 0.0, "MDH": 5.06438, "ME1": -0.0, "ME2": -0.0, "NADH16": 38.53461, "NADTRHD": -0.0, "NH4t": 4.76532, "O2t": 21.79949, "PDH": 9.28253, "PFK": 7.47738, "PFL": -0.0, "PGI": 4.86086, "PGK": -16.02353, "PGL": 4.95998, "PGM": -14.71614, "PIt2r": 3.2149, "PPC": 2.50431, "PPCK": -0.0, "PPS": -0.0, "PTAr": -0.0, "PYK": 1.75818, "PYRt2": 0.0, "RPE": 2.67848, "RPI": -2.2815, "SUCCt2_2": -0.0, "SUCCt3": -0.0, "SUCDi": 30.98548, "SUCOAS": -5.06438, "TALA": 1.49698, "THD2": -0.0, "TKT1": 1.49698, "TKT2": 1.1815, "TPI": 7.47738}, "minimum": {"ACALD": 0.0, "ACALDt": 0.0, "ACKr": 0.0, "ACONTa": 6.00725, "ACONTb": 6.00725, "ACt2r": 0.0, "ADK1": 0.0, "AKGDH": 5.06438, "AKGt2r": 0.0, "ALCD2x": 0.0, "ATPM": 8.39, "ATPS4r": 45.51401, "Biomass_Ecoli_core": 0.87392, "CO2t": -22.80983, "CS": 6.00725, "CYTBD": 43.59899, "D_LACt2": 0.0, "ENO": 14.71614, "ETOHt2r": 0.0, "EX_ac_e": 0.0, "EX_acald_e": 0.0, "EX_akg_e": 0.0, "EX_co2_e": 22.80983, "EX_etoh_e": 0.0, "EX_for_e": 0.0, "EX_fru_e": 0.0, "EX_fum_e": 0.0, "EX_glc__D_e": -10.0, "EX_gln__L_e": 0.0, "EX_glu__L_e": 0.0, "EX_h2o_e": 29.17583, "EX_h_e": 17.53087, "EX_lac__D_e": 0.0, "EX_mal__L_e": 0.0, "EX_nh4_e": -4.76532, "EX_o2_e": -21.79949, "EX_pi_e": -3.2149, "EX_pyr_e": 0.0, "EX_succ_e": 0.0, "FBA": 7.47738, "FBP": 0.0, "FORt2": 0.0, "FORti": 0.0, "FRD7": 0.0, "FRUpts2": 0.0, "FUM": 5.06438, "FUMt2_2": 0.0, "G6PDH2r": 4.95998, "GAPD": 16.02353, "GLCpts": 10.0, "GLNS": 0.22346, "GLNabc": 0.0, "GLUDy": -4.54186, "GLUN": 0.0, "GLUSy": 0.0, "GLUt2r": 0.0, "GND": 4.95998, "H2Ot": -29.17583, "ICDHyr": 6.00725, "ICL": 0.0, "LDH_D": 0.0, "MALS": 0.0, "MALt2_2": 0.0, "MDH": 5.06438, "ME1": 0.0, "ME2": 0.0, "NADH16": 38.53461, "NADTRHD": 0.0, "NH4t": 4.76532, "O2t": 21.79949, "PDH": 9.28253, "PFK": 7.47738, "PFL": 0.0, "PGI": 4.86086, "PGK": -16.02353, "PGL": 4.95998, "PGM": -14.71614, "PIt2r": 3.2149, "PPC": 2.50431, "PPCK": 0.0, "PPS": 0.0, "PTAr": 0.0, "PYK": 1.75818, "PYRt2": 0.0, "RPE": 2.67848, "RPI": -2.2815, "SUCCt2_2": 0.0, "SUCCt3": 0.0, "SUCDi": 5.06438, "SUCOAS": -5.06438, "TALA": 1.49698, "THD2": 0.0, "TKT1": 1.49698, "TKT2": 1.1815, "TPI": 7.47738}}
\ No newline at end of file
+{"maximum": {"PTAr": 0.0, "ACONTb": 6.00725, "ACALDt": 0.0, "CYTBD": 43.59899, "FORt2": 0.0, "SUCDi": 30.98548, "GLCpts": 10.0, "EX_etoh_e": 0.0, "ACONTa": 6.00725, "EX_nh4_e": -4.76532, "FBA": 7.47738, "CS": 6.00725, "FRD7": 25.9211, "MDH": 5.06438, "GLNS": 0.22346, "ADK1": 0.0, "PPCK": 0.0, "EX_glu__L_e": 0.0, "FRUpts2": 0.0, "EX_co2_e": 22.80983, "EX_acald_e": 0.0, "EX_pyr_e": 0.0, "FUMt2_2": 0.0, "THD2": 0.0, "O2t": 21.79949, "RPE": 2.67848, "GAPD": 16.02353, "GND": 4.95998, "ICDHyr": 6.00725, "NH4t": 4.76532, "PGK": -16.02353, "EX_fru_e": 0.0, "PGM": -14.71614, "EX_glc__D_e": -10.0, "CO2t": -22.80983, "MALt2_2": 0.0, "EX_mal__L_e": 0.0, "ATPS4r": 45.51401, "FBP": 0.0, "EX_succ_e": 0.0, "D_LACt2": 0.0, "PYRt2": 0.0, "NADTRHD": 0.0, "TALA": 1.49698, "ATPM": 8.39, "GLUN": 0.0, "AKGDH": 5.06438, "PPC": 2.50431, "PIt2r": 3.2149, "G6PDH2r": 4.95998, "ETOHt2r": 0.0, "EX_ac_e": 0.0, "GLUDy": -4.54186, "ACALD": 0.0, "EX_pi_e": -3.2149, "TKT1": 1.49698, "ACKr": 0.0, "PDH": 9.28253, "EX_for_e": 0.0, "ME2": 0.0, "PGL": 4.95998, "ENO": 14.71614, "TKT2": 1.1815, "EX_h_e": 17.53087, "GLUt2r": 0.0, "SUCOAS": -5.06438, "FUM": 5.06438, "PGI": 4.86086, "ALCD2x": 0.0, "EX_gln__L_e": 0.0, "FORti": 0.0, "ICL": 0.0, "ME1": 0.0, "GLNabc": 0.0, "PFK": 7.47738, "EX_akg_e": 0.0, "NADH16": 38.53461, "PYK": 1.75818, "AKGt2r": 0.0, "MALS": 0.0, "EX_o2_e": -21.79949, "SUCCt3": 0.0, "RPI": -2.2815, "EX_lac__D_e": 0.0, "PFL": 0.0, "ACt2r": 0.0, "H2Ot": -29.17583, "LDH_D": 0.0, "SUCCt2_2": 0.0, "TPI": 7.47738, "GLUSy": 0.0, "Biomass_Ecoli_core": 0.87392, "PPS": 0.0, "EX_h2o_e": 29.17583, "EX_fum_e": 0.0}, "minimum": {"PTAr": 0.0, "ACONTb": 6.00725, "ACALDt": -0.0, "CYTBD": 43.59899, "FORt2": 0.0, "SUCDi": 5.06438, "GLCpts": 10.0, "EX_etoh_e": 0.0, "ACONTa": 6.00725, "EX_nh4_e": -4.76532, "FBA": 7.47738, "CS": 6.00725, "FRD7": 0.0, "MDH": 5.06438, "GLNS": 0.22346, "ADK1": 0.0, "PPCK": 0.0, "EX_glu__L_e": 0.0, "FRUpts2": -0.0, "EX_co2_e": 22.80983, "EX_acald_e": 0.0, "EX_pyr_e": 0.0, "FUMt2_2": -0.0, "THD2": 0.0, "O2t": 21.79949, "RPE": 2.67848, "GAPD": 16.02353, "GND": 4.95998, "ICDHyr": 6.00725, "NH4t": 4.76532, "PGK": -16.02353, "EX_fru_e": 0.0, "PGM": -14.71614, "EX_glc__D_e": -10.0, "CO2t": -22.80983, "MALt2_2": -0.0, "EX_mal__L_e": 0.0, "ATPS4r": 45.51401, "FBP": 0.0, "EX_succ_e": 0.0, "D_LACt2": -0.0, "PYRt2": -0.0, "NADTRHD": 0.0, "TALA": 1.49698, "ATPM": 8.39, "GLUN": 0.0, "AKGDH": 5.06438, "PPC": 2.50431, "PIt2r": 3.2149, "G6PDH2r": 4.95998, "ETOHt2r": -0.0, "EX_ac_e": 0.0, "GLUDy": -4.54186, "ACALD": -0.0, "EX_pi_e": -3.2149, "TKT1": 1.49698, "ACKr": -0.0, "PDH": 9.28253, "EX_for_e": 0.0, "ME2": 0.0, "PGL": 4.95998, "ENO": 14.71614, "TKT2": 1.1815, "EX_h_e": 17.53087, "GLUt2r": -0.0, "SUCOAS": -5.06438, "FUM": 5.06438, "PGI": 4.86086, "ALCD2x": -0.0, "EX_gln__L_e": 0.0, "FORti": 0.0, "ICL": 0.0, "ME1": 0.0, "GLNabc": 0.0, "PFK": 7.47738, "EX_akg_e": 0.0, "NADH16": 38.53461, "PYK": 1.75818, "AKGt2r": -0.0, "MALS": 0.0, "EX_o2_e": -21.79949, "SUCCt3": 0.0, "RPI": -2.2815, "EX_lac__D_e": 0.0, "PFL": 0.0, "ACt2r": -0.0, "H2Ot": -29.17583, "LDH_D": -0.0, "SUCCt2_2": 0.0, "TPI": 7.47738, "GLUSy": 0.0, "Biomass_Ecoli_core": 0.87392, "PPS": 0.0, "EX_h2o_e": 29.17583, "EX_fum_e": 0.0}}
\ No newline at end of file
diff --git a/cobra/test/data/textbook_solution.pickle b/cobra/test/data/textbook_solution.pickle
index a9633a37a..8bebff339 100644
Binary files a/cobra/test/data/textbook_solution.pickle and b/cobra/test/data/textbook_solution.pickle differ
diff --git a/cobra/test/test_model.py b/cobra/test/test_model.py
index ecb98fd68..9460374a5 100644
--- a/cobra/test/test_model.py
+++ b/cobra/test/test_model.py
@@ -12,7 +12,7 @@
from optlang.symbolics import Zero
import cobra.util.solver as su
-from cobra.core import Metabolite, Model, Reaction
+from cobra.core import Metabolite, Model, Reaction, Group
from cobra.util import create_stoichiometric_matrix
from cobra.exceptions import OptimizationError
@@ -238,13 +238,14 @@ def test_copy(self, model):
assert PGI is not copied
assert PGI._model is model
assert copied._model is not model
- # the copy should refer to different metabolites and genes
+ # the copy should refer to different metabolites, genes, and groups
for met in copied.metabolites:
assert met is not model.metabolites.get_by_id(met.id)
assert met.model is not model
for gene in copied.genes:
assert gene is not model.genes.get_by_id(gene.id)
assert gene.model is not model
+ assert len(model.get_associated_groups(copied.id)) == 0
def test_iadd(self, model):
PGI = model.reactions.PGI
@@ -331,6 +332,33 @@ def test_repr_html_(self, model):
assert '' in model.genes[0]._repr_html_()
+class TestCobraGroups:
+ def test_group_add_elements(self, model):
+ num_members = 5
+ reactions_for_group = model.reactions[0:num_members]
+ group = Group("arbitrary_group1")
+ group.add_members(reactions_for_group)
+ group.kind = "collection"
+ # number of member sin group should equal the number of reactions
+ # assigned to the group
+ assert len(group.members) == num_members
+
+ # Choose an overlapping, larger subset of reactions for the group
+ num_total_members = 12
+ reactions_for_larger_group = model.reactions[0:num_total_members]
+ group.add_members(reactions_for_larger_group)
+ assert len(group.members) == num_total_members
+
+ def test_group_kind(self):
+ group = Group("arbitrary_group1")
+ with pytest.raises(ValueError) as excinfo:
+ group.kind = "non-SBML compliant group kind"
+ assert "Kind can only by one of:" in str(excinfo.value)
+
+ group.kind = "collection"
+ assert group.kind == "collection"
+
+
class TestCobraModel:
"""test core cobra functions"""
@@ -602,6 +630,70 @@ def test_remove_gene(self, model):
for reaction in gene_reactions:
assert target_gene not in reaction.genes
+ def test_group_model_reaction_association(self, model):
+ num_members = 5
+ reactions_for_group = model.reactions[0:num_members]
+ group = Group("arbitrary_group1")
+ group.add_members(reactions_for_group)
+ group.kind = "collection"
+ model.add_groups([group])
+ # group should point to and be associated with the model
+ assert group._model is model
+ assert group in model.groups
+
+ # model.get_associated_groups should find the group for each reaction
+ # we added to the group
+ for reaction in reactions_for_group:
+ assert group in model.get_associated_groups(reaction)
+
+ # remove the group from the model and check that reactions are no
+ # longer associated with the group
+ model.remove_groups([group])
+ assert group not in model.groups
+ assert group._model is not model
+ for reaction in reactions_for_group:
+ assert group not in model.get_associated_groups(reaction)
+
+ def test_group_members_add_to_model(self, model):
+ # remove a few reactions from the model and add them to a new group
+ num_members = 5
+ reactions_for_group = model.reactions[0:num_members]
+ model.remove_reactions(reactions_for_group, remove_orphans=False)
+ group = Group("arbitrary_group1")
+ group.add_members(reactions_for_group)
+ group.kind = "collection"
+ # the old reactions should not be in the model
+ for reaction in reactions_for_group:
+ assert reaction not in model.reactions
+
+ # add the group to the model and check that the reactions were added
+ model.add_groups([group])
+ assert group in model.groups
+ for reaction in reactions_for_group:
+ assert reaction in model.reactions
+
+ def test_group_loss_of_elements(self, model):
+ # when a metabolite, reaction, or gene is removed from a model, it
+ # should no longer be a member of any groups
+ num_members_each = 5
+ elements_for_group = model.reactions[0:num_members_each]
+ elements_for_group.extend(model.metabolites[0:num_members_each])
+ elements_for_group.extend(model.genes[0:num_members_each])
+ group = Group("arbitrary_group1")
+ group.add_members(elements_for_group)
+ group.kind = "collection"
+ model.add_groups([group])
+
+ remove_met = model.metabolites[0]
+ model.remove_metabolites([remove_met])
+ remove_rxn = model.reactions[0]
+ model.remove_reactions([remove_rxn])
+ remove_gene = model.genes[0]
+ remove_gene.remove_from_model()
+ assert remove_met not in group.members
+ assert remove_rxn not in group.members
+ assert remove_gene not in group.members
+
def test_exchange_reactions(self, model):
assert set(model.exchanges) == set([rxn for rxn in model.reactions
if rxn.id.startswith("EX")])