Skip to content

Commit 24b3240

Browse files
zepfredtriceo
authored andcommitted
fix: continue the warmup when one configuration fails
1 parent dee8cff commit 24b3240

File tree

12 files changed

+203
-46
lines changed

12 files changed

+203
-46
lines changed

benchmark/src/main/java/ai/timefold/solver/benchmark/impl/DefaultPlannerBenchmark.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ private void warmUp(Map<Future<SubSingleBenchmarkRunner>, SubSingleBenchmarkRunn
225225
if (firstFailureSubSingleBenchmarkRunner == null) {
226226
firstFailureSubSingleBenchmarkRunner = subSingleBenchmarkRunner;
227227
}
228-
break; // Exit the warm-up loop in case of a failure.
228+
continue; // continue to the next task
229229
}
230230

231231
SolverBenchmarkResult solverBenchmarkResult = subSingleBenchmarkRunner.getSubSingleBenchmarkResult()

quarkus-integration/quarkus-benchmark/integration-test/src/main/java/ai/timefold/solver/quarkus/benchmark/it/TimefoldBenchmarkTestResource.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package ai.timefold.solver.quarkus.benchmark.it;
22

3-
import java.util.Arrays;
3+
import java.util.List;
44

55
import jakarta.inject.Inject;
66
import jakarta.ws.rs.POST;
@@ -9,7 +9,10 @@
99
import jakarta.ws.rs.core.MediaType;
1010

1111
import ai.timefold.solver.benchmark.api.PlannerBenchmark;
12+
import ai.timefold.solver.benchmark.api.PlannerBenchmarkException;
1213
import ai.timefold.solver.benchmark.api.PlannerBenchmarkFactory;
14+
import ai.timefold.solver.benchmark.impl.DefaultPlannerBenchmark;
15+
import ai.timefold.solver.quarkus.benchmark.it.domain.TestdataListValueShadowEntity;
1316
import ai.timefold.solver.quarkus.benchmark.it.domain.TestdataStringLengthShadowEntity;
1417
import ai.timefold.solver.quarkus.benchmark.it.domain.TestdataStringLengthShadowSolution;
1518

@@ -28,11 +31,17 @@ public TimefoldBenchmarkTestResource(PlannerBenchmarkFactory benchmarkFactory) {
2831
@Produces(MediaType.TEXT_PLAIN)
2932
public String benchmark() {
3033
TestdataStringLengthShadowSolution planningProblem = new TestdataStringLengthShadowSolution();
31-
planningProblem.setEntityList(Arrays.asList(
32-
new TestdataStringLengthShadowEntity(),
33-
new TestdataStringLengthShadowEntity()));
34-
planningProblem.setValueList(Arrays.asList("a", "bb", "ccc"));
34+
planningProblem.setEntityList(List.of(
35+
new TestdataStringLengthShadowEntity(1L),
36+
new TestdataStringLengthShadowEntity(2L)));
37+
planningProblem.setValueList(List.of(new TestdataListValueShadowEntity("a"), new TestdataListValueShadowEntity("bb"),
38+
new TestdataListValueShadowEntity("ccc")));
3539
PlannerBenchmark benchmark = benchmarkFactory.buildPlannerBenchmark(planningProblem);
36-
return benchmark.benchmark().toPath().toAbsolutePath().toString();
40+
try {
41+
return benchmark.benchmark().toPath().toAbsolutePath().toString();
42+
} catch (PlannerBenchmarkException e) {
43+
// ignore the exception
44+
return ((DefaultPlannerBenchmark) benchmark).getBenchmarkDirectory().getAbsolutePath();
45+
}
3746
}
3847
}

quarkus-integration/quarkus-benchmark/integration-test/src/main/java/ai/timefold/solver/quarkus/benchmark/it/domain/StringLengthVariableListener.java

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,47 +4,51 @@
44
import ai.timefold.solver.core.api.score.director.ScoreDirector;
55

