Doğrusal Toplam Atama Çözücü

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.

doğrusal toplam atama akış grafiği

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:

  1. 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]]
  2. 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.
    for worker in range(0, rows):
    for task in range(0, cols):
      if cost[worker][task] != 'NA':
        assignment.AddArcWithCost(worker, task, cost[worker][task])
    Eklenen satır, maliyet matrisine girişi '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.

doğrusal toplam atama çözümü akış grafiği

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.