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