120 lines
3.9 KiB
Python
120 lines
3.9 KiB
Python
"""
|
|
Column Generation Functions
|
|
|
|
Authors: Antony Phillips, Dr Stuart Mitchell 2008
|
|
"""
|
|
|
|
# Import PuLP modeler functions
|
|
from pulp import *
|
|
|
|
class Pattern:
|
|
"""
|
|
Information on a specific pattern in the SpongeRoll Problem
|
|
"""
|
|
cost = 1
|
|
trimValue = 0.04
|
|
totalRollLength = 20
|
|
lenOpts = ["5", "7", "9"]
|
|
|
|
def __init__(self, name, lengths = None):
|
|
self.name = name
|
|
self.lengthsdict = dict(zip(self.lenOpts,lengths))
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
def trim(self):
|
|
return Pattern.totalRollLength - sum([int(i)*int(self.lengthsdict[i]) for i in self.lengthsdict])
|
|
|
|
def masterSolve(Patterns, rollData, relax = True):
|
|
|
|
# The rollData is made into separate dictionaries
|
|
(rollDemand,surplusPrice) = splitDict(rollData)
|
|
|
|
# The variable 'prob' is created
|
|
prob = LpProblem("Cutting Stock Problem",LpMinimize)
|
|
|
|
# vartype represents whether or not the variables are relaxed
|
|
if relax:
|
|
vartype = LpContinuous
|
|
else:
|
|
vartype = LpInteger
|
|
|
|
# The problem variables are created
|
|
pattVars = LpVariable.dicts("Pattern", Patterns, 0, None, vartype)
|
|
surplusVars = LpVariable.dicts("Surplus", Pattern.lenOpts, 0, None, vartype)
|
|
|
|
# The objective function is entered: (the total number of large rolls used * the cost of each) -
|
|
# (the value of the surplus stock) - (the value of the trim)
|
|
prob += lpSum([pattVars[i]*Pattern.cost for i in Patterns]) - lpSum([surplusVars[i]*surplusPrice[i]\
|
|
for i in Pattern.lenOpts]) - lpSum([pattVars[i]*i.trim()*Pattern.trimValue for i in Patterns])
|
|
|
|
# The demand minimum constraint is entered
|
|
for j in Pattern.lenOpts:
|
|
prob += lpSum([pattVars[i]*i.lengthsdict[j] for i in Patterns]) - surplusVars[j]>=rollDemand[j],"Min%s"%j
|
|
|
|
# The problem is solved
|
|
prob.solve()
|
|
|
|
# The variable values are rounded
|
|
prob.roundSolution()
|
|
|
|
if relax:
|
|
# Creates a dual variables list
|
|
duals = {}
|
|
for name,i in zip(['Min5','Min7','Min9'],Pattern.lenOpts):
|
|
duals[i] = prob.constraints[name].pi
|
|
|
|
return duals
|
|
|
|
else:
|
|
# Creates a dictionary of the variables and their values
|
|
varsdict = {}
|
|
for v in prob.variables():
|
|
varsdict[v.name] = v.varValue
|
|
|
|
# The number of rolls of each length in each pattern is printed
|
|
for i in Patterns:
|
|
print(i, " = %s"%[i.lengthsdict[j] for j in Pattern.lenOpts])
|
|
|
|
return value(prob.objective), varsdict
|
|
|
|
def subSolve(Patterns, duals):
|
|
|
|
# The variable 'prob' is created
|
|
prob = LpProblem("SubProb",LpMinimize)
|
|
|
|
# The problem variables are created
|
|
vars = LpVariable.dicts("Roll Length", Pattern.lenOpts, 0, None, LpInteger)
|
|
|
|
trim = LpVariable("Trim", 0 ,None,LpInteger)
|
|
|
|
# The objective function is entered: the reduced cost of a new pattern
|
|
prob += (Pattern.cost - Pattern.trimValue*trim) - lpSum([vars[i]*duals[i] for i in Pattern.lenOpts]), "Objective"
|
|
|
|
# The conservation of length constraint is entered
|
|
prob += lpSum([vars[i]*int(i) for i in Pattern.lenOpts]) + trim == Pattern.totalRollLength, "lengthEquate"
|
|
|
|
# The problem is solved
|
|
prob.solve()
|
|
|
|
# The variable values are rounded
|
|
prob.roundSolution()
|
|
|
|
# The new pattern is written to a dictionary
|
|
varsdict = {}
|
|
newPattern = {}
|
|
for v in prob.variables():
|
|
varsdict[v.name] = v.varValue
|
|
for i,j in zip(Pattern.lenOpts,["Roll_Length_5","Roll_Length_7","Roll_Length_9"]):
|
|
newPattern[i] = int(varsdict[j])
|
|
|
|
# Check if there are more patterns which would reduce the master LP objective function further
|
|
if value(prob.objective) < -10**-5:
|
|
morePatterns = True # continue adding patterns
|
|
Patterns += [Pattern("P" + str(len(Patterns)), [newPattern[i] for i in ["5","7","9"]])]
|
|
else:
|
|
morePatterns = False # all patterns have been added
|
|
|
|
return Patterns, morePatterns
|