From 4b2f88c726f66cf466be68844b9714801e420a40 Mon Sep 17 00:00:00 2001
From: Maxime MORGE <maxime.morge@univ-lille.fr>
Date: Mon, 30 Sep 2024 17:59:31 +0200
Subject: [PATCH] Start implementing CBAA

---
 src/main/scala/org/scata/algorithm/CBAA.scala | 88 +++++++++++++++++++
 .../scata/core/SingleAssignmentProblem.scala  |  6 +-
 src/main/scala/org/scata/core/Worker.scala    |  1 +
 3 files changed, 92 insertions(+), 3 deletions(-)
 create mode 100644 src/main/scala/org/scata/algorithm/CBAA.scala

diff --git a/src/main/scala/org/scata/algorithm/CBAA.scala b/src/main/scala/org/scata/algorithm/CBAA.scala
new file mode 100644
index 0000000..ad01422
--- /dev/null
+++ b/src/main/scala/org/scata/algorithm/CBAA.scala
@@ -0,0 +1,88 @@
+// Copyright (C) Maxime MORGE, 2024
+package org.scata.algorithm
+
+import org.scata.core.{SingleAssignmentProblem, Task, Worker}
+
+/**
+ * The Consensus-Based Auction Algorithm (CBAA) is
+ * a single-assignment strategy
+ */
+class CBAA(pb : SingleAssignmentProblem) {
+
+  var neighbours : Map[Worker, List[Worker]] = fullyConnected()
+
+  var workerTaskList: Map[(Worker, Task), Boolean] = Map[(Worker, Task), Boolean]()
+  var winningBid : Map[(Worker, Task), Int] = Map[(Worker, Task), Int]()
+
+  pb.workers.foreach{ worker=>
+    pb.tasks.foreach{ task =>
+      workerTaskList = workerTaskList.updated((worker, task), false)
+      winningBid = winningBid.updated((worker, task), 0)
+    }
+  }
+
+  /**
+   * Generate a fully connect communication network
+   */
+  private def fullyConnected() :  Map[Worker, List[Worker]] = {
+    var neighbour = Map[Worker, List[Worker]]()
+    pb.workers.foreach{ worker =>
+      neighbour = neighbour.updated(worker, pb.workers.toList)
+    }
+    neighbour
+  }
+
+  /**
+   * Checks if two workers are neighbors
+   * @param worker1 the first worker
+   * @param worker2 the second worker
+   * @return true if they are neighbors, false otherwise
+   */
+  def isNeighbor(worker1: Worker, worker2: Worker): Boolean = neighbours(worker1).contains(worker2)
+
+
+  /**
+   * CBAA Phase 1 for worker i
+   * Selects the best task for the worker based on the cost
+   * @param i index of the worker in the workers set
+   * */
+  def selectTask(i : Int) : Unit = {
+    val worker = pb.workers.toIndexedSeq(i)
+    // Check if the worker has no tasks assigned
+    if (!pb.tasks.exists(task => workerTaskList((worker, task)))) {
+
+      // Determine valid tasks based on cost comparison
+      var validTasks: Map[Task, Boolean] = Map[Task, Boolean]()
+      pb.tasks.foreach { task =>
+        if (pb.cost(worker, task) <= winningBid(worker, task)) {
+          validTasks = validTasks.updated(task, true)
+        } else {
+          validTasks = validTasks.updated(task, false)
+        }
+      }
+      // If there are valid tasks, select the one with the minimum cost
+      if (pb.tasks.exists(task => validTasks(task))) {
+        val bestTask = validTasks.filter(_._2).keys.minBy(task => pb.cost(worker, task))
+        workerTaskList = workerTaskList.updated((worker, bestTask), true)
+        winningBid = winningBid.updated((worker, bestTask), pb.cost(worker, bestTask))
+      }
+    }
+  }
+  /**
+   * TODO CBAA Phase 1 for worker i
+   * @param i index of the worker in the workers set
+   */
+  def consensus(i: Int): Unit = {
+    val worker = pb.workers.toIndexedSeq(i)
+    // Iterate over all tasks
+    pb.tasks.foreach { task =>
+      // Find the minimum winning bid among the neighbors of the worker
+      val minWinningBid = pb.workers
+        .filter(neighbor => isNeighbor(worker, neighbor))
+        .map(neighbor => winningBid((neighbor, task)))
+        .min
+      // Update the winning bid for the worker and task
+      winningBid = winningBid.updated((worker, task), minWinningBid)
+    }
+  }
+}
diff --git a/src/main/scala/org/scata/core/SingleAssignmentProblem.scala b/src/main/scala/org/scata/core/SingleAssignmentProblem.scala
index 939719c..c81de55 100644
--- a/src/main/scala/org/scata/core/SingleAssignmentProblem.scala
+++ b/src/main/scala/org/scata/core/SingleAssignmentProblem.scala
@@ -12,7 +12,7 @@ import org.scata.utils.RandomUtils
   */
 class SingleAssignmentProblem(val workers: SortedSet[Worker],
                               val tasks : SortedSet[Task],
-                              val cost : Map[(Worker,Task),Double]) {
+                              val cost : Map[(Worker,Task), Int]) {
 
   /**
     * Returns a string describing the MASTAPlus problem
@@ -43,7 +43,7 @@ object SingleAssignmentProblem{
   implicit val order  : Ordering[Double] = Ordering.Double.TotalOrdering
   // eventually Ordering.Double.IeeeOrdering
 
-  private val MAX_COST = 100 // Maximum task cost
+  private val MAX_COST : Int = 100 // Maximum task cost
 
   /**
     * Returns a random single-assignment problem  instance with
@@ -59,7 +59,7 @@ object SingleAssignmentProblem{
     val tasks : SortedSet[Task] = collection.immutable.SortedSet[Task]() ++
       (for (i <- 1 to n) yield new Task(name = "t%02d".format(i)))
 
-    var cost : Map[(Worker,Task),Double] = Map [(Worker,Task),Double]()
+    var cost : Map[(Worker,Task),Int] = Map [(Worker,Task),Int]()
     // Adjust the resource sizes
     workers.foreach{ worker =>
       tasks.foreach{ task =>
diff --git a/src/main/scala/org/scata/core/Worker.scala b/src/main/scala/org/scata/core/Worker.scala
index 931cbb0..d05a470 100644
--- a/src/main/scala/org/scata/core/Worker.scala
+++ b/src/main/scala/org/scata/core/Worker.scala
@@ -26,3 +26,4 @@ final class Worker(val name : String) extends Ordered[Worker]{
     -1
   }
 }
+
-- 
GitLab