Esempio n. 1
0
def fix_pfba_as_constraint(model, multiplier=1, fraction_of_optimum=1):
    """Fix the pFBA optimum as a constraint

    Useful when setting other objectives, like the maximum flux through given reaction may be more realistic if all
    other fluxes are not allowed to reach their full upper bounds, but collectively constrained to max sum.

    Parameters
    ----------
    model : cobra.Model
        The model to add the pfba constraint to
    multiplier : float
        The multiplier of the minimal sum of all reaction fluxes to use as the constraint.
    fraction_of_optimum : float
        The fraction of the objective value's optimum to use as constraint when getting the pFBA objective's minimum
    """

    fix_constraint_name = '_fixed_pfba_constraint'
    if fix_constraint_name in model.solver.constraints:
        model.solver.remove(fix_constraint_name)
    with model:
        add_pfba(model, fraction_of_optimum=fraction_of_optimum)
        pfba_objective_value = model.slim_optimize(
            error_value=None) * multiplier
        constraint = model.solver.interface.Constraint(
            model.objective.expression,
            name=fix_constraint_name,
            ub=pfba_objective_value)
    model.add_cons_vars(constraint, sloppy=True)
Esempio n. 2
0
def fix_pfba_as_constraint(model, multiplier=1, fraction_of_optimum=1):
    """Fix the pFBA optimum as a constraint

    Useful when setting other objectives, like the maximum flux through given reaction may be more realistic if all
    other fluxes are not allowed to reach their full upper bounds, but collectively constrained to max sum.

    Parameters
    ----------
    model : cobra.Model
        The model to add the pfba constraint to
    multiplier : float
        The multiplier of the minimal sum of all reaction fluxes to use as the constraint.
    fraction_of_optimum : float
        The fraction of the objective value's optimum to use as constraint when getting the pFBA objective's minimum
    """

    fix_constraint_name = '_fixed_pfba_constraint'
    if fix_constraint_name in model.solver.constraints:
        model.solver.remove(fix_constraint_name)
    with model:
        add_pfba(model, fraction_of_optimum=fraction_of_optimum)
        pfba_objective_value = model.slim_optimize(error_value=None) * multiplier
        constraint = model.solver.interface.Constraint(model.objective.expression,
                                                       name=fix_constraint_name,
                                                       ub=pfba_objective_value)
    model.add_cons_vars(constraint, sloppy=True)
Esempio n. 3
0
 def test_add_remove_pfba(self, core_model):
     with core_model:
         add_pfba(core_model)
         assert '_pfba_objective' == core_model.objective.name
     assert '_pfba_objective' != core_model.solver.constraints
     with core_model:
         fix_pfba_as_constraint(core_model)
         assert '_fixed_pfba_constraint' in core_model.solver.constraints
     assert '_fixed_pfba_constraint' not in core_model.solver.constraints
Esempio n. 4
0
 def test_add_remove_pfba(self, core_model):
     with core_model:
         add_pfba(core_model)
         assert '_pfba_objective' == core_model.objective.name
     assert '_pfba_objective' != core_model.solver.constraints
     with core_model:
         fix_pfba_as_constraint(core_model)
         assert '_fixed_pfba_constraint' in core_model.solver.constraints
     assert '_fixed_pfba_constraint' not in core_model.solver.constraints
Esempio n. 5
0
    def test_pfba(self, model, solver):
        with model:
            add_pfba(model)
            with pytest.raises(ValueError):
                add_pfba(model)

        if solver in optlang_solvers:
            model.solver = solver
        expression = model.objective.expression
        n_constraints = len(model.constraints)
        solution = optimize_minimal_flux(model, solver=solver)
        assert solution.status == "optimal"
        assert numpy.isclose(solution.x_dict["Biomass_Ecoli_core"],
                             0.8739,
                             atol=1e-4,
                             rtol=0.0)
        abs_x = [abs(i) for i in solution.x]
        assert numpy.isclose(sum(abs_x), 518.4221, atol=1e-4, rtol=0.0)
        # test changes to model reverted
        assert expression == model.objective.expression
        assert len(model.constraints) == n_constraints

        # needed?
        # Test desired_objective_value
        # desired_objective = 0.8
        # optimize_minimal_flux(model, solver=solver,
        #                       desired_objective_value=desired_objective)
        # abs_x = [abs(i) for i in model.solution.x]
        # assert model.solution.status == "optimal"
        # assert abs(model.solution.f - desired_objective) < 0.001
        # assert abs(sum(abs_x) - 476.1594) < 0.001

        # TODO: parametrize fraction (DRY it up)
        # Test fraction_of_optimum
        solution = optimize_minimal_flux(model,
                                         solver=solver,
                                         fraction_of_optimum=0.95)
        assert solution.status == "optimal"
        assert numpy.isclose(solution.x_dict["Biomass_Ecoli_core"],
                             0.95 * 0.8739,
                             atol=1e-4,
                             rtol=0.0)
        abs_x = [abs(i) for i in solution.x]
        assert numpy.isclose(sum(abs_x), 493.4400, atol=1e-4, rtol=0.0)

        # Infeasible solution
        model.reactions.ATPM.lower_bound = 500
        with warnings.catch_warnings():
            warnings.simplefilter("error", UserWarning)
            with pytest.raises((UserWarning, ValueError)):
                optimize_minimal_flux(model, solver=solver)
Esempio n. 6
0
def test_pfba(model, all_solvers):
    """Test pFBA functionality."""
    model.solver = all_solvers
    with model:
        add_pfba(model)
        with pytest.raises(ValueError):
            add_pfba(model)

    expression = model.objective.expression
    n_constraints = len(model.constraints)
    solution = pfba(model)
    assert solution.status == "optimal"
    assert solution.fluxes["Biomass_Ecoli_core"] == pytest.approx(0.8739,
                                                                  abs=1e-4,
                                                                  rel=0.0)
    assert solution.fluxes.abs().sum() == pytest.approx(518.4221,
                                                        abs=1e-4,
                                                        rel=0.0)

    # test changes to model reverted
    assert expression == model.objective.expression
    assert len(model.constraints) == n_constraints

    # needed?
    # Test desired_objective_value
    # desired_objective = 0.8
    # pfba(model, solver=solver,
    #                       desired_objective_value=desired_objective)
    # abs_x = [abs(i) for i in model.solution.x]
    # assert model.solution.status == "optimal"
    # assert abs(model.solution.f - desired_objective) < 0.001
    # assert abs(sum(abs_x) - 476.1594) < 0.001

    # TODO: parametrize fraction (DRY it up)
    # Test fraction_of_optimum
    solution = pfba(model, fraction_of_optimum=0.95)
    assert solution.status == "optimal"
    assert solution.fluxes["Biomass_Ecoli_core"] == pytest.approx(0.95 *
                                                                  0.8739,
                                                                  abs=1e-4,
                                                                  rel=0.0)
    abs_x = [abs(i) for i in solution.fluxes.values]
    assert sum(abs_x) == pytest.approx(493.4400, abs=1e-4, rel=0.0)

    # Infeasible solution
    model.reactions.ATPM.lower_bound = 500
    with warnings.catch_warnings():
        warnings.simplefilter("error", UserWarning)
        with pytest.raises((UserWarning, Infeasible, ValueError)):
            pfba(model)
