├── ACO-JSSP-Report.pdf ├── ACO_JSSP_Novel_2_4J3M_04-15-2017_1947_bestWork1f.py ├── ACO_JSSP_Novel_2_8J4M_04-16-2017_1800_bestWork1f - play.py ├── README.md └── presentation.pdf /ACO-JSSP-Report.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addejans/ACO-JSSP/ac8e3ff25d913d6bbd8d724b2c015d050f52ab66/ACO-JSSP-Report.pdf -------------------------------------------------------------------------------- /ACO_JSSP_Novel_2_4J3M_04-15-2017_1947_bestWork1f.py: -------------------------------------------------------------------------------- 1 | import sys #for maxInt 2 | import random 3 | from collections import defaultdict 4 | import datetime #for Gantt (time formatting) #duration is in days 5 | import plotly #for Gantt (full package) 6 | import plotly.plotly as py #for Gantt 7 | import plotly.figure_factory as ff #for Gantt 8 | #plotly.tools.set_credentials_file(username='addejans', api_key='65E3LDJVN63Y0Fx0tGIQ') #for Gantt -- DeJans pw:O********1* 9 | plotly.tools.set_credentials_file(username='tutrinhkt94', api_key='vSrhCEpX6ADg7esoVCbc') #for Gantt -- T.Tran pw:OaklandU 10 | import copy #for saving best objects 11 | 12 | 13 | ############################## INITIALIZATION --- START 14 | 15 | def initialization(): 16 | random.seed(1) 17 | 18 | #**************************************************************************# 19 | #!!!!!!!!!!!!!!!!!!!!!!!!!!! DATA INPUT --- START !!!!!!!!!!!!!!!!!!!!!!!!!!!# 20 | #!!!!!!!!!!!!!!!!!!!!!!!!!!! DATA ENTRY --- START !!!!!!!!!!!!!!!!!!!!!!!!!!!# 21 | 22 | global numJobs, numMachines, numNodes 23 | numJobs = 4 24 | numMachines = 3 25 | numNodes = numJobs*numMachines + 2 26 | 27 | parameterInitialization(8,8) #input number of ants and cycles //Needed before creating node object 28 | 29 | #machine numbers are indexed starting at 1 30 | Job1MachSeq = [1,2,3] 31 | Job2MachSeq = [2,1,3] 32 | Job3MachSeq = [1,3,2] 33 | 34 | global Node0, Node1, Node2, Node3, Node4, Node5, Node6, Node7, Node8, Node9, Node10, Node11 35 | #NODE(dependents, duration, machine, nodeNum) //Duration is defined with unit as days, machine 0 is equivalent to the first machine (i.e. zero indexed) 36 | #the node.num property is 1 larger than the object Node# variable name 37 | Node0 = Node([],1,0,1) 38 | Node1 = Node([Node0],2,1,2) 39 | Node2 = Node([Node0, Node1],3,2,3) 40 | Node3 = Node([],4,1,4) 41 | Node4 = Node([Node3],5,0,5) 42 | Node5 = Node([Node3, Node4],6,2,6) 43 | Node6 = Node([],7,0,7) 44 | Node7 = Node([Node6],8,2,8) 45 | Node8 = Node([Node6, Node7],9,1,9) 46 | Node9 = Node([],10,0,10) 47 | Node10 = Node([Node9],11,1,11) 48 | Node11 = Node([Node9, Node10],12,2,12) 49 | #dummyNodes 50 | global Source, Sink 51 | Source = Node([],0,-1,0) #The source information will not change 52 | sinkDependents = [Node2, Node5, Node8, Node11] #list of the last operation of each job; these will be dependents for the sink 53 | Sink = Node(sinkDependents,0,-1,(numNodes-1)) #The sink information will not change 54 | 55 | global NODESLIST 56 | NODESLIST = [Source,Node0,Node1,Node2,Node3,Node4,Node5,Node6,Node7,Node8,Node9,Node10,Node11,Sink] #the NODESLIST should be appended appropriately in numerical order 57 | 58 | Job1Nodes = [Node0,Node1,Node2] #Nodes should be added dependent to which job they're attached 59 | Job2Nodes = [Node3,Node4,Node5] 60 | Job3Nodes = [Node6,Node7,Node8] 61 | Job4Nodes = [Node9,Node10,Node11] 62 | 63 | Job1 = Jobs([0,1,2], Job1Nodes, 1) #Jobs(jobSequence, Nodes, num) //where num refers to the job number 64 | Job2 = Jobs([3,4,5], Job2Nodes, 2) 65 | Job3 = Jobs([6,7,8], Job3Nodes, 3) 66 | Job4 = Jobs([9,10,11], Job4Nodes, 4) 67 | 68 | global JOBSLIST 69 | JOBSLIST = [Job1, Job2, Job3, Job4] #Append list appropriately in accordance to the jobs created. 70 | 71 | #final note: sequential order is necessary and required for proper functionality at this stage. 72 | 73 | #!!!!!!!!!!!!!!!!!!!!!!!!!!! DATA ENTRY --- END !!!!!!!!!!!!!!!!!!!!!!!!!!!# 74 | #!!!!!!!!!!!!!!!!!!!!!!!!!!! DATA INPUT --- END !!!!!!!!!!!!!!!!!!!!!!!!!!!# 75 | #**************************************************************************# 76 | 77 | global ANTLIST 78 | ANTLIST = [] 79 | 80 | global has_cycle 81 | has_cycle = False 82 | 83 | global smallestSoFarMakespan, bestSoFarAnt, smallestSoFarAntNum, bestSoFarANTLIST, bestSoFarNODESLIST, JOBSLIST2 84 | bestSoFarANTLIST = [] 85 | bestSoFarNODESLIST = [] 86 | JOBSLIST2 = [] 87 | 88 | constructConjunctiveGraph() 89 | 90 | global solutionGraphList 91 | solutionGraphList = [[] for i in range(K) ] 92 | generateSolutionGraphs() 93 | 94 | global nextMachine 95 | nextMachine = -1 96 | 97 | global currentMachine 98 | currentMachine = -1 99 | 100 | global feasibleNodeLists 101 | feasibleNodeLists = [[] for i in range(K)] 102 | 103 | global T 104 | T = [[0.2 for i in range(numNodes)] for j in range(numNodes)] #start with a small constant? 105 | 106 | global H #Heuristic matrix --- will this be different for different types/"species" of ants?.. TBD ***********************************************!!!!!!!!!!!!!!! 107 | H = [[0.5 for i in range(numNodes)] for j in range(numNodes)] #****************** NEEDS TO BE FIXED TO WORK WITH HEURISTICS 108 | 109 | global machineList 110 | machineList = [[] for i in range(numMachines)] 111 | 112 | global cliquesVisited 113 | cliquesVisited = [[] for i in range(K)] 114 | 115 | generateAnts() 116 | generateMachineLists() 117 | 118 | ############################## INITIALIZATION --- END 119 | 120 | 121 | ############################## CLASSES --- START 122 | 123 | class Jobs: 124 | def __init__(self, jobSequence, Nodes, num): 125 | self.jobSequence = jobSequence 126 | self.Nodes = Nodes 127 | self.num = num 128 | 129 | class Node: 130 | def __init__(self, dependents, duration, machine, nodeNum): 131 | self.duration = duration 132 | self.dependents = [dependents for i in range(K)] 133 | self.machine = machine 134 | self.visited = False 135 | self.num = nodeNum 136 | self.startTime = 0 137 | self.endTime = 0 138 | self.scheduled = False 139 | self.antsVisited = [False for i in range(K)] 140 | self.name = 'name goes here' #fill in names via constructor 141 | self.discovered = False 142 | 143 | class Ant: 144 | def __init__(self, num): 145 | self.num = num #label each ant by a number 0 to (k-1) 146 | self.tabu = [] 147 | self.position = -1 148 | self.T = [[0 for i in range(numNodes)] for j in range(numNodes)] #pheromone matrix 149 | self.pheromoneAccumulator = [[0 for i in range(numNodes)] for j in range(numNodes)] #accumulator 150 | self.transitionRuleMatrix = [[0 for i in range(numNodes)] for j in range(numNodes)] #for equation 1 transition probability 151 | self.makespan = 0 152 | self.species = 'none' 153 | self.cycleDetected = False 154 | 155 | ############################## CLASSES --- END 156 | 157 | ############################## OTHER --- START 158 | 159 | def parameterInitialization(numAnts, numCycles): 160 | global K, C, alpha, beta, rho 161 | global Q 162 | global Q1, Q2 163 | alpha = 0.2 #influence of pheromone 164 | beta = 1 - alpha #influence of heuristic 165 | rho = 0.7 #evaporation constant 166 | K = numAnts #number of ants 167 | C = numCycles #number of cycles 168 | Q1 = float(20) #**Programming Note: this must be a float in order for TSum to calculuate as float in calculatePheromoneAccumulation() 169 | Q2 = float(5) 170 | #EAS Procedure determination (below) of number of ants and fixed number of cycles 171 | #K = int(numJobs/2) 172 | #C = 1000 173 | #Q = float(5) # //note: there is no Q1 and Q2 -- these are original 174 | 175 | def generateAnts(): 176 | for i in range(K): 177 | ANTLIST.append(Ant(i)) 178 | 179 | def generateMachineLists(): 180 | for i in range(numMachines): 181 | for j in range(numNodes): 182 | if NODESLIST[j].machine == i: 183 | machineList[i].append(NODESLIST[j].num) 184 | 185 | def generateSolutionGraphs(): 186 | for k in range(K): 187 | constructConjunctiveGraph() 188 | solutionGraphList[k] = conjunctiveGraph 189 | 190 | def constructConjunctiveGraph(): 191 | global conjunctiveGraph 192 | conjunctiveGraph = [[-1 for i in range(numNodes)] for j in range(numNodes)] 193 | 194 | for job in JOBSLIST: 195 | for seq1 in job.jobSequence: 196 | for seq2 in job.jobSequence: 197 | if seq1 <> seq2 and seq1+1 == seq2: 198 | conjunctiveGraph[seq1+1][seq2+1] = NODESLIST[seq2+1].duration 199 | for j in range(numJobs): 200 | conjunctiveGraph[Source.num][JOBSLIST[j].Nodes[0].num] = JOBSLIST[j].Nodes[0].duration 201 | for j in range(numJobs): 202 | conjunctiveGraph[JOBSLIST[j].Nodes[numMachines-1].num][Sink.num] = 0 203 | 204 | def chooseClique(antNum): 205 | randomClique = random.randint(0,numMachines-1) #choose Clique by random - we then choose to travel on nodes based off of pheromone trails - this prevents local optimia to occur 206 | while randomClique in cliquesVisited[antNum]: 207 | randomClique = random.randint(0,numMachines-1) 208 | cliquesVisited[antNum].append(randomClique) 209 | return randomClique 210 | 211 | def randomAssignment(): 212 | for i in range(K): 213 | randNode = random.randint(1,numNodes-2) 214 | ANTLIST[i].tabu.append(NODESLIST[randNode].num) 215 | ANTLIST[i].position = randNode 216 | NODESLIST[randNode].antsVisited[i] = True 217 | 218 | def defineDecidabilityRule(): #UNUSED 04/15/2017 -- For Heuristics ** 219 | for ant in ANTLIST: 220 | speciesType = random.randint(1,2) 221 | if speciesType == 1: 222 | ant.species = 'SPT' #Shortest Processing Time (SPT) 223 | elif speciesType == 2: 224 | ant.species = 'LPT' #Longest Processing Time (LPT) 225 | 226 | ############################## OTHER --- END 227 | 228 | ############################## SCHEDULING --- START 229 | 230 | def schedule(ant): 231 | scheduleNode(bestSoFarNODESLIST[numNodes-1],ant) 232 | 233 | def scheduleNode(node,ant): 234 | for proceedingNode in node.dependents[ant.num]: 235 | if proceedingNode.scheduled == False: 236 | scheduleNode(proceedingNode,ant) 237 | positionNode(node,ant) #base case 238 | node.scheduled = True 239 | 240 | def positionNode(node,ant): 241 | global longestProceedingTime 242 | if len(node.dependents[ant.num])>0: 243 | node.startTime = (bestSoFarNODESLIST[node.num].dependents[ant.num][0].startTime + node.dependents[ant.num][0].duration) 244 | bestSoFarNODESLIST[node.num].startTime = (bestSoFarNODESLIST[node.num].dependents[ant.num][0].startTime + node.dependents[ant.num][0].duration) 245 | for proceedingNode in node.dependents[ant.num]: 246 | longestProceedingTime = (proceedingNode.startTime + proceedingNode.duration) 247 | if longestProceedingTime > node.startTime: 248 | node.startTime = longestProceedingTime 249 | bestSoFarNODESLIST[node.num].startTime = longestProceedingTime 250 | else: #node has no proceeding nodes and can be scheduled right away 251 | node.startTime = 0 252 | bestSoFarNODESLIST[node.num].startTime = 0 253 | node.endTime = node.startTime + node.duration 254 | bestSoFarNODESLIST[node.num].endTime = node.startTime + node.duration 255 | ############################## SCHEDULING --- END 256 | 257 | 258 | ############################## END OF CYCLE --- START 259 | 260 | def calculatePheromoneAccumulation(ant,b): 261 | if b == 0: 262 | for i in range(numNodes): 263 | for j in range(numNodes): 264 | if i != j and solutionGraphList[ant.num][i][j] > 0: 265 | ant.pheromoneAccumulator[i][j] = Q1/ant.makespan # calculate w.r.t. equation 3 or 4 *** //Python Note: in python 2.7 one of the two integers 266 | # must be a float, we declared Q as a float() type in the parameterInitialization() method 267 | elif b == 1: 268 | print 'look here see' 269 | for i in range(numNodes): 270 | print solutionGraphList[ant.num][i] 271 | for j in range(numNodes): 272 | if i != j and solutionGraphList[ant.num][i][j] > 0: 273 | ant.pheromoneAccumulator[i][j] = Q2/ant.makespan 274 | 275 | def updatePheromone(bestMakespan, bestAntNum): 276 | TSum = 0 277 | TOld = T 278 | for i in range(numNodes): 279 | for j in range(numNodes): 280 | for ant in ANTLIST: 281 | TSum += ant.pheromoneAccumulator[i][j] 282 | T[i][j] = TSum + rho*TOld[i][j] #update T[i][j] pheromone matrix based on equation 2 *** ***c<1 accounted for in construction() [main] method*** 283 | #pheromoneAccumulator[i][j] = 0 <---- Necessary? 284 | TSum = 0 285 | for i in range(numNodes): #update for best ant 286 | for j in range(numNodes): 287 | if solutionGraphList[bestAntNum][i][j] > 0: 288 | T[i][j] += float(float(solutionGraphList[bestAntNum][i][j])/float(bestMakespan)) 289 | 290 | def resetAnts(): 291 | nextMachine = -1 292 | for k in range(K): 293 | for i in range(numMachines): 294 | cliquesVisited[k].pop() 295 | constructConjunctiveGraph() 296 | generateSolutionGraphs() 297 | #**LEARNING REMARK: #cliquesVisited = [[] for i in range(K)] *** NOTE: In Python this does not reset the variable/ double array list 298 | for ant in ANTLIST: 299 | ant.tabu = [] 300 | ant.position = -1 301 | ant.T = [[0 for i in range(numNodes)] for j in range(numNodes)] #pheromone matrix 302 | ant.pheromoneAccumulator = [[0 for i in range(numNodes)] for j in range(numNodes)] #accumulator 303 | ant.makespan = 0 304 | ant.cycleDetected = False 305 | currentMachine = -1 306 | 307 | def resetNodes(): 308 | for k in range(K): 309 | for node in NODESLIST: 310 | node.visited = False 311 | node.antsVisited[k] = False 312 | for k in range(K): 313 | Node0.dependents[k]=[] #**LEARNING REMARK: #We can't overwrite objects 314 | Node1.dependents[k]=[Node0] 315 | Node2.dependents[k]=[Node0, Node1] 316 | Node3.dependents[k]=[] 317 | Node4.dependents[k]=[Node3] 318 | Node5.dependents[k]=[Node3, Node4] 319 | Node6.dependents[k]=[] 320 | Node7.dependents[k]=[Node6] 321 | Node8.dependents[k]=[Node6, Node7] 322 | Node9.dependents[k]=[] 323 | Node10.dependents[k]=[Node9] 324 | Node11.dependents[k]=[Node9, Node10] 325 | #dummyNodes: (Source & Sink) 326 | Source.dependents[k] = [] 327 | Sink.dependents[k] = [Node2, Node5, Node8, Node11] 328 | ############################## END OF CYCLE --- END 329 | 330 | ############################## EXPLORATION --- START 331 | 332 | def nextOperation(ant, machNum, cycle): 333 | findFeasibleNodes(ant, machNum) 334 | calculateTransitionProbability(ant) 335 | makeDecision(ant) 336 | 337 | def findFeasibleNodes(ant,currentMachine): 338 | global feasibleNodeLists 339 | feasibleNodeLists = [[] for i in range(K)] 340 | for node in NODESLIST: 341 | if node.antsVisited[ant.num] == False: #04/04/17: Removed not() 342 | if node.num in machineList[currentMachine]: 343 | feasibleNodeLists[ant.num].append(node) 344 | 345 | def calculateTransitionProbability(ant): 346 | for node in feasibleNodeLists[ant.num]: 347 | if node.num not in ant.tabu: 348 | ant.transitionRuleMatrix[ant.position][node.num] = (((T[ant.position][node.num])**(alpha)) * ((H[ant.position][node.num])**(beta)))/sum((((T[ant.position][l.num])**(alpha)) * ((H[ant.position][l.num])**(beta))) for l in feasibleNodeLists[ant.num]) 349 | 350 | def makeDecision(ant): 351 | probabilityList = [] #assign ranges between [0,1] and pick a rand num in interval. 352 | for node in feasibleNodeLists[ant.num]: 353 | probabilityList.append([ant.transitionRuleMatrix[ant.position][node.num]*100,node.num]) 354 | for i in range(len(probabilityList)-1): 355 | probabilityList[i+1][0] += probabilityList[i][0] 356 | randomSelection = random.randint(0,100) 357 | selectedNode = -1 358 | for i in range(len(probabilityList)-1): 359 | if (probabilityList[i][0] <= randomSelection) and (randomSelection <= probabilityList[i+1][0]): 360 | selectedNode = probabilityList[i+1][1] 361 | break 362 | elif randomSelection <= probabilityList[i][0]: #should this be a strict less than, "<" ? 363 | selectedNode = probabilityList[i][1] 364 | break 365 | if selectedNode == -1: 366 | selectedNode = probabilityList[0][1] 367 | ant.position = selectedNode 368 | 369 | ############################## EXPLORATON --- END 370 | 371 | ############################## CONSTRUCTION PHASE --- START 372 | 373 | def constructionPhase(): #The Probabilistic Construction Phrase of solutions begins by K ants 374 | for c in range(C): 375 | for i in range(numNodes):#loop for printing purposes 376 | print T[i] 377 | #print ("{0:.15f}".format(round(float(a),2))) 378 | print '\n \n \n' 379 | defineDecidabilityRule() 380 | for node in NODESLIST: #reset nodes visited to false since this is a new cycle 381 | for ant in ANTLIST: 382 | node.antsVisited[ant.num] = False 383 | for ant in ANTLIST: 384 | skip_counter = 0 385 | for i in range(numMachines): 386 | currentMachine = chooseClique(ant.num) #at random 387 | if c<1: 388 | shuffledNodes = machineList[currentMachine] #We could shuffle this list or we could just keep as is 389 | for x in machineList[currentMachine]: 390 | if skip_counter%numJobs != 0: 391 | oldAntPosition = ant.position 392 | ant.tabu.append(x) 393 | ant.position = x 394 | if skip_counter%numJobs == 0: 395 | NODESLIST[ant.position].antsVisited[ant.num] = True 396 | if skip_counter%numJobs != 0: 397 | NODESLIST[ant.position].antsVisited[ant.num] = True 398 | NODESLIST[ant.position].dependents[ant.num].append(NODESLIST[oldAntPosition]) 399 | solutionGraphList[ant.num][oldAntPosition][ant.position] = NODESLIST[ant.position].duration 400 | skip_counter += 1 401 | else: 402 | for j in range(len(machineList[currentMachine])): 403 | if skip_counter%numJobs != 0: 404 | moveFrom = ant.position 405 | nextOperation(ant, currentMachine, c) 406 | moveTo = ant.position 407 | ant.tabu.append(moveTo) 408 | NODESLIST[moveTo].visited = True 409 | NODESLIST[moveTo].antsVisited[ant.num] = True 410 | if skip_counter%numJobs != 0: 411 | NODESLIST[moveTo].dependents[ant.num].append(NODESLIST[moveFrom]) 412 | solutionGraphList[ant.num][moveFrom][moveTo] = NODESLIST[moveTo].duration # set equal to the duration of the moving to node (puts weight on edge) 413 | skip_counter += 1 414 | for ant in ANTLIST: 415 | if c == 0: 416 | for j in range(numNodes): 417 | print solutionGraphList[ant.num][j] 418 | print 'The c=0 makespan is: ', ant.makespan 419 | print '\n \n \n' 420 | 421 | undiscoverNodes() 422 | global has_cycle 423 | has_cycle = False 424 | cycleDetector(ant) 425 | ant.cycleDetected = has_cycle 426 | if ant.cycleDetected == False: 427 | ant.makespan = getMakespan(ant) #longest path in solution graph --- this is equivalent to the makespan 428 | calculatePheromoneAccumulation(ant,0) 429 | elif ant.cycleDetected == True: 430 | ant.makespan = sys.float_info.max 431 | #print(' need to fill this out, so this way pheromones and stuff are filled properly for cycle ants, i.e. they contribute nothing.') #**************************************!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 432 | smallestMakespan = sys.float_info.max 433 | smallestMakespan,smallestMakespanAntNum = getSmallestMakespan() 434 | calculatePheromoneAccumulation(ANTLIST[smallestMakespanAntNum],1) #reinforce the smallestMakespan ant 435 | print 'The best makespan of cycle ' + str(c) + ' is: ', smallestMakespan 436 | print ANTLIST[smallestMakespanAntNum].makespan 437 | print ANTLIST[smallestMakespanAntNum].cycleDetected 438 | for j in range(numNodes): 439 | print solutionGraphList[smallestMakespanAntNum][j] 440 | if c>0: 441 | if smallestMakespan < smallestSoFarMakespan: 442 | bestSoFarAnt = copy.deepcopy(ANTLIST[smallestMakespanAntNum]) 443 | for i in range(numNodes): 444 | bestSoFarNODESLIST[i] = copy.deepcopy(NODESLIST[i]) 445 | for i in range(K): 446 | bestSoFarANTLIST[i] = copy.deepcopy(ANTLIST[i]) 447 | for i in range(numJobs): 448 | JOBSLIST2[i] = copy.deepcopy(JOBSLIST[i]) 449 | smallestSoFarMakespan = smallestMakespan 450 | smallestSoFarAntNum = smallestMakespanAntNum 451 | elif c == 0: 452 | bestSoFarAnt = copy.deepcopy(ANTLIST[smallestMakespanAntNum]) 453 | for i in range(numNodes): 454 | bestSoFarNODESLIST.append(copy.deepcopy(NODESLIST[i])) 455 | for i in range(K): 456 | bestSoFarANTLIST.append(copy.deepcopy(ANTLIST[i])) 457 | for i in range(numJobs): 458 | JOBSLIST2.append(copy.deepcopy(JOBSLIST[i])) 459 | smallestSoFarMakespan = smallestMakespan 460 | smallestSoFarAntNum = smallestMakespanAntNum 461 | updatePheromone(smallestMakespan, smallestMakespanAntNum) 462 | resetNodes() 463 | resetAnts() 464 | print 'bestSoFarAnt.makespan: =', bestSoFarAnt.makespan 465 | for i in range(numNodes): 466 | print i,':' 467 | for j in range(len(bestSoFarNODESLIST[i].dependents[bestSoFarAnt.num])): 468 | print bestSoFarNODESLIST[i].dependents[bestSoFarAnt.num][j].num 469 | print '\n' 470 | schedule(bestSoFarAnt) 471 | print 'bestSoFarAnt.makespan: =', bestSoFarAnt.makespan 472 | for i in range(numNodes): 473 | print i,':' 474 | for j in range(len(bestSoFarNODESLIST[i].dependents[bestSoFarAnt.num])): 475 | print bestSoFarNODESLIST[i].dependents[bestSoFarAnt.num][j].num 476 | print '\n' 477 | for i in range(numNodes): 478 | print i,':' 479 | print 'num',bestSoFarNODESLIST[i].num 480 | print 'start',bestSoFarNODESLIST[i].startTime 481 | print 'end',bestSoFarNODESLIST[i].endTime 482 | print '\n' 483 | makeGanttChart(bestSoFarAnt) 484 | 485 | ############################## CONSTRUCTION PHASE --- END 486 | 487 | ############################## USED FOR DETECTING CYCLES --- START 488 | def cycleDetector(ant): 489 | global has_cycle 490 | for node in NODESLIST: 491 | undiscoverNodes() #sets all nodes to undiscovered 492 | pcount = 0 493 | S = [] #let S be a stack 494 | S.append(node) 495 | while len(S) > 0: 496 | v = S.pop() 497 | if v.discovered == False: 498 | if v != node: 499 | v.discovered = True 500 | if v == node and pcount >=1: 501 | has_cycle = True 502 | return 503 | for j in range(numNodes): 504 | if solutionGraphList[ant.num][v.num][j] >= 0: 505 | S.append(NODESLIST[j]) 506 | pcount += 1 507 | 508 | def undiscoverNodes(): 509 | for node in NODESLIST: 510 | node.discovered = False 511 | 512 | ############################## USED FOR DETECTING CYCLES --- END 513 | 514 | 515 | ############################## MAKESPAN --- START 516 | 517 | def getSmallestMakespan(): 518 | smallestMakespan = sys.float_info.max #1.7976931348623157e+308 519 | smallestMakespanAntNum = -1 520 | for ant in ANTLIST: 521 | if ant.makespan < smallestMakespan: 522 | smallestMakespan = ant.makespan 523 | smallestMakespanAntNum = ant.num 524 | return smallestMakespan, smallestMakespanAntNum 525 | 526 | def getMakespan(ant): 527 | G = defaultdict(list) 528 | edges = [] 529 | for i in range(numNodes): 530 | for j in range(numNodes): 531 | if solutionGraphList[ant.num][i][j] != -1: 532 | edges.append([NODESLIST[i], NODESLIST[j]]) 533 | for (s,t) in edges: 534 | G[s].append(t) 535 | all_paths = DFS(G,Source) 536 | max_len = 0 537 | max_paths = [] 538 | max_makespan = 0 539 | path_duration = 0 540 | mkspnIndex_i = -1 541 | for i in range(len(all_paths)): 542 | path_duration = 0 543 | for j in range(len(all_paths[i])): 544 | path_duration += all_paths[i][j].duration 545 | if path_duration > max_makespan: 546 | max_makespan = path_duration 547 | mkspnIndex_i = i 548 | return max_makespan 549 | 550 | def DFS(G,v,seen=None,path=None): #v is the starting node 551 | if seen is None: seen = [] 552 | if path is None: path = [v] 553 | seen.append(v) 554 | paths = [] 555 | for t in G[v]: 556 | if t not in seen: 557 | t_path = path + [t] 558 | paths.append(tuple(t_path)) 559 | paths.extend(DFS(G, t, seen[:], t_path)) 560 | return paths 561 | 562 | ############################## USED FOR GETTING MAKESPAN --- END 563 | 564 | ############################## GANTT --- START 565 | def makeGanttChart(bestSoFarAnt): 566 | #quit() #to not use more than 30 API calls per hour or 50 per day 567 | df = [] 568 | for job in JOBSLIST: #I believe this could stay as JOBSLIST since we pass in only the node.num into the bestSoFarNODESLIST 569 | for node in job.Nodes: 570 | print node.num 571 | print job.num 572 | s = str(datetime.datetime.strptime('2017-04-18 00:00:00', "%Y-%m-%d %H:%M:%S") + datetime.timedelta(days=bestSoFarNODESLIST[node.num].startTime)) 573 | d = datetime.datetime.strptime(s, "%Y-%m-%d %H:%M:%S") + datetime.timedelta(days=bestSoFarNODESLIST[node.num].duration) 574 | df.append(dict(Task=str(node.machine), Start=str(s), Finish=str(d), Resource=str(job.num))) 575 | print s 576 | print d 577 | 578 | colors = {'1': 'rgb(220, 0, 0)', 579 | '2': (1, 0.9, 0.16), 580 | '3': 'rgb(0, 255, 100)', 581 | '4': 'rgb(100, 200, 50)'} 582 | quit() 583 | fig = ff.create_gantt(df, colors=colors, index_col='Resource', show_colorbar=True, group_tasks=True) 584 | py.iplot(fig, filename='4J:3M; param(' + str(K) + ',' + str(C) + ')-makespan: ' + str(bestSoFarAnt.makespan) , world_readable=True) 585 | print 'Schedule completed.' 586 | 587 | ############################## GANTT --- END 588 | 589 | ############################## Main() Method Below: 590 | 591 | initialization() 592 | constructionPhase() 593 | print 'Schedule completed.' 594 | 595 | ''' 596 | Idea for interface: 597 | When an ant moves to a node, add dependency to node it's moving from if it's not already a dependent. 598 | Give node a position propery for matrix. e.g., node.matrixPosition = [i][j] #look into paper about using modulus 599 | ''' 600 | -------------------------------------------------------------------------------- /ACO_JSSP_Novel_2_8J4M_04-16-2017_1800_bestWork1f - play.py: -------------------------------------------------------------------------------- 1 | import sys #for maxInt 2 | import random 3 | from collections import defaultdict 4 | import datetime #for Gantt (time formatting) #duration is in days 5 | import plotly #for Gantt (full package) 6 | import plotly.plotly as py #for Gantt 7 | import plotly.figure_factory as ff #for Gantt 8 | #plotly.tools.set_credentials_file(username='addejans', api_key='65E3LDJVN63Y0Fx0tGIQ') #for Gantt -- DeJans pw:O********1* 9 | plotly.tools.set_credentials_file(username='tutrinhkt94', api_key='vSrhCEpX6ADg7esoVCbc') #for Gantt -- T.Tran pw:OaklandU 10 | import copy #for saving best objects 11 | 12 | 13 | ############################## INITIALIZATION --- START 14 | 15 | def initialization(): 16 | random.seed(1) 17 | 18 | #**************************************************************************# 19 | #!!!!!!!!!!!!!!!!!!!!!!!!!!! DATA INPUT --- START !!!!!!!!!!!!!!!!!!!!!!!!!!!# 20 | #!!!!!!!!!!!!!!!!!!!!!!!!!!! DATA ENTRY --- START !!!!!!!!!!!!!!!!!!!!!!!!!!!# 21 | 22 | global numJobs, numMachines, numNodes 23 | numJobs = 8 #****** Note, the Gantt maker needs to be updated to display numJobs amount of colors also modify resetNodes()! (if changed) 24 | numMachines = 4 25 | numNodes = numJobs*numMachines + 2 26 | 27 | parameterInitialization(200,700) #input number of ants and cycles //Needed before creating node object 28 | 29 | #machine numbers are indexed starting at 1 30 | Job1MachSeq = [1,2,3,4] 31 | Job2MachSeq = [2,1,3,4] 32 | Job3MachSeq = [1,3,4,2] 33 | Job4MachSeq = [4,1,2,3] 34 | Job5MachSeq = [2,1,4,3] 35 | Job6MachSeq = [1,3,4,2] 36 | Job7MachSeq = [1,4,2,3] 37 | Job8MachSeq = [2,4,1,3] 38 | 39 | global Node0, Node1, Node2, Node3, Node4, Node5, Node6, Node7, Node8, Node9, Node10, Node11, Node12, Node13, Node14, Node15, Node16, Node17, Node18, Node19, Node20, Node21, Node22, Node23, Node24, Node25, Node26, Node27, Node28, Node29, Node30, Node31 40 | #NODE(dependents, duration, machine, nodeNum) //Duration is defined with unit as days, machine 0 is equivalent to the first machine (i.e. zero indexed) 41 | #the node.num property is 1 larger than the object Node# variable name 42 | Node0 = Node([],1,0,1) #J1 43 | Node1 = Node([Node0],2,1,2) 44 | Node2 = Node([Node0, Node1],3,2,3) 45 | Node3 = Node([Node0, Node1, Node2],4,3,4) 46 | Node4 = Node([],5,1,5) #J2 47 | Node5 = Node([Node4],6,0,6) 48 | Node6 = Node([Node4, Node5],7,2,7) 49 | Node7 = Node([Node4, Node5, Node6],8,3,8) 50 | Node8 = Node([],9,0,9) #J3 51 | Node9 = Node([Node8],10,2,10) 52 | Node10 = Node([Node8, Node9],11,3,11) 53 | Node11 = Node([Node8, Node9, Node10],12,1,12) 54 | Node12 = Node([],13,3,13) #J4 55 | Node13 = Node([Node12],14,0,14) 56 | Node14 = Node([Node12, Node13],15,1,15) 57 | Node15 = Node([Node12, Node13, Node14],16,2,16) 58 | Node16 = Node([],17,1,17) #J5 59 | Node17 = Node([Node16],18,0,18) 60 | Node18 = Node([Node16, Node17],19,3,19) 61 | Node19 = Node([Node16, Node17, Node18],20,2,20) 62 | Node20 = Node([],21,0,21) #J6 63 | Node21 = Node([Node20],22,2,22) 64 | Node22 = Node([Node20, Node21],23,3,23) 65 | Node23 = Node([Node20, Node21, Node22],24,1,24) 66 | Node24 = Node([],25,0,25) #J7 67 | Node25 = Node([Node24],26,3,26) 68 | Node26 = Node([Node24, Node25],27,1,27) 69 | Node27 = Node([Node24, Node25, Node26],28,2,28) 70 | Node28 = Node([],29,1,29) #J8 71 | Node29 = Node([Node28],30,3,30) 72 | Node30 = Node([Node28, Node29],31,0,31) 73 | Node31 = Node([Node28, Node29, Node30],32,2,32) 74 | #dummyNodes 75 | global Source, Sink 76 | Source = Node([],0,-1,0) #The source information will not change 77 | sinkDependents = [Node3, Node7, Node11, Node15, Node19, Node23, Node27, Node31] #list of the last operation of each job; these will be dependents for the sink 78 | Sink = Node(sinkDependents,0,-1,(numNodes-1)) #The sink information will not change 79 | 80 | global NODESLIST 81 | NODESLIST = [Source,Node0,Node1,Node2,Node3,Node4,Node5,Node6,Node7,Node8,Node9,Node10,Node11,Node12,Node13,Node14,Node15,Node16,Node17,Node18,Node19,Node20,Node21,Node22,Node23,Node24,Node25,Node26,Node27,Node28,Node29,Node30,Node31,Sink] #the NODESLIST should be appended appropriately in numerical order 82 | 83 | Job1Nodes = [Node0,Node1,Node2,Node3] #Nodes should be added dependent to which job they're attached 84 | Job2Nodes = [Node4,Node5,Node6,Node7] 85 | Job3Nodes = [Node8,Node9,Node10,Node11] 86 | Job4Nodes = [Node12,Node13,Node14,Node15] 87 | Job5Nodes = [Node16,Node17,Node18,Node19] 88 | Job6Nodes = [Node20,Node21,Node22,Node23] 89 | Job7Nodes = [Node24,Node25,Node26,Node27] 90 | Job8Nodes = [Node28,Node29,Node30,Node31] 91 | 92 | Job1 = Jobs([0,1,2,3], Job1Nodes, 1) #Jobs(jobSequence, Nodes, num) //where num refers to the job number 93 | Job2 = Jobs([4,5,6,7], Job2Nodes, 2) 94 | Job3 = Jobs([8,9,10,11], Job3Nodes, 3) 95 | Job4 = Jobs([12,13,14,15], Job4Nodes, 4) 96 | Job5 = Jobs([16,17,18,19], Job5Nodes, 5) 97 | Job6 = Jobs([20,21,22,23], Job6Nodes, 6) 98 | Job7 = Jobs([24,25,26,27], Job7Nodes, 7) 99 | Job8 = Jobs([28,29,30,31], Job8Nodes, 8) 100 | global JOBSLIST 101 | JOBSLIST = [Job1, Job2, Job3, Job4, Job5, Job6, Job7, Job8] #Append list appropriately in accordance to the jobs created. 102 | 103 | #final note: sequential order is necessary and required for proper functionality at this stage. 104 | 105 | #!!!!!!!!!!!!!!!!!!!!!!!!!!! DATA ENTRY --- END !!!!!!!!!!!!!!!!!!!!!!!!!!!# 106 | #!!!!!!!!!!!!!!!!!!!!!!!!!!! DATA INPUT --- END !!!!!!!!!!!!!!!!!!!!!!!!!!!# 107 | #**************************************************************************# 108 | 109 | global ANTLIST 110 | ANTLIST = [] 111 | 112 | global has_cycle 113 | has_cycle = False 114 | 115 | global smallestSoFarMakespan, bestSoFarAnt, smallestSoFarAntNum, bestSoFarANTLIST, bestSoFarNODESLIST, JOBSLIST2, bestSoFarSolutionGraph 116 | bestSoFarANTLIST = [] 117 | bestSoFarNODESLIST = [] 118 | JOBSLIST2 = [] 119 | bestSoFarSolutionGraph = [] 120 | 121 | constructConjunctiveGraph() 122 | 123 | global solutionGraphList 124 | solutionGraphList = [[] for i in range(K) ] 125 | generateSolutionGraphs() 126 | 127 | global nextMachine 128 | nextMachine = -1 129 | 130 | global currentMachine 131 | currentMachine = -1 132 | 133 | global feasibleNodeLists 134 | feasibleNodeLists = [[] for i in range(K)] 135 | 136 | global T 137 | T = [[0.2 for i in range(numNodes)] for j in range(numNodes)] #start with a small constant? 138 | 139 | global H #Heuristic matrix --- will this be different for different types/"species" of ants?.. TBD ***********************************************!!!!!!!!!!!!!!! 140 | H = [[0.5 for i in range(numNodes)] for j in range(numNodes)] #****************** NEEDS TO BE FIXED TO WORK WITH HEURISTICS 141 | 142 | global machineList 143 | machineList = [[] for i in range(numMachines)] 144 | 145 | global cliquesVisited 146 | cliquesVisited = [[] for i in range(K)] 147 | 148 | generateAnts() 149 | generateMachineLists() 150 | 151 | ############################## INITIALIZATION --- END 152 | 153 | 154 | ############################## CLASSES --- START 155 | 156 | class Jobs: 157 | def __init__(self, jobSequence, Nodes, num): 158 | self.jobSequence = jobSequence 159 | self.Nodes = Nodes 160 | self.num = num 161 | 162 | class Node: 163 | def __init__(self, dependents, duration, machine, nodeNum): 164 | self.duration = duration 165 | self.dependents = [dependents for i in range(K)] 166 | self.machine = machine 167 | self.visited = False 168 | self.num = nodeNum 169 | self.startTime = 0 170 | self.endTime = 0 171 | self.scheduled = False 172 | self.antsVisited = [False for i in range(K)] 173 | self.name = 'name goes here' #fill in names via constructor 174 | self.discovered = False 175 | 176 | class Ant: 177 | def __init__(self, num): 178 | self.num = num #label each ant by a number 0 to (k-1) 179 | self.tabu = [] 180 | self.position = -1 181 | self.T = [[0 for i in range(numNodes)] for j in range(numNodes)] #pheromone matrix 182 | self.pheromoneAccumulator = [[0 for i in range(numNodes)] for j in range(numNodes)] #accumulator 183 | self.transitionRuleMatrix = [[0 for i in range(numNodes)] for j in range(numNodes)] #for equation 1 transition probability 184 | self.makespan = 0 185 | self.species = 'none' 186 | self.cycleDetected = False 187 | 188 | ############################## CLASSES --- END 189 | 190 | ############################## OTHER --- START 191 | 192 | def parameterInitialization(numAnts, numCycles): 193 | global K, C, alpha, beta, rho 194 | global Q 195 | global Q1, Q2 196 | alpha = 0.5 #influence of pheromone 197 | beta = 1 - alpha #influence of heuristic 198 | rho = 0.7 #evaporation constant 199 | K = numAnts #number of ants 200 | C = numCycles #number of cycles 201 | Q1 = float(20) #**Programming Note: this must be a float in order for TSum to calculuate as float in calculatePheromoneAccumulation() 202 | Q2 = float(5) 203 | #EAS Procedure determination (below) of number of ants and fixed number of cycles 204 | #K = int(numJobs/2) 205 | #C = 1000 206 | #Q = float(5) # //note: there is no Q1 and Q2 -- these are original 207 | 208 | def generateAnts(): 209 | for i in range(K): 210 | ANTLIST.append(Ant(i)) 211 | 212 | def generateMachineLists(): 213 | for i in range(numMachines): 214 | for j in range(numNodes): 215 | if NODESLIST[j].machine == i: 216 | machineList[i].append(NODESLIST[j].num) 217 | 218 | def generateSolutionGraphs(): 219 | for k in range(K): 220 | constructConjunctiveGraph() 221 | solutionGraphList[k] = conjunctiveGraph 222 | 223 | def constructConjunctiveGraph(): 224 | global conjunctiveGraph 225 | conjunctiveGraph = [[-1 for i in range(numNodes)] for j in range(numNodes)] 226 | 227 | for job in JOBSLIST: 228 | for seq1 in job.jobSequence: 229 | for seq2 in job.jobSequence: 230 | if seq1 <> seq2 and seq1+1 == seq2: 231 | conjunctiveGraph[seq1+1][seq2+1] = NODESLIST[seq2+1].duration 232 | for j in range(numJobs): 233 | conjunctiveGraph[Source.num][JOBSLIST[j].Nodes[0].num] = JOBSLIST[j].Nodes[0].duration 234 | for j in range(numJobs): 235 | conjunctiveGraph[JOBSLIST[j].Nodes[numMachines-1].num][Sink.num] = 0 236 | 237 | def chooseClique(antNum): 238 | randomClique = random.randint(0,numMachines-1) #choose Clique by random - we then choose to travel on nodes based off of pheromone trails - this prevents local optimia to occur 239 | while randomClique in cliquesVisited[antNum]: 240 | randomClique = random.randint(0,numMachines-1) 241 | cliquesVisited[antNum].append(randomClique) 242 | return randomClique 243 | 244 | def randomAssignment(): 245 | for i in range(K): 246 | randNode = random.randint(1,numNodes-2) 247 | ANTLIST[i].tabu.append(NODESLIST[randNode].num) 248 | ANTLIST[i].position = randNode 249 | NODESLIST[randNode].antsVisited[i] = True 250 | 251 | def defineDecidabilityRule(): #UNUSED 04/15/2017 -- For Heuristics ** 252 | for ant in ANTLIST: 253 | speciesType = random.randint(1,2) 254 | if speciesType == 1: 255 | ant.species = 'SPT' #Shortest Processing Time (SPT) 256 | elif speciesType == 2: 257 | ant.species = 'LPT' #Longest Processing Time (LPT) 258 | 259 | ############################## OTHER --- END 4,1,25,14,13,14,9,4 260 | 261 | ############################## SCHEDULING --- START 262 | 263 | def schedule(ant): 264 | scheduleNode(bestSoFarNODESLIST[numNodes-1],ant) 265 | def scheduleNode(node,ant): 266 | for proceedingNode in node.dependents[ant.num]: 267 | if proceedingNode.scheduled == False: 268 | scheduleNode(proceedingNode,ant) 269 | positionNode(node,ant) #base case 270 | node.scheduled = True 271 | 272 | def positionNode(node,ant): 273 | global longestProceedingTime 274 | if len(node.dependents[ant.num])>0: 275 | node.startTime = (bestSoFarNODESLIST[node.num].dependents[ant.num][0].startTime + node.dependents[ant.num][0].duration) 276 | bestSoFarNODESLIST[node.num].startTime = (bestSoFarNODESLIST[node.num].dependents[ant.num][0].startTime + node.dependents[ant.num][0].duration) 277 | for proceedingNode in node.dependents[ant.num]: 278 | longestProceedingTime = (proceedingNode.startTime + proceedingNode.duration) 279 | if longestProceedingTime > node.startTime: 280 | node.startTime = longestProceedingTime 281 | bestSoFarNODESLIST[node.num].startTime = longestProceedingTime 282 | else: #node has no proceeding nodes and can be scheduled right away 283 | node.startTime = 0 284 | bestSoFarNODESLIST[node.num].startTime = 0 285 | node.endTime = node.startTime + node.duration 286 | bestSoFarNODESLIST[node.num].endTime = node.startTime + node.duration 287 | ############################## SCHEDULING --- END 288 | 289 | 290 | ############################## END OF CYCLE --- START 291 | 292 | def calculatePheromoneAccumulation(ant,b): 293 | if b == 0: 294 | for i in range(numNodes): 295 | for j in range(numNodes): 296 | if i != j and solutionGraphList[ant.num][i][j] > 0: 297 | ant.pheromoneAccumulator[i][j] = Q1/ant.makespan # calculate w.r.t. equation 3 or 4 *** //Python Note: in python 2.7 one of the two integers 298 | # must be a float, we declared Q as a float() type in the parameterInitialization() method 299 | elif b == 1: 300 | for i in range(numNodes): 301 | for j in range(numNodes): 302 | if i != j and solutionGraphList[ant.num][i][j] > 0: 303 | ant.pheromoneAccumulator[i][j] = Q2/ant.makespan 304 | 305 | def updatePheromone(bestMakespan, bestAntNum): 306 | TSum = 0 307 | TOld = T 308 | for i in range(numNodes): 309 | for j in range(numNodes): 310 | for ant in ANTLIST: 311 | TSum += ant.pheromoneAccumulator[i][j] 312 | T[i][j] = TSum + rho*TOld[i][j] #update T[i][j] pheromone matrix based on equation 2 *** ***c<1 accounted for in construction() [main] method*** 313 | #pheromoneAccumulator[i][j] = 0 <---- Necessary? 314 | TSum = 0 315 | for i in range(numNodes): #update for best ant 316 | for j in range(numNodes): 317 | if solutionGraphList[bestAntNum][i][j] > 0: 318 | T[i][j] += float(float(solutionGraphList[bestAntNum][i][j])/float(bestMakespan)) 319 | 320 | def resetAnts(): 321 | nextMachine = -1 322 | for k in range(K): 323 | for i in range(numMachines): 324 | cliquesVisited[k].pop() 325 | constructConjunctiveGraph() 326 | generateSolutionGraphs() 327 | #**LEARNING REMARK: #cliquesVisited = [[] for i in range(K)] *** NOTE: In Python this does not reset the variable/ double array list 328 | for ant in ANTLIST: 329 | ant.tabu = [] 330 | ant.position = -1 331 | ant.T = [[0 for i in range(numNodes)] for j in range(numNodes)] #pheromone matrix 332 | ant.pheromoneAccumulator = [[0 for i in range(numNodes)] for j in range(numNodes)] #accumulator 333 | ant.makespan = 0 334 | ant.cycleDetected = False 335 | currentMachine = -1 336 | 337 | def resetNodes(): 338 | for k in range(K): 339 | for node in NODESLIST: 340 | node.visited = False 341 | node.antsVisited[k] = False 342 | for k in range(K): 343 | Node0.dependents[k]=[] #**LEARNING REMARK: #We can't overwrite objects 344 | Node1.dependents[k]=[Node0] 345 | Node2.dependents[k]=[Node0, Node1] 346 | Node3.dependents[k]=[Node0, Node1, Node2] 347 | Node4.dependents[k]=[] 348 | Node5.dependents[k]=[Node4] 349 | Node6.dependents[k]=[Node4, Node5] 350 | Node7.dependents[k]=[Node4, Node5, Node6] 351 | Node8.dependents[k]=[] 352 | Node9.dependents[k]=[Node8] 353 | Node10.dependents[k]=[Node8, Node9] 354 | Node11.dependents[k]=[Node8, Node9, Node10] 355 | Node12.dependents[k]=[] 356 | Node13.dependents[k]=[Node12] 357 | Node14.dependents[k]=[Node12, Node13] 358 | Node15.dependents[k]=[Node12, Node13, Node14] 359 | Node16.dependents[k]=[] 360 | Node17.dependents[k]=[Node16] 361 | Node18.dependents[k]=[Node16, Node17] 362 | Node19.dependents[k]=[Node16, Node17, Node18] 363 | Node20.dependents[k]=[] 364 | Node21.dependents[k]=[Node20] 365 | Node22.dependents[k]=[Node20, Node21] 366 | Node23.dependents[k]=[Node20, Node21, Node22] 367 | Node24.dependents[k]=[] 368 | Node25.dependents[k]=[Node24] 369 | Node26.dependents[k]=[Node24, Node25] 370 | Node27.dependents[k]=[Node24, Node25, Node26] 371 | Node28.dependents[k]=[] 372 | Node29.dependents[k]=[Node28] 373 | Node30.dependents[k]=[Node28, Node29] 374 | Node31.dependents[k]=[Node28, Node29, Node30] 375 | #dummyNodes: (Source & Sink) 376 | Source.dependents[k] = [] 377 | Sink.dependents[k] = [Node3, Node7, Node11, Node15, Node19, Node23, Node27, Node31] 378 | ############################## END OF CYCLE --- END 379 | 380 | ############################## EXPLORATION --- START 381 | 382 | def nextOperation(ant, machNum, cycle): 383 | findFeasibleNodes(ant, machNum) 384 | calculateTransitionProbability(ant) 385 | makeDecision(ant) 386 | 387 | def findFeasibleNodes(ant,currentMachine): 388 | global feasibleNodeLists 389 | feasibleNodeLists = [[] for i in range(K)] 390 | for node in NODESLIST: 391 | if node.antsVisited[ant.num] == False: #04/04/17: Removed not() 392 | if node.num in machineList[currentMachine]: 393 | feasibleNodeLists[ant.num].append(node) 394 | 395 | def calculateTransitionProbability(ant): 396 | for node in feasibleNodeLists[ant.num]: 397 | if node.num not in ant.tabu: 398 | ant.transitionRuleMatrix[ant.position][node.num] = (((T[ant.position][node.num])**(alpha)) * ((H[ant.position][node.num])**(beta)))/sum((((T[ant.position][l.num])**(alpha)) * ((H[ant.position][l.num])**(beta))) for l in feasibleNodeLists[ant.num]) 399 | 400 | def makeDecision(ant): 401 | probabilityList = [] #assign ranges between [0,1] and pick a rand num in interval. 402 | for node in feasibleNodeLists[ant.num]: 403 | probabilityList.append([ant.transitionRuleMatrix[ant.position][node.num]*100,node.num]) 404 | for i in range(len(probabilityList)-1): 405 | probabilityList[i+1][0] += probabilityList[i][0] 406 | randomSelection = random.randint(0,100) 407 | selectedNode = -1 408 | for i in range(len(probabilityList)-1): 409 | if (probabilityList[i][0] <= randomSelection) and (randomSelection <= probabilityList[i+1][0]): 410 | selectedNode = probabilityList[i+1][1] 411 | break 412 | elif randomSelection <= probabilityList[i][0]: #should this be a strict less than, "<" ? 413 | selectedNode = probabilityList[i][1] 414 | break 415 | if selectedNode == -1: 416 | selectedNode = probabilityList[0][1] 417 | ant.position = selectedNode 418 | 419 | ############################## EXPLORATON --- END 420 | 421 | ############################## CONSTRUCTION PHASE --- START 422 | 423 | def constructionPhase(): #The Probabilistic Construction Phrase of solutions begins by K ants 424 | for c in range(C): 425 | defineDecidabilityRule() 426 | for node in NODESLIST: #reset nodes visited to false since this is a new cycle 427 | for ant in ANTLIST: 428 | node.antsVisited[ant.num] = False 429 | for ant in ANTLIST: 430 | skip_counter = 0 431 | for i in range(numMachines): 432 | currentMachine = chooseClique(ant.num) #at random 433 | if c<1: 434 | if ant.num == 0: 435 | shuffledNodes = machineList[currentMachine] #Without shuffling, this *guarantees* no 'cycle' in the solution graph for c = 0. 436 | skip_counter = 0 437 | for x in machineList[currentMachine]: 438 | if skip_counter%numJobs != 0: 439 | oldAntPosition = ant.position 440 | ant.tabu.append(x) 441 | ant.position = x 442 | NODESLIST[ant.position].antsVisited[ant.num] = True 443 | if skip_counter%numJobs != 0: 444 | NODESLIST[ant.position].dependents[ant.num].append(NODESLIST[oldAntPosition]) 445 | solutionGraphList[ant.num][oldAntPosition][ant.position] = NODESLIST[ant.position].duration 446 | skip_counter += 1 447 | else: 448 | for j in range(len(machineList[currentMachine])): 449 | if skip_counter%numJobs != 0: 450 | moveFrom = ant.position 451 | nextOperation(ant, currentMachine, c) 452 | moveTo = ant.position 453 | ant.tabu.append(moveTo) 454 | NODESLIST[moveTo].visited = True 455 | NODESLIST[moveTo].antsVisited[ant.num] = True 456 | if skip_counter%numJobs != 0: 457 | NODESLIST[moveTo].dependents[ant.num].append(NODESLIST[moveFrom]) 458 | solutionGraphList[ant.num][moveFrom][moveTo] = NODESLIST[moveTo].duration # set equal to the duration of the moving to node (puts weight on edge) 459 | skip_counter += 1 460 | for ant in ANTLIST: 461 | undiscoverNodes() 462 | global has_cycle 463 | has_cycle = False 464 | cycleDetector(ant) 465 | ant.cycleDetected = has_cycle 466 | if ant.cycleDetected == False: 467 | if c == 0: 468 | if ant.num == 0: 469 | ant.makespan = getMakespan(ant) #longest path in solution graph --- this is equivalent to the makespan 470 | elif ant.num != 0: 471 | ant.makespan = sys.float_info.max 472 | elif c != 0: 473 | ant.makespan = getMakespan(ant) #longest path in solution graph --- this is equivalent to the makespan 474 | calculatePheromoneAccumulation(ant,0) 475 | elif ant.cycleDetected == True: 476 | ant.makespan = sys.float_info.max 477 | #print(' need to fill this out, so this way pheromones and stuff are filled properly for cycle ants, i.e. they contribute nothing.') #**************************************!!!!!!!!!!!!!!!!!!!!!!!!!!!!! 478 | smallestMakespan = sys.float_info.max 479 | smallestMakespan,smallestMakespanAntNum = getSmallestMakespan() 480 | calculatePheromoneAccumulation(ANTLIST[smallestMakespanAntNum],1) #reinforce the smallestMakespan ant 481 | print 'The best makespan of cycle ' + str(c) + ' is: ', smallestMakespan 482 | #print ANTLIST[smallestMakespanAntNum].makespan 483 | print ANTLIST[smallestMakespanAntNum].cycleDetected 484 | ## for j in range(numNodes): 485 | ## print solutionGraphList[smallestMakespanAntNum][j] 486 | if c>0: 487 | if smallestMakespan < smallestSoFarMakespan: 488 | bestSoFarAnt = copy.deepcopy(ANTLIST[smallestMakespanAntNum]) 489 | for i in range(numNodes): 490 | bestSoFarNODESLIST[i] = copy.deepcopy(NODESLIST[i]) 491 | bestSoFarSolutionGraph.append(copy.deepcopy(solutionGraphList[bestSoFarAnt.num][i])) 492 | for i in range(K): 493 | bestSoFarANTLIST[i] = copy.deepcopy(ANTLIST[i]) 494 | for i in range(numJobs): 495 | JOBSLIST2[i] = copy.deepcopy(JOBSLIST[i]) 496 | smallestSoFarMakespan = smallestMakespan 497 | smallestSoFarAntNum = smallestMakespanAntNum 498 | elif c == 0: 499 | bestSoFarAnt = copy.deepcopy(ANTLIST[smallestMakespanAntNum]) 500 | for i in range(numNodes): 501 | bestSoFarNODESLIST.append(copy.deepcopy(NODESLIST[i])) 502 | bestSoFarSolutionGraph.append(copy.deepcopy(solutionGraphList[bestSoFarAnt.num][i])) 503 | for i in range(K): 504 | bestSoFarANTLIST.append(copy.deepcopy(ANTLIST[i])) 505 | for i in range(numJobs): 506 | JOBSLIST2.append(copy.deepcopy(JOBSLIST[i])) 507 | smallestSoFarMakespan = smallestMakespan 508 | smallestSoFarAntNum = smallestMakespanAntNum 509 | updatePheromone(smallestMakespan, smallestMakespanAntNum) 510 | resetNodes() 511 | resetAnts() 512 | print 'bestSoFarAnt.makespan: =', bestSoFarAnt.makespan 513 | print '\n' 514 | print 'The absolute best makespan =', bestSoFarAnt.makespan 515 | for i in range(numNodes): 516 | print i,':' 517 | for j in range(len(bestSoFarNODESLIST[i].dependents[bestSoFarAnt.num])): 518 | print bestSoFarNODESLIST[i].dependents[bestSoFarAnt.num][j].num 519 | print '\n' 520 | schedule(bestSoFarAnt) 521 | for i in range(numNodes): 522 | print i,':' 523 | print 'num',bestSoFarNODESLIST[i].num 524 | print 'start',bestSoFarNODESLIST[i].startTime 525 | print 'end',bestSoFarNODESLIST[i].endTime 526 | print '\n' 527 | makeGanttChart(bestSoFarAnt) 528 | 529 | ############################## CONSTRUCTION PHASE --- END 530 | 531 | ############################## USED FOR DETECTING CYCLES --- START 532 | def cycleDetector(ant): 533 | global has_cycle 534 | for node in NODESLIST: 535 | undiscoverNodes() #sets all nodes to undiscovered 536 | pcount = 0 537 | S = [] #let S be a stack 538 | S.append(node) 539 | while len(S) > 0: 540 | v = S.pop() 541 | if v.discovered == False: 542 | if v != node: 543 | v.discovered = True 544 | if v == node and pcount >=1: 545 | has_cycle = True 546 | return 547 | for j in range(numNodes): 548 | if solutionGraphList[ant.num][v.num][j] >= 0: 549 | S.append(NODESLIST[j]) 550 | pcount += 1 551 | 552 | def undiscoverNodes(): 553 | for node in NODESLIST: 554 | node.discovered = False 555 | 556 | ############################## USED FOR DETECTING CYCLES --- END 557 | 558 | 559 | ############################## MAKESPAN --- START 560 | 561 | def getSmallestMakespan(): 562 | smallestMakespan = sys.float_info.max #1.7976931348623157e+308 563 | smallestMakespanAntNum = -1 564 | for ant in ANTLIST: 565 | if ant.makespan < smallestMakespan: 566 | smallestMakespan = ant.makespan 567 | smallestMakespanAntNum = ant.num 568 | return smallestMakespan, smallestMakespanAntNum 569 | 570 | def getMakespan(ant): 571 | G = defaultdict(list) 572 | edges = [] 573 | for i in range(numNodes): 574 | for j in range(numNodes): 575 | if solutionGraphList[ant.num][i][j] != -1: 576 | edges.append([NODESLIST[i], NODESLIST[j]]) 577 | for (s,t) in edges: 578 | G[s].append(t) 579 | all_paths = DFS(G,Source) 580 | max_len = 0 581 | max_paths = [] 582 | max_makespan = 0 583 | path_duration = 0 584 | mkspnIndex_i = -1 585 | for i in range(len(all_paths)): 586 | path_duration = 0 587 | for j in range(len(all_paths[i])): 588 | path_duration += all_paths[i][j].duration 589 | if path_duration > max_makespan: 590 | max_makespan = path_duration 591 | mkspnIndex_i = i 592 | return max_makespan 593 | 594 | def DFS(G,v,seen=None,path=None): #v is the starting node 595 | if seen is None: seen = [] 596 | if path is None: path = [v] 597 | seen.append(v) 598 | paths = [] 599 | for t in G[v]: 600 | if t not in seen: 601 | t_path = path + [t] 602 | paths.append(tuple(t_path)) 603 | paths.extend(DFS(G, t, seen[:], t_path)) 604 | return paths 605 | 606 | ############################## USED FOR GETTING MAKESPAN --- END 607 | 608 | ############################## GANTT --- START 609 | def makeGanttChart(bestSoFarAnt): 610 | #quit() #to not use more than 30 API calls per hour or 50 per day 611 | df = [] 612 | for job in JOBSLIST: #I believe this could stay as JOBSLIST since we pass in only the node.num into the bestSoFarNODESLIST 613 | for node in job.Nodes: 614 | print 'Operation/Node num: ' + str(node.num) + ', Job num: ' + str(job.num) + ', Machine num: ' + str(node.machine) 615 | s = str(datetime.datetime.strptime('2017-04-18 00:00:00', "%Y-%m-%d %H:%M:%S") + datetime.timedelta(days=bestSoFarNODESLIST[node.num].startTime)) 616 | d = datetime.datetime.strptime(s, "%Y-%m-%d %H:%M:%S") + datetime.timedelta(days=bestSoFarNODESLIST[node.num].duration) 617 | df.append(dict(Task=str(node.machine), Start=str(s), Finish=str(d), Resource=str(job.num))) 618 | print 'Start date: ' + str(s) 619 | print 'End date: ' + str(d) 620 | print '\n' 621 | 622 | colors = {'1': 'rgb(255, 155, 0)', 623 | #'1': 'rgb(0, 0, 0)', // this is black 624 | #'2': (1, 0.9, 0.16), 625 | '2': 'rgb(255, 0, 0)', 626 | '3': 'rgb(0, 255, 0)', 627 | '4': 'rgb(0, 0, 255)', 628 | '5': 'rgb(255, 255, 0)', 629 | '6': 'rgb(255, 0, 255)', 630 | '7': 'rgb(0, 255, 255)', 631 | #'8': 'rgb(255, 255, 255)', //this is white 632 | '8': 'rgb(30, 30, 30)'} 633 | #quit() 634 | fig = ff.create_gantt(df, colors=colors, index_col='Resource', show_colorbar=True, group_tasks=True) 635 | py.iplot(fig, filename='4J:3M; param(' + str(K) + ',' + str(C) + ')-makespan: ' + str(bestSoFarAnt.makespan) , world_readable=True) 636 | print 'Schedule completed.' 637 | 638 | ############################## GANTT --- END 639 | 640 | ############################## Main() Method Below: 641 | 642 | initialization() 643 | constructionPhase() 644 | print 'Schedule completed.' 645 | 646 | ''' 647 | Idea for interface: 648 | When an ant moves to a node, add dependency to node it's moving from if it's not already a dependent. 649 | Give node a position propery for matrix. e.g., node.matrixPosition = [i][j] #look into paper about using modulus 650 | ''' 651 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ACO-JSSP 2 | ## Job Shop Scheduling Problem via Ant Colony Optimization 3 | 4 | #### Introduction to Ant Colony Optimization 5 | 6 | Combinatorial optimization problems naturally arise in the industrial world all the time. Often, having sufficient solutions to these problems can save companies millions of dollars. We 7 | know of many examples in which optimization problems arise; for example: bus scheduling, 8 | telecommunication network design, travelling, and vehicle routing problems. Perhaps the 9 | most famous combinatorial optimization problem is the travelling salesman problem (TSP). 10 | The TSP can be simply thought of as the problem of figuring out a tour of cities a salesman 11 | must travel (visiting each city exactly once) so that the total distance travelled is minimized. 12 | 13 | With the evergrowing arise of combinatorial optimization problems and their intrinsic link 14 | to industrial problem, many researchers, mathematicians and computer scientists, have developed a plethora of algorithms to solve these problems. We now distinguish the two types 15 | of algorithms; complete and approximate. Complete algorithms are able to solve these problems in such a way that in the end, the optimal solution is given. However, since many of 16 | these problems are N P-hard the optimal solution may take a long time to obtain. This is 17 | the reason why there are approximate algorithms. Approximate algorithms are designed, as 18 | expected, to give approximately the optimal solution. The great thing about using an approximate algorithm is that they can obtain good results in a relatively short amount of time. 19 | 20 | Ant colony optimization (ACO) algorithms are some of the most recent class of algorithms 21 | designed to approximate combinatorial optimization problems. The algorithm behaves similar to real ants and their biological abilities to find the nearest food source and bring it back 22 | to their nest. The main source of communication between ants is the depositing of chemically produced pheromone onto their paths. It is with this key idea that Marco Dorigo and 23 | colleagues were inspired to create this new class of algorithms. 24 | -------------------------------------------------------------------------------- /presentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/addejans/ACO-JSSP/ac8e3ff25d913d6bbd8d724b2c015d050f52ab66/presentation.pdf --------------------------------------------------------------------------------