Skip to content

Commit 0f1e2fb

Browse files
committed
* Added cutpool_start to highspy
* Added some helper functions to access cuts in highspy * Added another highspy callback example
1 parent 36645a4 commit 0f1e2fb

File tree

3 files changed

+94
-0
lines changed

3 files changed

+94
-0
lines changed

examples/callback_gap.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# An example of solving the Generalized Assignment Problem (GAP) using highspy
2+
# Also demonstrates how to use callbacks to print cuts as they are found
3+
from highspy import *
4+
import numpy as np
5+
6+
#
7+
# GAP instances can be taken from:
8+
# http://people.brunel.ac.uk/~mastjjb/jeb/orlib/gapinfo.html
9+
#
10+
# Expected format:
11+
# - number machines
12+
# _ number jobs
13+
# - cost of each job on each machine
14+
# - size of each job on each machine
15+
# - capacity of each machine
16+
17+
input_data = ''' 5 15
18+
17 21 22 18 24 15 20 18 19 18 16 22 24 24 16
19+
23 16 21 16 17 16 19 25 18 21 17 15 25 17 24
20+
16 20 16 25 24 16 17 19 19 18 20 16 17 21 24
21+
19 19 22 22 20 16 19 17 21 19 25 23 25 25 25
22+
18 19 15 15 21 25 16 16 23 15 22 17 19 22 24
23+
8 15 14 23 8 16 8 25 9 17 25 15 10 8 24
24+
15 7 23 22 11 11 12 10 17 16 7 16 10 18 22
25+
21 20 6 22 24 10 24 9 21 14 11 14 11 19 16
26+
20 11 8 14 9 5 6 19 19 7 6 6 13 9 18
27+
8 13 13 13 10 20 25 16 16 17 10 10 5 12 23
28+
36 34 38 27 33
29+
'''.split()
30+
31+
# read from file
32+
# with open(r"filename", 'r') as input_file:
33+
# input_data = input_file.read().split()
34+
35+
# parse input
36+
M = int(input_data[0])
37+
J = int(input_data[1])
38+
idx = np.cumsum([2, M * J, M * J, M])
39+
cost = np.array(input_data[idx[0]:idx[1]], dtype=np.float64).reshape((M, J))
40+
size = np.array(input_data[idx[1]:idx[2]], dtype=np.float64).reshape((M, J))
41+
capacity = np.array(input_data[idx[2]:idx[3]], dtype=np.float64)
42+
43+
# build model
44+
model = Highs()
45+
46+
X = model.addBinaries(M, J)
47+
48+
# assign each job to exactly one machine
49+
model.addConstrs(X[:, j].sum() == 1 for j in range(J))
50+
51+
# each machine can only take jobs that fit
52+
model.addConstrs((size[m, :] * X[m, :]).sum() <= capacity[m] for m in range(M))
53+
54+
# print out the cuts as we solve
55+
def printCuts(e):
56+
for c in e.cuts:
57+
print(c)
58+
59+
model.cbMipGetCutPool += printCuts
60+
61+
# minimize total cost
62+
model.minimize((cost * X).sum())
63+
64+
# print out solution (i.e., which jobs are assigned to which machines)
65+
print(model.getObjectiveValue())
66+
67+
np.set_printoptions(formatter={'all': lambda x: str(x)})
68+
69+
for m in range(M):
70+
jobs_on_machine = np.nonzero(model.vals(X[m, :]) > 0)[0]
71+
print(jobs_on_machine)

src/highs_bindings.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1669,6 +1669,9 @@ PYBIND11_MODULE(_core, m, py::mod_gil_not_used()) {
16691669
"mip_solution", make_readonly_ptr(&HighsCallbackOutput::mip_solution))
16701670
.def_readwrite("cutpool_num_col", &HighsCallbackOutput::cutpool_num_col)
16711671
.def_readwrite("cutpool_num_cut", &HighsCallbackOutput::cutpool_num_cut)
1672+
.def_property_readonly(
1673+
"cutpool_start",
1674+
make_readonly_ptr(&HighsCallbackOutput::cutpool_start))
16721675
.def_property_readonly(
16731676
"cutpool_index",
16741677
make_readonly_ptr(&HighsCallbackOutput::cutpool_index))

src/highspy/highs.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,6 +1401,26 @@ def val(
14011401
"""
14021402
return Highs.internal_get_value(self.data_out.mip_solution, var_expr)
14031403

1404+
def cut(self, index: int):
1405+
"""
1406+
Gets the cut pool for the given index.
1407+
"""
1408+
cut = highs_linear_expression()
1409+
1410+
if self.data_out and index >= 0 and index < self.data_out.cutpool_num_cut:
1411+
start, end = self.data_out.cutpool_start[index:index+2]
1412+
cut.bounds = (self.data_out.cutpool_lower[index], self.data_out.cutpool_upper[index])
1413+
cut.idxs = list(map(int, self.data_out.cutpool_index[start:end]))
1414+
cut.vals = list(map(float, self.data_out.cutpool_value[start:end]))
1415+
1416+
return cut
1417+
1418+
@property
1419+
def cuts(self):
1420+
"""
1421+
Gets all cuts in the cut pool.
1422+
"""
1423+
return [self.cut(i) for i in range(self.data_out.cutpool_num_cut)]
14041424

14051425
class HighsCallback(object):
14061426
__slots__ = ["callbacks", "user_callback_data", "highs", "callback_type"]

0 commit comments

Comments
 (0)