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..44dab27 --- /dev/null +++ b/geckopy/integration/multitfa.py @@ -0,0 +1,123 @@ +from copy import copy, deepcopy + +from cobra.core.dictlist import DictList +from multitfa.core import Thermo_met, thermo_reaction, tmodel + +import geckopy + + +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 + 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..fc550f6 --- /dev/null +++ b/tests/test_integration/test_multitfa.py @@ -0,0 +1,18 @@ +import pytest +from optlang import available_solvers + +from geckopy.integration.multitfa import ThermoProtModel + + +@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()) + 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, )