Exemplo n.º 1
0
def cooperative_tradeoff(community, min_growth, fraction, fluxes, pfba):
    """Find the best tradeoff between community and individual growth."""
    with community as com:
        check_modification(community)
        min_growth = _format_min_growth(min_growth, community.species)
        _apply_min_growth(community, min_growth)

        com.objective = 1000.0 * com.variables.community_objective
        min_growth = optimize_with_retry(
            com, message="could not get community growth rate.") / 1000.0

        if not isinstance(fraction, Sized):
            fraction = [fraction]
        else:
            fraction = np.sort(fraction)[::-1]

        # Add needed variables etc.
        regularize_l2_norm(com, 0.0)
        results = []
        for fr in fraction:
            com.variables.community_objective.lb = fr * min_growth
            com.variables.community_objective.ub = min_growth
            sol = solve(community, fluxes=fluxes, pfba=pfba)
            if sol.status != OPTIMAL:
                sol = crossover(com, sol, fluxes=fluxes, pfba=pfba)
            results.append((fr, sol))
        if len(results) == 1:
            return results[0][1]
        return pd.DataFrame.from_records(results,
                                         columns=["tradeoff", "solution"])
Exemplo n.º 2
0
def add_pfba_objective(community, atol=1e-6, rtol=1e-6):
    """Add pFBA objective.

    Add objective to minimize the summed flux of all reactions to the
    current objective. This one will work with any objective (even non-linear
    ones).

    See Also
    --------
    pfba

    Parameters
    ----------
    community : micom.Community
        The community to add the objective to.
    """
    # Fix all growth rates
    rates = {
        sp: community.constraints["objective_" + sp].primal
        for sp in community.taxa
    }
    _apply_min_growth(community, rates, atol, rtol)

    if community.solver.objective.name == "_pfba_objective":
        raise ValueError("model already has pfba objective")
    reaction_variables = ((rxn.forward_variable, rxn.reverse_variable)
                          for rxn in community.reactions)
    variables = chain(*reaction_variables)
    community.objective = Zero
    community.objective_direction = "min"
    community.objective.set_linear_coefficients(dict.fromkeys(variables, 1.0))
    if community.modification is None:
        community.modification = "pFBA"
    else:
        community.modification += " and pFBA"
Exemplo n.º 3
0
def cooperative_tradeoff(community, min_growth, fraction, fluxes, pfba):
    """Find the best tradeoff between community and individual growth."""
    with community as com:
        solver = interface_to_str(community.problem)
        check_modification(community)
        min_growth = _format_min_growth(min_growth, community.taxa)
        _apply_min_growth(community, min_growth)

        com.objective = com.scale * com.variables.community_objective
        min_growth = (optimize_with_retry(
            com, message="could not get community growth rate.") / com.scale)
        if not isinstance(fraction, Sized):
            fraction = [fraction]
        else:
            fraction = np.sort(fraction)[::-1]

        # Add needed variables etc.
        regularize_l2_norm(com, 0.0)
        results = []
        for fr in fraction:
            com.variables.community_objective.lb = fr * min_growth
            com.variables.community_objective.ub = min_growth
            sol = solve(community, fluxes=fluxes, pfba=pfba)
            # OSQP is better with QPs then LPs
            # so it won't get better with the crossover
            if (sol.status != OPTIMAL and solver != "osqp"):
                sol = crossover(com, sol, fluxes=fluxes, pfba=pfba)
            results.append((fr, sol))
        if len(results) == 1:
            return results[0][1]
        return pd.DataFrame.from_records(results,
                                         columns=["tradeoff", "solution"])
Exemplo n.º 4
0
def knockout_taxa(
    community, taxa, fraction, method, progress, diag=True
):
    """Knockout a taxon from the community."""
    with community as com:
        check_modification(com)
        min_growth = _format_min_growth(0.0, com.taxa)
        _apply_min_growth(com, min_growth)

        com.objective = com.scale * com.variables.community_objective
        community_min_growth = (
            optimize_with_retry(com, "could not get community growth rate.")
            / com.scale
        )
        regularize_l2_norm(com, fraction * community_min_growth)
        old = com.optimize().members["growth_rate"]
        results = []

        iter = track(taxa, description="Knockouts") if progress else taxa
        for sp in iter:
            with com:
                logger.info("getting growth rates for " "%s knockout." % sp)
                [
                    r.knock_out()
                    for r in com.reactions.query(
                        lambda ri: ri.community_id == sp
                    )
                ]

                sol = optimize_with_fraction(com, fraction)
                new = sol.members["growth_rate"]
                if "change" in method:
                    new = new - old
                if "relative" in method:
                    new /= old
                results.append(new)

        ko = pd.DataFrame(results, index=taxa).drop("medium", 1)
        ko = ko.loc[ko.index.sort_values(), ko.columns.sort_values()]
        if not diag:
            np.fill_diagonal(ko.values, np.NaN)

        return ko
