Skip to content

Commit 63ce057

Browse files
author
uri.akavia
committed
fixed according to comments
1 parent 7ea50ad commit 63ce057

File tree

5 files changed

+128
-75
lines changed

5 files changed

+128
-75
lines changed

src/cobra/core/gene.py

+24-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from ast import parse as ast_parse
1919
from copy import deepcopy
2020
from keyword import kwlist
21-
from typing import FrozenSet, Iterable, Optional, Set, Tuple, Union
21+
from typing import TYPE_CHECKING, FrozenSet, Iterable, Optional, Set, Tuple, Union
2222
from warnings import warn
2323

2424
import sympy.logic.boolalg as spl
@@ -30,6 +30,10 @@
3030
from cobra.util.util import format_long_string
3131

3232

33+
if TYPE_CHECKING:
34+
from cobra.core import Reaction
35+
36+
3337
# TODO - When https://github.com/symengine/symengine.py/issues/334 is resolved,
3438
# change sympy.Symbol (above in imports) to optlang.symbolics.Symbol
3539

@@ -246,10 +250,28 @@ def knock_out(self) -> None:
246250
context.
247251
"""
248252
self.functional = False
249-
for reaction in self.reactions:
253+
for reaction in self.reactions: # type: ignore
250254
if not reaction.functional:
251255
reaction.bounds = (0, 0)
252256

257+
@property
258+
def reactions(self) -> FrozenSet["Reaction"]:
259+
"""Return a frozenset of reactions.
260+
261+
If the gene is present in a model, calculates this set by querying
262+
the model reactions for this gene.
263+
If not, returns the _reaction field, see cobra.Species.
264+
265+
Returns
266+
-------
267+
FrozenSet
268+
A frozenset that includes the reactions of the gene.
269+
"""
270+
if self.model:
271+
return frozenset(self.model.reactions.query(lambda x: self in x, "genes"))
272+
else:
273+
return frozenset(self._reaction)
274+
253275
def _repr_html_(self):
254276
return f"""
255277
<table>

src/cobra/core/metabolite.py

+22-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""Define the Metabolite class."""
22

33
import re
4-
from typing import TYPE_CHECKING, Dict, Optional, Union
4+
from typing import TYPE_CHECKING, Dict, FrozenSet, Optional, Union
55
from warnings import warn
66

77
from ..exceptions import OptimizationError
@@ -15,7 +15,7 @@
1515
from optlang.interface import Container
1616
from pandas import DataFrame
1717

18-
from cobra.core import Solution
18+
from cobra.core import Reaction, Solution
1919
from cobra.summary.metabolite_summary import MetaboliteSummary
2020

2121
# Numbers are not required because of the |(?=[A-Z])? block. See the
@@ -276,6 +276,26 @@ def remove_from_model(self, destructive: bool = False) -> None:
276276
"""
277277
self._model.remove_metabolites(self, destructive)
278278

