Giả sử bài toán JSP đã cho có công việc được xử lý trên máy. Số thao tác của công việc thứ được ký hiệu là [ ] (không quá thao tác với mọi ). Tổng số
các thao tác cần được xử lý của tất cả các công việc là:
= [ ]
Chúng ta mã hóa thao tác từ 1 đến [1], của từ [1] + 1 đến [2], …, của từ [1] + [2] + ⋯ + [ − 1] + 1 đến . Như vậy mỗi lời giải là một hoán vị nào đó của dãy số tự nhiên {1, 2, … , } thỏa mãn các ràng buộc của bài toán.
Ví dụ, với bài toán 3 công việc, 3 máy đã cho trong bảng 4.1. Các thao tác đã được mã hóa trong Bảng 4.2
Công việc Máy (thời gian xử lý)
1(1) 2(2) 3(3)
1(4) 3(5) 2(6)
2(7) 1(8) 3(9)
Bảng 4.2: Bài toán JSP 3 công việc, 3 máy
Theo { }, các công đoạn đầu tiên của và được xử lý trên máy , công đoạn thứ 2 của được xử lý trên máy 1. Vì vậy, mã của các công đoạn trên là một hoán vị nào đó của {1, 4, 8}. Tương tự các công đoạn trên là một hoán vị nào đó của {2, 6,7}, trên là một hoán vị nào đó của {3, 5, 9}. Một lời giải có thể của bài toán được biểu diễn bằng hình vẽ có dạng như Hình 4.5.
Hình 4.6: Một lời giải hợp lệ cho JSP 3x3
Lời giải trong Hình 4.6 cũng có thể biểu diễn bằng một ma trận lời giải . Trong đó, , tức là thao tác thứ trên máy là công việc .
=
1 2 3 3 2 1 2 1 3 4.3.2. Khởi tạo tập lời giải cho thế hệ ban đầu
Để sinh ra một lời giải cho thế hệ ban đầu (0), bao gồm các lịch biểu tích cực trong JSP được cho bởi ma trận tuần tự công nghệ và ma trận thời gian xử lý , chúng ta sử dụng thuật toán GT đã được trình bày mục 4.2.2. Sau khi khởi tạo tập lời giải ban đầu, chọn ra một cá thể có độ thích nghi cao nhất gọi là “cá thể tinh hoa”. Cá thể này không tham gia vào toán tử di truyền và sẽ được cập nhật sau mỗi thế hệ.
4.3.3. Xây dựng hàm thích nghi
Giả sử (0) = { , , … , }. Chúng ta ký hiệu makespan của là ( ), khi đó makespan trung bình được tính theo công thức:
(0) = 1 ( ) Hàm thích nghi của mỗi cá thể được xây dựng như sau:
( ) = − ( ), trong đó = 2 ∗ ( (0)). là tham số được đưa vào để chuyển bài toán thành bài toán (vì thuật toán di truyển chỉ áp dụng trực tiếp cho bài toán tìm ).
4.3.4. Các toán tử di truyền
4.3.4.1. Toán tử đột biến
- Chọn ngẫu nhiên một thao tác (ký hiệu là 1) trong cá thể cha. Xác định
máy thực hiện thao tác đó (ký hiệu là ) và vị trí của thao tác đó ( 1).
- Chọn ngẫu nhiên một thao tác (ký hiệu là 2) trong cá thể cha. Xác định
máy thực hiện thao tác đó (ký hiệu là ) và vị trí của thao tác đó ( 2).
- Nếu = thì tiến hành đột biến (hoán đổi vị trí của hai thao tác).
Kết quả chúng ta có cá thể con. Trong trường hợp ≠ thì cá thể cha được giữ nguyên.
- Tính độ thích nghi của cá thể con, cá thể con chỉ được chấp thuận khi có độ thích nghi tốt hơn cá thể cha hoặc số lần đột biến lại vượt ngưỡng cho phép. Mỗi cá thể con thu được sau phép đột biến có thể xem như là một lân cận của cá thể cha.
- Nếu cá thể con sau đột biến có độ thích nghi tốt hơn cá thể cha thì nó sẽ được thay thể cho cá thể cha, ngược lại giữ nguyên cá thể cha.
Trong Hình 4.6 minh họa quá trình đột biến trong cá thể cha với: + 1 = 6 → = 2 và 1 = 5;
+ 2 = 7 → = 2 và 1 = 4.
Hình 4.7: Cá thể cha cho phép đột biến
Cá thể con sau khi đột biến được biểu diễn như trong Hình 4.8. Cá thể con này sẽ kiểm tra độ thích nghi, nếu độ thích nghi của nó tốt hơn cá thể cha thì sẽ được chấp thuận, còn không phép đột biến được tiến hành lai cho tới khi gặp điều kiện kết thúc hoặc cá thể con có độ thích nghi tốt hơn.
Hình 4.8: Cá thể con thu được sau phép đội biến
4.3.4.2. Toán tử lai ghép hay còn gọi là toán tử trao đổi chéo
Toán tử lai ghép được thực hiện trên 3 cá thể cha , và được biểu diễn bởi các ma trận lời giải tương ứng = , = và = . Các gien trong cá thể con = { } sẽ được tái kết hợp từ các gien trong 3 cá thể cha. Trong phương pháp này, một toán tử lai ghép mới kết hợp đồng thời phép lai ghép đồng nhất, thuật toán GT và được thực hiện trên 3 cá thể cha để tăng tính đa dạng của cá thể con. Do sử dụng thuật toán GT nên sau khi lai ghép cá thể con vẫn là một lịch biểu tích cực. Phép lai ghép này sử dụng thuật toán GT nên đa số các bước giống như trong thuật toán GT. Sự khác biệt giữa GT và phép lai ghép này là ở bước 4 trong quy trình mô tả dưới đây:
Bước 1. Khởi tạo là tập các thao tác đầu tiền trong tuần tự công nghệ của tất cả các công việc (cột đầu tiên của ma trận { }, = { , , . . . , }. Đối với mỗi thao tác ∈ , ( ) ≔ 0 và (0) ≔ ( ).
Bước 2. Tìm thao tác hoàn thành sớm nhất ∗ ∈ . Một tập con của chứa các thao tác được xử lý ở trên máy ký hiệu là .
Bước 3. Xác định tập cạnh tranh , ⊂ . Ở đây − 1 thao tác đã được lập lịch trên máy .
1 4 8 7 2 5 3 9
1 4 8 7 6 2 5 3 9
1 2
Bước 4. Chọn một trong các cá thể cha { , , } tùy theo giá trị của ma trận , ≔ và = . Đối với mỗi ∈ , , tồn tại một chỉ số sao cho = . Gọi là chỉ số nhỏ nhất, từc là = min { / = và ∈ , }. Gọi ≔ , ∈ [ , ] sẽ được chọn để lập lịch ở trong cá thể con .
Bước 5. Lập lịch cho là công đoạn thứ trên máy : tức là ≔ , với thời gian bắt đầu và thời gian hoàn thành của nó là và ( ): =
; = .
Bước 6. Đối với tất cả các công đoạn ∈ \{ }:
- Cập nhật như sau: ≔ max { , }.
- Cập nhật như sau: ≔ + .
Bước 7. Xóa khỏi (và do đó khỏi ) và bổ sung công đoạn kế tiếp trong tuần tự công nghệ vào nếu nó tồn tại. Tức là, nếu = và < , thì ≔ và ≔ \ { ∪ { }). Tính ( ) và ( ) như sau:
- ( ) ≔ max , ( ) .
- ( ) ≔ ( ) + ( ).
Hình 4.8 trình bày một ví dụ minh họa về trao đổi chéo đồng nhất sử dụng thuật toán GT, được áp dụng cho ba cá thể cha , và với một ma trận ngẫu nhiên . Con là kết quả của phép trao đổi chéo. Cá thể cha tham gia trao đổi chéo và cá thể con sau phép trao đổi chéo được biểu diễn bằng Hình vẽ 4.9.
= 2 3 1 3 2 1 2 3 1 = 1 3 2 3 1 2 3 1 2 = 3 3 3 2 2 2 1 1 1 = 1 2 3 3 1 2 2 3 2 = 1 2 3 3 1 2 1 2 3
Hình 4.9: Cá thể cha tham gia lai ghép và cá thể con sau lai ghép
Hình 4.10: Cá thể con sau lai ghép
Bước 8. Lặp lại từ bước 2 đến bước 7 cho tới khi tất cả các công đoạn được lập lịch trong cá thể con .
Bước 9. Ma trận lời giải là lịch biểu tích cực thu được với tập thời gian bắt đầu và thời gian hoàn thành là và . Ở đây = .
4.3.4.3. Toán tử chọn lọc
Toán tử chọn lọc cho _ cá thể cho thế hệ thứ + 1 được tiến hành như sau:
1. Chọn một cá thể có độ thích nghi tốt nhất kể từ thế hệ đầu đến thế hệ ; 2. Xây dựng lời giải trung gian ( ) áp dụng cho phép đột biến và phép lai ghép:
1 4 8 2 6 5 9 2 1 4 8 2 6 3 5 9 1 8 4 7 2 6 9 3 5 4 8 1 7 6 2 5 9 3 7 7
- Áp dụng toán tử đột biến đối với ( ) được ( ) - Áp dụng toán tử trao đổi chéo đối với ( ) được ( ) - ( ) = ( ) ∪ ( ) ∪ ( )
3. Chọn _ − 1 trong tập ( ) theo nguyên lý bánh xe số. 4.3.5. Thuật toán di truyền
Thuật toán lai cho JSP được mô tả như sau: Procedure GA_JSP
Begin ← 0
Khởi tạo ( ) Đánh giá ( )
While(not điều kiện dừng) do Begin
Xây dựng lời giải ( ) bằng cách áp dụng phép đội biến và lai ghép: - Áp dụng toán tử đột biến đối với ( ) được ( )
- Áp dụng toán tử trao đổi chéo đối với ( ) được ( ) - ( ) = ( ) ∪ ( ) ∪ ( ) Đánh giá ( ) ← + 1 Chọn lọc ( ) từ ( − 1) End End
4.3.6. Tính đúng đắn của thuật toán
Tính đúng đắn của thuật toán được khẳng định thông qua các đặc trưng sau đây: 1. Do sử dụng thuật toán GT để sinh ra các lịch biểu, cho nên mỗi cá thể con được sinh ra đều là một lịch biểu hợp lệ và hơn nữa nó còn là một lịch biểu tích cực.
2. Trong phép đột biến, cá thể tham gia đột biến được sửa đổi bằng cách thay đổi thứ tự sắp xếp trong một máy nào đó, sau đó thời gian bắt đầu và thời gian kết thúc của toàn lịch biểu được cập nhật lại nên các thể con sau đột biến vẫn đảm bảo là một lịch biểu hợp lệ.
3. Phép trao đổi chéo sử dụng thuật toán GT để sinh ra các lịch biểu tích con, cho nên mỗi cá thể con được sinh ra đều là một lịch biểu hợp lệ và hơn nữa nó còn là một lịch biểu tích cực.
4. Vì thuật toán luôn duy trì lời giải tốt nhất trước hoặc sau khi chọn lọc vì vậy thuật toán được chứng tỏ hội tụ tới tối ưu toàn cục nhờ thuộc tính không giảm của thế hệ tiếp theo.
4.4. Kết quả thực nghiệm
Dựa vào thuật toán được trình bày trong mục 4.3, luận văn cài đặt thuật toán di truyền lai giải bài toán JSP bằng ngôn ngữ lập trình C# và kết quả chạy trên máy PC với bộ xử lý Core 2 Duo có tốc độ 2.4GHz. Kết quả chạy thử nghiệm trên một số bài toán (chạy mỗi bài toán 10 lần) được thống kê trên trong Bảng 4.3.
Bài toán (m x n) Cỡ lời giải Số thế hệ Xác suất lai ghép Xác suất đột biến Kết quả chạy (tốt nhất) Tối ưu thực sự Số lần đạt kết quả tốt nhất/ số lần chạy BT3x3 9 200 0.8 0.1 12 12 10/10 MT6x6 36 200 0.8 0.1 55 55 6/10 MT10x10 100 200 0.8 0.1 1010 930 0/10 LA01(5x10) 50 200 0.8 0.1 666 666 4/10 LA02(5x10) 50 200 0.8 0.1 671 655 1/10 LA03(5x10) 50 200 0.8 0.1 613 597 1/10
KẾT LUẬN 1. Các kết quả của luận văn
Luận văn trình bày bài toán lập lịch job shop (JSP) tổng quát, các phương pháp tiếp cận giải quyết bài toán JSP.
Luận văn trình bày thuật toán di truyền bao gồm: các khái niệm cơ bản, các tham số đầu vào, các toán tử và thuật toán di truyền.
Luận văn trình bày hai bài toán con của bài toán lập lịch job shop đó là bài toán flow shop hoán vị (PFSP) và bài toán flow shop (FSP). Thuật toán di truyền mã hóa số tự nhiên cho hai bài toán này.
Luận văn trình bày một thuật toán di truyền lai là sự kết hợp thuật toán di truyền với các kỹ thuật tìm kiếm khác cho bài toán lập lịch job shop (JSP).
2. Hạn chế
Vì thời thực hiện đề tài có hạn, đề tài đã dừng lại ở mức độ nghiên cứu lý thuyết về bài toán lập lịch job shop (JSP); thuật toán di truyền; các bài toán con của bài toán JSP, thuật toán di truyền lại mới là kết hợp giữa thuật toán di truyền với các kỹ thuật tìm kiếm khác cho JSP. Cài đặt thuật toán di truyền lai ở mức độ thực nghiệm giải, chưa phải là một ứng dụng hoàn thiện.
3. Hướng nghiên cứu tiếp theo
Nghiên cứu sâu hơn về bài toán lập lịch job shop và cách tiếp cận giữa thuật toán di truyền và các kỹ thuật tìm kiếm khác để tìm ra một thuật toán tốt cho JSP.
Ứng dụng thuật toán vào trong các bài toán quản lý: lập thời khóa biểu, lập lịch thi trong phạm vi trường đại học.
PHỤ LỤC
1. Các hàm chính của thuật toán di truyền lai luận văn trình bày được cài đặt bằng ngôn ngữ C# như dưới đây:
1.1. Hàm GT dùng để khởi tạo quần thể
publicOperation[,] GT(){
Operation[,] S = newOperation[numMachine + 1, numJob + 1];
List<Operation> G = newList<Operation>();
List<Operation> C = newList<Operation>();
List<Operation>[] PM = newList<Operation>[numMachine + 1];
//Khởi tạo PM
for (int i = 0; i < PM.Length; i++){
PM[i] = newList<Operation>(); }
//Bước 1 Khởi tạo G là các thao tác trên cột đầu tiên
for (int i = 1; i <= numJob; i++){
Operation ope = (Operation)arrope[i, 1].Clone();
ope.ES = 0; ope.EC = ope.pt; G.Add(ope); }
for (int k = 0; k < numOpe; k++){
Operation opeMinEc = findOpeMinEC(G);
int machineIndex = opeMinEc.machineId;
List<Operation> GM = newList<Operation>();
//Tính tập GM ở bước 2
for (int i = 0; i < G.Count; i++){
if (G[i].machineId == machineIndex){ GM.Add(G[i]);
} }
//Bước 3 CM tập cạnh tranh
List<Operation> CM = newList<Operation>();
for (int i = 0; i < GM.Count; i++){
if (GM[i].ES < opeMinEc.EC) CM.Add(GM[i]); }
//Bước 4
int idOpeChooser = rd.Next(CM.Count);
//Bước 5 int J = machineIndex; int I = numOpeCom(PM, J) + 1; S[J, I] = (Operation)opeChooser.Clone(); PM[J].Add(S[J, I]); opeChooser.s = opeChooser.ES; opeChooser.c = opeChooser.EC; S[J, I].s = opeChooser.s; S[J, I].c = opeChooser.c; //Bước 6
foreach (Operation ope in GM){
if (ope != opeChooser){
ope.ES = Math.Max(ope.ES, opeChooser.EC); ope.EC = ope.ES + ope.pt;
} }
//Bước 7
Operation opeNext = findOpeNext(opeChooser);
if (opeNext != null){
G.Add(opeNext);
Operation opeResult = findOpeBothMachine(PM, opeNext);
opeNext.ES = Math.Max(opeChooser.EC, opeResult.EC); opeNext.EC = opeNext.ES + opeNext.pt;
}
G.Remove(opeChooser); }
return S;
}
1.2. Hàm khởi tạo quần thể ban đầu
privatevoid InitPopulation(){
population = newList<Gene>();
for (int k = 0; k < nPopulation; k++) {
Operation[,] S = GT();
Gene gene = newGene();
for (int i = 1; i <= numMachine; i++){
for (int j = 1; j <= numJob; j++){
if (S[i, j] != null){
gene.Add((Operation)S[i, j]); }
} } gene.isGT = true; population.Add(gene); } tinhM();
foreach (Gene ge in population){
tinhMakespan(ge); } } 1.3. Hàm tính tham số M publicvoid tinhM(){ M = 0;
foreach (Gene ge in population){
M += 2* (getMS(ge)/ nPopulation); }
}
1.4.Hàm chọn cá thể tham gia đột biến
publicvoid selectMutation() {
List<Gene> listMutation = newList<Gene>();
int n = (int)pMutation* numOpe;
List<Gene> Temp = newList<Gene>(); Temp = Select();
for (int i = 0; i < nPopulation; i++){
if (rd.NextDouble() < pMutation){ listMutation.Add(Temp[i]); }
}
if (listMutation.Count > 0){
for (int i = 0; i < listMutation.Count ; i ++){ Mutation(listMutation[i]); }
} }
1.5. Hàm đột biến
publicvoid Mutation(Gene g){
Gene geneResult = newGene();
foreach (Operation ope in g){
Operation ope1 = (Operation)ope.Clone();
geneResult.Add(ope1); }
int p1, p2;
do{
p1 = rd.Next(0, geneResult.Count); p2 = rd.Next(0, geneResult.Count);
if (this.getMachine(geneResult[p1]) ==
this.getMachine(geneResult[p2]) && (p1 != p2)) { Operation temp; temp = geneResult[p2]; geneResult[p2] = geneResult[p1]; geneResult[p1] = temp; }
} while (this.getMachine(geneResult[p1]) !=
this.getMachine(geneResult[p2]) || (p1 == p2));
if (this.updateGene(geneResult) == true&&
kiemtratrunggene(geneResult)==false){ tinhMakespan(geneResult);
population.Add(geneResult); }
}
1.6. Hàm chọn cá thể tham gia trao đổi chéo
publicvoid selectCrossOver(){
List<Gene> listcross = newList<Gene>();
int n = (int)pCrossover * numOpe;
List<Gene> Temp = newList<Gene>(); Temp = Select();
for (int i = 0; i < nPopulation; i++){
if (rd.NextDouble() < pCrossover) { listcross.Add(Temp[i]); } } if (listcross.Count % 2 != 0){ listcross.RemoveAt(listcross.Count - 1); } if (listcross.Count > 0){
for (int i = 0; i < listcross.Count - 1; i += 2) {
Gene g = newGene();
g = CrossOver(listcross[i], listcross[i + 1]); } while (updateGene(g) == false);
if (kiemtratrunggene(g) == false){ tinhMakespan(g); population.Add(g); } } } }
1.7. Hàm trao đổi chéo
publicGene CrossOver(Gene g1, Gene g2){
Gene child = newGene();
Operation[,] S = newOperation[numMachine + 1, numJob + 1];
Operation[,] S1 = newOperation[numMachine + 1, numJob + 1];
Operation[,] S2 = newOperation[numMachine + 1, numJob + 1];
{
int k = 0;
for (int i = 1; i <= numMachine; i++)
for (int j = 1; j <= numJob; j++) { S1[i, j] = g1[k]; k++; } } { int k = 0;
for (int i = 1; i <= numMachine; i++)
for (int j = 1; j <= numJob; j++) {
S2[i, j] = g2[k]; k++;
} }
List<Operation> G = newList<Operation>();
List<Operation> C = newList<Operation>();
List<Operation>[] PM = newList<Operation>[numMachine + 1];
//Khởi tạo PM
for (int i = 0; i < PM.Length; i++) { PM[i] = newList<Operation>();
}
//Bước 1 khởi tạo tập G là các thao tác tren cột đầu tiên
for (int i = 1; i <= numJob; i++){
Operation ope = (Operation)arrope[i, 1].Clone();
ope.ES = 0; ope.EC = ope.pt; G.Add(ope); }
for (int k = 0; k < numOpe; k++){
Operation opeMinEc = findOpeMinEC(G);
int machineIndex = opeMinEc.machineId;
List<Operation> GM = newList<Operation>();
//Tính tập GM ở bước 2.
for (int i = 0; i < G.Count; i++){
if (G[i].machineId == machineIndex){ GM.Add(G[i]);
} }
//Bước 3
List<Operation> CM = newList<Operation>();//tap tranh chap
for (int i = 0; i < GM.Count; i++){
if (GM[i].ES < opeMinEc.EC) CM.Add(GM[i]); }
//Bước 4
Operation opeChooser = null;
int num = rd.Next(2);
int Lmin = int.MaxValue;
if (num == 0){
for (int kk = 0; kk < CM.Count; kk++){
int i = CM[kk].jobId;
int l = int.MaxValue;
for (int j = 1; j <= numJob; j++)
if (S1[machineIndex, j].jobId == i) { l = j; break; } if (l < Lmin) Lmin = l; }
for (int i = 0; i < CM.Count; i++){ Operation x = CM[i]; if (x.jobId == r) opeChooser = x; } } else{
for (int kk = 0; kk < CM.Count; kk++){
int i = CM[kk].jobId;
int l = int.MaxValue;
for (int j = 1; j <= numJob; j++)
if (S2[machineIndex, j].jobId == i){ l = j;
break;
}
if (l < Lmin) Lmin = l; }
int r = S2[machineIndex, Lmin].jobId;
for (int i = 0; i < CM.Count; i++){
Operation x = CM[i];