Example #1
0
def test_choose_solver(model):
    so = su.choose_solver(model, "glpk")
    assert su.interface_to_str(so) == "glpk"

    if any(s in su.solvers for s in su.qp_solvers):
        qp_choice = su.choose_solver(model, qp=True)
        assert su.interface_to_str(qp_choice) in su.qp_solvers
    else:
        with pytest.raises(su.SolverNotFound):
            su.choose_solver(model, qp=True)
Example #2
0
def test_choose_solver(model: "Model") -> Optional[su.SolverNotFound]:
    """Test that solver switching is working."""
    so = su.choose_solver(model, "glpk")
    assert su.interface_to_str(so) == "glpk"

    if any(s in su.solvers for s in su.qp_solvers):
        qp_choice = su.choose_solver(model, qp=True)
        assert su.interface_to_str(qp_choice) in su.qp_solvers
    else:
        with pytest.raises(su.SolverNotFound):
            su.choose_solver(model, qp=True)
Example #3
0
    def test_choose_solver(self, model):
        so = su.choose_solver(model)
        assert su.interface_to_str(so) == "glpk"
        so = su.choose_solver(model, "glpk")
        assert su.interface_to_str(so) == "glpk"

        if any(s in su.solvers for s in su.qp_solvers):
            qp_choice = su.choose_solver(model, qp=True)
            assert su.interface_to_str(qp_choice) in su.qp_solvers
        else:
            with pytest.raises(su.SolverNotFound):
                su.choose_solver(model, qp=True)
Example #4
0
    def optimize(self, objective_sense=None, raise_error=False, **kwargs):
        """
        Optimize the model using flux balance analysis.

        Parameters
        ----------
        objective_sense : {None, 'maximize' 'minimize'}, optional
            Whether fluxes should be maximized or minimized. In case of None,
            the previous direction is used.
        raise_error : bool
            If true, raise an OptimizationError if solver status is not
             optimal.
        solver : {None, 'glpk', 'cglpk', 'gurobi', 'cplex'}, optional
            If unspecified will use the currently defined `self.solver`
            otherwise it will use the given solver and update the attribute.
        quadratic_component : {None, scipy.sparse.dok_matrix}, optional
            The dimensions should be (n, n) where n is the number of
            reactions. This sets the quadratic component (Q) of the
            objective coefficient, adding :math:`\\frac{1}{2} v^T \cdot Q
            \cdot v` to the objective. Ignored for optlang based solvers.
        tolerance_feasibility : float
            Solver tolerance for feasibility. Ignored for optlang based
            solvers
        tolerance_markowitz : float
            Solver threshold during pivot. Ignored for optlang based solvers
        time_limit : float
            Maximum solver time (in seconds). Ignored for optlang based solvers

        Notes
        -----
        Only the most commonly used parameters are presented here.  Additional
        parameters for cobra.solvers may be available and specified with the
        appropriate keyword argument.

        """
        legacy, solver = choose_solver(self, solver=kwargs.get("solver"))
        original_direction = self.objective.direction

        if legacy:
            if objective_sense is None:
                objective_sense = {
                    "max": "maximize",
                    "min": "minimize"
                }[original_direction]
            solution = optimize(self,
                                objective_sense=objective_sense,
                                **kwargs)
            check_solver_status(solution.status, raise_error=raise_error)
            return solution

        self.solver = solver
        self.objective.direction = \
            {"maximize": "max", "minimize": "min"}.get(
                objective_sense, original_direction)
        self.slim_optimize()
        solution = get_solution(self, raise_error=raise_error)
        self.objective.direction = original_direction
        return solution
    def test_choose_solver(self, model):
        legacy, so = su.choose_solver(model)
        assert not legacy
        assert su.interface_to_str(so) == "glpk"
        legacy, so = su.choose_solver(model, "optlang-glpk")
        assert not legacy
        assert su.interface_to_str(so) == "glpk"
        assert su.choose_solver(model, "cglpk")[0]

        if any(s in su.solvers for s in su.qp_solvers):
            legacy, qp_choice = su.choose_solver(model, qp=True)
            assert not legacy
            assert su.interface_to_str(qp_choice) in su.qp_solvers
        else:
            with pytest.raises(su.SolverNotFound):
                su.choose_solver(model, qp=True)
