Skip to content

Commit 5b41cbe

Browse files
fix: Propagate updates to indictments in ifExists even if left side doesn't update, make PrecomputeUni tests aware of indictments
1 parent a14d811 commit 5b41cbe

7 files changed

Lines changed: 134 additions & 43 deletions

File tree

core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractIfExistsNode.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ protected void incrementCounterRightUpdatingIndictment(ExistsCounter<LeftTuple_>
125125
} else {
126126
doRetractCounter(counter);
127127
}
128+
} else {
129+
// count != 0, so only propagate if we are in an `ifExists`
130+
if (shouldExist) {
131+
doUpdateCounter(counter);
132+
}
128133
} // Else do not even propagate an update
129134
// NOTE: By not propagating here, the left tuple's indicted objects can be stale
130135
// if an element is removed.
@@ -152,6 +157,11 @@ protected void decrementCounterRightUpdatingIndictment(ExistsCounter<LeftTuple_>
152157
} else {
153158
doInsertCounter(counter);
154159
}
160+
} else {
161+
// count != 0, so only propagate if we are in an `ifExists`
162+
if (shouldExist) {
163+
doUpdateCounter(counter);
164+
}
155165
} // Else do not even propagate an update
156166
}
157167

@@ -310,6 +320,15 @@ private void doRetractCounter(ExistsCounter<LeftTuple_> counter) {
310320
}
311321
}
312322

