From ac0d87b812df2f3507d4704b38a57dc155209839 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Anne-C=C3=A9cile=20Caron?= <anne-cecile.caron@univ-lille.fr>
Date: Fri, 8 Dec 2023 16:55:14 +0100
Subject: [PATCH] algo VDS for reallocation

---
 .../balancer/local/VDSBalancer.scala          | 237 ++++++++++++++++++
 .../org/smastaplus/core/Allocation.scala      |   2 +
 .../core/AllocationComparison.scala           |  25 ++
 3 files changed, 264 insertions(+)
 create mode 100644 src/main/scala/org/smastaplus/balancer/local/VDSBalancer.scala
 create mode 100644 src/test/scala/org/smastaplus/core/AllocationComparison.scala

diff --git a/src/main/scala/org/smastaplus/balancer/local/VDSBalancer.scala b/src/main/scala/org/smastaplus/balancer/local/VDSBalancer.scala
new file mode 100644
index 00000000..d0ccba28
--- /dev/null
+++ b/src/main/scala/org/smastaplus/balancer/local/VDSBalancer.scala
@@ -0,0 +1,237 @@
+package org.smastaplus.balancer.local
+
+import org.smastaplus.core.{Allocation, GlobalFlowtime, LocalFlowtime, Makespan, STAP, SocialRule}
+import org.smastaplus.process.{Deal, SingleDelegation, SingleSwap}
+
+/**
+ *
+ * @param stap instance to tackle
+ * @param rule is the social rule to be applied
+ * @param name of the balancer
+ * @param withSwap is true if swap are allowed
+ *
+ *  Inspired by Algo VDS in MIC97 "A variable depth search algorithm for the generalized assignment problem"
+ *  Authors : Mutsunori Yagiura, Takashi Yamaguchi and Toshihide Ibaraki
+ *
+ *  best feasible solution is estimated with Global Flowtime
+ *  cost(alloc) = local flowtime, so needs to know the 2 contractors
+ *  pcost(alloc) = cost(alloc) + alpha * global flowtime
+ *
+ */
+class VDSBalancer (stap: STAP, rule: SocialRule = GlobalFlowtime, name: String = "VDSBalancer",
+                   withSwap: Boolean = false, alpha:Double = 0.5)
+  extends LocalBalancer(stap,rule, name, withSwap) {
+
+  var newBestAlloc: Boolean = false
+  var bestAllocForCost : Allocation = null
+  var bestCost : Double = Double.MaxValue
+
+  /**
+   *
+   * compute the cost of an allocation, i.e. the local flowtime between two contractors of a deal
+   */
+  def cost(allocation: Allocation, deal: Deal): Double = {
+    val node1 = deal.contractors(0)
+    val node2 = deal.contractors(1)
+    // local flowtime between node1 and node2
+    allocation.jobs.foldLeft(0.0) { (sum, job) => sum + Math.max(allocation.completionTime(job, node1), allocation.completionTime(job, node2)) }
+  }
+
+  /**
+   *
+   * compute the "penalized" cost of an allocation, i.e. the local flowtime between two contractors of a deal, penalized by global flowtime
+   */
+  def pcost(allocation: Allocation, deal: Deal): Double =
+    cost(allocation, deal) + alpha * allocation.globalFlowtime
+
+  /**
+   *
+   * best feasible allocation between a1 and a2, estimated with Global Flowtime
+   */
+  def bestBetween(a1 : Allocation, a2 : Allocation) : Allocation =
+    if (a1.globalFlowtime <= a2.globalFlowtime) a1 else a2
+
+  /**
+   * returns a neighbor better than the current allocation (the first encountered)
+   */
+  def firstBetterNeighbor(current: Allocation, mycost: (Allocation, Deal) => Double, withSwap: Boolean, alreadyVisited : Set[Allocation]): Option[(Allocation, Deal)] = {
+    newBestAlloc = false
+    stap.ds.computingNodes.foreach { initiator => // Foreach initiator
+      current.bundle(initiator).foreach { task => // Foreach task in its bundle
+        (stap.ds.computingNodes diff Set(initiator)).foreach { responder => // Foreach responder
+          if (withSwap) { // Let us consider the swaps from this delegation
+            current.bundle(responder).foreach { counterpart => // Foreach counterpart
+              val swap = new SingleSwap(stap, initiator, responder, task, counterpart)
+              val nextAllocation = swap.execute(current)
+              val currentValue = mycost(current, swap)
+              val nextValue = mycost(nextAllocation, swap)
+              if (nextValue < currentValue && !alreadyVisited.contains(nextAllocation)) {
+                bestAllocForCost = bestBetween(current, nextAllocation)
+                bestCost = bestAllocForCost.globalFlowtime
+                if (bestCost > current.globalFlowtime) newBestAlloc = true else newBestAlloc = false
+                return Some((nextAllocation, swap))
+              }
+            }
+          } else { // gift, not swap
+            val gift = new SingleDelegation(stap, initiator, responder, task)
+            val nextAllocation = gift.execute(current)
+            val nextValue = mycost(nextAllocation, gift)
+            val currentValue = mycost(current, gift)
+            if (nextValue < currentValue && !alreadyVisited.contains(nextAllocation)) {
+              bestAllocForCost = bestBetween(current, nextAllocation)
+              bestCost = bestAllocForCost.globalFlowtime
+              if (bestCost > current.globalFlowtime) newBestAlloc = true else newBestAlloc = false
+              return Some((nextAllocation, gift))
+            }
+          }
+
+        }
+      }
+    }
+    None
+  }
+
+  /**
+   * returns the best neighbor of the current allocation w.r.t. mycost
+   */
+  def bestNeighbor(current: Allocation, mycost: (Allocation, Deal) => Double, withSwap: Boolean, alreadyVisited : Set[Allocation]): Option[(Allocation, Deal)] = {
+    newBestAlloc = false
+    var bestAllocation : Allocation = null
+    var bestValue : Double = Double.MaxValue
+    var bestDeal : Deal = null
+    stap.ds.computingNodes.foreach { initiator => // Foreach initiator
+      current.bundle(initiator).foreach { task => // Foreach task in its bundle
+        (stap.ds.computingNodes diff Set(initiator)).foreach { responder => // Foreach responder
+          if (withSwap) { // Let us consider the swaps from this delegation
+            current.bundle(responder).foreach { counterpart => // Foreach counterpart
+              val swap = new SingleSwap(stap, initiator, responder, task, counterpart)
+              val nextAllocation = swap.execute(current)
+              val currentValue = mycost(current, swap)
+              val nextValue = mycost(nextAllocation, swap)
+              if (nextValue < currentValue && !alreadyVisited.contains(nextAllocation)) {
+                bestAllocation = nextAllocation
+                bestValue = nextValue
+                bestDeal = swap
+              }
+            }
+          } else { // gift, not swap
+            val gift = new SingleDelegation(stap, initiator, responder, task)
+            val nextAllocation = gift.execute(current)
+            val nextValue = mycost(nextAllocation, gift)
+            val currentValue = mycost(current, gift)
+            if (nextValue < currentValue && !alreadyVisited.contains(nextAllocation)) {
+              bestAllocation = nextAllocation
+              bestValue = nextValue
+              bestDeal = gift
+            }
+          }
+        }
+      }
+    }
+    if (bestAllocation == null) None
+    else {
+      bestAllocForCost = bestBetween(current, bestAllocation)
+      bestCost = bestAllocForCost.globalFlowtime
+      if (bestCost > current.globalFlowtime) newBestAlloc = true else newBestAlloc = false
+      Some((bestAllocation, bestDeal))
+    }
+  }
+
+  /**
+   * returns a better allocation, found iterating firstBetterNeighbor
+   * Use reflexive transitive closure of neighborhood, i.e. If no better allocation is found in neighborhood, returns the allocation itself.
+   */
+  def LS(allocation: Allocation, mycost: (Allocation, Deal) => Double, withSwap: Boolean, alreadyVisited : Set[Allocation]): (Allocation, Set[Allocation]) = {
+    //super.init(allocation)
+    val candidate = firstBetterNeighbor(allocation, mycost, withSwap, alreadyVisited)
+    candidate match {
+      case Some((alloc, _)) =>
+        if (alloc.equals(allocation)) {
+          (allocation, alreadyVisited)
+        } else {
+          LS(alloc, mycost, withSwap, alreadyVisited+alloc)
+        }
+      case None => (allocation, alreadyVisited)
+    }
+  }
+
+  def step2b(allocation: Allocation, alreadyVisited : Set[Allocation]): (Allocation, Set[Allocation]) = {
+    val result1 = bestNeighbor(allocation, pcost, true, alreadyVisited)
+    result1 match{
+      case Some((allocbis, _)) => (allocbis, alreadyVisited+allocbis)
+      case None => (allocation, alreadyVisited)
+    }
+  }
+
+  def variable_depth_search_step2(allocation: Allocation, alreadyVisited : Set[Allocation]): (Allocation, Set[Allocation]) ={
+    // step 2a = a shift move
+    val step2a = bestNeighbor(allocation, pcost, false, alreadyVisited)
+    step2a match {
+      case Some((alloc, _)) => // step2b = sequence of swap moves
+        if (newBestAlloc) //exit to step3
+          variable_depth_search_step2(bestAllocForCost, alreadyVisited+bestAllocForCost+alloc)
+        else {
+          var result1 = step2b(allocation, alreadyVisited+alloc)
+          //val nextstep = step2b(alloc, alreadyVisited+alloc)
+          result1 match {
+            case (allocbis, visited) =>
+              if (newBestAlloc) //step3
+                variable_depth_search_step2(bestAllocForCost, visited + bestAllocForCost)
+              else if (allocbis.equals(alloc) ) // return to step2a (i.e. try shif)
+                variable_depth_search_step2(allocbis, visited)
+              else step2b(allocbis, visited+allocbis)
+          }
+        }
+      case None => (bestAllocForCost, alreadyVisited) // exit to step 3 ?
+    }
+  }
+
+  /**
+   * Modify the current allocation
+   * iterate step 2/step 3 in VDS (no iteration with new initial allocation)
+   */
+  override def reallocate(allocation: Allocation): Allocation = {
+    val current = scheduler.schedule(allocation)
+    val result_vds = variable_depth_search_step2(current, Set[Allocation]()+current)
+    result_vds._1
+  }
+
+  /**
+   * step 1 : Generation of an initial solution
+   *
+   * @param allocation
+   * @return
+   */
+  override def init(allocation: Allocation): Allocation = {
+    val initialAllocation = super.init(allocation)
+    bestCost = initialAllocation.globalFlowtime
+    bestAllocForCost = initialAllocation
+    // LS modifies bestGF and bestAlloc :
+    val allocbisWithPath = LS(initialAllocation, pcost, false, Set[Allocation]()+initialAllocation)
+    LS(allocbisWithPath._1, pcost, true,allocbisWithPath._2)
+    bestAllocForCost
+  }
+}
+
+  /**
+   * companion object, for testing
+   */
+  object VDSBalancer extends App {
+    val debug = false
+
+    import org.smastaplus.example.stap.ex3.stap
+
+    println(stap)
+    val balancer = new VDSBalancer(stap)
+    // run = build a first allocation with init(), and then reallocate a
+    val allocation = balancer.run()
+    allocation match {
+      case Some(a) => {
+        println("after VDS : " )
+        println(a)
+        println("global flowtime : "+a.globalFlowtime)
+      }
+      case None => println("no allocation")
+    }
+  }
+
diff --git a/src/main/scala/org/smastaplus/core/Allocation.scala b/src/main/scala/org/smastaplus/core/Allocation.scala
index 9c89aa41..3124c38b 100644
--- a/src/main/scala/org/smastaplus/core/Allocation.scala
+++ b/src/main/scala/org/smastaplus/core/Allocation.scala
@@ -38,6 +38,8 @@ class Allocation(val stap: STAP,
       case _ => false
     }
 
