Skip to content

Commit

Permalink
Various fixes (#78)
Browse files Browse the repository at this point in the history
fix: objective.value should return None if not in model
fix: get correct primal values from glpk MIPs
test: add test for obj value without model
fix: allow older versions of cplex
Numerous gurobi fixes
Miplib test suite
  • Loading branch information
KristianJensen authored Mar 14, 2017
1 parent da0eca7 commit 33d846b
Show file tree
Hide file tree
Showing 50 changed files with 446 additions and 84 deletions.
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ include README.rst
include requirements.txt
include versioneer.py
include optlang/_version.py
recursive-exclude slow_tests *
138 changes: 73 additions & 65 deletions optlang/cplex_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,74 +43,82 @@
Zero = S.Zero
One = S.One

_CPLEX_STATUS_TO_STATUS = {
cplex.Cplex.solution.status.MIP_abort_feasible: interface.ABORTED,
cplex.Cplex.solution.status.MIP_abort_infeasible: interface.ABORTED,
cplex.Cplex.solution.status.MIP_dettime_limit_feasible: interface.TIME_LIMIT,
cplex.Cplex.solution.status.MIP_dettime_limit_infeasible: interface.TIME_LIMIT,
cplex.Cplex.solution.status.MIP_feasible: interface.FEASIBLE,
cplex.Cplex.solution.status.MIP_feasible_relaxed_inf: interface.SPECIAL,
cplex.Cplex.solution.status.MIP_feasible_relaxed_quad: interface.SPECIAL,
cplex.Cplex.solution.status.MIP_feasible_relaxed_sum: interface.SPECIAL,
cplex.Cplex.solution.status.MIP_infeasible: interface.INFEASIBLE,
cplex.Cplex.solution.status.MIP_infeasible_or_unbounded: interface.INFEASIBLE_OR_UNBOUNDED,
cplex.Cplex.solution.status.MIP_optimal: interface.OPTIMAL,
cplex.Cplex.solution.status.MIP_optimal_infeasible: interface.SPECIAL,
cplex.Cplex.solution.status.MIP_optimal_relaxed_inf: interface.SPECIAL,
cplex.Cplex.solution.status.MIP_optimal_relaxed_sum: interface.SPECIAL,
cplex.Cplex.solution.status.MIP_time_limit_feasible: interface.TIME_LIMIT,
cplex.Cplex.solution.status.MIP_time_limit_infeasible: interface.TIME_LIMIT,
cplex.Cplex.solution.status.MIP_unbounded: interface.UNBOUNDED,
cplex.Cplex.solution.status.abort_dettime_limit: interface.ABORTED,
cplex.Cplex.solution.status.abort_dual_obj_limit: interface.ABORTED,
cplex.Cplex.solution.status.abort_iteration_limit: interface.ABORTED,
cplex.Cplex.solution.status.abort_obj_limit: interface.ABORTED,
cplex.Cplex.solution.status.abort_primal_obj_limit: interface.ABORTED,
cplex.Cplex.solution.status.abort_relaxed: interface.ABORTED,
cplex.Cplex.solution.status.abort_time_limit: interface.TIME_LIMIT,
cplex.Cplex.solution.status.abort_user: interface.ABORTED,
cplex.Cplex.solution.status.conflict_abort_contradiction: interface.SPECIAL,
cplex.Cplex.solution.status.conflict_abort_dettime_limit: interface.SPECIAL,
cplex.Cplex.solution.status.conflict_abort_iteration_limit: interface.SPECIAL,
cplex.Cplex.solution.status.conflict_abort_memory_limit: interface.SPECIAL,
cplex.Cplex.solution.status.conflict_abort_node_limit: interface.SPECIAL,
cplex.Cplex.solution.status.conflict_abort_obj_limit: interface.SPECIAL,
cplex.Cplex.solution.status.conflict_abort_time_limit: interface.SPECIAL,
cplex.Cplex.solution.status.conflict_abort_user: interface.SPECIAL,
cplex.Cplex.solution.status.conflict_feasible: interface.SPECIAL,
cplex.Cplex.solution.status.conflict_minimal: interface.SPECIAL,
cplex.Cplex.solution.status.fail_feasible: interface.SPECIAL,
cplex.Cplex.solution.status.fail_feasible_no_tree: interface.SPECIAL,
cplex.Cplex.solution.status.fail_infeasible: interface.SPECIAL,
cplex.Cplex.solution.status.fail_infeasible_no_tree: interface.SPECIAL,
cplex.Cplex.solution.status.feasible: interface.FEASIBLE,
cplex.Cplex.solution.status.feasible_relaxed_inf: interface.SPECIAL,
cplex.Cplex.solution.status.feasible_relaxed_quad: interface.SPECIAL,
cplex.Cplex.solution.status.feasible_relaxed_sum: interface.SPECIAL,
cplex.Cplex.solution.status.first_order: interface.SPECIAL,
cplex.Cplex.solution.status.infeasible: interface.INFEASIBLE,
cplex.Cplex.solution.status.infeasible_or_unbounded: interface.INFEASIBLE_OR_UNBOUNDED,
cplex.Cplex.solution.status.mem_limit_feasible: interface.MEMORY_LIMIT,
cplex.Cplex.solution.status.mem_limit_infeasible: interface.MEMORY_LIMIT,
cplex.Cplex.solution.status.node_limit_feasible: interface.NODE_LIMIT,
cplex.Cplex.solution.status.node_limit_infeasible: interface.NODE_LIMIT,
cplex.Cplex.solution.status.num_best: interface.NUMERIC,
cplex.Cplex.solution.status.optimal: interface.OPTIMAL,
cplex.Cplex.solution.status.optimal_face_unbounded: interface.SPECIAL,
cplex.Cplex.solution.status.optimal_infeasible: interface.INFEASIBLE,
cplex.Cplex.solution.status.optimal_populated: interface.SPECIAL,
cplex.Cplex.solution.status.optimal_populated_tolerance: interface.SPECIAL,
cplex.Cplex.solution.status.optimal_relaxed_inf: interface.SPECIAL,
cplex.Cplex.solution.status.optimal_relaxed_quad: interface.SPECIAL,
cplex.Cplex.solution.status.optimal_relaxed_sum: interface.SPECIAL,
cplex.Cplex.solution.status.optimal_tolerance: interface.OPTIMAL,
cplex.Cplex.solution.status.populate_solution_limit: interface.SPECIAL,
cplex.Cplex.solution.status.solution_limit: interface.SPECIAL,
cplex.Cplex.solution.status.unbounded: interface.UNBOUNDED,
cplex.Cplex.solution.status.relaxation_unbounded: interface.UNBOUNDED,
_STATUS_MAP = {
'MIP_abort_feasible': interface.ABORTED,
'MIP_abort_infeasible': interface.ABORTED,
'MIP_dettime_limit_feasible': interface.TIME_LIMIT,
'MIP_dettime_limit_infeasible': interface.TIME_LIMIT,
'MIP_feasible': interface.FEASIBLE,
'MIP_feasible_relaxed_inf': interface.SPECIAL,
'MIP_feasible_relaxed_quad': interface.SPECIAL,
'MIP_feasible_relaxed_sum': interface.SPECIAL,
'MIP_infeasible': interface.INFEASIBLE,
'MIP_infeasible_or_unbounded': interface.INFEASIBLE_OR_UNBOUNDED,
'MIP_optimal': interface.OPTIMAL,
'MIP_optimal_infeasible': interface.SPECIAL,
'MIP_optimal_relaxed_inf': interface.SPECIAL,
'MIP_optimal_relaxed_sum': interface.SPECIAL,
'MIP_time_limit_feasible': interface.TIME_LIMIT,
'MIP_time_limit_infeasible': interface.TIME_LIMIT,
'MIP_unbounded': interface.UNBOUNDED,
'abort_dettime_limit': interface.ABORTED,
'abort_dual_obj_limit': interface.ABORTED,
'abort_iteration_limit': interface.ABORTED,
'abort_obj_limit': interface.ABORTED,
'abort_primal_obj_limit': interface.ABORTED,
'abort_relaxed': interface.ABORTED,
'abort_time_limit': interface.TIME_LIMIT,
'abort_user': interface.ABORTED,
'conflict_abort_contradiction': interface.SPECIAL,
'conflict_abort_dettime_limit': interface.SPECIAL,
'conflict_abort_iteration_limit': interface.SPECIAL,
'conflict_abort_memory_limit': interface.SPECIAL,
'conflict_abort_node_limit': interface.SPECIAL,
'conflict_abort_obj_limit': interface.SPECIAL,
'conflict_abort_time_limit': interface.SPECIAL,
'conflict_abort_user': interface.SPECIAL,
'conflict_feasible': interface.SPECIAL,
'conflict_minimal': interface.SPECIAL,
'fail_feasible': interface.SPECIAL,
'fail_feasible_no_tree': interface.SPECIAL,
'fail_infeasible': interface.SPECIAL,
'fail_infeasible_no_tree': interface.SPECIAL,
'feasible': interface.FEASIBLE,
'feasible_relaxed_inf': interface.SPECIAL,
'feasible_relaxed_quad': interface.SPECIAL,
'feasible_relaxed_sum': interface.SPECIAL,
'first_order': interface.SPECIAL,
'infeasible': interface.INFEASIBLE,
'infeasible_or_unbounded': interface.INFEASIBLE_OR_UNBOUNDED,
'mem_limit_feasible': interface.MEMORY_LIMIT,
'mem_limit_infeasible': interface.MEMORY_LIMIT,
'node_limit_feasible': interface.NODE_LIMIT,
'node_limit_infeasible': interface.NODE_LIMIT,
'num_best': interface.NUMERIC,
'optimal': interface.OPTIMAL,
'optimal_face_unbounded': interface.SPECIAL,
'optimal_infeasible': interface.INFEASIBLE,
'optimal_populated': interface.SPECIAL,
'optimal_populated_tolerance': interface.SPECIAL,
'optimal_relaxed_inf': interface.SPECIAL,
'optimal_relaxed_quad': interface.SPECIAL,
'optimal_relaxed_sum': interface.SPECIAL,
'optimal_tolerance': interface.OPTIMAL,
'populate_solution_limit': interface.SPECIAL,
'solution_limit': interface.SPECIAL,
'unbounded': interface.UNBOUNDED,
'relaxation_unbounded': interface.UNBOUNDED,
'non-existing-status': 'Here for testing that missing statuses are handled.'
# 102: interface.OPTIMAL # The same as cplex.Cplex.solution.status.optimal_tolerance
}

# Check if each status is supported by the current cplex version
_CPLEX_STATUS_TO_STATUS = {}
for status_name, optlang_status in _STATUS_MAP.items():
cplex_status = getattr(cplex.Cplex.solution.status, status_name, None)
if cplex_status is not None:
_CPLEX_STATUS_TO_STATUS[cplex_status] = optlang_status

_LP_METHODS = ["auto", "primal", "dual", "network", "barrier", "sifting", "concurrent"]

_SOLUTION_TARGETS = ("auto", "convex", "local", "global")
Expand Down
38 changes: 23 additions & 15 deletions optlang/gurobi_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,17 @@ def lb(self, value):
if value is None:
value = -gurobipy.GRB.INFINITY
if self.problem:
return self._internal_variable.setAttr('LB', value)
self._internal_variable.setAttr('LB', value)
self.problem.problem.update()

@interface.Variable.ub.setter
def ub(self, value):
super(Variable, self.__class__).ub.fset(self, value)
if value is None:
value = gurobipy.GRB.INFINITY
if self.problem:
return self._internal_variable.setAttr('UB', value)
self._internal_variable.setAttr('UB', value)
self.problem.problem.update()

def set_bounds(self, lb, ub):
super(Variable, self).set_bounds(lb, ub)
Expand Down Expand Up @@ -188,6 +190,7 @@ def set_linear_coefficients(self, coefficients):
grb_constraint = self.problem.problem.getConstrByName(self.name)
for var, coeff in six.iteritems(coefficients):
self.problem.problem.chgCoeff(grb_constraint, self.problem.problem.getVarByName(var.name), float(coeff))
self.problem.update()
else:
raise Exception("Can't change coefficients if constraint is not associated with a model.")

Expand Down Expand Up @@ -280,6 +283,7 @@ def _set_constraint_bounds(self, sense, rhs, range_value):
self.problem.problem.update()
self.problem.problem.addConstr(updated_row, sense, rhs, self.name)
aux_var.setAttr("UB", range_value)
self.problem.update()

@interface.Constraint.lb.setter
def lb(self, value):
Expand Down Expand Up @@ -326,9 +330,12 @@ def __init__(self, expression, sloppy=False, *args, **kwargs):

@property
def value(self):
try:
return self.problem.problem.getAttr("ObjVal")
except gurobipy.GurobiError: # TODO: let this check the actual error message
if getattr(self, "problem", None) is not None:
try:
return self.problem.problem.getAttr("ObjVal")
except gurobipy.GurobiError: # TODO: let this check the actual error message
return None
else:
return None

@interface.Objective.direction.setter
Expand All @@ -339,12 +346,10 @@ def direction(self, value):

def set_linear_coefficients(self, coefficients):
if self.problem is not None:
grb_obj = self.problem.problem.getObjective()
for var, coeff in six.iteritems(coefficients):
grb_var = self.problem.problem.getVarByName(var.name)
grb_obj.remove(grb_var)
grb_obj.addTerms(float(coeff), grb_var)
self._expression_expired = True
for var, coeff in coefficients.items():
var._internal_variable.setAttr("Obj", coeff)
self._expression_expired = True
self.problem.update()
else:
raise Exception("Can't change coefficients if objective is not associated with a model.")

Expand Down Expand Up @@ -564,6 +569,8 @@ def update(self):
super(Model, self).update(callback=self.problem.update)

def _optimize(self):
if self.status != interface.OPTIMAL:
self.problem.reset()
self.problem.update()
self.problem.optimize()
status = _GUROBI_STATUS_TO_STATUS[self.problem.getAttr("Status")]
Expand All @@ -580,13 +587,12 @@ def _add_variables(self, variables):
ub = gurobipy.GRB.INFINITY
else:
ub = variable.ub
gurobi_var = self.problem.addVar(
self.problem.addVar(
name=variable.name,
lb=lb,
ub=ub,
vtype=_VTYPE_TO_GUROBI_VTYPE[variable.type]
)
variable._internal_var = gurobi_var

def _remove_variables(self, variables):
# Not calling parent method to avoid expensive variable removal from sympy expressions
Expand All @@ -611,17 +617,19 @@ def _add_constraints(self, constraints, sloppy=False):
lhs = gurobipy.quicksum([coef * var._internal_variable for var, coef in coef_dict.items()])
sense, rhs, range_value = _constraint_lb_and_ub_to_gurobi_sense_rhs_and_range_value(constraint.lb,
constraint.ub)
if range_value != 0.:

if range_value != 0:
aux_var = self.problem.addVar(name=constraint.name + '_aux', lb=0, ub=range_value)
self.problem.update()
lhs = lhs - aux_var
rhs = constraint.ub

self.problem.addConstr(lhs, sense, rhs, name=constraint.name)
else:
raise ValueError(
"GUROBI currently only supports linear constraints. %s is not linear." % self)
# self.problem.addQConstr(lhs, sense, rhs)
constraint.problem = self
self.problem.update()

def _remove_constraints(self, constraints):
self.problem.update()
Expand Down
8 changes: 4 additions & 4 deletions optlang/tests/test_gurobi_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ def test_gurobi_add_constraints(self):
for i in range(row.size()):
coeff_dict[row.getVar(i).VarName] = row.getCoeff(i)
self.assertDictEqual(coeff_dict, {'x': 0.3, 'y': 0.4, 'z': 66., 'test_aux': -1.0})
self.assertEqual(internal_constraint.RHS, 0)
self.assertEqual(internal_constraint.RHS, constr1.lb)
self.assertEqual(self.model.problem.getVarByName(internal_constraint.getAttr('ConstrName') + '_aux'), 100)
# constr2
coeff_dict = dict()
Expand All @@ -253,7 +253,7 @@ def test_gurobi_add_constraints(self):
for i in range(row.size()):
coeff_dict[row.getVar(i).VarName] = row.getCoeff(i)
self.assertDictEqual(coeff_dict, {'x': 2.333, 'y': 1.})
self.assertEqual(internal_constraint.RHS, 96.997)
self.assertEqual(internal_constraint.RHS, constr2.ub)
self.assertEqual(internal_constraint.Sense, '<')
# constr3
coeff_dict = dict()
Expand All @@ -263,7 +263,7 @@ def test_gurobi_add_constraints(self):
for i in range(row.size()):
coeff_dict[row.getVar(i).VarName] = row.getCoeff(i)
self.assertDictEqual(coeff_dict, {'x': 2.333, 'y': 1., 'z': 1.})
self.assertEqual(internal_constraint.RHS, -300)
self.assertEqual(internal_constraint.RHS, constr3.lb)
self.assertEqual(internal_constraint.Sense, '>')
# constr4
coeff_dict = dict()
Expand All @@ -273,7 +273,7 @@ def test_gurobi_add_constraints(self):
for i in range(row.size()):
coeff_dict[row.getVar(i).VarName] = row.getCoeff(i)
self.assertDictEqual(coeff_dict, {'x': 1})
self.assertEqual(internal_constraint.RHS, -300)
self.assertEqual(internal_constraint.RHS, constr4.lb)
self.assertEqual(internal_constraint.Sense, '=')

def test_change_of_constraint_is_reflected_in_low_level_solver(self):
Expand Down
4 changes: 4 additions & 0 deletions slow_tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
This directory contains long-running tests that should not
be packaged with optlang. `data/miplib2003` contains all
benchmark problems that can be solved <1 h with commercial
solvers.
Empty file added slow_tests/__init__.py
Empty file.
74 changes: 74 additions & 0 deletions slow_tests/data/miplib2003.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"10teams": {
"status": "optimal",
"solution": 924.0
},
"aflow30a": {
"status": "optimal",
"solution": 1158.0
},
"air05": {
"status": "optimal",
"solution": 26374.0
},
"arki001": {
"status": "optimal",
"solution": 7580810.0
},
"danoint": {
"status": "optimal",
"solution": 65.67
},
"disctom": {
"status": "optimal",
"solution": -5000.0
},
"fiber": {
"status": "optimal",
"solution": 405935.18
},
"fixnet6": {
"status": "optimal",
"solution": 3983.0
},
"gesa2": {
"status": "optimal",
"solution": 25779856.3719999976
},
"gesa2-o": {
"status": "optimal",
"solution": 25779856.3719999976
},
"glass4": {
"status": "optimal",
"solution": 1200010000.0
},
"harp2": {
"status": "optimal",
"solution": -73899800.0
},
"markshare1": {
"status": "optimal",
"solution": 1.0
},
"mas74": {
"status": "optimal",
"solution": 11801.2
},
"mas76": {
"status": "optimal",
"solution": 40005.1
},
"misc07": {
"status": "optimal",
"solution": 2810.0
},
"modglob": {
"status": "optimal",
"solution": 20740500.0
},
"momentum2": {
"status": "optimal",
"solution": 12314.2
}
}
Binary file added slow_tests/data/miplib2003/10teams.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/aflow30a.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/aflow40b.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/air04.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/air05.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/arki001.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/cap6000.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/danoint.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/disctom.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/fiber.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/fixnet6.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/gesa2-o.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/gesa2.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/glass4.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/harp2.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/manna81.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/markshare1.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/mas74.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/mas76.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/misc07.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/mod011.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/modglob.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/msc98-ip.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/mzzv11.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/mzzv42z.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/net12.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/noswot.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/nsrand-ipx.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/opt1217.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/p2756.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/pk1.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/pp08a.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/pp08aCUTS.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/qiu.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/roll3000.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/rout.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/set1ch.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/timtab1.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/tr12-30.mps.gz
Binary file not shown.
Binary file added slow_tests/data/miplib2003/vpm2.mps.gz
Binary file not shown.
Loading

0 comments on commit 33d846b

Please sign in to comment.