Esempio n. 7
0
def test_pfba(model, all_solvers):
    """Test pFBA functionality."""
    model.solver = all_solvers
    with model:
        add_pfba(model)
        with pytest.raises(ValueError):
            add_pfba(model)

    expression = model.objective.expression
    n_constraints = len(model.constraints)
    solution = pfba(model)
    assert solution.status == "optimal"
    assert solution.fluxes["Biomass_Ecoli_core"] == \
        pytest.approx(0.8739, abs=1e-4, rel=0.0)
    assert solution.fluxes.abs().sum() == \
        pytest.approx(518.4221, abs=1e-4, rel=0.0)

    # test changes to model reverted
    assert expression == model.objective.expression
    assert len(model.constraints) == n_constraints

    # needed?
    # Test desired_objective_value
    # desired_objective = 0.8
    # pfba(model, solver=solver,
    #                       desired_objective_value=desired_objective)
    # abs_x = [abs(i) for i in model.solution.x]
    # assert model.solution.status == "optimal"
    # assert abs(model.solution.f - desired_objective) < 0.001
    # assert abs(sum(abs_x) - 476.1594) < 0.001

    # TODO: parametrize fraction (DRY it up)
    # Test fraction_of_optimum
    solution = pfba(model, fraction_of_optimum=0.95)
    assert solution.status == "optimal"
    assert solution.fluxes["Biomass_Ecoli_core"] == pytest.approx(
        0.95 * 0.8739, abs=1e-4, rel=0.0)
    abs_x = [abs(i) for i in solution.fluxes.values]
    assert sum(abs_x) == pytest.approx(493.4400, abs=1e-4, rel=0.0)

    # Infeasible solution
    model.reactions.ATPM.lower_bound = 500
    with warnings.catch_warnings():
        warnings.simplefilter("error", UserWarning)
        with pytest.raises((UserWarning, Infeasible, ValueError)):
            pfba(model)
    def test_pfba(self, model, solver):
        model.solver = solver
        with model:
            add_pfba(model)
            with pytest.raises(ValueError):
                add_pfba(model)

        expression = model.objective.expression
        n_constraints = len(model.constraints)
        solution = pfba(model)
        assert solution.status == "optimal"
        assert numpy.isclose(solution.x_dict["Biomass_Ecoli_core"],
                             0.8739, atol=1e-4, rtol=0.0)
        abs_x = [abs(i) for i in solution.x]
        assert numpy.isclose(sum(abs_x), 518.4221, atol=1e-4, rtol=0.0)
        # test changes to model reverted
        assert expression == model.objective.expression
        assert len(model.constraints) == n_constraints

        # needed?
        # Test desired_objective_value
        # desired_objective = 0.8
        # pfba(model, solver=solver,
        #                       desired_objective_value=desired_objective)
        # abs_x = [abs(i) for i in model.solution.x]
        # assert model.solution.status == "optimal"
        # assert abs(model.solution.f - desired_objective) < 0.001
        # assert abs(sum(abs_x) - 476.1594) < 0.001

        # TODO: parametrize fraction (DRY it up)
        # Test fraction_of_optimum
        solution = pfba(model, fraction_of_optimum=0.95)
        assert solution.status == "optimal"
        assert numpy.isclose(solution.x_dict["Biomass_Ecoli_core"],
                             0.95 * 0.8739, atol=1e-4, rtol=0.0)
        abs_x = [abs(i) for i in solution.x]
        assert numpy.isclose(sum(abs_x), 493.4400, atol=1e-4, rtol=0.0)

        # Infeasible solution
        model.reactions.ATPM.lower_bound = 500
        with warnings.catch_warnings():
            warnings.simplefilter("error", UserWarning)
            with pytest.raises((UserWarning, Infeasible, ValueError)):
                pfba(model)
Esempio n. 9
0
def flux_variability_analysis(model, reaction_list=None, loopless=False,
                              fraction_of_optimum=1.0, pfba_factor=None):
    """
    Determine the minimum and maximum possible flux value for each reaction.

    Parameters
    ----------
    model : cobra.Model
        The model for which to run the analysis. It will *not* be modified.
    reaction_list : list of cobra.Reaction or str, optional
        The reactions for which to obtain min/max fluxes. If None will use
        all reactions in the model (default).
    loopless : boolean, optional
        Whether to return only loopless solutions. This is significantly
        slower. Please also refer to the notes.
    fraction_of_optimum : float, optional
        Must be <= 1.0. Requires that the objective value is at least the
        fraction times maximum objective value. A value of 0.85 for instance
        means that the objective has to be at least at 85% percent of its
        maximum.
    pfba_factor : float, optional
        Add an additional constraint to the model that requires the total sum
        of absolute fluxes must not be larger than this value times the
        smallest possible sum of absolute fluxes, i.e., by setting the value
        to 1.1 the total sum of absolute fluxes must not be more than
        10% larger than the pFBA solution. Since the pFBA solution is the
        one that optimally minimizes the total flux sum, the ``pfba_factor``
        should, if set, be larger than one. Setting this value may lead to
        more realistic predictions of the effective flux bounds.

    Returns
    -------
    pandas.DataFrame
        A data frame with reaction identifiers as the index and two columns:
        - maximum: indicating the highest possible flux
        - minimum: indicating the lowest possible flux

    Notes
    -----
    This implements the fast version as described in [1]_. Please note that
    the flux distribution containing all minimal/maximal fluxes does not have
    to be a feasible solution for the model. Fluxes are minimized/maximized
    individually and a single minimal flux might require all others to be
    suboptimal.

    Using the loopless option will lead to a significant increase in
    computation time (about a factor of 100 for large models). However, the
    algorithm used here (see [2]_) is still more than 1000x faster than the
    "naive" version using ``add_loopless(model)``. Also note that if you have
    included constraints that force a loop (for instance by setting all fluxes
    in a loop to be non-zero) this loop will be included in the solution.

    References
    ----------
    .. [1] Computationally efficient flux variability analysis.
       Gudmundsson S, Thiele I.
       BMC Bioinformatics. 2010 Sep 29;11:489.
       doi: 10.1186/1471-2105-11-489, PMID: 20920235

    .. [2] CycleFreeFlux: efficient removal of thermodynamically infeasible
       loops from flux distributions.
       Desouki AA, Jarre F, Gelius-Dietrich G, Lercher MJ.
       Bioinformatics. 2015 Jul 1;31(13):2159-65.
       doi: 10.1093/bioinformatics/btv096.
    """
    if reaction_list is None:
        reaction_list = model.reactions
    else:
        reaction_list = model.reactions.get_by_any(reaction_list)

    prob = model.problem
    fva_results = DataFrame({
        "minimum": zeros(len(reaction_list), dtype=float),
        "maximum": zeros(len(reaction_list), dtype=float)
    }, index=[r.id for r in reaction_list])
    with model:
        # Safety check before setting up FVA.
        model.slim_optimize(error_value=None,
                            message="There is no optimal solution for the "
                                    "chosen objective!")
        # Add the previous objective as a variable to the model then set it to
        # zero. This also uses the fraction to create the lower/upper bound for
        # the old objective.
        if model.solver.objective.direction == "max":
            fva_old_objective = prob.Variable(
                "fva_old_objective",
                lb=fraction_of_optimum * model.solver.objective.value)
        else:
            fva_old_objective = prob.Variable(
                "fva_old_objective",
                ub=fraction_of_optimum * model.solver.objective.value)
        fva_old_obj_constraint = prob.Constraint(
            model.solver.objective.expression - fva_old_objective, lb=0, ub=0,
            name="fva_old_objective_constraint")
        model.add_cons_vars([fva_old_objective, fva_old_obj_constraint])

        if pfba_factor is not None:
            if pfba_factor < 1.:
                warn("The 'pfba_factor' should be larger or equal to 1.",
                     UserWarning)
            with model:
                add_pfba(model, fraction_of_optimum=0)
                ub = model.slim_optimize(error_value=None)
                flux_sum = prob.Variable("flux_sum", ub=pfba_factor * ub)
                flux_sum_constraint = prob.Constraint(
                    model.solver.objective.expression - flux_sum, lb=0, ub=0,
                    name="flux_sum_constraint")
            model.add_cons_vars([flux_sum, flux_sum_constraint])

        model.objective = Zero  # This will trigger the reset as well
        for what in ("minimum", "maximum"):
            sense = "min" if what == "minimum" else "max"
            for rxn in reaction_list:
                # The previous objective assignment already triggers a reset
                # so directly update coefs here to not trigger redundant resets
                # in the history manager which can take longer than the actual
                # FVA for small models
                model.solver.objective.set_linear_coefficients(
                    {rxn.forward_variable: 1, rxn.reverse_variable: -1})
                model.solver.objective.direction = sense
                model.slim_optimize()
                sutil.check_solver_status(model.solver.status)
                if loopless:
                    value = loopless_fva_iter(model, rxn)
                else:
                    value = model.solver.objective.value
                fva_results.at[rxn.id, what] = value
                model.solver.objective.set_linear_coefficients(
                    {rxn.forward_variable: 0, rxn.reverse_variable: 0})

    return fva_results
