def test_inequality(self, solver_test): solver, old_solution, infeasible_model = solver_test # The space enclosed by the constraints is a 2D triangle with # vertexes as (3, 0), (1, 2), and (0, 1) # c1 encodes y - x > 1 ==> y > x - 1 # c2 encodes y + x < 3 ==> y < 3 - x c1 = Metabolite("c1") c2 = Metabolite("c2") x = Reaction("x") x.lower_bound = 0 y = Reaction("y") y.lower_bound = 0 x.add_metabolites({c1: -1, c2: 1}) y.add_metabolites({c1: 1, c2: 1}) c1._bound = 1 c1._constraint_sense = "G" c2._bound = 3 c2._constraint_sense = "L" m = Model() m.add_reactions([x, y]) # test that optimal values are at the vertices m.objective = "x" assert abs(solver.solve(m).f - 1.0) < 10 ** -3 assert abs(solver.solve(m).x_dict["y"] - 2.0) < 10 ** -3 m.objective = "y" assert abs(solver.solve(m).f - 3.0) < 10 ** -3 assert abs( solver.solve(m, objective_sense="minimize").f - 1.0) < 10 ** -3
def test_quadratic(self, solver_test): solver, old_solution, infeasible_model = solver_test if not hasattr(solver, "set_quadratic_objective"): pytest.skip("no qp support") c = Metabolite("c") c._bound = 2 x = Reaction("x") x.objective_coefficient = -0.5 x.lower_bound = 0. y = Reaction("y") y.objective_coefficient = -0.5 y.lower_bound = 0. x.add_metabolites({c: 1}) y.add_metabolites({c: 1}) m = Model() m.add_reactions([x, y]) lp = solver.create_problem(m) quadratic_obj = scipy.sparse.eye(2) * 2 solver.set_quadratic_objective(lp, quadratic_obj) solver.solve_problem(lp, objective_sense="minimize") solution = solver.format_solution(lp, m) assert solution.status == "optimal" # Respecting linear objectives also makes the objective value 1. assert abs(solution.f - 1.) < 10 ** -3 assert abs(solution.x_dict["y"] - 1.) < 10 ** -3 assert abs(solution.x_dict["y"] - 1.) < 10 ** -3 # When the linear objectives are removed the objective value is 2. solver.change_variable_objective(lp, 0, 0.) solver.change_variable_objective(lp, 1, 0.) solver.solve_problem(lp, objective_sense="minimize") solution = solver.format_solution(lp, m) assert solution.status == "optimal" assert abs(solution.f - 2.) < 10 ** -3 # test quadratic from solve function solution = solver.solve(m, quadratic_component=quadratic_obj, objective_sense="minimize") assert solution.status == "optimal" assert abs(solution.f - 1.) < 10 ** -3 c._bound = 6 z = Reaction("z") x.objective_coefficient = 0. y.objective_coefficient = 0. z.lower_bound = 0. z.add_metabolites({c: 1}) m.add_reaction(z) solution = solver.solve(m, quadratic_component=scipy.sparse.eye(3), objective_sense="minimize") # should be 12 not 24 because 1/2 (V^T Q V) assert solution.status == "optimal" assert abs(solution.f - 6) < 10 ** -3 assert abs(solution.x_dict["x"] - 2) < 10 ** -6 assert abs(solution.x_dict["y"] - 2) < 10 ** -6 assert abs(solution.x_dict["z"] - 2) < 10 ** -6
def test_change_coefficient(self, solver_test): solver, old_solution, infeasible_model = solver_test c = Metabolite("c") c._bound = 6 x = Reaction("x") x.lower_bound = 1. y = Reaction("y") y.lower_bound = 0. x.add_metabolites({c: 1}) z = Reaction("z") z.add_metabolites({c: 1}) z.objective_coefficient = 1 m = Model("test_model") m.add_reactions([x, y, z]) # change an existing coefficient lp = solver.create_problem(m) solver.solve_problem(lp) sol1 = solver.format_solution(lp, m) assert sol1.status == "optimal" solver.change_coefficient(lp, 0, 0, 2) solver.solve_problem(lp) sol2 = solver.format_solution(lp, m) assert sol2.status == "optimal" assert abs(sol1.f - 5.0) < 10 ** -3 assert abs(sol2.f - 4.0) < 10 ** -3 # change a new coefficient z.objective_coefficient = 0. y.objective_coefficient = 1. lp = solver.create_problem(m) solver.change_coefficient(lp, 0, 1, 2) solver.solve_problem(lp) solution = solver.format_solution(lp, m) assert solution.status == "optimal" assert abs(solution.x_dict["y"] - 2.5) < 10 ** -3
def test_solve_mip(self, solver_test): solver, old_solution, infeasible_model = solver_test if not hasattr(solver, "_SUPPORTS_MILP") or not solver._SUPPORTS_MILP: pytest.skip("no milp support") cobra_model = Model('MILP_implementation_test') constraint = Metabolite("constraint") constraint._bound = 2.5 x = Reaction("x") x.lower_bound = 0. x.objective_coefficient = 1. x.add_metabolites({constraint: 2.5}) y = Reaction("y") y.lower_bound = 0. y.objective_coefficient = 1. y.add_metabolites({constraint: 1.}) cobra_model.add_reactions([x, y]) float_sol = solver.solve(cobra_model) # add an integer constraint y.variable_kind = "integer" int_sol = solver.solve(cobra_model) assert abs(float_sol.f - 2.5) < 10 ** -5 assert abs(float_sol.x_dict["y"] - 2.5) < 10 ** -5 assert int_sol.status == "optimal" assert abs(int_sol.f - 2.2) < 10 ** -3 assert abs(int_sol.x_dict["y"] - 2.0) < 10 ** -3
def test_canonical_form(self, model): # add G constraint to test g_constr = Metabolite("SUCCt2_2__test_G_constraint") g_constr._constraint_sense = "G" g_constr._bound = 5.0 model.reactions.get_by_id("SUCCt2_2").add_metabolites({g_constr: 1}) assert abs(model.optimize("maximize").f - 0.855) < 0.001 # convert to canonical form model = canonical_form(model) assert abs(model.optimize("maximize").f - 0.855) < 10**-3
def test_canonical_form(self, model): # add G constraint to test g_constr = Metabolite("SUCCt2_2__test_G_constraint") g_constr._constraint_sense = "G" g_constr._bound = 5.0 model.reactions.get_by_id("SUCCt2_2").add_metabolites({g_constr: 1}) assert abs(model.optimize("maximize").f - 0.855) < 0.001 # convert to canonical form model = canonical_form(model) assert abs(model.optimize("maximize").f - 0.855) < 10 ** -3
def construct_loopless_model(cobra_model): """Construct a loopless model. This adds MILP constraints to prevent flux from proceeding in a loop, as done in http://dx.doi.org/10.1016/j.bpj.2010.12.3707 Please see the documentation for an explanation of the algorithm. This must be solved with an MILP capable solver. """ # copy the model and make it irreversible model = cobra_model.copy() convert_to_irreversible(model) max_ub = max(model.reactions.list_attr("upper_bound")) # a dict for storing S^T thermo_stoic = { "thermo_var_" + metabolite.id: {} for metabolite in model.metabolites } # Slice operator is so that we don't get newly added metabolites original_metabolites = model.metabolites[:] for reaction in model.reactions[:]: # Boundary reactions are not subjected to these constraints if len(reaction._metabolites) == 1: continue # populate the S^T dict bound_id = "thermo_bound_" + reaction.id for met, stoic in iteritems(reaction._metabolites): thermo_stoic["thermo_var_" + met.id][bound_id] = stoic # I * 1000 > v --> I * 1000 - v > 0 reaction_ind = Reaction(reaction.id + "_indicator") reaction_ind.variable_kind = "integer" reaction_ind.upper_bound = 1 reaction_ub = Metabolite(reaction.id + "_ind_ub") reaction_ub._constraint_sense = "G" reaction.add_metabolites({reaction_ub: -1}) reaction_ind.add_metabolites({reaction_ub: max_ub}) # This adds a compensating term for 0 flux reactions, so we get # S^T x - (1 - I) * 1001 < -1 which becomes # S^T x < 1000 for 0 flux reactions and # S^T x < -1 for reactions with nonzero flux. reaction_bound = Metabolite(bound_id) reaction_bound._constraint_sense = "L" reaction_bound._bound = max_ub reaction_ind.add_metabolites({reaction_bound: max_ub + 1}) model.add_reaction(reaction_ind) for metabolite in original_metabolites: metabolite_var = Reaction("thermo_var_" + metabolite.id) metabolite_var.lower_bound = -max_ub model.add_reaction(metabolite_var) metabolite_var.add_metabolites({ model.metabolites.get_by_id(k): v for k, v in iteritems(thermo_stoic[metabolite_var.id]) }) return model
def test_canonical_form(self): model = create_test_model("textbook") # add G constraint to test g_constr = Metabolite("SUCCt2_2__test_G_constraint") g_constr._constraint_sense = "G" g_constr._bound = 5.0 model.reactions.get_by_id("SUCCt2_2").add_metabolites({g_constr: 1}) self.assertAlmostEqual(model.optimize("maximize").f, 0.855, places=3) # convert to canonical form model = canonical_form(model) self.assertAlmostEqual(model.optimize("maximize").f, 0.855, places=3)
def test_canonical_form(self): model = create_test_model("textbook") # add G constraint to test g_constr = Metabolite("SUCCt2_2__test_G_constraint") g_constr._constraint_sense = "G" g_constr._bound = 5.0 model.reactions.get_by_id("SUCCt2_2").add_metabolites({g_constr: 1}) self.assertAlmostEqual(model.optimize("maximize").f, 0.855, places=3) # convert to canonical form model = canonical_form(model) self.assertAlmostEqual(model.optimize("maximize").f, 0.855, places=3)
def construct_loopless_model(cobra_model): """Construct a loopless model. This adds MILP constraints to prevent flux from proceeding in a loop, as done in http://dx.doi.org/10.1016/j.bpj.2010.12.3707 Please see the documentation for an explanation of the algorithm. This must be solved with an MILP capable solver. """ # copy the model and make it irreversible model = cobra_model.copy() convert_to_irreversible(model) max_ub = max(model.reactions.list_attr("upper_bound")) # a dict for storing S^T thermo_stoic = {"thermo_var_" + metabolite.id: {} for metabolite in model.metabolites} # Slice operator is so that we don't get newly added metabolites original_metabolites = model.metabolites[:] for reaction in model.reactions[:]: # Boundary reactions are not subjected to these constraints if len(reaction._metabolites) == 1: continue # populate the S^T dict bound_id = "thermo_bound_" + reaction.id for met, stoic in iteritems(reaction._metabolites): thermo_stoic["thermo_var_" + met.id][bound_id] = stoic # I * 1000 > v --> I * 1000 - v > 0 reaction_ind = Reaction(reaction.id + "_indicator") reaction_ind.variable_kind = "integer" reaction_ind.upper_bound = 1 reaction_ub = Metabolite(reaction.id + "_ind_ub") reaction_ub._constraint_sense = "G" reaction.add_metabolites({reaction_ub: -1}) reaction_ind.add_metabolites({reaction_ub: max_ub}) # This adds a compensating term for 0 flux reactions, so we get # S^T x - (1 - I) * 1001 < -1 which becomes # S^T x < 1000 for 0 flux reactions and # S^T x < -1 for reactions with nonzero flux. reaction_bound = Metabolite(bound_id) reaction_bound._constraint_sense = "L" reaction_bound._bound = max_ub reaction_ind.add_metabolites({reaction_bound: max_ub + 1}) model.add_reaction(reaction_ind) for metabolite in original_metabolites: metabolite_var = Reaction("thermo_var_" + metabolite.id) metabolite_var.lower_bound = -max_ub model.add_reaction(metabolite_var) metabolite_var.add_metabolites( {model.metabolites.get_by_id(k): v for k, v in iteritems(thermo_stoic[metabolite_var.id])}) return model
def convert_to_irreversible_with_indicators(cobra_model,reaction_id_list,metabolite_list, mutually_exclusive_directionality_constraint = False,label_model=None): #Function modified from the work by : """Schmidt BJ1, Ebrahim A, Metz TO, Adkins JN, Palsson B, Hyduke DR. GIM3E: condition-specific models of cellular metabolism developed from metabolomics and expression data Bioinformatics. 2013 Nov 15;29(22):2900-8. doi: 10.1093/bioinformatics/btt493. Epub 2013 Aug 23.""" """Will break all of the reversible reactions into two separate irreversible reactions with different directions. This function call modified from a version in the core cobra to facilitate the MILP formulation and include gene_reaction_rules with the reverse reaction Arguments: cobra_model: A model object which will be modified in place. mutually_exclusive_directionality_constraint: Boolean. If True, turnover reactions are constructed to serve as MILP constraints to prevent loops. Returns: None, cobra_model is modified in place """ reactions_to_add = [] from cobra.core.Reaction import Reaction from cobra.core import Metabolite reactions_to_make_irreversible=[] for x in reaction_id_list: reactions_to_make_irreversible.append(cobra_model.reactions.get_by_id(x)) """for x in lexs: reactions_to_make_irreversible.append(cobra_model.reactions.get_by_id(x))""" #If a label model object is provided make sure all experimentally measured metabolites (or at least one of the metabolites in the pool) is produced full_metabolite_list=copy.copy(metabolite_list) print full_metabolite_list if label_model!=None: emus=[] for condition in label_model.experimental_dict: for emu in label_model.experimental_dict[condition]: if emu not in emus: emus.append(emu) measured_metabolite_dict={} for emu in emus: iso_id=str(label_model.emu_dict[emu]["met_id"]) #print label_model.id_isotopomer_object_dict #isotopomer_object=label_model.id_isotopomer_object_dict[iso_id] metabolites=label_model.isotopomer_id_metabolite_id_dict[iso_id] print [iso_id,label_model.isotopomer_id_metabolite_id_dict[iso_id]] if isinstance(metabolites,list): for metabolite in metabolites: full_metabolite_list.append(metabolite) else: full_metabolite_list.append(metabolites) for metabolites in full_metabolite_list: print metabolites if not isinstance(metabolites,list): metabolites=[metabolites] for metabolite in metabolites: print metabolite the_metabolite=cobra_model.metabolites.get_by_id(metabolite) for x in the_metabolite.reactions: if x not in reactions_to_make_irreversible: reactions_to_make_irreversible.append(x) for reaction in reactions_to_make_irreversible: # Potential artifact because a reaction might run backwards naturally # and this would result in adding an empty reaction to the # model in addition to the reverse reaction. if reaction.lower_bound < 0: #reverse_reaction = Reaction(reaction.id + "_reverse") reverse_reaction = reaction.copy() reverse_reaction.id = reaction.id + "_reverse" reverse_reaction.lower_bound = max(0,-1*reaction.upper_bound) reverse_reaction.upper_bound = reaction.lower_bound * -1. reaction.lower_bound = 0 if reaction.upper_bound<0: reaction.upper_bound=0 # Make the directions aware of each other reaction.notes["reflection"] = reverse_reaction.id reverse_reaction.notes["reflection"] = reaction.id reaction_dict = {} current_metabolites = [x for x in reaction.metabolites] for the_metabolite in current_metabolites: reaction_dict[the_metabolite] = -2 * reaction.get_coefficient(the_metabolite.id) reverse_reaction.add_metabolites(reaction_dict) reactions_to_add.append(reverse_reaction) # Also: GPRs should already copy # reverse_reaction.gene_reaction_rule = reaction.gene_reaction_rule # reverse_reaction._genes = reaction._genes if mutually_exclusive_directionality_constraint: # A continuous reaction bounded by 0., 1. # Serves as a source for the indicator metabolites tmp_source = Reaction('IRRMILP_direction_constraint_source_for_%s_and_%s' %(reaction.id, reverse_reaction.id)) tmp_source.upper_bound = 1. tmp_source.lower_bound = 0. # The reverse indicator reaction is # an integer-valued reaction bounded by 0,1 # that activates flux to the reverse reaction # and deactivates the forward reaction only when it is # turned on to 1 tmp_indicator = Reaction('IRRMILP_reverse_indicator_for_%s_and_%s' %(reaction.id, reverse_reaction.id)) tmp_indicator.upper_bound = 1 tmp_indicator.lower_bound = 0 tmp_indicator.variable_kind = 'integer' flux_constraint_forward = Metabolite(id = 'IRRMILP_direction_constraint_for_%s'%reaction.id) flux_constraint_reverse = Metabolite(id = 'IRRMILP_direction_constraint_for_%s'%reverse_reaction.id) flux_constraint_reverse._constraint_sense = 'G' flux_constraint_reverse._bound = 0. tmp_source.add_metabolites({flux_constraint_forward: 1}) tmp_indicator.add_metabolites({flux_constraint_forward: -1, flux_constraint_reverse: 1}) if reaction.upper_bound != 0: reaction.add_metabolites({flux_constraint_forward: -1./reaction.upper_bound}) else: # could put 1.01 X the tolerance here, # This is arbitrary. Use 0.001 # since 1000 is a typical upper bound reaction.add_metabolites({flux_constraint_forward: -0.001}) if reverse_reaction.upper_bound != 0: reverse_reaction.add_metabolites({flux_constraint_reverse: -1./reverse_reaction.upper_bound}) else: reverse_reaction.add_metabolites({flux_constraint_reverse: -0.001}) reactions_to_add.append(tmp_indicator) reactions_to_add.append(tmp_source) cobra_model.add_reactions(reactions_to_add)
def optimize_minimum_flux(model, objective_sense='maximize', tolerance_optimality=1e-8, tolerance_feasibility=1e-8): # return the flux distribution in which the total amount of fluxes is minimum while the growth is maximum #Get the optimal wt objective value and adjust based on optimality tolerances model.optimize() optimal_value = deepcopy(model.solution.f) # print('opt val: %s' % optimal_value) if objective_sense == 'maximize': optimal_value = floor(optimal_value/tolerance_optimality)*tolerance_optimality else: optimal_value = ceil(optimal_value/tolerance_optimality)*tolerance_optimality # print('adjusted opt val: %s' % optimal_value) #Add in the virtual objective metabolite to constrain the wt_model to the space where #the objective was maximal objective_metabolite = Metabolite('objective metabolite') objective_metabolite._bound = optimal_value if objective_sense == 'maximize': objective_metabolite._constraint_sense = 'G' else: objective_metabolite._constraint_sense = 'L' # print('objm const sense: %s, objm bound: %s' % (objective_metabolite._constraint_sense, objective_metabolite._bound)) # construct irreversible model to assure all flux values are positive irreve_model = model.copy() # this is necessary to avoid invalid bound error when model is changed to irreversible for r in irreve_model.reactions: if r.upper_bound < 0: reverse_reaction = Reaction(r.id + "_reverse") reverse_reaction.lower_bound = r.upper_bound * -1 reverse_reaction.upper_bound = r.lower_bound * -1 reverse_reaction.objective_coefficient = r.objective_coefficient * -1 reaction_dict = dict([(k, v*-1) for k, v in r.metabolites.items()]) reverse_reaction.add_metabolites(reaction_dict) irreve_model.add_reaction(reverse_reaction) r.upper_bound, r.lower_bound = 0, 0 cobra.manipulation.modify.convert_to_irreversible(irreve_model) objective_reaction_coefficient_dict = dict([(x.id, x.objective_coefficient) for x in model.reactions if x.objective_coefficient]) # this couples the objective reaction to the virtual metabolite [irreve_model.reactions.get_by_id(k).add_metabolites({objective_metabolite: v}) for k, v in objective_reaction_coefficient_dict.items()] # print('irregular metabolites: %s' % [(m.id, m._constraint_sense, m._bound) # for m in irreve_model.metabolites if m._constraint_sense != 'E' or m._bound != 0]) # minimize the sum of fluxes for r in irreve_model.reactions: r.objective_coefficient = 1 # print([r.id for r in irreve_model.reactions if r.objective_coefficient != 1]) # print(tolerance_feasibility) irreve_model.optimize(objective_sense='minimize', tolerance_feasibility=tolerance_feasibility) # adjust this to the solution of wt_model original_flux = model.solution.x_dict irreve_flux = irreve_model.solution.x_dict for k in original_flux.keys(): original_flux[k] = irreve_flux[k] # if reverse reaction exists and its flux is not zero, assign as a negative flux in wt_flux if k + '_reverse' in irreve_flux.keys() and irreve_flux[k + '_reverse'] != 0: if irreve_flux[k] != 0: print('Attention: non-optimal solution') original_flux[k] = -irreve_flux[k + '_reverse'] model.solution.status = irreve_model.solution.status model.solution.f = sum([irreve_model.reactions.get_by_id(k).x * v for k, v in objective_reaction_coefficient_dict.items()]) return model.solution
def moma(wt_model, mutant_model, objective_sense='maximize', solver=None, tolerance_optimality=1e-8, tolerance_feasibility=1e-8, minimize_norm=True, norm_flux_dict=None, the_problem='return', lp_method=0, combined_model=None, norm_type='euclidean'): """Runs the minimization of metabolic adjustment method described in Segre et al 2002 PNAS 99(23): 15112-7. wt_model: A cobra.Model object mutant_model: A cobra.Model object with different reaction bounds vs wt_model. objective_sense: 'maximize' or 'minimize' solver: 'gurobi', 'cplex', or 'glpk'. Note: glpk cannot be used with norm_type 'euclidean' tolerance_optimality: Solver tolerance for optimality. tolerance_feasibility: Solver tolerance for feasibility. the_problem: None or a problem object for the specific solver that can be used to hot start the next solution. lp_method: The method to use for solving the problem. Depends on the solver. See the cobra.flux_analysis.solvers.py file for more info. For norm_type == 'euclidean': the primal simplex works best for the test model (gurobi: lp_method=0, cplex: lp_method=1) combined_model: an output from moma that represents the combined optimization to be solved. """ if solver is None: if norm_type == "euclidean": solver = get_solver_name(qp=True) else: solver = get_solver_name() # linear is not even implemented yet if combined_model is not None or the_problem not in ['return']: warn("moma currently does not support reusing models or problems. " +\ "continuing without them") combined_model = None the_problem = 'return' if solver.lower() == 'cplex' and lp_method == 0: #print 'for moma, solver method 0 is very slow for cplex. changing to method 1' lp_method = 1 if solver.lower() == 'glpk' and norm_type == 'euclidean': try: # from gurobipy import Model solver = 'gurobi' warn("GLPK can't solve quadratic problems like MOMA. Switched solver to %s"%solver) except: warn("GLPK can't solve quadratic problems like MOMA. Switching to linear MOMA") if norm_type == 'euclidean': #Reusing the basis can get the solver stuck. reuse_basis = False if combined_model and combined_model.norm_type != norm_type: print('Cannot use combined_model.norm_type = %s with user-specified norm type'%(combined_model.norm_type, norm_type)) print('Defaulting to user-specified norm_type') combined_model = None if norm_type == 'linear': raise Exception('linear MOMA is not currently implmented') quadratic_component = None if minimize_norm: if norm_flux_dict is None: optimize_minimum_flux(wt_model, objective_sense='maximize', tolerance_feasibility=tolerance_feasibility) norm_flux_dict = wt_model.solution.x_dict else: # update the solution of wt model according to norm_flux_dict wt_model.optimize() # this is just to make sure wt_model.solution and mutant_model.solution refer to different object. objective_reaction_coefficient_dict = dict([(x.id, x.objective_coefficient) for x in wt_model.reactions if x.objective_coefficient]) try: wt_model.solution.f = sum([norm_flux_dict[k] * v for k, v in objective_reaction_coefficient_dict.items()]) wt_model.solution.x_dict = norm_flux_dict except: print('incorrect norm_flux_dict') raise # formulate qMOMA using wt_flux as reference # make a copy not to change the objective coefficients of original mutant model mutant_model_moma = mutant_model.copy() nRxns = len(mutant_model_moma.reactions) quadratic_component = 2 * eye(nRxns, nRxns) # linear component [setattr(x, 'objective_coefficient', -2 * norm_flux_dict[x.id]) for x in mutant_model_moma.reactions] the_problem = mutant_model_moma.optimize(objective_sense='minimize', quadratic_component=quadratic_component, solver=solver, tolerance_optimality=tolerance_optimality, tolerance_feasibility=tolerance_feasibility, lp_method=lp_method) #, reuse_basis=reuse_basis) # this should be commented out when solver is 'cplex' if mutant_model_moma.solution.status != 'optimal': warn('optimal moma solution not found: solver status %s'%mutant_model_moma.solution.status +\ ' returning the problem, the_combined model, and the quadratic component for trouble shooting') return(the_problem, mutant_model_moma, quadratic_component) solution = mutant_model_moma.solution mutant_dict = {} mutant_f = sum([mutant_model.reactions.get_by_id(x.id).objective_coefficient * x.x for x in mutant_model_moma.reactions]) mutant_dict['objective_value'] = mutant_f mutant_dict['status'] = solution.status #TODO: Deal with maximize / minimize issues for a reversible model that's been converted to irreversible mutant_dict['flux_difference'] = flux_difference = sum([(norm_flux_dict[r.id] - mutant_model_moma.solution.x_dict[r.id])**2 for r in mutant_model_moma.reactions]) mutant_dict['the_problem'] = the_problem mutant_dict['mutant_model'] = mutant_model_moma # update the solution of original mutant model mutant_model.solution.x_dict = the_problem.x_dict mutant_model.solution.status = solution.status mutant_model.solution.f = mutant_f # del wt_model, mutant_model, quadratic_component, solution return(mutant_dict) else: #Construct a problem that attempts to maximize the objective in the WT model while #solving the quadratic problem. This new problem is constructed to try to find #a solution for the WT model that lies close to the mutant model. There are #often multiple equivalent solutions with M matrices and the one returned #by a simple cobra_model.optimize call may be too far from the mutant. #This only needs to be adjusted if we update mutant_model._S after deleting reactions number_of_reactions_in_common = len(set([x.id for x in wt_model.reactions]).intersection([x.id for x in mutant_model.reactions])) number_of_reactions = len(wt_model.reactions) + len(mutant_model.reactions) #Get the optimal wt objective value and adjust based on optimality tolerances wt_model.optimize(solver=solver) wt_optimal = deepcopy(wt_model.solution.f) if objective_sense == 'maximize': wt_optimal = floor(wt_optimal/tolerance_optimality)*tolerance_optimality else: wt_optimal = ceil(wt_optimal/tolerance_optimality)*tolerance_optimality if not combined_model: #Collect the set of wt reactions contributing to the objective. objective_reaction_coefficient_dict = dict([(x.id, x.objective_coefficient) for x in wt_model.reactions if x.objective_coefficient]) combined_model = construct_difference_model(wt_model, mutant_model, norm_type) #Add in the virtual objective metabolite to constrain the wt_model to the space where #the objective was maximal objective_metabolite = Metabolite('wt_optimal') objective_metabolite._bound = wt_optimal if objective_sense == 'maximize': objective_metabolite._constraint_sense = 'G' else: objective_metabolite._constraint_sense = 'L' #TODO: this couples the wt_model objective reaction to the virtual metabolite #Currently, assumes a single objective reaction; however, this may be extended [combined_model.reactions.get_by_id(k).add_metabolites({objective_metabolite: v}) for k, v in objective_reaction_coefficient_dict.items()] if norm_type == 'euclidean': #Makes assumptions about the structure of combined model quadratic_component = s_vstack((lil_matrix((number_of_reactions, number_of_reactions + number_of_reactions_in_common )), s_hstack((lil_matrix((number_of_reactions_in_common, number_of_reactions)), eye(number_of_reactions_in_common,number_of_reactions_in_common))))) elif norm_type == 'linear': quadratic_component = None combined_model.norm_type = norm_type cobra_model = combined_model the_problem = combined_model.optimize(objective_sense='minimize', quadratic_component=quadratic_component, solver=solver, tolerance_optimality=tolerance_optimality, tolerance_feasibility=tolerance_feasibility, lp_method=lp_method) #, reuse_basis=reuse_basis) # this should be commented out when solver is 'cplex' if combined_model.solution.status != 'optimal': warn('optimal moma solution not found: solver status %s'%combined_model.solution.status +\ ' returning the problem, the_combined model, and the quadratic component for trouble shooting') return(the_problem, combined_model, quadratic_component) solution = combined_model.solution mutant_dict = {} #Might be faster to quey based on mutant_model.reactions with the 'mutant_' prefix added _reaction_list = [x for x in combined_model.reactions if x.id.startswith('mutant_')] mutant_f = sum([mutant_model.reactions.get_by_id(x.id[len('mutant_'):]).objective_coefficient * x.x for x in _reaction_list]) mutant_dict['objective_value'] = mutant_f wild_type_flux_total = sum([abs(solution.x_dict[x.id]) for x in wt_model.reactions]) mutant_flux_total = sum(abs(x.x) for x in _reaction_list) #Need to use the new solution as there are multiple ways to achieve an optimal solution in #simulations with M matrices. mutant_dict['status'] = solution.status #TODO: Deal with maximize / minimize issues for a reversible model that's been converted to irreversible mutant_dict['flux_difference'] = flux_difference = sum([(solution.x_dict[x.id[len('mutant_'):]] - x.x)**2 for x in _reaction_list]) mutant_dict['the_problem'] = the_problem mutant_dict['combined_model'] = combined_model del wt_model, mutant_model, quadratic_component, solution return(mutant_dict)
def min_incon_parsi_mwRange(self, metInterest, **kwargs): '''The main step called by compute_met_range to find the range for the molecular weight Return min_incon_parsi_info object summarizing the results of solving MIP. formulae: (formula for min MW, formula max MW) mw_range: (min MW, max MW) rhs: {e: {i: RHS[i] for all metabolite i}} the RHS value in the MIP problem for each element solved. (compute_met_range only) infeas: infeasibility of each solve bound: bound used for total inconsistency or the relaxation value eps0 for each solve obj: objective function value for each solve solution: solution values for each type of variables (m, xp, xn) met_model: the MIP problem solved for each element as a cobra model. Same model but different rhs for different elements. final: the final status of the solution sol_stat: solution status for each element/connected set of elements ''' inf, neg_inf = self.infinity, self.negative_infinity pre = self.pre metK, metU, rxnK, ele, feasTol, digitRounded \ = pre.met_known, pre.met_unknown, pre.rxn_known, pre.ele, pre.feasTol, pre.digitRounded model = self.model #handle metInterest if isinstance(metInterest,type(model.metabolites[0])): metI = metInterest elif isinstance(metInterest, str): metI = model.metabolites.get_by_id(metInterest) if metI in metK: print("%s in the input is already known." %metI.id) mip_info = min_incon_parsi_info() mip_info.mw_range = (metK[metI].mw, metK[metI].mw) return mip_info print('Find the range for the molecular weight of %s ... %s' %(metI.id, datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) infeas, bound, solution, sol_stat, obj, rhs = ({} for i in range(6)) for k in ['minIncon', 'minMw', 'maxMw']: #pre-assignment solution[k] = {'m': {}, 'xp': {}, 'xn': {}} ct = 0 #The optimization problem for each element is the same except the RHS met_model = Model('min/max Mw of ' + metI.id) met_model.solver = self.__solver constraint = {j: Metabolite(j.id) for j in rxnK} m = {i: Reaction('m_' + i.id) for i in metU} xp = {j: Reaction('xp_' + j.id) for j in rxnK} xn = {j: Reaction('xn_' + j.id) for j in rxnK} for j in rxnK: xp[j].add_metabolites({constraint[j]: 1}) xn[j].add_metabolites({constraint[j]: -1}) xp[j].lower_bound, xn[j].lower_bound, xp[j].upper_bound, xn[j].upper_bound = 0, 0, inf, inf constraint[j]._constraint_sense = 'E' for i in metU: # S_ij x m_ie for each i m[i].add_metabolites({constraint[j]: j._metabolites[i] for j in list(i._reaction) if j in rxnK}) m[i].upper_bound = inf #Add constraint to fix the total inconsistency for each element constraint_minIncon = Metabolite('minIncon') constraint_minIncon._bound = inf constraint_minIncon._constraint_sense = 'L' for j in rxnK: xp[j].add_metabolites({constraint_minIncon: 1}) xn[j].add_metabolites({constraint_minIncon: 1}) met_model.add_reactions([m[i] for i in metU]) met_model.add_reactions([xp[j] for j in rxnK]) met_model.add_reactions([xn[j] for j in rxnK]) met_model.constraints[constraint_minIncon.id].lb = neg_inf #to avoid error objective_dict_minIncon = {xp[j]: 1 for j in rxnK} objective_dict_minIncon.update({xn[j]: 1 for j in rxnK}) for e in ele: ct += 1 print("Optimizing for %d / %d element ... %s" %(ct, len(ele), datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) # metModelJ = Model('min/max ' + e) # metModelJ.solver = self.__solver infeasJ, boundJ, objJ, rhsJ = {}, {}, {}, {} # constraint = {j: Metabolite(j.id + ',' + e) for j in rxnK} # m = {i: Reaction('m_' + i.id + ',' + e) for i in metU} # xp = {j: Reaction('xp_' + j.id + ',' + e) for j in rxnK} # xn = {j: Reaction('xn_' + j.id + ',' + e) for j in rxnK} # for j in rxnK: #RHS for each constraint: -sum(S_ij * m^known_ie) (obsolete) # constraint[j]._bound = -sum([S_ij * metK[i].elements[e] for i, S_ij in j._metabolites.items() if i in metK and e in metK[i].elements]) # constraint[j]._constraint_sense = 'E' #x_pos - x_neg for each constraint # xp[j].add_metabolites({constraint[j]: 1}) # xn[j].add_metabolites({constraint[j]: -1}) # xp[j].lower_bound, xn[j].lower_bound, xp[j].upper_bound, xn[j].upper_bound = 0, 0, inf, inf for i in metU: # # S_ij x m_ie for each i # m[i].add_metabolites({constraint[j]: j._metabolites[i] for j in list(i._reaction) if j in rxnK}) # m[i].upper_bound = inf m[i].lower_bound = neg_inf if e == 'Charge' else 0 #add reactions into the model # metModelJ.add_reactions([m[i] for i in metU]) # metModelJ.add_reactions([xp[j] for j in rxnK]) # metModelJ.add_reactions([xn[j] for j in rxnK]) #set the objective function # objective_dict = {xp[j]: 1 for j in rxnK} # objective_dict.update({xn[j]: 1 for j in rxnK}) met_model.constraints[constraint_minIncon.id].ub = inf set_objective(met_model, objective_dict_minIncon) #set RHS: -sum(S_ij * m^known_ie) for j in rxnK: met_model.constraints[constraint[j].id].ub = inf #ub must be set to be > lb to avoid error rhsJ[j] = -sum([S_ij * metK[i].elements[e] for i, S_ij in j._metabolites.items() if i in metK and e in metK[i].elements]) met_model.constraints[constraint[j].id].lb, met_model.constraints[constraint[j].id].ub = rhsJ[j], rhsJ[j] constraint[j]._bound = rhsJ[j] #Solve for minimum inconsistency kwargs['objective_sense'] = 'minimize' sol = met_model.optimize(**kwargs) solStatJ = 'minIncon' infeasJ[solStatJ] = solution_infeasibility(met_model, sol) if sol.fluxes is None: boundJ[solStatJ] = float('nan') else: boundJ[solStatJ] = sum([sol.fluxes[j.id] for j in chain(xp.values(), xn.values())]) if not infeasJ[solStatJ] <= feasTol: #infeasible (should not happen) infeasJ['minMw'], infeasJ['maxMw'] = inf, inf solStatJ = 'infeasible' objJ['minIncon'], objJ['minMw'], objJ['maxMw'] = (float('nan') for i in range(3)) for s in ['minIncon','minMw','maxMw']: solution[s]['m'][e] = {i: float('nan') for i in metU} solution[s]['xp'][e] = {j: float('nan') for j in rxnK} solution[s]['xn'][e] = {j: float('nan') for j in rxnK} else: #Feasible. Store the solution solution[solStatJ]['m'][e] = {i: sol.fluxes[m[i].id] for i in metU} solution[solStatJ]['xp'][e] = {j: sol.fluxes[xp[j].id] for j in rxnK} solution[solStatJ]['xn'][e] = {j: sol.fluxes[xn[j].id] for j in rxnK} objJ[solStatJ] = sol.objective_value # #Add constraint to fix the total inconsistency for each element # constraint_minIncon = Metabolite('minIncon_'+e) # constraint_minIncon._bound = round(boundJ['minIncon'], digitRounded) # constraint_minIncon._constraint_sense = 'L' # for j in rxnK: # xp[j].add_metabolites({constraint_minIncon: 1}) # xn[j].add_metabolites({constraint_minIncon: 1}) # met_model.constraints[constraint_minIncon.id].lb = neg_inf #to avoid error met_model.constraints[constraint_minIncon.id].ub = round(boundJ['minIncon'], digitRounded) #reset the objective function to minimize molecular weight objective_dict = {m[metI]: Formula(formula=e).mw} set_objective(met_model, objective_dict) solStatJ = 'minMw' kwargs['objective_sense'] = 'minimize' eps0 = 1e-6 while True: sol = met_model.optimize(**kwargs) infeasJ[solStatJ] = solution_infeasibility(met_model, sol) if infeasJ[solStatJ] <= feasTol or eps0 > 1e-4 + 1e-8: break eps0 *= 10 #rounding to avoid infeasibility due to numerical issues met_model.constraints[constraint_minIncon.id].ub = round(boundJ['minIncon'] * (1 + eps0), digitRounded) boundJ[solStatJ] = eps0 if infeasJ[solStatJ] <= feasTol: #Feasible. Store the solution solution[solStatJ]['m'].update({e: {i: sol.fluxes[m[i].id] for i in metU}}) solution[solStatJ]['xp'].update({e: {j: sol.fluxes[xp[j].id] for j in rxnK}}) solution[solStatJ]['xn'].update({e: {j: sol.fluxes[xn[j].id] for j in rxnK}}) objJ[solStatJ] = sol.objective_value else: #infeasible, should not happen objJ[solStatJ] = float('nan') solution['minMw']['m'][e] = {i: float('nan') for i in metU} solution['minMw']['xp'][e] = {j: float('nan') for j in rxnK} solution['minMw']['xn'][e] = {j: float('nan') for j in rxnK} #maximize molecular weight solStatJ = 'maxMw' #reset the bound for total inconsistency met_model.constraints[constraint_minIncon.id].ub = round(boundJ['minIncon'], digitRounded) kwargs['objective_sense'] = 'maximize' eps0 = 1e-6 while True: sol = met_model.optimize(**kwargs) infeasJ[solStatJ] = solution_infeasibility(met_model, sol) if infeasJ[solStatJ] <= feasTol or eps0 > 1e-4 + 1e-8: break eps0 *= 10 #rounding to avoid infeasibility due to numerical issues met_model.constraints[constraint_minIncon.id].ub = round(boundJ['minIncon'] * (1 + eps0), digitRounded) boundJ[solStatJ] = eps0 if infeasJ[solStatJ] <= feasTol: #Feasible. Store the solution solution[solStatJ]['m'].update({e: {i: sol.fluxes[m[i].id] for i in metU}}) solution[solStatJ]['xp'].update({e: {j: sol.fluxes[xp[j].id] for j in rxnK}}) solution[solStatJ]['xn'].update({e: {j: sol.fluxes[xn[j].id] for j in rxnK}}) objJ[solStatJ] = sol.objective_value else: #infeasible, should not happen objJ[solStatJ] = float('nan') solution['maxMw']['m'][e] = {i: float('nan') for i in metU} solution['maxMw']['xp'][e] = {j: float('nan') for j in rxnK} solution['maxMw']['xn'][e] = {j: float('nan') for j in rxnK} #store data infeas[e] = infeasJ bound[e] = boundJ obj[e] = objJ # met_model[e] = met_model sol_stat[e] = solStatJ rhs[e] = rhsJ #summarize the final solution state if any([k == 'infeasible' for k in sol_stat.values()]): print('Failure: no feasible solution can be found.') solFinal = 'infeasible' else: if all([obj[e]['minMw'] == obj[e]['minMw'] for e in ele]): if all([obj[e]['maxMw'] == obj[e]['maxMw'] for e in ele]): solFinal = 'minMw + maxMw' else: solFinal = 'minMw only' else: if all([obj[e]['maxMw'] == obj[e]['maxMw'] for e in ele]): solFinal = 'maxMw only' else: solFinal = 'minIncon' mip_info = min_incon_parsi_info() mip_info.infeas, mip_info.bound, mip_info.obj, mip_info.solution, \ mip_info.met_model, mip_info.final, mip_info.sol_stat \ = infeas, bound, obj, solution, met_model, solFinal, sol_stat mip_info.rhs = rhs mip_info.mw_range = (sum([obj[e]['minMw'] for e in ele]), sum([obj[e]['maxMw'] for e in ele])) mip_info.formulae = tuple([formula_dict2str({e: solution[s]['m'][e][metI] for e in ele}) for s in ['minMw', 'maxMw']]) return mip_info