Exemplo n.º 5
0
def knockout_species(community,
                     species,
                     fraction,
                     method,
                     progress,
                     diag=True):
    """Knockout a species from the community."""
    with community as com:
        check_modification(com)
        min_growth = _format_min_growth(0.0, com.species)
        _apply_min_growth(com, min_growth)

        com.objective = 1000.0 * com.variables.community_objective
        community_min_growth = (
            optimize_with_retry(com, "could not get community growth rate.") /
            1000.0)
        regularize_l2_norm(com, fraction * community_min_growth)
        old = com.optimize().members["growth_rate"]
        results = []

        if progress:
            species = tqdm(species, unit="knockout(s)")
        for sp in species:
            with com:
                logger.info("getting growth rates for " "%s knockout." % sp)
                [
                    r.knock_out() for r in com.reactions.query(
                        lambda ri: ri.community_id == sp)
                ]

                sol = optimize_with_fraction(com, fraction)
                new = sol.members["growth_rate"]
                if "change" in method:
                    new = new - old
                if "relative" in method:
                    new /= old
                results.append(new)

        ko = pd.DataFrame(results, index=species).drop("medium", 1)
        if not diag:
            np.fill_diagonal(ko.values, np.NaN)

        return ko
Exemplo n.º 6
0
def minimal_medium(
    community,
    community_growth,
    min_growth=0.0,
    exports=False,
    exchanges=None,
    minimize_components=False,
    open_exchanges=False,
    solution=False,
    weights=None,
    atol=None,
    rtol=None,
):
    """Find the minimal growth medium for the community.

    Finds the minimal growth medium for the community which allows for
    community as well as individual growth. Here, a minimal medium can either
    be the medium requiring the smallest total import flux or the medium
    requiring the least components (ergo ingredients).

    Arguments
    ---------
    community : micom.Community
        The community to modify.
    community_growth : positive float
        The minimum community-wide growth rate.
    min_growth : positive float or array-like object.
        The minimum growth rate for each individual in the community. Either
        a single value applied to all individuals or one value for each.
    exports : boolean
        Whether to include export fluxes in the returned medium. Defaults to
        False which will only return import fluxes.
    exchanges : list of cobra.Reactions
        The list of exchange reactions that are penalized.
    minimize_components : boolean
        Whether to minimize the number of components instead of the total
        import flux. Might be more intuitive if set to True but may also be
        slow to calculate for large communities.
    open_exchanges : boolean or number
        Whether to ignore currently set bounds and make all exchange reactions
        in the model possible. If set to a number all exchange reactions will
        be opened with (-number, number) as bounds.
    solution : boolean
        Whether to also return the entire solution and all fluxes for the
        minimal medium.
    weights : str
        Will scale the fluxes by a weight factor. Can either be "mass" which will
        scale by molecular mass, a single element which will scale by
        the elemental content (for instance "C" to scale by carbon content).
        If None every metabolite will receive the same weight.
        Will be ignored if `minimize_components` is True.
    atol : float
        Absolute tolerance for the growth rates. If None will use the solver tolerance.
    rtol : float
        Relative tolerqance for the growth rates. If None will use the solver tolerance.


    Returns
    -------
    pandas.Series or dict
        A series {rid: flux} giving the import flux for each required import
        reaction. If `solution` is True retuns a dictionary
        {"medium": panas.Series, "solution": micom.CommunitySolution}.

    """
    logger.info("calculating minimal medium for %s" % community.id)

    if atol is None:
        atol = community.solver.configuration.tolerances.optimality
    if rtol is None:
        rtol = community.solver.configuration.tolerances.optimality

    if exchanges is None:
        boundary_rxns = community.exchanges
    else:
        boundary_rxns = community.reactions.get_by_any(exchanges)
    if isinstance(open_exchanges, bool):
        open_bound = 1000
    else:
        open_bound = open_exchanges
    min_growth = _format_min_growth(min_growth, community.taxa)
    with community as com:
        if open_exchanges:
            logger.info("opening exchanges for %d imports" %
                        len(boundary_rxns))
            for rxn in boundary_rxns:
                rxn.bounds = (-open_bound, open_bound)
        logger.info("applying growth rate constraints")
        _apply_min_growth(community, min_growth, atol, rtol)
        com.objective = Zero
        logger.info("adding new media objective")
        if minimize_components:
            add_mip_obj(com, boundary_rxns)
        else:
            scales = weight(boundary_rxns, weights)
            add_linear_obj(com, boundary_rxns, scales)
        sol = com.optimize(fluxes=True, pfba=False)
        if sol is None:
            logger.warning("minimization of medium was unsuccessful")
            return None

        logger.info("formatting medium")
        medium = pd.Series()
        ex = set(com.exchanges) & set(boundary_rxns)
        for rxn in ex:
            export = len(rxn.reactants) == 1
            flux = sol.fluxes.loc["medium", rxn.id]
            if abs(flux) < atol:
                continue
            if export:
                medium[rxn.id] = -flux
            elif not export:
                medium[rxn.id] = flux
        if not exports:
            medium = medium[medium > 0.0]

    if solution:
        return {"medium": medium, "solution": sol}
    else:
        return medium
