Skip to content

Commit

Permalink
chore: add minimal execution time
Browse files Browse the repository at this point in the history
  • Loading branch information
zepfred committed Nov 14, 2024
1 parent a20eba8 commit b64318c
Show file tree
Hide file tree
Showing 7 changed files with 70 additions and 41 deletions.
3 changes: 3 additions & 0 deletions benchmark/src/main/resources/benchmark.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,9 @@
<xs:element minOccurs="0" name="newCurveDetectionRatio" type="xs:double"/>


<xs:element minOccurs="0" name="minimalExecutionTimeSeconds" type="xs:long"/>


<xs:element maxOccurs="unbounded" minOccurs="0" name="termination" type="tns:terminationConfig"/>


Expand Down
2 changes: 1 addition & 1 deletion core/src/build/revapi-differences.json
Original file line number Diff line number Diff line change
Expand Up @@ -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\", \"flatLineDetectionRatio\", \"newCurveDetectionRatio\", \"terminationConfigList\"}",
"newValue": "{\"terminationClass\", \"terminationCompositionStyle\", \"spentLimit\", \"millisecondsSpentLimit\", \"secondsSpentLimit\", \"minutesSpentLimit\", \"hoursSpentLimit\", \"daysSpentLimit\", \"unimprovedSpentLimit\", \"unimprovedMillisecondsSpentLimit\", \"unimprovedSecondsSpentLimit\", \"unimprovedMinutesSpentLimit\", \"unimprovedHoursSpentLimit\", \"unimprovedDaysSpentLimit\", \"unimprovedScoreDifferenceThreshold\", \"bestScoreLimit\", \"bestScoreFeasible\", \"stepCountLimit\", \"unimprovedStepCountLimit\", \"scoreCalculationCountLimit\", \"moveCountLimit\", \"flatLineDetectionRatio\", \"newCurveDetectionRatio\", \"minimalExecutionTimeSeconds\", \"terminationConfigList\"}",
"justification": "Add new termination config"
}
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"moveCountLimit",
"flatLineDetectionRatio",
"newCurveDetectionRatio",
"minimalExecutionTimeSeconds",
"terminationConfigList"
})
public class TerminationConfig extends AbstractConfig<TerminationConfig> {
Expand Down Expand Up @@ -82,6 +83,7 @@ public class TerminationConfig extends AbstractConfig<TerminationConfig> {

private Double flatLineDetectionRatio = null;
private Double newCurveDetectionRatio = null;
private Long minimalExecutionTimeSeconds = null;

@XmlElement(name = "termination")
private List<TerminationConfig> terminationConfigList = null;
Expand Down Expand Up @@ -278,6 +280,14 @@ public void setNewCurveDetectionRatio(@Nullable Double newCurveDetectionRatio) {
this.newCurveDetectionRatio = newCurveDetectionRatio;
}

public @Nullable Long getMinimalExecutionTimeSeconds() {
return minimalExecutionTimeSeconds;
}

public void setMinimalExecutionTimeSeconds(@Nullable Long minimalExecutionTimeSeconds) {
this.minimalExecutionTimeSeconds = minimalExecutionTimeSeconds;
}

public @Nullable List<@NonNull TerminationConfig> getTerminationConfigList() {
return terminationConfigList;
}
Expand Down Expand Up @@ -411,6 +421,11 @@ public TerminationConfig withTerminationClass(Class<? extends Termination> termi
return this;
}

public @NonNull TerminationConfig withMinimalExecutionTimeSeconds(@NonNull Long minimalExecutionTimeSeconds) {
this.minimalExecutionTimeSeconds = minimalExecutionTimeSeconds;
return this;
}

public @NonNull TerminationConfig
withTerminationConfigList(@NonNull List<@NonNull TerminationConfig> terminationConfigList) {
this.terminationConfigList = terminationConfigList;
Expand Down Expand Up @@ -522,6 +537,7 @@ public boolean isConfigured() {
moveCountLimit != null ||
flatLineDetectionRatio != null ||
newCurveDetectionRatio != null ||
minimalExecutionTimeSeconds != null ||
isTerminationListConfigured();
}

Expand Down Expand Up @@ -566,6 +582,8 @@ private boolean isTerminationListConfigured() {
inheritedConfig.getFlatLineDetectionRatio());
newCurveDetectionRatio = ConfigUtils.inheritOverwritableProperty(newCurveDetectionRatio,
inheritedConfig.getNewCurveDetectionRatio());
minimalExecutionTimeSeconds = ConfigUtils.inheritOverwritableProperty(minimalExecutionTimeSeconds,
inheritedConfig.getMinimalExecutionTimeSeconds());
terminationConfigList = ConfigUtils.inheritMergeableListConfig(
terminationConfigList, inheritedConfig.getTerminationConfigList());
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ public <Score_ extends Score<Score_>> Termination<Solution_> buildTermination(
}
if (terminationConfig.getFlatLineDetectionRatio() != null) {
terminationList.add(new UnimprovedBestSolutionTermination<>(terminationConfig.getFlatLineDetectionRatio(),
terminationConfig.getNewCurveDetectionRatio()));
terminationConfig.getNewCurveDetectionRatio(), terminationConfig.getMinimalExecutionTimeSeconds()));
}
terminationList.addAll(buildInnerTermination(configPolicy));
return buildTerminationFromList(terminationList);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
public final class UnimprovedBestSolutionTermination<Solution_> extends AbstractTermination<Solution_> {

// Minimal interval of time to avoid early conclusions
protected static final long MINIMAL_INTERVAL_TIME_MILLIS = 10_000L;
private final long minimalExecutionTimeMillis;
// 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,
Expand All @@ -37,15 +37,20 @@ public final class UnimprovedBestSolutionTermination<Solution_> extends Abstract
protected boolean waitForFirstBestScore;
protected Boolean terminate;

public UnimprovedBestSolutionTermination(Double flatLineDetectionRatio, Double newCurveDetectionRatio) {
this(flatLineDetectionRatio, newCurveDetectionRatio, Clock.systemUTC());
public UnimprovedBestSolutionTermination(Double flatLineDetectionRatio,
Double newCurveDetectionRatio, Long minimalExecutionTimeSeconds) {
this(flatLineDetectionRatio, newCurveDetectionRatio, minimalExecutionTimeSeconds, Clock.systemUTC());
}

public UnimprovedBestSolutionTermination(Double flatLineDetectionRatio, Double newCurveDetectionRatio, Clock clock) {
public UnimprovedBestSolutionTermination(Double flatLineDetectionRatio, Double newCurveDetectionRatio,
Long minimalExecutionTimeSeconds, Clock clock) {
this.flatLineDetectionRatio = Objects.requireNonNull(flatLineDetectionRatio,
"The field flatLineDetectionRatio is required for the termination UnimprovedBestSolutionTermination");
this.newCurveDetectionRatio = Objects.requireNonNull(newCurveDetectionRatio,
"The field newCurveDetectionRatio is required for the termination UnimprovedBestSolutionTermination");
this.minimalExecutionTimeMillis = Objects.requireNonNull(minimalExecutionTimeSeconds,
"The field minimalExecutionTimeSeconds is required for the termination UnimprovedBestSolutionTermination")
* 1000L;
this.clock = Objects.requireNonNull(clock);
if (flatLineDetectionRatio < 0) {
throw new IllegalArgumentException(
Expand All @@ -60,10 +65,10 @@ public UnimprovedBestSolutionTermination(Double flatLineDetectionRatio, Double n
"The newCurveDetectionRatio (%.2f) cannot be greater than flatLineDetectionRatio (%.2f)."
.formatted(newCurveDetectionRatio, flatLineDetectionRatio));
}
}

public double getFlatLineDetectionRatio() {
return flatLineDetectionRatio;
if (minimalExecutionTimeSeconds <= 0) {
throw new IllegalArgumentException(
"The minimalExecutionTimeSeconds %d must be great than zero.".formatted(minimalExecutionTimeSeconds));
}
}

// ************************************************************************
Expand Down Expand Up @@ -118,15 +123,16 @@ public boolean isPhaseTerminated(AbstractPhaseScope<Solution_> phaseScope) {
}
var currentTimeMillis = clock.millis();
var improved = currentBest.compareTo(phaseScope.getBestScore()) < 0;
var lastImprovementInterval = lastImprovementMillis - initialCurvePointMillis;
var completeInterval = currentTimeMillis - initialCurvePointMillis;
var newInterval = currentTimeMillis - lastImprovementMillis;
if (improved) {
// If there is a flat line between the last and new best solutions,
// the initial value becomes the most recent best score,
// as it would be the starting point for the new curve.
var minInterval = Math.floor(completeInterval * newCurveDetectionRatio);
var maxInterval = Math.floor(completeInterval * flatLineDetectionRatio);
if (lastImprovementMillis > 0 && completeInterval >= MINIMAL_INTERVAL_TIME_MILLIS && newInterval > minInterval
var minInterval = Math.floor(lastImprovementInterval * newCurveDetectionRatio);
var maxInterval = Math.floor(lastImprovementInterval * flatLineDetectionRatio);
if (lastImprovementMillis > 0 && completeInterval >= minimalExecutionTimeMillis && newInterval > minInterval
&& newInterval < maxInterval) {
initialCurvePointMillis = lastImprovementMillis;
previousBest = currentBest;
Expand All @@ -141,10 +147,10 @@ public boolean isPhaseTerminated(AbstractPhaseScope<Solution_> phaseScope) {
terminate = null;
return false;
} else {
if (completeInterval < MINIMAL_INTERVAL_TIME_MILLIS) {
if (completeInterval < minimalExecutionTimeMillis) {
return false;
}
var maxInterval = Math.floor(completeInterval * flatLineDetectionRatio);
var maxInterval = Math.floor(lastImprovementInterval * flatLineDetectionRatio);
if (newInterval > maxInterval) {
terminate = true;
return true;
Expand Down Expand Up @@ -179,7 +185,8 @@ public double calculatePhaseTimeGradient(AbstractPhaseScope<Solution_> phaseScop
@Override
public UnimprovedBestSolutionTermination<Solution_> createChildThreadTermination(SolverScope<Solution_> solverScope,
ChildThreadType childThreadType) {
return new UnimprovedBestSolutionTermination<>(flatLineDetectionRatio, newCurveDetectionRatio, clock);
return new UnimprovedBestSolutionTermination<>(flatLineDetectionRatio, newCurveDetectionRatio,
minimalExecutionTimeMillis, clock);
}

@Override
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/resources/solver.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@

<xs:element minOccurs="0" name="newCurveDetectionRatio" type="xs:double"/>

<xs:element minOccurs="0" name="minimalExecutionTimeSeconds" type="xs:long"/>

<xs:element maxOccurs="unbounded" minOccurs="0" name="termination" type="tns:terminationConfig"/>

</xs:sequence>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ class UnimprovedBestSolutionTerminationTest {
void testTermination() {
Clock clock = Mockito.mock(Clock.class);
var currentTime = Clock.systemUTC().millis();
var termination = new UnimprovedBestSolutionTermination<TestdataSolution>(0.5, 0.4,
clock);
var termination = new UnimprovedBestSolutionTermination<TestdataSolution>(0.5, 0.4, 10L, clock);
var solverScope = Mockito.mock(SolverScope.class);
var phaseScope = Mockito.mock(LocalSearchPhaseScope.class);
when(phaseScope.getSolverScope()).thenReturn(solverScope);
Expand All @@ -33,16 +32,16 @@ void testTermination() {
// Terminate
termination.currentBest = SimpleScore.of(2);
termination.initialCurvePointMillis = currentTime;
termination.lastImprovementMillis = currentTime + 10000;
when(clock.millis()).thenReturn(currentTime + 21000);
termination.lastImprovementMillis = currentTime + 10_000;
when(clock.millis()).thenReturn(currentTime + 21_000);
assertThat(termination.isPhaseTerminated(phaseScope)).isTrue();

// Don't terminate
termination.terminate = null;
termination.currentBest = SimpleScore.of(2);
termination.initialCurvePointMillis = currentTime;
termination.lastImprovementMillis = currentTime + 10000;
when(clock.millis()).thenReturn(currentTime + 20000);
termination.lastImprovementMillis = currentTime + 10_000;
when(clock.millis()).thenReturn(currentTime + 14_000);
assertThat(termination.isPhaseTerminated(phaseScope)).isFalse();

}
Expand All @@ -51,8 +50,7 @@ void testTermination() {
void testStartNewCurve() {
Clock clock = Mockito.mock(Clock.class);
var currentTime = Clock.systemUTC().millis();
var termination = new UnimprovedBestSolutionTermination<TestdataSolution>(0.5, 0.4,
clock);
var termination = new UnimprovedBestSolutionTermination<TestdataSolution>(0.5, 0.3, 10L, clock);
var solverScope = Mockito.mock(SolverScope.class);
var phaseScope = Mockito.mock(LocalSearchPhaseScope.class);
when(phaseScope.getSolverScope()).thenReturn(solverScope);
Expand All @@ -64,26 +62,26 @@ void testStartNewCurve() {
// Adding a new curve
termination.currentBest = SimpleScore.of(1);
termination.initialCurvePointMillis = currentTime;
termination.lastImprovementMillis = currentTime + 10000;
when(clock.millis()).thenReturn(currentTime + 19000);
termination.lastImprovementMillis = currentTime + 10_000;
when(clock.millis()).thenReturn(currentTime + 14_000);
assertThat(termination.isPhaseTerminated(phaseScope)).isFalse();
assertThat(termination.initialCurvePointMillis).isEqualTo(currentTime + 10000);
assertThat(termination.initialCurvePointMillis).isEqualTo(currentTime + 10_000);

// Not adding a new curve - flat line smaller than the minimum
termination.terminate = null;
termination.currentBest = SimpleScore.of(1);
termination.initialCurvePointMillis = currentTime;
termination.lastImprovementMillis = currentTime + 10000;
when(clock.millis()).thenReturn(currentTime + 11000);
termination.lastImprovementMillis = currentTime + 10_000;
when(clock.millis()).thenReturn(currentTime + 11_000);
assertThat(termination.isPhaseTerminated(phaseScope)).isFalse();
assertThat(termination.initialCurvePointMillis).isEqualTo(currentTime);

// Not adding a new curve - flat line larger than the minimum
termination.terminate = null;
termination.currentBest = SimpleScore.of(1);
termination.initialCurvePointMillis = currentTime;
termination.lastImprovementMillis = currentTime + 10000;
when(clock.millis()).thenReturn(currentTime + 20000);
termination.lastImprovementMillis = currentTime + 10_000;
when(clock.millis()).thenReturn(currentTime + 16_000);
assertThat(termination.isPhaseTerminated(phaseScope)).isFalse();
assertThat(termination.initialCurvePointMillis).isEqualTo(currentTime);
}
Expand All @@ -92,8 +90,7 @@ void testStartNewCurve() {
void testMinimalInterval() {
Clock clock = Mockito.mock(Clock.class);
var currentTime = Clock.systemUTC().millis();
var termination = new UnimprovedBestSolutionTermination<TestdataSolution>(0.5, 0.4,
clock);
var termination = new UnimprovedBestSolutionTermination<TestdataSolution>(0.5, 0.4, 10L, clock);
var solverScope = Mockito.mock(SolverScope.class);
var phaseScope = Mockito.mock(LocalSearchPhaseScope.class);
when(phaseScope.getSolverScope()).thenReturn(solverScope);
Expand All @@ -105,26 +102,28 @@ void testMinimalInterval() {
// Don't terminate
termination.currentBest = SimpleScore.of(2);
termination.initialCurvePointMillis = currentTime;
termination.lastImprovementMillis = currentTime + UnimprovedBestSolutionTermination.MINIMAL_INTERVAL_TIME_MILLIS;
when(clock.millis()).thenReturn(currentTime + UnimprovedBestSolutionTermination.MINIMAL_INTERVAL_TIME_MILLIS * 2 - 1);
termination.lastImprovementMillis = currentTime + 1_000;
when(clock.millis()).thenReturn(currentTime + 9_000);
assertThat(termination.isPhaseTerminated(phaseScope)).isFalse();

// Don't terminate
// Terminate
termination.terminate = null;
termination.currentBest = SimpleScore.of(2);
termination.initialCurvePointMillis = currentTime;
termination.lastImprovementMillis = currentTime + UnimprovedBestSolutionTermination.MINIMAL_INTERVAL_TIME_MILLIS;
when(clock.millis()).thenReturn(currentTime + UnimprovedBestSolutionTermination.MINIMAL_INTERVAL_TIME_MILLIS * 2 + 1);
termination.lastImprovementMillis = currentTime + 1_000;
when(clock.millis()).thenReturn(currentTime + 10_000);
assertThat(termination.isPhaseTerminated(phaseScope)).isTrue();
}

@Test
void invalidTermination() {
assertThatIllegalArgumentException()
.isThrownBy(() -> new UnimprovedBestSolutionTermination<TestdataSolution>(-1.0, 0.0));
.isThrownBy(() -> new UnimprovedBestSolutionTermination<TestdataSolution>(-1.0, 0.0, 1L));
assertThatIllegalArgumentException()
.isThrownBy(() -> new UnimprovedBestSolutionTermination<TestdataSolution>(0.0, -1.0, 1L));
assertThatIllegalArgumentException()
.isThrownBy(() -> new UnimprovedBestSolutionTermination<TestdataSolution>(0.0, -1.0));
.isThrownBy(() -> new UnimprovedBestSolutionTermination<TestdataSolution>(0.0, 1.0, 1L));
assertThatIllegalArgumentException()
.isThrownBy(() -> new UnimprovedBestSolutionTermination<TestdataSolution>(0.0, 1.0));
.isThrownBy(() -> new UnimprovedBestSolutionTermination<TestdataSolution>(1.0, 1.0, 0L));
}
}

0 comments on commit b64318c

Please sign in to comment.