Esempio n. 10
0
def _continuous_iterative_binary_gapfill(model,
                                         phenotype_dict,
                                         cycle_order,
                                         universal=None,
                                         output_ensemble_size=1,
                                         lower_bound=0.05,
                                         penalties=None,
                                         demand_reactions=False,
                                         exchange_reactions=False,
                                         flux_cutoff=1E-8,
                                         exchange_prefix='EX_'):
    if exchange_reactions:
        raise NotImplementedError("Inclusion of new exchange reactions is not"
                                  "supported for continuous gapfill")
    if demand_reactions:
        raise NotImplementedError("Inclusion of demand reactions is not"
                                  "supported for continuous gapfill")

    solutions = []
    gapfiller = universal.copy()

    # get the original objective from the model being gapfilled
    model_to_gapfill = model.copy()
    original_objective = linear_reaction_coefficients(model_to_gapfill)
    # convert to IDs to avoid issues with model membership when these reactions
    # are added to gapfiller
    original_objective = {
        rxn.id: original_objective[rxn]
        for rxn in original_objective.keys()
    }

    # get the reactions in the original model, which need to be removed from
    # the universal if present. This cannot catch identical reactions that do
    # not share IDs, so make sure your model and universal are in the same
    # namespace.
    rxns_to_remove = [rxn for rxn in gapfiller.reactions if rxn.id in \
                        [rxn.id for rxn in model_to_gapfill.reactions]]
    gapfiller.remove_reactions(rxns_to_remove)

    # get the list of reactions currently in the gapfiller, which are the ones
    # we will need to check for flux after solving the problem (e.g. these are
    # the reactions we are considering adding to the model)
    get_fluxes = [rxn.id for rxn in gapfiller.reactions]

    # add the reactions from the model to the gapfiller, which are not
    # included in the pFBA formulation, and thus flux is not penalized
    # through them.
    original_model_reactions = [
        rxn.copy() for rxn in model_to_gapfill.reactions
    ]
    gapfiller.add_reactions(original_model_reactions)
    original_reaction_ids = [
        reaction.id for reaction in original_model_reactions
    ]

    # Add the pFBA constraints and objective (minimizes sum of fluxes)
    add_pfba(gapfiller)

    # set the linear coefficients for reactions in the original model to 0
    coefficients = (gapfiller.objective.get_linear_coefficients(
        gapfiller.variables))
    reaction_variables = (
        ((gapfiller.reactions.get_by_id(reaction).forward_variable),
         (gapfiller.reactions.get_by_id(reaction).reverse_variable))
        for reaction in original_reaction_ids)
    variables = chain(*reaction_variables)
    for variable in variables:
        coefficients[variable] = 0.0
    gapfiller.objective.set_linear_coefficients(coefficients)

    ## set a constraint on flux through the original objective
    for reaction in original_objective.keys():
        print("Constraining lower bound for " + reaction)
        gapfiller.reactions.get_by_id(reaction).lower_bound = lower_bound

    exchange_reactions = [rxn for rxn in gapfiller.reactions if\
                            rxn.id.startswith(exchange_prefix)]
    for rxn in exchange_reactions:
        rxn.lower_bound = 0

    for cycle_num in range(0, output_ensemble_size):
        print("starting cycle number " + str(cycle_num))
        cycle_reactions = set()
        original_coefficients = \
            gapfiller.objective.get_linear_coefficients(gapfiller.variables)

        for condition in cycle_order[cycle_num]:
            # set the medium for this condition.
            for ex_rxn in phenotype_dict[condition].keys():
                gapfiller.reactions.get_by_id(ex_rxn).lower_bound = \
                    -1.0*phenotype_dict[condition][ex_rxn]
                gapfiller.reactions.get_by_id(ex_rxn).upper_bound = \
                    1.0*phenotype_dict[condition][ex_rxn]

            # gapfill and get the solution
            iteration_solution = gapfiller.optimize()

            filtered_solution = {rxn:iteration_solution.x_dict[rxn] for rxn in\
               get_fluxes if abs(iteration_solution.x_dict[rxn]) > flux_cutoff}

            add_rxns = [universal.reactions.get_by_id(rxn).copy() for \
                                            rxn in filtered_solution.keys()]
            # combine the solution from this iteration with all others within
            # the current cycle
            cycle_reactions = cycle_reactions | \
                                set([rxn.id for rxn in add_rxns])

            # validate that the proposed solution restores flux through the
            # objective in the original model
            # set the bounds on the original model to represent media
            # and validate the gapfill solution
            for ex_rxn in [rxn for rxn in model_to_gapfill.reactions if \
                            rxn.id.startswith(exchange_prefix)]:
                ex_rxn.lower_bound = 0
            for ex_rxn in phenotype_dict[condition].keys():
                model_to_gapfill.reactions.get_by_id(ex_rxn).lower_bound = \
                    -1.0*phenotype_dict[condition][ex_rxn]
                model_to_gapfill.reactions.get_by_id(ex_rxn).upper_bound = \
                    1.0*phenotype_dict[condition][ex_rxn]
            if not validate(model_to_gapfill,\
                            [universal.reactions.get_by_id(rxn).copy() for \
                            rxn in cycle_reactions],lower_bound):
                raise RuntimeError('Failed to validate gapfilled model, '
                                   'try lowering the flux_cutoff through '
                                   'inclusion_threshold')

            # remove the flux minimization penalty on the gapfilled reactions
            coefficients = (gapfiller.objective.get_linear_coefficients(
                gapfiller.variables).copy())
            reaction_variables = (
                ((gapfiller.reactions.get_by_id(rxn).forward_variable),
                 (gapfiller.reactions.get_by_id(rxn).reverse_variable))
                for rxn in cycle_reactions)
            variables = chain(*reaction_variables)
            for variable in variables:
                coefficients[variable] = 0.0

            gapfiller.objective.set_linear_coefficients(coefficients)
            check = gapfiller.slim_optimize()  # optimizing might be necessary
            # to update coefficients.

            # reset the media condition
            for ex_rxn in exchange_reactions:
                ex_rxn.lower_bound = 0

        gapfiller.objective.set_linear_coefficients(original_coefficients)
        solutions.append(list(cycle_reactions))
    return solutions
Esempio n. 11
0
def geometric_fba(model, epsilon=1E-06, max_tries=200):
    """Perform geometric FBA to obtain a unique, centered flux distribution.

    Geometric FBA [1]_ formulates the problem as a polyhedron and
    then solves it by bounding the convex hull of the polyhedron.
    The bounding forms a box around the convex hull which reduces
    with every iteration and extracts a unique solution in this way.

    Parameters
    ----------
    model: cobra.Model
        The model to perform geometric FBA on.
    epsilon: float, optional
        The convergence tolerance of the model (default 1E-06).
    max_tries: int, optional
        Maximum number of iterations (default 200).

    Returns
    -------
    cobra.Solution
        The solution object containing all the constraints required
        for geometric FBA.

    References
    ----------
    .. [1] Smallbone, Kieran & Simeonidis, Vangelis. (2009).
    Flux balance analysis: A geometric perspective.
    Journal of theoretical biology.258. 311-5. 10.1016/j.jtbi.2009.01.027.
    """

    with model:
        # iteration parameters
        delta = 1.0  # initialize at 1.0 to enter while loop
        count = 2  # iteration #1 happens out of the loop

        # vars and consts storage variables
        consts = []
        obj_vars = []
        updating_vars_cons = []

        # first iteration
        prob = model.problem
        add_pfba(model)  # minimizes the solution space to convex hull
        model.optimize()
        fva_sol = flux_variability_analysis(model)
        mean_flux = (fva_sol["maximum"] + fva_sol["minimum"]).abs() / 2

        # set gFBA constraints
        for rxn in model.reactions:
            var = prob.Variable("geometric_fba_" + rxn.id,
                                lb=0,
                                ub=mean_flux[rxn.id])
            upper_const = prob.Constraint(rxn.flux_expression - var,
                                          ub=mean_flux[rxn.id],
                                          name="geometric_fba_upper_const_" +
                                          rxn.id)
            lower_const = prob.Constraint(rxn.flux_expression + var,
                                          lb=fva_sol.at[rxn.id, "minimum"],
                                          name="geometric_fba_lower_const_" +
                                          rxn.id)
            updating_vars_cons.append((rxn.id, var, upper_const, lower_const))
            consts.extend([var, upper_const, lower_const])
            obj_vars.append(var)
        model.add_cons_vars(consts)

        # minimize distance between flux and centre
        model.objective = prob.Objective(Zero, sloppy=True, direction="min")
        model.objective.set_linear_coefficients({v: 1.0 for v in obj_vars})
        model.optimize()

        # further iterations
        while delta > epsilon and count <= max_tries:
            fva_sol = flux_variability_analysis(model)
            mean_flux = (fva_sol["maximum"] + fva_sol["minimum"]).abs() / 2

            for rxn_id, var, u_c, l_c in updating_vars_cons:
                var.ub = mean_flux[rxn_id]
                u_c.ub = mean_flux[rxn_id]
                l_c.lb = fva_sol.at[rxn_id, "minimum"]
            model.optimize()
            delta = (fva_sol["maximum"] - fva_sol["minimum"]).max()

            count += 1
            if count == max_tries:
                raise RuntimeError(
                    "The iterations have exceeded the maximum value of {}. "
                    "This is probably due to the increased complexity of the "
                    "model and can lead to inaccurate results. Please set a "
                    "different convergence tolerance and/or increase the "
                    "maximum iterations".format(max_tries)
                )

    return get_solution(model)
Esempio n. 12
0
def _fva_optlang(model, reaction_list, fraction, loopless, pfba_factor):
    """Helper function to perform FVA with the optlang interface.

    Parameters
    ----------
    model : a cobra model
    reaction_list : list of reactions
    fraction : float, optional
        Must be <= 1.0. Requires that the objective value is at least
        fraction * max_objective_value. A value of 0.85 for instance means that
        the objective has to be at least at 85% percent of its maximum.
    loopless : boolean, optional
        Whether to return only loopless solutions.
    pfba_factor : float, optional
        Add additional constraint to the model that the total sum of
        absolute fluxes must not be larger than this value times the
        smallest possible sum of absolute fluxes, i.e., by setting the value
        to 1.1 then the total sum of absolute fluxes must not be more than
        10% larger than the pfba solution. Setting this value may lead to
        more realistic predictions of the effective flux bounds.

    Returns
    -------
    dict
        A dictionary containing the results.
    """
    prob = model.problem
    fva_results = {rxn.id: {} for rxn in reaction_list}
    with model as m:
        m.slim_optimize(error_value=None,
                        message="There is no optimal solution for the "
                                "chosen objective!")
        # Add objective as a variable to the model than set to zero
        # This also uses the fraction to create the lower bound for the
        # old objective
        fva_old_objective = prob.Variable(
            "fva_old_objective", lb=fraction * m.solver.objective.value)
        fva_old_obj_constraint = prob.Constraint(
            m.solver.objective.expression - fva_old_objective, lb=0, ub=0,
            name="fva_old_objective_constraint")
        m.add_cons_vars([fva_old_objective, fva_old_obj_constraint])

        if pfba_factor is not None:
            if pfba_factor < 1.:
                warn('pfba_factor should be larger or equal to 1', UserWarning)
            with m:
                add_pfba(m, fraction_of_optimum=0)
                ub = m.slim_optimize(error_value=None)
                flux_sum = prob.Variable("flux_sum", ub=pfba_factor * ub)
                flux_sum_constraint = prob.Constraint(
                    m.solver.objective.expression - flux_sum, lb=0, ub=0,
                    name="flux_sum_constraint")
            m.add_cons_vars([flux_sum, flux_sum_constraint])

        m.objective = Zero  # This will trigger the reset as well
        for what in ("minimum", "maximum"):
            sense = "min" if what == "minimum" else "max"
            for rxn in reaction_list:
                r_id = rxn.id
                rxn = m.reactions.get_by_id(r_id)
                # The previous objective assignment already triggers a reset
                # so directly update coefs here to not trigger redundant resets
                # in the history manager which can take longer than the actual
                # FVA for small models
                m.solver.objective.set_linear_coefficients(
                    {rxn.forward_variable: 1, rxn.reverse_variable: -1})
                m.solver.objective.direction = sense
                m.slim_optimize()
                sutil.check_solver_status(m.solver.status)
                if loopless:
                    value = loopless_fva_iter(m, rxn)
                else:
                    value = m.solver.objective.value
                fva_results[r_id][what] = value
                m.solver.objective.set_linear_coefficients(
                    {rxn.forward_variable: 0, rxn.reverse_variable: 0})

    return fva_results
