# -*- coding: utf-8 -*-
from __future__ import absolute_import
import logging
from warnings import warn
from itertools import chain
import sympy
from cobra.util import solver as sutil
from cobra.manipulation.modify import (
convert_to_irreversible, revert_to_reversible)
from cobra.util import linear_reaction_coefficients, set_objective
from cobra.core.solution import get_solution
add = sympy.Add._from_args
mul = sympy.Mul._from_args
LOGGER = logging.getLogger(__name__)
[docs]def optimize_minimal_flux(*args, **kwargs):
warn("optimize_minimal_flux has been renamed to pfba", DeprecationWarning)
return pfba(*args, **kwargs)
[docs]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)
[docs]def add_pfba(model, objective=None, fraction_of_optimum=1.0):
"""Add pFBA objective
Add objective to minimize the summed flux of all reactions to the
current objective.
See Also
-------
pfba
Parameters
----------
model : cobra.Model
The model to add the objective to
objective :
An objective to set in combination with the pFBA objective.
fraction_of_optimum : float
Fraction of optimum which must be maintained. The original objective
reaction is constrained to be greater than maximal_value *
fraction_of_optimum.
"""
if objective is not None:
model.objective = objective
if model.solver.objective.name == '_pfba_objective':
raise ValueError('model already has pfba objective')
sutil.fix_objective_as_constraint(model, fraction=fraction_of_optimum)
reaction_variables = ((rxn.forward_variable, rxn.reverse_variable)
for rxn in model.reactions)
variables = chain(*reaction_variables)
pfba_objective = model.problem.Objective(add(
[mul((sympy.singleton.S.One, variable))
for variable in variables]), direction='min', sloppy=True,
name="_pfba_objective")
set_objective(model, pfba_objective)
def _pfba_optlang(model, objective=None, reactions=None,
fraction_of_optimum=1.0):
"""Helper function to perform pFBA with the optlang interface
Not meant to be used directly.
Parameters
----------
model : a cobra model
The model to perform pFBA on
objective :
An objective to use in addition to the pFBA constraints.
reactions : iterable
List of reactions or reaction identifiers.
Returns
-------
cobra.Solution
The solution to the pFBA optimization.
Updates everything in-place, returns model to original state at end.
"""
reactions = model.reactions if reactions is None \
else model.reactions.get_by_any(reactions)
with model as m:
add_pfba(m, objective=objective,
fraction_of_optimum=fraction_of_optimum)
m.slim_optimize(error_value=None)
solution = get_solution(m, reactions=reactions)
return solution
def _pfba_legacy(model, solver, already_irreversible=False,
fraction_of_optimum=1.0,
desired_objective_value=None,
**optimize_kwargs):
"""Perform basic pFBA (parsimonious FBA) and minimize total flux.
The function attempts to act as a drop-in replacement for optimize. It
will make the reaction reversible and perform an optimization, then
force the objective value to remain the same and minimize the total
flux. Finally, it will convert the reaction back to the irreversible
form it was in before. See http://dx.doi.org/10.1038/msb.2010.47
Parameters
----------
model : cobra.Model
The model
solver : solver
The solver object to use
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
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. By default, this option is specified to be 1.0
desired_objective_value : float, optional
A desired objective value for the minimal solution that bypasses the
initial optimization result.
Updates everything in-place, returns model to original state at end.
"""
objective_reactions = linear_reaction_coefficients(model)
if len(objective_reactions) > 1:
raise ValueError('pfba only supports models with'
' a single objective function')
if 'objective_sense' in optimize_kwargs:
if optimize_kwargs['objective_sense'] == 'minimize':
raise ValueError(
'Minimization not supported in pfba')
optimize_kwargs.pop('objective_sense', None)
if not already_irreversible:
convert_to_irreversible(model)
lp = solver.create_problem(model, **optimize_kwargs)
if not desired_objective_value:
solver.solve_problem(lp, objective_sense='maximize')
status = solver.get_status(lp)
if status != "optimal":
revert_to_reversible(model)
raise ValueError(
"pFBA requires optimal solution status, not {}".format(status))
desired_objective_value = solver.get_objective_value(lp)
for i, reaction in enumerate(model.reactions):
if reaction.objective_coefficient != 0:
# Enforce a certain fraction of the original objective
target = (desired_objective_value * fraction_of_optimum /
reaction.objective_coefficient)
solver.change_variable_bounds(lp, i, target, reaction.upper_bound)
# Minimize all reaction fluxes (including objective?)
solver.change_variable_objective(lp, i, 1)
solver.solve_problem(lp, objective_sense='minimize', **optimize_kwargs)
solution = solver.format_solution(lp, model)
# Return the model to its original state
# model.solution = solution
revert_to_reversible(model)
# if solution.status == "optimal":
# model.solution.f = sum([coeff * reaction.x for reaction, coeff in
# iteritems(objective_reactions)])
return solution