From d088dc44850dfe389bb9ec771ce8fe32f7ed4194 Mon Sep 17 00:00:00 2001 From: mmorge <maxime.morge@univ-lyon1.fr> Date: Tue, 22 Apr 2025 10:52:37 +0200 Subject: [PATCH] Add Environment, Robot and Target --- src/main/scala/Main.scala | 10 +- src/main/scala/org/scata/algorithm/CBAA.scala | 4 +- src/main/scala/org/scata/patrol/Entity.scala | 22 ++++ .../scala/org/scata/patrol/Environment.scala | 102 ++++++++++++++++++ src/main/scala/org/scata/patrol/Robot.scala | 6 ++ src/main/scala/org/scata/patrol/Target.scala | 6 ++ 6 files changed, 145 insertions(+), 5 deletions(-) create mode 100644 src/main/scala/org/scata/patrol/Entity.scala create mode 100644 src/main/scala/org/scata/patrol/Environment.scala create mode 100644 src/main/scala/org/scata/patrol/Robot.scala create mode 100644 src/main/scala/org/scata/patrol/Target.scala diff --git a/src/main/scala/Main.scala b/src/main/scala/Main.scala index ddb5e0f..e3d7bf4 100644 --- a/src/main/scala/Main.scala +++ b/src/main/scala/Main.scala @@ -1,13 +1,17 @@ // Copyright (C) Maxime MORGE 2024 import org.scata.core.SingleAssignmentProblem import org.scata.algorithm.CBAA +import org.scata.patrol.Environment object Main { def main(args: Array[String]): Unit = { - val nbWorkers = 4 - val nbTasks = 3 - val pb = SingleAssignmentProblem.randomProblem(nbWorkers, nbTasks) + val nbRobots= 4 + val nbTargets = 3 + val env = Environment.randomEnvironment(nbRobots, nbTargets) + println(env) + val pb = env.toSAP() + println(pb) val cbaa = new CBAA(pb) println(pb) val solution = cbaa.solve() diff --git a/src/main/scala/org/scata/algorithm/CBAA.scala b/src/main/scala/org/scata/algorithm/CBAA.scala index cd2b02b..55cd064 100644 --- a/src/main/scala/org/scata/algorithm/CBAA.scala +++ b/src/main/scala/org/scata/algorithm/CBAA.scala @@ -54,7 +54,7 @@ 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)) + val bestTask = validTasks.filter(_._2).keys.minBy(task => (pb.cost(worker, task), task.name)) 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)))) @@ -77,7 +77,7 @@ class CBAA(pb : SingleAssignmentProblem) { // Find the minimum winning bid among the worker's neighbors (including itself) val minWinningBid = neighbours(worker) .map(neighbor => winningBid(neighbor)(task)) - .minBy(_._2) + .minBy { case (winningWorker, cost) => (cost, winningWorker.name) } 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) { diff --git a/src/main/scala/org/scata/patrol/Entity.scala b/src/main/scala/org/scata/patrol/Entity.scala new file mode 100644 index 0000000..e1a55b9 --- /dev/null +++ b/src/main/scala/org/scata/patrol/Entity.scala @@ -0,0 +1,22 @@ +// Copyright (C) Maxime MORGE, 2024 +package org.scata.patrol + +abstract class Entity(val name : String, val x : Int, val y: Int) extends Ordered[Entity]{ + + override def equals(that: Any): Boolean = + that match { + case that: Entity => that.canEqual(this) && this.name == that.name + case _ => false + } + private def canEqual(a: Any) : Boolean = a.isInstanceOf[Entity] + + /** + * Returns 0 if this an that are the same, negative if this < that, and positive otherwise + * Entitys are sorted with their name + */ + def compare(that: Entity) : Int = { + if (this.name == that.name) return 0 + else if (this.name > that.name) return 1 + -1 + } +} diff --git a/src/main/scala/org/scata/patrol/Environment.scala b/src/main/scala/org/scata/patrol/Environment.scala new file mode 100644 index 0000000..2efab9f --- /dev/null +++ b/src/main/scala/org/scata/patrol/Environment.scala @@ -0,0 +1,102 @@ +// Copyright (C) Maxime MORGE, 2024 +package org.scata.patrol + +import org.scata.core.{SingleAssignmentProblem, Task, Worker} +import org.scata.core.SingleAssignmentProblem.MAX_COST + +import scala.collection.SortedSet +import org.scata.utils.RandomUtils + +/** + * Class representing an environment + * @param robots are the robots + * @param targets art the targets + */ +class Environment(val robots: SortedSet[Robot], + val targets: SortedSet[Target]) { + + /** + * Returns a string describing the MASTAPlus problem + */ + override def toString: String = { + val robotsStr = robots.map(_.toString).mkString(", ") + val targetStr = targets.map(_.toString).mkString(", ") + + s"Robots (${robots.size}): $robotsStr\n" + + s"Tasks (${targets.size}): $targetStr" + } + + /** + * Returns the number of robots + */ + def m: Int = robots.size + + /** + * Returns the number of targets + */ + def n: Int = targets.size + + /** + * Generate a single assignment problem + */ + def toSAP(): SingleAssignmentProblem = { + if (n > m) + throw new RuntimeException("Cannot generate a single assignment problem since there are more targets than robots") + + // Convert robots and targets into Workers and Tasks respectively + val workers: SortedSet[Worker] = robots.map(r => new Worker(r.name)) + val tasks: SortedSet[Task] = targets.map(t => new Task(t.name)) + + // Create mapping from names to positions for cost calculation + val robotMap = robots.map(r => r.name -> (r.x, r.y)).toMap + val targetMap = targets.map(t => t.name -> (t.x, t.y)).toMap + + // Compute cost using Euclidean distance + val cost: Map[(Worker, Task), Int] = (for { + worker <- workers + task <- tasks + } yield { + val (rx, ry) = robotMap(worker.name) + val (tx, ty) = targetMap(task.name) + val distance = math.abs(rx - tx) + math.abs(ry - ty) + ((worker, task), distance.toInt) + }).toMap + + new SingleAssignmentProblem(workers, tasks, cost) + } +} + +/** + * Factory for SingleAssignmentProblem + */ +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 + + /** + * Returns a random single-assignment problem instance with + * @param m robots + * @param n tasks + */ + def randomEnvironment(m: Int, n: Int): Environment = { + // Robots + val robots: SortedSet[Robot] = collection.immutable.SortedSet[Robot]() ++ + (for (i <- 1 to m) yield new Robot(name = "r%02d".format(i), + RandomUtils.random(1, MAX_X), + RandomUtils.random(1, MAX_Y) + )) + + // Targets + val targets: SortedSet[Target] = collection.immutable.SortedSet[Target]() ++ + (for (i <- 1 to n) yield new Target(name = "t%02d".format(i), + RandomUtils.random(0, MAX_X-1), + RandomUtils.random(0, MAX_Y-1) + )) + + new Environment(robots, targets) + } +} diff --git a/src/main/scala/org/scata/patrol/Robot.scala b/src/main/scala/org/scata/patrol/Robot.scala new file mode 100644 index 0000000..fb9d887 --- /dev/null +++ b/src/main/scala/org/scata/patrol/Robot.scala @@ -0,0 +1,6 @@ +// Copyright (C) Maxime MORGE, 2024 +package org.scata.patrol + +class Robot(name: String, x: Int, y: Int) extends Entity(name, x, y) { + override def toString: String = s"Robot($name, $x, $y)" +} \ No newline at end of file diff --git a/src/main/scala/org/scata/patrol/Target.scala b/src/main/scala/org/scata/patrol/Target.scala new file mode 100644 index 0000000..55f3e6d --- /dev/null +++ b/src/main/scala/org/scata/patrol/Target.scala @@ -0,0 +1,6 @@ +// Copyright (C) Maxime MORGE, 2024 +package org.scata.patrol + +class Target(name: String, x: Int, y: Int) extends Entity(name, x, y) { + override def toString: String = s"Target($name, $x, $y)" +} -- GitLab