def test_pfba(model, all_solvers): """Test pFBA functionality.""" model.solver = all_solvers with model: add_pfba(model) with pytest.raises(ValueError): add_pfba(model) expression = model.objective.expression n_constraints = len(model.constraints) solution = pfba(model) assert solution.status == "optimal" assert solution.fluxes["Biomass_Ecoli_core"] == pytest.approx(0.8739, abs=1e-4, rel=0.0) assert solution.fluxes.abs().sum() == pytest.approx(518.4221, abs=1e-4, rel=0.0) # test changes to model reverted assert expression == model.objective.expression assert len(model.constraints) == n_constraints # needed? # Test desired_objective_value # desired_objective = 0.8 # pfba(model, solver=solver, # desired_objective_value=desired_objective) # abs_x = [abs(i) for i in model.solution.x] # assert model.solution.status == "optimal" # assert abs(model.solution.f - desired_objective) < 0.001 # assert abs(sum(abs_x) - 476.1594) < 0.001 # TODO: parametrize fraction (DRY it up) # Test fraction_of_optimum solution = pfba(model, fraction_of_optimum=0.95) assert solution.status == "optimal" assert solution.fluxes["Biomass_Ecoli_core"] == pytest.approx(0.95 * 0.8739, abs=1e-4, rel=0.0) abs_x = [abs(i) for i in solution.fluxes.values] assert sum(abs_x) == pytest.approx(493.4400, abs=1e-4, rel=0.0) # Infeasible solution model.reactions.ATPM.lower_bound = 500 with warnings.catch_warnings(): warnings.simplefilter("error", UserWarning) with pytest.raises((UserWarning, Infeasible, ValueError)): pfba(model)
def test_pfba(model, all_solvers): """Test pFBA functionality.""" model.solver = all_solvers with model: add_pfba(model) with pytest.raises(ValueError): add_pfba(model) expression = model.objective.expression n_constraints = len(model.constraints) solution = pfba(model) assert solution.status == "optimal" assert solution.fluxes["Biomass_Ecoli_core"] == \ pytest.approx(0.8739, abs=1e-4, rel=0.0) assert solution.fluxes.abs().sum() == \ pytest.approx(518.4221, abs=1e-4, rel=0.0) # test changes to model reverted assert expression == model.objective.expression assert len(model.constraints) == n_constraints # needed? # Test desired_objective_value # desired_objective = 0.8 # pfba(model, solver=solver, # desired_objective_value=desired_objective) # abs_x = [abs(i) for i in model.solution.x] # assert model.solution.status == "optimal" # assert abs(model.solution.f - desired_objective) < 0.001 # assert abs(sum(abs_x) - 476.1594) < 0.001 # TODO: parametrize fraction (DRY it up) # Test fraction_of_optimum solution = pfba(model, fraction_of_optimum=0.95) assert solution.status == "optimal" assert solution.fluxes["Biomass_Ecoli_core"] == pytest.approx( 0.95 * 0.8739, abs=1e-4, rel=0.0) abs_x = [abs(i) for i in solution.fluxes.values] assert sum(abs_x) == pytest.approx(493.4400, abs=1e-4, rel=0.0) # Infeasible solution model.reactions.ATPM.lower_bound = 500 with warnings.catch_warnings(): warnings.simplefilter("error", UserWarning) with pytest.raises((UserWarning, Infeasible, ValueError)): pfba(model)
def moma(model, solution=None, linear=True): with model: add_moma(model=model, solution=solution, linear=lenear) solution = model.oprimize() return solutiondef, optimize_minimal_flux(*args, **kwargs) warn("optimize_minimal_flux has been renamed to pfba", DeprecationWarning) return pfba(*args, **kwargs) """
def test_metabolite_summary_to_frame_previous_solution(model, opt_solver, met): """Test metabolite summary.to_frame() of previous solution.""" model.solver = opt_solver solution = pfba(model) expected_percent = [100.0, 88.4, 11.6] out_df = model.metabolites.get_by_id(met).summary(solution).to_frame() assert out_df['PERCENT'].round(1).tolist() == expected_percent
def test_single_point_space(model): """Test the reduction of the sampling space to one point.""" pfba_sol = pfba(model) pfba_const = model.problem.Constraint( sum(model.variables), ub=pfba_sol.objective_value) model.add_cons_vars(pfba_const) model.reactions.Biomass_Ecoli_core.lower_bound = \ pfba_sol.fluxes.Biomass_Ecoli_core with pytest.raises(ValueError): s = sample(model, 1)
def test_metabolite_summary_to_table_previous_solution(model, opt_solver, met): """Test metabolite summary._to_table() of previous solution.""" model.solver = opt_solver solution = pfba(model) expected_entry = [ 'PRODUCING CYTBD 100 43.6 2.0 h_c + 0.5 o2_c + ' 'q8h2_c --> h2o_c + 2.0 h_' ] with captured_output() as (out, _): print(model.metabolites.get_by_id(met).summary(solution)) check_in_line(out.getvalue(), expected_entry)
def get_pfba_fluxes(cobra_model): convert_to_irreversible(cobra_model) pfba_sol = pfba(cobra_model, solver='cglpk', already_irreversible=True) x_dict = dict(pfba_sol.x_dict.items()) print("FBA JSON: %s, %f" % (pfba_sol.status, x_dict[BM_RXN])) # revert the solution to a reversible one: reverse_reactions = [x for x in x_dict.keys() if x.endswith('_reverse')] for rxn_id in reverse_reactions: fwd_id = rxn_id.replace('_reverse', '') x_dict[fwd_id] -= x_dict[rxn_id] x_dict.pop(rxn_id) return x_dict
def fbaSol(m): if type(m) == cmo.DynamicModel: cm = m.cbModel else: cm = m solution = parsimonious.pfba(cm) sdf = pandas.DataFrame(solution.x_dict) sdf.columns = ['flux'] sdf['rxn'] = sdf.index print('FBA solution') print(cm.summary()) print('FVA=1. solution') print(cm.summary(fva=1.)) return
def add_moma(model, solution=None, linear=True): r"""Add constraints and objective representing for MOMA. This adds variables and constraints for the minimization of metabolic adjustment (MOMA) to the model. Parameters ---------- model : cobra.Model The model to add MOMA constraints and objective to. solution : cobra.Solution, optional A previous solution to use as a reference. If no solution is given, one will be computed using pFBA. linear : bool, optional Whether to use the linear MOMA formulation or not (default True). Notes ----- In the original MOMA [1]_ specification one looks for the flux distribution of the deletion (v^d) closest to the fluxes without the deletion (v). In math this means: minimize \sum_i (v^d_i - v_i)^2 s.t. Sv^d = 0 lb_i <= v^d_i <= ub_i Here, we use a variable transformation v^t := v^d_i - v_i. Substituting and using the fact that Sv = 0 gives: minimize \sum_i (v^t_i)^2 s.t. Sv^d = 0 v^t = v^d_i - v_i lb_i <= v^d_i <= ub_i So basically we just re-center the flux space at the old solution and then find the flux distribution closest to the new zero (center). This is the same strategy as used in cameo. In the case of linear MOMA [2]_, we instead minimize \sum_i abs(v^t_i). The linear MOMA is typically significantly faster. Also quadratic MOMA tends to give flux distributions in which all fluxes deviate from the reference fluxes a little bit whereas linear MOMA tends to give flux distributions where the majority of fluxes are the same reference with few fluxes deviating a lot (typical effect of L2 norm vs L1 norm). The former objective function is saved in the optlang solver interface as ``"moma_old_objective"`` and this can be used to immediately extract the value of the former objective after MOMA optimization. See Also -------- pfba : parsimonious FBA References ---------- .. [1] Segrè, Daniel, Dennis Vitkup, and George M. Church. “Analysis of Optimality in Natural and Perturbed Metabolic Networks.” Proceedings of the National Academy of Sciences 99, no. 23 (November 12, 2002): 15112. https://doi.org/10.1073/pnas.232349399. .. [2] Becker, Scott A, Adam M Feist, Monica L Mo, Gregory Hannum, Bernhard Ø Palsson, and Markus J Herrgard. “Quantitative Prediction of Cellular Metabolism with Constraint-Based Models: The COBRA Toolbox.” Nature Protocols 2 (March 29, 2007): 727. """ if 'moma_old_objective' in model.solver.variables: raise ValueError('model is already adjusted for MOMA') # Fall back to default QP solver if current one has no QP capability if not linear: model.solver = sutil.choose_solver(model, qp=True) if solution is None: solution = pfba(model) prob = model.problem v = prob.Variable("moma_old_objective") c = prob.Constraint(model.solver.objective.expression - v, lb=0.0, ub=0.0, name="moma_old_objective_constraint") to_add = [v, c] model.objective = prob.Objective(Zero, direction="min", sloppy=True) obj_vars = [] for r in model.reactions: flux = solution.fluxes[r.id] if linear: components = sutil.add_absolute_expression(model, r.flux_expression, name="moma_dist_" + r.id, difference=flux, add=False) to_add.extend(components) obj_vars.append(components.variable) else: dist = prob.Variable("moma_dist_" + r.id) const = prob.Constraint(r.flux_expression - dist, lb=flux, ub=flux, name="moma_constraint_" + r.id) to_add.extend([dist, const]) obj_vars.append(dist**2) model.add_cons_vars(to_add) if linear: model.objective.set_linear_coefficients({v: 1.0 for v in obj_vars}) else: model.objective = prob.Objective(add(obj_vars), direction="min", sloppy=True)
def test_metabolite_summary_previous_solution(model, opt_solver, met): """Test metabolite summary of previous solution.""" model.solver = opt_solver solution = pfba(model) model.metabolites.get_by_id(met).summary(solution)
def add_room(model, solution=None, linear=False, delta=0.03, epsilon=1E-03): r""" Add constraints and objective for ROOM. This function adds variables and constraints for applying regulatory on/off minimization (ROOM) to the model. Parameters ---------- model : cobra.Model The model to add ROOM constraints and objective to. solution : cobra.Solution, optional A previous solution to use as a reference. If no solution is given, one will be computed using pFBA. linear : bool, optional Whether to use the linear ROOM formulation or not (default False). delta: float, optional The relative tolerance range which is additive in nature (default 0.03). epsilon: float, optional The absolute range of tolerance which is multiplicative (default 0.001). Notes ----- The formulation used here is the same as stated in the original paper [1]_. The mathematical expression is given below: minimize \sum_{i=1}^m y^i s.t. Sv = 0 v_min <= v <= v_max v_j = 0 j ∈ A for 1 <= i <= m v_i - y_i(v_{max,i} - w_i^u) <= w_i^u (1) v_i - y_i(v_{min,i} - w_i^l) <= w_i^l (2) y_i ∈ {0,1} (3) w_i^u = w_i + \delta|w_i| + \epsilon w_i^l = w_i - \delta|w_i| - \epsilon So, for the linear version of the ROOM , constraint (3) is relaxed to 0 <= y_i <= 1. See Also -------- pfba : parsimonious FBA References ---------- .. [1] Tomer Shlomi, Omer Berkman and Eytan Ruppin, "Regulatory on/off minimization of metabolic flux changes after genetic perturbations", PNAS 2005 102 (21) 7695-7700; doi:10.1073/pnas.0406346102 """ if 'room_old_objective' in model.solver.variables: raise ValueError('model is already adjusted for ROOM') # optimizes if no reference solution is provided if solution is None: solution = pfba(model) prob = model.problem variable = prob.Variable("room_old_objective", ub=solution.objective_value) constraint = prob.Constraint( model.solver.objective.expression - variable, ub=0.0, lb=0.0, name="room_old_objective_constraint" ) model.objective = prob.Objective(Zero, direction="min", sloppy=True) vars_and_cons = [variable, constraint] obj_vars = [] for rxn in model.reactions: flux = solution.fluxes[rxn.id] if linear: y = prob.Variable("y_" + rxn.id, lb=0, ub=1) delta = epsilon = 0.0 else: y = prob.Variable("y_" + rxn.id, type="binary") # upper constraint w_u = flux + (delta * abs(flux)) + epsilon upper_const = prob.Constraint( rxn.flux_expression - y * (rxn.upper_bound - w_u), ub=w_u, name="room_constraint_upper_" + rxn.id) # lower constraint w_l = flux - (delta * abs(flux)) - epsilon lower_const = prob.Constraint( rxn.flux_expression - y * (rxn.lower_bound - w_l), lb=w_l, name="room_constraint_lower_" + rxn.id) vars_and_cons.extend([y, upper_const, lower_const]) obj_vars.append(y) model.add_cons_vars(vars_and_cons) model.objective.set_linear_coefficients({v: 1.0 for v in obj_vars})
def add_room(model, solution=None, linear=False, delta=0.03, epsilon=1E-03): r""" Add constraints and objective for ROOM. This function adds variables and constraints for applying regulatory on/off minimization (ROOM) to the model. Parameters ---------- model : cobra.Model The model to add ROOM constraints and objective to. solution : cobra.Solution, optional A previous solution to use as a reference. If no solution is given, one will be computed using pFBA. linear : bool, optional Whether to use the linear ROOM formulation or not (default False). delta: float, optional The relative tolerance range which is additive in nature (default 0.03). epsilon: float, optional The absolute range of tolerance which is multiplicative (default 0.001). Notes ----- The formulation used here is the same as stated in the original paper [1]_. The mathematical expression is given below: minimize \sum_{i=1}^m y^i s.t. Sv = 0 v_min <= v <= v_max v_j = 0 j ∈ A for 1 <= i <= m v_i - y_i(v_{max,i} - w_i^u) <= w_i^u (1) v_i - y_i(v_{min,i} - w_i^l) <= w_i^l (2) y_i ∈ {0,1} (3) w_i^u = w_i + \delta|w_i| + \epsilon w_i^l = w_i - \delta|w_i| - \epsilon So, for the linear version of the ROOM , constraint (3) is relaxed to 0 <= y_i <= 1. See Also -------- pfba : parsimonious FBA References ---------- .. [1] Tomer Shlomi, Omer Berkman and Eytan Ruppin, "Regulatory on/off minimization of metabolic flux changes after genetic perturbations", PNAS 2005 102 (21) 7695-7700; doi:10.1073/pnas.0406346102 """ if 'room_old_objective' in model.solver.variables: raise ValueError('model is already adjusted for ROOM') # optimizes if no reference solution is provided if solution is None: solution = pfba(model) prob = model.problem variable = prob.Variable("room_old_objective", ub=solution.objective_value) constraint = prob.Constraint(model.solver.objective.expression - variable, ub=0.0, lb=0.0, name="room_old_objective_constraint") model.objective = prob.Objective(Zero, direction="min", sloppy=True) vars_and_cons = [variable, constraint] obj_vars = [] for rxn in model.reactions: flux = solution.fluxes[rxn.id] if linear: y = prob.Variable("y_" + rxn.id, lb=0, ub=1) delta = epsilon = 0.0 else: y = prob.Variable("y_" + rxn.id, type="binary") # upper constraint w_u = flux + (delta * abs(flux)) + epsilon upper_const = prob.Constraint(rxn.flux_expression - y * (rxn.upper_bound - w_u), ub=w_u, name="room_constraint_upper_" + rxn.id) # lower constraint w_l = flux - (delta * abs(flux)) - epsilon lower_const = prob.Constraint(rxn.flux_expression - y * (rxn.lower_bound - w_l), lb=w_l, name="room_constraint_lower_" + rxn.id) vars_and_cons.extend([y, upper_const, lower_const]) obj_vars.append(y) model.add_cons_vars(vars_and_cons) model.objective.set_linear_coefficients({v: 1.0 for v in obj_vars})
def test_metabolite_summary_previous_solution( model, opt_solver, met): """Test metabolite summary of previous solution.""" model.solver = opt_solver solution = pfba(model) model.metabolites.get_by_id(met).summary(solution)
def add_moma(model, solution=None, linear=True): r"""Add constraints and objective representing for MOMA. This adds variables and constraints for the minimization of metabolic adjustment (MOMA) to the model. Parameters ---------- model : cobra.Model The model to add MOMA constraints and objective to. solution : cobra.Solution, optional A previous solution to use as a reference. If no solution is given, one will be computed using pFBA. linear : bool, optional Whether to use the linear MOMA formulation or not (default True). Notes ----- In the original MOMA [1]_ specification one looks for the flux distribution of the deletion (v^d) closest to the fluxes without the deletion (v). In math this means: minimize \sum_i (v^d_i - v_i)^2 s.t. Sv^d = 0 lb_i <= v^d_i <= ub_i Here, we use a variable transformation v^t := v^d_i - v_i. Substituting and using the fact that Sv = 0 gives: minimize \sum_i (v^t_i)^2 s.t. Sv^d = 0 v^t = v^d_i - v_i lb_i <= v^d_i <= ub_i So basically we just re-center the flux space at the old solution and then find the flux distribution closest to the new zero (center). This is the same strategy as used in cameo. In the case of linear MOMA [2]_, we instead minimize \sum_i abs(v^t_i). The linear MOMA is typically significantly faster. Also quadratic MOMA tends to give flux distributions in which all fluxes deviate from the reference fluxes a little bit whereas linear MOMA tends to give flux distributions where the majority of fluxes are the same reference with few fluxes deviating a lot (typical effect of L2 norm vs L1 norm). The former objective function is saved in the optlang solver interface as ``"moma_old_objective"`` and this can be used to immediately extract the value of the former objective after MOMA optimization. See Also -------- pfba : parsimonious FBA References ---------- .. [1] Segrè, Daniel, Dennis Vitkup, and George M. Church. “Analysis of Optimality in Natural and Perturbed Metabolic Networks.” Proceedings of the National Academy of Sciences 99, no. 23 (November 12, 2002): 15112. https://doi.org/10.1073/pnas.232349399. .. [2] Becker, Scott A, Adam M Feist, Monica L Mo, Gregory Hannum, Bernhard Ø Palsson, and Markus J Herrgard. “Quantitative Prediction of Cellular Metabolism with Constraint-Based Models: The COBRA Toolbox.” Nature Protocols 2 (March 29, 2007): 727. """ if 'moma_old_objective' in model.solver.variables: raise ValueError('model is already adjusted for MOMA') # Fall back to default QP solver if current one has no QP capability if not linear: model.solver = sutil.choose_solver(model, qp=True) if solution is None: solution = pfba(model) prob = model.problem v = prob.Variable("moma_old_objective") c = prob.Constraint(model.solver.objective.expression - v, lb=0.0, ub=0.0, name="moma_old_objective_constraint") to_add = [v, c] model.objective = prob.Objective(Zero, direction="min", sloppy=True) obj_vars = [] for r in model.reactions: flux = solution.fluxes[r.id] if linear: components = sutil.add_absolute_expression( model, r.flux_expression, name="moma_dist_" + r.id, difference=flux, add=False) to_add.extend(components) obj_vars.append(components.variable) else: dist = prob.Variable("moma_dist_" + r.id) const = prob.Constraint(r.flux_expression - dist, lb=flux, ub=flux, name="moma_constraint_" + r.id) to_add.extend([dist, const]) obj_vars.append(dist ** 2) model.add_cons_vars(to_add) if linear: model.objective.set_linear_coefficients({v: 1.0 for v in obj_vars}) else: model.objective = prob.Objective( add(obj_vars), direction="min", sloppy=True)
def main(): ''' Reproduce results from ** Enjalbert et al 2015 With Single EC model ''' args = options() verbose = args.verbose bmname = 'BIOMASS_Ecoli_core_w_GAM' m = cobra.io.read_sbml_model(args.model) exrxnname = ['EX_glc__D_e', 'EX_ac_e', 'EX_o2_e'] exrxn = [] otname = args.pathout+'tab_v.csv' if args.constraintAcSec: m.reactions.get_by_id('EX_ac_e').upper_bound = 3 otname = args.pathout+'maxAcOut_tab_v.csv' for n in exrxnname: exrxn.append(m.reactions.get_by_id(n)) vsp = np.linspace(-10, 0, 11) for r in exrxn: r.lower_bound = vsp[0] msol = parsimonious.pfba(m) df_tmp = msol.x_dict[exrxnname+[bmname]] idxlab = '_'.join(3*['%.1f' % vsp[0]]) df = pandas.DataFrame(data=df_tmp).T df.index = [idxlab] # with glucose for r in exrxn: for v in vsp: r.lower_bound = v msol = parsimonious.pfba(m) df_tmp = msol.x_dict[exrxnname+[bmname]] lab = [] for rl in exrxn: lab.append('%.1f' % rl.lower_bound) df_tmp.name = '_'.join(lab) df = df.append(df_tmp) r.lower_bound = vsp[0] # without glucose exrxn[0].lower_bound = 0 for r in exrxn[1:]: for v in vsp: r.lower_bound = v try: msol = parsimonious.pfba(m) df_tmp = msol.x_dict[exrxnname+[bmname]] except: msol = None df_tmp = pandas.Series(dict(zip(exrxnname+[bmname], 4*[np.nan]))) lab = [] for rl in exrxn: lab.append('%.1f' % rl.lower_bound) df_tmp.name = '_'.join(lab) df = df.append(df_tmp) r.lower_bound = vsp[0] df.to_csv(otname) return
def MinFluxSolve(model, PrintStatus=True, PrimObjVal=True, norm="linear", weighting='uniform', ExcReacs=[], adjusted=False, tol_step=1e-9, max_tol=1e-8, DisplayMsg=True, cobra=True, subopt=1.0): """ norm = "linear" | "euclidean" weighting = "uniform" | "random" """ # """Temporary fix, need to change the structure of saving solution objects""" if norm == "linear" and weighting == "uniform" and (not ExcReacs) and ( not adjusted) and cobra: # """Temporary fix, need to change the structure of saving solution objects""" # from cobra.flux_analysis.parsimonious import pfba sol = None try: sol = pfba(model, fraction_of_optimum=subopt) except Infeasible: if DisplayMsg: print("no solution") model.UpdateSolution(None) return model.UpdateSolution(sol) if DisplayMsg: try: print(sol.status) except AttributeError: print("no solution") return # return sol.fluxes # # RemoveReverse(model) model.Solve(PrintStatus=PrintStatus) if model.Optimal(): state = model.GetState() objective = model.GetObjective() objval = model.GetObjVal() objbounds = (objval - abs(1 - subopt) * objval, objval + abs(1 - subopt) * objval) #SetValAsConstraint(model,name="MinFlux_Objective", objval=objval,objective=objective) #model.SetSumReacsConstraint(reacsdic=objective, bounds=objval,name="MinFlux_Objective") model.SetSumReacsConstraint(reacsdic=objective, bounds=objbounds, name="MinFlux_Objective") if norm == "linear": #modify.convert_to_irreversible(model) model.SplitRev() SetLinearMinFluxObjective(model=model, weighting=weighting, ExcReacs=ExcReacs) # ExcReacs = model.GetReactionNames(ExcReacs) # for reaction in model.reactions: # if not (reaction.id.endswith("_sum_reaction") or # reaction.id.endswith("_metbounds") or # (reaction.id.split('_reverse')[0] in ExcReacs)): # if weighting == 'uniform': # reaction.objective_coefficient = 1 # elif weighting == 'random': # reaction.objective_coefficient = random.random() # else: # #print "wrong weighting" # raise NameError(weighting) model.SetObjDirec("Min") model.Solve(PrintStatus=DisplayMsg) if adjusted: Tolerance = 0 while (model.Optimal() == False and Tolerance < max_tol): Tolerance = Tolerance + tol_step model.DelObjAsConstraint("MinFlux_Objective") bounds = (objval - Tolerance, objval + Tolerance) if DisplayMsg: print('Objective value = ' + str(objval)) print("Modified 'MinFlux_Objective' bounds = " + str(bounds)) model.SetSumReacsConstraint(reacsdic=objective, bounds=bounds, name="MinFlux_Objective") model.SetObjDirec("Min") model.Solve(PrintStatus=DisplayMsg) #modify.revert_to_reversible(model) model.MergeRev(True) #print(model.GetConstraints()) if norm == "euclidean": num_reacs = len(model.reactions) model.quadratic_component = scipy.sparse.identity( num_reacs).todok() model.SetObjDirec("Min") model.Solve(PrintStatus=DisplayMsg) #"""This whole section used to be run after, either linear or euclidean norms, but tabbed to accomodate temporary change described above""" model.DelObjAsConstraint("MinFlux_Objective") model.SetState(state, IncSol=False) if PrimObjVal: try: model.solution.objective_value = state[ "solution"].objective_value except AttributeError: pass #return model.GetSol() else: print("not feasible")