diff --git a/benchmark/src/main/resources/benchmark.xsd b/benchmark/src/main/resources/benchmark.xsd
index 28963b2b01..34bef398b7 100644
--- a/benchmark/src/main/resources/benchmark.xsd
+++ b/benchmark/src/main/resources/benchmark.xsd
@@ -593,7 +593,7 @@
-
+
diff --git a/core/src/build/revapi-differences.json b/core/src/build/revapi-differences.json
index 58e0b12894..eea65dcb89 100644
--- a/core/src/build/revapi-differences.json
+++ b/core/src/build/revapi-differences.json
@@ -89,7 +89,7 @@
"annotationType": "jakarta.xml.bind.annotation.XmlType",
"attribute": "propOrder",
"oldValue": "{\"terminationClass\", \"terminationCompositionStyle\", \"spentLimit\", \"millisecondsSpentLimit\", \"secondsSpentLimit\", \"minutesSpentLimit\", \"hoursSpentLimit\", \"daysSpentLimit\", \"unimprovedSpentLimit\", \"unimprovedMillisecondsSpentLimit\", \"unimprovedSecondsSpentLimit\", \"unimprovedMinutesSpentLimit\", \"unimprovedHoursSpentLimit\", \"unimprovedDaysSpentLimit\", \"unimprovedScoreDifferenceThreshold\", \"bestScoreLimit\", \"bestScoreFeasible\", \"stepCountLimit\", \"unimprovedStepCountLimit\", \"scoreCalculationCountLimit\", \"terminationConfigList\"}",
- "newValue": "{\"terminationClass\", \"terminationCompositionStyle\", \"spentLimit\", \"millisecondsSpentLimit\", \"secondsSpentLimit\", \"minutesSpentLimit\", \"hoursSpentLimit\", \"daysSpentLimit\", \"unimprovedSpentLimit\", \"unimprovedMillisecondsSpentLimit\", \"unimprovedSecondsSpentLimit\", \"unimprovedMinutesSpentLimit\", \"unimprovedHoursSpentLimit\", \"unimprovedDaysSpentLimit\", \"unimprovedScoreDifferenceThreshold\", \"bestScoreLimit\", \"bestScoreFeasible\", \"stepCountLimit\", \"unimprovedStepCountLimit\", \"scoreCalculationCountLimit\", \"moveCountLimit\", \"stopFlatLineDetectionRatio\", \"noStopFlatLineDetectionRatio\", \"minimalExecutionTimeSeconds\", \"terminationConfigList\"}",
+ "newValue": "{\"terminationClass\", \"terminationCompositionStyle\", \"spentLimit\", \"millisecondsSpentLimit\", \"secondsSpentLimit\", \"minutesSpentLimit\", \"hoursSpentLimit\", \"daysSpentLimit\", \"unimprovedSpentLimit\", \"unimprovedMillisecondsSpentLimit\", \"unimprovedSecondsSpentLimit\", \"unimprovedMinutesSpentLimit\", \"unimprovedHoursSpentLimit\", \"unimprovedDaysSpentLimit\", \"unimprovedScoreDifferenceThreshold\", \"bestScoreLimit\", \"bestScoreFeasible\", \"stepCountLimit\", \"unimprovedStepCountLimit\", \"scoreCalculationCountLimit\", \"moveCountLimit\", \"stopFlatLineDetectionRatio\", \"noStopFlatLineDetectionRatio\", \"delayFlatLineSecondsSpentLimit\", \"terminationConfigList\"}",
"justification": "Add new termination config"
}
]
diff --git a/core/src/main/java/ai/timefold/solver/core/config/solver/termination/TerminationConfig.java b/core/src/main/java/ai/timefold/solver/core/config/solver/termination/TerminationConfig.java
index 99b90c854a..08396b056b 100644
--- a/core/src/main/java/ai/timefold/solver/core/config/solver/termination/TerminationConfig.java
+++ b/core/src/main/java/ai/timefold/solver/core/config/solver/termination/TerminationConfig.java
@@ -41,7 +41,7 @@
"moveCountLimit",
"stopFlatLineDetectionRatio",
"noStopFlatLineDetectionRatio",
- "minimalExecutionTimeSeconds",
+ "delayFlatLineSecondsSpentLimit",
"terminationConfigList"
})
public class TerminationConfig extends AbstractConfig {
@@ -83,7 +83,7 @@ public class TerminationConfig extends AbstractConfig {
private Double stopFlatLineDetectionRatio = null;
private Double noStopFlatLineDetectionRatio = null;
- private Long minimalExecutionTimeSeconds = null;
+ private Long delayFlatLineSecondsSpentLimit = null;
@XmlElement(name = "termination")
private List terminationConfigList = null;
@@ -280,12 +280,12 @@ public void setNoStopFlatLineDetectionRatio(@Nullable Double noStopFlatLineDetec
this.noStopFlatLineDetectionRatio = noStopFlatLineDetectionRatio;
}
- public @Nullable Long getMinimalExecutionTimeSeconds() {
- return minimalExecutionTimeSeconds;
+ public @Nullable Long getDelayFlatLineSecondsSpentLimit() {
+ return delayFlatLineSecondsSpentLimit;
}
- public void setMinimalExecutionTimeSeconds(@Nullable Long minimalExecutionTimeSeconds) {
- this.minimalExecutionTimeSeconds = minimalExecutionTimeSeconds;
+ public void setDelayFlatLineSecondsSpentLimit(@Nullable Long delayFlatLineSecondsSpentLimit) {
+ this.delayFlatLineSecondsSpentLimit = delayFlatLineSecondsSpentLimit;
}
public @Nullable List<@NonNull TerminationConfig> getTerminationConfigList() {
@@ -421,8 +421,8 @@ public TerminationConfig withTerminationClass(Class extends Termination> termi
return this;
}
- public @NonNull TerminationConfig withMinimalExecutionTimeSeconds(@NonNull Long minimalExecutionTimeSeconds) {
- this.minimalExecutionTimeSeconds = minimalExecutionTimeSeconds;
+ public @NonNull TerminationConfig withDelayFlatLineSecondsSpentLimit(@NonNull Long delayFlatLineSecondsSpentLimit) {
+ this.delayFlatLineSecondsSpentLimit = delayFlatLineSecondsSpentLimit;
return this;
}
@@ -537,7 +537,7 @@ public boolean isConfigured() {
moveCountLimit != null ||
stopFlatLineDetectionRatio != null ||
noStopFlatLineDetectionRatio != null ||
- minimalExecutionTimeSeconds != null ||
+ delayFlatLineSecondsSpentLimit != null ||
isTerminationListConfigured();
}
@@ -582,8 +582,8 @@ private boolean isTerminationListConfigured() {
inheritedConfig.getStopFlatLineDetectionRatio());
noStopFlatLineDetectionRatio = ConfigUtils.inheritOverwritableProperty(noStopFlatLineDetectionRatio,
inheritedConfig.getNoStopFlatLineDetectionRatio());
- minimalExecutionTimeSeconds = ConfigUtils.inheritOverwritableProperty(minimalExecutionTimeSeconds,
- inheritedConfig.getMinimalExecutionTimeSeconds());
+ delayFlatLineSecondsSpentLimit = ConfigUtils.inheritOverwritableProperty(delayFlatLineSecondsSpentLimit,
+ inheritedConfig.getDelayFlatLineSecondsSpentLimit());
terminationConfigList = ConfigUtils.inheritMergeableListConfig(
terminationConfigList, inheritedConfig.getTerminationConfigList());
return this;
diff --git a/core/src/main/java/ai/timefold/solver/core/impl/solver/termination/TerminationFactory.java b/core/src/main/java/ai/timefold/solver/core/impl/solver/termination/TerminationFactory.java
index 204dc6946f..8f51535218 100644
--- a/core/src/main/java/ai/timefold/solver/core/impl/solver/termination/TerminationFactory.java
+++ b/core/src/main/java/ai/timefold/solver/core/impl/solver/termination/TerminationFactory.java
@@ -90,9 +90,10 @@ The termination with bestScoreFeasible (%s) can only be used with a score type \
}
if (terminationConfig.getStopFlatLineDetectionRatio() != null
|| terminationConfig.getNoStopFlatLineDetectionRatio() != null
- || terminationConfig.getMinimalExecutionTimeSeconds() != null) {
+ || terminationConfig.getDelayFlatLineSecondsSpentLimit() != null) {
terminationList.add(new UnimprovedBestSolutionTermination<>(terminationConfig.getStopFlatLineDetectionRatio(),
- terminationConfig.getNoStopFlatLineDetectionRatio(), terminationConfig.getMinimalExecutionTimeSeconds()));
+ terminationConfig.getNoStopFlatLineDetectionRatio(),
+ terminationConfig.getDelayFlatLineSecondsSpentLimit()));
}
terminationList.addAll(buildInnerTermination(configPolicy));
return buildTerminationFromList(terminationList);
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 dc391f12d9..7233900be7 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
@@ -11,8 +11,8 @@
public final class UnimprovedBestSolutionTermination extends AbstractTermination {
- // Minimal interval of time to avoid early conclusions
- private final long minimalExecutionTimeMillis;
+ // Evaluation delay to avoid early conclusions
+ private final long delayExecutionTimeMillis;
// This setting determines the amount of time
// that is allowed without any improvements since the last best solution was identified.
// For example, if the last solution was found at 10 seconds and the setting is configured to 0.5,
@@ -38,19 +38,20 @@ public final class UnimprovedBestSolutionTermination extends Abstract
protected Boolean terminate;
public UnimprovedBestSolutionTermination(Double stopFlatLineDetectionRatio,
- Double noStopFlatLineDetectionRatio, Long minimalExecutionTimeSeconds) {
- this(stopFlatLineDetectionRatio, noStopFlatLineDetectionRatio, minimalExecutionTimeSeconds, Clock.systemUTC());
+ Double noStopFlatLineDetectionRatio, Long delayFlatLineSecondsSpentLimit) {
+ this(stopFlatLineDetectionRatio, noStopFlatLineDetectionRatio, delayFlatLineSecondsSpentLimit, Clock.systemUTC());
}
public UnimprovedBestSolutionTermination(Double stopFlatLineDetectionRatio, Double noStopFlatLineDetectionRatio,
- Long minimalExecutionTimeSeconds, Clock clock) {
+ Long delayFlatLineSecondsSpentLimit, Clock clock) {
this.stopFlatLineDetectionRatio = Objects.requireNonNull(stopFlatLineDetectionRatio,
"The field stopFlatLineDetectionRatio is required for the termination UnimprovedBestSolutionTermination");
this.noStopFlatLineDetectionRatio = Objects.requireNonNull(noStopFlatLineDetectionRatio,
"The field noStopFlatLineDetectionRatio is required for the termination UnimprovedBestSolutionTermination");
- this.minimalExecutionTimeMillis = Objects.requireNonNull(minimalExecutionTimeSeconds,
- "The field minimalExecutionTimeSeconds is required for the termination UnimprovedBestSolutionTermination")
- * 1000L;
+ this.delayExecutionTimeMillis =
+ (Objects.requireNonNull(delayFlatLineSecondsSpentLimit,
+ "The field delayFlatLineSecondsSpentLimit is required for the termination UnimprovedBestSolutionTermination")
+ * 1000L);
this.clock = Objects.requireNonNull(clock);
if (stopFlatLineDetectionRatio < 0) {
throw new IllegalArgumentException(
@@ -65,14 +66,14 @@ public UnimprovedBestSolutionTermination(Double stopFlatLineDetectionRatio, Doub
"The noStopFlatLineDetectionRatio (%.2f) cannot be greater than stopFlatLineDetectionRatio (%.2f)."
.formatted(noStopFlatLineDetectionRatio, stopFlatLineDetectionRatio));
}
- if (minimalExecutionTimeSeconds <= 0) {
+ if (delayFlatLineSecondsSpentLimit < 0) {
throw new IllegalArgumentException(
- "The minimalExecutionTimeSeconds %d must be great than zero.".formatted(minimalExecutionTimeSeconds));
+ "The delayFlatLineSecondsSpentLimit (%d) cannot be negative.".formatted(delayFlatLineSecondsSpentLimit));
}
}
- public long getMinimalExecutionTimeMillis() {
- return minimalExecutionTimeMillis;
+ public long getDelayExecutionTimeMillis() {
+ return delayExecutionTimeMillis;
}
public double getStopFlatLineDetectionRatio() {
@@ -129,7 +130,7 @@ public boolean isPhaseTerminated(AbstractPhaseScope phaseScope) {
if (terminate != null) {
return terminate;
}
- // Validate if there is a first best solution and the poll time
+ // Validate if there is a first best solution
if (waitForFirstBestScore) {
return false;
}
@@ -144,7 +145,7 @@ public boolean isPhaseTerminated(AbstractPhaseScope phaseScope) {
// as it would be the starting point for the new curve.
var minInterval = Math.floor(lastImprovementInterval * noStopFlatLineDetectionRatio);
var maxInterval = Math.floor(lastImprovementInterval * stopFlatLineDetectionRatio);
- if (lastImprovementMillis > 0 && completeInterval >= minimalExecutionTimeMillis && newInterval > minInterval
+ if (lastImprovementMillis > 0 && completeInterval >= delayExecutionTimeMillis && newInterval >= minInterval
&& newInterval < maxInterval) {
initialCurvePointMillis = lastImprovementMillis;
previousBest = currentBest;
@@ -159,7 +160,7 @@ public boolean isPhaseTerminated(AbstractPhaseScope phaseScope) {
terminate = null;
return false;
} else {
- if (completeInterval < minimalExecutionTimeMillis) {
+ if (completeInterval < delayExecutionTimeMillis) {
return false;
}
var maxInterval = Math.floor(lastImprovementInterval * stopFlatLineDetectionRatio);
@@ -198,12 +199,12 @@ public double calculatePhaseTimeGradient(AbstractPhaseScope phaseScop
public UnimprovedBestSolutionTermination createChildThreadTermination(SolverScope solverScope,
ChildThreadType childThreadType) {
return new UnimprovedBestSolutionTermination<>(stopFlatLineDetectionRatio, noStopFlatLineDetectionRatio,
- minimalExecutionTimeMillis, clock);
+ delayExecutionTimeMillis / 1000, clock);
}
@Override
public String toString() {
- return "UnimprovedBestSolutionTermination(%.2f, %.2f)".formatted(stopFlatLineDetectionRatio,
- noStopFlatLineDetectionRatio);
+ return "UnimprovedBestSolutionTermination(%.2f, %.2f, %d)".formatted(stopFlatLineDetectionRatio,
+ noStopFlatLineDetectionRatio, delayExecutionTimeMillis / 1000);
}
}
diff --git a/core/src/main/resources/solver.xsd b/core/src/main/resources/solver.xsd
index 7b817f77fd..0c0be6c341 100644
--- a/core/src/main/resources/solver.xsd
+++ b/core/src/main/resources/solver.xsd
@@ -201,7 +201,7 @@
-
+
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 1f600f32ef..3791f8a68d 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
@@ -90,11 +90,11 @@ void childWithUnimprovedPropertiesFromParent() {
TerminationConfig parent = new TerminationConfig()
.withStopFlatLineDetectionRatio(0.5)
.withNoStopFlatLineDetectionRatio(0.1)
- .withMinimalExecutionTimeSeconds(10L);
+ .withDelayFlatLineSecondsSpentLimit(10L);
child.inherit(parent);
assertThat(child.getStopFlatLineDetectionRatio()).isEqualTo(0.5);
assertThat(child.getNoStopFlatLineDetectionRatio()).isEqualTo(0.1);
- assertThat(child.getMinimalExecutionTimeSeconds()).isEqualTo(10L);
+ assertThat(child.getDelayFlatLineSecondsSpentLimit()).isEqualTo(10L);
}
@Test
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 284de54e9b..751a611728 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
@@ -240,7 +240,7 @@ void buildUnimprovedBestScoreRatio() {
var terminationConfig = new TerminationConfig();
terminationConfig.setStopFlatLineDetectionRatio(0.5);
terminationConfig.setNoStopFlatLineDetectionRatio(0.1);
- terminationConfig.setMinimalExecutionTimeSeconds(10L);
+ terminationConfig.setDelayFlatLineSecondsSpentLimit(10L);
var termination = TerminationFactory.create(terminationConfig)
.buildTermination(mock(HeuristicConfigPolicy.class));
assertThat(termination)
@@ -249,7 +249,7 @@ void buildUnimprovedBestScoreRatio() {
.isEqualTo(0.5);
assertThat(((UnimprovedBestSolutionTermination>) termination).getNoStopFlatLineDetectionRatio())
.isEqualTo(0.1);
- assertThat(((UnimprovedBestSolutionTermination>) termination).getMinimalExecutionTimeMillis())
+ assertThat(((UnimprovedBestSolutionTermination>) termination).getDelayExecutionTimeMillis())
.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 2b6349a214..2bdaf510b2 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
@@ -48,10 +48,10 @@ void testTermination() {
}
@Test
- void testStartNewCurve() {
+ void testStartNewPoint() {
Clock clock = Mockito.mock(Clock.class);
var currentTime = Clock.systemUTC().millis();
- var termination = new UnimprovedBestSolutionTermination(0.5, 0.3, 10L, clock);
+ var termination = new UnimprovedBestSolutionTermination(0.5, 0.4, 10L, clock);
var solverScope = Mockito.mock(SolverScope.class);
var phaseScope = Mockito.mock(LocalSearchPhaseScope.class);
when(phaseScope.getSolverScope()).thenReturn(solverScope);
@@ -60,7 +60,7 @@ void testStartNewCurve() {
termination.phaseStarted(phaseScope);
termination.waitForFirstBestScore = false;
- // Adding a new curve
+ // New start point
termination.currentBest = SimpleScore.of(1);
termination.initialCurvePointMillis = currentTime;
termination.lastImprovementMillis = currentTime + 10_000;
@@ -68,7 +68,7 @@ void testStartNewCurve() {
assertThat(termination.isPhaseTerminated(phaseScope)).isFalse();
assertThat(termination.initialCurvePointMillis).isEqualTo(currentTime + 10_000);
- // Not adding a new curve - flat line smaller than the minimum
+ // Don't change start point - flat line smaller than the minimum
termination.terminate = null;
termination.currentBest = SimpleScore.of(1);
termination.initialCurvePointMillis = currentTime;
@@ -77,7 +77,7 @@ void testStartNewCurve() {
assertThat(termination.isPhaseTerminated(phaseScope)).isFalse();
assertThat(termination.initialCurvePointMillis).isEqualTo(currentTime);
- // Not adding a new curve - flat line larger than the minimum
+ // Don't change start point - flat line larger than the minimum
termination.terminate = null;
termination.currentBest = SimpleScore.of(1);
termination.initialCurvePointMillis = currentTime;
@@ -88,7 +88,7 @@ void testStartNewCurve() {
}
@Test
- void testMinimalInterval() {
+ void testDelayInterval() {
Clock clock = Mockito.mock(Clock.class);
var currentTime = Clock.systemUTC().millis();
var termination = new UnimprovedBestSolutionTermination(0.5, 0.4, 10L, clock);
@@ -119,15 +119,12 @@ void testMinimalInterval() {
@Test
void invalidTermination() {
assertThatIllegalArgumentException()
- .isThrownBy(() -> new UnimprovedBestSolutionTermination(-1.0, 0.0, 1L));
- assertThatIllegalArgumentException()
- .isThrownBy(() -> new UnimprovedBestSolutionTermination(0.0, -1.0, 1L));
+ .isThrownBy(() -> new UnimprovedBestSolutionTermination(-1.0, 0.0, 0L));
assertThatIllegalArgumentException()
- .isThrownBy(() -> new UnimprovedBestSolutionTermination(0.0, 1.0, 1L));
+ .isThrownBy(() -> new UnimprovedBestSolutionTermination(0.0, -1.0, 0L));
assertThatIllegalArgumentException()
- .isThrownBy(() -> new UnimprovedBestSolutionTermination(1.0, 1.0, 0L));
+ .isThrownBy(() -> new UnimprovedBestSolutionTermination(0.0, 1.0, 0L));
assertThatIllegalArgumentException()
- .isThrownBy(() -> new UnimprovedBestSolutionTermination(0.1, 1.0, 1L)
- .calculateSolverTimeGradient(null));
+ .isThrownBy(() -> new UnimprovedBestSolutionTermination(1.0, 1.0, -1L));
}
}
diff --git a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc
index effdeb4b72..32b1842beb 100644
--- a/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc
+++ b/docs/src/modules/ROOT/pages/optimization-algorithms/overview.adoc
@@ -577,7 +577,7 @@ the solver improved the solution and how much time has passed without any improv
0.5
0.1
- 120
+ 120
----
@@ -596,7 +596,7 @@ setting the `stopFlatLineDetectionRatio` to `1.0` will lead to termination after
[NOTE]
====
-The `Termination` process requires a minimum execution time, specified by `minimalExecutionTimeSeconds`.
+The `Termination` process requires a minimum execution time, specified by `delayFlatLineSecondsSpentLimit`.
====
The solving process may identify no improvement periods that are not significant enough to trigger the termination.