Esempio n. 13
0
def pfba_gapfill(model,
                 reaction_bag,
                 obj=None,
                 obj_lb=10.,
                 obj_constraint=False,
                 iters=1,
                 tasks=None,
                 task_lb=0.05,
                 add_exchanges=True,
                 extracellular='e'):
    '''
    Function that utilizes iterations of pFBA solution with a universal reaction bag 
    in order to gapfill a model.
    
    Parameters
    ----------
    model : cobra.Model
        Model to be gapfilled
    reaction_bag : cobra.Model
        Reaction bag reference to use during gapfilling
    obj : string
        Reaction ID for objective function in model to be gapfilled.
    obj_lb : float
        Lower bound for objective function
    obj_constraint : bool
        Sets objective as contstraint which must be maximized
    tasks : list or None
        List of reactions IDs (strings) of metabolic tasks 
        to set a minimum lower bound for
    task_lb : float
        Lower bound for any metabolic tasks
    iters : int
        Number of gapfilling rounds. Unique reactions from each round are 
        saved and the union is added simulatneously to the model
    add_exchanges : bool
        Identifies extracellular metabolites added during gapfilling that
        are not associated with exchange reactions and creates them
    extracellular : string
        Label for extracellular compartment of model
    '''
    start_time = time.time()

    # Save some basic network info for downstream membership testing
    orig_rxn_ids = set([str(x.id) for x in model.reactions])
    orig_cpd_ids = set([str(y.id) for y in model.metabolites])
    univ_rxn_ids = set([str(z.id) for z in reaction_bag.reactions])

    # Find overlap in model and reaction bag
    overlap_rxn_ids = univ_rxn_ids.intersection(orig_rxn_ids)

    # Get model objective reaction ID
    if obj == None:
        obj = get_objective(model)
    else:
        obj = obj

    # Modify universal reaction bag
    new_rxn_ids = set()
    print('Creating universal model...')
    with reaction_bag as universal:

        # Remove overlapping reactions from universal bag, and reset objective if needed
        for rxn in overlap_rxn_ids:
            universal.reactions.get_by_id(rxn).remove_from_model()

        # Set objective in universal if told by user
        # Made constraint as fraction of minimum in next step
        if obj_constraint:
            universal.add_reactions([model.reactions.get_by_id(obj)])
            universal.objective = obj
            orig_rxn_ids.remove(obj)
            orig_rxns = []
            for rxn in orig_rxn_ids:
                orig_rxns.append(copy.deepcopy(model.reactions.get_by_id(rxn)))
        else:
            orig_rxns = list(copy.deepcopy(model.reactions))

        # Add pFBA to universal model and add model reactions
        add_pfba(universal)
        #universal = copy.deepcopy(universal) # reset solver
        universal.add_reactions(orig_rxns)

        # If previous objective not set as constraint, set minimum lower bound
        if not obj_constraint:
            universal.reactions.get_by_id(obj).lower_bound = obj_lb

        # Set metabolic tasks that must carry flux in gapfilled solution
        if tasks != None:
            for task in tasks:
                try:
                    universal.reactions.get_by_id(task).lower_bound = task_lb
                except:
                    print(task + 'not found in model. Ignoring.')
                    continue

        # Run FBA and save solution
        print('Optimizing model with combined reactions...')
        solution = universal.optimize()

        if iters > 1:
            print('Generating flux sampling object...')
            optgp_object = OptGPSampler(universal, processes=4)

            # Assess the sampled flux distributions
            print('Sampling ' + str(iters) + ' flux distributions...')
            flux_samples = optgp_object.sample(iters)
            rxns = list(flux_samples.columns)
            for distribution in flux_samples.iterrows():
                for flux in range(0, len(list(distribution[1]))):
                    if abs(list(distribution[1])[flux]) > 1e-6:
                        new_rxn_ids |= set([rxns[flux]
                                            ]).difference(orig_rxn_ids)
        else:
            rxns = list(solution.fluxes.index)
            fluxes = list(solution.fluxes)
            for flux in range(0, len(fluxes)):
                if abs(fluxes[flux]) > 1e-6:
                    new_rxn_ids |= set([rxns[flux]])

    # Screen new reaction IDs
    if obj in new_rxn_ids: new_rxn_ids.remove(obj)
    for rxn in orig_rxn_ids:
        try:
            new_rxn_ids.remove(rxn)
        except:
            continue

    # Get reactions and metabolites to be added to the model
    print('Gapfilling model...')
    new_rxns = copy.deepcopy(
        [reaction_bag.reactions.get_by_id(rxn) for rxn in new_rxn_ids])
    new_cpd_ids = set()
    for rxn in new_rxns:
        new_cpd_ids |= set([str(x.id) for x in list(rxn.metabolites)])
    new_cpd_ids = new_cpd_ids.difference(orig_cpd_ids)
    new_cpds = copy.deepcopy(
        [reaction_bag.metabolites.get_by_id(cpd) for cpd in new_cpd_ids])

    # Copy model and gapfill
    new_model = copy.deepcopy(model)
    new_model.add_metabolites(new_cpds)
    new_model.add_reactions(new_rxns)

    # Identify extracellular metabolites with no exchanges
    if add_exchanges == True:
        new_exchanges = extend_exchanges(new_model, new_cpd_ids, extracellular)
        if len(new_exchanges) > 0: new_rxn_ids |= new_exchanges

    duration = int(round(time.time() - start_time))
    print('Took ' + str(duration) + ' seconds to gapfill ' + str(len(new_rxn_ids)) + \
          ' reactions and ' + str(len(new_cpd_ids)) + ' metabolites.')

    new_obj_val = new_model.slim_optimize()
    if new_obj_val > 1e-6:
        print('Gapfilled model objective now carries flux (' +
              str(new_obj_val) + ').')
    else:
        print('Gapfilled model objective still does not carry flux.')

    return new_model