279+
@property
280+
def reactions(self) -> FrozenSet["Reaction"]:
281+
"""Return a frozenset of reactions.
282+
283+
If the metabolite is present in a model, calculates this set by querying
284+
the model reactions for this metabolite.
285+
If not, returns the _reaction field, see cobra.Species.
286+
287+
Returns
288+
-------
289+
FrozenSet
290+
A frozenset that includes the reactions of the metabolite.
291+
"""
292+
if self.model:
293+
return frozenset(
294+
self.model.reactions.query(lambda x: self in x, "metabolites")
295+
)
296+
else:
297+
return frozenset(self._reaction)
298+
279299
def summary(
280300
self,
281301
solution: Optional["Solution"] = None,

src/cobra/core/model.py

+22-37
Original file line numberDiff line numberDiff line change
@@ -723,34 +723,24 @@ def existing_filter(rxn: Reaction) -> bool:
723723
return True
724724

725725
# First check whether the reactions exist in the model. Copy reactions that
726-
# belong to another model, in order not to remove them (see
727-
# https://github.com/opencobra/cobrapy/issues/673#issuecomment-371361476).
728-
exist_but_different_stoich = set()
726+
# belong to another model, in order not to remove them from the original model.
727+
exist_but_different = set()
729728
other_model = ""
730729
for reaction in filter(lambda x: not existing_filter(x), reaction_list):
731-
met_id_dict = {
732-
met.id: stoich for met, stoich in reaction.metabolites.items()
733-
}
734-
existing_met_id_dict = {
735-
met.id: stoich
736-
for met, stoich in self.reactions.get_by_id(
737-
reaction.id
738-
).metabolites.items()
739-
}
740-
if met_id_dict != existing_met_id_dict:
741-
exist_but_different_stoich.add(reaction.id)
742-
other_model = reaction.model
743-
if len(exist_but_different_stoich):
730+
if reaction != self.reactions.get_by_id(reaction.id):
731+
exist_but_different.add(reaction.id)
732+
other_model = other_model or reaction.model
733+
if len(exist_but_different):
744734
logger.warning(
745-
f"The reactions {', '.join(exist_but_different_stoich)} exist"
735+
f"The reactions {', '.join(exist_but_different)} exist"
746736
f" in both {self} and {other_model}, but have different "
747-
f"stoichiometries in the two mdoels. Please check to see "
748-
f"which stoichiometry you'd like to add."
737+
f"properties in the two mdoels. Please check to see "
738+
f"which properties are the correct ones you'd like to add."
749739
)
750740
pruned = DictList(
751741
[
752742
reaction
753-
if not reaction._model or reaction._model == self
743+
if not reaction.model or reaction.model == self
754744
else reaction.copy()
755745
for reaction in filter(existing_filter, reaction_list)
756746
]
@@ -763,20 +753,15 @@ def existing_filter(rxn: Reaction) -> bool:
763753
reaction._model = self
764754
if context:
765755
context(partial(setattr, reaction, "_model", None))
766-
met_id_dict = {
767-
met.id: stoich for met, stoich in reaction.metabolites.items()
768-
}
769-
met_dict = {met.id: met for met in reaction.metabolites.keys()}
770756
mets_to_add = [
771-
met_dict[met_id]
772-
for met_id in set(met_id_dict.keys()).difference(
773-
self.metabolites.list_attr("id")
774-
)
757+
met
758+
for met in reaction.metabolites
759+
if met.id not in self.metabolites.list_attr("id")
775760
]
776761
self.add_metabolites(mets_to_add)
777762
new_stoich = {
778-
self.metabolites.get_by_id(met_id): stoich
779-
for met_id, stoich in met_id_dict.items()
763+
self.metabolites.get_by_id(met.id): stoich
764+
for met, stoich in reaction.metabolites.items()
780765
}
781766
reaction.metabolites = new_stoich
782767
reaction.update_genes_from_gpr()
@@ -846,15 +831,15 @@ def remove_reactions(
846831
self.reactions.remove(reaction)
847832
reaction._model = None
848833

849-
for met in reaction.metabolites:
834+
for met in reaction._metabolites:
850835
if reaction in met._reaction:
851-
met.reaction_remove(reaction, context)
836+
met.remove_reaction(reaction)
852837
if remove_orphans and len(met.reactions) == 0:
853838
self.remove_metabolites(met)
854839

855-
for gene in reaction.genes:
840+
for gene in reaction._genes:
856841
if reaction in gene._reaction:
857-
gene.reaction_remove(reaction, context)
842+
gene.remove_reaction(reaction)
858843

859844
if remove_orphans and len(gene.reactions) == 0:
860845
self.genes.remove(gene)
@@ -1287,13 +1272,13 @@ def repair(
12871272
self.groups._generate_index()
12881273
if rebuild_relationships:
12891274
for met in self.metabolites:
1290-
met.reaction_clear()
1275+
met.clear_reaction()
12911276
for gene in self.genes:
1292-
gene.reaction_clear()
1277+
gene.clear_reaction()
12931278
for rxn in self.reactions:
12941279
rxn.update_genes_from_gpr()
12951280
for met in rxn.metabolites:
1296-
met.reaction_add(rxn)
1281+
met.add_reaction(rxn)
12971282

12981283
# point _model to self
12991284
for dict_list in (self.reactions, self.genes, self.metabolites, self.groups):

src/cobra/core/reaction.py

+55-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Define the Reaction class."""
22

33
import hashlib
4+
import math
45
import re
56
from collections import defaultdict
67
from copy import copy, deepcopy
@@ -594,7 +595,7 @@ def metabolites(self, value: Dict[Metabolite, float]) -> None:
594595
self._metabolites = value
595596
context = get_context(self)
596597
for met in value.keys():
597-
met.reaction_add(self, context)
598+
met.add_reaction(self)
598599

599600
@property
600601
def genes(self) -> FrozenSet:
@@ -858,9 +859,9 @@ def _update_awareness(self) -> None:
858859
This function is deprecated and shouldn't be used.
859860
"""
860861
for x in self._metabolites:
861-
x.reaction_add(self)
862+
x.add_reaction(self)
862863
for x in self._genes:
863-
x.reaction_add(self)
864+
x.add_reaction(self)
864865

865866
def remove_from_model(self, remove_orphans: bool = False) -> None:
866867
"""Remove the reaction from a model.
@@ -951,7 +952,7 @@ def __setstate__(self, state: Dict) -> None:
951952
self.__dict__.update(state)
952953
for x in set(state["_metabolites"]).union(state["_genes"]):
953954
x._model = self._model
954-
x.reaction_add(self)
955+
x.add_reaction(self)
955956

956957
def copy(self) -> "Reaction":
957958
"""Copy a reaction.
@@ -1001,7 +1002,7 @@ def __add__(self, other: "Reaction") -> "Reaction":
10011002
10021003
"""
10031004
new_reaction = self.copy()
1004-
if other == 0:
1005+
if isinstance(other, int) and other == 0:
10051006
return new_reaction
10061007
else:
10071008
new_reaction += other
@@ -1285,7 +1286,7 @@ def add_metabolites(
12851286
self._metabolites[metabolite] = coefficient
12861287
# make the metabolite aware that it is involved in this
12871288
# reaction
1288-
metabolite.reaction_add(self)
1289+
metabolite.add_reaction(self)
12891290

12901291
# from cameo ...
12911292
model = self.model
@@ -1304,7 +1305,7 @@ def add_metabolites(
13041305
if the_coefficient == 0:
13051306
# make the metabolite aware that it no longer participates
13061307
# in this reaction
1307-
metabolite.reaction_remove(self)
1308+
metabolite.remove_reaction(self)
13081309
self._metabolites.pop(metabolite)
13091310

13101311
context = get_context(self)
@@ -1504,7 +1505,7 @@ def _associate_gene(self, cobra_gene: Gene) -> None:
15041505
15051506
"""
15061507
self._genes.add(cobra_gene)
1507-
cobra_gene.reaction_add(self)
1508+
cobra_gene.add_reaction(self)
15081509
cobra_gene._model = self._model
15091510

15101511
def _dissociate_gene(self, cobra_gene: Gene) -> None:
@@ -1516,7 +1517,7 @@ def _dissociate_gene(self, cobra_gene: Gene) -> None:
15161517
15171518
"""
15181519
self._genes.discard(cobra_gene)
1519-
cobra_gene.reaction_remove(self)
1520+
cobra_gene.remove_reaction(self)
15201521

15211522
def knock_out(self) -> None:
15221523
"""Knockout reaction by setting its bounds to zero."""
@@ -1673,6 +1674,51 @@ def summary(
16731674
fva=fva,
16741675
)
16751676

1677+
def __eq__(self, other) -> bool:
1678+
self_state = self.__getstate__()
1679+
self_state["_metabolites"] = {
1680+
met.id: stoic for met, stoic in self.metabolites.items()
1681+
}
1682+
self_state["_genes"] = {gene.id for gene in self.genes}
1683+
self_state.pop("_model")
1684+
other_state = other.__getstate__()
1685+
other_state["_metabolites"] = {
1686+
met.id: stoic for met, stoic in other.metabolites.items()
1687+
}
1688+
other_state["_genes"] = {gene.id for gene in other.genes}
1689+
other_state.pop("_model")
1690+
1691+
self_state.pop("_lower_bound")
1692+
self_state.pop("_upper_bound")
1693+
other_state.pop("_lower_bound")
1694+
other_state.pop("_upper_bound")
1695+
1696+
_tolerance = 1e-7
1697+
if self.model:
1698+
_tolerance = self.model.tolerance
1699+
elif other.model:
1700+
_tolerance = other.model.tolerance
1701+
elif self.model and other.model:
1702+
_tolerance = min(self.model.tolerance, other.model.tolerance)
1703+
if not math.isclose(self.lower_bound, other.lower_bound, abs_tol=_tolerance):
1704+
return False
1705+
elif not math.isclose(self.upper_bound, other.upper_bound, abs_tol=_tolerance):
1706+
return False
1707+
else:
1708+
return self_state == other_state
1709+
1710+
def __hash__(self) -> int:
1711+
"""Define __hash__ explicitly to allow usage of reaction in sets.
1712+
1713+
Python objects that define __eq__ MUST define __hash__ explicitly, or they
1714+
are unhashable. __hash__ is set to the __hash__ function of cobra.Object.
1715+
1716+
Returns
1717+
-------
1718+
int
1719+
"""
1720+
return Object.__hash__(self)
1721+
16761722
def __str__(self) -> str:
16771723
"""Return reaction id and reaction as str.
16781724

0 commit comments

Comments
 (0)