Skip to content

Commit c0859c2

Browse files
chore: throw an exception if a custom phase command returns an inconsistent solution
1 parent 8d712fc commit c0859c2

2 files changed

Lines changed: 53 additions & 0 deletions

File tree

core/src/main/java/ai/timefold/solver/core/impl/phase/custom/DefaultCustomPhase.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ private void doStep(CustomStepScope<Solution_> stepScope, PhaseCommand<Solution_
8888
() -> phaseTermination.isPhaseTerminated(stepScope.getPhaseScope()));
8989
customPhaseCommand.changeWorkingSolution(commandContext);
9090
calculateWorkingStepScore(stepScope, customPhaseCommand);
91+
if (stepScope.getScore().isInvalid()) {
92+
throw new IllegalStateException("The custom phase command (%s) resulted in an inconsistent solution."
93+
.formatted(customPhaseCommand));
94+
}
9195
var solver = stepScope.getPhaseScope().getSolverScope().getSolver();
9296
solver.getBestSolutionRecaller().processWorkingSolutionDuringStep(stepScope);
9397
}

core/src/test/java/ai/timefold/solver/core/impl/solver/DefaultSolverTest.java

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1549,6 +1549,55 @@ void solveWhenIgnoringInconsistentSolutionsThrowsIfInconsistentEntityPinned() {
15491549
.hasMessageContainingAll("Entity", "b2", "is pinned but is involved in a dependency loop");
15501550
}
15511551

1552+
@Test
1553+
void solveCustomPhaseReturnsInconsistent() {
1554+
// Solver config
1555+
var solverConfig = PlannerTestUtils.buildSolverConfig(
1556+
TestdataDependencyNoInconsistentFieldSolution.class, TestdataDependencyNoInconsistentFieldEntity.class,
1557+
TestdataDependencyNoInconsistentFieldValue.class)
1558+
.withEasyScoreCalculatorClass(null)
1559+
.withConstraintProviderClass(TestdataDependencyNoInconsistentFieldConstraintProvider.class)
1560+
.withPhases(new CustomPhaseConfig()
1561+
.withCustomPhaseCommands(command -> {
1562+
var solution = (TestdataDependencyNoInconsistentFieldSolution) command.getWorkingSolution();
1563+
var sE1 = solution.getEntities().getFirst();
1564+
var sB1 = solution.getValues().get(2);
1565+
var sB2 = solution.getValues().get(3);
1566+
var metaModel = command.getSolutionMetaModel()
1567+
.genuineEntity(TestdataDependencyNoInconsistentFieldEntity.class)
1568+
.listVariable("values", TestdataDependencyNoInconsistentFieldValue.class);
1569+
command.execute(Moves.compose(
1570+
Moves.assign(metaModel, sB2, sE1, 0),
1571+
Moves.assign(metaModel, sB1, sE1, 1)));
1572+
}));
1573+
1574+
var e1 = new TestdataDependencyNoInconsistentFieldEntity("a");
1575+
var e2 = new TestdataDependencyNoInconsistentFieldEntity("b");
1576+
1577+
var a1 = new TestdataDependencyNoInconsistentFieldValue("a1");
1578+
var a2 = new TestdataDependencyNoInconsistentFieldValue("a2");
1579+
var b1 = new TestdataDependencyNoInconsistentFieldValue("b1");
1580+
var b2 = new TestdataDependencyNoInconsistentFieldValue("b2");
1581+
1582+
a2.setDependencies(List.of(a1));
1583+
b2.setDependencies(List.of(b1));
1584+
1585+
e1.setValues(List.of(b2, b1));
1586+
e2.setValues(List.of(a1, a2));
1587+
1588+
var entities = List.of(e1, e2);
1589+
var values = List.of(a1, a2, b1, b2);
1590+
1591+
var problem = new TestdataDependencyNoInconsistentFieldSolution();
1592+
1593+
problem.setEntities(entities);
1594+
problem.setValues(values);
1595+
1596+
assertThatCode(() -> PlannerTestUtils.solve(solverConfig, problem, false))
1597+
.isInstanceOf(IllegalStateException.class)
1598+
.hasMessageContainingAll("The custom phase command", "resulted in an inconsistent solution.");
1599+
}
1600+
15521601
@Test
15531602
void solveIgnoreInconsistent() {
15541603
// Solver config

0 commit comments

Comments
 (0)