diff --git a/src/main/scala/Main.scala b/src/main/scala/Main.scala index e3d7bf4bafbfa7c06643abccce14831e34291a0c..4363f07c19ef3f7a50c2a72c76d7faf06d52a962 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 55cd064688704df8e07e3420a8ccfc1817e21e24..77b94690a5295bf990f22ced83d21b9ac9951ab3 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 3da60cc9be69427c3a2fe4987652b3199f929f0d..ecb8b0d9c63af721cefa1a1d14a081390648c2ce 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 0000000000000000000000000000000000000000..b267451ed8e53eb0d9f27018073bb42171406b8f --- /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 4133822b67793f214f8fca9146bf5d4694f7c271..d6eedaa631cabbfafcc8408e7de4ccc679773519 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 2efab9f5ba19cc4aa5ecbf5f9c737795fcfdc49d..2d1fe6d285e0e92e67a237a321b1c5c7ebb66916 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