Skip to content

Commit cf87aa7

Browse files
authored
chore: make init score inaccessible to users (#1501)
The init score exposed by the Score interface is now always zero. To find out when the solver has finished initializing a solution, users are encouraged to catch the initialization event thrown by the solver manager. Internally, the init score remains. A new type was created (InnerScore), which now carries the information that init score used to carry. All code which requires to know the initialization status of the solution now has to use InnerScore. Code which does not care about this information can continue using Score.
1 parent 966ea64 commit cf87aa7

File tree

341 files changed

+4148
-5717
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

341 files changed

+4148
-5717
lines changed

.github/workflows/pull_request_secure.yml

+9-1
Original file line numberDiff line numberDiff line change
@@ -371,13 +371,21 @@ jobs:
371371
run: mvn -B clean install -Prun-code-coverage
372372

373373
- name: Get JaCoCo Agent
374-
run: mvn org.apache.maven.plugins:maven-dependency-plugin:2.8:get -Dartifact=org.jacoco:org.jacoco.agent:0.8.11:jar:runtime -Ddest=target/jacocoagent.jar
374+
run: mvn org.apache.maven.plugins:maven-dependency-plugin:3.8.1:copy -Dartifact=org.jacoco:org.jacoco.agent:0.8.13:jar:runtime -DoutputDirectory=target/jacocoagent.jar
375375

376376
- name: Run tox to measure timefold solver python code coverage from Python tests
377+
# Sometimes crashes at the very end when using JaCoCo.
378+
# This crash happens after everything is processed and stored, and therefore can be safely ignored.
379+
# Ignore the crash to not ruin the long-running build.
380+
continue-on-error: true
377381
run: python -m tox -- --cov=timefold --cov-report=xml:target/coverage.xml --cov-config=tox.ini --cov-branch --cov-append --jacoco-agent=./target/jacocoagent.jar
378382

379383
- name: Run tox to measure jpyinterpreter code coverage from Python tests
380384
working-directory: ./python/jpyinterpreter
385+
# Sometimes crashes at the very end when using JaCoCo.
386+
# This crash happens after everything is processed and stored, and therefore can be safely ignored.
387+
# Ignore the crash to not ruin the long-running build.
388+
continue-on-error: true
381389
run: python -m tox -- --cov=jpyinterpreter --cov-report=xml:target/coverage.xml --cov-config=tox.ini --cov-branch --cov-append --jacoco-agent=../../target/jacocoagent.jar --jacoco-output=../../target/jacoco.exec
382390

383391
- name: Run analysis

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

+18-26
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
package ai.timefold.solver.benchmark.impl;
22

33
import java.util.HashMap;
4-
import java.util.Map;
54
import java.util.UUID;
65
import java.util.concurrent.Callable;
76

8-
import ai.timefold.solver.benchmark.impl.result.ProblemBenchmarkResult;
9-
import ai.timefold.solver.benchmark.impl.result.SingleBenchmarkResult;
107
import ai.timefold.solver.benchmark.impl.result.SubSingleBenchmarkResult;
118
import ai.timefold.solver.benchmark.impl.statistic.StatisticRegistry;
12-
import ai.timefold.solver.benchmark.impl.statistic.SubSingleStatistic;
13-
import ai.timefold.solver.core.api.score.ScoreExplanation;
149
import ai.timefold.solver.core.api.solver.SolutionManager;
1510
import ai.timefold.solver.core.api.solver.SolutionUpdatePolicy;
1611
import ai.timefold.solver.core.config.solver.SolverConfig;
@@ -67,71 +62,68 @@ public void setFailureThrowable(Throwable failureThrowable) {
6762
@Override
6863
public SubSingleBenchmarkRunner<Solution_> call() {
6964
MDC.put(NAME_MDC, subSingleBenchmarkResult.getName());
70-
Runtime runtime = Runtime.getRuntime();
71-
SingleBenchmarkResult singleBenchmarkResult = subSingleBenchmarkResult.getSingleBenchmarkResult();
72-
ProblemBenchmarkResult<Solution_> problemBenchmarkResult = singleBenchmarkResult
73-
.getProblemBenchmarkResult();
74-
Solution_ problem = problemBenchmarkResult.readProblem();
65+
var runtime = Runtime.getRuntime();
66+
var singleBenchmarkResult = subSingleBenchmarkResult.getSingleBenchmarkResult();
67+
var problemBenchmarkResult = singleBenchmarkResult.getProblemBenchmarkResult();
68+
var problem = (Solution_) problemBenchmarkResult.readProblem();
7569
if (!problemBenchmarkResult.getPlannerBenchmarkResult().hasMultipleParallelBenchmarks()) {
7670
runtime.gc();
7771
subSingleBenchmarkResult.setUsedMemoryAfterInputSolution(runtime.totalMemory() - runtime.freeMemory());
7872
}
7973
LOGGER.trace("Benchmark problem has been read for subSingleBenchmarkResult ({}).",
8074
subSingleBenchmarkResult);
8175

82-
SolverConfig solverConfig = singleBenchmarkResult.getSolverBenchmarkResult()
76+
var solverConfig = singleBenchmarkResult.getSolverBenchmarkResult()
8377
.getSolverConfig();
8478
if (singleBenchmarkResult.getSubSingleCount() > 1) {
8579
solverConfig = new SolverConfig(solverConfig);
8680
solverConfig.offerRandomSeedFromSubSingleIndex(subSingleBenchmarkResult.getSubSingleBenchmarkIndex());
8781
}
88-
Map<String, String> subSingleBenchmarkTagMap = new HashMap<>();
89-
String runId = UUID.randomUUID().toString();
82+
var subSingleBenchmarkTagMap = new HashMap<String, String>();
83+
var runId = UUID.randomUUID().toString();
9084
subSingleBenchmarkTagMap.put("timefold.benchmark.run", runId);
9185
solverConfig = new SolverConfig(solverConfig);
9286
randomSeed = solverConfig.getRandomSeed();
9387

9488
// Defensive copy of solverConfig for every SingleBenchmarkResult to reset Random, tabu lists, ...
95-
DefaultSolverFactory<Solution_> solverFactory = new DefaultSolverFactory<>(new SolverConfig(solverConfig));
89+
var solverFactory = new DefaultSolverFactory<Solution_>(new SolverConfig(solverConfig));
9690

9791
// Register metrics
98-
StatisticRegistry<Solution_> statisticRegistry =
99-
new StatisticRegistry<>(solverFactory.getSolutionDescriptor().getScoreDefinition());
92+
var statisticRegistry = new StatisticRegistry<Solution_>(solverFactory.getSolutionDescriptor().getScoreDefinition());
10093
Metrics.addRegistry(statisticRegistry);
101-
Tags runTag = Tags.of("timefold.benchmark.run", runId);
94+
var runTag = Tags.of("timefold.benchmark.run", runId);
10295
subSingleBenchmarkResult.getEffectiveSubSingleStatisticMap().forEach((statisticType, subSingleStatistic) -> {
10396
subSingleStatistic.open(statisticRegistry, runTag);
10497
subSingleStatistic.initPointList();
10598
});
10699

107-
DefaultSolver<Solution_> solver = (DefaultSolver<Solution_>) solverFactory.buildSolver();
100+
var solver = (DefaultSolver<Solution_>) solverFactory.buildSolver();
108101
solver.setMonitorTagMap(subSingleBenchmarkTagMap);
109102
solver.addPhaseLifecycleListener(statisticRegistry);
110-
Solution_ solution = solver.solve(problem);
103+
var solution = solver.solve(problem);
111104

112105
solver.removePhaseLifecycleListener(statisticRegistry);
113106
Metrics.removeRegistry(statisticRegistry);
114-
long timeMillisSpent = solver.getTimeMillisSpent();
107+
var timeMillisSpent = solver.getTimeMillisSpent();
115108

116-
for (SubSingleStatistic<Solution_, ?> subSingleStatistic : subSingleBenchmarkResult.getEffectiveSubSingleStatisticMap()
117-
.values()) {
109+
for (var subSingleStatistic : subSingleBenchmarkResult.getEffectiveSubSingleStatisticMap().values()) {
118110
subSingleStatistic.close(statisticRegistry, runTag);
119111
subSingleStatistic.hibernatePointList();
120112
}
121113
if (!warmUp) {
122114
var solverScope = solver.getSolverScope();
123115
var solutionDescriptor = solverScope.getSolutionDescriptor();
124116
problemBenchmarkResult.registerProblemSizeStatistics(solverScope.getProblemSizeStatistics());
125-
subSingleBenchmarkResult.setScore(solutionDescriptor.getScore(solution));
117+
subSingleBenchmarkResult.setScore(solutionDescriptor.getScore(solution), solverScope.isBestSolutionInitialized());
126118
subSingleBenchmarkResult.setTimeMillisSpent(timeMillisSpent);
127119
subSingleBenchmarkResult.setScoreCalculationCount(solverScope.getScoreCalculationCount());
128120
subSingleBenchmarkResult.setMoveEvaluationCount(solverScope.getMoveEvaluationCount());
129121

130-
SolutionManager<Solution_, ?> solutionManager = SolutionManager.create(solverFactory);
131-
boolean isConstraintMatchEnabled = solver.getSolverScope().getScoreDirector().getConstraintMatchPolicy()
122+
var solutionManager = SolutionManager.create(solverFactory);
123+
var isConstraintMatchEnabled = solver.getSolverScope().getScoreDirector().getConstraintMatchPolicy()
132124
.isEnabled();
133125
if (isConstraintMatchEnabled) { // Easy calculator fails otherwise.
134-
ScoreExplanation<Solution_, ?> scoreExplanation =
126+
var scoreExplanation =
135127
solutionManager.explain(solution, SolutionUpdatePolicy.NO_UPDATE);
136128
subSingleBenchmarkResult.setScoreExplanationSummary(scoreExplanation.getSummary());
137129
}

benchmark/src/main/java/ai/timefold/solver/benchmark/impl/ranking/ScoreSubSingleBenchmarkRankingComparator.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@
33
import java.util.Comparator;
44

55
import ai.timefold.solver.benchmark.impl.result.SubSingleBenchmarkResult;
6-
import ai.timefold.solver.core.impl.score.definition.ScoreDefinition;
76

87
public class ScoreSubSingleBenchmarkRankingComparator implements Comparator<SubSingleBenchmarkResult> {
98

109
@Override
1110
public int compare(SubSingleBenchmarkResult a, SubSingleBenchmarkResult b) {
12-
ScoreDefinition<?> aScoreDefinition = a.getSingleBenchmarkResult().getSolverBenchmarkResult().getScoreDefinition();
11+
var aScoreDefinition = a.getSingleBenchmarkResult().getSolverBenchmarkResult().getScoreDefinition();
1312
return Comparator
1413
// Reverse, less is better (redundant: failed benchmarks don't get ranked at all)
1514
.comparing(SubSingleBenchmarkResult::hasAnyFailure, Comparator.reverseOrder())
15+
.thenComparing(SubSingleBenchmarkResult::isInitialized)
1616
.thenComparing(SubSingleBenchmarkResult::getScore,
1717
new ResilientScoreComparator(aScoreDefinition))
1818
.compare(a, b);

benchmark/src/main/java/ai/timefold/solver/benchmark/impl/ranking/TotalScoreSingleBenchmarkRankingComparator.java

+3-4
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,17 @@
33
import java.util.Comparator;
44

55
import ai.timefold.solver.benchmark.impl.result.SingleBenchmarkResult;
6-
import ai.timefold.solver.core.impl.score.definition.ScoreDefinition;
76

87
public class TotalScoreSingleBenchmarkRankingComparator implements Comparator<SingleBenchmarkResult> {
98

109
@Override
1110
public int compare(SingleBenchmarkResult a, SingleBenchmarkResult b) {
12-
ScoreDefinition aScoreDefinition = a.getSolverBenchmarkResult().getScoreDefinition();
11+
var aScoreDefinition = a.getSolverBenchmarkResult().getScoreDefinition();
1312
return Comparator
1413
// Reverse, less is better (redundant: failed benchmarks don't get ranked at all)
1514
.comparing(SingleBenchmarkResult::hasAnyFailure, Comparator.reverseOrder())
16-
.thenComparing(SingleBenchmarkResult::getTotalScore,
17-
new ResilientScoreComparator(aScoreDefinition))
15+
.thenComparing(SingleBenchmarkResult::isInitialized)
16+
.thenComparing(SingleBenchmarkResult::getTotalScore, new ResilientScoreComparator(aScoreDefinition))
1817
.compare(a, b);
1918
}
2019

benchmark/src/main/java/ai/timefold/solver/benchmark/impl/result/PlannerBenchmarkResult.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ public void accumulateResults(BenchmarkReport benchmarkReport) {
314314
determineSolverRanking(benchmarkReport);
315315
}
316316

317-
private void determineTotalsAndAverages() {
317+
private <Score_ extends Score<Score_>> void determineTotalsAndAverages() {
318318
failureCount = 0;
319319
long totalProblemScale = 0L;
320320
int problemScaleCount = 0;
@@ -327,7 +327,7 @@ private void determineTotalsAndAverages() {
327327
failureCount += problemBenchmarkResult.getFailureCount();
328328
}
329329
averageProblemScale = problemScaleCount == 0 ? null : totalProblemScale / problemScaleCount;
330-
Score totalScore = null;
330+
Score_ totalScore = null;
331331
int solverBenchmarkCount = 0;
332332
boolean firstSolverBenchmarkResult = true;
333333
for (SolverBenchmarkResult solverBenchmarkResult : solverBenchmarkResultList) {
@@ -339,9 +339,9 @@ private void determineTotalsAndAverages() {
339339
environmentMode = null;
340340
}
341341

342-
Score score = solverBenchmarkResult.getAverageScore();
342+
Score_ score = (Score_) solverBenchmarkResult.getAverageScore();
343343
if (score != null) {
344-
ScoreDefinition scoreDefinition = solverBenchmarkResult.getScoreDefinition();
344+
ScoreDefinition<Score_> scoreDefinition = solverBenchmarkResult.getScoreDefinition();
345345
if (totalScore != null && !scoreDefinition.isCompatibleArithmeticArgument(totalScore)) {
346346
// Mixing different use cases with different score definitions.
347347
totalScore = null;

0 commit comments

Comments
 (0)