def moma(wt_model, mutant_model, objective_sense='maximize', solver=None, tolerance_optimality=1e-8, tolerance_feasibility=1e-8, minimize_norm=False, 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. To simulate deletions 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. when this is not none. only assume that bounds have changed for the mutant or wild-type. This saves 0.2 seconds in stacking matrices. NOTE: Current function makes too many assumptions about the structures of the models """ if solver is None: if norm_type == "euclidiean": 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 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 norm_type == 'linear': raise Exception('linear MOMA is not currently implmented') quadratic_component = None if minimize_norm: raise Exception('minimize_norm is not currently implemented') #just worry about the flux distribution and not the objective from the wt combined_model = mutant_model.copy() #implement this: combined_model.reactions[:].objective_coefficients = -wt_solution.x_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 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) 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 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 moma(wt_model, mutant_model, objective_sense='maximize', solver='gurobi', tolerance_optimality=1e-8, tolerance_feasibility=1e-8, minimize_norm=False, the_problem='return', lp_method=0, combined_model=None, norm_type='euclidean', print_time=False): """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. To simulate deletions 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. when this is not none. only assume that bounds have changed for the mutant or wild-type. This saves 0.2 seconds in stacking matrices. """ warn('MOMA is currently non-functional. check back later') 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': print "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 #Add a prefix in front of the mutant_model metabolites and reactions to prevent #name collisions in DictList for the_dict_list in [mutant_model.metabolites, mutant_model.reactions]: [setattr(x, 'id', 'mutant_%s'%x.id) for x in the_dict_list] the_dict_list._generate_index() #Update the DictList.dicts wt_model.optimize(solver=solver) wt_solution = deepcopy(wt_model.solution) if objective_sense == 'maximize': wt_optimal = floor(wt_solution.f/tolerance_optimality)*tolerance_optimality else: wt_optimal = ceil(wt_solution.f/tolerance_optimality)*tolerance_optimality if norm_type == 'euclidean': quadratic_component = eye(wt_solution.x.shape[0],wt_solution.x.shape[0]) elif norm_type == 'linear': raise Exception('linear MOMA is not currently implmented') quadratic_component = None if minimize_norm: raise Exception('minimize_norm is not currently implemented') #just worry about the flux distribution and not the objective from the wt combined_model = mutant_model.copy() #implement this: combined_model.reactions[:].objective_coefficients = -wt_solution.x_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 if print_time: start_time = time() number_of_reactions = len(mutant_model.reactions) if norm_type == 'euclidean': reaction_coefficient = 1 elif norm_type == 'linear': reaction_coefficient = 2 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]) #This does a deepcopy of both models which might result in a huge overhead. #Update cobra.core.Model to improve performance. combined_model = wt_model + mutant_model if print_time: print 'add time %f'%(time()-start_time) [setattr(x, 'objective_coefficient', 0.) for x in combined_model.reactions] #Add in the difference reactions. The mutant reactions and metabolites are already added. #This must be a list to maintain the correct order when adding the difference_metabolites difference_reactions = [Reaction('difference_%i'%i) for i in range(reaction_coefficient*number_of_reactions)] [setattr(x, 'lower_bound', -1000) for x in difference_reactions] combined_model.add_reactions(difference_reactions) index_to_reaction = combined_model.reactions id_to_reaction = combined_model.reactions._object_dict #This is slow #Add in difference metabolites difference_metabolite_dict = dict([(i, Metabolite('difference_%i'%i)) for i in xrange(number_of_reactions)]) combined_model.add_metabolites(difference_metabolite_dict.values()) for i, tmp_metabolite in difference_metabolite_dict.iteritems(): if norm_type == 'linear': tmp_metabolite._constraint_sense = 'G' index_to_reaction[i].add_metabolites({tmp_metabolite: -1.}, add_to_container_model=False) index_to_reaction[i+number_of_reactions].add_metabolites({tmp_metabolite: 1.}, add_to_container_model=False) index_to_reaction[i+2*number_of_reactions].add_metabolites({tmp_metabolite: 1.}, add_to_container_model=False) #Add in the virtual objective metabolite 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 [id_to_reaction[k].add_metabolites({objective_metabolite: v}) for k, v in objective_reaction_coefficient_dict.items()] if print_time: print 'Took %f seconds to construct combined model'%(time()-start_time) start_time = time() if norm_type == 'euclidean': quadratic_component = s_vstack((lil_matrix((2*number_of_reactions, 3*number_of_reactions)), s_hstack((lil_matrix((number_of_reactions, 2*number_of_reactions)), quadratic_component)))) elif norm_type == 'linear': quadratic_component = None combined_model.norm_type = norm_type cobra_model = combined_model if print_time: print 'Took %f seconds to update combined model'%(time()-start_time) start_time = time() the_result = 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) the_problem = the_result the_solution = combined_model.solution if print_time: print 'Took %f seconds to solve problem'%(time()-start_time) start_time = time() mutant_dict = {} x_vector = the_solution.x if hasattr(x_vector, 'flatten'): x_vector = x_vector.flatten() mutant_dict['x'] = mutant_fluxes = array(x_vector[1*number_of_reactions:2*number_of_reactions]) #Need to use the new solution as there are multiple ways to achieve an optimal solution in #simulations with M matrices. wt_model.solution.x = array(x_vector[:number_of_reactions]) mutant_dict['objective_value'] = mutant_f = float(matrix(mutant_fluxes)*matrix([x.objective_coefficient for x in mutant_model.reactions]).T) mutant_dict['status'] = the_solution.status mutant_dict['flux_difference'] = flux_difference = sum((wt_model.solution.x - mutant_fluxes)**2) mutant_dict['the_problem'] = the_problem mutant_dict['combined_model'] = combined_model if print_time: print 'Took %f seconds to assemble solution'%(time()-start_time) del wt_model, mutant_model, quadratic_component, the_solution return(mutant_dict)