def add_moma(model0, solution=None, linear=False, runcopy=False):
    r"""Add constraints and objective representing for MOMA.

    This adds variables and constraints for the minimization of metabolic
    adjustment (MOMA) to the model.

    Parameters
    ----------
    model : cobra.Model
        The model to add MOMA constraints and objective to.
    solution : cobra.Solution
        A previous solution to use as a reference.
    linear : bool
        Whether to use the linear MOMA formulation or not.

    Returns
    -------
    Nothing.

    Notes
    -----
    In the original MOMA specification one looks for the flux distribution
    of the deletion (v^d) closest to the fluxes without the deletion (v).
    In math this means:

    minimize \sum_i (v^d_i - v_i)^2
    s.t. Sv^d = 0
         lb_i <= v^d_i <= ub_i

    Here, we use a variable transformation v^t := v^d_i - v_i. Substituting
    and using the fact that Sv = 0 gives:

    minimize \sum_i (v^t_i)^2
    s.t. Sv^d = 0
         v^t = v^d_i - v_i
         lb_i <= v^d_i <= ub_i

    So basically we just re-center the flux space at the old solution and than
    find the flux distribution closest to the new zero (center). This is the
    same strategy as used in cameo.

    In the case of linear MOMA, we instead minimize \sum_i abs(v^t_i). The
    linear MOMA is typically significantly faster. Also quadratic MOMA tends
    to give flux distributions in which all fluxes deviate from the reference
    fluxes a little bit whereas linear MOMA tends to give flux distributions
    where the majority of fluxes are the same reference which few fluxes
    deviating a lot (typical effect of L2 norm vs L1 norm).

    The former objective function is saved in the optlang solver interface as
    "moma_old_objective" and this can be used to immediately extract the value
    of the former objective after MOMA optimization.
    """
    if runcopy:
        model = model0.copy()
    else:
        model = model0
    if 'moma_old_objective' in model.solver.variables:
        raise ValueError('model is already adjusted for MOMA')

    # Fall back to default QP solver if current one has no QP capability
    if not linear:
        model.solver = sutil.choose_solver(model, qp=True)[1]

    if solution is None:
        solution = model.optimize()
    prob = model.problem
    v = prob.Variable("moma_old_objective")
    c = prob.Constraint(model.solver.objective.expression - v,
                        lb=0.0,
                        ub=0.0,
                        name="moma_old_objective_constraint")
    to_add = [v, c]
    new_obj = S.Zero
    for r in model.reactions:
        flux = solution.fluxes[r.id]
        if linear:
            components = sutil.add_absolute_expression(model,
                                                       r.flux_expression,
                                                       name="moma_dist_" +
                                                       r.id,
                                                       difference=flux,
                                                       add=False)
            to_add.extend(components)
            new_obj += components.variable
        else:
            dist = prob.Variable("moma_dist_" + r.id)
            const = prob.Constraint(r.flux_expression - dist,
                                    lb=flux,
                                    ub=flux,
                                    name="moma_constraint_" + r.id)
            to_add.extend([dist, const])
            new_obj += dist**2
    model.add_cons_vars(to_add)
    model.objective = prob.Objective(new_obj, direction='min')
    return model
