Bu bölümde doğrusal toplam atama problemi çözme aracı açıklanmaktadır. basit ödev problemi çözme aracı MIP veya CP-SAT çözücü. Ancak, MIP ve CP-SAT çözücüler, bir dizi dolayısıyla çoğu durumda en iyi seçenektir.
Maliyet matrisi
Çalışanların ve görevlerin maliyetleri aşağıdaki tabloda verilmiştir.
Çalışan | Görev 0 | Görev 1 | Görev 2 | Görev 3 |
---|---|---|---|---|
0 | 90 | 76 | 75 | 70 |
1 | 35 | 85 | 55 | 65 |
2 | 125 | 95 | 90 | 105 |
3 | 45 | 110 | 95 | 115 |
Aşağıdaki bölümlerde bir ödevi çözen bir Python programı sunulmaktadır problem çözme ve problem çözme problemlerini çözebilmelisiniz.
Kitaplıkları içe aktarma
Gerekli kitaplığı içe aktaran kod aşağıda gösterilmektedir.
Python
import numpy as np from ortools.graph.python import linear_sum_assignment
C++
#include "ortools/graph/assignment.h" #include <cstdint> #include <numeric> #include <string> #include <vector>
Java
import com.google.ortools.Loader; import com.google.ortools.graph.LinearSumAssignment; import java.util.stream.IntStream;
C#
using System; using System.Collections.Generic; using System.Linq; using Google.OrTools.Graph;
Verileri tanımlama
Aşağıdaki kod, program için veri oluşturur.
Python
costs = np.array( [ [90, 76, 75, 70], [35, 85, 55, 65], [125, 95, 90, 105], [45, 110, 95, 115], ] ) # Let's transform this into 3 parallel vectors (start_nodes, end_nodes, # arc_costs) end_nodes_unraveled, start_nodes_unraveled = np.meshgrid( np.arange(costs.shape[1]), np.arange(costs.shape[0]) ) start_nodes = start_nodes_unraveled.ravel() end_nodes = end_nodes_unraveled.ravel() arc_costs = costs.ravel()
C++
const int num_workers = 4; std::vector<int> all_workers(num_workers); std::iota(all_workers.begin(), all_workers.end(), 0); const int num_tasks = 4; std::vector<int> all_tasks(num_tasks); std::iota(all_tasks.begin(), all_tasks.end(), 0); const std::vector<std::vector<int>> costs = {{ {{90, 76, 75, 70}}, // Worker 0 {{35, 85, 55, 65}}, // Worker 1 {{125, 95, 90, 105}}, // Worker 2 {{45, 110, 95, 115}}, // Worker 3 }};
Java
final int[][] costs = { {90, 76, 75, 70}, {35, 85, 55, 65}, {125, 95, 90, 105}, {45, 110, 95, 115}, }; final int numWorkers = 4; final int numTasks = 4; final int[] allWorkers = IntStream.range(0, numWorkers).toArray(); final int[] allTasks = IntStream.range(0, numTasks).toArray();
C#
int[,] costs = { { 90, 76, 75, 70 }, { 35, 85, 55, 65 }, { 125, 95, 90, 105 }, { 45, 110, 95, 115 }, }; int numWorkers = 4; int[] allWorkers = Enumerable.Range(0, numWorkers).ToArray(); int numTasks = 4; int[] allTasks = Enumerable.Range(0, numTasks).ToArray();
Dizi, maliyet matrisi olup i, j girişi, i çalışanının maliyetidir. j görevini yerine getirmek için kullanılır. Satırlarda yer alan dört çalışana karşılık ilgili dört görevi görebilirsiniz.
Çözücü oluşturun
Program, doğrusal atama çözücü, özel bir çözücü olarak sunulmaktadır.
Aşağıdaki kod çözücüyü oluşturur.
Python
assignment = linear_sum_assignment.SimpleLinearSumAssignment()
C++
SimpleLinearSumAssignment assignment;
Java
LinearSumAssignment assignment = new LinearSumAssignment();
C#
LinearSumAssignment assignment = new LinearSumAssignment();
Kısıtlama ekleme
Aşağıdaki kod, çalışanları döngüye alarak ve çözücüye maliyetleri ekleyerek görevlerden biridir.
Python
assignment.add_arcs_with_cost(start_nodes, end_nodes, arc_costs)
C++
for (int w : all_workers) { for (int t : all_tasks) { if (costs[w][t]) { assignment.AddArcWithCost(w, t, costs[w][t]); } } }
Java
// Add each arc. for (int w : allWorkers) { for (int t : allTasks) { if (costs[w][t] != 0) { assignment.addArcWithCost(w, t, costs[w][t]); } } }
C#
// Add each arc. foreach (int w in allWorkers) { foreach (int t in allTasks) { if (costs[w, t] != 0) { assignment.AddArcWithCost(w, t, costs[w, t]); } } }
Çözücüyü çağır
Aşağıdaki kod çözücüyü çağırır.
Python
status = assignment.solve()
C++
SimpleLinearSumAssignment::Status status = assignment.Solve();
Java
LinearSumAssignment.Status status = assignment.solve();
C#
LinearSumAssignment.Status status = assignment.Solve();
Sonuçları görüntüle
Çözüm aşağıdaki kodda gösterilir.
Python
if status == assignment.OPTIMAL: print(f"Total cost = {assignment.optimal_cost()}\n") for i in range(0, assignment.num_nodes()): print( f"Worker {i} assigned to task {assignment.right_mate(i)}." + f" Cost = {assignment.assignment_cost(i)}" ) elif status == assignment.INFEASIBLE: print("No assignment is possible.") elif status == assignment.POSSIBLE_OVERFLOW: print("Some input costs are too large and may cause an integer overflow.")
C++
if (status == SimpleLinearSumAssignment::OPTIMAL) { LOG(INFO) << "Total cost: " << assignment.OptimalCost(); for (int worker : all_workers) { LOG(INFO) << "Worker " << std::to_string(worker) << " assigned to task " << std::to_string(assignment.RightMate(worker)) << ". Cost: " << std::to_string(assignment.AssignmentCost(worker)) << "."; } } else { LOG(INFO) << "Solving the linear assignment problem failed."; }
Java
if (status == LinearSumAssignment.Status.OPTIMAL) { System.out.println("Total cost: " + assignment.getOptimalCost()); for (int worker : allWorkers) { System.out.println("Worker " + worker + " assigned to task " + assignment.getRightMate(worker) + ". Cost: " + assignment.getAssignmentCost(worker)); } } else { System.out.println("Solving the min cost flow problem failed."); System.out.println("Solver status: " + status); }
C#
if (status == LinearSumAssignment.Status.OPTIMAL) { Console.WriteLine($"Total cost: {assignment.OptimalCost()}."); foreach (int worker in allWorkers) { Console.WriteLine($"Worker {worker} assigned to task {assignment.RightMate(worker)}. " + $"Cost: {assignment.AssignmentCost(worker)}."); } } else { Console.WriteLine("Solving the linear assignment problem failed."); Console.WriteLine($"Solver status: {status}."); }
Aşağıdaki çıktıda, çalışanların görevlere en uygun şekilde nasıl atandığı gösterilmektedir.
Total cost = 265 Worker 0 assigned to task 3. Cost = 70 Worker 1 assigned to task 2. Cost = 55 Worker 2 assigned to task 1. Cost = 95 Worker 3 assigned to task 0. Cost = 45 Time = 0.000147 seconds
Aşağıdaki grafikte, çözüm grafikte kesik çizgili kenarlar olarak gösterilmektedir. İlgili içeriği oluşturmak için kullanılan sayıların maliyetini gösterir. Bu atamanın toplam bekleme süresi, kesikli kenarlar (265) vardır.
Grafik teorisinde, iki taraflı bir grafikteki her düğümle eşleşen bir kenar kümesi sağında tam olarak bir düğüm bulunan soldaki soldaki mükemmel eşleşme olarak adlandırılır.
Programın tamamı
Programın tamamı burada.
Python
"""Solve assignment problem using linear assignment solver.""" import numpy as np from ortools.graph.python import linear_sum_assignment def main(): """Linear Sum Assignment example.""" assignment = linear_sum_assignment.SimpleLinearSumAssignment() costs = np.array( [ [90, 76, 75, 70], [35, 85, 55, 65], [125, 95, 90, 105], [45, 110, 95, 115], ] ) # Let's transform this into 3 parallel vectors (start_nodes, end_nodes, # arc_costs) end_nodes_unraveled, start_nodes_unraveled = np.meshgrid( np.arange(costs.shape[1]), np.arange(costs.shape[0]) ) start_nodes = start_nodes_unraveled.ravel() end_nodes = end_nodes_unraveled.ravel() arc_costs = costs.ravel() assignment.add_arcs_with_cost(start_nodes, end_nodes, arc_costs) status = assignment.solve() if status == assignment.OPTIMAL: print(f"Total cost = {assignment.optimal_cost()}\n") for i in range(0, assignment.num_nodes()): print( f"Worker {i} assigned to task {assignment.right_mate(i)}." + f" Cost = {assignment.assignment_cost(i)}" ) elif status == assignment.INFEASIBLE: print("No assignment is possible.") elif status == assignment.POSSIBLE_OVERFLOW: print("Some input costs are too large and may cause an integer overflow.") if __name__ == "__main__": main()
C++
#include "ortools/graph/assignment.h" #include <cstdint> #include <numeric> #include <string> #include <vector> namespace operations_research { // Simple Linear Sum Assignment Problem (LSAP). void AssignmentLinearSumAssignment() { SimpleLinearSumAssignment assignment; const int num_workers = 4; std::vector<int> all_workers(num_workers); std::iota(all_workers.begin(), all_workers.end(), 0); const int num_tasks = 4; std::vector<int> all_tasks(num_tasks); std::iota(all_tasks.begin(), all_tasks.end(), 0); const std::vector<std::vector<int>> costs = {{ {{90, 76, 75, 70}}, // Worker 0 {{35, 85, 55, 65}}, // Worker 1 {{125, 95, 90, 105}}, // Worker 2 {{45, 110, 95, 115}}, // Worker 3 }}; for (int w : all_workers) { for (int t : all_tasks) { if (costs[w][t]) { assignment.AddArcWithCost(w, t, costs[w][t]); } } } SimpleLinearSumAssignment::Status status = assignment.Solve(); if (status == SimpleLinearSumAssignment::OPTIMAL) { LOG(INFO) << "Total cost: " << assignment.OptimalCost(); for (int worker : all_workers) { LOG(INFO) << "Worker " << std::to_string(worker) << " assigned to task " << std::to_string(assignment.RightMate(worker)) << ". Cost: " << std::to_string(assignment.AssignmentCost(worker)) << "."; } } else { LOG(INFO) << "Solving the linear assignment problem failed."; } } } // namespace operations_research int main() { operations_research::AssignmentLinearSumAssignment(); return EXIT_SUCCESS; }
Java
package com.google.ortools.graph.samples; import com.google.ortools.Loader; import com.google.ortools.graph.LinearSumAssignment; import java.util.stream.IntStream; /** Minimal Linear Sum Assignment problem. */ public class AssignmentLinearSumAssignment { public static void main(String[] args) { Loader.loadNativeLibraries(); LinearSumAssignment assignment = new LinearSumAssignment(); final int[][] costs = { {90, 76, 75, 70}, {35, 85, 55, 65}, {125, 95, 90, 105}, {45, 110, 95, 115}, }; final int numWorkers = 4; final int numTasks = 4; final int[] allWorkers = IntStream.range(0, numWorkers).toArray(); final int[] allTasks = IntStream.range(0, numTasks).toArray(); // Add each arc. for (int w : allWorkers) { for (int t : allTasks) { if (costs[w][t] != 0) { assignment.addArcWithCost(w, t, costs[w][t]); } } } LinearSumAssignment.Status status = assignment.solve(); if (status == LinearSumAssignment.Status.OPTIMAL) { System.out.println("Total cost: " + assignment.getOptimalCost()); for (int worker : allWorkers) { System.out.println("Worker " + worker + " assigned to task " + assignment.getRightMate(worker) + ". Cost: " + assignment.getAssignmentCost(worker)); } } else { System.out.println("Solving the min cost flow problem failed."); System.out.println("Solver status: " + status); } } private AssignmentLinearSumAssignment() {} }
C#
using System; using System.Collections.Generic; using System.Linq; using Google.OrTools.Graph; public class AssignmentLinearSumAssignment { static void Main() { LinearSumAssignment assignment = new LinearSumAssignment(); int[,] costs = { { 90, 76, 75, 70 }, { 35, 85, 55, 65 }, { 125, 95, 90, 105 }, { 45, 110, 95, 115 }, }; int numWorkers = 4; int[] allWorkers = Enumerable.Range(0, numWorkers).ToArray(); int numTasks = 4; int[] allTasks = Enumerable.Range(0, numTasks).ToArray(); // Add each arc. foreach (int w in allWorkers) { foreach (int t in allTasks) { if (costs[w, t] != 0) { assignment.AddArcWithCost(w, t, costs[w, t]); } } } LinearSumAssignment.Status status = assignment.Solve(); if (status == LinearSumAssignment.Status.OPTIMAL) { Console.WriteLine($"Total cost: {assignment.OptimalCost()}."); foreach (int worker in allWorkers) { Console.WriteLine($"Worker {worker} assigned to task {assignment.RightMate(worker)}. " + $"Cost: {assignment.AssignmentCost(worker)}."); } } else { Console.WriteLine("Solving the linear assignment problem failed."); Console.WriteLine($"Solver status: {status}."); } } }
Çalışanlar tüm görevleri yapamadığında sunulan çözüm
Önceki örnekte tüm çalışanların tüm görevleri gerçekleştirebileceğini varsaymıştık. Ama Bu durum her zaman yaşanmaz. Bir çalışan, bir veya daha fazla görevi yerine getiremeyebilir. çeşitli nedenlerle kullanır. Ancak, yukarıdaki programı işleyecek şekilde değiştirmek kolaydır. bu.
Örneğin, 0 çalışanının 3. görevi gerçekleştiremediğini varsayalım. aşağıdaki değişiklikleri yapın:
- Maliyet matrisinde girilen 0, 3 değerini
'NA'
dizesiyle değiştirin. (Herhangi bir dize kullanılabilir.)cost = [[90, 76, 75, 'NA'], [35, 85, 55, 65], [125, 95, 90, 105], [45, 110, 95, 115]]
- Kodun maliyetleri çözücüye atayan bölümüne şu satırı ekleyin:
if cost[worker][task] != 'NA':
, aşağıda gösterildiği gibi. Eklenen satır, maliyet matrisine girişifor worker in range(0, rows): for task in range(0, cols): if cost[worker][task] != 'NA': assignment.AddArcWithCost(worker, task, cost[worker][task])
'NA'
olan herhangi bir kenarı engeller eklenmesini engeller.
Bu değişiklikleri yapıp değiştirilen kodu çalıştırdıktan sonra, şunu görürsünüz: çıkış:
Total cost = 276 Worker 0 assigned to task 1. Cost = 76 Worker 1 assigned to task 3. Cost = 65 Worker 2 assigned to task 2. Cost = 90 Worker 3 assigned to task 0. Cost = 45
Toplam maliyetin şu anda orijinal problem için olduğundan daha yüksek olduğuna dikkat edin. Bu şaşırtıcı değildir, çünkü ilk problemde en iyi çözüm 0. çalışana görev 3 atanırken, değiştirilen problemde atama izin verilmez.
Daha fazla çalışanın görev yapamaması durumunda ne olacağını görmek için
gösteren ek çalışanları belirtmek için 'NA'
ile maliyet matrisine daha fazla giriş
kişi belirli görevleri gerçekleştiremez:
cost = [[90, 76, 'NA', 'NA'], [35, 85, 'NA', 'NA'], [125, 95, 'NA','NA'], [45, 110, 95, 115]]
Programı bu sefer çalıştırdığınızda olumsuz bir sonuç alıyorsunuz:
No assignment is possible.
Yani çalışanlar görevlere atanamaz; böylece her çalışan
farklı bir görev gerçekleştiriyor. Grafiğe bakarak bunun nedenini öğrenebilirsiniz
'NA'
değerlerine karşılık gelen kenarların bulunmadığı
dahil edilir.
0, 1 ve 2 numaralı üç çalışanın düğümleri yalnızca ikisine bağlı olduğundan görevlere ayrı görevler atamak mümkün değildir. birlikte çalışır.
Evlilik Teoremi
Grafik teorisinde iyi bilinen bir sonuç vardır Evlilik Teoremi, Bu, soldaki her bir düğümü tam olarak ne zaman farklı bir konuma düğümünü yukarıdaki gibi iki taraflı bir grafikte görürsünüz. Böyle bir atama mükemmel eşleme olarak adlandırılır. Özetle teorem, bunun mümkün olduğunu söylüyor soldaki düğüm alt kümesi yoksa (önceki örnekteki gibi) şeklinde gösterilir.
Daha net ifade etmek gerekirse teorem, iki partili bir grafiğin mükemmel eşleşme yalnızca grafiğin sol tarafındaki düğümlerin herhangi bir S alt kümesi için grafiğin sağ tarafında, bir kenarla belirli bir noktaya bağlanan düğümler S'deki düğüm en az S kadar büyüktür.