-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathcolumn_generation.py
More file actions
152 lines (124 loc) · 4.62 KB
/
column_generation.py
File metadata and controls
152 lines (124 loc) · 4.62 KB
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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import types
from amplpy import AMPL, Parameter
# Example description
# This example uses Gilmore-Gomory column generation procedure for
# the cutting-stock (roll trim) problem.
# Keeps the knapsack problem as an AMPL model, and adds columns
# to the cutting-stock via ampls; finally it imports the results in AMPL.
import amplpy_copt as ampls
SOLVER = "copt"
WIDTHS = [20, 45, 50, 55, 75]
ORDERS = [48, 35, 24, 10, 8]
RAW_WIDTH = 110
# We represent a pattern as a dictionary width -> numberofcuts
# where we only store the nonzeroes
def generate_default_patterns(widths : list):
return [{w : RAW_WIDTH // w} for w in widths]
def add_patterns(ampl: AMPL, patterns : list):
npat = int(ampl.param["nPatterns"].value())
ampl.param["nPatterns"].set(len(patterns))
ampl.eval("update data rolls;")
ampl.param['rolls'] = {
(w, 1+i): n
for i in range(npat,len(patterns))
for w, n in patterns[i].items()
}
def cutting_stock_model():
"""Create an instance of AMPL and a model"""
a = AMPL()
a.option["presolve"]=0
a.option["solver"]=SOLVER
a.eval("""param nPatterns integer >= 0;
set PATTERNS = 1..nPatterns;
set WIDTHS;
param order{ WIDTHS } >= 0;
param rawWidth;
param rolls{ WIDTHS,PATTERNS } >= 0, default 0;
var Cut{ PATTERNS } integer >= 0;
minimize TotalRawRolls : sum{ p in PATTERNS } Cut[p] + to_come;
subject to OrderLimits{ w in WIDTHS }:
sum{ p in PATTERNS } rolls[w, p] * Cut[p]+ to_come >= order[w];""")
a.param["nPatterns"]=0
a.set["WIDTHS"]=WIDTHS
a.param["order"]=ORDERS
a.param["rawWidth"]=RAW_WIDTH
return a
def generate_pattern(ampl: AMPL, duals : list):
ampl.param["price"]=duals
ampl.solve()
ampl.eval("display price;")
reduced_cost=ampl.obj['Reduced_Cost'].value()
if reduced_cost < -0.00001:
return ampl.var["Use"].getValues().to_dict()
return None
def knapsack_model():
a = AMPL()
a.option["presolve"]=0
a.option["solver"]=SOLVER
a.eval("""set WIDTHS;
param rawWidth;
param price {WIDTHS} default 0.0;
var Use {WIDTHS} integer >= 0;
minimize Reduced_Cost: 1 - sum{ i in WIDTHS } price[i] * Use[i];
subject to Width_Limit: sum{ i in WIDTHS } i * Use[i] <=rawWidth;""")
a.set["WIDTHS"]=WIDTHS
a.param["rawWidth"]=RAW_WIDTH
return a
USE_AMPLS_ENTITY_RECORDING = True
def run_example():
# Decalare the two models in AMPL
cs = cutting_stock_model()
knap = knapsack_model()
# Generate starting patterns (each patterns simply cuts the roll
# all at the same width)
patterns = generate_default_patterns(WIDTHS)
# Add them to the AMPL instance
add_patterns(cs, patterns)
# Export the (relaxed) cutting stock model to ampls
cs.option["relax_integrality"]=1
options = ["outlev=1", "pre:maxrounds=0"] if SOLVER=="scip" else None
ampls_cs = cs.to_ampls(SOLVER, options)
while True: # Column generation happens in the solver
# Optimize the cutting stock model, get the dual vector,
# use it in the knapsack model (in AMPL) to generate
# a new pattern
ampls_cs.optimize()
duals = ampls_cs.get_dual_vector()
print(duals)
p=generate_pattern(knap, duals)
if p is None: break # No new pattern has been found, finish
# Keep track of the patterns we add
patterns.append(p)
# Create the variable indices and coefficients
index=0
indices=[]
coeffs=[]
for _, c in p.items():
if c != 0:
indices.append(index)
coeffs.append(c)
index+=1
# Add variable in the cutting stock model
# Note the last parameter: we add the variable as "relaxed", so that it is
v=ampls_cs.addVariable(indices, coeffs, 0, 10000000,
1, ampls.VarType.Integer, True)
if USE_AMPLS_ENTITY_RECORDING:
ampls_cs.record(v)
# Add all the patterns that has been generated in the loop above to
# the AMPL version of the cutting stock model, then solve the
# integer problem to get the final result
if USE_AMPLS_ENTITY_RECORDING:
cs.import_ampls_solution(ampls_cs, import_entities=True)
else:
add_patterns(cs, patterns)
cs.import_ampls_solution(ampls_cs)
cs.option["relax_integrality"]=0
cs.solve()
cs.display("TotalRawRolls, rolls;")
cs.display("{i in 1.._nvars} _varname[i]");
assert cs.get_current_objective().value() == 47
run_example()