Example #7
0
def pfba(model,
         already_irreversible=False,
         fraction_of_optimum=1.0,
         solver=None,
         desired_objective_value=None,
         objective=None,
         reactions=None,
         **optimize_kwargs):
    """Perform basic pFBA (parsimonious Enzyme Usage Flux Balance Analysis)
    to minimize total flux.

    pFBA [1] adds the minimization of all fluxes the the objective of the
    model. This approach is motivated by the idea that high fluxes have a
    higher enzyme turn-over and that since producing enzymes is costly,
    the cell will try to minimize overall flux while still maximizing the
    original objective function, e.g. the growth rate.

    Parameters
    ----------
    model : cobra.Model
        The model
    already_irreversible : bool, optional
        By default, the model is converted to an irreversible one.
        However, if the model is already irreversible, this step can be
        skipped. Ignored for optlang solvers as not relevant.
    fraction_of_optimum : float, optional
        Fraction of optimum which must be maintained. The original objective
        reaction is constrained to be greater than maximal_value *
        fraction_of_optimum.
    solver : str, optional
        Name of the solver to be used. If None it will respect the solver set
        in the model (model.solver).
    desired_objective_value : float, optional
        A desired objective value for the minimal solution that bypasses the
        initial optimization result. Ignored for optlang solvers, instead,
        define your objective separately and pass using the `objective`
        argument.
    objective : dict or model.problem.Objective
        A desired objective to use during optimization in addition to the
        pFBA objective. Dictionaries (reaction as key, coefficient as value)
        can be used for linear objectives. Not used for non-optlang solvers.
    reactions : iterable
        List of reactions or reaction identifiers. Implies `return_frame` to
        be true. Only return fluxes for the given reactions. Faster than
        fetching all fluxes if only a few are needed. Only supported for
        optlang solvers.
    **optimize_kwargs : additional arguments for legacy solver, optional
        Additional arguments passed to the legacy solver. Ignored for
        optlang solver (those can be configured using
         model.solver.configuration).

    Returns
    -------
    cobra.Solution
        The solution object to the optimized model with pFBA constraints added.

    References
    ----------
    .. [1] Lewis, N. E., Hixson, K. K., Conrad, T. M., Lerman, J. A.,
       Charusanti, P., Polpitiya, A. D., Palsson, B. O. (2010). Omic data
       from evolved E. coli are consistent with computed optimal growth from
       genome-scale models. Molecular Systems Biology, 6,
       390. doi:10.1038/msb.2010.47

    """
    legacy, solver = sutil.choose_solver(model, solver)
    if legacy:
        return _pfba_legacy(model,
                            already_irreversible=already_irreversible,
                            fraction_of_optimum=fraction_of_optimum,
                            solver=solver,
                            desired_objective_value=desired_objective_value,
                            **optimize_kwargs)
    else:
        model.solver = solver
        return _pfba_optlang(model,
                             objective=objective,
                             fraction_of_optimum=fraction_of_optimum,
                             reactions=reactions)
Example #8
0
def add_moma(model, solution=None, linear=True):
    r"""Add constraints and objective representing for MOMA.

    This adds variables and constraints for the minimization of metabolic
    adjustment (MOMA) to the model.

    Parameters
    ----------
    model : cobra.Model
        The model to add MOMA constraints and objective to.
    solution : cobra.Solution, optional
        A previous solution to use as a reference. If no solution is given,
        one will be computed using pFBA.
    linear : bool, optional
        Whether to use the linear MOMA formulation or not (default True).

    Notes
    -----
    In the original MOMA [1]_ specification one looks for the flux distribution
    of the deletion (v^d) closest to the fluxes without the deletion (v).
    In math this means:

    minimize \sum_i (v^d_i - v_i)^2
    s.t. Sv^d = 0
         lb_i <= v^d_i <= ub_i

    Here, we use a variable transformation v^t := v^d_i - v_i. Substituting
    and using the fact that Sv = 0 gives:

    minimize \sum_i (v^t_i)^2
    s.t. Sv^d = 0
         v^t = v^d_i - v_i
         lb_i <= v^d_i <= ub_i

    So basically we just re-center the flux space at the old solution and then
    find the flux distribution closest to the new zero (center). This is the
    same strategy as used in cameo.

    In the case of linear MOMA [2]_, we instead minimize \sum_i abs(v^t_i). The
    linear MOMA is typically significantly faster. Also quadratic MOMA tends
    to give flux distributions in which all fluxes deviate from the reference
    fluxes a little bit whereas linear MOMA tends to give flux distributions
    where the majority of fluxes are the same reference with few fluxes
    deviating a lot (typical effect of L2 norm vs L1 norm).

    The former objective function is saved in the optlang solver interface as
    ``"moma_old_objective"`` and this can be used to immediately extract the
    value of the former objective after MOMA optimization.

    See Also
    --------
    pfba : parsimonious FBA

    References
    ----------
    .. [1] Segrè, Daniel, Dennis Vitkup, and George M. Church. “Analysis of
           Optimality in Natural and Perturbed Metabolic Networks.”
           Proceedings of the National Academy of Sciences 99, no. 23
           (November 12, 2002): 15112. https://doi.org/10.1073/pnas.232349399.
    .. [2] Becker, Scott A, Adam M Feist, Monica L Mo, Gregory Hannum,
           Bernhard Ø Palsson, and Markus J Herrgard. “Quantitative
           Prediction of Cellular Metabolism with Constraint-Based Models:
           The COBRA Toolbox.” Nature Protocols 2 (March 29, 2007): 727.
    """
    if 'moma_old_objective' in model.solver.variables:
        raise ValueError('model is already adjusted for MOMA')

    # Fall back to default QP solver if current one has no QP capability
    if not linear:
        model.solver = sutil.choose_solver(model, qp=True)

    if solution is None:
        solution = pfba(model)
    prob = model.problem
    v = prob.Variable("moma_old_objective")
    c = prob.Constraint(model.solver.objective.expression - v,
                        lb=0.0,
                        ub=0.0,
                        name="moma_old_objective_constraint")
    to_add = [v, c]
    model.objective = prob.Objective(Zero, direction="min", sloppy=True)
    obj_vars = []
    for r in model.reactions:
        flux = solution.fluxes[r.id]
        if linear:
            components = sutil.add_absolute_expression(model,
                                                       r.flux_expression,
                                                       name="moma_dist_" +
                                                       r.id,
                                                       difference=flux,
                                                       add=False)
            to_add.extend(components)
            obj_vars.append(components.variable)
        else:
            dist = prob.Variable("moma_dist_" + r.id)
            const = prob.Constraint(r.flux_expression - dist,
                                    lb=flux,
                                    ub=flux,
                                    name="moma_constraint_" + r.id)
            to_add.extend([dist, const])
            obj_vars.append(dist**2)
    model.add_cons_vars(to_add)
    if linear:
        model.objective.set_linear_coefficients({v: 1.0 for v in obj_vars})
    else:
        model.objective = prob.Objective(add(obj_vars),
                                         direction="min",
                                         sloppy=True)
