14
14
import java .util .Map ;
15
15
import java .util .Objects ;
16
16
import java .util .Set ;
17
+ import java .util .function .Function ;
17
18
import java .util .function .IntFunction ;
18
19
19
20
import ai .timefold .solver .core .api .function .TriFunction ;
24
25
import ai .timefold .solver .core .preview .api .domain .metamodel .VariableMetaModel ;
25
26
26
27
import org .jspecify .annotations .NullMarked ;
28
+ import org .jspecify .annotations .Nullable ;
27
29
import org .slf4j .Logger ;
28
30
import org .slf4j .LoggerFactory ;
29
31
@@ -186,9 +188,17 @@ private static <Solution_> VariableReferenceGraph buildVariableReferenceGraph(
186
188
private record GroupVariableUpdaterInfo <Solution_ >(
187
189
List <DeclarativeShadowVariableDescriptor <Solution_ >> sortedDeclarativeVariableDescriptors ,
188
190
List <VariableUpdaterInfo <Solution_ >> allUpdaters ,
189
- List <VariableUpdaterInfo <Solution_ >> groupedUpdaters ) {
191
+ List <VariableUpdaterInfo <Solution_ >> groupedUpdaters ,
192
+ Map <DeclarativeShadowVariableDescriptor <Solution_ >, Map <Object , VariableUpdaterInfo <Solution_ >>> variableToEntityToGroupUpdater ) {
190
193
191
- public List <VariableUpdaterInfo <Solution_ >> getUpdatersForEntity (Object entity ) {
194
+ public List <VariableUpdaterInfo <Solution_ >> getUpdatersForEntityVariable (Object entity ,
195
+ DeclarativeShadowVariableDescriptor <Solution_ > declarativeShadowVariableDescriptor ) {
196
+ if (variableToEntityToGroupUpdater .containsKey (declarativeShadowVariableDescriptor )) {
197
+ var updater = variableToEntityToGroupUpdater .get (declarativeShadowVariableDescriptor ).get (entity );
198
+ if (updater != null ) {
199
+ return List .of (updater );
200
+ }
201
+ }
192
202
for (var shadowVariableDescriptor : sortedDeclarativeVariableDescriptors ) {
193
203
for (var rootSource : shadowVariableDescriptor .getSources ()) {
194
204
if (rootSource .parentVariableType () == ParentVariableType .GROUP ) {
@@ -207,16 +217,32 @@ public List<VariableUpdaterInfo<Solution_>> getUpdatersForEntity(Object entity)
207
217
208
218
private static <Solution_ > Map <VariableMetaModel <Solution_ , ?, ?>, GroupVariableUpdaterInfo <Solution_ >>
209
219
getGroupVariableUpdaterInfoMap (
210
- List <DeclarativeShadowVariableDescriptor <Solution_ >> declarativeShadowVariableDescriptors ) {
220
+ List <DeclarativeShadowVariableDescriptor <Solution_ >> declarativeShadowVariableDescriptors ,
221
+ Object [] entities ) {
211
222
var sortedDeclarativeVariableDescriptors =
212
223
topologicallySortedDeclarativeShadowVariables (declarativeShadowVariableDescriptors );
213
224
var groupIndexToVariables = new HashMap <Integer , List <DeclarativeShadowVariableDescriptor <Solution_ >>>();
214
225
var groupVariables = new ArrayList <DeclarativeShadowVariableDescriptor <Solution_ >>();
215
226
groupIndexToVariables .put (0 , groupVariables );
216
227
for (var declarativeShadowVariableDescriptor : sortedDeclarativeVariableDescriptors ) {
217
- if (!groupVariables .isEmpty () && Arrays .stream (declarativeShadowVariableDescriptor .getSources ())
218
- .anyMatch (rootVariableSource -> rootVariableSource .parentVariableType () == ParentVariableType .GROUP )) {
219
- // Create a new variable group, since the group might reference prior variables
228
+ // If a @ShadowSources has a group source (i.e. "visitGroup[].arrivalTimes"),
229
+ // create a new group since it must wait until all members of that group are processed
230
+ var hasGroupSources = Arrays .stream (declarativeShadowVariableDescriptor .getSources ())
231
+ .anyMatch (rootVariableSource -> rootVariableSource .parentVariableType () == ParentVariableType .GROUP );
232
+
233
+ // If a @ShadowSources has an alignment key,
234
+ // create a new group since multiple entities must be updated for this node
235
+ var hasAlignmentKey = declarativeShadowVariableDescriptor .getAlignmentKeyMap () != null ;
236
+
237
+ // If the previous @ShadowSources has an alignment key,
238
+ // create a new group since we are updating a single entity again
239
+ // NOTE: Can potentially be optimized/share a node if VariableUpdaterInfo
240
+ // update each group member independently after the alignmentKey
241
+ var previousHasAlignmentKey = !groupVariables .isEmpty () && groupVariables .get (0 ).getAlignmentKeyMap () != null ;
242
+
243
+ if (!groupVariables .isEmpty () && (hasGroupSources
244
+ || hasAlignmentKey
245
+ || previousHasAlignmentKey )) {
220
246
groupVariables = new ArrayList <>();
221
247
groupIndexToVariables .put (groupIndexToVariables .size (), groupVariables );
222
248
}
@@ -225,25 +251,57 @@ public List<VariableUpdaterInfo<Solution_>> getUpdatersForEntity(Object entity)
225
251
226
252
var out = new HashMap <VariableMetaModel <Solution_ , ?, ?>, GroupVariableUpdaterInfo <Solution_ >>();
227
253
var allUpdaters = new ArrayList <VariableUpdaterInfo <Solution_ >>();
254
+ var groupedUpdaters =
255
+ new HashMap <DeclarativeShadowVariableDescriptor <Solution_ >, Map <Object , VariableUpdaterInfo <Solution_ >>>();
256
+ var updaterKey = 0 ;
228
257
for (var entryKey = 0 ; entryKey < groupIndexToVariables .size (); entryKey ++) {
229
258
var entryGroupVariables = groupIndexToVariables .get (entryKey );
230
259
var updaters = new ArrayList <VariableUpdaterInfo <Solution_ >>();
231
260
for (var declarativeShadowVariableDescriptor : entryGroupVariables ) {
232
261
var updater = new VariableUpdaterInfo <>(
233
262
declarativeShadowVariableDescriptor .getVariableMetaModel (),
234
- entryKey ,
263
+ updaterKey ,
235
264
declarativeShadowVariableDescriptor ,
236
265
declarativeShadowVariableDescriptor .getEntityDescriptor ().getShadowVariableLoopedDescriptor (),
237
266
declarativeShadowVariableDescriptor .getMemberAccessor (),
238
267
declarativeShadowVariableDescriptor .getCalculator ()::executeGetter );
239
- updaters .add (updater );
240
- allUpdaters .add (updater );
268
+ if (declarativeShadowVariableDescriptor .getAlignmentKeyMap () != null ) {
269
+ var alignmentKeyFunction = declarativeShadowVariableDescriptor .getAlignmentKeyMap ();
270
+ var alignmentKeyToAlignedEntitiesMap = new HashMap <Object , List <Object >>();
271
+ for (var entity : entities ) {
272
+ if (declarativeShadowVariableDescriptor .getEntityDescriptor ().getEntityClass ().isInstance (entity )) {
273
+ var alignmentKey = alignmentKeyFunction .apply (entity );
274
+ alignmentKeyToAlignedEntitiesMap .computeIfAbsent (alignmentKey , k -> new ArrayList <>()).add (entity );
275
+ }
276
+ }
277
+ for (var alignmentGroup : alignmentKeyToAlignedEntitiesMap .entrySet ()) {
278
+ var updaterCopy = updater .withGroupId (updaterKey );
279
+ if (alignmentGroup .getKey () == null ) {
280
+ updaters .add (updaterCopy );
281
+ allUpdaters .add (updaterCopy );
282
+ } else {
283
+ updaterCopy = updaterCopy .withGroupEntities (alignmentGroup .getValue ().toArray (new Object [0 ]));
284
+ var variableUpdaterMap = groupedUpdaters .computeIfAbsent (declarativeShadowVariableDescriptor ,
285
+ ignored -> new IdentityHashMap <>());
286
+ for (var entity : alignmentGroup .getValue ()) {
287
+ variableUpdaterMap .put (entity , updaterCopy );
288
+ }
289
+ }
290
+ updaterKey ++;
291
+ }
292
+ updaterKey --; // it will be incremented again at end of the loop
293
+ } else {
294
+ updaters .add (updater );
295
+ allUpdaters .add (updater );
296
+ }
241
297
}
242
298
var groupVariableUpdaterInfo =
243
- new GroupVariableUpdaterInfo <Solution_ >(sortedDeclarativeVariableDescriptors , allUpdaters , updaters );
299
+ new GroupVariableUpdaterInfo <Solution_ >(sortedDeclarativeVariableDescriptors , allUpdaters , updaters ,
300
+ groupedUpdaters );
244
301
for (var declarativeShadowVariableDescriptor : entryGroupVariables ) {
245
302
out .put (declarativeShadowVariableDescriptor .getVariableMetaModel (), groupVariableUpdaterInfo );
246
303
}
304
+ updaterKey ++;
247
305
}
248
306
allUpdaters .replaceAll (updater -> updater .withGroupId (groupIndexToVariables .size ()));
249
307
return out ;
@@ -256,7 +314,16 @@ private static <Solution_> VariableReferenceGraph buildArbitrarySingleEntityGrap
256
314
var declarativeShadowVariableDescriptors = solutionDescriptor .getDeclarativeShadowVariableDescriptors ();
257
315
// Use a dependent lookup; if an entity does not use groups, then all variables can share the same node.
258
316
// If the entity use groups, then variables must be grouped into their own nodes.
259
- var variableIdToUpdater = EntityVariableUpdaterLookup .<Solution_ > entityDependentLookup ();
317
+ var alignmentKeyMappers = new HashMap <VariableMetaModel <Solution_ , ?, ?>, Function <Object , @ Nullable Object >>();
318
+ for (var declarativeShadowVariableDescriptor : declarativeShadowVariableDescriptors ) {
319
+ if (declarativeShadowVariableDescriptor .getAlignmentKeyMap () != null ) {
320
+ alignmentKeyMappers .put (declarativeShadowVariableDescriptor .getVariableMetaModel (),
321
+ declarativeShadowVariableDescriptor .getAlignmentKeyMap ());
322
+ }
323
+ }
324
+ var variableIdToUpdater =
325
+ alignmentKeyMappers .isEmpty () ? EntityVariableUpdaterLookup .<Solution_ > entityDependentLookup ()
326
+ : EntityVariableUpdaterLookup .<Solution_ > groupedEntityDependentLookup (alignmentKeyMappers ::get );
260
327
261
328
// Create graph node for each entity/declarative shadow variable group pair.
262
329
// Maps a variable id to the source aliases of all variables in its group;
@@ -266,11 +333,11 @@ private static <Solution_> VariableReferenceGraph buildArbitrarySingleEntityGrap
266
333
// the groups are [arrivalTime, readyTime] and [serviceStartTime, serviceFinishTime]
267
334
// this is because from arrivalTime, you can compute readyTime without knowing either
268
335
// serviceStartTime or serviceFinishTime.
269
- var variableIdToGroupedUpdater = getGroupVariableUpdaterInfoMap (declarativeShadowVariableDescriptors );
336
+ var variableIdToGroupedUpdater = getGroupVariableUpdaterInfoMap (declarativeShadowVariableDescriptors , entities );
270
337
var declarativeShadowVariableToAliasMap = createGraphNodes (variableReferenceGraphBuilder , entities ,
271
338
declarativeShadowVariableDescriptors , variableIdToUpdater ,
272
339
(entity , declarativeShadowVariable , variableId ) -> variableIdToGroupedUpdater .get (variableId )
273
- .getUpdatersForEntity (entity ));
340
+ .getUpdatersForEntityVariable (entity , declarativeShadowVariable ));
274
341
return buildVariableReferenceGraph (declarativeShadowVariableDescriptors , variableReferenceGraphBuilder ,
275
342
declarativeShadowVariableToAliasMap ,
276
343
graphCreator , entities );
0 commit comments