Esempio n. 14
0
def flux_variability_analysis(
    model,
    reaction_list=None,
    loopless=False,
    fraction_of_optimum=1.0,
    pfba_factor=None,
    processes=None,
):
    """
    Determine the minimum and maximum possible flux value for each reaction.

    Parameters
    ----------
    model : cobra.Model
        The model for which to run the analysis. It will *not* be modified.
    reaction_list : list of cobra.Reaction or str, optional
        The reactions for which to obtain min/max fluxes. If None will use
        all reactions in the model (default).
    loopless : boolean, optional
        Whether to return only loopless solutions. This is significantly
        slower. Please also refer to the notes.
    fraction_of_optimum : float, optional
        Must be <= 1.0. Requires that the objective value is at least the
        fraction times maximum objective value. A value of 0.85 for instance
        means that the objective has to be at least at 85% percent of its
        maximum.
    pfba_factor : float, optional
        Add an additional constraint to the model that requires the total sum
        of absolute fluxes must not be larger than this value times the
        smallest possible sum of absolute fluxes, i.e., by setting the value
        to 1.1 the total sum of absolute fluxes must not be more than
        10% larger than the pFBA solution. Since the pFBA solution is the
        one that optimally minimizes the total flux sum, the ``pfba_factor``
        should, if set, be larger than one. Setting this value may lead to
        more realistic predictions of the effective flux bounds.
    processes : int, optional
        The number of parallel processes to run. If not explicitly passed,
        will be set from the global configuration singleton.

    Returns
    -------
    pandas.DataFrame
        A data frame with reaction identifiers as the index and two columns:
        - maximum: indicating the highest possible flux
        - minimum: indicating the lowest possible flux

    Notes
    -----
    This implements the fast version as described in [1]_. Please note that
    the flux distribution containing all minimal/maximal fluxes does not have
    to be a feasible solution for the model. Fluxes are minimized/maximized
    individually and a single minimal flux might require all others to be
    suboptimal.

    Using the loopless option will lead to a significant increase in
    computation time (about a factor of 100 for large models). However, the
    algorithm used here (see [2]_) is still more than 1000x faster than the
    "naive" version using ``add_loopless(model)``. Also note that if you have
    included constraints that force a loop (for instance by setting all fluxes
    in a loop to be non-zero) this loop will be included in the solution.

    References
    ----------
    .. [1] Computationally efficient flux variability analysis.
       Gudmundsson S, Thiele I.
       BMC Bioinformatics. 2010 Sep 29;11:489.
       doi: 10.1186/1471-2105-11-489, PMID: 20920235

    .. [2] CycleFreeFlux: efficient removal of thermodynamically infeasible
       loops from flux distributions.
       Desouki AA, Jarre F, Gelius-Dietrich G, Lercher MJ.
       Bioinformatics. 2015 Jul 1;31(13):2159-65.
       doi: 10.1093/bioinformatics/btv096.
    """
    if reaction_list is None:
        reaction_ids = [r.id for r in model.reactions]
    else:
        reaction_ids = [
            r.id for r in model.reactions.get_by_any(reaction_list)
        ]

    if processes is None:
        processes = CONFIGURATION.processes
    num_reactions = len(reaction_ids)
    processes = min(processes, num_reactions)

    fva_result = DataFrame(
        {
            "minimum": zeros(num_reactions, dtype=float),
            "maximum": zeros(num_reactions, dtype=float),
        },
        index=reaction_ids,
    )
    prob = model.problem
    with model:
        # Safety check before setting up FVA.
        model.slim_optimize(
            error_value=None,
            message="There is no optimal solution for the "
            "chosen objective!",
        )
        # Add the previous objective as a variable to the model then set it to
        # zero. This also uses the fraction to create the lower/upper bound for
        # the old objective.
        # TODO: Use utility function here (fix_objective_as_constraint)?
        if model.solver.objective.direction == "max":
            fva_old_objective = prob.Variable(
                "fva_old_objective",
                lb=fraction_of_optimum * model.solver.objective.value,
            )
        else:
            fva_old_objective = prob.Variable(
                "fva_old_objective",
                ub=fraction_of_optimum * model.solver.objective.value,
            )
        fva_old_obj_constraint = prob.Constraint(
            model.solver.objective.expression - fva_old_objective,
            lb=0,
            ub=0,
            name="fva_old_objective_constraint",
        )
        model.add_cons_vars([fva_old_objective, fva_old_obj_constraint])

        if pfba_factor is not None:
            if pfba_factor < 1.0:
                warn(
                    "The 'pfba_factor' should be larger or equal to 1.",
                    UserWarning,
                )
            with model:
                add_pfba(model, fraction_of_optimum=0)
                ub = model.slim_optimize(error_value=None)
                flux_sum = prob.Variable("flux_sum", ub=pfba_factor * ub)
                flux_sum_constraint = prob.Constraint(
                    model.solver.objective.expression - flux_sum,
                    lb=0,
                    ub=0,
                    name="flux_sum_constraint",
                )
            model.add_cons_vars([flux_sum, flux_sum_constraint])

        model.objective = Zero  # This will trigger the reset as well
        for what in ("minimum", "maximum"):
            if processes > 1:
                # We create and destroy a new pool here in order to set the
                # objective direction for all reactions. This creates a
                # slight overhead but seems the most clean.
                chunk_size = len(reaction_ids) // processes
                pool = multiprocessing.Pool(
                    processes,
                    initializer=_init_worker,
                    initargs=(model, loopless, what[:3]),
                )
                for rxn_id, value in pool.imap_unordered(_fva_step,
                                                         reaction_ids,
                                                         chunksize=chunk_size):
                    fva_result.at[rxn_id, what] = value
                pool.close()
                pool.join()
            else:
                _init_worker(model, loopless, what[:3])
                for rxn_id, value in map(_fva_step, reaction_ids):
                    fva_result.at[rxn_id, what] = value

    return fva_result[["minimum", "maximum"]]
Esempio n. 15
0
def _fva_optlang(model, reaction_list, fraction, loopless, pfba_factor):
    """Helper function to perform FVA with the optlang interface.

    Parameters
    ----------
    model : a cobra model
    reaction_list : list of reactions
    fraction : float, optional
        Must be <= 1.0. Requires that the objective value is at least
        fraction * max_objective_value. A value of 0.85 for instance means that
        the objective has to be at least at 85% percent of its maximum.
    loopless : boolean, optional
        Whether to return only loopless solutions.
    pfba_factor : float, optional
        Add additional constraint to the model that the total sum of
        absolute fluxes must not be larger than this value times the
        smallest possible sum of absolute fluxes, i.e., by setting the value
        to 1.1 then the total sum of absolute fluxes must not be more than
        10% larger than the pfba solution. Setting this value may lead to
        more realistic predictions of the effective flux bounds.

    Returns
    -------
    dict
        A dictionary containing the results.
    """
    prob = model.problem
    fva_results = {rxn.id: {} for rxn in reaction_list}
    with model as m:
        m.slim_optimize(error_value=None,
                        message="There is no optimal solution for the "
                        "chosen objective!")
        # Add objective as a variable to the model than set to zero
        # This also uses the fraction to create the lower bound for the
        # old objective
        fva_old_objective = prob.Variable("fva_old_objective",
                                          lb=fraction *
                                          m.solver.objective.value)
        fva_old_obj_constraint = prob.Constraint(
            m.solver.objective.expression - fva_old_objective,
            lb=0,
            ub=0,
            name="fva_old_objective_constraint")
        m.add_cons_vars([fva_old_objective, fva_old_obj_constraint])

        if pfba_factor is not None:
            if pfba_factor < 1.:
                warn('pfba_factor should be larger or equal to 1', UserWarning)
            with m:
                add_pfba(m, fraction_of_optimum=0)
                ub = m.slim_optimize(error_value=None)
                flux_sum = prob.Variable("flux_sum", ub=pfba_factor * ub)
                flux_sum_constraint = prob.Constraint(
                    m.solver.objective.expression - flux_sum,
                    lb=0,
                    ub=0,
                    name="flux_sum_constraint")
            m.add_cons_vars([flux_sum, flux_sum_constraint])

        m.objective = Zero  # This will trigger the reset as well
        for what in ("minimum", "maximum"):
            sense = "min" if what == "minimum" else "max"
            for rxn in reaction_list:
                r_id = rxn.id
                rxn = m.reactions.get_by_id(r_id)
                # The previous objective assignment already triggers a reset
                # so directly update coefs here to not trigger redundant resets
                # in the history manager which can take longer than the actual
                # FVA for small models
                m.solver.objective.set_linear_coefficients({
                    rxn.forward_variable:
                    1,
                    rxn.reverse_variable:
                    -1
                })
                m.solver.objective.direction = sense
                m.slim_optimize()
                sutil.check_solver_status(m.solver.status)
                if loopless:
                    value = loopless_fva_iter(m, rxn)
                else:
                    value = m.solver.objective.value
                fva_results[r_id][what] = value
                m.solver.objective.set_linear_coefficients({
                    rxn.forward_variable:
                    0,
                    rxn.reverse_variable:
                    0
                })

    return fva_results
