Commit 01200f43 authored by Georgios Dagkakis's avatar Georgios Dagkakis

Demand planning algorith added in application layer

parent a36c0b18
# ===========================================================================
# Copyright 2015 Dublin City University
#
# This file is part of DREAM.
#
# DREAM is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DREAM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with DREAM. If not, see <http://www.gnu.org/licenses/>.
# ===========================================================================
'''
Created on 15 Dec 2014
@author: Anna
'''
from AllocationRoutine_2 import AllocationRoutine2
from AllocationRoutine_Forecast import AllocationRoutine_Forecast
from Allocation_GA import Allocation_GA
from Allocation_ACO import Allocation_ACO
from Globals import G
import tablib
def AllocManagement_Hybrid():
# allocate items based on type and priority level
print G.priorityList
for priority in G.priorityList['order']:
ACOresults = tablib.Dataset(title='ACO_'+'order'+'_'+str(priority))
ACOresults.headers = ('Week', 'generation', 'replication', 'antID', 'excess', 'lateness', 'earliness', 'targetUtil', 'minUtil')
# starting from first week in planning horizon, complete the allocation of all orders within the same priority group
for week in G.WeekList:
if week in G.sortedOrders['order'][priority]:
print 'order week', week
ACOresults = Allocation_ACO(week, G.sortedOrders['order'][priority][week],'order',ACOresults)
G.reportResults.add_sheet(ACOresults)
print 'prio forecast', G.priorityList['forecast']
for priority in G.priorityList['forecast']:
print 'weeks', G.sortedOrders['forecast'][priority]
for week in G.WeekList:
if week in G.sortedOrders['forecast'][priority]:
print 'forecast week', week
AllocationRoutine_Forecast(week, G.sortedOrders['forecast'][priority][week],'forecast')
def AllocManagement_Hybrid2():
# allocate items based on type and priority level
for priority in G.priorityList['order']:
ACOresults = tablib.Dataset(title='ACO_'+'order'+'_'+str(priority))
ACOresults.headers = ('Week', 'generation', 'replication', 'antID', 'excess', 'lateness', 'earliness', 'targetUtil', 'minUtil')
# starting from first week in planning horizon, complete the allocation of all orders within the same priority group
for week in G.WeekList:
if week in G.sortedOrders['order'][priority]:
print 'order week', week
if G.ACO:
ACOresults = Allocation_ACO(week, G.sortedOrders['order'][priority][week],'order',ACOresults)
else:
AllocationRoutine2(week, G.sortedOrders['order'][priority][week],'order')
if G.ACO:
G.reportResults.add_sheet(ACOresults)
print 'start forecast allocation'
for priority in G.priorityList['forecast']:
GAresults = tablib.Dataset(title='GA_'+'order'+'_'+str(priority))
GAresults.headers = ('Week', 'generation', 'replication', 'antID', 'excess', 'lateness', 'earliness', 'targetUtil', 'minUtil','sequence')
for week in G.WeekList:
if week in G.sortedOrders['forecast'][priority]:
print 'forecast week', week
itemList = G.sortedOrders['forecast'][priority][week]
# if GA is required perform order sequence optimisation combined with internal LP optimisation
if G.GA:
GAresults = Allocation_GA(week,itemList,'forecast',GAresults)
# if GA is not require perform allocation with internal LP optimisation
else:
# get the list of orders ID
orderList = {}
orderIDlist = []
for item in itemList:
orderList[item['orderID']]=item
orderIDlist.append(item['orderID'])
AllocationRoutine_Forecast(week,orderList,'forecast',{'seq':orderIDlist})
if G.GA:
G.reportResults.add_sheet(GAresults)
# ===========================================================================
# Copyright 2015 Dublin City University
#
# This file is part of DREAM.
#
# DREAM is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DREAM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with DREAM. If not, see <http://www.gnu.org/licenses/>.
# ===========================================================================
'''
Created on 12 Dec 2014
@author: Anna
'''
from Globals import G
from pulp import *
from os import remove
from glob import glob
def Allocation_IP(item, week, previousAss, capacity, weightFactor):
MAlist = item['MAlist']
# calculate number of units to be allocated
allQty = item['Qty']
for ma in previousAss.keys():
allQty -= previousAss[ma]
assert(allQty>0)
# define LP problem
prob = LpProblem("disaggregation", LpMaximize)
# declare variables (first set of variables: MA qty)
MA_var = LpVariable.dicts("MA",MAlist,0,cat='Integer')
# print 'ma ver', MA_var
#============================
# multi - objective function
#============================
#___________________________________________________
# first objective: max SP units allocated to a week
BS = [float(G.BatchSize[ma][week]) for ma in MAlist]
h1=[MA_var[ma]*(weightFactor[0]/min(BS)) for ma in MAlist]
# print 'h1', h1, allQty
#_________________________________________________
# second objective: balance bottleneck utilisation
# second set of variables (Delta_TargetUt represents the delta utilisation wrt target utilisation
Delta_targetUt = LpVariable.dicts("target",G.Bottlenecks)
Util = LpVariable.dicts("utilisation",G.Bottlenecks)
# remaining capacity and utilisation calculation
capDict_obj = {} #reports the remaining capacity corresponding with MA allocation
utilisation = {} # reports bottleneck utilisation
requiredCapacity={}
for bottleneck in G.Bottlenecks:
requiredCapacity[bottleneck] = []
for ma in MAlist:
if bottleneck in G.RouteDict[ma]:
requiredCapacity[bottleneck].append(G.RouteDict[ma][bottleneck][week] * MA_var[ma] * G.BatchSize[ma][week])
capDict_obj[bottleneck] = lpSum([-1*capacity[bottleneck][week]]+[requiredCapacity[bottleneck]]+[G.Capacity[bottleneck][week]['OriginalCapacity']])
utilisation[bottleneck] = 1.0/float(G.Capacity[bottleneck][week]['OriginalCapacity']) *capDict_obj[bottleneck]
Util[bottleneck] = utilisation[bottleneck]*-1
# delta target utilisation calculation (through definition of constraints)
prob += lpSum([(utilisation[bottleneck] - G.Capacity[bottleneck][week]['targetUtilisation'])/float(G.Capacity[bottleneck][week]['targetUtilisation'])]) >= Delta_targetUt[bottleneck]
prob += lpSum([(G.Capacity[bottleneck][week]['targetUtilisation'] - utilisation[bottleneck])/float(G.Capacity[bottleneck][week]['targetUtilisation'])]) >= Delta_targetUt[bottleneck]
h1.append(Util[bottleneck]*weightFactor[1] for bottleneck in G.Bottlenecks)
h1.append(Delta_targetUt[bottleneck]*weightFactor[2] for bottleneck in G.Bottlenecks)
# third set of variables (Delta_Ut represents the delta between target utilisation
Delta_Ut = LpVariable.dicts("DeltaTarget",[(b1,b2) for i1, b1 in enumerate(G.Bottlenecks) for b2 in G.Bottlenecks[i1+1:]])
for i1, b1 in enumerate(G.Bottlenecks):
for b2 in G.Bottlenecks[i1+1:]:
prob += lpSum([Util[b1],-1*Util[b2]]) >= Delta_Ut[(b1,b2)]
prob += lpSum([Util[b2],-1*Util[b1]]) >= Delta_Ut[(b1,b2)]
#prob += lpSum([Delta_targetUt[b1],-1*Delta_targetUt[b2]]) >= Delta_Ut[(b1,b2)]
#prob += lpSum([Delta_targetUt[b2], -1*Delta_targetUt[b1]]) >= Delta_Ut[(b1,b2)]
# aggregate objective
h1.append(Delta_Ut[(b1,b2)]*weightFactor[3] for i1, b1 in enumerate(G.Bottlenecks) for b2 in G.Bottlenecks[i1+1:])
#___________________________________________________________________________________
# third objective: support proportional disaggregation of SP into corresponding MAs
# create set of variables reporting the delta assignment across the MAs belonging to a SP
Delta_MA = LpVariable.dicts("SPdistribution",MAlist)
# calculate the delta assignment of MAs corresponding to SPs (through constraints)
for ma in MAlist:
if ma in item['suggestedMA']:
prob += lpSum((previousAss[ma] + MA_var[ma] * G.BatchSize[ma][week] - item['suggestedMA'][ma])/ item['Qty']) >= Delta_MA[ma]
prob += lpSum((item['suggestedMA'][ma] - previousAss[ma] - MA_var[ma] * G.BatchSize[ma][week])/ item['Qty']) >= Delta_MA[ma]
#_____________________________
# aggregate and set objectives
h1.append(Delta_MA[ma]*0.5 for ma in MAlist)
prob += lpSum(h1)
#=================
# set constraints
#=================
# print 'batch size', G.BatchSize[ma][week], 'qty', allQty
# production constraints
for ma in MAlist:
prob += lpSum([MA_var[ma2]*G.BatchSize[ma2][week] for ma2 in MAlist]) <= allQty+G.BatchSize[ma][week]-1
# capacity constraints
for bottleneck in G.Bottlenecks:
prob += lpSum([MA_var[ma]*G.RouteDict[ma][bottleneck][week]*G.BatchSize[ma][week] for ma in MAlist if bottleneck in G.RouteDict[ma]]) <= capacity[bottleneck][week]- 0.1
#_______________________________________
# write the problem data to an .lp file.
prob.writeLP("IPifx.lp")
prob.solve()
allocation = {}
for ma in MAlist:
allocation[ma] = MA_var[ma].varValue * G.BatchSize[ma][week]
# remove lp files
files = glob('*.mps')
for f in files:
remove(f)
files = glob('*.lp')
for f in files:
remove(f)
return allocation
# ===========================================================================
# Copyright 2015 Dublin City University
#
# This file is part of DREAM.
#
# DREAM is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DREAM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with DREAM. If not, see <http://www.gnu.org/licenses/>.
# ===========================================================================
'''
Created on 27 Nov 2014
@author: Anna
'''
from Globals import G
from Allocation_3 import Allocation2
from numpy import mean, array, absolute, std
from operator import itemgetter
from copy import deepcopy
def AllocationRoutine2(initialWeek, itemList, itemType):
excess = []
# repeat allocation procedure for all items in the list
for item in itemList:
# print 'item', item['orderID']
#================================================
# Allocation step 1...allocation at current Week
#================================================
Results = {}
step = 1
ind = G.WeekList.index(initialWeek)
weekList = [initialWeek]
capacity = deepcopy(G.CurrentCapacityDict)
qty = item['Qty']
Allocation = []
earliness = 0
lateness = 0
while step <= 3:
possibleSolutions = []
if step == 2:
weekList = [G.WeekList[i] for i in range(ind-1, max(-1,ind-G.maxEarliness-1), -1)]
if step == 3:
weekList = [G.WeekList[i] for i in range(ind+1, min(G.planningHorizon,ind+G.maxLateness+1))]
if len(weekList) == 0:
step+=1
continue
# check different MAs
for ma in item['MAlist']:
if step > 1:
capacity = deepcopy(Results[ma]['remainingCap'])
qty = deepcopy(Results[ma]['remainingUnits'])
Allocation = deepcopy(Results[ma]['Allocation'])
earliness = deepcopy(Results[ma]['earliness'])
lateness = deepcopy(Results[ma]['lateness'])
else:
capacity = deepcopy(G.CurrentCapacityDict)
qty = item['Qty']
Allocation = []
earliness = 0
lateness = 0
# try allocation to ma
Results[ma] = Allocation2(ma, qty, weekList, capacity, G.incompleteBatches, earliness, lateness, Allocation, initialWeek)
if Results[ma]['remainingUnits'] == 0: # if the allocation is not successful delete the record of the allocation results
possibleSolutions.append(ma)
# chose best MA
if G.minDeltaUt:
chosenMA = choseMA2(Results, possibleSolutions, weekList)
else:
chosenMA = choseMA(Results, possibleSolutions, weekList)
# confirm the solution
if chosenMA != None:
G.CurrentCapacityDict = Results[chosenMA]['remainingCap']
G.incompleteBatches = Results[chosenMA]['remUnits']
G.Earliness[initialWeek][chosenMA]['qty'].append(item['Qty'])
G.Earliness[initialWeek][chosenMA]['earliness'].append(float(Results[chosenMA]['earliness'])/item['Qty'])
G.Lateness[initialWeek][chosenMA]['qty'].append(item['Qty'])
G.Lateness[initialWeek][chosenMA]['lateness'].append(float(Results[chosenMA]['lateness'])/item['Qty'])
G.orders[item['orderID']]['Allocation'] = Results[chosenMA]['Allocation']
G.orders[item['orderID']]['Excess'] = False
G.orders[item['orderID']]['chosenMA'] = chosenMA
for allRep in Results[chosenMA]['Allocation']:
G.globalMAAllocation[chosenMA][allRep['week']][itemType][item['priority']] += allRep['units']
break
step += 1
if chosenMA == None:
excess.append(item)
G.Excess[item['sp']][initialWeek] += item['Qty']
G.orders[item['orderID']]['Allocation'] = []
G.orders[item['orderID']]['Excess'] = True
G.orders[item['orderID']]['chosenMA'] = None
# for orders add allocation information
if itemType == 'order':
if chosenMA == None:
G.OrderResults.append((item['ppos'], item['sp'], item['MAlist'], item['Week'], item['Customer'], item['Qty'], item['priority'], chosenMA, 'NaN', 'NaN', 'None'))
else:
G.OrderResults.append((item['ppos'], item['sp'], item['MAlist'], item['Week'], item['Customer'], item['Qty'], item['priority'], chosenMA, Results[chosenMA]['lateness'], Results[chosenMA]['earliness'], Results[chosenMA]['Allocation']))
if itemType == 'forecast':
if chosenMA == None:
G.forecastResults.append((item['ppos'], item['sp'], item['MAlist'], item['Week'], item['Qty'], item['priority'], chosenMA, 'NaN', 'NaN', 'None'))
else:
G.forecastResults.append((item['ppos'], item['sp'], item['MAlist'], item['Week'], item['Qty'], item['priority'], chosenMA, Results[chosenMA]['lateness'], Results[chosenMA]['earliness']/item['Qty'], Results[chosenMA]['Allocation']))
return excess
def choseMA(allResults, possibleSolutions, weeklist):
chosenMA = None
# if there is only one solution, chose the only solution available
if len(possibleSolutions) == 1:
chosenMA = possibleSolutions[0]
# if there are more than one successful allocations choose between them
if len(possibleSolutions) > 1:
res = []
for ma in possibleSolutions:
minUtil = []
targetUtil = []
for week in weeklist:
for bn in allResults[ma]['utilisation']:
if week in allResults[ma]['utilisation'][bn]:
minUtil.append(max(0, (G.Capacity[bn][week]['minUtilisation']-allResults[ma]['utilisation'][bn][week])/G.Capacity[bn][week]['minUtilisation']))
targetUtil.append((G.Capacity[bn][week]['targetUtilisation']-allResults[ma]['utilisation'][bn][week])/G.Capacity[bn][week]['targetUtilisation'])
res.append([ma, allResults[ma]['lateness'], std(array(targetUtil)), std(array(minUtil)), allResults[ma]['earliness']])
# order results...1st criterion: target utilisation (stdDev), 2nd criterion: min utilisation(stdDev)
sortedMA = sorted(res, key=itemgetter(1, 2, 3, 4))
chosenMA = sortedMA[0][0]
return chosenMA
def choseMA2(allResults, possibleSolutions, weeklist): # more similar to ACO selection criteria
chosenMA = None
# if there is only one solution, chose the only solution available
if len(possibleSolutions) == 1:
chosenMA = possibleSolutions[0]
# if there are more than one successful allocations choose between them
if len(possibleSolutions) > 1:
res = []
for ma in possibleSolutions:
minUtil = []
targetUtil = []
for bottleneck in G.Bottlenecks:
minU = []
targetU = []
for week in weeklist:
utilisation = float(G.Capacity[bottleneck][week]['OriginalCapacity'] - allResults[ma]['remainingCap'][bottleneck][week])/G.Capacity[bottleneck][week]['OriginalCapacity']
minU.append(max(0, (G.Capacity[bottleneck][week]['minUtilisation']-utilisation)/G.Capacity[bottleneck][week]['minUtilisation']))
targetU.append((utilisation - G.Capacity[bottleneck][week]['targetUtilisation'])/G.Capacity[bottleneck][week]['targetUtilisation'])
minUtil.append(mean(array(minU)))
targetUtil.append(mean(absolute(targetU)))
res.append([ma, allResults[ma]['lateness'], mean(array(targetUtil)), mean(array(minUtil)), allResults[ma]['earliness']])
# order results...1st criterion: target utilisation (stdDev), 2nd criterion: min utilisation(stdDev)
sortedMA = sorted(res, key=itemgetter(1, 2, 3, 4))
chosenMA = sortedMA[0][0]
return chosenMA
# ===========================================================================
# Copyright 2015 Dublin City University
#
# This file is part of DREAM.
#
# DREAM is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DREAM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with DREAM. If not, see <http://www.gnu.org/licenses/>.
# ===========================================================================
'''
Created on 28 Jan 2015
@author: Anna
'''
from Globals import G
from Allocation_3 import Allocation2
from UtilisationCalculation import utilisationCalc2, utilisationCalc1
from copy import deepcopy
# allocates orders of a give week/priority level implementing the ant choice for MAs
def AllocationRoutine_ACO(initialWeek, itemList, itemType, ant):
ACOexcess = 0
ACOcapacityDict = deepcopy(G.CurrentCapacityDict)
ACOincompleteBatches = deepcopy(G.incompleteBatches)
ACOearliness = 0
ACOlateness = 0
ACOtargetUtil = 0
ACOminUtil = 0
# repeat allocation procedure for all items in the list
for item in itemList:
#================================================
# Allocation step 1...allocation at current Week
#================================================
Results = {}
step = 1
ind = G.WeekList.index(initialWeek)
weekList = [initialWeek]
capacity = deepcopy(ACOcapacityDict)
qty = item['Qty']
Allocation = []
earliness = 0
lateness = 0
# FIXME: ma
ma = ant[item['orderID']]
chosenMA = None
while step <= 3:
if step == 2:
weekList = [G.WeekList[i] for i in range(ind-1, max(-1,ind-G.maxEarliness-1), -1)]
if step == 3:
weekList = [G.WeekList[i] for i in range(ind+1, min(G.planningHorizon,ind+G.maxLateness+1))]
if len(weekList) == 0:
step+=1
continue
# check different MAs
if step > 1:
capacity = deepcopy(Results[ma]['remainingCap'])
inBatches = deepcopy(Results[ma]['remUnits'])
qty = deepcopy(Results[ma]['remainingUnits'])
Allocation = deepcopy(Results[ma]['Allocation'])
earliness = deepcopy(Results[ma]['earliness'])
lateness = deepcopy(Results[ma]['lateness'])
else:
capacity = deepcopy(ACOcapacityDict)
inBatches = deepcopy(ACOincompleteBatches)
qty = item['Qty']
Allocation = []
earliness = 0
lateness = 0
# try allocation to ma
Results[ma] = Allocation2(ma, qty, weekList, capacity, inBatches, earliness, lateness, Allocation, initialWeek)
if Results[ma]['remainingUnits'] == 0: # if the allocation is not successful delete the record of the allocation results
chosenMA = ma
# confirm the solution
if chosenMA != None:
ACOcapacityDict = Results[chosenMA]['remainingCap']
ACOincompleteBatches = Results[chosenMA]['remUnits']
ACOearliness += Results[chosenMA]['earliness']/item['Qty']
ACOlateness += Results[chosenMA]['lateness']/item['Qty']
break
step += 1
if chosenMA == None:
ACOexcess += item['Qty']
if G.minDeltaUt:
ACOtargetUtil, ACOminUtil = utilisationCalc1(ACOcapacityDict, initialWeek, ind)
else:
ACOtargetUtil, ACOminUtil = utilisationCalc2(ACOcapacityDict, initialWeek, ind)
return {'ant':ant, 'excess':ACOexcess, 'earliness':ACOearliness, 'lateness':ACOlateness, 'targetUtil':ACOtargetUtil, 'minUtil':ACOminUtil}
# ===========================================================================
# Copyright 2015 Dublin City University
#
# This file is part of DREAM.
#
# DREAM is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DREAM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with DREAM. If not, see <http://www.gnu.org/licenses/>.
# ===========================================================================
'''
Created on 9 Dec 2014
@author: Anna
'''
from Globals import G
from Allocation_3 import Allocation2
from copy import deepcopy
def AllocationRoutine_Final(initialWeek, itemList, itemType, ant):
excess = []
# repeat allocation procedure for all items in the list
for item in itemList:
#================================================
# Allocation step 1...allocation at current Week
#================================================
Results = {}
step = 1
ind = G.WeekList.index(initialWeek)
weekList = [initialWeek]
capacity = deepcopy(G.CurrentCapacityDict)
qty = item['Qty']
Allocation = []
earliness = 0
lateness = 0
ma = ant[item['orderID']]
chosenMA = None
while step <= 3:
if step == 2:
weekList = [G.WeekList[i] for i in range(ind-1, max(-1,ind-G.maxEarliness-1), -1)]
if step == 3:
weekList = [G.WeekList[i] for i in range(ind+1, min(G.planningHorizon,ind+G.maxLateness+1))]
if len(weekList) == 0:
step+=1
continue
if step > 1:
capacity = deepcopy(Results[ma]['remainingCap'])
qty = deepcopy(Results[ma]['remainingUnits'])
Allocation = deepcopy(Results[ma]['Allocation'])
earliness = deepcopy(Results[ma]['earliness'])
lateness = deepcopy(Results[ma]['lateness'])
else:
capacity = deepcopy(G.CurrentCapacityDict)
qty = item['Qty']
Allocation = []
earliness = 0
lateness = 0
# try allocation to ma
Results[ma] = Allocation2(ma, qty, weekList, capacity, G.incompleteBatches, earliness, lateness, Allocation, initialWeek)
if Results[ma]['remainingUnits'] == 0: # if the allocation is not successful delete the record of the allocation results
chosenMA = ma
# confirm the solution
if chosenMA != None:
G.CurrentCapacityDict = Results[chosenMA]['remainingCap']
G.incompleteBatches = Results[chosenMA]['remUnits']
G.Earliness[initialWeek][chosenMA]['qty'].append(item['Qty'])
G.Earliness[initialWeek][chosenMA]['earliness'].append(float(Results[chosenMA]['earliness'])/item['Qty'])
G.Lateness[initialWeek][chosenMA]['qty'].append(item['Qty'])
G.Lateness[initialWeek][chosenMA]['lateness'].append(float(Results[chosenMA]['lateness'])/item['Qty'])
G.orders[item['orderID']]['Allocation'] = Results[chosenMA]['Allocation']
G.orders[item['orderID']]['Excess'] = False
G.orders[item['orderID']]['chosenMA'] = chosenMA
for allRep in Results[chosenMA]['Allocation']:
G.globalMAAllocation[chosenMA][allRep['week']][itemType][item['priority']] += allRep['units']
break
step += 1
if chosenMA == None:
excess.append(item)
G.Excess[item['sp']][initialWeek] += item['Qty']
G.orders[item['orderID']]['Allocation'] = []
G.orders[item['orderID']]['Excess'] = True
G.orders[item['orderID']]['chosenMA'] = None
# for orders add allocation information
if itemType == 'order':
if chosenMA == None:
G.OrderResults.append((item['ppos'], item['sp'], item['MAlist'], item['Week'], item['Customer'], item['Qty'], item['priority'], chosenMA, 'NaN', 'NaN', 'None'))
else:
G.OrderResults.append((item['ppos'], item['sp'], item['MAlist'], item['Week'], item['Customer'], item['Qty'], item['priority'], chosenMA, Results[chosenMA]['lateness'], Results[chosenMA]['earliness'], Results[chosenMA]['Allocation']))
if itemType == 'forecast':
if chosenMA == None:
G.forecastResults.append((item['ppos'], item['sp'], item['MAlist'], item['Week'], item['Qty'], item['priority'], chosenMA, 'NaN', 'NaN', 'None'))
else:
G.forecastResults.append((item['ppos'], item['sp'], item['MAlist'], item['Week'], item['Qty'], item['priority'], chosenMA, Results[chosenMA]['lateness'], Results[chosenMA]['earliness']/item['Qty'], Results[chosenMA]['Allocation']))
# ===========================================================================
# Copyright 2015 Dublin City University
#
# This file is part of DREAM.
#
# DREAM is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DREAM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with DREAM. If not, see <http://www.gnu.org/licenses/>.
# ===========================================================================
'''
Created on 15 Dec 2014
@author: Anna
'''
from copy import deepcopy
from Globals import G
from AllocationForecast_IP import Allocation_IP
from Allocation_3 import Allocation2
def AllocationRoutine_Forecast(initialWeek, itemList, itemType, seq):
EarlinessMA = {}
LatenessMA = {}
# repeat allocation procedure for all items in the list
for order in seq['seq']:
item = itemList[order]
# print 'item', item['orderID']
#================================================
# Allocation step 1...allocation at current Week
#================================================
Results = {}
step = 1
ind = G.WeekList.index(initialWeek)
weekList = [initialWeek]
capacity = deepcopy(G.CurrentCapacityDict)
qty = item['Qty']
Allocation = []
earliness = 0
lateness = 0
previousAss = {}
for ma in G.SPlist[item['sp']]:
previousAss[ma] = 0
while step <= 3 and qty>0:
if step == 2:
weekList = [G.WeekList[i] for i in range(ind-1, max(-1,ind-G.maxEarliness-1), -1)]
if step == 3:
weekList = [G.WeekList[i] for i in range(ind+1, min(G.planningHorizon,ind+G.maxLateness+1))]
# print 'weeklist', weekList
if len(weekList) == 0:
step+=1
continue
# check different MAs
for week in weekList:
# optimise MA allocation
spAllocation = Allocation_IP(item, week, previousAss, capacity,G.weightFactor)
# print 'all', spAllocation
# implement optimal MA solution
for ma in spAllocation.keys():
if spAllocation[ma]:
Results = Allocation2(ma, spAllocation[ma], [week], capacity, G.incompleteBatches, earliness, lateness, Allocation, initialWeek)
assert (Results['remainingUnits'] == 0)
# update variables
capacity = deepcopy(Results['remainingCap'])
qty -= spAllocation[ma]
Allocation = deepcopy(Results['Allocation'])
earliness = deepcopy(Results['earliness'])
lateness = deepcopy(Results['lateness'])
if ma not in EarlinessMA:
EarlinessMA[ma] = 0
LatenessMA[ma] = 0
EarlinessMA[ma] += max([0, initialWeek - week])*spAllocation[ma]
LatenessMA[ma] += max([0, week - initialWeek])*spAllocation[ma]
previousAss[ma] += spAllocation[ma]
if qty <= 0:
break
step += 1
# confirm results
if qty <= 0:
G.CurrentCapacityDict = Results['remainingCap']
G.incompleteBatches = Results['remUnits']
# print initialWeek, G.Earliness
for maT in EarlinessMA:
G.Earliness[initialWeek][maT]['qty'].append(item['Qty'])
G.Earliness[initialWeek][maT]['earliness'].append(EarlinessMA[maT]/item['Qty'])
G.Lateness[initialWeek][maT]['qty'].append(item['Qty'])
G.Lateness[initialWeek][maT]['lateness'].append(LatenessMA[maT]/item['Qty'])
G.orders[item['orderID']]['Allocation'] = Results['Allocation']
G.orders[item['orderID']]['Excess'] = False
chosenMA = []
for allMA in Results['Allocation']:
if allMA['ma'] not in chosenMA:
chosenMA.append(allMA['ma'])
G.orders[item['orderID']]['chosenMA'] = chosenMA
for allRep in Results['Allocation']:
G.globalMAAllocation[allRep['ma']][allRep['week']][itemType][item['priority']] += allRep['units']
if itemType == 'forecast':
G.forecastResults.append((item['ppos'], item['sp'], item['MAlist'], item['Week'], item['Qty'], item['priority'], chosenMA, Results['lateness'], Results['earliness']/item['Qty'], Results['Allocation']))
else:
G.Excess[item['sp']][initialWeek] += item['Qty']
G.orders[item['orderID']]['Allocation'] = []
G.orders[item['orderID']]['Excess'] = True
G.orders[item['orderID']]['chosenMA'] = None
if itemType == 'forecast':
G.forecastResults.append((item['ppos'], item['sp'], item['MAlist'], item['Week'], item['Qty'], item['priority'], None, 'NaN', 'NaN', 'None'))
# ===========================================================================
# Copyright 2015 Dublin City University
#
# This file is part of DREAM.
#
# DREAM is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DREAM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with DREAM. If not, see <http://www.gnu.org/licenses/>.
# ===========================================================================
'''
Created on 30 Jan 2015
@author: Anna
allocates the forecast demand following the SP sequence order provided in input...does not modify global variables
'''
from copy import deepcopy
from Globals import G
from AllocationForecast_IP import Allocation_IP
from Allocation_3 import Allocation2
from UtilisationCalculation import utilisationCalc1, utilisationCalc2
def AllocationRoutine_ForecastGA(initialWeek, itemList, itemType, chromo):
EarlinessMA = {}
LatenessMA = {}
GAexcess = 0
GAcapacityDict = deepcopy(G.CurrentCapacityDict)
GAincompleteBatches = deepcopy(G.incompleteBatches)
GAearliness = 0
GAlateness = 0
GAtargetUtil = 0
GAminUtil = 0
# repeat allocation procedure for all items in the list
for order in chromo['seq']:
item=itemList[order]
# print 'item', item['orderID']
#================================================
# Allocation step 1...allocation at current Week
#================================================
# variables created for one specific order
# will be confirmed in the GA variables provided that there is enough capacity to allocate the entire order (possibly across different weeks)
Results = {}
step = 1
ind = G.WeekList.index(initialWeek)
weekList = [initialWeek]
capacity = deepcopy(GAcapacityDict)
inBatches = deepcopy(GAincompleteBatches)
qty = item['Qty']
Allocation = []
earliness = 0
lateness = 0
previousAss = {}
for ma in G.SPlist[item['sp']]:
previousAss[ma] = 0
while step <= 3 and qty>0:
if step == 2:
weekList = [G.WeekList[i] for i in range(ind-1, max(-1,ind-G.maxEarliness-1), -1)]
if step == 3:
weekList = [G.WeekList[i] for i in range(ind+1, min(G.planningHorizon,ind+G.maxLateness+1))]
# print 'weeklist', weekList
if len(weekList) == 0:
step+=1
continue
# allocate requested qty
for week in weekList:
# optimise MA allocation at current week
spAllocation = Allocation_IP(item, week, previousAss, capacity, G.weightFactor)
# implement optimal MA solution to update temporary variables
for ma in spAllocation.keys():
if spAllocation[ma]:
Results = Allocation2(ma, spAllocation[ma], [week], capacity, inBatches, earliness, lateness, Allocation, initialWeek)
assert (Results['remainingUnits'] == 0)
# update order variables
capacity = deepcopy(Results['remainingCap'])
inBatches = deepcopy(Results['remUnits'])
qty -= spAllocation[ma]
Allocation = deepcopy(Results['Allocation'])
earliness = deepcopy(Results['earliness'])
lateness = deepcopy(Results['lateness'])
if ma not in EarlinessMA:
EarlinessMA[ma] = 0
LatenessMA[ma] = 0
EarlinessMA[ma] += max([0, initialWeek - week])*spAllocation[ma]
LatenessMA[ma] += max([0, week - initialWeek])*spAllocation[ma]
previousAss[ma] += spAllocation[ma]
# if order has been fully allocated update GA variables
if qty <= 0:
GAcapacityDict = deepcopy(capacity)
GAincompleteBatches = inBatches
GAearliness += earliness/item['Qty']
GAlateness += lateness/item['Qty']
break
step += 1
# if order can not been confirmed then update the GA excess variable
if qty > 0:
GAexcess += item['Qty']
if G.minDeltaUt:
GAtargetUtil, GAminUtil = utilisationCalc1(GAcapacityDict, initialWeek, ind)
else:
GAtargetUtil, GAminUtil = utilisationCalc2(GAcapacityDict, initialWeek, ind)
return {'chromo':chromo, 'excess':GAexcess, 'earliness':GAearliness, 'lateness':GAlateness, 'targetUtil':GAtargetUtil, 'minUtil':GAminUtil}
\ No newline at end of file
# ===========================================================================
# Copyright 2015 Dublin City University
#
# This file is part of DREAM.
#
# DREAM is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DREAM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with DREAM. If not, see <http://www.gnu.org/licenses/>.
# ===========================================================================
'''
Created on 28 Jan 2015
@author: Anna
'''
from Globals import G
from math import ceil, fabs
from copy import deepcopy
def Allocation2(currentMA, qty, weekList, capIn, inBatches, earliness, lateness, Allocation, demandWeek):
# allocate item on its own route
step = 0
sufficient = False
remainingUnits = qty
# Allocation = [] #reports allocation results in the form of dcitionaries ('allocatedQty':..,'week':..)
currentCapacity = deepcopy(capIn)
remUnits = deepcopy(inBatches)
utilisation = {}
for bottleneck in G.RouteDict[currentMA]:
utilisation[bottleneck] = {}
while sufficient == False:
sufficient = True #flag that shows if we have sufficient capacity
# define the currentWeek
currentWeek = weekList[step]
# verify whether the qty is an exact multiple of the batch size
surplus = qty%G.BatchSize[currentMA][currentWeek]
roundDown = 0
if surplus == 0:
#confirm the qty to be assigned
correctedQty = remainingUnits
else:
# if there is enough incomplete Batch units...use them and round down the qty to produce
if remUnits[currentMA] >= surplus:
correctedQty = remainingUnits - surplus
roundDown = 1
else: # round up the qty to produce
correctedQty = remainingUnits + G.BatchSize[currentMA][currentWeek] - surplus
# # round the qty to exact multiples of the batch size
# correctedQty = qty - (qty%G.BatchSize[currentMA][currentWeek])
# if correctedQty == 0:
# # FIXME: maybe excess units should be defined here
# break
# read the capacity that the MA requires
requiredCapacity={}
for x in G.RouteDict[currentMA]:
requiredCapacity[x]=G.RouteDict[currentMA][x][currentWeek]*correctedQty
# read the remaining capacity for the given week and subtract the required from it
remainingCapacity={}
for bottleneck in G.RouteDict[currentMA]:
remainingCapacity[bottleneck]=currentCapacity[bottleneck][currentWeek]-requiredCapacity[bottleneck]
# if we dropped below zero then the capacity is not sufficient
if remainingCapacity[bottleneck]<0:
sufficient=False
# check if there is sufficient capacity to process the order
if sufficient:
remainingUnits = 0
#remainingUnits = max(remainingUnits, 0)
for bottleneck in G.RouteDict[currentMA]:
currentCapacity[bottleneck][currentWeek]=remainingCapacity[bottleneck]
utilisation[bottleneck][currentWeek] = float(G.Capacity[bottleneck][currentWeek]['OriginalCapacity'] - currentCapacity[bottleneck][currentWeek])/G.Capacity[bottleneck][currentWeek]['OriginalCapacity']
Allocation.append({'ma':currentMA, 'units':correctedQty, 'week':currentWeek})
lateness += max([0, currentWeek - demandWeek])*correctedQty
earliness += max([0, demandWeek - currentWeek])*correctedQty
# if there is a surplus...update remUnits
if surplus:
if roundDown:
remUnits[currentMA] -= surplus
else:
remUnits[currentMA] = G.BatchSize[currentMA][currentWeek] - surplus
surplus = 0
# if the capacity available is not sufficient, the max allocable qty is derived
else:
# calculate max qty allocable
excessUnits=0
excess=0
for bottleneck in remainingCapacity:
if requiredCapacity[bottleneck]>0 and remainingCapacity[bottleneck]<0:
excessUnits = remainingCapacity[bottleneck]/G.RouteDict[currentMA][bottleneck][currentWeek]
if ceil(fabs(excessUnits))>excess:
excess = ceil(fabs(excessUnits))
# update remaining capacity
assert(excess <= correctedQty)
# round allocable qty to multiple of batch size...in this case is necessarily round down and remUnits are not affected
allocableQty = correctedQty - excess
allocableQty -= allocableQty%G.BatchSize[currentMA][currentWeek]
remainingUnits -= allocableQty
assert(remainingUnits>0)
for bottleneck in G.RouteDict[currentMA]:
currentCapacity[bottleneck][currentWeek]-=allocableQty*G.RouteDict[currentMA][bottleneck][currentWeek]
Allocation.append({'ma':currentMA,'units':allocableQty, 'week':currentWeek})
lateness += max([0, currentWeek - demandWeek])*allocableQty
earliness += max([0, demandWeek - currentWeek])*allocableQty
if remainingUnits == 0:
sufficient = True
else:
sufficient = False
step += 1
if step >= len(weekList):
break
# if the entire qty has been assigned the allocation can be confirmed
# if remainingUnits > 0:
# self.allocation = []
# remainingUnits = qty
return {'remainingUnits': remainingUnits, 'Allocation':Allocation, 'earliness':earliness, 'lateness':lateness, 'utilisation':utilisation, 'remUnits':remUnits, 'remainingCap':currentCapacity}
# ===========================================================================
# Copyright 2015 Dublin City University
#
# This file is part of DREAM.
#
# DREAM is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DREAM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with DREAM. If not, see <http://www.gnu.org/licenses/>.
# ===========================================================================
'''
Created on 8 Dec 2014
@author: Anna
'''
''' implements ACO for the allocation of orders/forecast of a certain week/priority level '''
from AllocationRoutine_ACO2 import AllocationRoutine_ACO
from AllocationRoutine_Final import AllocationRoutine_Final
from Globals import G
from random import choice
from operator import itemgetter
from math import ceil
from numpy import mean
from copy import deepcopy
def ranking(candidates,elg):
if elg >= len(candidates): #it always picks even number of chromosomes for crossover in order to have required pair of parents for crossover in LS
elg = (elg-elg%2)
else:
elg = (elg+elg%2)
finalCandidates = candidates
# add order sequence to candidates
for i in range(len(finalCandidates)):
finalCandidates[i]['orderSequence'] = i
# sort candidates
fittestLateness = sorted(finalCandidates, key=itemgetter('excess', 'lateness', 'earliness'))
fittestUtilisation = sorted(finalCandidates, key=itemgetter('targetUtil', 'minUtil'))
# select candidates that appear in the first positions in both lists...with preference on Lateness metrics
numTop = int(ceil(len(finalCandidates)*0.2))
aLateness = []
aUtilisation = []
for i in range(numTop):
aLateness.append(fittestLateness[i]['orderSequence'])
aUtilisation.append(fittestUtilisation[i]['orderSequence'])
aLateness = set(aLateness)
aUtilisation = set(aUtilisation)
selection = list(aLateness.intersection(aUtilisation))
fittest = []
fitID = []
numFit = min([len(selection), elg])
for i in range(numFit):
x = 0
while x < numTop:
if fittestLateness[x]['orderSequence'] == selection[i]:
fittest.append(fittestLateness[x])
fitID.append(fittestLateness[x]['orderSequence'])
break
x += 1
# if there is not enough ants in selection then calculate the average ranking between the two sorted lists and
# select the ants with highest average ranking not previously included
# solve the ties in favour of lateness
if numFit < elg:
ultRanking = {}
rankingList = []
for i in range(len(fittestLateness)):
ultRanking[fittestLateness[i]['orderSequence']] = {'lateness':i, 'orderSequence':fittestLateness[i]['orderSequence']}
for i in range(len(fittestUtilisation)):
ultRanking[fittestUtilisation[i]['orderSequence']]['utilisation'] = i
ultRanking[fittestUtilisation[i]['orderSequence']]['avg'] = mean([ultRanking[fittestUtilisation[i]['orderSequence']]['utilisation'], ultRanking[fittestUtilisation[i]['orderSequence']]['lateness']])
rankingList.append(ultRanking[fittestUtilisation[i]['orderSequence']])
rankingList = sorted(rankingList, key=itemgetter('avg', 'lateness'))
for i in range(len(rankingList)):
if rankingList[i]['orderSequence'] not in fitID:
x = 0
while x < len(fittestLateness):
if fittestLateness[x]['orderSequence'] == rankingList[i]['orderSequence']:
fittest.append(fittestLateness[x])
fitID.append(fittestLateness[x]['orderSequence'])
break
x += 1
numFit += 1
if numFit == elg:
break
termCriterion = 0
scores = [ant['excess'] for ant in fittest]
if max(scores) - min(scores) <= 0.001*min(scores): #Termination Criteria to check for convergence - in this case, if the current solutions are within 10% range
termCriterion += 1
scores = [ant['targetUtil'] for ant in fittest]
if max(scores) - min(scores) <= 0.001*min(scores): #Termination Criteria to check for convergence - in this case, if the current solutions are within 10% range
termCriterion += 1
if termCriterion == 2:
print 'Termination Criterion Reached'
return fittest,'Terminate'
else:
return fittest,'Continue'
def finalRanking(candidates):
finalCandidates = candidates
# add order sequence to candidates
for i in range(len(finalCandidates)):
finalCandidates[i]['orderSequence'] = i
# sort candidates
fittestLateness = sorted(finalCandidates, key=itemgetter('excess', 'lateness', 'targetUtil', 'minUtil', 'earliness'))
return fittestLateness[0]
def Allocation_ACO(initialWeek, itemList, itemType,ACOresults):
# create an ant dictionary that contains applicable MA
antDictionary = {}
for item in itemList:
antDictionary[item['orderID']] = deepcopy(item['MAlist'])
ants = [] #list of ants that are being evaluated, an ant is a combination of different weighting factors for multi-obj optimisation (PB assignment)
testedAnts = []
antID = 1
for gen in range(G.noGen):
print 'generation', gen
for rep in range(G.popSize):
print 'ant', rep
# create an ant
ant = {}
for item in itemList:
ant[item['orderID']] = choice(antDictionary[item['orderID']])
if ant in testedAnts:
continue
# record ant
testedAnts.append(ant)
ant['antID'] = antID
# simulate ant
resultAnt = AllocationRoutine_ACO(initialWeek, itemList, itemType, ant)
ants.append(resultAnt)
antID += 1
# save ants results
ACOresults.append((initialWeek, gen, rep, resultAnt['ant']['antID'],resultAnt['excess'], resultAnt['lateness'], resultAnt['earliness'], resultAnt['targetUtil'], resultAnt['minUtil'] ))
# rank ants and select best ones
ants, termCond = ranking(ants,10)
if termCond == 'Terminate':
break
# update weights
for x in range(len(ants)):
for orderID in ants[x]['ant'].keys():
if orderID != 'antID':
antDictionary[orderID].append(ants[x]['ant'][orderID])
# selection of final solution and results recording
print 'final allocation'
ant = finalRanking(ants)
AllocationRoutine_Final(initialWeek, itemList, itemType, ant['ant'])
ACOresults.append((initialWeek, gen, rep, ant['ant']['antID'], 'selected', '', '', '', ''))
return ACOresults
# ===========================================================================
# Copyright 2015 Dublin City University
#
# This file is part of DREAM.
#
# DREAM is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DREAM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with DREAM. If not, see <http://www.gnu.org/licenses/>.
# ===========================================================================
'''
Created on 29 Jan 2015
@author: Anna
Implements GA for identifying best SP sequence order in forecast disaggregation...best MA combination for each SP is chosen by LP method
Equivalent of Allocation_ACO for orders disaggregation
'''
from AllocationRoutine_ForecastGA import AllocationRoutine_ForecastGA
from AllocationRoutine_Forecast import AllocationRoutine_Forecast
from Globals import G
from RankingAlgorithms import rankingElitist, compareChromosomes, finalRanking
from GAoperators import order2x, displacement
from numpy import random
from copy import deepcopy
def Allocation_GA(initialWeek, itemList, itemType,GAresults):
chromosomes = [] #list of ants that are being evaluated, an ant is a combination of different weighting factors for multi-obj optimisation (PB assignment)
testedChrom = []
bestChromosome = [] # record best chromosome for current generation
chromoID = 0
# get the list of orders ID
orderList = {}
orderIDlist = []
for item in itemList:
orderList[item['orderID']]=item
orderIDlist.append(item['orderID'])
#===========================
# generate first population
#===========================
print 'generation 0'
while chromoID < G.popSizeGA:
# generate new order sequence
if chromoID == 0:
chromo = {'cID': chromoID, 'seq':orderIDlist}
else:
chromo = {'cID':chromoID, 'seq':list(random.permutation(orderIDlist))}
# verify whether the sequence has already being tested
if chromo['seq'] in testedChrom:
continue
# record chromosome
testedChrom.append(chromo['seq'])
# simulate chromosome
resultGA = AllocationRoutine_ForecastGA(initialWeek, orderList, itemType,chromo)
chromosomes.append(resultGA)
chromoID += 1
# save chromosomes results
GAresults.append((initialWeek, 0, chromoID, resultGA['chromo']['cID'], resultGA['excess'], resultGA['lateness'], \
resultGA['earliness'], resultGA['targetUtil'], resultGA['minUtil'],chromo['seq'] ))
# start optimisation cycle
for gen in range(1,G.noGenGA):
print 'generation', gen
# selection: elitist selection with linear ranking procedure for completing the population
chromosomes, bc = rankingElitist(chromosomes,G.elitistSelection)
# save best chromosome for previous generation
bestChromosome.append(deepcopy(bc))
# check if the solution is different or the termination criterion is reached...done here to avoid ranking the results multiple times
if compareChromosomes(bestChromosome,G.terminationGA):
break
# keep track of chromosomes with changes...for these chromosomes allocation would be required
changeC = [0]*G.popSizeGA
# cross-over: order2 cross-over is applied
for item in range(G.popSizeGA):
# apply X-over based on X-over probability
xOver = random.random()
if item < G.popSizeGA-1 and xOver <= G.probXover:
chromosomes[item]['chromo']['seq'], chromosomes[item+1]['chromo']['seq'] = order2x(chromosomes[item]['chromo']['seq'], chromosomes[item+1]['chromo']['seq'])
changeC[item] = 1 # both chromosomes have changes and they should be reassessed
changeC[item+1] = 1
mutation = random.random()
# apply mutation based on mutation probability
if mutation <= G.probMutation:
chromosomes[item]['chromo']['seq'] = displacement(chromosomes[item]['chromo']['seq'])
changeC[item] = 1
# reassess the chromosome if it has been changed and has never been investigated (does not belong to testedChromosomes)
#if changeC[item] and chromosomes[item]['chromo']['seq'] not in testedChrom: #FIXME: se e`in tested non si hanno i risultati...si possono lasciare in bianco perche`counque non e`il milgiore cromosoma :
testedChrom.append(chromosomes[item]['chromo']['seq'])
chromosomes[item]['chromo']['cID'] = chromoID
chromoID += 1
# simulate chromosome
resultGA = AllocationRoutine_ForecastGA(initialWeek, orderList, itemType,chromosomes[item]['chromo'])
chromosomes[item] = deepcopy(resultGA)
# save chromosomes results
GAresults.append((initialWeek, gen, item, chromosomes[item]['chromo']['cID'], chromosomes[item]['excess'], chromosomes[item]['lateness'], \
chromosomes[item]['earliness'], chromosomes[item]['targetUtil'], chromosomes[item]['minUtil'], chromosomes[item]['chromo']['seq']))
# final ranking
bestC = finalRanking(chromosomes+bestChromosome)
AllocationRoutine_Forecast(initialWeek, orderList, itemType, bestC['chromo'])
GAresults.append((initialWeek, bestC['chromo']['cID'], 'selected', bestC['chromo']['seq'], '', '', '','','',''))
return GAresults
# ===========================================================================
# Copyright 2015 Dublin City University
#
# This file is part of DREAM.
#
# DREAM is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DREAM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with DREAM. If not, see <http://www.gnu.org/licenses/>.
# ===========================================================================
'''
Created on 29 Jan 2015
@author: Anna
'''
from numpy import random
# defines order 2 cross over
def order2x(parent1, parent2):
# verify that the parents have same length
assert(len(parent1)==len(parent2))
numGenes = len(parent1)
# generate number of key positions
numKP = random.random_integers(numGenes-1)
# generate key positions
keyPositions = []
key = 0
while key < numKP:
possibleKey = random.random_integers(numGenes-1)
if possibleKey not in keyPositions:
keyPositions.append(possibleKey)
key += 1
# sort key positions
keyPositions.sort()
# generate first offspring
offspring1 = offGeneration(keyPositions, parent1, parent2)
# generate second offspring
offspring2 = offGeneration(keyPositions, parent2, parent1)
return offspring1, offspring2
# generates offspring for order2 crossover
def offGeneration(keyPositions, parent1, parent2):
# create list with parent2 alleles corresponding to key positions
fromP2 = [parent2[i] for i in keyPositions]
# generate offspring
# copy from parent 1 if the allele is not contained in fromP2 otherwise copy from fromP2
off = []
c2=0
for gene in range(len(parent1)):
if parent1[gene] not in fromP2:
off.append(parent1[gene])
else:
off.append(fromP2[c2])
c2+=1
return off
# mutation operator
def displacement(parent):
# generate two key positions; second position should be different than first
p1 = random.random_integers(len(parent)-2)
while True:
p2 = random.random_integers(len(parent)-2)
if p2 != p1:
break
keyPos = [p1,p2]
keyPos.sort()
# generate third key position different than the min keyPos
while True:
p3 = random.random_integers(len(parent))-1
if p3 != keyPos[0]:
break
offspring = [None for i in range(len(parent))]
slicedP = parent[:keyPos[0]]+parent[keyPos[1]+1:]
# starting from p3 report the alleles between p1 and p2
for i in range(keyPos[1]-keyPos[0]+1):
offspring[(p3+i)%len(parent)] = parent[keyPos[0]+i]
startPos = (p3+keyPos[1]-keyPos[0]+1)%len(parent)
# complete the offspring with alleles from the slicedP
for i in range(len(slicedP)):
offspring[(startPos+i)%len(parent)] = slicedP[i]
return offspring
if __name__ == "__main__":
order2x([1, 2, 3, 4],[1,3,2,4])
\ No newline at end of file
# ===========================================================================
# Copyright 2015 Dublin City University
#
# This file is part of DREAM.
#
# DREAM is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DREAM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with DREAM. If not, see <http://www.gnu.org/licenses/>.
# ===========================================================================
'''
Created on 5 Sep 2013
@author: Anna
'''
import tablib
class G:
Capacity = {}
RouteDict = {}
maxEarliness = 0 # max number of weeks for earliness
maxLateness = 0 # max number of weeks for lateness
planningHorizon =0 # for future demand purposes
# demandFile = None
CurrentCapacityDict = {}
Bottlenecks = []
SPlist = {}
SPs = []
BatchSize = {}
orders = {}
sortedOrders = {}
forecast = {}
incompleteBatches = {}
items ={}
WeekList=[]
priorityList = {}
Earliness = {}
Lateness = {}
Excess = {}
weightFactor = [10.0,1.0,0,2]
# ACO parameters
ACO = 1
noGen = 5
popSize = 10
# GA parameters
GA = 0 # suggests whether application of GA to forecast diseggragation is required
noGenGA = 5
popSizeGA = 8
probXover = 0.6
probMutation = 0.1
elitistSelection = 1
terminationGA = 4
# utilisation calculation
minDeltaUt = 0
# output variables
reportResults = tablib.Databook()
OrderResults = tablib.Dataset(title='OrderSummary')
OrderResults.headers = ('PPOS', 'SP_NUMBER', 'MA_LIST', 'REQUEST_DATE', 'DFSELLER', 'ORDERQTY', 'PRIORITY', 'CHOSEN_MA', 'LATENESS', 'EARLINESS', 'ALLOCATION')
forecastResults = tablib.Dataset(title='ForecastSummary')
forecastResults.headers = ('PPOS', 'SP_NUMBER', 'MA_LIST', 'REQUEST_DATE', 'ORDERQTY', 'PRIORITY', 'CHOSEN_MA', 'LATENESS', 'EARLINESS', 'ALLOCATION')
globalMAAllocation = {}
spForecastOrder = []
CapacityResults = tablib.Dataset(title = 'BN_Capa')
allocationResults = tablib.Dataset(title = 'Demand_coverage')
# filterItem = 0
# filterWeek = 0
\ No newline at end of file
This diff is collapsed.
# ===========================================================================
# Copyright 2015 Dublin City University
#
# This file is part of DREAM.
#
# DREAM is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DREAM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with DREAM. If not, see <http://www.gnu.org/licenses/>.
# ===========================================================================
'''
Created on 29 Jan 2015
@author: Anna
'''
from operator import itemgetter
from numpy import mean, random
# elitist selection with linear ranking completion
def rankingElitist(candidates,elg):
if elg >= len(candidates): #it always picks even number of chromosomes for crossover in order to have required pair of parents for crossover in LS
elg = (elg-elg%2)
else:
elg = (elg+elg%2)
# in original implementation final candidates are generated by eliminating duplicate solutions
finalCandidates = candidates
# add order sequence to candidates
for i in range(len(finalCandidates)):
finalCandidates[i]['orderSequence'] = i
# sort candidates
fittestLateness = sorted(finalCandidates, key=itemgetter('excess', 'lateness', 'earliness'))
fittestUtilisation = sorted(finalCandidates, key=itemgetter('targetUtil', 'minUtil'))
Both = sorted(finalCandidates, key=itemgetter('excess', 'lateness', 'targetUtil', 'minUtil', 'earliness'))
bestC = Both[0]
#============================
# first sorting criterion
#============================
# select candidates that appear in the first positions in both lists...with preference on Lateness metrics
numTop = elg #int(math.ceil(len(finalCandidates)*0.2))
aLateness = []
aUtilisation = []
for i in range(numTop):
aLateness.append(fittestLateness[i]['orderSequence'])
aUtilisation.append(fittestUtilisation[i]['orderSequence'])
aLateness = set(aLateness)
aUtilisation = set(aUtilisation)
selection = list(aLateness.intersection(aUtilisation))
fittest = []
fitID = []
numFit = min([len(selection), elg])
for i in range(numFit):
x = 0
while x < numTop:
if fittestLateness[x]['orderSequence'] == selection[i]:
fittest.append(fittestLateness[x])
fitID.append(fittestLateness[x]['orderSequence'])
break
x += 1
# other ranking option for elitist selection
# i = 0
# x = 0
# if numFit < elg:
# while i < elg-numFit:
# while x<len(fittestLateness):
# if fittestLateness[x]['orderSequence'] not in fitID:
# fittest.append(fittestLateness[x])
# fitID.append(fittestLateness[x]['orderSequence'])
# i+=1
# break
# x+=1
#==========================
# second sorting criterion
#==========================
# if there is not enough ants in selection then calculate the average ranking between the two sorted lists and
# select the ants with highest average ranking not previously included
# solve the ties in favour of lateness...ranking is also done to complete the selection with linear ranking
ultRanking = {}
rankingList = []
for i in range(len(fittestLateness)):
ultRanking[fittestLateness[i]['orderSequence']] = {'lateness':i, 'orderSequence':fittestLateness[i]['orderSequence']}
for i in range(len(fittestUtilisation)):
ultRanking[fittestUtilisation[i]['orderSequence']]['utilisation'] = i
ultRanking[fittestUtilisation[i]['orderSequence']]['avg'] = mean([ultRanking[fittestUtilisation[i]['orderSequence']]['utilisation'], ultRanking[fittestUtilisation[i]['orderSequence']]['lateness']])
rankingList.append(ultRanking[fittestUtilisation[i]['orderSequence']])
rankingList = sorted(rankingList, key=itemgetter('avg', 'lateness'))
# add the best solutions to the elitist list until the number of chromosomes specified in the selection parameters is reached
for i in range(len(rankingList)):
if numFit == elg:
break
if rankingList[i]['orderSequence'] not in fitID:
x = 0
while x < len(fittestLateness):
if fittestLateness[x]['orderSequence'] == rankingList[i]['orderSequence']:
fittest.append(fittestLateness[x])
fitID.append(fittestLateness[x]['orderSequence'])
break
x += 1
numFit += 1
#======================
# start linear ranking
#======================
# create the probability list
numCand = len(candidates)
s = [0 for i in range(numCand)]
# assuming a linear ranking parameter equal to 2, complete the probability list...see matlab files as reference
for i in range(numCand-2,-1,-1):
s[i] = s[i+1] + (2.0/numCand)*float(numCand-1-i)/(numCand-1)
for i in range(numCand-elg):
# randomly generate probability of selection
probSel = random.random()*float(s[0])
# find chromosome that corresponds to that probability
chromosome = numCand-1
while chromosome >= 0 and s[chromosome]<probSel:
chromosome -= 1
x = 0
while x < len(fittestLateness):
if fittestLateness[x]['orderSequence'] == rankingList[chromosome]['orderSequence']:
fittest.append(fittestLateness[x])
fitID.append(fittestLateness[x]['orderSequence'])
break
x += 1
return fittest, bestC
# compares best chromosomes and suggest to terminate if the best one does not change
def compareChromosomes(bestChromosome, termination):
if len(bestChromosome) < termination:
return False
for chrom in range(len(bestChromosome)-2,len(bestChromosome)-termination,-1):
if (bestChromosome[chrom+1]['excess'] != bestChromosome[chrom]['excess'] or \
bestChromosome[chrom+1]['lateness'] != bestChromosome[chrom]['lateness'] or \
bestChromosome[chrom+1]['earliness'] != bestChromosome[chrom]['earliness'] or \
bestChromosome[chrom+1]['targetUtil'] != bestChromosome[chrom]['targetUtil'] or \
bestChromosome[chrom+1]['minUtil'] != bestChromosome[chrom]['minUtil']):
return False
return True
# choose final solution based on first
def finalRanking(finalCandidates):
# add order sequence to candidates
for i in range(len(finalCandidates)):
finalCandidates[i]['orderSequence'] = i
# sort candidates
fittestLateness = sorted(finalCandidates, key=itemgetter('excess', 'lateness', 'targetUtil', 'minUtil', 'earliness'))
return fittestLateness[0]
\ No newline at end of file
# ===========================================================================
# Copyright 2015 Dublin City University
#
# This file is part of DREAM.
#
# DREAM is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DREAM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with DREAM. If not, see <http://www.gnu.org/licenses/>.
# ===========================================================================
'''
Created on 30 Jan 2015
@author: Anna
'''
from Globals import G
from numpy import array, mean, std, absolute
def utilisationCalc1(ACOcapacityDict, initialWeek, ind):
#==============================================
# calculate min and target utilisation metrics
#==============================================
# min utilisation
# for weeks including the demand week and earlier weeks boolean variables are created for each bottleneck
# 1 if the min target has been reached 0 otherwise
# the boolean variables are averaged across the weeks for each bottleneck and the sum is returned
# target utilisation
# for weeks including the demand week and earlier weeks
# the quadratic distance from target utilisation is calculated and divided by the target utilisation
# for each bottleneck the mean value is calculated (across weeks)
# the global mean (across bottlenecks) is returned
minUtil = []
targetUtil = []
for bottleneck in G.Bottlenecks:
weekList = [initialWeek] + [G.WeekList[i] for i in range(ind-1, max(-1,ind-G.maxEarliness-1), -1)]
minU = []
targetU = []
for week in weekList:
utilisation = float(G.Capacity[bottleneck][week]['OriginalCapacity']-ACOcapacityDict[bottleneck][week])/G.Capacity[bottleneck][week]['OriginalCapacity']
minU.append(utilisation > G.Capacity[bottleneck][week]['minUtilisation'])
targetU.append(float(utilisation - G.Capacity[bottleneck][week]['targetUtilisation'])/G.Capacity[bottleneck][week]['targetUtilisation'])
minUtil.append(mean(array(minU)))
targetUtil.append(mean(absolute(targetU)))
ACOtargetUtil = mean(array(targetUtil))
ACOminUtil = mean(array(minUtil))*-1
return ACOtargetUtil, ACOminUtil
def utilisationCalc2(ACOcapacityDict, initialWeek, ind):
#==============================================
# calculate min and target utilisation metrics
#==============================================
# similar to chosenMA logic
minUtil = []
targetUtil = []
for bottleneck in G.Bottlenecks:
weekList = [initialWeek] + [G.WeekList[i] for i in range(ind-1, max(-1,ind-G.maxEarliness-1), -1)]
for week in weekList:
utilisation = float(G.Capacity[bottleneck][week]['OriginalCapacity']-ACOcapacityDict[bottleneck][week])/G.Capacity[bottleneck][week]['OriginalCapacity']
minUtil.append(utilisation > G.Capacity[bottleneck][week]['minUtilisation'])
targetUtil.append(float(utilisation - G.Capacity[bottleneck][week]['targetUtilisation'])/G.Capacity[bottleneck][week]['targetUtilisation'])
ACOtargetUtil = std(array(targetUtil))
ACOminUtil = mean(array(minUtil))*-1
return ACOtargetUtil, ACOminUtil
# ===========================================================================
# Copyright 2013 University of Limerick
#
# This file is part of DREAM.
#
# DREAM is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DREAM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with DREAM. If not, see <http://www.gnu.org/licenses/>.
# ===========================================================================
# See http://peak.telecommunity.com/DevCenter/setuptools#namespace-packages
try:
__import__('pkg_resources').declare_namespace(__name__)
except ImportError:
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)
\ No newline at end of file
# ===========================================================================
# Copyright 2015 Dublin City University
#
# This file is part of DREAM.
#
# DREAM is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# DREAM is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with DREAM. If not, see <http://www.gnu.org/licenses/>.
# ===========================================================================
'''
Created on 27 Nov 2014
@author: Anna
'''
from AllocManagement_Hybrid import AllocManagement_Hybrid2
from ImportInput import ImportInput
from outputResults import outputResults
def main(input):
assert input, 'no input is provided, the algorithm cannot run'
ImportInput(input)
AllocManagement_Hybrid2()
outputResults()
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment