In diesem Abschnitt wird der Lineare Summenzuweisungslöser beschrieben, für die einfache Aufgabe, die schneller sein kann als die MIP- oder CP-SAT-Löser. Die MIP- und CP-SAT-Lösen können jedoch breiteres Spektrum an Problemen, sodass sie in den meisten Fällen die beste Option sind.
Kostenmatrix
Die Kosten für Worker und die Aufgabe sind in der folgenden Tabelle aufgeführt.
Worker | Aufgabe 0 | Aufgabe 1 | Aufgabe 2 | Aufgabe 3 |
---|---|---|---|---|
0 | 90 | 76 | 75 | 70 |
1 | 35 | 85 | 55 | 65 |
2 | 125 | 95 | 90 | 105 |
3 | 45 | 110 | 95 | 115 |
In den folgenden Abschnitten wird ein Python-Programm vorgestellt, das eine Aufgabe löst. Aufgabe des linearen Summenlösers lösen.
Bibliotheken importieren
Unten sehen Sie den Code, der die erforderliche Bibliothek importiert.
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;
Daten definieren
Mit dem folgenden Code werden die Daten für das Programm erstellt.
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();
Das Array ist die Kostenmatrix, deren Eintrag i, j die Kosten für Worker i darstellt. um Aufgabe j auszuführen. Es gibt vier Worker, die den Zeilen der und vier Aufgaben, die den Spalten entsprechen.
Matherechner erstellen
Das Programm verwendet das linearen Aufgaben-Löser einen speziellen Löser für die Aufgabe.
Mit dem folgenden Code wird der Löser erstellt.
Python
assignment = linear_sum_assignment.SimpleLinearSumAssignment()
C++
SimpleLinearSumAssignment assignment;
Java
LinearSumAssignment assignment = new LinearSumAssignment();
C#
LinearSumAssignment assignment = new LinearSumAssignment();
Einschränkungen hinzufügen
Der folgende Code fügt die Kosten zum Solver hinzu, indem Worker und Aufgaben.
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]); } } }
Solver aufrufen
Der folgende Code ruft den Solver auf.
Python
status = assignment.solve()
C++
SimpleLinearSumAssignment::Status status = assignment.Solve();
Java
LinearSumAssignment.Status status = assignment.solve();
C#
LinearSumAssignment.Status status = assignment.Solve();
Ergebnisse anzeigen
Der folgende Code zeigt die Lösung.
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}."); }
Die folgende Ausgabe zeigt die optimale Zuweisung von Workern zu Aufgaben.
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
Das folgende Diagramm zeigt die Lösung als gestrichelte Kanten im Diagramm. Die Zahlen neben den gestrichelten Kanten sind die Kosten. Die Gesamtwartezeit für diese Zuweisung ist die Summe der Kosten für den gestrichelten Kanten, also 265.
In der Graphtheorie ist ein Satz von Kanten in einem zweiteiligen Graphen, der jedem Knoten mit genau einem Knoten auf der rechten Seite wird als perfekter Abgleich bezeichnet.
Das gesamte Programm
Hier ist das gesamte Programm.
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}."); } } }
Lösung, wenn Worker nicht alle Aufgaben ausführen können
Im vorherigen Beispiel wurde davon ausgegangen, dass alle Worker alle Aufgaben ausführen können. Aber Dies ist nicht immer der Fall. Ein Mitarbeiter kann möglicherweise eine oder mehrere Aufgaben nicht ausführen. aus verschiedenen Gründen. Es ist jedoch einfach, das obige Programm zu modifizieren, dies.
Angenommen, der Worker 0 kann Aufgabe 3 nicht ausführen. So ändern Sie die nehmen Sie die folgenden Änderungen vor:
- Ändern Sie den Eintrag 0, 3 der Kostenmatrix in den String
'NA'
. Es kann ein beliebiger String verwendet werden.cost = [[90, 76, 75, 'NA'], [35, 85, 55, 65], [125, 95, 90, 105], [45, 110, 95, 115]]
- Fügen Sie im Abschnitt des Codes, der dem Matherechner die Kosten zuweist, die folgende Zeile hinzu:
if cost[worker][task] != 'NA':
, wie unten gezeigt. Die hinzugefügte Zeile verhindert alle Kanten, deren Eintrag in der Kostenmatrixfor worker in range(0, rows): for task in range(0, cols): if cost[worker][task] != 'NA': assignment.AddArcWithCost(worker, task, cost[worker][task])
'NA'
ist. dem Rechner hinzugefügt werden soll.
Nachdem Sie diese Änderungen vorgenommen und den geänderten Code ausgeführt haben, wird Folgendes angezeigt: Ausgabe:
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
Beachten Sie, dass die Gesamtkosten jetzt höher sind als für das ursprüngliche Problem. Dies ist keine Überraschung, da für das ursprüngliche Problem die optimale Lösung hat Aufgabe 3 Worker 0 zugewiesen, während bei der geänderten Aufgabe die Zuweisung nicht zulässig.
Um zu sehen, was passiert, wenn mehr Worker Aufgaben nicht ausführen können, können Sie
weitere Einträge der Kostenmatrix mit 'NA'
, um zusätzliche Worker anzugeben, die
bestimmte Aufgaben nicht ausführen können:
cost = [[90, 76, 'NA', 'NA'], [35, 85, 'NA', 'NA'], [125, 95, 'NA','NA'], [45, 110, 95, 115]]
Wenn Sie das Programm dieses Mal ausführen, erhalten Sie ein negatives Ergebnis:
No assignment is possible.
Das bedeutet, dass es keine Möglichkeit gibt, Worker Aufgaben zuzuweisen, sodass jeder Worker
eine andere Aufgabe ausführt. In der Grafik sehen Sie, warum das so ist.
für das Problem (wenn es keine Kanten gibt, die den Werten von 'NA'
entsprechen)
in der Kostenmatrix).
Da die Knoten für die drei Worker 0, 1 und 2 nur mit den beiden Knoten für die Aufgaben 0 und 1 haben, ist es nicht möglich, diesen Arbeiter.
Der Ehesatz
Es gibt ein bekanntes Ergebnis aus der Graphtheorie: Der Paarsatz So lässt sich genau ermitteln, wann Sie jedem Knoten auf der linken Seite einen eigenen Knoten auf der rechten Seite in einem zweiteiligen Diagramm wie der obigen. Eine solche Abtretung wird als perfekter Abgleich bezeichnet. Der Satz besagt, dass dies möglich ist. Wenn sich auf der linken Seite keine Teilmenge von Knoten befindet (wie bei dem vorherigen Beispiel) ), deren Kanten zu einem kleineren Satz von Knoten auf der rechten Seite führen.
Genauer gesagt besagt der Satz, dass ein zweiteiliger Graph eine perfekte Übereinstimmung wenn und nur wenn für eine Teilmenge S der Knoten auf der linken Seite des Graphen der Wert Satz von Knoten auf der rechten Seite des Graphen, die durch eine Kante mit einem Knoten in S mindestens so groß wie S.