Exemple #1
0
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,
        ))
Exemple #2
0
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
Exemple #3
0
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
Exemple #4
0
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))
Exemple #5
0
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))
Exemple #6
0
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]
Exemple #7
0
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))
Exemple #8
0
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)
Exemple #10
0
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
Exemple #11
0
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)
Exemple #12
0
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)