1
1
package ai .timefold .solver .core .impl .domain .variable .declarative ;
2
2
3
- import java .util .Arrays ;
4
3
import java .util .BitSet ;
4
+ import java .util .IdentityHashMap ;
5
5
import java .util .List ;
6
6
import java .util .Objects ;
7
7
import java .util .PriorityQueue ;
8
- import java .util .Set ;
9
8
import java .util .function .Consumer ;
10
9
import java .util .function .Function ;
10
+ import java .util .stream .Collectors ;
11
11
12
12
import ai .timefold .solver .core .impl .domain .variable .descriptor .VariableDescriptor ;
13
- import ai .timefold .solver .core .impl .util .LinkedIdentityHashSet ;
14
13
15
14
final class AffectedEntitiesUpdater <Solution_ >
16
15
implements Consumer <BitSet > {
17
16
18
17
// From WorkingReferenceGraph.
19
18
private final BaseTopologicalOrderGraph graph ;
20
- private final List <EntityVariablePair <Solution_ >> instanceList ; // Immutable.
21
- private final Function <Object , List <EntityVariablePair <Solution_ >>> entityVariablePairFunction ;
19
+ private final List <GraphNode <Solution_ >> nodeList ; // Immutable.
22
20
private final ChangedVariableNotifier <Solution_ > changedVariableNotifier ;
23
21
24
22
// Internal state; expensive to create, therefore we reuse.
25
- private final AffectedEntities <Solution_ > affectedEntities ;
26
23
private final LoopedTracker loopedTracker ;
27
24
private final BitSet visited ;
28
25
private final PriorityQueue <BaseTopologicalOrderGraph .NodeTopologicalOrder > changeQueue ;
29
26
30
- AffectedEntitiesUpdater (BaseTopologicalOrderGraph graph , List <EntityVariablePair <Solution_ >> instanceList ,
31
- Function <Object , List <EntityVariablePair <Solution_ >>> entityVariablePairFunction ,
32
- ChangedVariableNotifier <Solution_ > changedVariableNotifier ) {
27
+ AffectedEntitiesUpdater (BaseTopologicalOrderGraph graph , List <GraphNode <Solution_ >> nodeList ,
28
+ Function <Object , List <GraphNode <Solution_ >>> entityToContainingNode ,
29
+ int entityCount , ChangedVariableNotifier <Solution_ > changedVariableNotifier ) {
33
30
this .graph = graph ;
34
- this .instanceList = instanceList ;
35
- this .entityVariablePairFunction = entityVariablePairFunction ;
31
+ this .nodeList = nodeList ;
36
32
this .changedVariableNotifier = changedVariableNotifier ;
37
- var instanceCount = instanceList .size ();
38
- this .affectedEntities = new AffectedEntities <>( this :: updateLoopedStatusOfAffectedEntity );
39
- this . loopedTracker = new LoopedTracker ( instanceCount );
33
+ var instanceCount = nodeList .size ();
34
+ this .loopedTracker = new LoopedTracker ( instanceCount ,
35
+ createNodeToEntityNodes ( entityCount , nodeList , entityToContainingNode ) );
40
36
this .visited = new BitSet (instanceCount );
41
37
this .changeQueue = new PriorityQueue <>(instanceCount );
42
38
}
43
39
40
+ static <Solution_ > int [][] createNodeToEntityNodes (int entityCount ,
41
+ List <GraphNode <Solution_ >> nodeList ,
42
+ Function <Object , List <GraphNode <Solution_ >>> entityToContainingNode ) {
43
+ record EntityIdPair (Object entity , int entityId ) {
44
+ @ Override
45
+ public boolean equals (Object o ) {
46
+ if (!(o instanceof EntityIdPair that ))
47
+ return false ;
48
+ return entityId == that .entityId ;
49
+ }
50
+
51
+ @ Override
52
+ public int hashCode () {
53
+ return Objects .hashCode (entityId );
54
+ }
55
+ }
56
+ int [][] out = new int [entityCount ][];
57
+ var entityToNodes = new IdentityHashMap <Integer , int []>();
58
+ var entityIdPairSet = nodeList .stream ()
59
+ .map (node -> new EntityIdPair (node .entity (), node .entityId ()))
60
+ .collect (Collectors .toSet ());
61
+ for (var entityIdPair : entityIdPairSet ) {
62
+ entityToNodes .put (entityIdPair .entityId (),
63
+ entityToContainingNode .apply (entityIdPair .entity ).stream ().mapToInt (GraphNode ::graphNodeId )
64
+ .toArray ());
65
+ }
66
+
67
+ for (var entry : entityToNodes .entrySet ()) {
68
+ out [entry .getKey ()] = entry .getValue ();
69
+ }
70
+
71
+ return out ;
72
+ }
73
+
44
74
@ Override
45
75
public void accept (BitSet changed ) {
46
76
initializeChangeQueue (changed );
@@ -51,7 +81,7 @@ public void accept(BitSet changed) {
51
81
continue ;
52
82
}
53
83
visited .set (nextNode );
54
- var shadowVariable = instanceList .get (nextNode );
84
+ var shadowVariable = nodeList .get (nextNode );
55
85
var isChanged = updateEntityShadowVariables (shadowVariable , graph .isLooped (loopedTracker , nextNode ));
56
86
57
87
if (isChanged ) {
@@ -65,7 +95,6 @@ public void accept(BitSet changed) {
65
95
}
66
96
}
67
97
68
- affectedEntities .processAndClear ();
69
98
// Prepare for the next time updateChanged() is called.
70
99
// No need to clear changeQueue, as that already finishes empty.
71
100
loopedTracker .clear ();
@@ -91,52 +120,50 @@ private void initializeChangeQueue(BitSet changed) {
91
120
changed .clear ();
92
121
}
93
122
94
- private void updateLoopedStatusOfAffectedEntity (Object affectedEntity ) {
95
- ShadowVariableLoopedVariableDescriptor <Solution_ > shadowVariableLoopedDescriptor = null ;
96
- var isEntityLooped = false ;
97
- for (var node : entityVariablePairFunction .apply (affectedEntity )) {
98
- // All variables come from the same entity,
99
- // therefore all have the same looped marker.
100
- shadowVariableLoopedDescriptor = node .variableReferences ().get (0 ).shadowVariableLoopedDescriptor ();
101
- if (graph .isLooped (loopedTracker , node .graphNodeId ())) {
102
- isEntityLooped = true ;
103
- break ;
104
- }
105
- }
106
- if (shadowVariableLoopedDescriptor == null ) {
107
- // At this point, affectedEntity is guaranteed to have looped marker.
108
- // Otherwise AffectedEntities would not have sent it here.
109
- throw new IllegalStateException ("Impossible state: loop marker descriptor does not exist." );
110
- }
111
- var oldValue = shadowVariableLoopedDescriptor .getValue (affectedEntity );
112
- if (!Objects .equals (oldValue , isEntityLooped )) {
113
- changeShadowVariableAndNotify (shadowVariableLoopedDescriptor , affectedEntity , isEntityLooped );
114
- }
115
-
116
- }
117
-
118
- private boolean updateEntityShadowVariables (EntityVariablePair <Solution_ > entityVariable , boolean isLooped ) {
123
+ private boolean updateEntityShadowVariables (GraphNode <Solution_ > entityVariable , boolean isVariableLooped ) {
119
124
var entity = entityVariable .entity ();
120
125
var shadowVariableReferences = entityVariable .variableReferences ();
121
126
var loopDescriptor = shadowVariableReferences .get (0 ).shadowVariableLoopedDescriptor ();
122
127
var anyChanged = false ;
123
128
124
129
if (loopDescriptor != null ) {
125
- var oldLooped = loopDescriptor .getValue (entity );
126
- if (!Objects .equals (oldLooped , isLooped )) {
127
- // Loop status change; add to affected entities
128
- affectedEntities .add (entityVariable );
129
- anyChanged = true ;
130
+ // Do not need to update anyChanged here; the graph already marked
131
+ // all nodes whose looped status changed for us
132
+ var groupEntities = shadowVariableReferences .get (0 ).groupEntities ();
133
+ var groupEntityIds = entityVariable .groupEntityIds ();
134
+
135
+ if (groupEntities != null ) {
136
+ for (var i = 0 ; i < groupEntityIds .length ; i ++) {
137
+ var groupEntity = groupEntities [i ];
138
+ var groupEntityId = groupEntityIds [i ];
139
+ anyChanged |= updateLoopedStatusOfEntity (groupEntity , groupEntityId , loopDescriptor );
140
+ }
141
+ } else {
142
+ anyChanged |= updateLoopedStatusOfEntity (entity , entityVariable .entityId (), loopDescriptor );
130
143
}
131
144
}
132
145
133
146
for (var shadowVariableReference : shadowVariableReferences ) {
134
- anyChanged |= updateShadowVariable (isLooped , shadowVariableReference , entity );
147
+ anyChanged |= updateShadowVariable (isVariableLooped , shadowVariableReference , entity );
135
148
}
136
149
137
150
return anyChanged ;
138
151
}
139
152
153
+ private boolean updateLoopedStatusOfEntity (Object entity , int entityId ,
154
+ ShadowVariableLoopedVariableDescriptor <Solution_ > loopDescriptor ) {
155
+ var oldLooped = (boolean ) loopDescriptor .getValue (entity );
156
+ var isEntityLooped = loopedTracker .isEntityLooped (graph , entityId , oldLooped );
157
+ if (!Objects .equals (oldLooped , isEntityLooped )) {
158
+ changeShadowVariableAndNotify (loopDescriptor , entity , isEntityLooped );
159
+ }
160
+ // We return true if the entity's loop status changed at any point;
161
+ // Since an entity might correspond to multiple nodes, we want all nodes
162
+ // for that entity to be marked as changed, not just the first node the
163
+ // updater encounters
164
+ return loopedTracker .didEntityLoopedStatusChange (entityId );
165
+ }
166
+
140
167
private boolean updateShadowVariable (boolean isLooped ,
141
168
VariableUpdaterInfo <Solution_ > shadowVariableReference , Object entity ) {
142
169
if (isLooped ) {
@@ -152,37 +179,4 @@ private void changeShadowVariableAndNotify(VariableDescriptor<Solution_> variabl
152
179
variableDescriptor .setValue (entity , newValue );
153
180
changedVariableNotifier .afterVariableChanged ().accept (variableDescriptor , entity );
154
181
}
155
-
156
- private static final class AffectedEntities <Solution_ > {
157
-
158
- private final Consumer <Object > consumer ;
159
- private final Set <Object > entitiesForLoopedVarUpdateSet ;
160
-
161
- public AffectedEntities (Consumer <Object > consumer ) {
162
- this .consumer = consumer ;
163
- this .entitiesForLoopedVarUpdateSet = new LinkedIdentityHashSet <>();
164
- }
165
-
166
- public void add (EntityVariablePair <Solution_ > shadowVariable ) {
167
- var shadowVariableLoopedDescriptor = shadowVariable .variableReferences ().get (0 ).shadowVariableLoopedDescriptor ();
168
- if (shadowVariableLoopedDescriptor == null ) {
169
- return ;
170
- }
171
- var entityGroup = shadowVariable .variableReferences ().get (0 ).groupEntities ();
172
- if (entityGroup == null ) {
173
- entitiesForLoopedVarUpdateSet .add (shadowVariable .entity ());
174
- } else {
175
- entitiesForLoopedVarUpdateSet .addAll (Arrays .asList (entityGroup ));
176
- }
177
- }
178
-
179
- public void processAndClear () {
180
- for (var entity : entitiesForLoopedVarUpdateSet ) {
181
- consumer .accept (entity );
182
- }
183
- entitiesForLoopedVarUpdateSet .clear ();
184
- }
185
-
186
- }
187
-
188
182
}
0 commit comments