Exemplo n.º 7
0
def add_moma_optcom(community, min_growth, linear=False):
    """Add a dualized MOMA version of OptCom.

    Solves a MOMA (minimization of metabolic adjustment) formulation of OptCom
    given by::

        minimize cooperativity_cost
        s.t. maximize community_objective
             s.t. Sv = 0
                  lb >= v >= ub
        where community_cost = sum (growth_rate - max_growth)**2
              if linear=False or
              community_cost = sum |growth_rate - max_growth|
              if linear=True

    Arguments
    ---------
    community : micom.Community
        The community to modify.
    min_growth : positive float or array-like object.
        The minimum growth rate for each individual in the community. Either
        a single value applied to all individuals or one value for each.
    linear : boolean
        Whether to use a non-linear (sum of squares) or linear version of the
        cooperativity cost. If set to False requires a QP-capable solver.

    """
    logger.info("adding dual %s moma to %s" %
                ("linear" if linear else "quadratic", community.id))
    check_modification(community)
    min_growth = _format_min_growth(min_growth, community.taxa)

    prob = community.solver.interface
    old_obj = community.objective
    coefs = old_obj.get_linear_coefficients(old_obj.variables)

    # Get maximum individual growth rates
    max_gcs = community.optimize_all(progress=False)

    _apply_min_growth(community, min_growth)
    dual_coefs = fast_dual(community)
    coefs.update({v: -coef for v, coef in dual_coefs.items()})
    obj_constraint = prob.Constraint(Zero,
                                     lb=0,
                                     ub=0,
                                     name="optcom_suboptimality")
    community.add_cons_vars([obj_constraint])
    community.solver.update()
    obj_constraint.set_linear_coefficients(coefs)
    obj_expr = Zero
    logger.info("adding expressions for %d taxa" % len(community.taxa))
    for sp in community.taxa:
        v = prob.Variable("gc_constant_" + sp, lb=max_gcs[sp], ub=max_gcs[sp])
        community.add_cons_vars([v])
        taxa_obj = community.constraints["objective_" + sp]
        ex = v - taxa_obj.expression
        if not linear:
            ex = ex**2
        obj_expr += ex.expand()
    community.objective = prob.Objective(obj_expr, direction="min")
    community.modification = "moma optcom"
    logger.info("finished dual moma to %s" % community.id)