66
public class StringLengthVariableListener
7-
implements VariableListener<TestdataStringLengthShadowSolution, TestdataStringLengthShadowEntity> {
7+
implements VariableListener<TestdataStringLengthShadowSolution, TestdataListValueShadowEntity> {
88

99
@Override
1010
public void beforeEntityAdded(ScoreDirector<TestdataStringLengthShadowSolution> scoreDirector,
11-
TestdataStringLengthShadowEntity entity) {
11+
TestdataListValueShadowEntity entity) {
1212
/* Nothing to do */
1313
}
1414

1515
@Override
1616
public void afterEntityAdded(ScoreDirector<TestdataStringLengthShadowSolution> scoreDirector,
17-
TestdataStringLengthShadowEntity entity) {
17+
TestdataListValueShadowEntity entity) {
1818
/* Nothing to do */
1919
}
2020

2121
@Override
2222
public void beforeVariableChanged(ScoreDirector<TestdataStringLengthShadowSolution> scoreDirector,
23-
TestdataStringLengthShadowEntity entity) {
23+
TestdataListValueShadowEntity entity) {
2424
/* Nothing to do */
2525
}
2626

2727
@Override
2828
public void afterVariableChanged(ScoreDirector<TestdataStringLengthShadowSolution> scoreDirector,
29-
TestdataStringLengthShadowEntity entity) {
29+
TestdataListValueShadowEntity entity) {
3030
int oldLength = (entity.getLength() != null) ? entity.getLength() : 0;
31-
int newLength = getLength(entity.getValue());
31+
int newLength =
32+
entity.getEntity() != null
33+
? entity.getEntity().getValues().stream().map(TestdataListValueShadowEntity::getValue)
34+
.mapToInt(StringLengthVariableListener::getLength).sum()
35+
: 0;
3236
if (oldLength != newLength) {
3337
scoreDirector.beforeVariableChanged(entity, "length");
34-
entity.setLength(getLength(entity.getValue()));
38+
entity.setLength(newLength);
3539
scoreDirector.afterVariableChanged(entity, "length");
3640
}
3741
}
3842

3943
@Override
4044
public void beforeEntityRemoved(ScoreDirector<TestdataStringLengthShadowSolution> scoreDirector,
41-
TestdataStringLengthShadowEntity entity) {
45+
TestdataListValueShadowEntity entity) {
4246
/* Nothing to do */
4347
}
4448

4549
@Override
4650
public void afterEntityRemoved(ScoreDirector<TestdataStringLengthShadowSolution> scoreDirector,
47-
TestdataStringLengthShadowEntity entity) {
51+
TestdataListValueShadowEntity entity) {
4852
/* Nothing to do */
4953
}
5054

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package ai.timefold.solver.quarkus.benchmark.it.domain;
2+
3+
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
4+
import ai.timefold.solver.core.api.domain.variable.InverseRelationShadowVariable;
5+
import ai.timefold.solver.core.api.domain.variable.ShadowVariable;
6+
7+
@PlanningEntity
8+
public class TestdataListValueShadowEntity {
9+
10+
private String value;
11+
12+
@InverseRelationShadowVariable(sourceVariableName = "values")
13+
private TestdataStringLengthShadowEntity entity;
14+
15+
@ShadowVariable(variableListenerClass = StringLengthVariableListener.class, sourceVariableName = "entity")
16+
private Integer length;
17+
18+
public TestdataListValueShadowEntity() {
19+
}
20+
21+
public TestdataListValueShadowEntity(String value) {
22+
this.value = value;
23+
}
24+
25+
public String getValue() {
26+
return value;
27+
}
28+
29+
public void setValue(String value) {
30+
this.value = value;
31+
}
32+
33+
public TestdataStringLengthShadowEntity getEntity() {
34+
return entity;
35+
}
36+
37+
public void setEntity(TestdataStringLengthShadowEntity entity) {
38+
this.entity = entity;
39+
}
40+
41+
public Integer getLength() {
42+
return length;
43+
}
44+
45+
public void setLength(Integer length) {
46+
this.length = length;
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,43 @@
11
package ai.timefold.solver.quarkus.benchmark.it.domain;
22

3+
import java.util.ArrayList;
4+
import java.util.List;
5+
36
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
4-
import ai.timefold.solver.core.api.domain.variable.PlanningVariable;
5-
import ai.timefold.solver.core.api.domain.variable.ShadowVariable;
7+
import ai.timefold.solver.core.api.domain.lookup.PlanningId;
8+
import ai.timefold.solver.core.api.domain.variable.PlanningListVariable;
69

710
@PlanningEntity
811
public class TestdataStringLengthShadowEntity {
912

10-
@PlanningVariable(valueRangeProviderRefs = "valueRange")
11-
private String value;
13+
@PlanningId
14+
private Long id;
15+
16+
@PlanningListVariable
17+
private List<TestdataListValueShadowEntity> values;
1218

13-
@ShadowVariable(variableListenerClass = StringLengthVariableListener.class,
14-
sourceEntityClass = TestdataStringLengthShadowEntity.class, sourceVariableName = "value")
15-
private Integer length;
19+
public TestdataStringLengthShadowEntity() {
20+
}
21+
22+
public TestdataStringLengthShadowEntity(Long id) {
23+
this.id = id;
24+
this.values = new ArrayList<>();
25+
}
1626

1727
// ************************************************************************
1828
// Getters/setters
1929
// ************************************************************************
2030

21-
public String getValue() {
22-
return value;
23-
}
24-
25-
public void setValue(String value) {
26-
this.value = value;
31+
public Long getId() {
32+
return id;
2733
}
2834

29-
public Integer getLength() {
30-
return length;
35+
public List<TestdataListValueShadowEntity> getValues() {
36+
return values;
3137
}
3238

33-
public void setLength(Integer length) {
34-
this.length = length;
39+
public void setValues(List<TestdataListValueShadowEntity> values) {
40+
this.values = values;
3541
}
3642

3743
}

quarkus-integration/quarkus-benchmark/integration-test/src/main/java/ai/timefold/solver/quarkus/benchmark/it/domain/TestdataStringLengthShadowSolution.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@
55
import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty;
66
import ai.timefold.solver.core.api.domain.solution.PlanningScore;
77
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
8+
import ai.timefold.solver.core.api.domain.solution.ProblemFactCollectionProperty;
89
import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider;
910
import ai.timefold.solver.core.api.score.buildin.hardsoft.HardSoftScore;
1011

1112
@PlanningSolution
1213
public class TestdataStringLengthShadowSolution {
1314

14-
@ValueRangeProvider(id = "valueRange")
15-
private List<String> valueList;
15+
16+
@ProblemFactCollectionProperty
17+
@ValueRangeProvider
18+
private List<TestdataListValueShadowEntity> valueList;
19+
1620
@PlanningEntityCollectionProperty
1721
private List<TestdataStringLengthShadowEntity> entityList;
1822

@@ -23,11 +27,11 @@ public class TestdataStringLengthShadowSolution {
2327
// Getters/setters
2428
// ************************************************************************
2529

26-
public List<String> getValueList() {
30+
public List<TestdataListValueShadowEntity> getValueList() {
2731
return valueList;
2832
}
2933

30-
public void setValueList(List<String> valueList) {
34+
public void setValueList(List<TestdataListValueShadowEntity> valueList) {
3135
this.valueList = valueList;
3236
}
3337

quarkus-integration/quarkus-benchmark/integration-test/src/main/java/ai/timefold/solver/quarkus/benchmark/it/solver/TestdataStringLengthConstraintProvider.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,20 @@
44
import ai.timefold.solver.core.api.score.stream.Constraint;
55
import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
66
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
7-
import ai.timefold.solver.core.api.score.stream.Joiners;
7+
import ai.timefold.solver.quarkus.benchmark.it.domain.TestdataListValueShadowEntity;
88
import ai.timefold.solver.quarkus.benchmark.it.domain.TestdataStringLengthShadowEntity;
99

1010
public class TestdataStringLengthConstraintProvider implements ConstraintProvider {
1111

1212
@Override
1313
public Constraint[] defineConstraints(ConstraintFactory factory) {
1414
return new Constraint[] {
15-
factory.forEach(TestdataStringLengthShadowEntity.class)
16-
.join(TestdataStringLengthShadowEntity.class, Joiners.equal(TestdataStringLengthShadowEntity::getValue))
17-
.filter((a, b) -> a != b)
15+
factory.forEachUniquePair(TestdataStringLengthShadowEntity.class)
16+
.filter((a, b) -> a.getValues().stream().anyMatch(v -> b.getValues().contains(v)))
1817
.penalize(HardSoftScore.ONE_HARD)
1918
.asConstraint("Don't assign 2 entities the same value."),
20-
factory.forEach(TestdataStringLengthShadowEntity.class)
21-
.reward(HardSoftScore.ONE_SOFT, TestdataStringLengthShadowEntity::getLength)
19+
factory.forEach(TestdataListValueShadowEntity.class)
20+
.reward(HardSoftScore.ONE_SOFT, a -> a.getLength())
2221
.asConstraint("Maximize value length")
2322
};
2423
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
# The solver runs to a known best score to avoid a HTTP timeout in this simple implementation.
2-
quarkus.timefold.benchmark.solver.termination.best-score-limit=0hard/5soft
2+
quarkus.timefold.benchmark.solver.termination.best-score-limit=0hard/10soft
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package ai.timefold.solver.quarkus.benchmark.it;
2+
3+
import static org.apache.http.params.CoreConnectionPNames.SO_TIMEOUT;
4+
import static org.assertj.core.api.Assertions.assertThat;
5+
6+
import java.nio.file.Files;
7+
import java.nio.file.Path;
8+
import java.util.HashMap;
9+
import java.util.Map;
10+
11+
import org.junit.jupiter.api.Test;
12+
13+
import io.quarkus.test.junit.QuarkusTest;
14+
import io.quarkus.test.junit.QuarkusTestProfile;
15+
import io.quarkus.test.junit.TestProfile;
16+
import io.restassured.RestAssured;
17+
import io.restassured.config.HttpClientConfig;
18+
import io.restassured.config.RestAssuredConfig;
19+
import io.restassured.path.xml.XmlPath;
20+
21+
/**
22+
* Test the benchmark for typical solver configs (blueprint).
23+
*/
24+
@QuarkusTest
25+
@TestProfile(TimefoldBenchmarkBlueprintTest.BlueprintTestProfile.class)
26+
class TimefoldBenchmarkBlueprintTest {
27+
28+
@Test
29+
void benchmark() throws Exception {
30+
RestAssuredConfig timeoutConfig = RestAssured.config()
31+
.httpClient(HttpClientConfig.httpClientConfig()
32+
.setParam(SO_TIMEOUT, 10000));
33+
String benchmarkResultDirectory = RestAssured
34+
.given()
35+
.config(timeoutConfig)
36+
.header("Content-Type", "text/plain;charset=UTF-8")
37+
.when()
38+
.post("/timefold/test/benchmark")
39+
.body().asString();
40+
assertThat(benchmarkResultDirectory).isNotNull();
41+
Path benchmarkResultDirectoryPath = Path.of(benchmarkResultDirectory);
42+
assertThat(Files.isDirectory(benchmarkResultDirectoryPath)).isTrue();
43+
Path benchmarkResultPath = Files.walk(benchmarkResultDirectoryPath, 2)
44+
.filter(path -> path.endsWith("plannerBenchmarkResult.xml")).findFirst().orElseThrow();
45+
assertThat(Files.isRegularFile(benchmarkResultPath)).isTrue();
46+
XmlPath xmlPath = XmlPath.from(benchmarkResultPath.toFile());
47+
assertThat(xmlPath
48+
.getList(
49+
"plannerBenchmarkResult.solverBenchmarkResult.singleBenchmarkResult.subSingleBenchmarkResult.succeeded")
50+
.stream().anyMatch(node -> node.equals("true")))
51+
.isTrue();
52+
}
53+
54+
public static class BlueprintTestProfile implements QuarkusTestProfile {
55+
56+
@Override
57+
public Map<String, String> getConfigOverrides() {
58+
Map<String, String> overrides = new HashMap<>(QuarkusTestProfile.super.getConfigOverrides());
59+
overrides.put("quarkus.timefold.benchmark.solver-benchmark-config-xml", "blueprintSolverBenchmarkConfig.xml");
60+
return overrides;
61+
}
62+
}
63+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,6 @@
99
*/
1010
@QuarkusIntegrationTest
1111
@Disabled("timefold-solver-quarkus-benchmark cannot compile to native")
12-
public class TimefoldBenchmarkTestResourceIT extends TimefoldBenchmarkTestResourceTest {
12+
public class TimefoldBenchmarkResourceIT extends TimefoldBenchmarkResourceTest {
1313

1414
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
*/
1818

1919
@QuarkusTest
20-
class TimefoldBenchmarkTestResourceTest {
20+
class TimefoldBenchmarkResourceTest {
2121

2222
@Test
2323
@Timeout(600)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<plannerBenchmark xmlns="https://timefold.ai/xsd/benchmark"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="https://timefold.ai/xsd/benchmark https://timefold.ai/xsd/benchmark/benchmark.xsd">
5+
<parallelBenchmarkCount>AUTO</parallelBenchmarkCount>
6+
7+
<inheritedSolverBenchmark>
8+
<solver>
9+
<solutionClass>ai.timefold.solver.quarkus.benchmark.it.domain.TestdataStringLengthShadowSolution</solutionClass>
10+
<entityClass>ai.timefold.solver.quarkus.benchmark.it.domain.TestdataStringLengthShadowEntity</entityClass>
11+
<entityClass>ai.timefold.solver.quarkus.benchmark.it.domain.TestdataListValueShadowEntity</entityClass>
12+
<scoreDirectorFactory>
13+
<constraintProviderClass>ai.timefold.solver.quarkus.benchmark.it.solver.TestdataStringLengthConstraintProvider</constraintProviderClass>
14+
</scoreDirectorFactory>
15+
<termination>
16+
<secondsSpentLimit>5</secondsSpentLimit>
17+
</termination>
18+
</solver>
19+
</inheritedSolverBenchmark>
20+
21+
<solverBenchmarkBluePrint>
22+
<solverBenchmarkBluePrintType>EVERY_CONSTRUCTION_HEURISTIC_TYPE_WITH_EVERY_LOCAL_SEARCH_TYPE</solverBenchmarkBluePrintType>
23+
</solverBenchmarkBluePrint>
24+
</plannerBenchmark>

0 commit comments

Comments
 (0)