Skip to content

Commit 855d722

Browse files
chore: use lack of @ShadowVariablesInconsistent to toggle feature
1 parent 1ef7e2c commit 855d722

11 files changed

Lines changed: 350 additions & 51 deletions

File tree

core/src/main/java/ai/timefold/solver/core/config/solver/PreviewFeature.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ public enum PreviewFeature {
2121

2222
DIVERSIFIED_LATE_ACCEPTANCE,
2323
PLANNING_SOLUTION_DIFF,
24-
IGNORE_INCONSISTENT_SOLUTIONS,
2524
/**
2625
* Unlike other preview features, Neighborhoods are an active research project.
2726
* It is intended to simplify the creation of custom moves, eventually replacing move selectors.

core/src/main/java/ai/timefold/solver/core/impl/domain/solution/descriptor/SolutionDescriptor.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
import ai.timefold.solver.core.impl.domain.solution.cloner.gizmo.GizmoSolutionCloner;
6060
import ai.timefold.solver.core.impl.domain.solution.cloner.gizmo.GizmoSolutionClonerFactory;
6161
import ai.timefold.solver.core.impl.domain.variable.declarative.DeclarativeShadowVariableDescriptor;
62+
import ai.timefold.solver.core.impl.domain.variable.declarative.ShadowVariablesInconsistentVariableDescriptor;
6263
import ai.timefold.solver.core.impl.domain.variable.descriptor.BasicVariableDescriptor;
6364
import ai.timefold.solver.core.impl.domain.variable.descriptor.GenuineVariableDescriptor;
6465
import ai.timefold.solver.core.impl.domain.variable.descriptor.ListVariableDescriptor;
@@ -1037,6 +1038,13 @@ public List<DeclarativeShadowVariableDescriptor<Solution_>> getDeclarativeShadow
10371038
return declarativeShadowVariableDescriptorList;
10381039
}
10391040

1041+
public boolean hasAnyShadowVariablesInconsistentMember() {
1042+
return entityDescriptorMap.values().stream()
1043+
.flatMap(entityDescriptor -> entityDescriptor.getShadowVariableDescriptors().stream())
1044+
.anyMatch(
1045+
shadowVariableDescriptor -> shadowVariableDescriptor instanceof ShadowVariablesInconsistentVariableDescriptor<Solution_>);
1046+
}
1047+
10401048
public Stream<Object> extractAllEntitiesStream(Solution_ solution) {
10411049
var stream = Stream.empty();
10421050
for (var memberAccessor : entityMemberAccessorMap.values()) {
@@ -1102,5 +1110,4 @@ public <Score_ extends Score<Score_>> void setScore(Solution_ solution, Score_ s
11021110
public String toString() {
11031111
return "%s(%s)".formatted(getClass().getSimpleName(), solutionClass.getName());
11041112
}
1105-
11061113
}

core/src/main/java/ai/timefold/solver/core/impl/domain/variable/declarative/AffectedEntitiesUpdater.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ final class AffectedEntitiesUpdater<Solution_>
2525
private final PriorityQueue<BaseTopologicalOrderGraph.NodeTopologicalOrder> changeQueue;
2626
private boolean consistencyProcessed;
2727

28-
2928
AffectedEntitiesUpdater(BaseTopologicalOrderGraph graph, List<GraphNode<Solution_>> nodeList,
3029
BaseTopologicalOrderGraph.NodeTopologicalOrder[] nodeTopologicalOrders,
3130
Function<Object, List<GraphNode<Solution_>>> entityToContainingNode,

core/src/main/java/ai/timefold/solver/core/impl/solver/DefaultSolverFactory.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,7 @@ public Solver<Solution_> buildSolver(SolverConfigOverride configOverride) {
125125
}
126126
var castScoreDirector = scoreDirectorFactory.createScoreDirectorBuilder()
127127
.withLookUpEnabled(true) // Custom phases and problem changes may rely on lookups.
128-
.withIgnoreInconsistentSolutions(Objects.requireNonNullElse(
129-
solverConfig.getEnablePreviewFeatureSet(), Collections.emptySet())
130-
.contains(PreviewFeature.IGNORE_INCONSISTENT_SOLUTIONS))
128+
.withIgnoreInconsistentSolutions(!solutionDescriptor.hasAnyShadowVariablesInconsistentMember())
131129
.withConstraintMatchPolicy(
132130
constraintMatchEnabled ? ConstraintMatchPolicy.ENABLED : ConstraintMatchPolicy.DISABLED)
133131
.build();

core/src/main/resources/solver.xsd

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1461,8 +1461,6 @@
14611461

14621462
<xs:enumeration value="PLANNING_SOLUTION_DIFF"/>
14631463

1464-
<xs:enumeration value="IGNORE_INCONSISTENT_SOLUTIONS"/>
1465-
14661464
<xs:enumeration value="NEIGHBORHOODS"/>
14671465

14681466
</xs:restriction>

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

Lines changed: 40 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,10 @@
134134
import ai.timefold.solver.core.testdomain.shadow.inverserelation.TestdataInverseRelationEntity;
135135
import ai.timefold.solver.core.testdomain.shadow.inverserelation.TestdataInverseRelationSolution;
136136
import ai.timefold.solver.core.testdomain.shadow.inverserelation.TestdataInverseRelationValue;
137+
import ai.timefold.solver.core.testdomain.shadow.no_inconsistent_field.TestdataDependencyNoInconsistentFieldConstraintProvider;
138+
import ai.timefold.solver.core.testdomain.shadow.no_inconsistent_field.TestdataDependencyNoInconsistentFieldEntity;
139+
import ai.timefold.solver.core.testdomain.shadow.no_inconsistent_field.TestdataDependencyNoInconsistentFieldSolution;
140+
import ai.timefold.solver.core.testdomain.shadow.no_inconsistent_field.TestdataDependencyNoInconsistentFieldValue;
137141
import ai.timefold.solver.core.testdomain.sort.comparator.OneValuePerEntityComparatorEasyScoreCalculator;
138142
import ai.timefold.solver.core.testdomain.sort.comparator.TestdataComparatorSortableEntity;
139143
import ai.timefold.solver.core.testdomain.sort.comparator.TestdataComparatorSortableSolution;
@@ -1453,36 +1457,34 @@ void solveStaleDeclarativeShadows() {
14531457
void solveWhenIgnoringInconsistentSolutionsThrowsIfInitialSolutionInconsistent() {
14541458
// Solver config
14551459
var solverConfig = PlannerTestUtils.buildSolverConfig(
1456-
TestdataConcurrentSolution.class, TestdataConcurrentEntity.class, TestdataConcurrentValue.class)
1460+
TestdataDependencyNoInconsistentFieldSolution.class, TestdataDependencyNoInconsistentFieldEntity.class,
1461+
TestdataDependencyNoInconsistentFieldValue.class)
14571462
.withEasyScoreCalculatorClass(null)
1458-
.withConstraintProviderClass(TestdataConcurrentConstraintProvider.class)
1459-
.withPreviewFeature(PreviewFeature.IGNORE_INCONSISTENT_SOLUTIONS);
1463+
.withConstraintProviderClass(TestdataDependencyNoInconsistentFieldConstraintProvider.class);
14601464

1461-
var e1 = new TestdataConcurrentEntity("e1");
1462-
var e2 = new TestdataConcurrentEntity("e2");
1465+
var e1 = new TestdataDependencyNoInconsistentFieldEntity("a");
1466+
var e2 = new TestdataDependencyNoInconsistentFieldEntity("b");
14631467

1464-
var a1 = new TestdataConcurrentValue("a1");
1465-
var a2 = new TestdataConcurrentValue("a2");
1466-
var b1 = new TestdataConcurrentValue("b1");
1467-
var b2 = new TestdataConcurrentValue("b2");
1468+
var a1 = new TestdataDependencyNoInconsistentFieldValue("a1");
1469+
var a2 = new TestdataDependencyNoInconsistentFieldValue("a2");
1470+
var b1 = new TestdataDependencyNoInconsistentFieldValue("b1");
1471+
var b2 = new TestdataDependencyNoInconsistentFieldValue("b2");
14681472

1469-
a1.setConcurrentValueGroup(List.of(a1, a2));
1470-
a2.setConcurrentValueGroup(List.of(a1, a2));
1473+
a2.setDependencies(List.of(a1));
1474+
b2.setDependencies(List.of(b1));
14711475

1472-
b1.setConcurrentValueGroup(List.of(b1, b2));
1473-
b2.setConcurrentValueGroup(List.of(b1, b2));
1474-
1475-
e1.setValues(List.of(a1, b1));
1476-
e2.setValues(List.of(b2, a2));
1476+
e1.setValues(List.of(b2, b1));
1477+
e2.setValues(List.of(a2, a1));
14771478

14781479
var entities = List.of(e1, e2);
14791480
var values = List.of(a1, a2, b1, b2);
14801481

1481-
var problem = new TestdataConcurrentSolution();
1482+
var problem = new TestdataDependencyNoInconsistentFieldSolution();
14821483

14831484
problem.setEntities(entities);
14841485
problem.setValues(values);
14851486

1487+
// TODO: Verify the values were unassigned instead
14861488
assertThatCode(() -> PlannerTestUtils.solve(solverConfig, problem)).isInstanceOf(IllegalStateException.class)
14871489
.hasMessageContainingAll("The initial solution passed to the solver",
14881490
"is invalid because it has dependency loops");
@@ -1492,42 +1494,40 @@ void solveWhenIgnoringInconsistentSolutionsThrowsIfInitialSolutionInconsistent()
14921494
void solveIgnoreInconsistent() {
14931495
// Solver config
14941496
var solverConfig = PlannerTestUtils.buildSolverConfig(
1495-
TestdataConcurrentSolution.class, TestdataConcurrentEntity.class, TestdataConcurrentValue.class)
1497+
TestdataDependencyNoInconsistentFieldSolution.class, TestdataDependencyNoInconsistentFieldEntity.class,
1498+
TestdataDependencyNoInconsistentFieldValue.class)
14961499
.withEasyScoreCalculatorClass(null)
1497-
.withConstraintProviderClass(TestdataConcurrentConstraintProvider.class);
1500+
.withConstraintProviderClass(TestdataDependencyNoInconsistentFieldConstraintProvider.class);
14981501

1499-
var e1 = new TestdataConcurrentEntity("e1");
1500-
var e2 = new TestdataConcurrentEntity("e2");
1501-
1502-
var a1 = new TestdataConcurrentValue("a1");
1503-
var a2 = new TestdataConcurrentValue("a2");
1504-
var b1 = new TestdataConcurrentValue("b1");
1505-
var b2 = new TestdataConcurrentValue("b2");
1506-
1507-
a1.setConcurrentValueGroup(List.of(a1, a2));
1508-
a2.setConcurrentValueGroup(List.of(a1, a2));
1502+
var e1 = new TestdataDependencyNoInconsistentFieldEntity("a");
1503+
var e2 = new TestdataDependencyNoInconsistentFieldEntity("b");
15091504

1510-
b1.setConcurrentValueGroup(List.of(b1, b2));
1511-
b2.setConcurrentValueGroup(List.of(b1, b2));
1505+
var a1 = new TestdataDependencyNoInconsistentFieldValue("a1");
1506+
var a2 = new TestdataDependencyNoInconsistentFieldValue("a2");
1507+
var b1 = new TestdataDependencyNoInconsistentFieldValue("b1");
1508+
var b2 = new TestdataDependencyNoInconsistentFieldValue("b2");
15121509

1513-
e1.setValues(List.of(a1, b2));
1514-
e2.setValues(List.of(a2, b1));
1510+
a2.setDependencies(List.of(a1));
1511+
b2.setDependencies(List.of(b1));
15151512

15161513
var entities = List.of(e1, e2);
15171514
var values = List.of(a1, a2, b1, b2);
15181515

1519-
var problem = new TestdataConcurrentSolution();
1516+
var problem = new TestdataDependencyNoInconsistentFieldSolution();
15201517

15211518
problem.setEntities(entities);
15221519
problem.setValues(values);
15231520

15241521
var solution = PlannerTestUtils.solve(solverConfig, problem);
1525-
1526-
assertThat(solution.getEntities().getFirst().getValues()).map(TestdataConcurrentValue::getId).containsExactly("a1",
1527-
"b2");
1528-
assertThat(solution.getEntities().get(1).getValues()).map(TestdataConcurrentValue::getId).containsExactly("a2", "b1");
1529-
1530-
assertThat(solution.getScore()).isEqualTo(HardSoftScore.of(0, -240));
1522+
assertThat(solution.getScore()).isEqualTo(HardSoftScore.of(0L, -360L));
1523+
var solutionValues = solution.getValues();
1524+
var solutionA1 = solutionValues.get(0);
1525+
var solutionA2 = solutionValues.get(1);
1526+
var solutionB1 = solutionValues.get(2);
1527+
var solutionB2 = solutionValues.get(3);
1528+
1529+
assertThat(solutionA2.getStartTime()).isAfterOrEqualTo(solutionA1.getEndTime());
1530+
assertThat(solutionB2.getStartTime()).isAfterOrEqualTo(solutionB1.getEndTime());
15311531
}
15321532

15331533
private static List<MoveSelectorConfig<?>> generateMovesForMixedModel() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package ai.timefold.solver.core.testdomain.shadow.no_inconsistent_field;
2+
3+
import java.time.Duration;
4+
5+
import ai.timefold.solver.core.api.score.HardSoftScore;
6+
import ai.timefold.solver.core.api.score.stream.Constraint;
7+
import ai.timefold.solver.core.api.score.stream.ConstraintFactory;
8+
import ai.timefold.solver.core.api.score.stream.ConstraintProvider;
9+
10+
import org.jspecify.annotations.NonNull;
11+
12+
public class TestdataDependencyNoInconsistentFieldConstraintProvider implements ConstraintProvider {
13+
@Override
14+
public Constraint @NonNull [] defineConstraints(@NonNull ConstraintFactory constraintFactory) {
15+
return new Constraint[] {
16+
finishTasksAsSoonAsPossible(constraintFactory),
17+
};
18+
}
19+
20+
public Constraint finishTasksAsSoonAsPossible(@NonNull ConstraintFactory constraintFactory) {
21+
return constraintFactory.forEach(TestdataDependencyNoInconsistentFieldValue.class)
22+
.penalize(HardSoftScore.ONE_SOFT, t -> (int) Duration.between(t.getEntity().getStartTime(),
23+
t.getEndTime()).toMinutes())
24+
.asConstraint("Finish tasks as early as possible");
25+
}
26+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package ai.timefold.solver.core.testdomain.shadow.no_inconsistent_field;
2+
3+
import java.time.LocalDateTime;
4+
import java.time.ZoneOffset;
5+
import java.util.ArrayList;
6+
import java.util.List;
7+
8+
import ai.timefold.solver.core.api.domain.common.PlanningId;
9+
import ai.timefold.solver.core.api.domain.entity.PlanningEntity;
10+
import ai.timefold.solver.core.api.domain.variable.PlanningListVariable;
11+
12+
@PlanningEntity
13+
public class TestdataDependencyNoInconsistentFieldEntity {
14+
@PlanningId
15+
String id;
16+
17+
@PlanningListVariable
18+
List<TestdataDependencyNoInconsistentFieldValue> values;
19+
20+
LocalDateTime startTime;
21+
22+
public TestdataDependencyNoInconsistentFieldEntity() {
23+
this(null, LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.UTC));
24+
}
25+
26+
public TestdataDependencyNoInconsistentFieldEntity(String id) {
27+
this(id, LocalDateTime.ofEpochSecond(0L, 0, ZoneOffset.UTC));
28+
}
29+
30+
public TestdataDependencyNoInconsistentFieldEntity(String id, LocalDateTime startTime) {
31+
this.id = id;
32+
this.startTime = startTime;
33+
this.values = new ArrayList<>();
34+
}
35+
36+
public String getId() {
37+
return id;
38+
}
39+
40+
public void setId(String id) {
41+
this.id = id;
42+
}
43+
44+
public List<TestdataDependencyNoInconsistentFieldValue> getValues() {
45+
return values;
46+
}
47+
48+
public void setValues(List<TestdataDependencyNoInconsistentFieldValue> values) {
49+
this.values = values;
50+
}
51+
52+
public LocalDateTime getStartTime() {
53+
return startTime;
54+
}
55+
56+
public void setStartTime(LocalDateTime startTime) {
57+
this.startTime = startTime;
58+
}
59+
60+
@Override
61+
public String toString() {
62+
return "TestdataPredecessorEntity{id=%s, values=%s, startTime=%s}".formatted(id, values, startTime);
63+
}
64+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package ai.timefold.solver.core.testdomain.shadow.no_inconsistent_field;
2+
3+
import java.util.List;
4+
5+
import ai.timefold.solver.core.api.domain.solution.PlanningEntityCollectionProperty;
6+
import ai.timefold.solver.core.api.domain.solution.PlanningScore;
7+
import ai.timefold.solver.core.api.domain.solution.PlanningSolution;
8+
import ai.timefold.solver.core.api.domain.valuerange.ValueRangeProvider;
9+
import ai.timefold.solver.core.api.score.HardSoftScore;
10+
import ai.timefold.solver.core.impl.domain.solution.descriptor.SolutionDescriptor;
11+
12+
@PlanningSolution
13+
public class TestdataDependencyNoInconsistentFieldSolution {
14+
public static SolutionDescriptor<TestdataDependencyNoInconsistentFieldSolution> buildSolutionDescriptor() {
15+
return SolutionDescriptor.buildSolutionDescriptor(
16+
TestdataDependencyNoInconsistentFieldSolution.class, TestdataDependencyNoInconsistentFieldEntity.class,
17+
TestdataDependencyNoInconsistentFieldValue.class);
18+
}
19+
20+
@PlanningEntityCollectionProperty
21+
List<TestdataDependencyNoInconsistentFieldEntity> entities;
22+
23+
@PlanningEntityCollectionProperty
24+
@ValueRangeProvider
25+
List<TestdataDependencyNoInconsistentFieldValue> values;
26+
27+
@PlanningScore
28+
HardSoftScore score;
29+
30+
public TestdataDependencyNoInconsistentFieldSolution() {
31+
}
32+
33+
public TestdataDependencyNoInconsistentFieldSolution(List<TestdataDependencyNoInconsistentFieldEntity> entities,
34+
List<TestdataDependencyNoInconsistentFieldValue> values) {
35+
this.values = values;
36+
this.entities = entities;
37+
}
38+
39+
public List<TestdataDependencyNoInconsistentFieldValue> getValues() {
40+
return values;
41+
}
42+
43+
public void setValues(List<TestdataDependencyNoInconsistentFieldValue> values) {
44+
this.values = values;
45+
}
46+
47+
public List<TestdataDependencyNoInconsistentFieldEntity> getEntities() {
48+
return entities;
49+
}
50+
51+
public void setEntities(
52+
List<TestdataDependencyNoInconsistentFieldEntity> entities) {
53+
this.entities = entities;
54+
}
55+
56+
public HardSoftScore getScore() {
57+
return score;
58+
}
59+
60+
public void setScore(HardSoftScore score) {
61+
this.score = score;
62+
}
63+
64+
@Override
65+
public String toString() {
66+
return "TestdataPredecessorSolution{entities=%s, values=%s, score=%s}".formatted(entities, values, score);
67+
}
68+
}

0 commit comments

Comments
 (0)