From 98e11ee229896e8399746deaafb00a3545b0fe4f Mon Sep 17 00:00:00 2001 From: carrascomj Date: Tue, 4 Oct 2022 10:40:11 +0200 Subject: [PATCH 1/3] feat: build integration layer for multitfa --- docs/03-thermodynamics.rst | 2 +- geckopy/integration/__init__.py | 1 - geckopy/integration/multitfa.py | 121 ++++++++++++++++++++++ tests/test_integration/test_multitfa.py | 15 +++ tests/test_integration/test_relaxation.py | 2 +- 5 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 geckopy/integration/multitfa.py create mode 100644 tests/test_integration/test_multitfa.py diff --git a/docs/03-thermodynamics.rst b/docs/03-thermodynamics.rst index 4c24be9..8d5c866 100644 --- a/docs/03-thermodynamics.rst +++ b/docs/03-thermodynamics.rst @@ -273,7 +273,7 @@ or the thermodynamic constraint, see the ) # constrain the model objective so that the feashibility relaxation recovers growth tmodel_prot.reactions.BIOMASS_Ecoli_core_w_GAM.lower_bound = solution.objective_value - iis, status = geckopy.integration.relax_thermo_proteins( + iis, status = geckopy.integration.relaxation.relax_thermo_proteins( tmodel_prot, prot_candidates=[prot.id for prot in tmodel_prot.proteins], objective_rule=geckopy.experimental.relaxation.Objective_rule.MIN_ELASTIC_SUM diff --git a/geckopy/integration/__init__.py b/geckopy/integration/__init__.py index 9cec561..04cc201 100644 --- a/geckopy/integration/__init__.py +++ b/geckopy/integration/__init__.py @@ -11,4 +11,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from .relaxation import relax_thermo_concentrations_proteins, relax_thermo_proteins diff --git a/geckopy/integration/multitfa.py b/geckopy/integration/multitfa.py new file mode 100644 index 0000000..dc72794 --- /dev/null +++ b/geckopy/integration/multitfa.py @@ -0,0 +1,121 @@ +from copy import copy, deepcopy + +import geckopy +from cobra.core.dictlist import DictList + +from multitfa.core import tmodel +from multitfa.core import Thermo_met, thermo_reaction + + +class ThermoProtReaction(thermo_reaction): + """ + Class representation of thermo reaction Object. We calculate the required thermodynamic constraints for performing tMFA. To do the constraints, we need Gibbs energy of reaction and transport. + + Parameters + ---------- + cobra_rxn : cobra.core.Reaction + Cobra reaction object, to copy the attributes from. We copy metabolites and genes. + updated_model : core.tmodel, optional + tmodel object, with updated thermo properties, by default None + + """ + + def __init__( + self, + cobra_rxn, + updated_model=None, + ): + self._model = updated_model + do_not_copy_by_ref = {"_model", "_metabolites", "_genes"} + for attr, value in cobra_rxn.__dict__.items(): + if attr not in do_not_copy_by_ref: + self.__dict__[attr] = copy(value) + + self._metabolites = {} + for met, stoic in cobra_rxn._metabolites.items(): + # this is the only change; also account for proteins + new_met = self.model.metabolites.get_by_id(met.id) if met in self.model.metabolites else self.model.proteins.get_by_id(met.id) + self._metabolites[new_met] = stoic + new_met._reaction.add(self) + self._genes = set() + for gene in cobra_rxn._genes: + new_gene = self.model.genes.get_by_id(gene.id) + self._genes.add(new_gene) + new_gene._reaction.add(self) + + +class ThermoProtModel(tmodel): + def __init__( + self, + model, + Exclude_list=[], + tolerance_integral=1e-9, + compartment_info=None, + membrane_potential=None, + exclude_metabolites=[], + ): + + self.compartment_info = compartment_info + self.membrane_potential = membrane_potential + + do_not_copy_by_ref = { + "metabolites", + "reactions", + "proteins", + "genes", + "notes", + "annotation", + } + for attr in model.__dict__: + if attr not in do_not_copy_by_ref: + self.__dict__[attr] = model.__dict__[attr] + + self.metabolites = DictList() + do_not_copy_by_ref = {"_reaction", "_model"} + for metabolite in model.metabolites: + new_met = Thermo_met( + metabolite=metabolite, + updated_model=self, + ) + self.metabolites.append(new_met) + + self.genes = DictList() + + for gene in model.genes: + new_gene = gene.__class__(None) + for attr, value in gene.__dict__.items(): + if attr not in do_not_copy_by_ref: + new_gene.__dict__[attr] = ( + copy(value) if attr == "formula" else value + ) + new_gene._model = self + self.genes.append(new_gene) + self.proteins = DictList() + for protein in model.proteins: + new_prot = Thermo_met( + metabolite=protein, + updated_model=self, + ) + # proteins do not participate in dGf calculations + new_prot.is_ignore = True + self.proteins.append(new_prot) + + self.reactions = DictList() + do_not_copy_by_ref = {"_model", "_metabolites", "_genes"} + for reaction in model.reactions: + # this is custom to make the reaction aware of proteins + new_reaction = ThermoProtReaction( + cobra_rxn=reaction, + updated_model=self, + ) + self.reactions.append(new_reaction) + + try: + self._solver = deepcopy(model.solver) + # Cplex has an issue with deep copies + except Exception: # pragma: no cover + self._solver = copy(model.solver) # pragma: no cover + + self.Exclude_list = Exclude_list + self.solver.configuration.tolerances.integrality = tolerance_integral + self._var_update = False diff --git a/tests/test_integration/test_multitfa.py b/tests/test_integration/test_multitfa.py new file mode 100644 index 0000000..64c2a77 --- /dev/null +++ b/tests/test_integration/test_multitfa.py @@ -0,0 +1,15 @@ +import pytest +from optlang import available_solvers + +from geckopy.integration.multitfa import ThermoProtModel + + +@pytest.mark.skipif(not (available_solvers["GUROBI"] or available_solvers["CPLEX"])) +def test_protein_constrain_affects_multitfa_solution(ec_model_core): + """Check thermo model returns different solution when protein is constrained.""" + thermo_model = ThermoProtModel(ec_model_core.copy()) + tsol = thermo_model.slim_optimize() + ec_model_core.proteins.prot_P25516.add_concentration(2e-5) + thermo_model = ThermoProtModel(ec_model_core) + tsol_prot = thermo_model.slim_optimize() + assert pytest.approx(tsol) != tsol_prot diff --git a/tests/test_integration/test_relaxation.py b/tests/test_integration/test_relaxation.py index 71cda79..4369da9 100644 --- a/tests/test_integration/test_relaxation.py +++ b/tests/test_integration/test_relaxation.py @@ -23,7 +23,7 @@ from geckopy.experimental import from_copy_number from geckopy.experimental.relaxation import Objective_rule -from geckopy.integration import ( +from geckopy.integration.relaxation import ( relax_thermo_concentrations_proteins, relax_thermo_proteins, ) From 31be61f003dceb11bf84e77f14ca88831f819705 Mon Sep 17 00:00:00 2001 From: carrascomj Date: Tue, 4 Oct 2022 13:49:49 +0200 Subject: [PATCH 2/3] fix: remove is_ignore as it is not needed --- geckopy/integration/multitfa.py | 1 - tests/test_integration/test_multitfa.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/geckopy/integration/multitfa.py b/geckopy/integration/multitfa.py index dc72794..a505d34 100644 --- a/geckopy/integration/multitfa.py +++ b/geckopy/integration/multitfa.py @@ -97,7 +97,6 @@ def __init__( updated_model=self, ) # proteins do not participate in dGf calculations - new_prot.is_ignore = True self.proteins.append(new_prot) self.reactions = DictList() diff --git a/tests/test_integration/test_multitfa.py b/tests/test_integration/test_multitfa.py index 64c2a77..0cc3c64 100644 --- a/tests/test_integration/test_multitfa.py +++ b/tests/test_integration/test_multitfa.py @@ -4,7 +4,7 @@ from geckopy.integration.multitfa import ThermoProtModel -@pytest.mark.skipif(not (available_solvers["GUROBI"] or available_solvers["CPLEX"])) +@pytest.mark.skipif(not (available_solvers["GUROBI"] or available_solvers["CPLEX"]), reason="No quadratic programming available") def test_protein_constrain_affects_multitfa_solution(ec_model_core): """Check thermo model returns different solution when protein is constrained.""" thermo_model = ThermoProtModel(ec_model_core.copy()) From aed4416404c17fb46946c05aef0384434a6ca06b Mon Sep 17 00:00:00 2001 From: carrascomj Date: Tue, 4 Oct 2022 13:51:05 +0200 Subject: [PATCH 3/3] style: black and isort --- geckopy/integration/multitfa.py | 11 +++++++---- tests/test_integration/test_multitfa.py | 5 ++++- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/geckopy/integration/multitfa.py b/geckopy/integration/multitfa.py index a505d34..44dab27 100644 --- a/geckopy/integration/multitfa.py +++ b/geckopy/integration/multitfa.py @@ -1,10 +1,9 @@ from copy import copy, deepcopy -import geckopy from cobra.core.dictlist import DictList +from multitfa.core import Thermo_met, thermo_reaction, tmodel -from multitfa.core import tmodel -from multitfa.core import Thermo_met, thermo_reaction +import geckopy class ThermoProtReaction(thermo_reaction): @@ -34,7 +33,11 @@ def __init__( self._metabolites = {} for met, stoic in cobra_rxn._metabolites.items(): # this is the only change; also account for proteins - new_met = self.model.metabolites.get_by_id(met.id) if met in self.model.metabolites else self.model.proteins.get_by_id(met.id) + new_met = ( + self.model.metabolites.get_by_id(met.id) + if met in self.model.metabolites + else self.model.proteins.get_by_id(met.id) + ) self._metabolites[new_met] = stoic new_met._reaction.add(self) self._genes = set() diff --git a/tests/test_integration/test_multitfa.py b/tests/test_integration/test_multitfa.py index 0cc3c64..fc550f6 100644 --- a/tests/test_integration/test_multitfa.py +++ b/tests/test_integration/test_multitfa.py @@ -4,7 +4,10 @@ from geckopy.integration.multitfa import ThermoProtModel -@pytest.mark.skipif(not (available_solvers["GUROBI"] or available_solvers["CPLEX"]), reason="No quadratic programming available") +@pytest.mark.skipif( + not (available_solvers["GUROBI"] or available_solvers["CPLEX"]), + reason="No quadratic programming available", +) def test_protein_constrain_affects_multitfa_solution(ec_model_core): """Check thermo model returns different solution when protein is constrained.""" thermo_model = ThermoProtModel(ec_model_core.copy())