diff --git a/scala-source/com/barrybecker4/optimization/strategy/OptimizationStrategyType.scala b/scala-source/com/barrybecker4/optimization/strategy/OptimizationStrategyType.scala index be62375..694f40f 100644 --- a/scala-source/com/barrybecker4/optimization/strategy/OptimizationStrategyType.scala +++ b/scala-source/com/barrybecker4/optimization/strategy/OptimizationStrategyType.scala @@ -91,7 +91,7 @@ enum OptimizationStrategyType { case HILL_CLIMBING | GLOBAL_HILL_CLIMBING => locallySearchable(params) case TABU_SEARCH => - !optimizee.evaluateByComparison + !optimizee.evaluateByComparison && !hasContinuousParameter(params) case MIN_CONFLICTS => !optimizee.evaluateByComparison && params.supportsLocalMoves case _ => diff --git a/scala-source/com/barrybecker4/optimization/strategy/tabu/TabuSearchStrategy.scala b/scala-source/com/barrybecker4/optimization/strategy/tabu/TabuSearchStrategy.scala index d2fb8b9..5080564 100644 --- a/scala-source/com/barrybecker4/optimization/strategy/tabu/TabuSearchStrategy.scala +++ b/scala-source/com/barrybecker4/optimization/strategy/tabu/TabuSearchStrategy.scala @@ -3,7 +3,7 @@ package com.barrybecker4.optimization.strategy.tabu import com.barrybecker4.math.MathUtil import com.barrybecker4.optimization.optimizee.{EvaluationBudgetExceededException, Optimizee} -import com.barrybecker4.optimization.parameter.{ParameterArray, ParameterArrayWithFitness} +import com.barrybecker4.optimization.parameter.{NumericParameterArray, ParameterArray, ParameterArrayWithFitness} import com.barrybecker4.optimization.strategy.OptimizationStrategy import com.barrybecker4.optimization.strategy.statespace.StateSpaceSearchStrategy @@ -22,7 +22,7 @@ import scala.util.Random * scores random neighbors instead and forbids returning to a solution it has just left; that works but evaluates a * whole solution per candidate, so it is slow. * - * Works with absolute fitness only. When the optimizee is wrapped with an evaluation budget the best solution found + * Works with absolute fitness and discrete parameters only. When the optimizee is wrapped with an evaluation budget the best solution found * so far is returned when the budget is used up. * * @param optimizee the thing to be optimized. @@ -41,6 +41,7 @@ class TabuSearchStrategy(optimizee: Optimizee, rnd: Random = MathUtil.RANDOM) ex override def doOptimization(params: ParameterArray, fitnessRange: Double): ParameterArrayWithFitness = { require(!optimizee.evaluateByComparison, "Tabu search needs absolute fitness, not comparison") + require(!hasContinuousParameter(params), "Tabu search needs discrete parameters, but this array has a continuous one") var best = ParameterArrayWithFitness(params, Double.PositiveInfinity) try best = search(params) catch { case _: EvaluationBudgetExceededException => best = bestSoFar.getOrElse(best) } @@ -64,6 +65,11 @@ class TabuSearchStrategy(optimizee: Optimizee, rnd: Random = MathUtil.RANDOM) ex best } + private def hasContinuousParameter(params: ParameterArray): Boolean = params match { + case numeric: NumericParameterArray => !numeric.supportsLocalMoves + case _ => false + } + private def shouldRestart(stagnantIterations: Int): Boolean = config.restartAfterStagnantIterations > 0 && stagnantIterations >= config.restartAfterStagnantIterations diff --git a/scala-test/com/barrybecker4/optimization/StrategyScoreboardSuite.scala b/scala-test/com/barrybecker4/optimization/StrategyScoreboardSuite.scala index 03fb858..5c2f07d 100644 --- a/scala-test/com/barrybecker4/optimization/StrategyScoreboardSuite.scala +++ b/scala-test/com/barrybecker4/optimization/StrategyScoreboardSuite.scala @@ -160,6 +160,10 @@ class StrategyScoreboardSuite extends AnyFunSuite with TimeLimitedTests { case (VARIABLE_NEIGHBORHOOD_SEARCH, "ackley") => Reach(3.0) case (VARIABLE_NEIGHBORHOOD_SEARCH, "rastrigin") => Reach(2.0) + case (TABU_SEARCH, _) if continuous => Refuse("discrete") + case (MIN_CONFLICTS, _) if continuous => Refuse("local moves") + case (TABU_SEARCH | MIN_CONFLICTS, _) => Reach(Exact, Some(500)) + case _ => throw new IllegalStateException(s"No scoreboard bar for $strategy on $problem") } diff --git a/scala-test/com/barrybecker4/optimization/strategy/OptimizationStrategyTypeSuite.scala b/scala-test/com/barrybecker4/optimization/strategy/OptimizationStrategyTypeSuite.scala index b120d3a..c7f6d70 100644 --- a/scala-test/com/barrybecker4/optimization/strategy/OptimizationStrategyTypeSuite.scala +++ b/scala-test/com/barrybecker4/optimization/strategy/OptimizationStrategyTypeSuite.scala @@ -83,8 +83,7 @@ class OptimizationStrategyTypeSuite extends AnyFunSuite { "DIFFERENTIAL_EVOLUTION", "PARTICLE_SWARM", "ITERATED_LOCAL_SEARCH", - "VARIABLE_NEIGHBORHOOD_SEARCH", - "TABU_SEARCH" + "VARIABLE_NEIGHBORHOOD_SEARCH" )) } diff --git a/scala-test/com/barrybecker4/optimization/strategy/tabu/TabuSearchStrategySuite.scala b/scala-test/com/barrybecker4/optimization/strategy/tabu/TabuSearchStrategySuite.scala index 6d7a8dc..218e8fc 100644 --- a/scala-test/com/barrybecker4/optimization/strategy/tabu/TabuSearchStrategySuite.scala +++ b/scala-test/com/barrybecker4/optimization/strategy/tabu/TabuSearchStrategySuite.scala @@ -98,6 +98,11 @@ class TabuSearchStrategySuite extends AnyFunSuite { intercept[IllegalArgumentException](strategyFor(problem, TabuSearchConfig()).doOptimization(problem.getInitialGuess, 1.0)) } + test("an array with a real number is refused") { + val problem = new com.barrybecker4.optimization.optimizee.optimizees.SphereProblem(2) + intercept[IllegalArgumentException](strategyFor(problem, TabuSearchConfig()).doOptimization(problem.getInitialGuess, 1.0)) + } + test("the configuration refuses values that make no sense") { intercept[IllegalArgumentException](TabuSearchConfig(candidateMoves = 0)) intercept[IllegalArgumentException](TabuSearchConfig(tenureMin = 5, tenureMax = 4)) diff --git a/scala-test/com/barrybecker4/optimization/viewer/ui/OptimizerEvalStatusSuite.scala b/scala-test/com/barrybecker4/optimization/viewer/ui/OptimizerEvalStatusSuite.scala index 377c169..614c183 100644 --- a/scala-test/com/barrybecker4/optimization/viewer/ui/OptimizerEvalStatusSuite.scala +++ b/scala-test/com/barrybecker4/optimization/viewer/ui/OptimizerEvalStatusSuite.scala @@ -60,6 +60,8 @@ class OptimizerEvalStatusSuite extends AnyFunSuite { "DIFFERENTIAL_EVOLUTION", "PARTICLE_SWARM", "ITERATED_LOCAL_SEARCH", - "VARIABLE_NEIGHBORHOOD_SEARCH" + "VARIABLE_NEIGHBORHOOD_SEARCH", + "TABU_SEARCH", + "MIN_CONFLICTS" ) }