Example #9
0
def add_moma(model, solution=None, linear=True):
    r"""Add constraints and objective representing for MOMA.

    This adds variables and constraints for the minimization of metabolic
    adjustment (MOMA) to the model.

    Parameters
    ----------
    model : cobra.Model
        The model to add MOMA constraints and objective to.
    solution : cobra.Solution, optional
        A previous solution to use as a reference. If no solution is given,
        one will be computed using pFBA.
    linear : bool, optional
        Whether to use the linear MOMA formulation or not (default True).

    Notes
    -----
    In the original MOMA [1]_ specification one looks for the flux distribution
    of the deletion (v^d) closest to the fluxes without the deletion (v).
    In math this means:

    minimize \sum_i (v^d_i - v_i)^2
    s.t. Sv^d = 0
         lb_i <= v^d_i <= ub_i

    Here, we use a variable transformation v^t := v^d_i - v_i. Substituting
    and using the fact that Sv = 0 gives:

    minimize \sum_i (v^t_i)^2
    s.t. Sv^d = 0
         v^t = v^d_i - v_i
         lb_i <= v^d_i <= ub_i

    So basically we just re-center the flux space at the old solution and then
    find the flux distribution closest to the new zero (center). This is the
    same strategy as used in cameo.

    In the case of linear MOMA [2]_, we instead minimize \sum_i abs(v^t_i). The
    linear MOMA is typically significantly faster. Also quadratic MOMA tends
    to give flux distributions in which all fluxes deviate from the reference
    fluxes a little bit whereas linear MOMA tends to give flux distributions
    where the majority of fluxes are the same reference with few fluxes
    deviating a lot (typical effect of L2 norm vs L1 norm).

    The former objective function is saved in the optlang solver interface as
    ``"moma_old_objective"`` and this can be used to immediately extract the
    value of the former objective after MOMA optimization.

    See Also
    --------
    pfba : parsimonious FBA

    References
    ----------
    .. [1] Segrè, Daniel, Dennis Vitkup, and George M. Church. “Analysis of
           Optimality in Natural and Perturbed Metabolic Networks.”
           Proceedings of the National Academy of Sciences 99, no. 23
           (November 12, 2002): 15112. https://doi.org/10.1073/pnas.232349399.
    .. [2] Becker, Scott A, Adam M Feist, Monica L Mo, Gregory Hannum,
           Bernhard Ø Palsson, and Markus J Herrgard. “Quantitative
           Prediction of Cellular Metabolism with Constraint-Based Models:
           The COBRA Toolbox.” Nature Protocols 2 (March 29, 2007): 727.
    """
    if 'moma_old_objective' in model.solver.variables:
        raise ValueError('model is already adjusted for MOMA')

    # Fall back to default QP solver if current one has no QP capability
    if not linear:
        model.solver = sutil.choose_solver(model, qp=True)

    if solution is None:
        solution = pfba(model)
    prob = model.problem
    v = prob.Variable("moma_old_objective")
    c = prob.Constraint(model.solver.objective.expression - v,
                        lb=0.0, ub=0.0, name="moma_old_objective_constraint")
    to_add = [v, c]
    model.objective = prob.Objective(Zero, direction="min", sloppy=True)
    obj_vars = []
    for r in model.reactions:
        flux = solution.fluxes[r.id]
        if linear:
            components = sutil.add_absolute_expression(
                model, r.flux_expression, name="moma_dist_" + r.id,
                difference=flux, add=False)
            to_add.extend(components)
            obj_vars.append(components.variable)
        else:
            dist = prob.Variable("moma_dist_" + r.id)
            const = prob.Constraint(r.flux_expression - dist, lb=flux, ub=flux,
                                    name="moma_constraint_" + r.id)
            to_add.extend([dist, const])
            obj_vars.append(dist ** 2)
    model.add_cons_vars(to_add)
    if linear:
        model.objective.set_linear_coefficients({v: 1.0 for v in obj_vars})
    else:
        model.objective = prob.Objective(
            add(obj_vars), direction="min", sloppy=True)
