Skip to content

Commit b39544f

Browse files
chore: throw an exception if a custom phase command returns an inconsistent solution
1 parent cd4ddd8 commit b39544f

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
@@ -1555,6 +1555,55 @@ void solveWhenIgnoringInconsistentSolutionsThrowsIfInconsistentEntityPinned() {
15551555
.hasMessageContainingAll("Entity", "b2", "while involved in a dependency loop");
15561556
}
15571557

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

0 commit comments

Comments
 (0)