Esempio n. 16
0
def flux_variability_analysis(model,
                              reaction_list=None,
                              loopless=False,
                              fraction_of_optimum=1.0,
                              pfba_factor=None):
    """
    Determine the minimum and maximum possible flux value for each reaction.

    Parameters
    ----------
    model : cobra.Model
        The model for which to run the analysis. It will *not* be modified.
    reaction_list : list of cobra.Reaction or str, optional
        The reactions for which to obtain min/max fluxes. If None will use
        all reactions in the model (default).
    loopless : boolean, optional
        Whether to return only loopless solutions. This is significantly
        slower. Please also refer to the notes.
    fraction_of_optimum : float, optional
        Must be <= 1.0. Requires that the objective value is at least the
        fraction times maximum objective value. A value of 0.85 for instance
        means that the objective has to be at least at 85% percent of its
        maximum.
    pfba_factor : float, optional
        Add an additional constraint to the model that requires the total sum
        of absolute fluxes must not be larger than this value times the
        smallest possible sum of absolute fluxes, i.e., by setting the value
        to 1.1 the total sum of absolute fluxes must not be more than
        10% larger than the pFBA solution. Since the pFBA solution is the
        one that optimally minimizes the total flux sum, the ``pfba_factor``
        should, if set, be larger than one. Setting this value may lead to
        more realistic predictions of the effective flux bounds.

    Returns
    -------
    pandas.DataFrame
        A data frame with reaction identifiers as the index and two columns:
        - maximum: indicating the highest possible flux
        - minimum: indicating the lowest possible flux

    Notes
    -----
    This implements the fast version as described in [1]_. Please note that
    the flux distribution containing all minimal/maximal fluxes does not have
    to be a feasible solution for the model. Fluxes are minimized/maximized
    individually and a single minimal flux might require all others to be
    suboptimal.

    Using the loopless option will lead to a significant increase in
    computation time (about a factor of 100 for large models). However, the
    algorithm used here (see [2]_) is still more than 1000x faster than the
    "naive" version using ``add_loopless(model)``. Also note that if you have
    included constraints that force a loop (for instance by setting all fluxes
    in a loop to be non-zero) this loop will be included in the solution.

    References
    ----------
    .. [1] Computationally efficient flux variability analysis.
       Gudmundsson S, Thiele I.
       BMC Bioinformatics. 2010 Sep 29;11:489.
       doi: 10.1186/1471-2105-11-489, PMID: 20920235

    .. [2] CycleFreeFlux: efficient removal of thermodynamically infeasible
       loops from flux distributions.
       Desouki AA, Jarre F, Gelius-Dietrich G, Lercher MJ.
       Bioinformatics. 2015 Jul 1;31(13):2159-65.
       doi: 10.1093/bioinformatics/btv096.
    """
    if reaction_list is None:
        reaction_list = model.reactions
    else:
        reaction_list = model.reactions.get_by_any(reaction_list)

    prob = model.problem
    fva_results = DataFrame(
        {
            "minimum": zeros(len(reaction_list), dtype=float),
            "maximum": zeros(len(reaction_list), dtype=float)
        },
        index=[r.id for r in reaction_list])
    with model:
        # Safety check before setting up FVA.
        model.slim_optimize(error_value=None,
                            message="There is no optimal solution for the "
                            "chosen objective!")
        # Add the previous objective as a variable to the model then set it to
        # zero. This also uses the fraction to create the lower/upper bound for
        # the old objective.
        if model.solver.objective.direction == "max":
            fva_old_objective = prob.Variable("fva_old_objective",
                                              lb=fraction_of_optimum *
                                              model.solver.objective.value)
        else:
            fva_old_objective = prob.Variable("fva_old_objective",
                                              ub=fraction_of_optimum *
                                              model.solver.objective.value)
        fva_old_obj_constraint = prob.Constraint(
            model.solver.objective.expression - fva_old_objective,
            lb=0,
            ub=0,
            name="fva_old_objective_constraint")
        model.add_cons_vars([fva_old_objective, fva_old_obj_constraint])

        if pfba_factor is not None:
            if pfba_factor < 1.:
                warn("The 'pfba_factor' should be larger or equal to 1.",
                     UserWarning)
            with model:
                add_pfba(model, fraction_of_optimum=0)
                ub = model.slim_optimize(error_value=None)
                flux_sum = prob.Variable("flux_sum", ub=pfba_factor * ub)
                flux_sum_constraint = prob.Constraint(
                    model.solver.objective.expression - flux_sum,
                    lb=0,
                    ub=0,
                    name="flux_sum_constraint")
            model.add_cons_vars([flux_sum, flux_sum_constraint])

        model.objective = Zero  # This will trigger the reset as well
        for what in ("minimum", "maximum"):
            sense = "min" if what == "minimum" else "max"
            model.solver.objective.direction = sense
            for rxn in reaction_list:
                # The previous objective assignment already triggers a reset
                # so directly update coefs here to not trigger redundant resets
                # in the history manager which can take longer than the actual
                # FVA for small models
                model.solver.objective.set_linear_coefficients({
                    rxn.forward_variable:
                    1,
                    rxn.reverse_variable:
                    -1
                })
                model.slim_optimize()
                sutil.check_solver_status(model.solver.status)
                if loopless:
                    value = loopless_fva_iter(model, rxn)
                else:
                    value = model.solver.objective.value
                fva_results.at[rxn.id, what] = value
                model.solver.objective.set_linear_coefficients({
                    rxn.forward_variable:
                    0,
                    rxn.reverse_variable:
                    0
                })

    return fva_results[["minimum", "maximum"]]