Example #10
0
def model_summary(model, threshold=1E-8, fva=None, floatfmt='.3g',
                  **solver_args):
    """Print a summary of the input and output fluxes of the model.

    threshold: float
        tolerance for determining if a flux is zero (not printed)

    fva: int or None
        Whether or not to calculate and report flux variability in the
        output summary

    floatfmt: string
        format method for floats, passed to tabulate. Default is '.3g'.

    """
    legacy, _ = choose_solver(model, solver=solver_args.get("solver"))
    if legacy:
        raise NotImplementedError(
            "Summary support for legacy solvers was removed.")

    # Create a dataframe of objective fluxes
    objective_reactions = linear_reaction_coefficients(model)
    obj_fluxes = pd.DataFrame({key: key.flux * value for key, value in
                               iteritems(objective_reactions)},
                              index=['flux']).T
    obj_fluxes['id'] = obj_fluxes.apply(
        lambda x: format_long_string(x.name.id, 15), 1)

    # Build a dictionary of metabolite production from the boundary reactions
    # collect rxn.flux before fva which invalidates previous solver state
    boundary_reactions = model.exchanges
    metabolite_fluxes = {}
    for rxn in boundary_reactions:
        for met, stoich in iteritems(rxn.metabolites):
            metabolite_fluxes[met] = {
                'id': format_long_string(met.id, 15),
                'flux': stoich * rxn.flux}

    # Calculate FVA results if requested
    if fva:
        fva_results = flux_variability_analysis(
            model, reaction_list=boundary_reactions, fraction_of_optimum=fva,
            **solver_args)

        for rxn in boundary_reactions:
            for met, stoich in iteritems(rxn.metabolites):
                imin = stoich * fva_results.loc[rxn.id]['minimum']
                imax = stoich * fva_results.loc[rxn.id]['maximum']
                # Correct 'max' and 'min' for negative values
                metabolite_fluxes[met].update({
                    'fmin': imin if abs(imin) <= abs(imax) else imax,
                    'fmax': imax if abs(imin) <= abs(imax) else imin,
                })

    # Generate a dataframe of boundary fluxes
    metabolite_fluxes = pd.DataFrame(metabolite_fluxes).T
    metabolite_fluxes = _process_flux_dataframe(
        metabolite_fluxes, fva, threshold, floatfmt)

    # Begin building string output table
    def get_str_table(species_df, fva=False):
        """Formats a string table for each column"""

        if not fva:
            return tabulate(species_df.loc[:, ['id', 'flux']].values,
                            floatfmt=floatfmt, tablefmt='plain').split('\n')

        else:
            return tabulate(
                species_df.loc[:, ['id', 'flux', 'fva_fmt']].values,
                floatfmt=floatfmt, tablefmt='simple',
                headers=['id', 'Flux', 'Range']).split('\n')

    in_table = get_str_table(
        metabolite_fluxes[metabolite_fluxes.is_input], fva=fva)
    out_table = get_str_table(
        metabolite_fluxes[~metabolite_fluxes.is_input], fva=fva)
    obj_table = get_str_table(obj_fluxes, fva=False)

    # Print nested output table
    print_(tabulate(
        [entries for entries in zip_longest(in_table, out_table, obj_table)],
        headers=['IN FLUXES', 'OUT FLUXES', 'OBJECTIVES'], tablefmt='simple'))