+  override def hashCode(): Int = this.bundle.hashCode()
+
   def canEqual(a: Any): Boolean = a.isInstanceOf[Allocation]
 
   /**
diff --git a/src/test/scala/org/smastaplus/core/AllocationComparison.scala b/src/test/scala/org/smastaplus/core/AllocationComparison.scala
new file mode 100644
index 00000000..c4ca75cb
--- /dev/null
+++ b/src/test/scala/org/smastaplus/core/AllocationComparison.scala
@@ -0,0 +1,25 @@
+package org.smastaplus.core
+
+import org.scalatest.flatspec.AnyFlatSpec
+import org.smastaplus.example.allocation.stap1.ex1Allocation
+
+class AllocationComparison extends AnyFlatSpec {
+
+  "Two identical maps" should "be equal" in {
+    val m1: Map[String,Int] = Map[String,Int]("n1"->5,"n2"->3)
+    val m2: Map[String,Int] = Map[String,Int]("n2"->3,"n1"->5)
+    println(m1)
+    println(m2)
+    assert(m1.equals(m2))
+    assert(m1==m2)
+  }
+
+  "Two identical allocations" should "be equal" in {
+    val a1 : Allocation = ex1Allocation.a
+    val a2 : Allocation = a1.copy()
+    println(a1)
+    println(a2)
+    assert(a1.equals(a2))
+    assert(a1==a2)
+  }
+}
-- 
GitLab