From 4404221f7c94b2975ee81096dbe647cd473fcf66 Mon Sep 17 00:00:00 2001 From: mmorge <maxime.morge@univ-lyon1.fr> Date: Tue, 22 Apr 2025 11:18:06 +0200 Subject: [PATCH] Add MultiAssignement --- src/main/scala/Main.scala | 4 +- src/main/scala/org/scata/algorithm/CBAA.scala | 2 +- ...tProblem.scala => AssignmentProblem.scala} | 12 +-- .../org/scata/core/MultipleAssignment.scala | 82 +++++++++++++++++++ .../org/scata/core/SingleAssignment.scala | 2 +- .../scala/org/scata/patrol/Environment.scala | 12 +-- 6 files changed, 98 insertions(+), 16 deletions(-) rename src/main/scala/org/scata/core/{SingleAssignmentProblem.scala => AssignmentProblem.scala} (84%) create mode 100644 src/main/scala/org/scata/core/MultipleAssignment.scala diff --git a/src/main/scala/Main.scala b/src/main/scala/Main.scala index e3d7bf4..4363f07 100644 --- a/src/main/scala/Main.scala +++ b/src/main/scala/Main.scala @@ -1,5 +1,5 @@ // Copyright (C) Maxime MORGE 2024 -import org.scata.core.SingleAssignmentProblem +import org.scata.core.AssignmentProblem import org.scata.algorithm.CBAA import org.scata.patrol.Environment @@ -7,7 +7,7 @@ object Main { def main(args: Array[String]): Unit = { val nbRobots= 4 - val nbTargets = 3 + val nbTargets = 4 val env = Environment.randomEnvironment(nbRobots, nbTargets) println(env) val pb = env.toSAP() diff --git a/src/main/scala/org/scata/algorithm/CBAA.scala b/src/main/scala/org/scata/algorithm/CBAA.scala index 55cd064..77b9469 100644 --- a/src/main/scala/org/scata/algorithm/CBAA.scala +++ b/src/main/scala/org/scata/algorithm/CBAA.scala @@ -6,7 +6,7 @@ import org.scata.core._ * The Consensus-Based Auction Algorithm (CBAA) is * a single-assignment strategy */ -class CBAA(pb : SingleAssignmentProblem) { +class CBAA(pb : AssignmentProblem) { private val debug = true // Generate a fully connected communication network diff --git a/src/main/scala/org/scata/core/SingleAssignmentProblem.scala b/src/main/scala/org/scata/core/AssignmentProblem.scala similarity index 84% rename from src/main/scala/org/scata/core/SingleAssignmentProblem.scala rename to src/main/scala/org/scata/core/AssignmentProblem.scala index 3da60cc..ecb8b0d 100644 --- a/src/main/scala/org/scata/core/SingleAssignmentProblem.scala +++ b/src/main/scala/org/scata/core/AssignmentProblem.scala @@ -10,9 +10,9 @@ import org.scata.utils.RandomUtils * @param workers are the workers * @param tasks are the tasks */ -class SingleAssignmentProblem(val workers: SortedSet[Worker], - val tasks : SortedSet[Task], - val cost : Map[(Worker,Task), Int]) { +class AssignmentProblem(val workers: SortedSet[Worker], + val tasks : SortedSet[Task], + val cost : Map[(Worker,Task), Int]) { /** * Returns a string describing the MASTAPlus problem @@ -47,7 +47,7 @@ class SingleAssignmentProblem(val workers: SortedSet[Worker], /** * Factory for SingleAssignmentProblem */ -object SingleAssignmentProblem{ +object AssignmentProblem{ implicit val order : Ordering[Double] = Ordering.Double.TotalOrdering // eventually Ordering.Double.IeeeOrdering @@ -59,7 +59,7 @@ object SingleAssignmentProblem{ * @param m nodes * @param n tasks */ - def randomProblem(m: Int, n: Int): SingleAssignmentProblem = { + def randomProblem(m: Int, n: Int): AssignmentProblem = { // Workers val workers: SortedSet[Worker] = collection.immutable.SortedSet[Worker]() ++ (for (i <- 1 to m) yield new Worker(name = "w%02d".format(i))) @@ -75,6 +75,6 @@ object SingleAssignmentProblem{ cost = cost.updated((worker, task), RandomUtils.random(1, MAX_COST)) } } - new SingleAssignmentProblem(workers, tasks, cost) + new AssignmentProblem(workers, tasks, cost) } } diff --git a/src/main/scala/org/scata/core/MultipleAssignment.scala b/src/main/scala/org/scata/core/MultipleAssignment.scala new file mode 100644 index 0000000..b267451 --- /dev/null +++ b/src/main/scala/org/scata/core/MultipleAssignment.scala @@ -0,0 +1,82 @@ +// Copyright (C) Maxime MORGE, 2024 +package org.scata.core + +import scala.math.Ordering.Implicits.seqOrdering + +/** + * Class representing an allocation as + * a multi-assignment of tasks to workers. + * + * @param pb is a multiple-assignment instance + */ +class MultipleAssignment(val pb: AssignmentProblem) { + + var bundle: Map[Worker, List[Task]] = pb.workers.map(w => w -> List.empty[Task]).toMap + + override def toString: String = + pb.workers.toList.map(worker => s"$worker: ${bundle(worker).mkString(", ")}").mkString("\n") + + override def equals(that: Any): Boolean = + that match { + case that: MultipleAssignment => that.canEqual(this) && this.bundle == that.bundle + case _ => false + } + + override def hashCode(): Int = this.bundle.hashCode() + + def canEqual(a: Any): Boolean = a.isInstanceOf[MultipleAssignment] + + /** + * Returns a deep copy + */ + @throws(classOf[RuntimeException]) + private def copy(): MultipleAssignment = { + val assignment = new MultipleAssignment(pb) + this.bundle.foreach { + case (worker: Worker, tasks: List[Task]) => + assignment.bundle = assignment.bundle.updated(worker, tasks) + case _ => throw new RuntimeException("Not able to copy bundle") + } + assignment + } + + /** + * Adds a task to the list assigned to a worker + */ + def update(worker: Worker, task: Task): MultipleAssignment = { + val allocation = this.copy() + val updatedTasks = allocation.bundle(worker) :+ task + allocation.bundle = allocation.bundle.updated(worker, updatedTasks) + allocation + } + + /** + * Returns true if each task is assigned to no more than one worker + */ + private def isPartition: Boolean = { + val allTasks = bundle.values.flatten.toList + allTasks.distinct.size == allTasks.size + } + + /** + * Returns true if the assignment is complete, i.e., every task is assigned to a worker + */ + private def isComplete: Boolean = { + val assignedTasks = bundle.values.flatten.toSet + pb.tasks.forall(assignedTasks.contains) + } + + /** + * Returns true if the allocation is sound, i.e., a complete partition of the tasks + */ + def isSound: Boolean = isPartition && isComplete + + /** + * Returns the worker which has the task in its bundle + */ + def worker(task: Task): Option[Worker] = { + bundle.find { + case (_, tasks) => tasks.contains(task) + }.map(_._1) + } +} \ No newline at end of file diff --git a/src/main/scala/org/scata/core/SingleAssignment.scala b/src/main/scala/org/scata/core/SingleAssignment.scala index 4133822..d6eedaa 100644 --- a/src/main/scala/org/scata/core/SingleAssignment.scala +++ b/src/main/scala/org/scata/core/SingleAssignment.scala @@ -8,7 +8,7 @@ import scala.collection.SortedSet * a single-assignment of the tasks to some workers. * @param pb is a single-assignment instance */ -class SingleAssignment(val pb: SingleAssignmentProblem) { +class SingleAssignment(val pb: AssignmentProblem) { var bundle: Map[Worker, Task] = Map[Worker, Task]() diff --git a/src/main/scala/org/scata/patrol/Environment.scala b/src/main/scala/org/scata/patrol/Environment.scala index 2efab9f..2d1fe6d 100644 --- a/src/main/scala/org/scata/patrol/Environment.scala +++ b/src/main/scala/org/scata/patrol/Environment.scala @@ -1,8 +1,8 @@ // Copyright (C) Maxime MORGE, 2024 package org.scata.patrol -import org.scata.core.{SingleAssignmentProblem, Task, Worker} -import org.scata.core.SingleAssignmentProblem.MAX_COST +import org.scata.core.{AssignmentProblem, Task, Worker} +import org.scata.core.AssignmentProblem.MAX_COST import scala.collection.SortedSet import org.scata.utils.RandomUtils @@ -39,7 +39,7 @@ class Environment(val robots: SortedSet[Robot], /** * Generate a single assignment problem */ - def toSAP(): SingleAssignmentProblem = { + def toSAP(): AssignmentProblem = { if (n > m) throw new RuntimeException("Cannot generate a single assignment problem since there are more targets than robots") @@ -62,7 +62,7 @@ class Environment(val robots: SortedSet[Robot], ((worker, task), distance.toInt) }).toMap - new SingleAssignmentProblem(workers, tasks, cost) + new AssignmentProblem(workers, tasks, cost) } } @@ -74,8 +74,8 @@ object Environment{ implicit val order : Ordering[Double] = Ordering.Double.TotalOrdering // eventually Ordering.Double.IeeeOrdering - private val MAX_X : Int = 10 // Maximum width - private val MAX_Y : Int = 10 // Maximum height + private val MAX_X : Int = 20 // Maximum width + private val MAX_Y : Int = 20 // Maximum height /** * Returns a random single-assignment problem instance with -- GitLab