Example #11
0
def production_envelope(model,
                        reactions,
                        objective=None,
                        c_source=None,
                        points=20,
                        solver=None):
    """Calculate the objective value conditioned on all combinations of
    fluxes for a set of chosen reactions

    The production envelope can be used to analyze a models ability to
    produce a given compound conditional on the fluxes for another set of
    reaction, such as the uptake rates. The model is alternately optimize
    with respect to minimizing and maximizing the objective and record the
    obtained fluxes. Ranges to compute production is set to the effective
    bounds, i.e. the minimum / maximum fluxes that can be obtained given
    current reaction bounds.

    Parameters
    ----------
    model : cobra.Model
        The model to compute the production envelope for.
    reactions : list or string
        A list of reactions, reaction identifiers or single reaction
    objective : string, dict, model.solver.interface.Objective
        The objective (reaction) to use for the production envelope. Use the
        model's current objective is left missing.
    c_source : cobra.Reaction or string
       A reaction or reaction identifier that is the source of carbon for
       computing carbon (mol carbon in output over mol carbon in input) and
       mass yield (gram product over gram output). Only objectives with a
       carbon containing input and output metabolite is supported.
    points : int
       The number of points to calculate production for.
    solver : string
       The solver to use - only here for consistency with older
       implementations (this argument will be removed in the future). The
       solver should be set using `model.solver` directly. Only optlang
       based solvers are supported.

    Returns
    -------
    pandas.DataFrame
        A data frame with one row per evaluated point and

        - reaction id : one column per input reaction indicating the flux at
          each given point,
        - flux: the objective flux

        - carbon_yield: if carbon source is defined and the product is a
          single metabolite (mol carbon product per mol carbon feeding source)

        - _mass_yield: if carbon source is defined and the product is a
          single metabolite (gram product per 1 g of feeding source)

        - direction: the direction of the optimization.

        Only points that give a valid solution are returned.

    Examples
    --------
    >>> import cobra.test
    >>> from cobra.flux_analysis import production_envelope
    >>> model = cobra.test.create_test_model("textbook")
    >>> production_envelope(model, ["EX_glc__D_e", "EX_o2_e"])
    """
    legacy, solver = sutil.choose_solver(model, solver)
    if legacy:
        raise ValueError('production_envelope is only implemented for optlang '
                         'based solver interfaces.')
    reactions = model.reactions.get_by_any(reactions)
    objective = model.solver.objective if objective is None else objective
    with model:
        model.objective = objective
        carbon_io = _c_input_output(model, c_source)
        min_max = fva(model, reactions, fraction_of_optimum=0)
        grid = [
            linspace(min_max.minimum[rxn.id],
                     min_max.maximum[rxn.id],
                     points,
                     endpoint=True) for rxn in reactions
        ]
        grid_list = list(product(*grid))
        result = _envelope_for_points(model, reactions, grid_list, carbon_io)

    return pd.DataFrame(result)
