##cobra.solvers.glpk_solver
#This script provides wrappers for libglpk-java 1.0.22 and pyglpk 0.3
from __future__ import absolute_import
from warnings import warn
from os import name as __name
from copy import deepcopy
###solver specific parameters
from .parameters import status_dict, variable_kind_dict, \
sense_dict, parameter_mappings, parameter_defaults, \
objective_senses, default_objective_sense
from ..core.Solution import Solution
from ..flux_analysis.objective import update_objective
from time import time
solver_name = 'glpk'
sense_dict = eval(sense_dict[solver_name])
#Functions that are different for java implementation of a solver
if __name == 'java':
warn("cobra.solvers.glpk_solver isn't mature. consider using gurobi or cplex")
from org.gnu.glpk import GLPK, GLPKConstants, glp_smcp, glp_iocp
variable_kind_dict = eval(variable_kind_dict['%s_%s'%(solver_name,
__name)])
status_dict = eval(status_dict['%s_%s'%(solver_name,
__name)])
objective_senses = objective_senses['%s_%s'%(solver_name,
__name)]
parameter_mappings = parameter_mappings['%s_%s'%(solver_name,
__name)]
parameter_defaults = parameter_defaults['%s_%s'%(solver_name,
__name)]
class Problem():
"""Create a more pythonesqe class to wrap the key
features of the libglpk-java functions.
"""
def __init__(self):
"""the attributes g, lp, mip should be made private
"""
self._g = GLPK
self._lp= GLPK.glp_create_prob()
self._simplex_parameters = glp_smcp()
self._mip_parameters = None
self._g.glp_init_smcp(self._simplex_parameters)
self.status = self.objective_value = None
self._mip = False
def set_name(self, name=''):
self._g.glp_set_prob_name(self._lp, name)
def solve(self):
try:
self._g.glp_simplex(self._lp,
self._simplex_parameters)
if self._mip:
#perform the MIP
setattr(self._mip_parameters, 'msg_lev',
self._simplex_parameters.msg_lev)
self._g.glp_intopt(self._lp, self._mip_parameters)
self.status = self.get_status()
self.objective_value = self.get_objective_value()
except:
self.status = 'failed'
return self.status
def get_status(self):
if self._mip:
status = self._g.glp_mip_status(self._lp)
else:
status = self._g.glp_get_status(self._lp)
return status_dict[status]
def set_objective_sense(self, parameter_value='maximize'):
self._g.glp_set_obj_dir(self._lp,
eval(objective_senses[parameter_value]))
def set_parameter(self, parameter_name, parameter_value, warning=False):
if parameter_name == 'objective_sense':
self.set_objective_sense(parameter_value)
else:
if parameter_name == 'meth' and parameter_value not in [1,2,3]:
parameter_value = 1
try:
setattr(self._simplex_parameters, parameter_name,
parameter_value)
except Exception, e1:
try:
setattr(self._mip_parameters, parameter_name,
parameter_value)
except Exception, e2:
if warning:
print "Could not set simplex parameter " +\
"%s: %s"%(parameter_name, repr(e1))
if self._mip_parameters is not None:
print "Could not set mip parameter " +\
"%s: %s"%(parameter_name, repr(e2))
def get_objective_value(self):
if self._mip:
tmp_value = self._g.glp_mip_obj_val(self._lp)
else:
tmp_value = self._g.glp_get_obj_val(self._lp)
return tmp_value
def create_problem(self, cobra_model):
g = self._g
lp = self._lp
number_of_reactions = len(cobra_model.reactions)
number_of_metabolites = len(cobra_model.metabolites)
g.glp_add_cols(lp, number_of_reactions)
reaction_to_index = {}
objective_dict = {}
#Add in the variables
tmp_kinds = []
for i, the_reaction in enumerate(cobra_model.reactions):
i_offset = i + 1
reaction_to_index[the_reaction] = i_offset
if the_reaction.objective_coefficient != 0:
objective_dict[i_offset] = the_reaction.objective_coefficient
g.glp_set_col_name(lp, i_offset, the_reaction.id)
tmp_kinds.append(the_reaction.variable_kind)
the_kind = variable_kind_dict[the_reaction.variable_kind]
lower_bound = the_reaction.lower_bound
upper_bound = the_reaction.upper_bound
#Note. It is possible to have unbounded or one-bound variables
if lower_bound == upper_bound:
bound_kind = GLPKConstants.GLP_FX
else:
bound_kind = GLPKConstants.GLP_DB
g.glp_set_col_kind(lp, i_offset, the_kind)
g.glp_set_col_bnds(lp, i_offset,
bound_kind, the_reaction.lower_bound,
the_reaction.upper_bound)
tmp_kinds = set(tmp_kinds)
if 'integer' in tmp_kinds or 'binary' in tmp_kinds:
self._mip = True
self._mip_parameters = glp_iocp()
g.glp_init_iocp(self._mip_parameters)
#create constraints
g.glp_add_rows(lp, number_of_metabolites)
row_indices = []
column_indices = []
constraint_values = []
for i, the_metabolite in enumerate(cobra_model.metabolites):
i_offset = i + 1
g.glp_set_row_name(lp, i_offset, the_metabolite.id)
lower_bound = upper_bound = the_metabolite._bound
constraint_sense = sense_dict[the_metabolite._constraint_sense]
if constraint_sense == 'E':
bound_type = GLPKConstants.GLP_FX
elif constraint_sense == 'L':
bound_type = GLPKConstants.GLP_UP
elif constraint_sense == 'G':
bound_type = GLPKConstants.GLP_LO
elif constraint_sense == 'U':
bound_type = GLPKConstants.GLP_FR
elif hasattr(lower_bound, '__iter__'):
lower_bound, upper_bound = lower_bound[:2]
bound_type = GLPKConstants.GLP_DB
g.glp_set_row_bnds(lp, i_offset, bound_type,
lower_bound, upper_bound)
[(row_indices.append(i_offset),
column_indices.append(reaction_to_index[k]),
constraint_values.append(k._metabolites[the_metabolite]))
for k in the_metabolite._reaction]
#Load the constraints into the lp. Need to use
#typed arrays.
number_of_constraints = len(row_indices)
i_array = g.new_intArray(number_of_constraints)
j_array = g.new_intArray(number_of_constraints)
v_array = g.new_doubleArray(number_of_constraints)
for a, (i, j, v) in enumerate(zip(row_indices,
column_indices,
constraint_values)):
g.intArray_setitem(i_array, a+1, i)
g.intArray_setitem(j_array, a+1, j)
g.doubleArray_setitem(v_array, a+1, v)
g.glp_load_matrix(lp, number_of_constraints, i_array,
j_array, v_array)
g.delete_intArray(i_array)
g.delete_intArray(j_array)
g.delete_doubleArray(v_array)
g.glp_set_obj_name(lp, "z")
[g.glp_set_obj_coef(lp, k, v)
for k, v in objective_dict.iteritems()]
__solver_class = Problem
def set_parameter(lp, parameter_name, parameter_value):
lp.set_parameter(parameter_name, parameter_value)
def get_status(lp):
return lp.get_status()
def format_solution(lp, cobra_model, **kwargs):
"""
"""
status = get_status(lp)
if not lp._mip:
try:
x = [lp._g.glp_get_col_prim(lp._lp, i + 1)
for i in range(len(cobra_model.reactions))]
x_dict = dict(zip(cobra_model.reactions, x))
y = [lp._g.glp_get_row_dual(lp._lp, i + 1)
for i in range(len(cobra_model.metabolites))]
y_dict = dict(zip(cobra_model.metabolites, y))
objective_value = lp.objective_value
except Exception, e:
print repr(e)
y = y_dict = x = x_dict = objective_value = None
#print status
else:
try:
x = [lp._g.glp_mip_col_val(lp._lp, i + 1)
for i in range(len(cobra_model.reactions))]
x_dict = dict(zip(cobra_model.reactions, x))
y = y_dict = None
objective_value = lp.objective_value
except:
y = y_dict = x = x_dict = objective_value = None
return(Solution(objective_value, x=x, x_dict=x_dict, y=y,
y_dict=y_dict, status=status))
def create_problem(cobra_model, **kwargs):
"""Solver-specific method for constructing a solver problem from
a cobra.Model. This can be tuned for performance using kwargs
"""
the_parameters = parameter_defaults
if kwargs:
the_parameters = deepcopy(parameter_defaults)
the_parameters.update(kwargs)
quadratic_component = the_parameters['quadratic_component']
new_objective = the_parameters['new_objective']
if quadratic_component is not None:
raise Exception('%s cannot solve QPs, try a different solver'%solver_name)
lp = Problem() # Create empty problem instance
lp.create_problem(cobra_model)
[set_parameter(lp, parameter_mappings[k], v)
for k, v in the_parameters.iteritems() if k in parameter_mappings]
return(lp)
def update_problem(lp, cobra_model, **kwargs):
"""
Assumes that neither Metabolites nor Reaction have been
added or removed.
Currently only deals with reaction bounds and objective
coefficients.
"""
g = lp._g
l = lp._lp
for i, the_reaction in enumerate(cobra_model.reactions):
lower_bound = float(the_reaction.lower_bound)
upper_bound = float(the_reaction.upper_bound)
objective_coefficient = float(the_reaction.objective_coefficient)
if lower_bound == upper_bound:
bound_type = GLPKConstants.GLP_FX
else:
bound_type = GLPKConstants.GLP_DB
g.glp_set_col_bnds(l, i + 1, bound_type, lower_bound, upper_bound)
g.glp_set_obj_coef(l, i + 1, objective_coefficient)
def solve_problem(lp, **kwargs):
"""A performance tunable method for updating a model problem file
"""
#Update parameter settings if provided
if kwargs:
[set_parameter(lp, parameter_mappings[k], v)
for k, v in kwargs.iteritems() if k in parameter_mappings]
try:
print_solver_time = kwargs['print_solver_time']
start_time = time()
except:
print_solver_time = False
lp_method = lp._simplex_parameters.meth
lp.solve()
status = get_status(lp)
if print_solver_time:
print 'optimize time: %f'%(time() - start_time)
return status
else:
##Interface to pyGLPK 0.3
from glpk import LPX as GLPK
__solver_class = GLPK
objective_senses = objective_senses[solver_name]
variable_kind_dict = eval(variable_kind_dict[solver_name])
status_dict = eval(status_dict[solver_name])
parameter_mappings = parameter_mappings[solver_name]
parameter_defaults = parameter_defaults[solver_name]
[docs] def get_status(lp):
status = lp.status
if status in status_dict:
status = status_dict[status]
else:
status = 'failed'
return status
[docs] def set_parameter(lp, parameter_name, parameter_value):
"""with pyglpk the parameters are set during the solve phase
"""
warn("py glpk solver parameters are set during solve_problem")
pass
[docs] def create_problem(cobra_model, **kwargs):
"""Solver-specific method for constructing a solver problem from
a cobra.Model. This can be tuned for performance using kwargs
"""
the_parameters = parameter_defaults
if kwargs:
the_parameters = deepcopy(parameter_defaults)
the_parameters.update(kwargs)
quadratic_component = the_parameters['quadratic_component']
new_objective = the_parameters['new_objective']
if quadratic_component is not None:
raise Exception('%s cannot solve QPs, try a different solver'%solver_name)
#Faster to use these dicts than index lists
index_to_metabolite = dict(zip(range(len(cobra_model.metabolites)),
cobra_model.metabolites))
index_to_reaction = dict(zip(range(len(cobra_model.reactions)),
cobra_model.reactions))
reaction_to_index = dict(zip(index_to_reaction.values(),
index_to_reaction.keys()))
lp = __solver_class() # Create empty problem instance
lp.name = 'cobra' # Assign symbolic name to problem
lp.rows.add(len(cobra_model.metabolites))
lp.cols.add(len(cobra_model.reactions))
linear_constraints = []
for r in lp.rows:
the_metabolite = index_to_metabolite[r.index]
r.name = the_metabolite.id
b = float(the_metabolite._bound)
c = sense_dict[the_metabolite._constraint_sense]
if c == 'E':
r.bounds = b, b # Set metabolite to steady state levels
elif c == 'L':
r.bounds = None, b
elif c == 'G':
r.bounds = b, None
#Add in the linear constraints
for the_reaction in the_metabolite._reaction:
reaction_index = reaction_to_index[the_reaction]
the_coefficient = the_reaction._metabolites[the_metabolite]
linear_constraints.append((r.index, reaction_index,
the_coefficient))
#Need to assign lp.matrix after constructing the whole list
lp.matrix = linear_constraints
objective_coefficients = []
for c in lp.cols:
the_reaction = index_to_reaction[c.index]
c.name = the_reaction.id
the_reaction = index_to_reaction[c.index]
c.kind = variable_kind_dict[the_reaction.variable_kind]
c.bounds = the_reaction.lower_bound, the_reaction.upper_bound
objective_coefficients.append(float(the_reaction.objective_coefficient))
#Add the new objective coefficients to the problem
lp.obj[:] = objective_coefficients
return(lp)
[docs] def update_problem(lp, cobra_model, **kwargs):
"""A performance tunable method for updating a model problem file
lp: A gurobi problem object
cobra_model: the cobra.Model corresponding to 'lp'
"""
#When reusing the basis only assume that the objective coefficients or bounds can change
#BUG with changing / unchanging the basis
index_to_metabolite = dict(zip(range(len(cobra_model.metabolites)),
cobra_model.metabolites))
index_to_reaction = dict(zip(range(len(cobra_model.reactions)),
cobra_model.reactions))
reaction_to_index = dict(zip(index_to_reaction.values(),
index_to_reaction.keys()))
try:
new_objective = kwargs['new_objective']
except:
new_objective = None
if new_objective is not None:
objective_coefficients = []
for c in lp.cols: # Iterate over all rows
the_reaction = index_to_reaction[c.index]
c.name = the_reaction.id
c.bounds = the_reaction.lower_bound, the_reaction.upper_bound
objective_coefficients.append(float(the_reaction.objective_coefficient))
c.kind = variable_kind_dict[the_reaction.variable_kind]
#Add the new objective coefficients to the problem
lp.obj[:] = objective_coefficients
else:
for c in lp.cols: # Iterate over all rows
the_reaction = index_to_reaction[c.index]
c.name = the_reaction.id
c.bounds = the_reaction.lower_bound, the_reaction.upper_bound
c.kind = variable_kind_dict[the_reaction.variable_kind]
###
[docs] def solve_problem(lp, **kwargs):
"""A performance tunable method for updating a model problem file
lp: a pyGLPK 0.3 problem
For pyGLPK it is necessary to provide the following parameters, if they
are not provided then the default settings will be used: tolerance_optimality,
tolerance_integer, lp_method, and objective_sense
"""
if kwargs:
the_parameters = kwargs
else:
the_parameters = {}
#These need to be provided to solve_problem for pyGLPK because it's not possible
#AFAIK to set these during problem creation.
__function_parameters = ['tolerance_optimality', 'tolerance_integer', 'lp_method',
'objective_sense']
for the_parameter in __function_parameters:
if the_parameter not in the_parameters:
the_parameters.update({the_parameter: parameter_defaults[the_parameter]})
try:
print_solver_time = the_parameters['print_solver_time']
start_time = time()
except:
print_solver_time = False
tolerance_optimality = the_parameters['tolerance_optimality']
tolerance_integer = the_parameters['tolerance_integer']
lp_method = the_parameters['lp_method']
#[set_parameter(lp, parameter_mappings[k], v)
# for k, v in kwargs.iteritems() if k in parameter_mappings]
lp.obj.maximize = objective_senses[the_parameters['objective_sense']]
if lp.kind == int:
#For MILPs, it is faster to solve LP then move to MILP
lp.simplex(tol_bnd=tolerance_optimality,
tol_dj=tolerance_optimality, meth=lp_method)
lp.integer(tol_int=tolerance_integer)
else:
lp.simplex(tol_bnd=tolerance_optimality, tol_dj=tolerance_optimality,
meth=lp_method)
status = get_status(lp)
if print_solver_time:
print 'optimize time: %f'%(time() - start_time)
return status
[docs]def solve(cobra_model, **kwargs):
"""Smart interface to optimization solver functions that will convert
the cobra_model to a solver object, set the parameters, and try multiple
methods to get an optimal solution before returning the solver object and
a cobra.Solution (which is attached to cobra_model.solution)
cobra_model: a cobra.Model
returns a dict: {'the_problem': solver specific object, 'the_solution':
cobra.Solution for the optimization problem'}
"""
#Start out with default parameters and then modify if
#new onese are provided
the_parameters = deepcopy(parameter_defaults)
if kwargs:
the_parameters.update(kwargs)
#Update objectives if they are new.
error_reporting = the_parameters['error_reporting']
if 'new_objective' in the_parameters and \
the_parameters['new_objective'] not in ['update problem', None]:
update_objective(cobra_model, the_parameters['new_objective'])
if 'the_problem' in the_parameters:
the_problem = the_parameters['the_problem']
else:
the_problem = None
if isinstance(the_problem, __solver_class):
#Update the problem with the current cobra_model
lp = the_problem
update_problem(lp, cobra_model, **the_parameters)
else:
#Create a new problem
lp = create_problem(cobra_model, **the_parameters)
#Deprecated way for returning a solver problem created from a cobra_model
#without performing optimization
if the_problem == 'setup':
return lp
###Try to solve the problem using other methods if the first method doesn't work
lp_method = the_parameters['lp_method']
the_methods = [1, 2, 3]
if lp_method in the_methods:
the_methods.remove(lp_method)
#Start with the user specified method
the_methods.insert(0, lp_method)
for the_method in the_methods:
the_parameters['lp_method'] = the_method
try:
status = solve_problem(lp, **the_parameters)
except:
status = 'failed'
if status == 'optimal':
break
the_solution = format_solution(lp, cobra_model)
if status != 'optimal' and error_reporting:
print '%s failed: %s'%(solver_name, status)
cobra_model.solution = the_solution
solution = {'the_problem': lp, 'the_solution': the_solution}
return solution