├── .gitignore ├── Documentation.pdf ├── loading hurestic results (GA).xlsx ├── loading hurestic results (Tabu).xlsx ├── README.md ├── Blok.py ├── input.py ├── tabu.py ├── Solution.py ├── BOX.py ├── Genetic_AL.py ├── space.py └── Data ├── wtpack1.txt ├── wtpack2.txt └── wtpack3.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /Documentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdims/3D-bin-packing/HEAD/Documentation.pdf -------------------------------------------------------------------------------- /loading hurestic results (GA).xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdims/3D-bin-packing/HEAD/loading hurestic results (GA).xlsx -------------------------------------------------------------------------------- /loading hurestic results (Tabu).xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mahdims/3D-bin-packing/HEAD/loading hurestic results (Tabu).xlsx -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 3D-bin-packing 2 | Refer to the document for problem defination and project description. 3 | 4 | The implementation is partially inspired by (Jiamin Liu, et al., 2011) 5 | 6 | Instances ara from : http://people.brunel.ac.uk/~mastjjb/jeb/orlib/conloadinfo.html 7 | 8 | # Run the program 9 | To run the GA, run the "python2 Genetic_AL.py" 10 | 11 | In lines 227 - 229 you find these lists: 12 | 13 | 14 | * times = [60, 120] : specifies the time limit of the algorithm in seconds (give just one number if you want the algorithm runs once) 15 | 16 | * filenumbers= [4,5,6,7] : specifies which "Data/wtpack?.txt" files your instace is in. Currently each file has 100 instances 17 | 18 | * instances=[33,45,52,63,78,84] : specifies which instance inside the file will be solved 19 | 20 | 21 | # References 22 | 1- Liu J., Yue Y., Dong Z., Maple C., Keech M., 2011. "A novel hybrid Tabu Search approach the container loading". Computers & Operations Research 38, 797–807. 23 | -------------------------------------------------------------------------------- /Blok.py: -------------------------------------------------------------------------------- 1 | from space import space 2 | 3 | class Blok: 4 | AllBloks=[] #class attribute (we need to store the loaded blocks) 5 | def __init__(self,w,h,l,boxtype,quantity,orientation): #creates blocks 6 | self.L=l 7 | self.W=w 8 | self.H=h 9 | self.volume=self.L*self.W*self.H 10 | self.boxtype=boxtype 11 | self.boxori=orientation 12 | self.pos=[] 13 | self.quantity=quantity 14 | self.weight=[] 15 | self.priority=[] 16 | 17 | def partition(self,S): 18 | # According to Figure a in the article 19 | # Front space 20 | self.pos=[S.pos[0],S.pos[4]-self.W,S.pos[2]] 21 | fl2=S.pos[0]+self.L 22 | fw2=S.pos[4]-self.W 23 | Front_space=space(S.pos[0],S.pos[1],S.pos[2],fl2,fw2,S.pos[5]) 24 | # Upper sapce 25 | ul2=self.L+S.pos[0] 26 | uw1=S.pos[4]-self.W 27 | uh1=self.H+S.pos[2] 28 | Upper_space=space(S.pos[0],uw1,uh1,ul2,S.pos[4],S.pos[5]) 29 | Upper_space.lowerBox_type=[self.boxtype] 30 | # Right space 31 | rl1=S.pos[0]+self.L 32 | Right_space=space(rl1,S.pos[1],S.pos[2],S.pos[3],S.pos[4],S.pos[5]) 33 | # delete the initial space 34 | S.delet() 35 | Blok.AllBloks.append(self) 36 | return 37 | 38 | @classmethod 39 | def reset(cls): 40 | cls.AllBloks=[] 41 | @classmethod 42 | def blokweights(cls,Data): 43 | maxpri=max(Data.boxes[:,8]) 44 | for a in cls.AllBloks: 45 | a.weight=a.quantity*Data.boxes[a.boxtype,7] 46 | a.priority=maxpri-Data.boxes[a.boxtype,8] 47 | -------------------------------------------------------------------------------- /input.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import os 3 | dir_path = os.path.dirname(os.path.realpath(__file__)) 4 | class Input: 5 | 6 | def __init__(self,fileNo,ProblemNo): 7 | self.contdim=[] 8 | self.ntype=[] 9 | self.boxes=[] 10 | ## Obtain Data #### 11 | ifile=open(dir_path+'\Data\wtpack%d.txt' % fileNo, "rb") 12 | lines=ifile.readlines() 13 | self.ntype=int(lines[1].split()[0]) 14 | lines=lines[ProblemNo*(self.ntype+2):(ProblemNo+1)*(self.ntype+2)] 15 | ifile.close() 16 | ### Convert strings to numbers ### 17 | self.contdim=lines[0].split() 18 | self.contdim=[int(a) for a in self.contdim] 19 | box=[] 20 | for line in lines[2:2+self.ntype]: 21 | box.append(line.split()) 22 | box=np.array(box,dtype=float)[:,0:9] 23 | box[:,7]=box[:,0]*box[:,2]*box[:,4] 24 | box[:,8]=np.random.randint(10,size=(1,self.ntype)) 25 | box=box[np.argsort(box[:,7]),:] 26 | self.boxes=box 27 | #del lines[0:2+self.ntype[0]] 28 | ##### generate the constraint 29 | self.Top_Bot=np.ones((self.ntype,self.ntype )) 30 | inx1=np.random.randint(0,self.ntype , self.ntype) 31 | inx2=np.random.randint(0,self.ntype , self.ntype) 32 | self.Top_Bot[inx1,inx2]=0 33 | def RandomData(self,NO_Box_type,contdim=[587, 233, 220]): 34 | self.contdim=contdim 35 | self.ntype=NO_Box_type 36 | box=np.zeros((self.ntype,9)) 37 | for i in range (self.ntype): 38 | box[i,0]=np.random.randint(contdim[0]/15,contdim[0]/4.5) # lenght 39 | box[i,1]=np.random.randint(2) 40 | box[i,2]=np.random.randint(contdim[1]/15,contdim[1]/4.5) # width 41 | box[i,3]=np.random.randint(2) 42 | box[i,4]=np.random.randint(contdim[2]/15,contdim[2]/4.5) # hieght 43 | box[i,5]=np.random.randint(2) 44 | 45 | box[i,6]=np.random.randint(2,15) # quantity 46 | 47 | box[:,7]=box[:,0]*box[:,2]*box[:,4] 48 | 49 | box[:,8]=np.random.randint(10,size=(1,self.ntype)) 50 | box=box[np.argsort(box[:,7]),:] 51 | self.boxes=box 52 | self.Top_Bot=np.ones((self.ntype,self.ntype )) 53 | inx1=np.random.randint(0,self.ntype , self.ntype) 54 | inx2=np.random.randint(0,self.ntype , self.ntype) 55 | self.Top_Bot[inx1,inx2]=0 56 | 57 | -------------------------------------------------------------------------------- /tabu.py: -------------------------------------------------------------------------------- 1 | import heapq # Libarary for array sorting https://docs.python.org/2/library/heapq.html 2 | import time 3 | import numpy as np 4 | import math 5 | import pandas as pd 6 | from Solution import Solution 7 | from input import Input 8 | 9 | 10 | 11 | def Write2Excel(results): 12 | solution=pd.DataFrame(results, columns=['Box Type','Box Oriantation in Blok','Box quantity in Blok','Box priority','Blok Starting point','lenght','Width','Hight']) 13 | solution.to_excel('loading hurestic results (Tabu).xlsx') 14 | return 15 | 16 | def Tabu_search( Initial_Sol,alpha , beta , gamma ,tabu_size, max_iterations=300, max_solutions=10 , MaxRunTime=60): 17 | start=time.time() # start the timer 18 | Solution_list = [ (-1*Initial_Sol.Score_Calc(Data, alpha , beta , gamma)[0], Initial_Sol) ] 19 | current_Sol= Solution(None) #init for while loop 20 | Best_Sol=Solution(None) 21 | tabu_set = [] 22 | it=1 23 | # Main Loop 24 | while it<= max_iterations and time.time() <= start+MaxRunTime: 25 | # Select the solution with minimal score 26 | _ , current_Sol = heapq.heappop( Solution_list ) 27 | # if the current solution is better than best solution so far change the best solution 28 | if current_Sol.h_score>Best_Sol.h_score: 29 | Best_Sol=current_Sol 30 | Best_Sol.Score_Calc(Data, alpha , beta , gamma) 31 | #print current_Sol.VU, len(Solution_list) 32 | # genearting new solutions 33 | for Sol,rep in current_Sol.generate_children(6): 34 | # Check if the move is in Tabu list or not 35 | if rep not in tabu_set: 36 | tabu_set = [rep] + tabu_set[:tabu_size-1] 37 | heapq.heappush( Solution_list, (-1*Sol.Score_Calc(Data, alpha , beta , gamma)[0],Sol)) # Add the new solution into the solution list 38 | 39 | # Maintain a fix lenght for the solution list 40 | Solution_list = Solution_list[:max_solutions] 41 | it+=1 42 | 43 | return (Best_Sol, time.time()-start) 44 | 45 | 46 | 47 | 48 | 49 | 50 | alpha , beta , gamma = 1 , 0 , 0 51 | times=[3,5,10,15,30,60] 52 | filenumbers=[1,2,3,4,5,6,7] 53 | instances=[2,10,15,22,33,45,52,63,78,84] 54 | Final_results=np.zeros((6,7)) 55 | for t,T in enumerate(times): 56 | 57 | for f,FN in enumerate(filenumbers): 58 | VU=[] 59 | for PN in instances: 60 | Data=Input(FN,PN) 61 | #Data.RandomData(40) 62 | MaxRunTime=T 63 | 64 | 65 | tabulist_size=int(math.ceil(float(Data.ntype)/2)) 66 | max_solutions=2*Data.ntype 67 | 68 | Initial_sol= Solution(range(Data.ntype)) # gave a starting solution 69 | # Apply the Tabu 70 | 71 | (Best_Sol,Runtime )=Tabu_search( Initial_sol ,alpha , beta , gamma, 72 | tabulist_size, max_solutions=max_solutions ,MaxRunTime=MaxRunTime ) 73 | print('Volume Utilization = %f ' %Best_Sol.VU) 74 | VU.append(Best_Sol.VU) 75 | Final_results[t,f]=np.mean(VU) 76 | 77 | 78 | #print'Best Solution= ' ,Best_Sol.value #,100-Best_Sol.h_score) 79 | #print('Volume Utilization = %f ' %Best_Sol.VU) 80 | #print('Wieght distrbution measure= %f' %Best_Sol.WCG) 81 | #print('Distance from back measure= %f where the maximum is %f' %(Best_Sol.DFF,Solution.max_DFF())) 82 | #print('Run Time = %f sec' %Runtime) 83 | #print('Total number of loaded boxes = %f' %Best_Sol.Total_Box_Number(Data)) 84 | #Write2Excel(Best_Sol.Loading_Results) 85 | -------------------------------------------------------------------------------- /Solution.py: -------------------------------------------------------------------------------- 1 | from numpy.random import choice 2 | from space import space 3 | from BOX import BOX 4 | from Blok import Blok 5 | 6 | class Solution: 7 | DisFromFront=[] 8 | def __init__(self, value ): 9 | self.value = value 10 | self.h_score = None 11 | self.VU=None 12 | self.WCG=None 13 | self.DFF=None 14 | self.Loading_Results=None 15 | 16 | def generate_children(self,Childern_No): 17 | children=[] 18 | for _ in xrange(Childern_No): 19 | rep=choice(len(self.value),2,replace=False) 20 | rep.sort() 21 | children.append((Solution(self.value[:rep[0]]+[self.value[rep[1]]] 22 | +self.value[rep[0]+1:rep[1]] 23 | +[self.value[rep[0]]]+self.value[rep[1]+1:]),tuple(rep))) 24 | return children 25 | 26 | 27 | def Total_Box_Number(self,Data): 28 | self.loading(Data) 29 | TotalNumberBox=sum([a.quantity for a in Blok.AllBloks ]) 30 | return int(TotalNumberBox) 31 | 32 | 33 | 34 | def loading(self,Data): 35 | #### loading hurestic ### 36 | space.reset() 37 | BOX.reset() 38 | Blok.reset() 39 | (L,W,H)=Data.contdim 40 | S=space(0,0,0,L,W,H) 41 | BOXS=[] 42 | for j in self.value: 43 | BOXS.append(BOX(Data,j)) 44 | 45 | while len(space.remainlist)!=0 or BOX.Is_unloaded_BOX(): 46 | S=S.merge() 47 | j=0 48 | while(j0 and currentbox.Can_Load(Data,S)): 52 | BBlok=currentbox.Best_Blok(S) 53 | BBlok.partition(S) 54 | if len(space.remainlist)!=0: 55 | S=space.curentspace() 56 | j=0 57 | else: 58 | break 59 | 60 | S=S.merge() 61 | #j+=1 62 | else: 63 | j+=1 64 | 65 | S.waste() 66 | if len(space.remainlist)!=0: 67 | S=space.curentspace() 68 | else: 69 | break 70 | 71 | 72 | 73 | def Score_Calc(self, Data, alpha, beta, gamma ) : 74 | (L,W,H)=Data.contdim 75 | self.loading(Data) 76 | results=[] 77 | con_volume=L*W*H 78 | Utilized_volume=0 79 | CGX,CGY,CGZ = 0, 0, 0 80 | Totalweight=0 81 | DisX,Totalpriority=0,0 82 | Blok.blokweights(Data) # assign weights and priorities to Best Blockes 83 | for a in Blok.AllBloks: 84 | results.append((a.boxtype,a.boxori,a.quantity,a.priority,a.pos,a.L,a.W,a.H)) 85 | Utilized_volume+=a.volume 86 | #Calculating the weight distrbution 87 | CGX += a.weight*(a.pos[0]+a.L/2) 88 | CGY += a.weight*(a.pos[1]+a.W/2) 89 | CGZ += a.weight*(a.pos[2]+a.H/2) 90 | Totalweight += a.weight 91 | #Calculating the distance from front 92 | DisX+=a.priority*(a.pos[0]+a.L/2) 93 | Totalpriority+=a.priority 94 | 95 | CGX=CGX/Totalweight 96 | CGY=CGY/Totalweight 97 | CGZ=CGZ/Totalweight 98 | He=Utilized_volume/(L*W) 99 | Dist=max(abs(CGX-L/2)/(L/2),abs(CGY-W/2)/(W/2),abs(CGZ-He/2)/(He/2)) 100 | WCG=(1-Dist)*100 101 | #Calculating the Volume utilisation 102 | VU=(Utilized_volume/con_volume)*100 103 | # Calcualting the distance from front 104 | DisX=DisX/Totalpriority 105 | 106 | DFF=((DisX)/L)*100 107 | 108 | self.h_score=alpha*VU+beta*WCG+gamma*DFF 109 | self.Loading_Results=results 110 | self.VU=VU 111 | self.DFF=DFF 112 | self.WCG=WCG 113 | Solution.DisFromFront.append(self.DFF) 114 | return (self.h_score,self) 115 | 116 | 117 | @classmethod 118 | def max_DFF(cls): 119 | return max(cls.DisFromFront) -------------------------------------------------------------------------------- /BOX.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from Blok import Blok #the best blok arrangement is determined in the methods of this class 3 | 4 | class BOX: 5 | AllBoxes=[] 6 | def __init__(self,Data,No): #build boxes as objects 7 | if No<=Data.ntype: 8 | self.dims=Data.boxes[No,(0,2,4)] 9 | self.l=self.dims[0] 10 | self.w=self.dims[1] 11 | self.h=self.dims[2] 12 | self.oriantation=Data.boxes[No,(1,3,5)] 13 | self.quantity=Data.boxes[No,6] 14 | self.weight=Data.boxes[No,7] 15 | self.type=No 16 | BOX.AllBoxes.append(self) 17 | else: 18 | exit("Try less box numbers") 19 | 20 | def Can_Load(self,Data,S): 21 | # check if the volume of one box in less than or equal to the current remianig space volume 22 | check=0 23 | if S.volume>= self.l*self.w*self.h : 24 | if S.lowerBox_type==[]: 25 | if sum(map(lambda x: x[0] , self.Possible_Oriatation(S) ))>=1: 26 | check=1 27 | elif np.all(Data.Top_Bot[self.type,S.lowerBox_type]==1) : 28 | # box should fit in the space at least in one oriantation 29 | if sum(map(lambda x: x[0] , self.Possible_Oriatation(S) ))>=1: 30 | check=1 31 | return check 32 | 33 | def Possible_Oriatation(self,S): 34 | ori=[[0] for _ in range(6)] 35 | #Ori #1 y lenght x width 36 | #Ori #2 y lenght x hight 37 | if self.l <= S.W: 38 | if self.w <= S.L and self.h<=S.H and self.oriantation[2]==1: 39 | ori[0]=[1,self.l,self.h,self.w] #(w,h,l) 40 | 41 | if self.h <= S.L and self.w<=S.H and self.oriantation[1]==1: 42 | ori[1]=[1,self.l,self.w,self.h] 43 | 44 | #Ori #3 y width x lenght 45 | #Ori #4 y width x hight 46 | if self.w <= S.W: 47 | 48 | if self.l <= S.L and self.h<=S.H and self.oriantation[2]==1: 49 | ori[2]=[1,self.w,self.h,self.l] 50 | 51 | if self.h <= S.L and self.l<=S.H and self.oriantation[0]==1: 52 | ori[3]=[1,self.w,self.l,self.h] 53 | 54 | #Ori #5 y hight x lenght 55 | #Ori #6 y hight x width 56 | if self.h <= S.W: 57 | 58 | if self.l <= S.L and self.w<=S.H and self.oriantation[1]==1: 59 | ori[4]=[1,self.h,self.w,self.l] 60 | 61 | if self.w <= S.L and self.l<=S.H and self.oriantation[0]==1: 62 | ori[5]=[1,self.h,self.l,self.w] 63 | 64 | return ori 65 | 66 | def Best_Blok(self,S): 67 | #Bvol=self.l*self.w*self.h 68 | #Kmax=int(S.volume/Bvol) 69 | Pori=self.Possible_Oriatation(S) 70 | f=[] # Objective function 71 | B=[] # All six bloks 72 | Ks=[] # Blok quantity 73 | for i,a in enumerate(Pori): 74 | if a[0]==1: 75 | Max_num_in_W=int(S.W/a[1]) 76 | Max_num_in_H=int(S.H/a[2]) 77 | Max_num_in_L=int(S.L/a[3]) 78 | 79 | rows=int(self.quantity/Max_num_in_W) 80 | 81 | if rows==0: 82 | 83 | Ks.append(self.quantity) 84 | B.append(Blok(a[1]*self.quantity,a[2],a[3],self.type,Ks[-1],i)) 85 | elif 1<=rows<=Max_num_in_H: 86 | 87 | Ks.append(Max_num_in_W*rows) 88 | B.append(Blok(a[1]*Max_num_in_W,a[2]*rows,a[3],self.type,Ks[-1],i)) 89 | else: 90 | columes=int(self.quantity/(Max_num_in_W*Max_num_in_H)) 91 | if columes<=Max_num_in_L: 92 | 93 | Ks.append(Max_num_in_W*Max_num_in_H*columes) 94 | B.append(Blok(a[1]*Max_num_in_W,a[2]*Max_num_in_H,a[3]*columes,self.type,Ks[-1],i)) 95 | else: 96 | 97 | Ks.append(Max_num_in_W*Max_num_in_H*Max_num_in_L) 98 | B.append(Blok(a[1]*Max_num_in_W,a[2]*Max_num_in_H,a[3]*Max_num_in_L,self.type,Ks[-1],i)) 99 | f.append(S.volume-B[-1].volume) 100 | 101 | #rule 1 102 | minvol=min(f) 103 | if minvol==0: 104 | bestblok=f.index(minvol) 105 | self.quantity-= Ks[bestblok] 106 | return B[bestblok] 107 | 108 | # rule 2 109 | for j,b in enumerate(B): 110 | if sum([S.L==b.L,S.W==b.W,S.H==b.H])==2: 111 | bestblok=j 112 | self.quantity-= Ks[bestblok] 113 | return B[bestblok] 114 | 115 | minimal_indecs=[i for i, x in enumerate(f) if x == minvol] 116 | if len(minimal_indecs)==1: 117 | # Rule 3 118 | bestblok=minimal_indecs[0] 119 | self.quantity-= Ks[bestblok] 120 | return B[bestblok] 121 | 122 | else: 123 | # Rule 4 select the one with maximum surface among minimal volume Bloks 124 | surface=[] 125 | for inx in minimal_indecs: 126 | surface.append(B[inx].L*B[inx].W) 127 | bestblok=minimal_indecs[surface.index(max(surface))] 128 | 129 | self.quantity-= Ks[bestblok] 130 | return B[bestblok] 131 | 132 | @classmethod 133 | def reset(cls): 134 | cls.AllBoxes=[] 135 | @classmethod 136 | def Is_unloaded_BOX(cls): 137 | check=0 138 | for box in cls.AllBoxes: 139 | if box.quantity!=0: 140 | check=1 141 | break 142 | return check -------------------------------------------------------------------------------- /Genetic_AL.py: -------------------------------------------------------------------------------- 1 | from Solution import Solution 2 | from input import Input 3 | import math 4 | import numpy as np 5 | import numpy.random as numr 6 | from random import shuffle 7 | import time 8 | import pandas as pd 9 | 10 | 11 | 12 | 13 | 14 | class Chromo(Solution): 15 | 16 | Listofsolutions=[] 17 | pop=[] 18 | @classmethod 19 | def reset(cls): 20 | cls.pop=[] 21 | cls.Listofsolutions=[] 22 | 23 | @classmethod 24 | def initialpop(cls,Data,nPop,Varsize,crossover_rate,mutation_rate,mu): 25 | 26 | cls.nPop=nPop 27 | cls.Varsize=Varsize 28 | cls.crossover_rate=crossover_rate 29 | cls.mutation_rate=mutation_rate 30 | cls.mu=mu 31 | 32 | 33 | while len(cls.pop) < nPop: 34 | order=range(Varsize) 35 | shuffle( order ) 36 | sol = cls( order ) 37 | if order not in Chromo.Listofsolutions: 38 | Chromo.Listofsolutions.append(order) 39 | cls.pop.append(sol.Score_Calc(Data, alpha , beta , gamma)) 40 | @classmethod 41 | def pop_sort(cls): 42 | pp=cls.pop 43 | pp=sorted(pp,key=lambda pp:pp[0],reverse=True) 44 | cls.pop=pp 45 | 46 | def mutation(self): 47 | gen2change=int(math.ceil(Chromo.mu*Chromo.Varsize)) 48 | mutate=self 49 | for _ in range(gen2change): 50 | mutate=mutate.generate_children(1)[0][0] 51 | return Chromo(mutate.value) 52 | 53 | @classmethod 54 | def crossover(cls, Dad_value, Mom_value): 55 | 56 | """partially mapped crossover""" 57 | child_Val_1=[] 58 | child_Val_2=[] 59 | 60 | (x,y)=np.random.choice(cls.Varsize,2,False) 61 | if x > y: x,y = y,x 62 | 63 | dadParts = Dad_value[x:y+1] 64 | momParts = Mom_value[x:y+1] 65 | dadPartMap = dict(zip(dadParts, momParts)) 66 | momPartMap = dict(zip(momParts, dadParts)) 67 | # create the first child 68 | for i in xrange(x): 69 | while Dad_value[i] in momParts : 70 | Dad_value[i]=momPartMap[Dad_value[i]] 71 | 72 | child_Val_1.append(Dad_value[i]) 73 | 74 | child_Val_1.extend(momParts) 75 | 76 | for j in xrange(y+1, cls.Varsize): 77 | while Dad_value[j] in momParts: 78 | Dad_value[j]=momPartMap[Dad_value[j]] 79 | child_Val_1.append(Dad_value[j]) 80 | 81 | # create the second child 82 | for i in xrange(x): 83 | while Mom_value[i] in dadParts: 84 | Mom_value[i]=dadPartMap[Mom_value[i]] 85 | 86 | child_Val_2.append(Mom_value[i]) 87 | 88 | child_Val_2.extend(dadParts) 89 | 90 | for j in xrange(y+1, cls.Varsize): 91 | while Mom_value[j] in dadParts: 92 | Mom_value[j]=dadPartMap[Mom_value[j]] 93 | 94 | child_Val_2.append(Mom_value[j]) 95 | 96 | 97 | child1 = cls(child_Val_1) 98 | child2 = cls(child_Val_2) 99 | 100 | return (child1, child2) 101 | 102 | 103 | @classmethod 104 | def evolve(cls,Data,TotalNoSolution): 105 | 106 | 107 | nPop=cls.nPop 108 | sp=1.8 # parameter in parents selection 109 | parents_length = int(nPop/1.5) # number of the parents 110 | pv=[] 111 | ############################# parents ################################ 112 | # calculate the parents selection probablity 113 | for r,individual in enumerate(cls.pop): 114 | rank=float(nPop-r-1) 115 | pv.append(round((2-sp)/nPop+2*rank*(sp-1)/(nPop*(nPop-1)),5)) 116 | pv = np.array(pv) 117 | pv /= pv.sum() 118 | # selecting the parents 119 | parents= roulette_wheel_pop(cls.pop, pv, parents_length) 120 | #################### mutate some individuals########################### 121 | Mutation_number=math.ceil(Chromo.mutation_rate*nPop) 122 | counter=1 123 | Mutants=[] 124 | Mut_inner_counter=0 125 | while counter<=Mutation_number and Mut_inner_counter<=20*Mutation_number: 126 | #Select the individual 127 | individual=parents[numr.randint(len(parents))] 128 | individual=individual.mutation() 129 | 130 | if individual.value not in Chromo.Listofsolutions: 131 | Chromo.Listofsolutions.append(individual.value) 132 | Mutants.append(individual) 133 | counter+=1 134 | Mut_inner_counter=0 135 | else: 136 | Mut_inner_counter+=1 137 | if len(Chromo.Listofsolutions)==TotalNoSolution: 138 | #sys.exit("We have all solutions already") 139 | break 140 | ########################### crossover ############################## 141 | Crossover_number = int(Chromo.crossover_rate*nPop) 142 | crosscounter=0 143 | children = [] 144 | while len(children) <= Crossover_number and crosscounter<=2*Crossover_number: 145 | crosscounter+=1 146 | 147 | (male,female)=np.random.choice(parents_length,2,False) 148 | 149 | (child1, child2)=cls.crossover( parents[male].value[:] , parents[female].value[:] ) 150 | 151 | if child1.value not in cls.Listofsolutions : 152 | cls.Listofsolutions.append(child1.value) 153 | children.append(child1) 154 | 155 | if child2.value not in cls.Listofsolutions : 156 | cls.Listofsolutions.append(child2.value) 157 | children.append(child2) 158 | 159 | if len(cls.Listofsolutions)==TotalNoSolution: 160 | #sys.exit("We have all solutions already") 161 | break 162 | 163 | ################################################################### 164 | children=[x.Score_Calc(Data, alpha , beta , gamma) for x in children] 165 | Mutants=[x.Score_Calc(Data, alpha , beta , gamma) for x in Mutants] 166 | 167 | # create the pool 168 | pool=cls.pop[:parents_length] 169 | pool.extend(children) 170 | pool.extend(Mutants) 171 | # evaluate the pool 172 | pool=sorted(pool,key=lambda pool:pool[0],reverse=True) 173 | # truncate the pool and create the new generation 174 | cls.pop=pool[0:nPop] 175 | 176 | return 177 | 178 | 179 | 180 | def roulette_wheel_pop(pop, p, number): 181 | chosen=np.random.choice(len(pop),number,False,p) 182 | chosen = [pop[a][1] for a in chosen] 183 | return chosen 184 | def Write2Excel(results): 185 | solution=pd.DataFrame(results, columns=['Box Type','Box Oriantation in Blok','Box quantity in Blok','Box priority','Blok Starting point','lenght','Width','Hight']) 186 | solution.to_excel('loading hurestic results (GA).xlsx') 187 | return 188 | 189 | 190 | 191 | def GA(Data): 192 | Varsize=Data.ntype 193 | TotalNoSolution=math.factorial(Varsize) 194 | 195 | nPop=3*int(Varsize ) #Population Size 196 | if nPop>=TotalNoSolution: nPop=TotalNoSolution 197 | MaxIt=80 # Maximum Number of Iterations 198 | Max_noimprove=25 # Maximum number of iterations without improvement before termination 199 | crossover_rate=0.7 # 200 | mutation_rate=0.3 # Mutation Percentage 201 | mu=0.15 # Mutation Rate 202 | 203 | start=time.time() 204 | 205 | iterationNO=1 206 | Chromo.initialpop(Data,nPop,Varsize,crossover_rate,mutation_rate,mu) # generate intial solution 207 | Chromo.pop_sort() # 208 | 209 | current_bestsol=Chromo.pop[0][1] 210 | noimprove=0 211 | while iterationNO<=MaxIt and noimprove=s.pos[1] and s.pos[3]==self.pos[0] and s.pos[4]>=self.pos[4]: 89 | newspace=self.__class__(s.pos[0],self.pos[1],s.pos[2],self.pos[3],self.pos[4],s.pos[5]) 90 | lowerBoxType=set( self.lowerBox_type + s.lowerBox_type ) 91 | lowerBoxType.discard(None) 92 | if bool(lowerBoxType): 93 | newspace.lowerBox_type=list(lowerBoxType) 94 | s.delet() 95 | self.delet() 96 | newaste=self.__class__(s.pos[0],self.pos[4],s.pos[2],s.pos[3],s.pos[4],s.pos[5]) 97 | 98 | newaste2=self.__class__(s.pos[0],s.pos[1],s.pos[2],s.pos[3],self.pos[1],s.pos[5]) 99 | 100 | return newspace 101 | ##Case2-b 102 | elif s.pos[0]>=self.pos[0] and self.pos[4]==s.pos[1] and self.pos[3]>=s.pos[3]: 103 | newspace=self.__class__(s.pos[0],self.pos[1],s.pos[2],s.pos[3],s.pos[4],s.pos[5]) 104 | lowerBoxType=set( self.lowerBox_type + s.lowerBox_type ) 105 | lowerBoxType.discard(None) 106 | if bool(lowerBoxType): 107 | newspace.lowerBox_type=list(lowerBoxType) 108 | s.delet() 109 | self.delet() 110 | newaste=self.__class__(s.pos[0],s.pos[1],s.pos[2],self.pos[0],s.pos[4],s.pos[5]) 111 | newaste2=self.__class__(self.pos[3],s.pos[1],s.pos[2],s.pos[3],s.pos[4],s.pos[5]) 112 | 113 | return newspace 114 | 115 | #Third Case 116 | ## Case 3_a 117 | # We collect all the spaces that adjacent to self in X && staring point of them in Y in before ending of Self 118 | if (s.pos[3]==self.pos[0]) and (s.pos[1]<=self.pos[4]): #colect potential for case 3-a 119 | PSC3_a.append(s) 120 | # Find the one that start as same Y as self started 121 | if self.pos[1]==s.pos[1]: 122 | w2_a=s 123 | Iw2C3_a=1 124 | ## Case 3_b 125 | if (s.pos[4]==self.pos[1]) and (s.pos[0]<=self.pos[3]) : #colect potential for case 3-b 126 | PSC3_b.append(s) 127 | if self.pos[0]==s.pos[0]: 128 | w2_b=s 129 | Iw2C3_b=1 130 | 131 | ## Case 3_a (Contiued) 132 | 133 | for s in PSC3_a: # for all possible candidate spaces we found 134 | if Iw2C3_a: # we need to have a space that start as the same Y self started 135 | # 136 | if w2_a.pos[4]==s.pos[1] and w2_a.W+s.W>=self.W : 137 | 138 | if w2_a.pos[0]>=s.pos[0]: 139 | newspace=self.__class__(w2_a.pos[0],self.pos[1],w2_a.pos[2],self.pos[3],self.pos[4],self.pos[5]) 140 | lowerBoxType=set( self.lowerBox_type + w2_a.lowerBox_type + s.lowerBox_type ) 141 | lowerBoxType.discard(None) 142 | if bool(lowerBoxType): 143 | newspace.lowerBox_type=list(lowerBoxType) 144 | newaste=self.__class__(s.pos[0],s.pos[1],s.pos[2],w2_a.pos[0],s.pos[4],s.pos[5]) 145 | #newaste.waste() 146 | w2_a.delet() 147 | s.delet() 148 | self.delet() 149 | 150 | if w2_a.pos[0]=self.L : 168 | 169 | if w2_b.pos[4]>=s.pos[4]: 170 | newspace=self.__class__(self.pos[0],self.pos[1],self.pos[2],self.pos[3],s.pos[4],self.pos[5]) 171 | lowerBoxType=set( self.lowerBox_type + w2_b.lowerBox_type + s.lowerBox_type ) 172 | lowerBoxType.discard(None) 173 | if bool(lowerBoxType): 174 | newspace.lowerBox_type=list(lowerBoxType) 175 | newaste=self.__class__(w2_b.pos[0],s.pos[4],w2_b.pos[2],w2_b.pos[3],w2_b.pos[4],w2_b.pos[5]) 176 | 177 | #newaste.waste() 178 | w2_b.delet() 179 | s.delet() 180 | self.delet() 181 | 182 | if w2_b.pos[4]