Exemplo n.º 8
0
def add_dualized_optcom(community, min_growth):
    """Add dual Optcom variables and constraints to a community.

    Uses the original formulation of OptCom and solves the following
    multi-objective problem::

        maximize community_growth
        s.t. maximize growth_rate_i for all i
             s.t. Sv_i = 0
                  lb_i >= v_i >= ub_i

    Notes
    -----
    This method will only find one arbitrary solution from the Pareto front.
    There may exist several other optimal solutions.

    Arguments
    ---------
    community : micom.Community
        The community to modify.
    min_growth : positive float or array-like object.
        The minimum growth rate for each individual in the community. Either
        a single value applied to all individuals or one value for each.

    """
    logger.info("adding dual optcom to %s" % community.id)
    check_modification(community)
    min_growth = _format_min_growth(min_growth, community.taxa)

    prob = community.solver.interface

    # Temporarily subtitute objective with sum of individual objectives
    # for correct dual variables
    old_obj = community.objective
    community.objective = Zero
    for sp in community.taxa:
        taxa_obj = community.constraints["objective_" + sp]
        community.objective += taxa_obj.expression

    _apply_min_growth(community, min_growth)
    dual_coefs = fast_dual(community)

    logger.info("adding expressions for %d taxa" % len(community.taxa))
    for sp in community.taxa:
        primal_const = community.constraints["objective_" + sp]
        coefs = primal_const.get_linear_coefficients(primal_const.variables)
        coefs.update({
            dual_var: -coef
            for dual_var, coef in dual_coefs.items() if sp in dual_var.name
        })
        obj_constraint = prob.Constraint(Zero,
                                         lb=0,
                                         ub=0,
                                         name="optcom_suboptimality_" + sp)
        community.add_cons_vars([obj_constraint])
        community.solver.update()
        obj_constraint.set_linear_coefficients(coefs)

    community.objective = old_obj
    community.modification = "dual optcom"
    logger.info("finished adding dual optcom to %s" % community.id)
Exemplo n.º 9
0
def minimal_medium(
    community,
    community_growth,
    exchanges=None,
    min_growth=0.0,
    exports=False,
    minimize_components=False,
    open_exchanges=False,
    solution=False,
):
    """Find the minimal growth medium for the community.

    Finds the minimal growth medium for the community which allows for
    community as well as individual growth. Here, a minimal medium can either
    be the medium requiring the smallest total import flux or the medium
    requiring the least components (ergo ingredients).

    Arguments
    ---------
    community : micom.Community
        The community to modify.
    community_growth : positive float
        The minimum community-wide growth rate.
    exchanges : list of cobra.Reactions
        The list of exchange reactions that are penalized.
    min_growth : positive float or array-like object.
        The minimum growth rate for each individual in the community. Either
        a single value applied to all individuals or one value for each.
    exports : boolean
        Whether to include export fluxes in the returned medium. Defaults to
        False which will only return import fluxes.
    minimize_components : boolean
        Whether to minimize the number of components instead of the total
        import flux. Might be more intuitive if set to True but may also be
        slow to calculate for large communities.
    open_exchanges : boolean or number
        Whether to ignore currently set bounds and make all exchange reactions
        in the model possible. If set to a number all exchange reactions will
        be opened with (-number, number) as bounds.
    solution : boolean
        Whether to also return the entire solution and all fluxes for the
        minimal medium.


    Returns
    -------
    pandas.Series or dict
        A series {rid: flux} giving the import flux for each required import
        reaction. If `solution` is True retuns a dictionary
        {"medium": panas.Series, "solution": micom.CommunitySolution}.

    """
    logger.info("calculating minimal medium for %s" % community.id)
    boundary_rxns = community.exchanges
    if isinstance(open_exchanges, bool):
        open_bound = 1000
    else:
        open_bound = open_exchanges
    min_growth = _format_min_growth(min_growth, community.species)
    with community as com:
        if open_exchanges:
            logger.info("opening exchanges for %d imports" %
                        len(boundary_rxns))
            for rxn in boundary_rxns:
                rxn.bounds = (-open_bound, open_bound)
        logger.info("applying growth rate constraints")
        context = get_context(community)
        if context is not None:
            context(partial(reset_min_community_growth, com))
            com.variables.community_objective.lb = community_growth
        _apply_min_growth(community, min_growth)
        com.objective = Zero
        logger.info("adding new media objective")
        if minimize_components:
            add_mip_obj(com, boundary_rxns)
        else:
            add_linear_obj(com, boundary_rxns)
        sol = com.optimize(fluxes=True, pfba=False)
        if sol is None:
            logger.warning("minimization of medium was unsuccessful")
            return None

        logger.info("formatting medium")
        medium = pd.Series()
        tol = community.solver.configuration.tolerances.feasibility
        for rxn in boundary_rxns:
            export = len(rxn.reactants) == 1
            flux = sol.fluxes.loc["medium", rxn.id]
            if abs(flux) < tol:
                continue
            if export:
                medium[rxn.id] = -flux
            elif not export:
                medium[rxn.id] = flux
        if not exports:
            medium = medium[medium > 0]

    if solution:
        return {"medium": medium, "solution": sol}
    else:
        return medium