Esempio n. 17
0
def geometric_fba(model, epsilon=1E-06, max_tries=200):
    """
    Perform geometric FBA to obtain a unique, centered flux distribution.

    Geometric FBA [1]_ formulates the problem as a polyhedron and
    then solves it by bounding the convex hull of the polyhedron.
    The bounding forms a box around the convex hull which reduces
    with every iteration and extracts a unique solution in this way.

    Parameters
    ----------
    model: cobra.Model
        The model to perform geometric FBA on.
    epsilon: float, optional
        The convergence tolerance of the model (default 1E-06).
    max_tries: int, optional
        Maximum number of iterations (default 200).

    Returns
    -------
    cobra.Solution
        The solution object containing all the constraints required
        for geometric FBA.

    References
    ----------
    .. [1] Smallbone, Kieran & Simeonidis, Vangelis. (2009).
           Flux balance analysis: A geometric perspective.
           Journal of theoretical biology.258. 311-5.
           10.1016/j.jtbi.2009.01.027.

    """

    with model:
        # Variables' and constraints' storage variables.
        consts = []
        obj_vars = []
        updating_vars_cons = []

        # The first iteration.
        prob = model.problem
        add_pfba(model)  # Minimize the solution space to a convex hull.
        model.optimize()
        fva_sol = flux_variability_analysis(model)
        mean_flux = (fva_sol["maximum"] + fva_sol["minimum"]).abs() / 2

        # Set the gFBA constraints.
        for rxn in model.reactions:
            var = prob.Variable("geometric_fba_" + rxn.id,
                                lb=0,
                                ub=mean_flux[rxn.id])
            upper_const = prob.Constraint(rxn.flux_expression - var,
                                          ub=mean_flux[rxn.id],
                                          name="geometric_fba_upper_const_" +
                                          rxn.id)
            lower_const = prob.Constraint(rxn.flux_expression + var,
                                          lb=fva_sol.at[rxn.id, "minimum"],
                                          name="geometric_fba_lower_const_" +
                                          rxn.id)
            updating_vars_cons.append((rxn.id, var, upper_const, lower_const))
            consts.extend([var, upper_const, lower_const])
            obj_vars.append(var)
        model.add_cons_vars(consts)

        # Minimize the distance between the flux distribution and center.
        model.objective = prob.Objective(Zero, sloppy=True, direction="min")
        model.objective.set_linear_coefficients({v: 1.0 for v in obj_vars})
        # Update loop variables.
        sol = model.optimize()
        fva_sol = flux_variability_analysis(model)
        mean_flux = (fva_sol["maximum"] + fva_sol["minimum"]).abs() / 2
        delta = (fva_sol["maximum"] - fva_sol["minimum"]).max()
        count = 1
        LOGGER.debug("Iteration: %d; delta: %.3g; status: %s.", count, delta,
                     sol.status)

        # Following iterations that minimize the distance below threshold.
        while delta > epsilon and count < max_tries:
            for rxn_id, var, u_c, l_c in updating_vars_cons:
                var.ub = mean_flux[rxn_id]
                u_c.ub = mean_flux[rxn_id]
                l_c.lb = fva_sol.at[rxn_id, "minimum"]
            # Update loop variables.
            sol = model.optimize()
            fva_sol = flux_variability_analysis(model)
            mean_flux = (fva_sol["maximum"] + fva_sol["minimum"]).abs() / 2
            delta = (fva_sol["maximum"] - fva_sol["minimum"]).max()
            count += 1
            LOGGER.debug("Iteration: %d; delta: %.3g; status: %s.", count,
                         delta, sol.status)

        if count == max_tries:
            raise RuntimeError(
                "The iterations have exceeded the maximum value of {}. "
                "This is probably due to the increased complexity of the "
                "model and can lead to inaccurate results. Please set a "
                "different convergence tolerance and/or increase the "
                "maximum iterations".format(max_tries))

    return sol
Esempio n. 18
0
def geometric_fba(model, epsilon=1E-06, max_tries=200, processes=None):
    """
    Perform geometric FBA to obtain a unique, centered flux distribution.

    Geometric FBA [1]_ formulates the problem as a polyhedron and
    then solves it by bounding the convex hull of the polyhedron.
    The bounding forms a box around the convex hull which reduces
    with every iteration and extracts a unique solution in this way.

    Parameters
    ----------
    model: cobra.Model
        The model to perform geometric FBA on.
    epsilon: float, optional
        The convergence tolerance of the model (default 1E-06).
    max_tries: int, optional
        Maximum number of iterations (default 200).
    processes : int, optional
        The number of parallel processes to run. If not explicitly passed,
        will be set from the global configuration singleton.

    Returns
    -------
    cobra.Solution
        The solution object containing all the constraints required
        for geometric FBA.

    References
    ----------
    .. [1] Smallbone, Kieran & Simeonidis, Vangelis. (2009).
           Flux balance analysis: A geometric perspective.
           Journal of theoretical biology.258. 311-5.
           10.1016/j.jtbi.2009.01.027.

    """

    with model:
        # Variables' and constraints' storage variables.
        consts = []
        obj_vars = []
        updating_vars_cons = []

        # The first iteration.
        prob = model.problem
        add_pfba(model)  # Minimize the solution space to a convex hull.
        model.optimize()
        fva_sol = flux_variability_analysis(model, processes=processes)
        mean_flux = (fva_sol["maximum"] + fva_sol["minimum"]).abs() / 2

        # Set the gFBA constraints.
        for rxn in model.reactions:
            var = prob.Variable("geometric_fba_" + rxn.id,
                                lb=0,
                                ub=mean_flux[rxn.id])
            upper_const = prob.Constraint(rxn.flux_expression - var,
                                          ub=mean_flux[rxn.id],
                                          name="geometric_fba_upper_const_" +
                                          rxn.id)
            lower_const = prob.Constraint(rxn.flux_expression + var,
                                          lb=fva_sol.at[rxn.id, "minimum"],
                                          name="geometric_fba_lower_const_" +
                                          rxn.id)
            updating_vars_cons.append((rxn.id, var, upper_const, lower_const))
            consts.extend([var, upper_const, lower_const])
            obj_vars.append(var)
        model.add_cons_vars(consts)

        # Minimize the distance between the flux distribution and center.
        model.objective = prob.Objective(Zero, sloppy=True, direction="min")
        model.objective.set_linear_coefficients({v: 1.0 for v in obj_vars})
        # Update loop variables.
        sol = model.optimize()
        fva_sol = flux_variability_analysis(model, processes=processes)
        mean_flux = (fva_sol["maximum"] + fva_sol["minimum"]).abs() / 2
        delta = (fva_sol["maximum"] - fva_sol["minimum"]).max()
        count = 1
        LOGGER.debug("Iteration: %d; delta: %.3g; status: %s.",
                     count, delta, sol.status)

        # Following iterations that minimize the distance below threshold.
        while delta > epsilon and count < max_tries:
            for rxn_id, var, u_c, l_c in updating_vars_cons:
                var.ub = mean_flux[rxn_id]
                u_c.ub = mean_flux[rxn_id]
                l_c.lb = fva_sol.at[rxn_id, "minimum"]
            # Update loop variables.
            sol = model.optimize()
            fva_sol = flux_variability_analysis(model, processes=processes)
            mean_flux = (fva_sol["maximum"] + fva_sol["minimum"]).abs() / 2
            delta = (fva_sol["maximum"] - fva_sol["minimum"]).max()
            count += 1
            LOGGER.debug("Iteration: %d; delta: %.3g; status: %s.",
                         count, delta, sol.status)

        if count == max_tries:
            raise RuntimeError(
                "The iterations have exceeded the maximum value of {}. "
                "This is probably due to the increased complexity of the "
                "model and can lead to inaccurate results. Please set a "
                "different convergence tolerance and/or increase the "
                "maximum iterations".format(max_tries)
            )

    return sol