From 1c85bd4c2d8caeebb89e1807364668260c7f82be Mon Sep 17 00:00:00 2001 From: Maxime MORGE <maxime.morge@univ-lille.fr> Date: Fri, 7 Mar 2025 10:21:25 +0100 Subject: [PATCH] First centralize version of CBAA --- src/main/scala/Main.scala | 13 ++- src/main/scala/org/scata/algorithm/CBAA.scala | 98 +++++++++++++------ .../org/scata/core/SingleAssignment.scala | 2 +- .../scata/core/SingleAssignmentProblem.scala | 17 +++- 4 files changed, 92 insertions(+), 38 deletions(-) diff --git a/src/main/scala/Main.scala b/src/main/scala/Main.scala index 6650aa2..ddb5e0f 100644 --- a/src/main/scala/Main.scala +++ b/src/main/scala/Main.scala @@ -1,5 +1,16 @@ +// Copyright (C) Maxime MORGE 2024 +import org.scata.core.SingleAssignmentProblem +import org.scata.algorithm.CBAA + object Main { def main(args: Array[String]): Unit = { - println("Hello world!") + + val nbWorkers = 4 + val nbTasks = 3 + val pb = SingleAssignmentProblem.randomProblem(nbWorkers, nbTasks) + val cbaa = new CBAA(pb) + println(pb) + val solution = cbaa.solve() + println(solution) } } \ No newline at end of file diff --git a/src/main/scala/org/scata/algorithm/CBAA.scala b/src/main/scala/org/scata/algorithm/CBAA.scala index b11eb46..cd2b02b 100644 --- a/src/main/scala/org/scata/algorithm/CBAA.scala +++ b/src/main/scala/org/scata/algorithm/CBAA.scala @@ -1,6 +1,5 @@ // Copyright (C) Maxime MORGE, 2024 package org.scata.algorithm - import org.scata.core._ /** @@ -8,11 +7,12 @@ import org.scata.core._ * a single-assignment strategy */ class CBAA(pb : SingleAssignmentProblem) { + private val debug = true - var neighbours : Map[Worker, List[Worker]] = Map[Worker, List[Worker]]() - /* pb.workers.foreach{ worker => - neighbours = neighbours.updated(worker, pb.workers.toList) - }*/ + // Generate a fully connected communication network + private val neighbours: Map[Worker, List[Worker]] = pb.workers.toList.map { worker => + worker -> pb.workers.toList // Each worker is connected to all workers, including themselves + }.toMap private var workerTaskList: Map[Worker, Map[Task, Boolean]] = pb.workers.toList.map { worker => worker -> pb.tasks.toList.map { task => @@ -22,42 +22,31 @@ class CBAA(pb : SingleAssignmentProblem) { private var winningBid: Map[Worker, Map[Task, (Worker, Int)]] = pb.workers.toList.map { worker => worker -> pb.tasks.toList.map { task => - task -> (NoWorker, 0) + task -> (NoWorker, Int.MaxValue) }.toMap }.toMap - /** - * Generate a fully connect communication network - */ - //private def fullyConnected : Map[Worker, List[Worker]] = /** * Returns true if the worker has no task assigned */ private def isFree(worker : Worker) : Boolean = !pb.tasks.exists(task => workerTaskList(worker)(task)) - /** - * Checks if two workers are neighbors - * @param worker1 the first worker - * @param worker2 the second worker - * @return true if they are neighbors, false otherwise - */ - private 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 = { + private def selectTask(i : Int) : Unit = { val worker = pb.workers.toIndexedSeq(i) // Check if the worker has no tasks assigned if (isFree(worker)) { - + if (debug) println(s"Worker ${worker.name} is free") // 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)._2) { + if (debug) println(s"Worker ${worker.name} can perform task ${task.name}") validTasks = validTasks.updated(task, true) } else { validTasks = validTasks.updated(task, false) @@ -66,26 +55,71 @@ class CBAA(pb : SingleAssignmentProblem) { // 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(worker) = workerTaskList(worker).updated(bestTask, true) - winningBid(worker) = winningBid(worker).updated(bestTask, (worker, pb.cost(worker, bestTask))) + if (debug) println(s"Worker ${worker.name} selects task ${bestTask.name} with cost ${pb.cost(worker, bestTask)}") + workerTaskList = workerTaskList.updated(worker, workerTaskList(worker).updated(bestTask, true)) + winningBid = winningBid.updated(worker, winningBid(worker).updated(bestTask, (worker, pb.cost(worker, bestTask)))) } } } + /** - * TODO CBAA Phase 1 for worker i + * CBAA Phase 2 for worker i + * Updates the winning bids of the worker based on the minimum winning bid + * among its neighbors. + * Returns true if the worker's winning bid is updated * @param i index of the worker in the workers set */ - def consensus(i: Int): Unit = { + private def consensus(i: Int): Boolean = { + var isWinningBidUpdated = false 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) + pb.tasks.foreach { task : Task => + // Find the minimum winning bid among the worker's neighbors (including itself) + val minWinningBid = neighbours(worker) + .map(neighbor => winningBid(neighbor)(task)) + .minBy(_._2) + println(s"Worker ${worker.name} minimum winning bid for task ${task.name} is ${minWinningBid}") + // Update the worker's winning bid for the task if a lower bid is found + if (minWinningBid._2 < winningBid(worker)(task)._2) { + println(s"Worker ${worker.name} updates winning bid for task ${task.name} to ${minWinningBid}") + isWinningBidUpdated = true + winningBid = winningBid.updated(worker, winningBid(worker).updated(task, minWinningBid)) + // Update the worker's task list to reflect the new winning bid + workerTaskList = workerTaskList.updated(worker, workerTaskList(worker).updated(task, minWinningBid._1 == worker)) + } + } + isWinningBidUpdated + } + + /** + * Solves the single-assignment problem using the CBAA algorithm + * @return the final assignment + */ + def solve() : SingleAssignment = { + var hasConverged = false + while (!hasConverged) { + if (debug) println("CBAA Phase 1") + for (i <- 0 until pb.m) { + selectTask(i) + } + hasConverged = true + if (debug) println("CBAA Phase 2") + for (i <- 0 until pb.m) { + if (consensus(i)) { + hasConverged = false + } + if (debug) println(s"CBAA Phase 2 has converged: ${hasConverged}") + } + } + // Generate the final assignment + val assignment = new SingleAssignment(pb) + pb.workers.foreach { worker => + pb.tasks.foreach { task => + if (workerTaskList(worker)(task)) { + assignment.bundle = assignment.bundle.updated(worker, task) + } + } } + return assignment } } diff --git a/src/main/scala/org/scata/core/SingleAssignment.scala b/src/main/scala/org/scata/core/SingleAssignment.scala index c6452f9..4133822 100644 --- a/src/main/scala/org/scata/core/SingleAssignment.scala +++ b/src/main/scala/org/scata/core/SingleAssignment.scala @@ -17,7 +17,7 @@ class SingleAssignment(val pb: SingleAssignmentProblem) { } override def toString: String = - pb.workers.toList.map(worker => s"$worker: $bundle").mkString("\n") + pb.workers.toList.map(worker => s"$worker: ${bundle(worker)}").mkString("\n") override def equals(that: Any): Boolean = that match { diff --git a/src/main/scala/org/scata/core/SingleAssignmentProblem.scala b/src/main/scala/org/scata/core/SingleAssignmentProblem.scala index c81de55..3da60cc 100644 --- a/src/main/scala/org/scata/core/SingleAssignmentProblem.scala +++ b/src/main/scala/org/scata/core/SingleAssignmentProblem.scala @@ -18,10 +18,19 @@ class SingleAssignmentProblem(val workers: SortedSet[Worker], * Returns a string describing the MASTAPlus problem */ override def toString: String = { - s"m: ${workers.size}\n" + - s"n: ${tasks.size}\n" + - "workers: " + workers.mkString(", ") + "\n" + - "tasks: " + tasks.mkString(", ") + "\n" + val workersStr = workers.map(_.name).mkString(", ") + val tasksStr = tasks.map(_.name).mkString(", ") + + val costStr = workers.map { worker => + val taskCosts = tasks.map { task => + s"${task.name}: ${cost((worker, task))}" + }.mkString(", ") + s"${worker.name} -> [$taskCosts]" + }.mkString("\n") + + s"Workers (${workers.size}): $workersStr\n" + + s"Tasks (${tasks.size}): $tasksStr\n" + + s"Costs:\n$costStr" } /** -- GitLab