Each cobrapy solver must expose the following API. The solvers all will have their own distinct LP object types, but each can be manipulated by these functions. This API can be used directly when implementing algorithms efficiently on linear programs because it has 2 primary benefits:
Avoid the overhead of creating and destroying LP's for each operation
Many solver objects preserve the basis between subsequent LP's, making each subsequent LP solve faster
We will walk though the API with the cglpk solver, which links the cobrapy solver API with GLPK's C API.
In [1]:
import cobra.test
model = cobra.test.create_test_model("textbook")
solver = cobra.solvers.cglpk
In [2]:
solver.solver_name
Out[2]:
In [3]:
model.optimize(solver="cglpk")
Out[3]:
In [4]:
solver._SUPPORTS_MILP
Out[4]:
In [5]:
solver.solve(model)
Out[5]:
In [6]:
lp = solver.create_problem(model, objective_sense="maximize")
lp
Out[6]:
In [7]:
solver.solve_problem(lp)
Out[7]:
In [8]:
solver.format_solution(lp, model)
Out[8]:
In [9]:
solver.get_objective_value(lp)
Out[9]:
In [10]:
solver.get_status(lp)
Out[10]:
In [11]:
model.reactions.index("Biomass_Ecoli_core")
Out[11]:
In [12]:
solver.change_variable_objective(lp, 12, 2)
solver.solve_problem(lp)
solver.get_objective_value(lp)
Out[12]:
In [13]:
solver.change_variable_objective(lp, 12, 1)
solver.solve_problem(lp)
solver.get_objective_value(lp)
Out[13]:
In [14]:
solver.change_variable_bounds(lp, 12, 1000, 1000)
solver.solve_problem(lp)
Out[14]:
In [15]:
solver.change_variable_bounds(lp, 12, 0, 1000)
solver.solve_problem(lp)
Out[15]:
In [16]:
model.metabolites.index("atp_c")
Out[16]:
In [17]:
model.reactions.index("ATPM")
Out[17]:
In [18]:
solver.change_coefficient(lp, 16, 10, -10)
solver.solve_problem(lp)
Out[18]:
In [19]:
solver.change_coefficient(lp, 16, 10, -1)
solver.solve_problem(lp)
Out[19]:
In [20]:
solver.set_parameter(lp, "tolerance_feasibility", 1e-9)
In [21]:
solver.set_parameter(lp, "objective_sense", "minimize")
solver.solve_problem(lp)
solver.get_objective_value(lp)
Out[21]:
In [22]:
solver.set_parameter(lp, "objective_sense", "maximize")
solver.solve_problem(lp)
solver.get_objective_value(lp)
Out[22]:
In [23]:
%%time
# work on a copy of the model so the original is not changed
m = model.copy()
# set the lower bound on the objective to be the optimal value
f = m.optimize().f
for objective_reaction, coefficient in m.objective.items():
objective_reaction.lower_bound = coefficient * f
# now maximize and minimze every reaction to find its bounds
fva_result = {}
for r in m.reactions:
m.change_objective(r)
fva_result[r.id] = {
"maximum": m.optimize(objective_sense="maximize").f,
"minimum": m.optimize(objective_sense="minimize").f
}
Instead, we could use the solver API to do this more efficiently. This is roughly how cobrapy implementes FVA. It keeps uses the same LP object and repeatedly maximizes and minimizes it. This allows the solver to preserve the basis, and is much faster. The speed increase is even more noticeable the larger the model gets.
In [24]:
%%time
# create the LP object
lp = solver.create_problem(model)
# set the lower bound on the objective to be the optimal value
solver.solve_problem(lp)
f = solver.get_objective_value(lp)
for objective_reaction, coefficient in model.objective.items():
objective_index = model.reactions.index(objective_reaction)
# old objective is no longer the objective
solver.change_variable_objective(lp, objective_index, 0.)
solver.change_variable_bounds(
lp, objective_index, f * coefficient,
objective_reaction.upper_bound)
# now maximize and minimze every reaction to find its bounds
fva_result = {}
for index, r in enumerate(model.reactions):
solver.change_variable_objective(lp, index, 1.)
result = {}
solver.solve_problem(lp, objective_sense="maximize")
result["maximum"] = solver.get_objective_value(lp)
solver.solve_problem(lp, objective_sense="minimize")
result["minimum"] = solver.get_objective_value(lp)
solver.change_variable_objective(lp, index, 0.)
fva_result[r.id] = result