def _add_constraint_for_only_one_captain( problem: pulp.LpProblem, players: List[structures.Player], captain_vars: CaptainVarMap, ) -> None: problem.addConstraint( pulp.LpConstraint( pulp.LpAffineExpression({captain_vars[p]: 1 for p in players}), pulp.LpConstraintLE, 'There can only be one captain selected', 1, ))
def logistics_network( tbde, tbdi, tbfa, dep="需要地", dem="需要", fac="工場", prd="製品", tcs="輸送費", pcs="生産費", lwb="下限", upb="上限", ): """ ロジスティクスネットワーク問題を解く tbde: 需要地 製品 需要 tbdi: 需要地 工場 輸送費 tbfa: 工場 製品 生産費 (下限) (上限) 出力: 解の有無, 輸送表, 生産表 """ facprd = [fac, prd] m = LpProblem() tbpr = tbfa[facprd].sort_values(facprd).drop_duplicates() tbdi2 = pd.merge(tbdi, tbpr, on=fac) tbdi2["VarX"] = addvars(tbdi2.shape[0]) tbfa["VarY"] = addvars(tbfa.shape[0]) tbsm = pd.concat( [tbdi2.groupby(facprd).VarX.sum(), tbfa.groupby(facprd).VarY.sum()], 1) tbde2 = pd.merge(tbde, tbdi2.groupby([dep, prd]).VarX.sum().reset_index()) m += lpDot(tbdi2[tcs], tbdi2.VarX) + lpDot(tbfa[pcs], tbfa.VarY) tbsm.apply(lambda r: m.addConstraint(r.VarX <= r.VarY), 1) tbde2.apply(lambda r: m.addConstraint(r.VarX >= r[dem]), 1) if lwb in tbfa: def flwb(r): r.VarY.lowBound = r[lwb] tbfa[tbfa[lwb] > 0].apply(flwb, 1) if upb in tbfa: def fupb(r): r.VarY.upBound = r[upb] tbfa[tbfa[upb] != np.inf].apply(fupb, 1) m.solve() if m.status == 1: tbdi2["ValX"] = tbdi2.VarX.apply(value) tbfa["ValY"] = tbfa.VarY.apply(value) return m.status == 1, tbdi2, tbfa
def logistics_network(tbde, tbdi, tbfa, dep='需要地', dem='需要', fac='工場', prd='製品', tcs='輸送費', pcs='生産費', lwb='下限', upb='上限'): """ ロジスティクスネットワーク問題を解く tbde: 需要地 製品 需要 tbdi: 需要地 工場 輸送費 tbfa: 工場 製品 生産費 (下限) (上限) 出力: 解の有無, 輸送表, 生産表 """ import numpy as np, pandas as pd from pulp import LpProblem, lpDot, lpSum, value facprd = [fac, prd] m = LpProblem() tbpr = tbfa[facprd].sort_values(facprd).drop_duplicates() tbdi2 = pd.merge(tbdi, tbpr, on=fac) tbdi2['VarX'] = addvars(tbdi2.shape[0]) tbfa['VarY'] = addvars(tbfa.shape[0]) tbsm = pd.concat( [tbdi2.groupby(facprd).VarX.sum(), tbfa.groupby(facprd).VarY.sum()], 1) tbde2 = pd.merge(tbde, tbdi2.groupby([dep, prd]).VarX.sum().reset_index()) m += lpDot(tbdi2[tcs], tbdi2.VarX) + lpDot(tbfa[pcs], tbfa.VarY) tbsm.apply(lambda r: m.addConstraint(r.VarX <= r.VarY), 1) tbde2.apply(lambda r: m.addConstraint(r.VarX >= r[dem]), 1) if lwb in tbfa: def flwb(r): r.VarY.lowBound = r[lwb] tbfa[tbfa[lwb] > 0].apply(flwb, 1) if upb in tbfa: def fupb(r): r.VarY.upBound = r[upb] tbfa[tbfa[upb] != np.inf].apply(fupb, 1) m.solve() if m.status == 1: tbdi2['ValX'] = tbdi2.VarX.apply(value) tbfa['ValY'] = tbfa.VarY.apply(value) return m.status == 1, tbdi2, tbfa
def _add_constraint_for_salary_cap( problem: pulp.LpProblem, players: List[structures.Player], positions: List[structures.Position], squads: List[structures.Squad], player_vars: PlayerVarMap, salary_cap: float, ) -> None: problem.addConstraint( pulp.LpConstraint( pulp.LpAffineExpression({ player_vars[squad][pos][p]: p.price for squad in squads for pos in positions for p in players }), pulp.LpConstraintLE, 'Total cost of all players in team must not exceed the salary cap', salary_cap))
def _add_constraints_for_each_player_only_selected_once( problem: pulp.LpProblem, players: List[structures.Player], positions: List[structures.Position], squads: List[structures.Squad], player_vars: PlayerVarMap, ) -> None: for p in players: problem.addConstraint( pulp.LpConstraint( pulp.LpAffineExpression({ player_vars[squad][pos][p]: 1 for squad in squads for pos in positions }), pulp.LpConstraintLE, '{} can only play for one squad and in one position'.format( p.name), 1))
def binpacking(c, w): """ ビンパッキング問題 列生成法で解く(近似解法) 入力 c: ビンの大きさ w: 荷物の大きさのリスト 出力 ビンごとの荷物の大きさリスト """ from pulp import LpAffineExpression n = len(w) rn = range(n) mkp = LpProblem("knapsack", LpMaximize) # 子問題 mkpva = [addvar(cat=LpBinary) for _ in rn] mkp.addConstraint(lpDot(w, mkpva) <= c) mdl = LpProblem("dual", LpMaximize) # 双対問題 mdlva = [addvar() for _ in rn] for i, v in enumerate(mdlva): v.w = w[i] mdl.setObjective(lpSum(mdlva)) for i in rn: mdl.addConstraint(mdlva[i] <= 1) while True: mdl.solve() mkp.setObjective(lpDot([value(v) for v in mdlva], mkpva)) mkp.solve() if mkp.status != 1 or value(mkp.objective) < 1 + 1e-6: break mdl.addConstraint(lpDot([value(v) for v in mkpva], mdlva) <= 1) nwm = LpProblem("primal", LpMinimize) # 主問題 nm = len(mdl.constraints) rm = range(nm) nwmva = [addvar(cat=LpBinary) for _ in rm] nwm.setObjective(lpSum(nwmva)) dict = {} for v, q in mdl.objective.items(): dict[v] = LpAffineExpression() >= q const = list(mdl.constraints.values()) for i, q in enumerate(const): for v in q: dict[v].addterm(nwmva[i], 1) for q in dict.values(): nwm.addConstraint(q) nwm.solve() if nwm.status != 1: return None w0, result = list(w), [[] for _ in range(len(const))] for i, va in enumerate(nwmva): if value(va) < 0.5: continue for v in const[i]: if v.w in w0: w0.remove(v.w) result[i].append(v.w) return [r for r in result if r]
def _add_constraints_for_squad_position_cap( problem: pulp.LpProblem, positions: List[structures.Position], squads: List[structures.Squad], player_vars: PlayerVarMap, ) -> None: for squad in squads: for pos in positions: cap = squad.position_caps[pos] problem.addConstraint( pulp.LpConstraint( pulp.LpAffineExpression({ var: 1 for var in player_vars[squad][pos].values() }), pulp.LpConstraintEQ, 'Choose exactly {} players in position {} in squad {}'. format(cap, pos.name, squad.name), cap))
def _add_constraints_for_captain_must_be_on_team( problem: pulp.LpProblem, players: List[structures.Player], positions: List[structures.Position], squads: List[structures.Squad], player_vars: PlayerVarMap, captain_vars: CaptainVarMap, ) -> None: for p in players: problem.addConstraint( pulp.LpConstraint( pulp.lpSum([ pulp.LpAffineExpression({ player_vars[squad][pos][p]: 1 for squad in squads for pos in positions }), pulp.LpAffineExpression({captain_vars[p]: -1}) ]), pulp.LpConstraintGE, '{} can only be captain if also in the team'.format(p.name)))
def single_yc_lp_from_t(transition_p, t_atoms, yc_values, alpha, xis=False): """ Create LP: min Sum p_t * I 0 <= xi <= 1/alpha Sum p_t * xi == 1 I = max{y_cvar} return y_cvar[alpha] """ from pulp import LpVariable, LpProblem, value if alpha == 0: return 0. nb_transitions = len(transition_p) Xi = [LpVariable('xi_' + str(i)) for i in range(nb_transitions)] I = [LpVariable('I_' + str(i)) for i in range(nb_transitions)] prob = LpProblem(name='tamar') for xi in Xi: prob.addConstraint(0 <= xi) prob.addConstraint(xi <= 1. / alpha) prob.addConstraint(sum([xi * p for xi, p in zip(Xi, transition_p)]) == 1) for xi, i, yc, atoms in zip(Xi, I, yc_values, t_atoms): last_yc = 0. f_params = [] atom_p = atoms[1:] - atoms[:-1] for ix in range(len(yc)): # linear interpolation as a solution to 'y = kx + q' k = (yc[ix] - last_yc) / atom_p[ix] q = last_yc - k * atoms[ix] prob.addConstraint(i >= k * xi * alpha + q) f_params.append((k, q)) last_yc = yc[ix] # opt criterion prob.setObjective(sum([i * p for i, p in zip(I, transition_p)])) prob.solve() if xis: return value(prob.objective), [value(xi) * alpha for xi in Xi] else: return value(prob.objective)
class OptKnock(object): def __init__(self, model, solver, verbose=False): self.model = deepcopy(model) self.verbose = verbose self.solver = solver # locate the biomass reaction biomass_reactions = [ r for r in self.model.reactions if r.objective_coefficient != 0 ] if len(biomass_reactions) != 1: raise Exception('There should be only one single biomass reaction') self.r_biomass = biomass_reactions[0] self.has_flux_as_variables = False def create_prob(self, sense=LpMaximize): # create the LP self.prob = LpProblem('OptKnock', sense=sense) self.prob.solver = self.solver(msg=self.verbose) if not self.prob.solver.available(): raise Exception("solver not available") def add_primal_variables_and_constraints(self): # create the continuous flux variables (can be positive or negative) self.var_v = {} for r in self.model.reactions: self.var_v[r] = LpVariable("v_%s" % r.id, lowBound=r.lower_bound, upBound=r.upper_bound, cat=LpContinuous) # this flag will be used later to know if to expect the flux # variables to exist self.has_flux_as_variables = True # add the mass-balance constraints to each of the metabolites (S*v = 0) for m in self.model.metabolites: S_times_v = LpAffineExpression([ (self.var_v[r], r.get_coefficient(m)) for r in m.reactions ]) self.prob.addConstraint(S_times_v == 0, 'mass_balance_%s' % m.id) def add_dual_variables_and_constraints(self): # create dual variables associated with stoichiometric constraints self.var_lambda = dict([(m, LpVariable("lambda_%s" % m.id, lowBound=-M, upBound=M, cat=LpContinuous)) for m in self.model.metabolites]) # create dual variables associated with the constraints on the primal fluxes self.var_w_U = dict([(r, LpVariable("w_U_%s" % r.id, lowBound=0, upBound=M, cat=LpContinuous)) for r in self.model.reactions]) self.var_w_L = dict([(r, LpVariable("w_L_%s" % r.id, lowBound=0, upBound=M, cat=LpContinuous)) for r in self.model.reactions]) # add the dual constraints: # S'*lambda + w_U - w_L = c_biomass for r in self.model.reactions: S_times_lambda = LpAffineExpression([ (self.var_lambda[m], coeff) for m, coeff in r._metabolites.iteritems() if coeff != 0 ]) row_sum = S_times_lambda + self.var_w_U[r] - self.var_w_L[r] self.prob.addConstraint(row_sum == r.objective_coefficient, 'dual_%s' % r.id) def prepare_FBA_primal(self, use_glpk=False): """ Run standard FBA (primal) """ self.create_prob(sense=LpMaximize, use_glpk=use_glpk) self.add_primal_variables_and_constraints() self.prob.setObjective(self.var_v[self.r_biomass]) def prepare_FBA_dual(self, use_glpk=False): """ Run shadow FBA (dual) """ self.create_prob(sense=LpMinimize, use_glpk=use_glpk) self.add_dual_variables_and_constraints() w_sum = LpAffineExpression( [(self.var_w_U[r], r.upper_bound) for r in self.model.reactions if r.upper_bound != 0] + [(self.var_w_L[r], -r.lower_bound) for r in self.model.reactions if r.lower_bound != 0]) self.prob.setObjective(w_sum) def get_reaction_by_id(self, reaction_id): if reaction_id not in self.model.reactions: return None reaction_ind = self.model.reactions.index(reaction_id) reaction = self.model.reactions[reaction_ind] return reaction def add_optknock_variables_and_constraints(self): # create the binary variables indicating which reactions knocked out self.var_y = dict([(r, LpVariable("y_%s" % r.id, cat=LpBinary)) for r in self.model.reactions]) # create dual variables associated with the constraints on the primal fluxes self.var_mu = dict([(r, LpVariable("mu_%s" % r.id, cat=LpContinuous)) for r in self.model.reactions]) # equate the objectives of the primal and the dual of the inner problem # to force its optimization: # sum_j mu_j - v_biomass = 0 constr = (lpSum(self.var_mu.values()) - self.var_v[self.r_biomass] == 0) self.prob.addConstraint(constr, 'daul_equals_primal') # add the knockout constraints (when y_j = 0, v_j has to be 0) for r in self.model.reactions: # L_jj * y_j <= v_j self.prob.addConstraint( r.lower_bound * self.var_y[r] <= self.var_v[r], 'v_lower_%s' % r.id) # v_j <= U_jj * y_j self.prob.addConstraint( self.var_v[r] <= r.upper_bound * self.var_y[r], 'v_upper_%s' % r.id) # set the constraints on the auxiliary variables (mu): # mu_j == y_j * (U_jj * w_u_j - L_jj * w_l_j) for r in self.model.reactions: w_sum = LpAffineExpression([(self.var_w_U[r], r.upper_bound), (self.var_w_L[r], -r.lower_bound)]) # mu_j + M*y_j >= 0 self.prob.addConstraint(self.var_mu[r] + M * self.var_y[r] >= 0, 'aux_1_%s' % r.id) # -mu_j + M*y_j >= 0 self.prob.addConstraint(-self.var_mu[r] + M * self.var_y[r] >= 0, 'aux_2_%s' % r.id) # mu_j - (U_jj * w_u_j - L_jj * w_l_j) + M*(1-y_j) >= 0 self.prob.addConstraint( self.var_mu[r] - w_sum + M * (1 - self.var_y[r]) >= 0, 'aux_3_%s' % r.id) # -mu_j + (U_jj * w_u_j - L_jj * w_l_j) + M*(1-y_j) >= 0 self.prob.addConstraint( -self.var_mu[r] + w_sum + M * (1 - self.var_y[r]) >= 0, 'aux_4_%s' % r.id) def add_knockout_bounds(self, ko_candidates=None, num_deletions=5): """ construct the list of KO candidates and add a constraint that only K (num_deletians) of them can have a y_j = 0 """ ko_candidate_sum_y = [] if ko_candidates is None: ko_candidates = [ r for r in self.model.reactions if r != self.r_biomass ] for r in set(self.model.reactions).difference(ko_candidates): # if 'r' is not a candidate constrain it to be 'active' # i.e. y_j == 1 self.prob.addConstraint(self.var_y[r] == 1, 'active_%s' % r.id) # set the upper bound on the number of knockouts (K) # sum (1 - y_j) <= K ko_candidate_sum_y = [(self.var_y[r], 1) for r in ko_candidates] constr = (LpAffineExpression(ko_candidate_sum_y) >= len(ko_candidate_sum_y) - num_deletions) self.prob.addConstraint(constr, 'number_of_deletions') def prepare_optknock(self, target_reaction_id, ko_candidates=None, num_deletions=5, use_glpk=False): # find the target reaction self.r_target = self.get_reaction_by_id(target_reaction_id) self.create_prob(sense=LpMaximize, use_glpk=use_glpk) self.add_primal_variables_and_constraints() self.add_dual_variables_and_constraints() self.add_optknock_variables_and_constraints() # add the objective of maximizing the flux in the target reaction self.prob.setObjective(self.var_v[self.r_target]) self.add_knockout_bounds(ko_candidates, num_deletions) def prepare_optslope(self, target_reaction_id, ko_candidates=None, num_deletions=5, use_glpk=False): # add the objective of maximizing the flux in the target reaction self.r_target = self.get_reaction_by_id(target_reaction_id) # set biomass maximum to 0 self.r_biomass.lower_bound = 0 self.r_biomass.upper_bound = 0 self.r_target.lower_bound = 0 self.r_target.upper_bound = 0 self.create_prob(sense=LpMaximize, use_glpk=use_glpk) self.add_primal_variables_and_constraints() self.add_dual_variables_and_constraints() self.add_optknock_variables_and_constraints() # set the objective as maximizing the shadow price of v_target upper bound self.prob.setObjective(self.var_w_U[self.r_target] - self.var_w_L[self.r_target]) self.add_knockout_bounds(ko_candidates, num_deletions) def write_linear_problem(self, fname): self.prob.writeLP(fname) def solve(self): self.prob.solve() if self.prob.status != LpStatusOptimal: if self.verbose: print("LP was not solved because: " + LpStatus[self.prob.status]) self.solution = Solution(objective_value=None, status=self.prob.status, fluxes=None) else: if self.has_flux_as_variables: x = [self.var_v[r].varValue for r in self.model.reactions] else: x = [] self.solution = Solution( objective_value=self.prob.objective.value(), status=self.prob.status, fluxes=x) return self.solution def get_objective_value(self): if self.solution.status != LpStatusOptimal: return None else: return self.prob.objective.value() def print_primal_results(self, short=True): obj = self.get_objective_value() if obj is None: return print("Objective : %6.3f" % obj) if not short: print("List of reactions : ") for r in self.model.reactions: print("%30s (%4g <= v <= %4g) : v = %6.3f" % \ (r.name, r.lower_bound, r.upper_bound, self.var_v[r].varValue)) def print_dual_results(self, short=True): obj = self.get_objective_value() if obj is None: return print("Objective : %6.3f" % obj) if not short: print("List of reactions : ") for r in self.model.reactions: print("%30s (%4g <= v <= %4g) : w_L = %5.3f, w_U = %5.3f" % \ (r.id, r.lower_bound, r.upper_bound, self.var_w_L[r].varValue, self.var_w_U[r].varValue)) print("List of metabolites : ") for m in self.model.metabolites: print("%30s : lambda = %5.3f, " % \ (m.id, self.var_lambda[m].varValue)) def print_optknock_results(self, short=True): if self.solution.status != LpStatusOptimal: return print("Objective : %6.3f" % self.prob.objective.value()) print("Biomass rate : %6.3f" % self.var_v[self.r_biomass].varValue) print("Sum of mu : %6.3f" % np.sum([mu.varValue for mu in self.var_mu.values()])) print("Knockouts : ") print(' ; '.join([ '"%s" (%s)' % (r.name, r.id) for r, val in self.var_y.iteritems() if val.varValue == 0 ])) if not short: print("List of reactions : ") for r in self.model.reactions: print('%25s (%5s) : %4g <= v=%5g <= %4g ; y = %d ; mu = %g ; w_L = %5g ; w_U = %5g' % \ ('"' + r.name + '"', r.id, r.lower_bound, self.var_v[r].varValue, r.upper_bound, self.var_y[r].varValue, self.var_mu[r].varValue, self.var_w_L[r].varValue, self.var_w_U[r].varValue)) print("List of metabolites : ") for m in self.model.metabolites: print("%30s : lambda = %6.3f" % \ (m.id, self.var_lambda[m].varValue)) def get_optknock_knockouts(self): return ','.join( [r.id for r, val in self.var_y.iteritems() if val.varValue == 0]) def get_optknock_model(self): if self.solution.status != LpStatusOptimal: raise Exception('OptKnock failed, cannot generate a KO model') optknock_model = deepcopy(self.model) knockout_reactions = [ r for r, val in self.var_y.iteritems() if val.varValue == 0 ] for r in knockout_reactions: new_r = optknock_model.reactions[optknock_model.reactions.index( r.id)] new_r.lower_bound = 0 new_r.upper_bound = 0 return optknock_model def solve_FBA(self): self.create_prob(sense=LpMaximize) self.add_primal_variables_and_constraints() self.prob.setObjective(self.var_v[self.r_biomass]) self.solve() max_biomass = self.get_objective_value() return max_biomass def solve_FVA(self, reaction_id): """ Run Flux Variability Analysis on the provided reaction """ self.create_prob(sense=LpMaximize) self.add_primal_variables_and_constraints() self.prob.setObjective(self.var_v[self.r_biomass]) self.solve() max_biomass = self.get_objective_value() if max_biomass is None: raise Exception("Cannot run FVA because the model is infeasible") self.var_v[self.r_biomass].lowBound = max_biomass - 1e-5 r_target = self.get_reaction_by_id(reaction_id) self.prob.setObjective(self.var_v[r_target]) self.prob.sense = LpMaximize self.solve() max_v_target = self.get_objective_value() self.prob.sense = LpMinimize self.solve() min_v_target = self.get_objective_value() return min_v_target, max_v_target def get_PPP_data(self, reaction_id, bm_range=None): """ Run FVA on a gradient of biomass lower bounds and generate the data needed for creating the Phenotype Phase Plane """ self.create_prob(sense=LpMaximize) self.add_primal_variables_and_constraints() self.prob.setObjective(self.var_v[self.r_biomass]) r_target = self.get_reaction_by_id(reaction_id) if r_target is None: return None self.solve() if bm_range is None: max_biomass = self.get_objective_value() if max_biomass is None: return None bm_range = np.linspace(1e-5, max_biomass - 1e-5, 50) self.prob.setObjective(self.var_v[r_target]) data = [] for bm_lb in bm_range: self.var_v[self.r_biomass].lowBound = bm_lb self.prob.sense = LpMaximize self.solve() max_v_target = self.get_objective_value() self.prob.sense = LpMinimize self.solve() min_v_target = self.get_objective_value() data.append((bm_lb, min_v_target, max_v_target)) return np.matrix(data) def get_slope(self, reaction_id, epsilon_bm=0.01): data = self.get_PPP_data(reaction_id, bm_range=[epsilon_bm]) if data is None: return None else: return data[0, 1] / epsilon_bm def model_summary(self, html): import analysis_toolbox analysis_toolbox.model_summary(self.model, self.solution, html) @staticmethod def analyze_kos(carbon_sources, single_kos, target_reaction, knockins="", n_knockouts=2, n_threads=2, carbon_uptake_rate=50, solver='glpk'): """ Args: target_reaction - the reaction for which the coupling to BM yield is made knockins - extra reactions to add to the model max_knockouts - the maximum number of simultaneous knockouts carbon_uptake_rate - in units of mmol C / (gDW*h) """ if solver.lower() == 'gurobi': solver = solvers.GUROBI elif solver.lower() == 'glpk': solver = solvers.GLPK elif solver.lower() == 'scip': solver = solvers.SCIP elif solver.lower() == 'cplex': solver = solvers.CPLEX else: raise ValueError('unknown solver: ' + solver) wt_model = Model.initialize() if knockins is not None: wt_model.knockin_reactions(knockins, 0, 1000) sys.stdout.write("There are %d single knockouts\n" % len(single_kos)) sys.stdout.write("There are %d carbon sources: %s\n" % (len(carbon_sources), ', '.join(carbon_sources))) data = [] kos_and_cs = [(kos, cs) for kos in combinations(single_kos, n_knockouts) for cs in carbon_sources] def calculate_yield_and_slope(params): kos, carbon_source = params sys.stderr.write('KOs = ' + ', '.join(kos) + ': ' + carbon_source + '\n') temp_model = wt_model.clone() if carbon_source == 'electrons': temp_model.knockin_reactions('RED', 0, carbon_uptake_rate * 2) elif carbon_source != '': # find out how many carbon atoms are in the carbon source # and normalize the uptake rate to be in units of mmol # carbon-source / (gDW*h) nC = 0 for cs in carbon_source.split(','): met = wt_model.metabolites[wt_model.metabolites.index( cs + '_c')] nC += met.elements['C'] uptake_rate = carbon_uptake_rate / float(nC) for cs in carbon_source.split(','): temp_model.set_exchange_bounds(cs, lower_bound=-uptake_rate) for ko in kos: if ko != '': temp_model.knockout_reactions(ko) yd = OptKnock(temp_model, solver=solver).solve_FBA() or 0 if (target_reaction is not None) and (yd == 0): slope = 0 else: slope = OptKnock(temp_model, solver=solver).get_slope(target_reaction) return ('|'.join(kos), carbon_source, yd, slope) data = map(calculate_yield_and_slope, kos_and_cs) df = pd.DataFrame( data=list(data), columns=['knockouts', 'carbon source', 'yield', 'slope']) return df
class OptKnock(object): def __init__(self, model, verbose=False): self.model = deepcopy(model) self.verbose = verbose # locate the biomass reaction biomass_reactions = [ r for r in self.model.reactions if r.objective_coefficient != 0 ] if len(biomass_reactions) != 1: raise Exception('There should be only one single biomass reaction') self.r_biomass = biomass_reactions[0] self.has_flux_as_variables = False def create_prob(self, sense=LpMaximize, use_glpk=True): # create the LP self.prob = LpProblem('OptKnock', sense=sense) if use_glpk: self.prob.solver = solvers.GLPK() else: self.prob.solver = solvers.CPLEX(msg=self.verbose) if not self.prob.solver.available(): raise Exception("CPLEX not available") def add_primal_variables_and_constraints(self): # create the continuous flux variables (can be positive or negative) self.var_v = {} for r in self.model.reactions: self.var_v[r] = LpVariable("v_%s" % r.id, lowBound=r.lower_bound, upBound=r.upper_bound, cat=LpContinuous) # this flag will be used later to know if to expect the flux # variables to exist self.has_flux_as_variables = True # add the mass-balance constraints to each of the metabolites (S*v = 0) for m in self.model.metabolites: S_times_v = LpAffineExpression([ (self.var_v[r], r.get_coefficient(m)) for r in m.reactions ]) self.prob.addConstraint(S_times_v == 0, 'mass_balance_%s' % m.id) def add_dual_variables_and_constraints(self): # create dual variables associated with stoichiometric constraints self.var_lambda = dict([(m, LpVariable("lambda_%s" % m.id, lowBound=-M, upBound=M, cat=LpContinuous)) for m in self.model.metabolites]) # create dual variables associated with the constraints on the primal fluxes self.var_w_U = dict([(r, LpVariable("w_U_%s" % r.id, lowBound=0, upBound=M, cat=LpContinuous)) for r in self.model.reactions]) self.var_w_L = dict([(r, LpVariable("w_L_%s" % r.id, lowBound=0, upBound=M, cat=LpContinuous)) for r in self.model.reactions]) # add the dual constraints: # S'*lambda + w_U - w_L = c_biomass for r in self.model.reactions: S_times_lambda = LpAffineExpression([ (self.var_lambda[m], coeff) for m, coeff in r._metabolites.iteritems() if coeff != 0 ]) row_sum = S_times_lambda + self.var_w_U[r] - self.var_w_L[r] self.prob.addConstraint(row_sum == r.objective_coefficient, 'dual_%s' % r.id) def prepare_FBA_primal(self, use_glpk=False): """ Run standard FBA (primal) """ self.create_prob(sense=LpMaximize, use_glpk=use_glpk) self.add_primal_variables_and_constraints() self.prob.setObjective(self.var_v[self.r_biomass]) def prepare_FBA_dual(self, use_glpk=False): """ Run shadow FBA (dual) """ self.create_prob(sense=LpMinimize, use_glpk=use_glpk) self.add_dual_variables_and_constraints() w_sum = LpAffineExpression( [(self.var_w_U[r], r.upper_bound) for r in self.model.reactions if r.upper_bound != 0] + [(self.var_w_L[r], -r.lower_bound) for r in self.model.reactions if r.lower_bound != 0]) self.prob.setObjective(w_sum) def get_reaction_by_id(self, reaction_id): if reaction_id not in self.model.reactions: return None reaction_ind = self.model.reactions.index(reaction_id) reaction = self.model.reactions[reaction_ind] return reaction def add_optknock_variables_and_constraints(self): # create the binary variables indicating which reactions knocked out self.var_y = dict([(r, LpVariable("y_%s" % r.id, cat=LpBinary)) for r in self.model.reactions]) # create dual variables associated with the constraints on the primal fluxes self.var_mu = dict([(r, LpVariable("mu_%s" % r.id, cat=LpContinuous)) for r in self.model.reactions]) # equate the objectives of the primal and the dual of the inner problem # to force its optimization: # sum_j mu_j - v_biomass = 0 constr = (lpSum(self.var_mu.values()) - self.var_v[self.r_biomass] == 0) self.prob.addConstraint(constr, 'daul_equals_primal') # add the knockout constraints (when y_j = 0, v_j has to be 0) for r in self.model.reactions: # L_jj * y_j <= v_j self.prob.addConstraint( r.lower_bound * self.var_y[r] <= self.var_v[r], 'v_lower_%s' % r.id) # v_j <= U_jj * y_j self.prob.addConstraint( self.var_v[r] <= r.upper_bound * self.var_y[r], 'v_upper_%s' % r.id) # set the constraints on the auxiliary variables (mu): # mu_j == y_j * (U_jj * w_u_j - L_jj * w_l_j) for r in self.model.reactions: w_sum = LpAffineExpression([(self.var_w_U[r], r.upper_bound), (self.var_w_L[r], -r.lower_bound)]) # mu_j + M*y_j >= 0 self.prob.addConstraint(self.var_mu[r] + M * self.var_y[r] >= 0, 'aux_1_%s' % r.id) # -mu_j + M*y_j >= 0 self.prob.addConstraint(-self.var_mu[r] + M * self.var_y[r] >= 0, 'aux_2_%s' % r.id) # mu_j - (U_jj * w_u_j - L_jj * w_l_j) + M*(1-y_j) >= 0 self.prob.addConstraint( self.var_mu[r] - w_sum + M * (1 - self.var_y[r]) >= 0, 'aux_3_%s' % r.id) # -mu_j + (U_jj * w_u_j - L_jj * w_l_j) + M*(1-y_j) >= 0 self.prob.addConstraint( -self.var_mu[r] + w_sum + M * (1 - self.var_y[r]) >= 0, 'aux_4_%s' % r.id) def add_knockout_bounds(self, ko_candidates=None, num_deletions=5): """ construct the list of KO candidates and add a constraint that only K (num_deletians) of them can have a y_j = 0 """ ko_candidate_sum_y = [] if ko_candidates is None: ko_candidates = [ r for r in self.model.reactions if r != self.r_biomass ] for r in set(self.model.reactions).difference(ko_candidates): # if 'r' is not a candidate constrain it to be 'active' # i.e. y_j == 1 self.prob.addConstraint(self.var_y[r] == 1, 'active_%s' % r.id) # set the upper bound on the number of knockouts (K) # sum (1 - y_j) <= K ko_candidate_sum_y = [(self.var_y[r], 1) for r in ko_candidates] constr = (LpAffineExpression(ko_candidate_sum_y) >= len(ko_candidate_sum_y) - num_deletions) self.prob.addConstraint(constr, 'number_of_deletions') def prepare_optknock(self, target_reaction_id, ko_candidates=None, num_deletions=5, use_glpk=False): # find the target reaction self.r_target = self.get_reaction_by_id(target_reaction_id) self.create_prob(sense=LpMaximize, use_glpk=use_glpk) self.add_primal_variables_and_constraints() self.add_dual_variables_and_constraints() self.add_optknock_variables_and_constraints() # add the objective of maximizing the flux in the target reaction self.prob.setObjective(self.var_v[self.r_target]) self.add_knockout_bounds(ko_candidates, num_deletions) def prepare_optslope(self, target_reaction_id, ko_candidates=None, num_deletions=5, use_glpk=False): # add the objective of maximizing the flux in the target reaction self.r_target = self.get_reaction_by_id(target_reaction_id) # set biomass maximum to 0 self.r_biomass.lower_bound = 0 self.r_biomass.upper_bound = 0 self.r_target.lower_bound = 0 self.r_target.upper_bound = 0 self.create_prob(sense=LpMaximize, use_glpk=use_glpk) self.add_primal_variables_and_constraints() self.add_dual_variables_and_constraints() self.add_optknock_variables_and_constraints() # set the objective as maximizing the shadow price of v_target upper bound self.prob.setObjective(self.var_w_U[self.r_target] - self.var_w_L[self.r_target]) self.add_knockout_bounds(ko_candidates, num_deletions) def write_linear_problem(self, fname): self.prob.writeLP(fname) def solve(self): self.prob.solve() if self.prob.status != LpStatusOptimal: if self.verbose: print "LP was not solved because: ", LpStatus[self.prob.status] self.solution = Solution(None) else: self.solution = Solution(self.prob.objective.value()) if self.has_flux_as_variables: self.solution.x = [ self.var_v[r].varValue for r in self.model.reactions ] self.solution.status = self.prob.status return self.solution def get_objective_value(self): if self.solution.status != LpStatusOptimal: return None else: return self.prob.objective.value() def print_primal_results(self, short=True): obj = self.get_objective_value() if obj is None: return print "Objective : %6.3f" % obj if not short: print "List of reactions : " for r in self.model.reactions: print "%30s (%4g <= v <= %4g) : v = %6.3f" % \ (r.name, r.lower_bound, r.upper_bound, self.var_v[r].varValue) def print_dual_results(self, short=True): obj = self.get_objective_value() if obj is None: return print "Objective : %6.3f" % obj if not short: print "List of reactions : " for r in self.model.reactions: print "%30s (%4g <= v <= %4g) : w_L = %5.3f, w_U = %5.3f" % \ (r.id, r.lower_bound, r.upper_bound, self.var_w_L[r].varValue, self.var_w_U[r].varValue) print "List of metabolites : " for m in self.model.metabolites: print "%30s : lambda = %5.3f, " % \ (m.id, self.var_lambda[m].varValue) def print_optknock_results(self, short=True): if self.solution.status != LpStatusOptimal: return print "Objective : %6.3f" % self.prob.objective.value() print "Biomass rate : %6.3f" % self.var_v[self.r_biomass].varValue print "Sum of mu : %6.3f" % np.sum( [mu.varValue for mu in self.var_mu.values()]) print "Knockouts : " print ' ; '.join([ '"%s" (%s)' % (r.name, r.id) for r, val in self.var_y.iteritems() if val.varValue == 0 ]) if not short: print "List of reactions : " for r in self.model.reactions: print '%25s (%5s) : %4g <= v=%5g <= %4g ; y = %d ; mu = %g ; w_L = %5g ; w_U = %5g' % \ ('"' + r.name + '"', r.id, r.lower_bound, self.var_v[r].varValue, r.upper_bound, self.var_y[r].varValue, self.var_mu[r].varValue, self.var_w_L[r].varValue, self.var_w_U[r].varValue) print "List of metabolites : " for m in self.model.metabolites: print "%30s : lambda = %6.3f" % \ (m.id, self.var_lambda[m].varValue) def get_optknock_knockouts(self): return ','.join( [r.id for r, val in self.var_y.iteritems() if val.varValue == 0]) def get_optknock_model(self): if self.solution.status != LpStatusOptimal: raise Exception('OptKnock failed, cannot generate a KO model') optknock_model = deepcopy(self.model) knockout_reactions = [ r for r, val in self.var_y.iteritems() if val.varValue == 0 ] for r in knockout_reactions: new_r = optknock_model.reactions[optknock_model.reactions.index( r.id)] new_r.lower_bound = 0 new_r.upper_bound = 0 return optknock_model def solve_FBA(self): self.create_prob(sense=LpMaximize) self.add_primal_variables_and_constraints() self.prob.setObjective(self.var_v[self.r_biomass]) self.solve() max_biomass = self.get_objective_value() return max_biomass def solve_FVA(self, reaction_id): """ Run Flux Variability Analysis on the provided reaction """ self.create_prob(sense=LpMaximize) self.add_primal_variables_and_constraints() self.prob.setObjective(self.var_v[self.r_biomass]) self.solve() max_biomass = self.get_objective_value() if max_biomass is None: raise Exception("Cannot run FVA because the model is infeasible") self.var_v[self.r_biomass].lowBound = max_biomass - 1e-5 r_target = self.get_reaction_by_id(reaction_id) self.prob.setObjective(self.var_v[r_target]) self.prob.sense = LpMaximize self.solve() max_v_target = self.get_objective_value() self.prob.sense = LpMinimize self.solve() min_v_target = self.get_objective_value() return min_v_target, max_v_target def get_PPP_data(self, reaction_id, bm_range=None): """ Run FVA on a gradient of biomass lower bounds and generate the data needed for creating the Phenotype Phase Plane """ self.create_prob(sense=LpMaximize) self.add_primal_variables_and_constraints() self.prob.setObjective(self.var_v[self.r_biomass]) r_target = self.get_reaction_by_id(reaction_id) if r_target is None: return None self.solve() if bm_range is None: max_biomass = self.get_objective_value() if max_biomass is None: return None bm_range = np.linspace(1e-5, max_biomass - 1e-5, 50) self.prob.setObjective(self.var_v[r_target]) data = [] for bm_lb in bm_range: self.var_v[self.r_biomass].lowBound = bm_lb - 1e-3 self.var_v[self.r_biomass].upBound = bm_lb + 1e-3 self.prob.sense = LpMaximize self.solve() max_v_target = self.get_objective_value() self.prob.sense = LpMinimize self.solve() min_v_target = self.get_objective_value() data.append((bm_lb, min_v_target, max_v_target)) return np.matrix(data) def get_slope(self, reaction_id, epsilon_bm=0.01): data = self.get_PPP_data(reaction_id, bm_range=[epsilon_bm]) if data is None: return None else: return data[0, 1] / epsilon_bm def model_summary(self, html): import analysis_toolbox analysis_toolbox.model_summary(self.model, self.solution, html) def draw_svg(self, html): # Parse the SVG file of central metabolism drawer = DrawFlux('data/CentralMetabolism.svg') #drawer = DrawFlux('data/EcoliMetabolism.svg') drawer.ToSVG(self.model, self.solution, html)
class OptKnock(object): def __init__(self, model, verbose=False): self.model = deepcopy(model) self.verbose = verbose # locate the biomass reaction biomass_reactions = [r for r in self.model.reactions if r.objective_coefficient != 0] if len(biomass_reactions) != 1: raise Exception('There should be only one single biomass reaction') self.r_biomass = biomass_reactions[0] self.has_flux_as_variables = False def create_prob(self, sense=LpMaximize, use_glpk=False): # create the LP self.prob = LpProblem('OptKnock', sense=sense) if use_glpk: self.prob.solver = solvers.GLPK() else: self.prob.solver = solvers.CPLEX(msg=self.verbose) if not self.prob.solver.available(): raise Exception("CPLEX not available") def add_primal_variables_and_constraints(self): # create the continuous flux variables (can be positive or negative) self.var_v = {} for r in self.model.reactions: self.var_v[r] = LpVariable("v_%s" % r.id, lowBound=r.lower_bound, upBound=r.upper_bound, cat=LpContinuous) # this flag will be used later to know if to expect the flux # variables to exist self.has_flux_as_variables = True # add the mass-balance constraints to each of the metabolites (S*v = 0) for m in self.model.metabolites: S_times_v = LpAffineExpression([(self.var_v[r], r.get_coefficient(m)) for r in m.reactions]) self.prob.addConstraint(S_times_v == 0, 'mass_balance_%s' % m.id) def add_dual_variables_and_constraints(self): # create dual variables associated with stoichiometric constraints self.var_lambda = dict([(m, LpVariable("lambda_%s" % m.id, lowBound=-M, upBound=M, cat=LpContinuous)) for m in self.model.metabolites]) # create dual variables associated with the constraints on the primal fluxes self.var_w_U = dict([(r, LpVariable("w_U_%s" % r.id, lowBound=0, upBound=M, cat=LpContinuous)) for r in self.model.reactions]) self.var_w_L = dict([(r, LpVariable("w_L_%s" % r.id, lowBound=0, upBound=M, cat=LpContinuous)) for r in self.model.reactions]) # add the dual constraints: # S'*lambda + w_U - w_L = c_biomass for r in self.model.reactions: S_times_lambda = LpAffineExpression([(self.var_lambda[m], coeff) for m, coeff in r._metabolites.iteritems() if coeff != 0]) row_sum = S_times_lambda + self.var_w_U[r] - self.var_w_L[r] self.prob.addConstraint(row_sum == r.objective_coefficient, 'dual_%s' % r.id) def prepare_FBA_primal(self, use_glpk=False): """ Run standard FBA (primal) """ self.create_prob(sense=LpMaximize, use_glpk=use_glpk) self.add_primal_variables_and_constraints() self.prob.setObjective(self.var_v[self.r_biomass]) def prepare_FBA_dual(self, use_glpk=False): """ Run shadow FBA (dual) """ self.create_prob(sense=LpMinimize, use_glpk=use_glpk) self.add_dual_variables_and_constraints() w_sum = LpAffineExpression([(self.var_w_U[r], r.upper_bound) for r in self.model.reactions if r.upper_bound != 0] + [(self.var_w_L[r], -r.lower_bound) for r in self.model.reactions if r.lower_bound != 0]) self.prob.setObjective(w_sum) def get_reaction_by_id(self, reaction_id): if reaction_id not in self.model.reactions: return None reaction_ind = self.model.reactions.index(reaction_id) reaction = self.model.reactions[reaction_ind] return reaction def add_optknock_variables_and_constraints(self): # create the binary variables indicating which reactions knocked out self.var_y = dict([(r, LpVariable("y_%s" % r.id, cat=LpBinary)) for r in self.model.reactions]) # create dual variables associated with the constraints on the primal fluxes self.var_mu = dict([(r, LpVariable("mu_%s" % r.id, cat=LpContinuous)) for r in self.model.reactions]) # equate the objectives of the primal and the dual of the inner problem # to force its optimization: # sum_j mu_j - v_biomass = 0 constr = (lpSum(self.var_mu.values()) - self.var_v[self.r_biomass] == 0) self.prob.addConstraint(constr, 'daul_equals_primal') # add the knockout constraints (when y_j = 0, v_j has to be 0) for r in self.model.reactions: # L_jj * y_j <= v_j self.prob.addConstraint(r.lower_bound * self.var_y[r] <= self.var_v[r], 'v_lower_%s' % r.id) # v_j <= U_jj * y_j self.prob.addConstraint(self.var_v[r] <= r.upper_bound * self.var_y[r], 'v_upper_%s' % r.id) # set the constraints on the auxiliary variables (mu): # mu_j == y_j * (U_jj * w_u_j - L_jj * w_l_j) for r in self.model.reactions: w_sum = LpAffineExpression([(self.var_w_U[r], r.upper_bound), (self.var_w_L[r], -r.lower_bound)]) # mu_j + M*y_j >= 0 self.prob.addConstraint(self.var_mu[r] + M*self.var_y[r] >= 0, 'aux_1_%s' % r.id) # -mu_j + M*y_j >= 0 self.prob.addConstraint(-self.var_mu[r] + M*self.var_y[r] >= 0, 'aux_2_%s' % r.id) # mu_j - (U_jj * w_u_j - L_jj * w_l_j) + M*(1-y_j) >= 0 self.prob.addConstraint(self.var_mu[r] - w_sum + M*(1-self.var_y[r]) >= 0, 'aux_3_%s' % r.id) # -mu_j + (U_jj * w_u_j - L_jj * w_l_j) + M*(1-y_j) >= 0 self.prob.addConstraint(-self.var_mu[r] + w_sum + M*(1-self.var_y[r]) >= 0, 'aux_4_%s' % r.id) def add_knockout_bounds(self, ko_candidates=None, num_deletions=5): """ construct the list of KO candidates and add a constraint that only K (num_deletians) of them can have a y_j = 0 """ ko_candidate_sum_y = [] if ko_candidates is None: ko_candidates = [r for r in self.model.reactions if r != self.r_biomass] for r in set(self.model.reactions).difference(ko_candidates): # if 'r' is not a candidate constrain it to be 'active' # i.e. y_j == 1 self.prob.addConstraint(self.var_y[r] == 1, 'active_%s' % r.id) # set the upper bound on the number of knockouts (K) # sum (1 - y_j) <= K ko_candidate_sum_y = [(self.var_y[r], 1) for r in ko_candidates] constr = (LpAffineExpression(ko_candidate_sum_y) >= len(ko_candidate_sum_y) - num_deletions) self.prob.addConstraint(constr, 'number_of_deletions') def prepare_optknock(self, target_reaction_id, ko_candidates=None, num_deletions=5, use_glpk=False): # find the target reaction self.r_target = self.get_reaction_by_id(target_reaction_id) self.create_prob(sense=LpMaximize, use_glpk=use_glpk) self.add_primal_variables_and_constraints() self.add_dual_variables_and_constraints() self.add_optknock_variables_and_constraints() # add the objective of maximizing the flux in the target reaction self.prob.setObjective(self.var_v[self.r_target]) self.add_knockout_bounds(ko_candidates, num_deletions) def prepare_optslope(self, target_reaction_id, ko_candidates=None, num_deletions=5, use_glpk=False): # add the objective of maximizing the flux in the target reaction self.r_target = self.get_reaction_by_id(target_reaction_id) # set biomass maximum to 0 self.r_biomass.lower_bound = 0 self.r_biomass.upper_bound = 0 self.r_target.lower_bound = 0 self.r_target.upper_bound = 0 self.create_prob(sense=LpMaximize, use_glpk=use_glpk) self.add_primal_variables_and_constraints() self.add_dual_variables_and_constraints() self.add_optknock_variables_and_constraints() # set the objective as maximizing the shadow price of v_target upper bound self.prob.setObjective(self.var_w_U[self.r_target] - self.var_w_L[self.r_target]) self.add_knockout_bounds(ko_candidates, num_deletions) def write_linear_problem(self, fname): self.prob.writeLP(fname) def solve(self): self.prob.solve() if self.prob.status != LpStatusOptimal: if self.verbose: print "LP was not solved because: ", LpStatus[self.prob.status] self.solution = Solution(None) else: self.solution = Solution(self.prob.objective.value()) if self.has_flux_as_variables: self.solution.x = [self.var_v[r].varValue for r in self.model.reactions] self.solution.status = self.prob.status return self.solution def get_objective_value(self): if self.solution.status != LpStatusOptimal: return None else: return self.prob.objective.value() def print_primal_results(self, short=True): obj = self.get_objective_value() if obj is None: return print "Objective : %6.3f" % obj if not short: print "List of reactions : " for r in self.model.reactions: print "%30s (%4g <= v <= %4g) : v = %6.3f" % \ (r.name, r.lower_bound, r.upper_bound, self.var_v[r].varValue) def print_dual_results(self, short=True): obj = self.get_objective_value() if obj is None: return print "Objective : %6.3f" % obj if not short: print "List of reactions : " for r in self.model.reactions: print "%30s (%4g <= v <= %4g) : w_L = %5.3f, w_U = %5.3f" % \ (r.id, r.lower_bound, r.upper_bound, self.var_w_L[r].varValue, self.var_w_U[r].varValue) print "List of metabolites : " for m in self.model.metabolites: print "%30s : lambda = %5.3f, " % \ (m.id, self.var_lambda[m].varValue) def print_optknock_results(self, short=True): if self.solution.status != LpStatusOptimal: return print "Objective : %6.3f" % self.prob.objective.value() print "Biomass rate : %6.3f" % self.var_v[self.r_biomass].varValue print "Sum of mu : %6.3f" % np.sum([mu.varValue for mu in self.var_mu.values()]) print "Knockouts : " print ' ; '.join(['"%s" (%s)' % (r.name, r.id) for r, val in self.var_y.iteritems() if val.varValue == 0]) if not short: print "List of reactions : " for r in self.model.reactions: print '%25s (%5s) : %4g <= v=%5g <= %4g ; y = %d ; mu = %g ; w_L = %5g ; w_U = %5g' % \ ('"' + r.name + '"', r.id, r.lower_bound, self.var_v[r].varValue, r.upper_bound, self.var_y[r].varValue, self.var_mu[r].varValue, self.var_w_L[r].varValue, self.var_w_U[r].varValue) print "List of metabolites : " for m in self.model.metabolites: print "%30s : lambda = %6.3f" % \ (m.id, self.var_lambda[m].varValue) def get_optknock_knockouts(self): return ','.join([r.id for r, val in self.var_y.iteritems() if val.varValue == 0]) def get_optknock_model(self): if self.solution.status != LpStatusOptimal: raise Exception('OptKnock failed, cannot generate a KO model') optknock_model = deepcopy(self.model) knockout_reactions = [r for r, val in self.var_y.iteritems() if val.varValue == 0] for r in knockout_reactions: new_r = optknock_model.reactions[optknock_model.reactions.index(r.id)] new_r.lower_bound = 0 new_r.upper_bound = 0 return optknock_model def solve_FBA(self): self.create_prob(sense=LpMaximize) self.add_primal_variables_and_constraints() self.prob.setObjective(self.var_v[self.r_biomass]) self.solve() max_biomass = self.get_objective_value() return max_biomass def solve_FVA(self, reaction_id): """ Run Flux Variability Analysis on the provided reaction """ self.create_prob(sense=LpMaximize) self.add_primal_variables_and_constraints() self.prob.setObjective(self.var_v[self.r_biomass]) self.solve() max_biomass = self.get_objective_value() if max_biomass is None: raise Exception("Cannot run FVA because the model is infeasible") self.var_v[self.r_biomass].lowBound = max_biomass - 1e-5 r_target = self.get_reaction_by_id(reaction_id) self.prob.setObjective(self.var_v[r_target]) self.prob.sense = LpMaximize self.solve() max_v_target = self.get_objective_value() self.prob.sense = LpMinimize self.solve() min_v_target = self.get_objective_value() return min_v_target, max_v_target def get_PPP_data(self, reaction_id, bm_range=None): """ Run FVA on a gradient of biomass lower bounds and generate the data needed for creating the Phenotype Phase Plane """ self.create_prob(sense=LpMaximize) self.add_primal_variables_and_constraints() self.prob.setObjective(self.var_v[self.r_biomass]) r_target = self.get_reaction_by_id(reaction_id) if r_target is None: return None self.solve() if bm_range is None: max_biomass = self.get_objective_value() if max_biomass is None: return None bm_range = np.linspace(1e-5, max_biomass - 1e-5, 50) self.prob.setObjective(self.var_v[r_target]) data = [] for bm_lb in bm_range: self.var_v[self.r_biomass].lowBound = bm_lb - 1e-3 self.var_v[self.r_biomass].upBound = bm_lb + 1e-3 self.prob.sense = LpMaximize self.solve() max_v_target = self.get_objective_value() self.prob.sense = LpMinimize self.solve() min_v_target = self.get_objective_value() data.append((bm_lb, min_v_target, max_v_target)) return np.matrix(data) def get_slope(self, reaction_id, epsilon_bm=0.01): data = self.get_PPP_data(reaction_id, bm_range=[epsilon_bm]) if data is None: return None else: return data[0, 1] / epsilon_bm def model_summary(self, html): import analysis_toolbox analysis_toolbox.model_summary(self.model, self.solution, html) def draw_svg(self, html): # Parse the SVG file of central metabolism drawer = DrawFlux('data/CentralMetabolism.svg') #drawer = DrawFlux('data/EcoliMetabolism.svg') drawer.ToSVG(self.model, self.solution, html)