323+
private void doUpdateCounter(ExistsCounter<LeftTuple_> counter) {
324+
switch (counter.state) {
325+
case DYING, OK, UPDATING, CREATING -> propagationQueue.update(counter);
326+
case DEAD, ABORTING -> propagationQueue.insert(counter);
327+
default -> throw new IllegalStateException("Impossible state: the counter (%s) has an impossible insert state (%s)."
328+
.formatted(counter, counter.state));
329+
}
330+
}
331+
313332
@Override
314333
protected boolean canProduceTuples() {
315334
// The left input must produce tuples no matter what,

core/src/main/java/ai/timefold/solver/core/impl/bavet/common/AbstractIndexedIfExistsNode.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@ private void updateCounterRight(LeftTuple_ leftTuple, Object compositeKey, Exist
100100
leftTuple.setStore(inputStoreIndexLeftCounterEntry, counterEntry);
101101
if (!isFiltering) {
102102
counter.countRight = rightSize(leftTuple, compositeKey);
103+
if (leftTuple.getIndictmentSource() != IndictmentSource.DISABLED) {
104+
IndictmentSource.clearSupport(getId(), leftTuple);
105+
forEachRightFromLeft(leftTuple, compositeKey, rightTuple -> {
106+
IndictmentSource.addSupport(getId(), leftTuple, rightTuple);
107+
});
108+
}
103109
} else {
104110
// Trackers link themselves into the left tuple's inputStoreIndexLeftTrackerList slot.
105111
// No list object is needed; the slot starts null and the first tracker becomes the head.

core/src/main/java/ai/timefold/solver/core/impl/bavet/common/tuple/indictment/IndictmentSource.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,13 @@ static void addSupport(long nodeId, Tuple carry, Tuple support) {
8686
carry.getIndictmentSource().getSupportForNodeId(nodeId).add(support.getIndictmentSource());
8787
}
8888

89+
static void clearSupport(long nodeId, Tuple carry) {
90+
if (carry.getIndictmentSource() == DISABLED) {
91+
return;
92+
}
93+
carry.getIndictmentSource().getSupportForNodeId(nodeId).clear();
94+
}
95+
8996
static void removeSupport(long nodeId, Tuple carry, Tuple support) {
9097
if (carry.getIndictmentSource() == DISABLED) {
9198
return;
@@ -155,7 +162,7 @@ public void visitSources(Set<IndictmentSource> visited, long[] involvedNodeIds,
155162
return;
156163
}
157164
for (var source : sourceList) {
158-
source.visitSources(involvedNodeIds, sourceConsumer);
165+
source.visitSources(visited, involvedNodeIds, sourceConsumer);
159166
}
160167
}
161168

core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/AbstractAdvancedGroupByConstraintStreamTest.java

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -547,17 +547,20 @@ void existsAfterGroupBy() {
547547
// From scratch
548548
scoreDirector.setWorkingSolution(solution);
549549
assertScore(scoreDirector,
550-
assertMatchWithScore(-2, solution.getFirstEntityGroup(), 2L).withIndictedObjects(solution.getFirstEntity(),
551-
entity3),
552-
assertMatchWithScore(-2, entityGroup1, 2L).withIndictedObjects(entity1, entity2));
550+
assertMatchWithScore(-2, solution.getFirstEntityGroup(), 2L)
551+
.withIndictedObjects(solution.getFirstEntityGroup(), solution.getFirstEntity(),
552+
entity3),
553+
assertMatchWithScore(-2, entityGroup1, 2L)
554+
.withIndictedObjects(entityGroup1, entity1, entity2));
553555

554556
// Incremental
555557
scoreDirector.beforeProblemFactRemoved(entityGroup1);
556558
solution.getEntityGroupList().remove(entityGroup1);
557559
scoreDirector.afterProblemFactRemoved(entityGroup1);
558560
assertScore(scoreDirector,
559-
assertMatchWithScore(-2, solution.getFirstEntityGroup(), 2L).withIndictedObjects(solution.getFirstEntity(),
560-
entity3));
561+
assertMatchWithScore(-2, solution.getFirstEntityGroup(), 2L)
562+
.withIndictedObjects(solution.getFirstEntityGroup(), solution.getFirstEntity(),
563+
entity3));
561564
}
562565

563566
@TestTemplate
@@ -621,16 +624,19 @@ void groupByAfterExistsBi() {
621624
// From scratch
622625
scoreDirector.setWorkingSolution(solution);
623626
assertScore(scoreDirector,
624-
assertMatchWithScore(-3, solution.getFirstEntityGroup(), 3L).withIndictedObjects(solution.getFirstEntity(),
627+
assertMatchWithScore(-3, solution.getFirstEntityGroup(), 3L).withIndictedObjects(
628+
solution.getFirstEntityGroup(), solution.getFirstEntity(),
625629
entity1, entity2, entity3),
626-
assertMatchWithScore(-3, entityGroup1, 3L).withIndictedObjects(entity1, entity2, entity3));
630+
assertMatchWithScore(-3, entityGroup1, 3L).withIndictedObjects(
631+
entityGroup1, entity1, entity2, entity3));
627632

628633
// Incremental
629634
scoreDirector.beforeProblemFactRemoved(entityGroup1);
630635
solution.getEntityGroupList().remove(entityGroup1);
631636
scoreDirector.afterProblemFactRemoved(entityGroup1);
632637
assertScore(scoreDirector,
633-
assertMatchWithScore(-3, solution.getFirstEntityGroup(), 3L).withIndictedObjects(solution.getFirstEntity(),
638+
assertMatchWithScore(-3, solution.getFirstEntityGroup(), 3L).withIndictedObjects(
639+
solution.getFirstEntityGroup(), solution.getFirstEntity(),
634640
entity1, entity2, entity3));
635641
}
636642

core/src/test/java/ai/timefold/solver/core/impl/score/stream/common/ConstraintStreamTestExtension.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,6 @@ public boolean supportsTestTemplate(ExtensionContext context) {
3232

3333
@Override
3434
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(ExtensionContext context) {
35-
if (ConstraintStreamPrecomputeTest.class.isAssignableFrom(context.getTestClass().get())) {
36-
// Precompute test use
37-
return Stream
38-
.of(ConstraintMatchPolicy.ENABLED_WITHOUT_INDICTMENTS,
39-
ConstraintMatchPolicy.ENABLED_WITHOUT_JUSTIFICATIONS_AND_INDICTMENTS,
40-
ConstraintMatchPolicy.DISABLED)
41-
.map(ConstraintStreamTestExtension::invocationContext);
42-
}
4335
return Stream
4436
.of(ConstraintMatchPolicy.ENABLED,
4537
ConstraintMatchPolicy.ENABLED_WITHOUT_INDICTMENTS,

0 commit comments

Comments
 (0)