-
Notifications
You must be signed in to change notification settings - Fork 20
/
lp_solvers.py
executable file
·164 lines (122 loc) · 4.29 KB
/
lp_solvers.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
Implement a few MIP solvers, based on benchmark found on <https://scip.zib.de/>
SCIP solver is ~16x faster than GLPK solver.
However, I found in rare cases it will segfault.
Therefore the default is SCIP, the program will switch to GLPK solver for crashed cases.
The input lp_data is assumed in .lp format, see below
>>> lp_data = '''
... Maximize
... 5 x1 + 3 x2 + 2 x3
... Subject to
... x2 + x3 <= 1
... Binary
... x1
... x2
... x3
... End'''
>>> print GLPKSolver(lp_data).results
[0, 1]
>>> print SCIPSolver(lp_data).results
[0, 1]
"""
import os
import os.path as op
import sys
from subprocess import call
class AbstractMIPSolver(object):
"""
Base class for LP solvers
"""
def __init__(self, lp_data,
work_dir=op.join(op.dirname(__file__),"work"),
verbose=False):
if not os.path.isdir(work_dir):
os.mkdir(work_dir)
lpfile = work_dir + "/data.lp" # problem instance
print >>sys.stderr, "write MIP instance to '%s'" % lpfile
fw = file(lpfile, "w")
fw.write(lp_data)
fw.close()
retcode, outfile = self.run(lpfile, work_dir, verbose=verbose)
if retcode < 0:
self.results = []
else:
self.results = self.parse_output(outfile)
if self.results:
print >>sys.stderr, "optimized objective value (%d)" % self.obj_val
def run(self, lp_data, work_dir):
pass
def parse_output():
pass
class GLPKSolver(AbstractMIPSolver):
"""
GNU Linear Programming Kit (GLPK) solver, wrapper for calling GLPSOL executable
"""
def run(self, lpfile, work_dir="work", verbose=False):
outfile = work_dir + "/data.lp.out" # verbose output
listfile = work_dir +"/data.lp.list" # simple output
# cleanup in case something wrong happens
for f in (outfile, listfile):
if os.path.exists(f):
os.remove(f)
cmd = "glpsol --cuts --fpump --lp %s -o %s -w %s"
if not verbose: cmd += " >/dev/null"
retcode = call(cmd % (lpfile, outfile, listfile), shell=True)
if retcode==127:
print >>sys.stderr, "\nError:"
print >>sys.stderr, "You need to install program `glpsol' on your path"
print >>sys.stderr, "[https://www.gnu.org/software/glpk/]"
return -1, None
return retcode, listfile
def parse_output(self, listfile):
filtered_list = []
fp = file(listfile)
header = fp.readline()
columns, rows = header.split()
rows = int(rows)
data = fp.readlines()
self.obj_val = int(data[0].split()[-1])
# the info are contained in the last several lines
results = [int(x) for x in data[-rows:]]
results = [i for i, x in enumerate(results) if x==1]
return results
class SCIPSolver(AbstractMIPSolver):
"""
SCIP solver, wrapper for calling SCIP executable
"""
def run(self, lpfile, work_dir="work", verbose=False):
outfile = work_dir + "/data.lp.out" # verbose output
if os.path.exists(outfile):
os.remove(outfile)
cmd = "scip -f %s -l %s"
if not verbose:
cmd += " >/dev/null"
retcode = call(cmd % (lpfile, outfile), shell=True)
if retcode==127:
print >>sys.stderr, "\nError:"
print >>sys.stderr, "You need to install program `scip' on your path"
print >>sys.stderr, "[https://scip.zib.de/]"
return -1, None
return retcode, outfile
def parse_output(self, outfile):
fp = file(outfile)
for row in fp:
if row.startswith("objective value"):
obj_row = row
break
results = []
for row in fp:
#objective value: 8
#x1 1 (obj:5)
#x2 1 (obj:3)
if row.strip()=="": break # blank line ends the section
x = row.split()[0]
results.append(int(x[1:])-1) # 0-based indexing
if results:
self.obj_val = int(obj_row.split(":")[1])
return results
if __name__ == '__main__':
import doctest
doctest.testmod()