Example #12
0
def add_moma(model, solution=None, linear=False):
    r"""Add constraints and objective representing for MOMA.

    This adds variables and constraints for the minimization of metabolic
    adjustment (MOMA) to the model.

    Parameters
    ----------
    model : cobra.Model
        The model to add MOMA constraints and objective to.
    solution : cobra.Solution
        A previous solution to use as a reference.
    linear : bool
        Whether to use the linear MOMA formulation or not.

    Returns
    -------
    Nothing.

    Notes
    -----
    In the original MOMA specification one looks for the flux distribution
    of the deletion (v^d) closest to the fluxes without the deletion (v).
    In math this means:

    minimize \sum_i (v^d_i - v_i)^2
    s.t. Sv^d = 0
         lb_i <= v^d_i <= ub_i

    Here, we use a variable transformation v^t := v^d_i - v_i. Substituting
    and using the fact that Sv = 0 gives:

    minimize \sum_i (v^t_i)^2
    s.t. Sv^d = 0
         v^t = v^d_i - v_i
         lb_i <= v^d_i <= ub_i

    So basically we just re-center the flux space at the old solution and than
    find the flux distribution closest to the new zero (center). This is the
    same strategy as used in cameo.

    In the case of linear MOMA, we instead minimize \sum_i abs(v^t_i). The
    linear MOMA is typically significantly faster. Also quadratic MOMA tends
    to give flux distributions in which all fluxes deviate from the reference
    fluxes a little bit whereas linear MOMA tends to give flux distributions
    where the majority of fluxes are the same reference which few fluxes
    deviating a lot (typical effect of L2 norm vs L1 norm).

    The former objective function is saved in the optlang solver interface as
    "moma_old_objective" and this can be used to immediately extract the value
    of the former objective after MOMA optimization.
    """
    if 'moma_old_objective' in model.solver.variables:
        raise ValueError('model is already adjusted for MOMA')

    # Fall back to default QP solver if current one has no QP capability
    if not linear:
        model.solver = sutil.choose_solver(model, qp=True)

    if solution is None:
        solution = model.optimize()
    prob = model.problem
    v = prob.Variable("moma_old_objective")
    c = prob.Constraint(model.solver.objective.expression - v,
                        lb=0.0, ub=0.0, name="moma_old_objective_constraint")
    to_add = [v, c]
    new_obj = Zero
    for r in model.reactions:
        flux = solution.fluxes[r.id]
        if linear:
            components = sutil.add_absolute_expression(
                model, r.flux_expression, name="moma_dist_" + r.id,
                difference=flux, add=False)
            to_add.extend(components)
            new_obj += components.variable
        else:
            dist = prob.Variable("moma_dist_" + r.id)
            const = prob.Constraint(r.flux_expression - dist, lb=flux, ub=flux,
                                    name="moma_constraint_" + r.id)
            to_add.extend([dist, const])
            new_obj += dist**2
    model.add_cons_vars(to_add)
    model.objective = prob.Objective(new_obj, direction='min')
Example #13
0
def find_blocked_reactions(model,
                           reaction_list=None,
                           solver=None,
                           zero_cutoff=1e-9,
                           open_exchanges=False,
                           **solver_args):
    """Finds reactions that cannot carry a flux with the current
    exchange reaction settings for a cobra model, using flux variability
    analysis.

    Parameters
    ----------
    model : cobra.Model
        The model to analyze
    reaction_list : list
        List of reactions to consider, use all if left missing
    solver : string
        The name of the solver to use
    zero_cutoff : float
        Flux value which is considered to effectively be zero.
    open_exchanges : bool
        If true, set bounds on exchange reactions to very high values to
        avoid that being the bottle-neck.
    **solver_args :
        Additional arguments to the solver. Ignored for optlang based solvers.

    Returns
    -------
    list
        List with the blocked reactions
    """
    legacy, solver_interface = sutil.choose_solver(model, solver)
    with model:
        if open_exchanges:
            for reaction in model.exchanges:
                reaction.bounds = (min(reaction.lower_bound,
                                       -1000), max(reaction.upper_bound, 1000))
        if reaction_list is None:
            reaction_list = model.reactions
        # limit to reactions which are already 0. If the reactions already
        # carry flux in this solution, then they can not be blocked.
        if legacy:
            solution = solver_interface.solve(model, **solver_args)
            reaction_list = [
                i for i in reaction_list
                if abs(solution.x_dict[i.id]) < zero_cutoff
            ]
        else:
            model.solver = solver_interface
            model.solver.optimize()
            solution = get_solution(model, reactions=reaction_list)
            reaction_list = [
                rxn for rxn in reaction_list
                if abs(solution.fluxes[rxn.id]) < zero_cutoff
            ]
        # run fva to find reactions where both max and min are 0
        flux_span = flux_variability_analysis(model,
                                              fraction_of_optimum=0.,
                                              reaction_list=reaction_list,
                                              solver=solver,
                                              **solver_args)
        return [
            rxn_id for rxn_id, min_max in flux_span.iterrows()
            if max(abs(min_max)) < zero_cutoff
        ]
Example #14
0
def flux_variability_analysis(model,
                              reaction_list=None,
                              loopless=False,
                              fraction_of_optimum=1.0,
                              solver=None,
                              **solver_args):
    """Runs flux variability analysis to find the min/max flux values for each
    each reaction in `reaction_list`.

    Parameters
    ----------
    model : a 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.
    loopless : boolean, optional
        Whether to return only loopless solutions. Ignored for legacy solvers,
        also see `Notes`.
    fraction_of_optimum : 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 95% percent of its maximum.
    solver : str, optional
        Name of the solver to be used. If None it will respect the solver set
        in the model (model.solver).
    **solver_args : additional arguments for legacy solver, optional
        Additional arguments passed to the legacy solver. Ignored for
        optlang solver (those can be configured using
        model.solver.configuration).

    Returns
    -------
    pandas.DataFrame
        DataFrame with reaction identifier as the index 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.
    """
    legacy, solver = sutil.choose_solver(model, solver)

    if reaction_list is None:
        reaction_list = model.reactions

    if not legacy:
        fva_result = _fva_optlang(model, reaction_list, fraction_of_optimum,
                                  loopless)
    else:
        fva_result = _fva_legacy(model, reaction_list, fraction_of_optimum,
                                 "maximize", solver, **solver_args)
    return pandas.DataFrame(fva_result).T
Example #15
0
def add_moma(model):
    """Add constraints and objective representing a MOMA
    (minimization of metabolic adjustment) model.

    Parameters:
    -----------

    model : a cobra model

    Returns:
    --------
    Nothing.

    Notes
    -----

    In the original MOMA specification one looks for the flux distribution
    of the deletion (v^d) closest to the fluxes without the deletion (v).
    In math this means:

    minimize \sum_i (v^d_i - v_i)^2
    s.t. Sv^d = 0
         lb_i <= v^d_i <= ub_i

    Here, we use a variable transformation v^t := v^d_i - v_i. Substituting
    and using the fact that Sv = 0 gives:

    minimize \sum (v^t_i)^2
    s.t. Sv^d = 0
         v^t = v^d_i - v_i
         lb_i <= v^d_i <= ub_i

    So basically we just re-center the flux space at the old solution and than
    find the flux distribution closest to the new zero (center). This is the
    same strategy as used in cameo.

    The former objective function is saved in the optlang solver interface as
    "moma_old_objective" and this can be used to immediately extract the value
    of the former objective after MOMA optimization.
    """
    if 'moma_old_objective' in model.solver.variables:
        raise ValueError('model is already adjusted for MOMA')

    # Fall back to default QP solver if current one has no QP capability
    model.solver = sutil.choose_solver(model, qp=True)[1]

    solution = model.optimize()
    prob = model.problem
    v = prob.Variable("moma_old_objective")
    c = prob.Constraint(model.solver.objective.expression - v,
                        lb=0.0,
                        ub=0.0,
                        name="moma_old_objective_constraint")
    to_add = [v, c]
    new_obj = S.Zero
    for r in model.reactions:
        flux = solution.fluxes[r.id]
        dist = prob.Variable("moma_dist_" + r.id)
        const = prob.Constraint(r.flux_expression - dist,
                                lb=flux,
                                ub=flux,
                                name="moma_constraint_" + r.id)
        to_add.extend([dist, const])
        new_obj += dist**2
    model.add_cons_vars(to_add)
    model.objective = prob.Objective(new_obj, direction='min')