Exemplo n.º 10
0
def complete_medium(
    model,
    medium,
    growth=0.1,
    min_growth=0.001,
    max_import=1,
    minimize_components=False,
    weights=None,
):
    """Fill in missing components in a growth medium.

    Finds the minimal number of additions to make a model form biomass. In
    order to avoid bias all added reactions will have a maximum import
    rate of `max_import`.

    Note
    ----
    This function fixes the growth medium for a single cobra Model. We also
    provide a function `fix_medium` in `micom.workflows` that fixes a growth
    medium for an entire model database.

    Arguments
    ---------
    model : cobra.Model
        The model to use.
    medium : pandas.Series
        A growth medium. Must contain positive floats as elements and
        exchange reaction ids as index. Note that reactions not present in the
        model will be removed from the growth medium.
    growth : positive float
        The minimum overall growth rate that has to be achieved. For single COBRA
        model this is just the biomass flux and for community models this is the
        community biomass flux.
    min_growth : positive float or array-like object.
        The minimum growth rate for each individual in the community. Either
        a single value applied to all individuals or one value for each. Only used
        if model is a `micom.Community` model.
    minimize_components : boolean
        Whether to minimize the number of components instead of the total
        import flux. Might be more intuitive if set to True but may also be
        slow to calculate for large communities.
    max_import: positive float
        The import rate applied for the added exchanges.
    weights : str
        Will scale the fluxes by a weight factor. Can either be "mass" which will
        scale by molecular mass, a single element which will scale by
        the elemental content (for instance "C" to scale by carbon content).
        If None every metabolite will receive the same weight.
        Will be ignored if `minimize_components` is True.


    Returns
    -------
    pandas.Series or dict
        A series {rid: flux} giving the import flux for each required import
        reaction. This will include the initial `medium` as passed to the
        function as well as a minimal set of additional changes such that the
        model produces biomass with a rate >= `min_growth`.

    """
    exids = [r.id for r in model.exchanges]
    candidates = [r for r in model.exchanges if r.id not in medium.index]
    medium = medium[[i for i in medium.index if i in exids]]
    tol = model.solver.configuration.tolerances.feasibility
    with model:
        model.modification = None
        const = model.problem.Constraint(
            model.objective.expression,
            lb=growth,
            name="micom_growth_const",
        )
        if isinstance(model, Community):
            min_growth = _format_min_growth(min_growth, model.taxa)
            _apply_min_growth(model, min_growth, tol, tol)
        model.add_cons_vars([const])
        model.objective = Zero
        model.medium = medium.to_dict()
        for ex in candidates:
            export = len(ex.reactants) == 1
            if export:
                ex.lower_bound = -max_import
            else:
                ex.upper_bound = max_import
        if minimize_components:
            add_mip_obj(model, candidates)
        else:
            scales = weight(candidates, weights)
            add_linear_obj(model, candidates, scales)
        if isinstance(model, Community):
            sol = model.optimize(fluxes=True, pfba=False)
            fluxes = sol.fluxes.loc["medium", :]
        else:
            try:
                sol = model.optimize(raise_error=True)
                fluxes = sol.fluxes
            except OptimizationError:
                sol = None
    if sol is None:
        raise OptimizationError(
            "Could not find a solution that completes the medium :(")
    completed = pd.Series()
    for rxn in model.exchanges:
        export = len(rxn.reactants) == 1
        if rxn.id in medium.index:
            completed[rxn.id] = medium[rxn.id]
            continue
        else:
            flux = -fluxes[rxn.id] if export else fluxes[rxn.id]
        if abs(flux) < tol:
            continue
        completed[rxn.id] = flux

    return completed[completed > 0]