144 lines
4.5 KiB
Python
144 lines
4.5 KiB
Python
"""
|
|
Columnwise 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"]
|
|
numPatterns = 0
|
|
|
|
def __init__(self, name, lengths = None):
|
|
self.name = name
|
|
self.lengthsdict = dict(zip(self.lenOpts,lengths))
|
|
Pattern.numPatterns += 1
|
|
|
|
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 createMaster():
|
|
|
|
rollData = {#Length Demand SalePrice
|
|
"5": [150, 0.25],
|
|
"7": [200, 0.33],
|
|
"9": [300, 0.40]}
|
|
|
|
(rollDemand,surplusPrice) = splitDict(rollData)
|
|
|
|
# The variable 'prob' is created
|
|
prob = LpProblem("MasterSpongeRollProblem",LpMinimize)
|
|
|
|
# The variable 'obj' is created and set as the LP's objective function
|
|
obj = LpConstraintVar("Obj")
|
|
prob.setObjective(obj)
|
|
|
|
# The constraints are initialised and added to prob
|
|
constraints = {}
|
|
for l in Pattern.lenOpts:
|
|
constraints[l]= LpConstraintVar("Min" + str(l), LpConstraintGE, rollDemand[l])
|
|
prob += constraints[l]
|
|
|
|
# The surplus variables are created
|
|
surplusVars = []
|
|
for i in Pattern.lenOpts:
|
|
surplusVars += [LpVariable("Surplus "+ i,0,None,LpContinuous, -surplusPrice[i] * obj - constraints[i])]
|
|
|
|
return prob,obj,constraints
|
|
|
|
def addPatterns(obj,constraints,newPatterns):
|
|
|
|
# A list called Patterns is created to contain all the Pattern class
|
|
# objects created in this function call
|
|
Patterns = []
|
|
for i in newPatterns:
|
|
|
|
# The new patterns are checked to see that their length does not exceed
|
|
# the total roll length
|
|
lsum = 0
|
|
for j,k in zip(i,Pattern.lenOpts):
|
|
lsum += j * int(k)
|
|
if lsum > Pattern.totalRollLength:
|
|
raise "Length Options too large for Roll"
|
|
|
|
# The number of rolls of each length in each new pattern is printed
|
|
print "P"+str(Pattern.numPatterns),"=",i
|
|
|
|
# The patterns are instantiated as Pattern objects
|
|
Patterns += [Pattern("P" + str(Pattern.numPatterns),i)]
|
|
|
|
# The pattern variables are created
|
|
pattVars = []
|
|
for i in Patterns:
|
|
pattVars += [LpVariable("Pattern "+i.name,0,None,LpContinuous, (i.cost - Pattern.trimValue*i.trim()) * obj\
|
|
+ lpSum([constraints[l]*i.lengthsdict[l] for l in Pattern.lenOpts]))]
|
|
|
|
def masterSolve(prob,relax = True):
|
|
|
|
# Unrelaxes the Integer Constraint
|
|
if not relax:
|
|
for v in prob.variables():
|
|
v.cat = LpInteger
|
|
|
|
# The problem is solved and rounded
|
|
prob.solve(PULP_CBC_CMD())
|
|
prob.roundSolution()
|
|
|
|
if relax:
|
|
# A dictionary of dual variable values is returned
|
|
duals = {}
|
|
for i,name in zip(Pattern.lenOpts,["Min5","Min7","Min9"]):
|
|
duals[i] = prob.constraints[name].pi
|
|
return duals
|
|
else:
|
|
# A dictionary of variable values and the objective value are returned
|
|
varsdict = {}
|
|
for v in prob.variables():
|
|
varsdict[v.name] = v.varValue
|
|
|
|
return value(prob.objective), varsdict
|
|
|
|
def subSolve(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()
|
|
|
|
newPatterns = []
|
|
# Check if there are more patterns which would reduce the master LP objective function further
|
|
if value(prob.objective) < -10**-5:
|
|
varsdict = {}
|
|
for v in prob.variables():
|
|
varsdict[v.name] = v.varValue
|
|
# Adds the new pattern to the newPatterns list
|
|
newPatterns += [[int(varsdict["Roll_Length_5"]),int(varsdict["Roll_Length_7"]),int(varsdict["Roll_Length_9"])]]
|
|
|
|
return newPatterns
|