Source code for imatpy.model_utils

"""
Module for model utilities
"""

# External Imports
from __future__ import annotations
import cobra
import optlang.container
from sympy import parse_expr


# region Model IO
[docs] def read_model(model_path, file_type=None): """ Read a model from a file :param model_path: Path to the model file :type model_path: str | pathlib.Path :param file_type: Type of the file :type file_type: str :return: The model """ if file_type is None: model_path = str(model_path) file_type = model_path.split(".")[-1] file_type = _parse_file_type(file_type) if file_type == "joblib": from joblib import load model = load(model_path) elif file_type == "pickle": import pickle with open(model_path, "rb") as f: model = pickle.load(f) elif file_type == "sbml": from cobra.io import read_sbml_model model = read_sbml_model(model_path) elif file_type == "yaml": from cobra.io import load_yaml_model model = load_yaml_model(model_path) elif file_type == "json": from cobra.io import load_json_model model = load_json_model(model_path) elif file_type == "mat": from cobra.io import load_matlab_model model = load_matlab_model(model_path) else: raise ValueError("File type not supported") return model
[docs] def write_model(model, model_path, file_type=None): """ Write a model to a file :param model: Model to write :type model: cobra.Model :param model_path: Path to the model file :type model_path: str :param file_type: Type of the file :type file_type: str :return: Nothing """ if file_type is None: model_path = str(model_path) file_type = model_path.split(".")[-1] file_type = _parse_file_type(file_type) if file_type == "joblib": from joblib import dump dump(model, model_path) elif file_type == "pickle": import pickle with open(model_path, "wb") as f: pickle.dump(model, f) elif file_type == "sbml": from cobra.io import write_sbml_model write_sbml_model(model, model_path) elif file_type == "yaml": from cobra.io import save_yaml_model save_yaml_model(model, model_path) elif file_type == "json": from cobra.io import save_json_model save_json_model(model, model_path) elif file_type == "mat": from cobra.io import save_matlab_model save_matlab_model(model, model_path) else: raise ValueError("File type not supported")
def _parse_file_type(file_type): """ Parse the file type :param file_type: File type to parse :type file_type: str :return: Parsed file type :rtype: str """ if file_type.lower() in ["json", "jsn"]: return "json" elif file_type.lower() in ["yaml", "yml"]: return "yaml" elif file_type.lower() in ["sbml", "xml"]: return "sbml" elif file_type.lower() in ["mat", "m", "matlab"]: return "mat" elif file_type.lower() in ["joblib", "jl", "jlb"]: return "joblib" elif file_type.lower() in ["pickle", "pkl"]: return "pickle" else: raise ValueError("File type not supported") # endregion: Model IO # region Model Comparison
[docs] def model_eq(model1: cobra.Model, model2: cobra.Model, verbose: bool = False) -> bool: """ Check if two cobra models are equal. :param model1: The first model to compare. :type model1: cobra.Model :param model2: The second model to compare. :type model2: cobra.Model :param verbose: Whether to print where the models differ (default: False). :type verbose: bool :return: True if the models are equal, False otherwise. :rtype: bool """ if verbose: print("Verbose model comparison") # Check metabolites, reactions, and genes if not _check_dictlist_eq(model1.metabolites, model2.metabolites): if verbose: print("Models have different metabolites") return False if not _check_dictlist_eq(model1.reactions, model2.reactions): if verbose: print("Models have different reactions") return False if not _check_dictlist_eq(model1.genes, model2.genes): if verbose: print("Models have different genes") return False # Check reaction equality (guaranteed they have the same reactions) for reaction1 in model1.reactions: reaction2 = model2.reactions.get_by_id(reaction1.id) if not _check_reaction_eq(reaction1, reaction2, verbose=verbose): return False # Check objective if not _check_objective_eq(model1.objective, model2.objective, verbose=verbose): if verbose: print("Models have different objectives") print(f"Model 1 objective: {model1.objective}") print(f"Model 2 objective: {model2.objective}") return False # Check the underlying constraint model if not _check_optlang_container_eq( model1.solver.constraints, model2.solver.constraints ): if verbose: print("Models have different constraints") return False if not _check_optlang_container_eq( model1.solver.variables, model2.solver.variables ): if verbose: print("Models have different variables") return False # Checking more specifics for the variables for var1 in model1.variables: var2 = model2.variables[var1.name] if not _check_variable_eq(var1, var2, verbose=verbose): return False # Checking more specifics for the constraints for const1 in model1.constraints: const2 = model2.constraints[const1.name] if not _check_constraint_eq(const1, const2, verbose=verbose): return False return True
def _check_dictlist_subset( dictlist1: cobra.DictList, dictlist2: cobra.DictList ) -> bool: """ Check if dictlist1 is a subset of dictlist2. :param dictlist1: The first dictlist to compare. :type dictlist1: cobra.DictList :param dictlist2: The second dictlist to compare. :type dictlist2: cobra.DictList :return: True if dictlist1 is a subset of dictlist2, False otherwise. :rtype: bool """ for val in dictlist1: if val not in dictlist2: return False return True def _check_dictlist_eq(dictlist1: cobra.DictList, dictlist2: cobra.DictList) -> bool: """ Check if two dictlists are equal. :param dictlist1: The first dictlist to compare. :type dictlist1: cobra.DictList :param dictlist2: The second dictlist to compare. :type dictlist2: cobra.DictList :return: True if the dictlists are equal, False otherwise. :rtype: bool """ if not _check_dictlist_subset(dictlist1, dictlist2): return False if not _check_dictlist_subset(dictlist2, dictlist1): return False return True def _check_optlang_container_subset( cont1: optlang.container.Container, cont2: optlang.container.Container ) -> bool: """ Check if cont1 is a subset of cont2. :param cont1: The first container to compare. :type cont1: optlang.container.Container :param cont2: The second container to compare. :type cont2: optlang.container.Container :return: True if cont1 is a subset of cont2, False otherwise. :rtype: bool """ for val in cont1: if val.name not in cont2: return False return True def _check_optlang_container_eq( cont1: optlang.container.Container, cont2: optlang.container.Container ) -> bool: """ Check if two optlang containers are equal. :param cont1: The first container to compare. :type cont1: optlang.container.Container :param cont2: The second container to compare. :type cont2: optlang.container.Container :return: True if the containers are equal, False otherwise. :rtype: bool """ if not _check_optlang_container_subset(cont1, cont2): return False if not _check_optlang_container_subset(cont2, cont1): return False return True def _check_reaction_eq( rxn1: cobra.Reaction, rxn2: cobra.Reaction, verbose: bool = False ) -> bool: """ Check if two reactions are equal. :param rxn1: The first reaction to compare. :type rxn1: cobra.Reaction :param rxn2: The second reaction to compare. :type rxn2: cobra.Reaction :param verbose: Whether to print where the reactions differ (default: False). :type verbose: bool :return: True if the reactions are equal, False otherwise. :rtype: bool """ if rxn1.lower_bound != rxn2.lower_bound: if verbose: print(f"Reaction {rxn1.id} has different lower bounds") return False if rxn1.upper_bound != rxn2.upper_bound: if verbose: print(f"Reaction {rxn1.id} has different upper bounds") return False if rxn1.gene_reaction_rule != rxn2.gene_reaction_rule: if verbose: print(f"Reaction {rxn1.id} has different GPR") return False if rxn1.name != rxn2.name: if verbose: print(f"Reaction {rxn1.id} has different names") return False if rxn1.subsystem != rxn2.subsystem: if verbose: print(f"Reaction {rxn1.id} has different subsystems") return False if rxn1.objective_coefficient != rxn2.objective_coefficient: if verbose: print(f"Reaction {rxn1.id} has different objective coefficients") return False return True def _check_expression_eq(expr1, expr2, verbose=False) -> bool: """ Check if two sympy or optlang expressions are equal. :param expr1: The first expression to compare. :type expr1: sympy.Expr or optlang.Expression :param expr2: The second expression to compare. :type expr2: sympy.Expr or optlang.Expression :param verbose: Whether to print where the expressions differ (default: False). :type verbose: bool :return: True if the expressions are equal, False otherwise. :rtype: bool """ if parse_expr(str(expr1)) - parse_expr(str(expr2)) != 0: print("ENTERED IF STATEMENT") if verbose: print(f"Expressions {expr1} and {expr2} are not equal") return False return True def _check_objective_eq(objective1, objective2, verbose=False) -> bool: """ Check if two objectives are equal. :param objective1: The first objective to compare. :type objective1: cobra.core.objective.Objective :param objective2: The second objective to compare. :type objective2: cobra.core.objective.Objective :param verbose: Whether to print where the objectives differ (default: False). :type verbose: bool :return: True if the objectives are equal, False otherwise. :rtype: bool """ expr1 = objective1.expression expr2 = objective2.expression if not _check_expression_eq(expr1, expr2, verbose=verbose): if verbose: print("Expressions of the objectives are different") return False if objective1.direction != objective2.direction: if verbose: print("Directions of the objectives are different") return False return True def _check_variable_eq(var1, var2, verbose: bool = False) -> bool: if var1.lb != var2.lb: if verbose: print(f"Variable {var1.name} has different lower bounds") return False if var1.ub != var2.ub: if verbose: print(f"Variable {var1.name} has different upper bounds") return False if var1.type != var2.type: if verbose: print(f"Variable {var1.name} has different types") return False return True def _check_constraint_eq(constraint1, constraint2, verbose: bool = False) -> bool: if constraint1.lb != constraint2.lb: if verbose: print(f"Constraint {constraint1.name} has different lower bounds") return False if constraint1.ub != constraint2.ub: if verbose: print(f"Constraint {constraint1.name} has different upper bounds") return False if not _check_expression_eq( constraint1.expression, constraint2.expression, verbose=verbose ): if verbose: print(f"Constraint {constraint1.name} has different expressions") return False return True # endregion: Model Comparison