From 247966a3f66b13e908c152f0ca429f55b71318ae Mon Sep 17 00:00:00 2001
From: Julia Rossow <stu222169@mail.uni-kiel.de>
Date: Wed, 8 Dec 2021 16:41:46 +0100
Subject: [PATCH] New strategy InitialGuessSearchStrategy

---
 .../strategies/searchstrategy/BinarySearch.kt |  8 +-
 .../searchstrategy/CompositeStrategy.kt       |  3 +-
 .../strategies/searchstrategy/FullSearch.kt   |  7 +-
 .../InitialGuessSearchStrategy.kt             | 60 ++++++++++++
 .../strategies/searchstrategy/LinearSearch.kt |  8 +-
 .../searchstrategy/SearchStrategy.kt          |  2 +-
 .../InitialGuessSearchStrategyTest.kt         | 97 +++++++++++++++++++
 7 files changed, 180 insertions(+), 5 deletions(-)
 create mode 100644 theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt
 create mode 100644 theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt

diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt
index 28e8194c6..d2298fe6f 100644
--- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt
+++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/BinarySearch.kt
@@ -13,7 +13,13 @@ private val logger = KotlinLogging.logger {}
  * @param benchmarkExecutor Benchmark executor which runs the individual benchmarks.
  */
 class BinarySearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmarkExecutor) {
-    override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? {
+    override fun findSuitableResource(load: LoadDimension, resources: List<Resource>,
+                                      lastLowestResource: Resource?): Resource? {
+
+        if (lastLowestResource != null) {
+            logger.info { "Running LinearSearch with a set lastLowestResource value doesn't make sense." }
+        }
+
         val result = binarySearch(load, resources, 0, resources.size - 1)
         if (result == -1) {
             return null
diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt
index 41cc5c325..777a87c15 100644
--- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt
+++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/CompositeStrategy.kt
@@ -20,7 +20,8 @@ class CompositeStrategy(
     val restrictionStrategies: Set<RestrictionStrategy>
 ) : SearchStrategy(benchmarkExecutor) {
 
-    override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? {
+    override fun findSuitableResource(load: LoadDimension, resources: List<Resource>,
+                                      lastLowestResource: Resource?): Resource? {
         var restrictedResources = resources.toList()
         for (strategy in this.restrictionStrategies) {
             restrictedResources = restrictedResources.intersect(strategy.apply(load, resources)).toList()
diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt
index cb0dd2d8a..3a58bd29a 100644
--- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt
+++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/FullSearch.kt
@@ -17,7 +17,12 @@ private val logger = KotlinLogging.logger {}
  */
 class FullSearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmarkExecutor) {
 
-    override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? {
+    override fun findSuitableResource(load: LoadDimension, resources: List<Resource>,
+                                      lastLowestResource: Resource?): Resource? {
+        if (lastLowestResource != null) {
+            logger.info { "Running LinearSearch with a set lastLowestResource value doesn't make sense." }
+        }
+
         var minimalSuitableResources: Resource? = null
         for (res in resources) {
             logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" }
diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt
new file mode 100644
index 000000000..7efeea05d
--- /dev/null
+++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/InitialGuessSearchStrategy.kt
@@ -0,0 +1,60 @@
+package theodolite.strategies.searchstrategy
+
+import mu.KotlinLogging
+import theodolite.execution.BenchmarkExecutor
+import theodolite.util.LoadDimension
+import theodolite.util.Resource
+
+private val logger = KotlinLogging.logger {}
+
+/**
+ *  Linear-search-like implementation for determining the smallest suitable number of instances.
+ *
+ * @param benchmarkExecutor Benchmark executor which runs the individual benchmarks.
+ */
+class InitialGuessSearchStrategy(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmarkExecutor) {
+
+    override fun findSuitableResource(load: LoadDimension, resources: List<Resource>, lastLowestResource: Resource?): Resource? {
+
+        if (lastLowestResource != null) {
+            val resourcesToCheck: List<Resource>
+            val startIndex: Int = resources.indexOf(lastLowestResource)
+
+            logger.info { "Running experiment with load '${load.get()}' and resources '${lastLowestResource.get()}'" }
+
+            if (this.benchmarkExecutor.runExperiment(load, lastLowestResource)) {
+
+                resourcesToCheck = resources.subList(0, startIndex).reversed()
+                if(resourcesToCheck.isEmpty()) return lastLowestResource
+
+                var currentMin : Resource = lastLowestResource
+                for (res in resourcesToCheck) {
+
+                    logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" }
+                    if (this.benchmarkExecutor.runExperiment(load, res)) {
+                        currentMin = res
+                    }
+                }
+                return currentMin
+            }
+            else {
+                if (resources.size <= startIndex + 1) {
+                    logger.info{ "No more resources left to check." }
+                    return null
+                }
+                resourcesToCheck = resources.subList(startIndex + 1, resources.size)
+
+                for (res in resourcesToCheck) {
+
+                    logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" }
+                    if (this.benchmarkExecutor.runExperiment(load, res)) return res
+                }
+            }
+        }
+        else {
+            logger.info { "InitialGuessSearchStrategy called without lastLowestResource value, which is needed as a " +
+                    "starting point!" }
+        }
+        return null
+    }
+}
\ No newline at end of file
diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt
index 85deaf6fa..09f431e9f 100644
--- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt
+++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/LinearSearch.kt
@@ -14,12 +14,18 @@ private val logger = KotlinLogging.logger {}
  */
 class LinearSearch(benchmarkExecutor: BenchmarkExecutor) : SearchStrategy(benchmarkExecutor) {
 
-    override fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource? {
+    override fun findSuitableResource(load: LoadDimension, resources: List<Resource>,
+                                      lastLowestResource: Resource?): Resource? {
+        if (lastLowestResource != null) {
+            logger.info { "Running LinearSearch with a set lastLowestResource value doesn't make sense." }
+        }
+
         for (res in resources) {
 
             logger.info { "Running experiment with load '${load.get()}' and resources '${res.get()}'" }
             if (this.benchmarkExecutor.runExperiment(load, res)) return res
         }
+
         return null
     }
 }
diff --git a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt
index 4e304b010..b7994617b 100644
--- a/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt
+++ b/theodolite/src/main/kotlin/theodolite/strategies/searchstrategy/SearchStrategy.kt
@@ -20,5 +20,5 @@ abstract class SearchStrategy(val benchmarkExecutor: BenchmarkExecutor) {
      *
      * @return suitable resource for the specified load, or null if no suitable resource exists.
      */
-    abstract fun findSuitableResource(load: LoadDimension, resources: List<Resource>): Resource?
+    abstract fun findSuitableResource(load: LoadDimension, resources: List<Resource>, lastLowestResource: Resource? = null): Resource?
 }
diff --git a/theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt b/theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt
new file mode 100644
index 000000000..0397905d0
--- /dev/null
+++ b/theodolite/src/test/kotlin/theodolite/InitialGuessSearchStrategyTest.kt
@@ -0,0 +1,97 @@
+package theodolite
+
+import io.quarkus.test.junit.QuarkusTest
+import org.junit.jupiter.api.Assertions.assertEquals
+import org.junit.jupiter.api.Test
+import theodolite.benchmark.BenchmarkExecution
+import theodolite.strategies.searchstrategy.InitialGuessSearchStrategy
+import theodolite.util.LoadDimension
+import theodolite.util.Resource
+import theodolite.util.Results
+import mu.KotlinLogging
+
+private val logger = KotlinLogging.logger {}
+
+@QuarkusTest
+class InitialGuessSearchStrategyTest {
+
+    @Test
+    fun testEnd2EndInitialGuessSearch() {
+        val mockResults = arrayOf(
+            arrayOf(true, true, true, true, true, true, true),
+            arrayOf(false, false, true, true, true, true, true),
+            arrayOf(false, false, true, true, true, true, true),
+            arrayOf(false, false, false, true, true, true, true),
+            arrayOf(false, false, false, false, true, true, true),
+            arrayOf(false, false, false, false, false, false, true),
+            arrayOf(false, false, false, false, false, false, false)
+        )
+        val mockLoads: List<LoadDimension> = (0..6).map { number -> LoadDimension(number, emptyList()) }
+        val mockResources: List<Resource> = (0..6).map { number -> Resource(number, emptyList()) }
+        val results = Results()
+        val benchmark = TestBenchmark()
+        val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo()
+        val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, sloChecker, 0, 0, 5)
+        val strategy = InitialGuessSearchStrategy(benchmarkExecutor)
+
+        val actual: ArrayList<Resource?> = ArrayList()
+        val expected: ArrayList<Resource?> = ArrayList(listOf(0, 2, 2, 3, 4, 6).map { x -> Resource(x, emptyList()) })
+        expected.add(null)
+
+        var currentResource : Resource? = mockResources[0]
+        for (load in mockLoads) {
+            val returnVal : Resource? = strategy.findSuitableResource(load, mockResources, currentResource)
+            if(returnVal != null) {
+                logger.info { "returnVal '${returnVal.get()}'" }
+            }
+            else {
+                logger.info { "returnVal is null." }
+            }
+
+            actual.add(returnVal)
+            currentResource = returnVal
+        }
+
+        assertEquals(actual, expected)
+    }
+
+    @Test
+    fun testEnd2EndInitialGuessSearchLowerResourceDemandHigherLoad() {
+        val mockResults = arrayOf(
+            arrayOf(true, true, true, true, true, true, true),
+            arrayOf(false, false, true, true, true, true, true),
+            arrayOf(false, false, true, true, true, true, true),
+            arrayOf(false, true, true, true, true, true, true),
+            arrayOf(false, false, false, false, true, true, true),
+            arrayOf(false, false, false, false, false, false, true),
+            arrayOf(false, false, false, false, false, false, false)
+        )
+        val mockLoads: List<LoadDimension> = (0..6).map { number -> LoadDimension(number, emptyList()) }
+        val mockResources: List<Resource> = (0..6).map { number -> Resource(number, emptyList()) }
+        val results = Results()
+        val benchmark = TestBenchmark()
+        val sloChecker: BenchmarkExecution.Slo = BenchmarkExecution.Slo()
+        val benchmarkExecutor = TestBenchmarkExecutorImpl(mockResults, benchmark, results, sloChecker, 0, 0, 5)
+        val strategy = InitialGuessSearchStrategy(benchmarkExecutor)
+
+        val actual: ArrayList<Resource?> = ArrayList()
+        val expected: ArrayList<Resource?> = ArrayList(listOf(0, 2, 2, 1, 4, 6).map { x -> Resource(x, emptyList()) })
+        expected.add(null)
+
+        var currentResource : Resource? = mockResources[0]
+        for (load in mockLoads) {
+            val returnVal : Resource? = strategy.findSuitableResource(load, mockResources, currentResource)
+            if(returnVal != null) {
+                logger.info { "returnVal '${returnVal.get()}'" }
+            }
+            else {
+                logger.info { "returnVal is null." }
+            }
+
+            actual.add(returnVal)
+            currentResource = returnVal
+        }
+
+        assertEquals(actual, expected)
+    }
+}
\ No newline at end of file
-- 
GitLab