Skip to content
Snippets Groups Projects
Commit 1e8cffd4 authored by Maxime MORGE's avatar Maxime MORGE
Browse files

SingleAssignmentProblem

parent b401d525
No related branches found
No related tags found
No related merge requests found
// Copyright (C) Maxime MORGE, 2024
package org.scata.core
import scala.collection.SortedSet
/**
* Class representing an allocation as
* a single-assignment of the tasks to some workers.
* @param pb is a single-assignment instance
*/
class SingleAssignment(val pb: SingleAssignmentProblem) {
var bundle: Map[Worker, Task] = Map[Worker, Task]()
pb.workers.foreach{
worker=> bundle += worker -> NoTask
}
override def toString: String =
pb.workers.toList.map(worker => s"$worker: $bundle").mkString("\n")
override def equals(that: Any): Boolean =
that match {
case that: SingleAssignment => that.canEqual(this) && this.bundle == that.bundle
case _ => false
}
override def hashCode(): Int = this.bundle.hashCode()
def canEqual(a: Any): Boolean = a.isInstanceOf[SingleAssignment]
/**
* Returns a copy
*/
@throws(classOf[RuntimeException])
private def copy(): SingleAssignment = {
val assignment = new SingleAssignment(pb)
this.bundle.foreach {
case (worker: Worker, task: Task) =>
assignment.bundle = assignment.bundle.updated(worker, task)
case _ => throw new RuntimeException("Not able to copy bundle")
}
assignment
}
/**
* Updates an assignment with a new bundle for a computing node
*/
def update(worker: Worker, task: Task): SingleAssignment = {
val allocation = this.copy()
allocation.bundle = allocation.bundle.updated(worker, task)
allocation
}
/**
* Returns true if each task is assigned to no more than one worker
*/
private def isPartition: Boolean = {
val tasks = bundle.values.toList
tasks.distinct.size == tasks.size
}
/**
* Returns true if the assignment is complete, i.e., every task is assigned to a worker
*/
private def isComplete: Boolean = {
pb.tasks.forall(task => bundle.values.toSet.contains(task))
}
/**
* 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 (_, t) if task.equals(t) => true
case _ => false
}.map(_._1)
}
}
\ No newline at end of file
// Copyright (C) Maxime MORGE, 2024
package org.scata.core
import scala.collection.SortedSet
import org.scata.utils.RandomUtils
/**
* Class representing a Single Assignment Problem
*
* @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),Double]) {
/**
* 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"
}
/**
* Returns the number of computing nodes
*/
def m: Int = workers.size
/**
* Returns the number of tasks
*/
def n: Int = tasks.size
}
/**
* Factory for SingleAssignmentProblem
*/
object SingleAssignmentProblem{
implicit val order : Ordering[Double] = Ordering.Double.TotalOrdering
// eventually Ordering.Double.IeeeOrdering
private val MAX_COST = 100 // Maximum task cost
/**
* Returns a random single-assignment problem instance with
* @param m nodes
* @param n tasks
*/
def randomProblem(m: Int, n: Int): SingleAssignmentProblem = {
// Workers
val workers: SortedSet[Worker] = collection.immutable.SortedSet[Worker]() ++
(for (i <- 1 to m) yield new Worker(name = "w%02d".format(i)))
// Tasks
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]()
// Adjust the resource sizes
workers.foreach{ worker =>
tasks.foreach{ task =>
cost = cost.updated((worker, task), RandomUtils.random(1, MAX_COST))
}
}
new SingleAssignmentProblem(workers, tasks, cost)
}
}
// Copyright (C) Maxime MORGE 2024
package org.scata.core
/**
* Class representing a task
* @param name of the task
*/
class Task(val name : String) extends Ordered[Task]{
override def toString: String = name
/**
* Returns a full description of the task
*/
def describe: String = s"$name: "
override def equals(that: Any): Boolean =
that match {
case that: Task => that.canEqual(this) && this.name == that.name
case _ => false
}
private def canEqual(a: Any): Boolean = a.isInstanceOf[Task]
/**
* Returns 0 if this and that are the same, negative if this < that, and positive otherwise
* Tasks are sorted with their name
*/
def compare(that: Task) : Int = {
if (this.name == that.name) return 0
else if (this.name > that.name) return 1
-1
}
}
/**
* The default task
*/
object NoTask extends Task("NoTask"){
override def toString: String = "θ"
}
// Copyright (C) Maxime MORGE 2024
package org.scata.core
/**
* Class representing a worker
* @param name of the worker
*/
final class Worker(val name : String) extends Ordered[Worker]{
override def toString: String = name
override def equals(that: Any): Boolean =
that match {
case that: Worker => that.canEqual(this) && this.name == that.name
case _ => false
}
private def canEqual(a: Any) : Boolean = a.isInstanceOf[Worker]
/**
* Returns 0 if this an that are the same, negative if this < that, and positive otherwise
* Workers are sorted with their name
*/
def compare(that: Worker) : Int = {
if (this.name == that.name) return 0
else if (this.name > that.name) return 1
-1
}
}
// Copyright (C) Maxime MORGE 2024
package org.scata.utils
import java.util.concurrent.TimeUnit
import scala.annotation.unused
import scala.collection.SortedSet
/**
* Compare floating-point numbers in Scala
*
*/
object MathUtils {
/**
* Implicit class for classical list functions
*/
implicit class Count[T](list: List[T]) {
def count(n: T): Int = list.count(_ == n)
}
/**
* Implicit class for classical mathematical functions
*/
implicit class MathUtils(x: Double) {
private val precision = 0.000001
/**
* Returns true if x and y are equals according to an implicit precision parameter
*/
def ~=(y: Double): Boolean = {
if ((x - y).abs <= precision) true else false
}
/**
* Returns true if x is greater than y according to an implicit precision parameter
*/
def ~>(y: Double): Boolean = {
if (x - y > precision) true else false
}
/**
* Returns true if y is greater than x according to an implicit precision parameter
*/
def ~<(y: Double): Boolean = {
if (y - x > precision) true else false
}
/**
* Returns true if x is greater or equal than y according to an implicit precision parameter
*/
@unused
def ~>=(y: Double): Boolean = (x~>y) || (x~=y)
/**
* Returns true if y is greater than x according to an implicit precision parameter
*/
def ~<=(y: Double): Boolean = (x~<y) || (x~=y)
}
}
/**
* Random weight in Scala
*
*/
object RandomUtils {
val r: scala.util.Random = scala.util.Random // For reproducible XP new scala.util.Random(42)
/**
* Returns true with a probability p in [0;1]
*/
def randomBoolean(p: Double): Boolean = {
if (p < 0.0 || p > 1) throw new RuntimeException(s"The probability $p must be in [0 ; 1]")
if (math.random() < p) return true
false
}
/**
* Returns a pseudo-randomly generated Double in ]0;1]
*/
def strictPositiveWeight(): Double = {
val number = r.nextDouble() // in [0.0;1.0[
1.0 - number
}
/**
* Returns the next pseudorandom, normally distributed
* double value with mean 0.0 and standard deviation 1.0
*/
@unused
def nextGaussian(): Double = {
r.nextGaussian()
}
/**
* Returns a pseudo-randomly generated Double in [-1.0;1.0[
*/
def weight(): Double = {
val number = r.nextDouble() // in [0.0;1.0[
number * 2 - 1
}
/**
* Returns a shuffle list
*/
def shuffle[T](s: List[T]): List[T] = {
r.shuffle(s)
}
/**
* Returns a shuffle list
*/
def shuffle[T](s: SortedSet[T]): List[T] = {
r.shuffle(s.toList)
}
/**
* Returns a random element in a non-empty list
*/
def random[T](s: Iterator[T]): T = {
val n = r.nextInt(s.size)
s.iterator.drop(n).next()
}
/**
* Returns a random element in a non-empty set
*/
def random[T](s: Set[T]): T = {
val n = r.nextInt(s.size)
s.iterator.drop(n).next()
}
/**
* Returns a random element in a non-empty set
*/
def random[T](s: SortedSet[T]): T = {
val n = r.nextInt(s.size)
s.iterator.drop(n).next()
}
/**
* Returns a pseudo-randomly generated Double in [min, max]
*/
@unused
def randomDouble(min: Int, max: Int): Double = {
(min + util.Random.nextInt((max - min) + 1)).toDouble
}
/**
* Returns a pseudo-randomly generated Double in [min, max]
*/
def random(min: Int, max: Int): Int = {
min + util.Random.nextInt((max - min) + 1)
}
/*
* Returns a pseudo-randomly generated subset of n elm
*/
def pick[T](s: SortedSet[T], n: Int): Set[T] = r.shuffle(s.toList).take(n).toSet
}
/**
* Matrix in Scala
*
*/
object Matrix {
/**
* Print
*
* @param matrix is an array of array
* @tparam T type of content
* @return string representation
**/
def show[T](matrix: Array[Array[T]]): String = matrix.map(_.mkString("[", ", ", "]")).mkString("\n")
/**
* Print
*
* @tparam T type of content
* @param f function
* @param L line number
* @param C column number
* @return string representation
**/
def show[T](f: (Integer, Integer) => T, L: Integer, C: Integer): String = {
(for (i <- 0 until L) yield {
(for (j <- 0 until C) yield f(i, j).toString).mkString("[", ", ", "]")
}).mkString("[\n", ",\n", "]\n")
}
}
/**
* List in Scala
*/
object MyList {
def insert[T](list: List[T], i: Int, value: T): List[T] = list match {
case head :: tail if i > 0 => head :: insert(tail, i - 1, value)
case _ => value :: list
}
}
/**
* Time in Scala
*/
object MyTime {
def show(nanoseconds: Long): String = {
s"${TimeUnit.NANOSECONDS.toHours(nanoseconds)}h " +
s"${TimeUnit.NANOSECONDS.toMinutes(nanoseconds) - TimeUnit.HOURS.toMinutes(TimeUnit.NANOSECONDS.toHours(nanoseconds))}min " +
s"${TimeUnit.NANOSECONDS.toSeconds(nanoseconds) - TimeUnit.MINUTES.toSeconds(TimeUnit.NANOSECONDS.toMinutes(nanoseconds))}sec " +
s"${TimeUnit.NANOSECONDS.toMillis(nanoseconds) - TimeUnit.SECONDS.toMillis(TimeUnit.NANOSECONDS.toSeconds(nanoseconds))}ms " +
s"${TimeUnit.NANOSECONDS.toNanos(nanoseconds) - TimeUnit.MILLISECONDS.toNanos(TimeUnit.NANOSECONDS.toMillis(nanoseconds))}ns "
}
}
/**
* Statistical tools
*/
object Stat {
/**
* Returns the mean of a random variable
*/
def mean(values: List[Double]): Double = values.sum / values.length
/**
* Returns the variance of a random variable with a Gaussian distribution (i.e. normally distributed)
*
* @param values of the random variable
*/
private def variance(values: List[Double]): Double = {
val mean: Double = Stat.mean(values)
values.map(a => math.pow(a - mean, 2)).sum / values.length
}
/**
* Returns the mean and the variance of a random variable with a Gaussian distribution (i.e. normally distributed)
*
* @param values of the random variable
*/
private def normal(values: List[Double]): (Double, Double) = (mean(values), variance(values))
/**
* Returns the statistic t for Welch's t-test
*/
@unused
def statistic(values1: List[Double], values2: List[Double]): Double = {
val (mean1, var1) = normal(values1)
val (mean2, var2) = normal(values2)
(mean1 - mean2) / math.sqrt(var1 / values1.length + var2 / values2.length)
}
/**
* Returns the first, the second (median) and the third quartile of data
*/
def quartiles(data: List[Double]): (Double, Double, Double) = {
val sortedData = data.sortWith(_ < _)
val dataSize = data.size
(sortedData(dataSize / 4), sortedData(dataSize / 2), sortedData(dataSize * 3 / 4))
}
}
\ No newline at end of file
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment