Skip to content

Commit 83b2569

Browse files
chore: track support in indictment, not tuple
This allows support infomation to be automatically passed when a new tuple is created (ex: in map/expand).
1 parent 57b24b1 commit 83b2569

5 files changed

Lines changed: 98 additions & 48 deletions

File tree

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

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package ai.timefold.solver.core.impl.bavet.common.tuple;
22

3-
import java.util.Set;
43
import java.util.function.Function;
54

65
import ai.timefold.solver.core.api.score.stream.uni.UniConstraintStream;
@@ -29,8 +28,6 @@ public sealed interface Tuple permits BiTuple, QuadTuple, TriTuple, UniTuple {
2928

3029
void setIndictmentSource(IndictmentSource indictmentSource);
3130

32-
Set<IndictmentSource> getIndictmentSupportForNodeId(long nodeId);
33-
3431
TupleState getState();
3532

3633
void setState(TupleState state);

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

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
package ai.timefold.solver.core.impl.bavet.common.tuple;
22

3-
import java.util.LinkedHashMap;
4-
import java.util.LinkedHashSet;
5-
import java.util.Map;
6-
import java.util.Set;
7-
83
import ai.timefold.solver.core.impl.bavet.common.tuple.indictment.IndictmentSource;
94

105
import org.jspecify.annotations.NullMarked;
@@ -28,7 +23,6 @@ final class UniversalTuple<A, B, C, D>
2823
private @Nullable C c;
2924
private @Nullable D d;
3025
private IndictmentSource indictmentSource = IndictmentSource.DISABLED;
31-
private @Nullable Map<Long, Set<IndictmentSource>> nodeIdToIndictmentSupport;
3226
private TupleState state = TupleState.DEAD; // It's the node's job to mark a new tuple as CREATING.
3327

3428
UniversalTuple(int storeSize, int cardinality) {
@@ -122,14 +116,6 @@ public void setIndictmentSource(IndictmentSource indictmentSource) {
122116
this.indictmentSource = indictmentSource;
123117
}
124118

125-
@Override
126-
public Set<IndictmentSource> getIndictmentSupportForNodeId(long nodeId) {
127-
if (nodeIdToIndictmentSupport == null) {
128-
nodeIdToIndictmentSupport = new LinkedHashMap<>();
129-
}
130-
return nodeIdToIndictmentSupport.computeIfAbsent(nodeId, ignored -> new LinkedHashSet<>());
131-
}
132-
133119
@Override
134120
public String toString() {
135121
return switch (cardinality) {
Lines changed: 94 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,54 @@
11
package ai.timefold.solver.core.impl.bavet.common.tuple.indictment;
22

33
import java.util.ArrayList;
4+
import java.util.Collections;
5+
import java.util.HashSet;
6+
import java.util.LinkedHashMap;
7+
import java.util.LinkedHashSet;
48
import java.util.List;
9+
import java.util.Map;
10+
import java.util.Set;
511
import java.util.function.Consumer;
612

713
import ai.timefold.solver.core.impl.bavet.common.tuple.Tuple;
814

915
public sealed interface IndictmentSource {
1016
IndictmentSource DISABLED = new DisabledIndictmentSource();
1117

12-
void visitSources(Consumer<Object> sourceConsumer);
18+
void visitSources(Set<IndictmentSource> visited, long[] involvedNodeIds, Consumer<Object> sourceConsumer);
19+
20+
Map<Long, Set<IndictmentSource>> support();
21+
22+
default void visitSources(long[] involvedNodeIds, Consumer<Object> sourceConsumer) {
23+
visitSources(new HashSet<>(), involvedNodeIds, sourceConsumer);
24+
}
25+
26+
default Set<IndictmentSource> getSupportForNodeId(long nodeId) {
27+
return support().computeIfAbsent(nodeId, ignored -> new LinkedHashSet<>());
28+
}
29+
30+
static boolean checkIfAlreadyVisitedAndVisitSupport(IndictmentSource self, Set<IndictmentSource> visited,
31+
long[] involvedNodeIds, Consumer<Object> sourceConsumer) {
32+
if (!visited.add(self)) {
33+
return true;
34+
}
35+
for (var nodeId : involvedNodeIds) {
36+
for (var indictmentSource : self.support().getOrDefault(nodeId, Collections.emptySet())) {
37+
indictmentSource.visitSources(visited, involvedNodeIds, sourceConsumer);
38+
}
39+
}
40+
return false;
41+
}
1342

1443
static IndictmentSource of(Object source) {
15-
return new RootIndictmentSource(source);
44+
return new RootIndictmentSource(source, new LinkedHashMap<>());
1645
}
1746

1847
static IndictmentSource joining(Tuple left, Tuple right) {
1948
if (left.getIndictmentSource() == DISABLED) {
2049
return DISABLED;
2150
}
22-
return new JoinedIndictmentSource(left.getIndictmentSource(), right.getIndictmentSource());
51+
return new JoinedIndictmentSource(left.getIndictmentSource(), right.getIndictmentSource(), new LinkedHashMap<>());
2352
}
2453

2554
static IndictmentSource aggregating(Tuple elementTuple, Tuple groupTuple) {
@@ -32,7 +61,7 @@ static IndictmentSource aggregating(Tuple elementTuple, Tuple groupTuple) {
3261
} else {
3362
var collection = new ArrayList<IndictmentSource>();
3463
collection.add(elementTuple.getIndictmentSource());
35-
return new AggregateIndictmentSource(collection);
64+
return new AggregateIndictmentSource(collection, new LinkedHashMap<>());
3665
}
3766
}
3867

@@ -46,62 +75,103 @@ static IndictmentSource removeFromAggregate(Tuple elementTuple, Tuple groupTuple
4675
} else {
4776
var collection = new ArrayList<IndictmentSource>();
4877
collection.add(elementTuple.getIndictmentSource());
49-
return new AggregateIndictmentSource(collection);
78+
return new AggregateIndictmentSource(collection, new LinkedHashMap<>());
5079
}
5180
}
5281

5382
static void addSupport(long nodeId, Tuple carry, Tuple support) {
5483
if (carry.getIndictmentSource() == DISABLED) {
5584
return;
5685
}
57-
carry.getIndictmentSupportForNodeId(nodeId).add(support.getIndictmentSource());
86+
carry.getIndictmentSource().getSupportForNodeId(nodeId).add(support.getIndictmentSource());
5887
}
5988

6089
static void removeSupport(long nodeId, Tuple carry, Tuple support) {
6190
if (carry.getIndictmentSource() == DISABLED) {
6291
return;
6392
}
64-
carry.getIndictmentSupportForNodeId(nodeId).remove(support.getIndictmentSource());
93+
carry.getIndictmentSource().getSupportForNodeId(nodeId).remove(support.getIndictmentSource());
6594
}
6695

6796
record DisabledIndictmentSource() implements IndictmentSource {
6897
@Override
69-
public void visitSources(Consumer<Object> sourceConsumer) {
98+
public void visitSources(Set<IndictmentSource> visited, long[] involvedNodeIds, Consumer<Object> sourceConsumer) {
99+
throw new UnsupportedOperationException("Impossible state: indictments are disabled.");
100+
}
101+
102+
@Override
103+
public Map<Long, Set<IndictmentSource>> support() {
70104
throw new UnsupportedOperationException("Impossible state: indictments are disabled.");
71105
}
72106
}
73107

74-
record RootIndictmentSource(Object source) implements IndictmentSource {
108+
record RootIndictmentSource(Object source, Map<Long, Set<IndictmentSource>> support) implements IndictmentSource {
75109
@Override
76-
public void visitSources(Consumer<Object> sourceConsumer) {
110+
public void visitSources(Set<IndictmentSource> visited, long[] involvedNodeIds, Consumer<Object> sourceConsumer) {
111+
if (checkIfAlreadyVisitedAndVisitSupport(this, visited, involvedNodeIds, sourceConsumer)) {
112+
return;
113+
}
77114
sourceConsumer.accept(source);
78115
}
79-
}
80116

81-
record JoinedIndictmentSource(IndictmentSource left, IndictmentSource right) implements IndictmentSource {
82117
@Override
83-
public void visitSources(Consumer<Object> sourceConsumer) {
84-
left.visitSources(sourceConsumer);
85-
right.visitSources(sourceConsumer);
118+
public boolean equals(Object o) {
119+
return this == o;
120+
}
121+
122+
@Override
123+
public int hashCode() {
124+
return System.identityHashCode(this);
86125
}
87126
}
88127

89-
record AggregateIndictmentSource(List<IndictmentSource> sourceList) implements IndictmentSource {
128+
record JoinedIndictmentSource(IndictmentSource left, IndictmentSource right,
129+
Map<Long, Set<IndictmentSource>> support) implements IndictmentSource {
90130
@Override
91-
public void visitSources(Consumer<Object> sourceConsumer) {
92-
for (var source : sourceList) {
93-
source.visitSources(sourceConsumer);
131+
public void visitSources(Set<IndictmentSource> visited, long[] involvedNodeIds, Consumer<Object> sourceConsumer) {
132+
if (checkIfAlreadyVisitedAndVisitSupport(this, visited, involvedNodeIds, sourceConsumer)) {
133+
return;
94134
}
135+
left.visitSources(visited, involvedNodeIds, sourceConsumer);
136+
right.visitSources(visited, involvedNodeIds, sourceConsumer);
137+
}
138+
139+
@Override
140+
public boolean equals(Object o) {
141+
return this == o;
142+
}
143+
144+
@Override
145+
public int hashCode() {
146+
return System.identityHashCode(this);
95147
}
96148
}
97149

98-
record IndictmentSourceWithSupport(IndictmentSource source, List<IndictmentSource> support) implements IndictmentSource {
150+
record AggregateIndictmentSource(List<IndictmentSource> sourceList,
151+
Map<Long, Set<IndictmentSource>> support) implements IndictmentSource {
99152
@Override
100-
public void visitSources(Consumer<Object> sourceConsumer) {
101-
source.visitSources(sourceConsumer);
102-
for (var support : support) {
103-
support.visitSources(sourceConsumer);
153+
public void visitSources(Set<IndictmentSource> visited, long[] involvedNodeIds, Consumer<Object> sourceConsumer) {
154+
if (checkIfAlreadyVisitedAndVisitSupport(this, visited, involvedNodeIds, sourceConsumer)) {
155+
return;
156+
}
157+
for (var source : sourceList) {
158+
source.visitSources(involvedNodeIds, sourceConsumer);
104159
}
105160
}
161+
162+
@Override
163+
public Set<IndictmentSource> getSupportForNodeId(long nodeId) {
164+
return support.computeIfAbsent(nodeId, ignored -> new LinkedHashSet<>());
165+
}
166+
167+
@Override
168+
public boolean equals(Object o) {
169+
return this == o;
170+
}
171+
172+
@Override
173+
public int hashCode() {
174+
return System.identityHashCode(this);
175+
}
106176
}
107177
}

core/src/main/java/ai/timefold/solver/core/impl/score/stream/common/inliner/ConstraintMatchSupplier.java

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,9 @@ static List<Object> collectIndictments(Constraint constraint, Tuple tuple) {
4747
return Collections.emptyList();
4848
}
4949
var out = new LinkedHashSet<>();
50-
tuple.getIndictmentSource().visitSources(out::add);
5150
var abstractConstraint = (AbstractConstraint<?, ?, ?>) constraint;
52-
for (var involvedNodeId : Objects.requireNonNull(abstractConstraint.getInvolvedNodeIds())) {
53-
for (var indictmentSource : tuple.getIndictmentSupportForNodeId(involvedNodeId)) {
54-
indictmentSource.visitSources(out::add);
55-
}
56-
}
51+
var involvedNodeIds = Objects.requireNonNull(abstractConstraint.getInvolvedNodeIds());
52+
tuple.getIndictmentSource().visitSources(involvedNodeIds, out::add);
5753
return new ArrayList<>(out);
5854
}
5955

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2449,7 +2449,8 @@ public void expandToTri() {
24492449
solution.getEntityList().remove(entity);
24502450
scoreDirector.afterEntityRemoved(entity);
24512451
assertScore(scoreDirector,
2452-
assertMatch(solution.getFirstEntity(), group2, value2).withIndictedObjects(solution.getEntityList().getFirst()));
2452+
assertMatch(solution.getFirstEntity(), group2, value2)
2453+
.withIndictedObjects(solution.getEntityList().getFirst()));
24532454
}
24542455

24552456
@Override

0 commit comments

Comments
 (0)