diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/termination/UnimprovedBestSolutionTermination.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/termination/UnimprovedBestSolutionTermination.java index 5211b108e7..dc391f12d9 100644 --- a/core/src/main/java/ai/timefold/solver/core/impl/solver/termination/UnimprovedBestSolutionTermination.java +++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/termination/UnimprovedBestSolutionTermination.java @@ -71,6 +71,18 @@ public UnimprovedBestSolutionTermination(Double stopFlatLineDetectionRatio, Doub } } + public long getMinimalExecutionTimeMillis() { + return minimalExecutionTimeMillis; + } + + public double getStopFlatLineDetectionRatio() { + return stopFlatLineDetectionRatio; + } + + public double getNoStopFlatLineDetectionRatio() { + return noStopFlatLineDetectionRatio; + } + // ************************************************************************ // Lifecycle methods // ************************************************************************ diff --git a/core/src/test/java/ai/timefold/solver/core/config/solver/termination/TerminationConfigTest.java b/core/src/test/java/ai/timefold/solver/core/config/solver/termination/TerminationConfigTest.java index db0bc3fbcf..1f600f32ef 100644 --- a/core/src/test/java/ai/timefold/solver/core/config/solver/termination/TerminationConfigTest.java +++ b/core/src/test/java/ai/timefold/solver/core/config/solver/termination/TerminationConfigTest.java @@ -84,6 +84,19 @@ void childWithTimeSpentLimitShouldNotInheritTimeSpentLimitFromParent() { assertThat(child.getMinutesSpentLimit()).isNull(); } + @Test + void childWithUnimprovedPropertiesFromParent() { + TerminationConfig child = new TerminationConfig(); + TerminationConfig parent = new TerminationConfig() + .withStopFlatLineDetectionRatio(0.5) + .withNoStopFlatLineDetectionRatio(0.1) + .withMinimalExecutionTimeSeconds(10L); + child.inherit(parent); + assertThat(child.getStopFlatLineDetectionRatio()).isEqualTo(0.5); + assertThat(child.getNoStopFlatLineDetectionRatio()).isEqualTo(0.1); + assertThat(child.getMinimalExecutionTimeSeconds()).isEqualTo(10L); + } + @Test void checkMoveCountMetrics() { TerminationConfig parent = new TerminationConfig() diff --git a/core/src/test/java/ai/timefold/solver/core/impl/solver/termination/TerminationFactoryTest.java b/core/src/test/java/ai/timefold/solver/core/impl/solver/termination/TerminationFactoryTest.java index ab32c6adc4..284de54e9b 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/solver/termination/TerminationFactoryTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/solver/termination/TerminationFactoryTest.java @@ -201,4 +201,55 @@ void bestScoreFeasible_requiresAtLeastOneFeasibleLevel() { .isThrownBy(() -> terminationFactory.buildTermination(heuristicConfigPolicy)) .withMessageContaining("can only be used with a score type that has at least 1 feasible level"); } + + @Test + void buildBestScoreFeasible() { + var heuristicConfigPolicy = mock(HeuristicConfigPolicy.class); + when(heuristicConfigPolicy.getScoreDefinition()).thenReturn(new HardSoftScoreDefinition()); + var terminationConfig = new TerminationConfig(); + terminationConfig.setBestScoreFeasible(true); + var termination = TerminationFactory.create(terminationConfig).buildTermination(heuristicConfigPolicy); + assertThat(termination) + .isInstanceOf(BestScoreFeasibleTermination.class); + } + + @Test + void buildStepCountLimit() { + var heuristicConfigPolicy = mock(HeuristicConfigPolicy.class); + when(heuristicConfigPolicy.getScoreDefinition()).thenReturn(new HardSoftScoreDefinition()); + var terminationConfig = new TerminationConfig(); + terminationConfig.setStepCountLimit(1); + var termination = TerminationFactory.create(terminationConfig).buildTermination(heuristicConfigPolicy); + assertThat(termination) + .isInstanceOf(StepCountTermination.class); + } + + @Test + void buildUnimprovedStepCountLimit() { + var heuristicConfigPolicy = mock(HeuristicConfigPolicy.class); + when(heuristicConfigPolicy.getScoreDefinition()).thenReturn(new HardSoftScoreDefinition()); + var terminationConfig = new TerminationConfig(); + terminationConfig.withUnimprovedStepCountLimit(1); + var termination = TerminationFactory.create(terminationConfig).buildTermination(heuristicConfigPolicy); + assertThat(termination) + .isInstanceOf(UnimprovedStepCountTermination.class); + } + + @Test + void buildUnimprovedBestScoreRatio() { + var terminationConfig = new TerminationConfig(); + terminationConfig.setStopFlatLineDetectionRatio(0.5); + terminationConfig.setNoStopFlatLineDetectionRatio(0.1); + terminationConfig.setMinimalExecutionTimeSeconds(10L); + var termination = TerminationFactory.create(terminationConfig) + .buildTermination(mock(HeuristicConfigPolicy.class)); + assertThat(termination) + .isInstanceOf(UnimprovedBestSolutionTermination.class); + assertThat(((UnimprovedBestSolutionTermination) termination).getStopFlatLineDetectionRatio()) + .isEqualTo(0.5); + assertThat(((UnimprovedBestSolutionTermination) termination).getNoStopFlatLineDetectionRatio()) + .isEqualTo(0.1); + assertThat(((UnimprovedBestSolutionTermination) termination).getMinimalExecutionTimeMillis()) + .isEqualTo(10000L); + } } diff --git a/core/src/test/java/ai/timefold/solver/core/impl/solver/termination/UnimprovedBestSolutionTerminationTest.java b/core/src/test/java/ai/timefold/solver/core/impl/solver/termination/UnimprovedBestSolutionTerminationTest.java index c0c1fb7d2f..2b6349a214 100644 --- a/core/src/test/java/ai/timefold/solver/core/impl/solver/termination/UnimprovedBestSolutionTerminationTest.java +++ b/core/src/test/java/ai/timefold/solver/core/impl/solver/termination/UnimprovedBestSolutionTerminationTest.java @@ -44,6 +44,7 @@ void testTermination() { when(clock.millis()).thenReturn(currentTime + 14_000); assertThat(termination.isPhaseTerminated(phaseScope)).isFalse(); + assertThat(termination.calculatePhaseTimeGradient(phaseScope)).isEqualTo(-1.0); } @Test @@ -125,5 +126,8 @@ void invalidTermination() { .isThrownBy(() -> new UnimprovedBestSolutionTermination(0.0, 1.0, 1L)); assertThatIllegalArgumentException() .isThrownBy(() -> new UnimprovedBestSolutionTermination(1.0, 1.0, 0L)); + assertThatIllegalArgumentException() + .isThrownBy(() -> new UnimprovedBestSolutionTermination(0.1, 1.0, 1L) + .calculateSolverTimeGradient(null)); } }