1
1
package ai .timefold .solver .core .impl .domain .variable .declarative ;
2
2
3
3
import java .util .ArrayList ;
4
+ import java .util .Arrays ;
4
5
import java .util .BitSet ;
5
6
import java .util .Collections ;
6
7
import java .util .Comparator ;
15
16
import java .util .Set ;
16
17
import java .util .function .IntFunction ;
17
18
19
+ import ai .timefold .solver .core .api .function .TriFunction ;
18
20
import ai .timefold .solver .core .impl .domain .solution .descriptor .SolutionDescriptor ;
19
21
import ai .timefold .solver .core .impl .domain .variable .descriptor .VariableDescriptor ;
20
22
import ai .timefold .solver .core .impl .score .director .InnerScoreDirector ;
23
+ import ai .timefold .solver .core .impl .util .MutableInt ;
21
24
import ai .timefold .solver .core .preview .api .domain .metamodel .VariableMetaModel ;
22
25
23
- import org .jspecify .annotations .NonNull ;
24
26
import org .jspecify .annotations .NullMarked ;
25
27
import org .slf4j .Logger ;
26
28
import org .slf4j .LoggerFactory ;
@@ -59,6 +61,8 @@ yield buildSingleDirectionalParentGraph(solutionDescriptor,
59
61
graphStructureAndDirection ,
60
62
entities );
61
63
}
64
+ case ARBITRARY_SINGLE_ENTITY_SINGLE_DIRECTIONAL_PARENT_TYPE ->
65
+ buildArbitrarySingleEntityGraph (solutionDescriptor , variableReferenceGraphBuilder , entities , graphCreator );
62
66
case NO_DYNAMIC_EDGES , ARBITRARY ->
63
67
buildArbitraryGraph (solutionDescriptor , variableReferenceGraphBuilder , entities , graphCreator );
64
68
};
@@ -81,7 +85,7 @@ static <Solution_> VariableReferenceGraph buildSingleDirectionalParentGraph(
81
85
topologicalSorter , changedVariableNotifier , entities );
82
86
}
83
87
84
- private static <Solution_ > @ NonNull List <DeclarativeShadowVariableDescriptor <Solution_ >>
88
+ private static <Solution_ > List <DeclarativeShadowVariableDescriptor <Solution_ >>
85
89
topologicallySortedDeclarativeShadowVariables (
86
90
List <DeclarativeShadowVariableDescriptor <Solution_ >> declarativeShadowVariables ) {
87
91
Map <String , Integer > nameToIndex = new LinkedHashMap <>();
@@ -94,8 +98,10 @@ static <Solution_> VariableReferenceGraph buildSingleDirectionalParentGraph(
94
98
var visited = new HashSet <Integer >();
95
99
for (var source : declarativeShadowVariable .getSources ()) {
96
100
var variableReferences = source .variableSourceReferences ();
97
- if (variableReferences .size () != 1 ) {
98
- // variableReferences is from directional variable
101
+ if (source .parentVariableType () != ParentVariableType .NO_PARENT ) {
102
+ // We only look at direct usage; if we also added
103
+ // edges for groups/directional, we will end up creating a cycle
104
+ // which makes all topological orders valid
99
105
continue ;
100
106
}
101
107
var variableReference = variableReferences .get (0 );
@@ -143,7 +149,7 @@ private static <Solution_> VariableReferenceGraph buildArbitraryGraph(
143
149
VariableReferenceGraphBuilder <Solution_ > variableReferenceGraphBuilder , Object [] entities ,
144
150
IntFunction <TopologicalOrderGraph > graphCreator ) {
145
151
var declarativeShadowVariableDescriptors = solutionDescriptor .getDeclarativeShadowVariableDescriptors ();
146
- var variableIdToUpdater = new HashMap < VariableMetaModel <?, ?, ?>, VariableUpdaterInfo < Solution_ >> ();
152
+ var variableIdToUpdater = EntityVariableUpdaterLookup .< Solution_ > entityIndependentLookup ();
147
153
148
154
// Create graph node for each entity/declarative shadow variable pair.
149
155
// Maps a variable id to its source aliases;
@@ -152,7 +158,16 @@ private static <Solution_> VariableReferenceGraph buildArbitraryGraph(
152
158
// to "startTime" of some visit, and thus alias it.
153
159
var declarativeShadowVariableToAliasMap = createGraphNodes (variableReferenceGraphBuilder , entities ,
154
160
declarativeShadowVariableDescriptors , variableIdToUpdater );
161
+ return buildVariableReferenceGraph (declarativeShadowVariableDescriptors , variableReferenceGraphBuilder ,
162
+ declarativeShadowVariableToAliasMap ,
163
+ graphCreator , entities );
164
+ }
155
165
166
+ private static <Solution_ > VariableReferenceGraph buildVariableReferenceGraph (
167
+ List <DeclarativeShadowVariableDescriptor <Solution_ >> declarativeShadowVariableDescriptors ,
168
+ VariableReferenceGraphBuilder <Solution_ > variableReferenceGraphBuilder ,
169
+ Map <VariableMetaModel <?, ?, ?>, Set <VariableSourceReference >> declarativeShadowVariableToAliasMap ,
170
+ IntFunction <TopologicalOrderGraph > graphCreator , Object ... entities ) {
156
171
// Create variable processors for each declarative shadow variable descriptor
157
172
for (var declarativeShadowVariable : declarativeShadowVariableDescriptors ) {
158
173
var fromVariableId = declarativeShadowVariable .getVariableMetaModel ();
@@ -168,23 +183,130 @@ private static <Solution_> VariableReferenceGraph buildArbitraryGraph(
168
183
return variableReferenceGraphBuilder .build (graphCreator );
169
184
}
170
185
186
+ private record GroupVariableUpdaterInfo <Solution_ >(
187
+ List <DeclarativeShadowVariableDescriptor <Solution_ >> sortedDeclarativeVariableDescriptors ,
188
+ List <VariableUpdaterInfo <Solution_ >> allUpdaters ,
189
+ List <VariableUpdaterInfo <Solution_ >> groupedUpdaters ) {
190
+
191
+ public List <VariableUpdaterInfo <Solution_ >> getUpdatersForEntity (Object entity ) {
192
+ for (var shadowVariableDescriptor : sortedDeclarativeVariableDescriptors ) {
193
+ for (var rootSource : shadowVariableDescriptor .getSources ()) {
194
+ if (rootSource .parentVariableType () == ParentVariableType .GROUP ) {
195
+ var visitedCount = new MutableInt ();
196
+ rootSource .valueEntityFunction ().accept (entity , ignored -> visitedCount .increment ());
197
+ if (visitedCount .intValue () > 0 ) {
198
+ return groupedUpdaters ;
199
+ }
200
+ }
201
+ }
202
+ }
203
+ return allUpdaters ;
204
+ }
205
+
206
+ }
207
+
208
+ private static <Solution_ > Map <VariableMetaModel <Solution_ , ?, ?>, GroupVariableUpdaterInfo <Solution_ >>
209
+ getGroupVariableUpdaterInfoMap (
210
+ List <DeclarativeShadowVariableDescriptor <Solution_ >> declarativeShadowVariableDescriptors ) {
211
+ var sortedDeclarativeVariableDescriptors =
212
+ topologicallySortedDeclarativeShadowVariables (declarativeShadowVariableDescriptors );
213
+ var groupIndexToVariables = new HashMap <Integer , List <DeclarativeShadowVariableDescriptor <Solution_ >>>();
214
+ var groupVariables = new ArrayList <DeclarativeShadowVariableDescriptor <Solution_ >>();
215
+ groupIndexToVariables .put (0 , groupVariables );
216
+ 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
220
+ groupVariables = new ArrayList <>();
221
+ groupIndexToVariables .put (groupIndexToVariables .size (), groupVariables );
222
+ }
223
+ groupVariables .add (declarativeShadowVariableDescriptor );
224
+ }
225
+
226
+ var out = new HashMap <VariableMetaModel <Solution_ , ?, ?>, GroupVariableUpdaterInfo <Solution_ >>();
227
+ var allUpdaters = new ArrayList <VariableUpdaterInfo <Solution_ >>();
228
+ for (var entryKey = 0 ; entryKey < groupIndexToVariables .size (); entryKey ++) {
229
+ var entryGroupVariables = groupIndexToVariables .get (entryKey );
230
+ var updaters = new ArrayList <VariableUpdaterInfo <Solution_ >>();
231
+ for (var declarativeShadowVariableDescriptor : entryGroupVariables ) {
232
+ var updater = new VariableUpdaterInfo <>(
233
+ declarativeShadowVariableDescriptor .getVariableMetaModel (),
234
+ entryKey ,
235
+ declarativeShadowVariableDescriptor ,
236
+ declarativeShadowVariableDescriptor .getEntityDescriptor ().getShadowVariableLoopedDescriptor (),
237
+ declarativeShadowVariableDescriptor .getMemberAccessor (),
238
+ declarativeShadowVariableDescriptor .getCalculator ()::executeGetter );
239
+ updaters .add (updater );
240
+ allUpdaters .add (updater );
241
+ }
242
+ var groupVariableUpdaterInfo =
243
+ new GroupVariableUpdaterInfo <Solution_ >(sortedDeclarativeVariableDescriptors , allUpdaters , updaters );
244
+ for (var declarativeShadowVariableDescriptor : entryGroupVariables ) {
245
+ out .put (declarativeShadowVariableDescriptor .getVariableMetaModel (), groupVariableUpdaterInfo );
246
+ }
247
+ }
248
+ allUpdaters .replaceAll (updater -> updater .withGroupId (groupIndexToVariables .size ()));
249
+ return out ;
250
+ }
251
+
252
+ private static <Solution_ > VariableReferenceGraph buildArbitrarySingleEntityGraph (
253
+ SolutionDescriptor <Solution_ > solutionDescriptor ,
254
+ VariableReferenceGraphBuilder <Solution_ > variableReferenceGraphBuilder , Object [] entities ,
255
+ IntFunction <TopologicalOrderGraph > graphCreator ) {
256
+ var declarativeShadowVariableDescriptors = solutionDescriptor .getDeclarativeShadowVariableDescriptors ();
257
+ // Use a dependent lookup; if an entity does not use groups, then all variables can share the same node.
258
+ // If the entity use groups, then variables must be grouped into their own nodes.
259
+ var variableIdToUpdater = EntityVariableUpdaterLookup .<Solution_ > entityDependentLookup ();
260
+
261
+ // Create graph node for each entity/declarative shadow variable group pair.
262
+ // Maps a variable id to the source aliases of all variables in its group;
263
+ // If the variables are (in topological order)
264
+ // arrivalTime, readyTime, serviceStartTime, serviceFinishTime,
265
+ // where serviceStartTime depends on a group of readyTime, then
266
+ // the groups are [arrivalTime, readyTime] and [serviceStartTime, serviceFinishTime]
267
+ // this is because from arrivalTime, you can compute readyTime without knowing either
268
+ // serviceStartTime or serviceFinishTime.
269
+ var variableIdToGroupedUpdater = getGroupVariableUpdaterInfoMap (declarativeShadowVariableDescriptors );
270
+ var declarativeShadowVariableToAliasMap = createGraphNodes (variableReferenceGraphBuilder , entities ,
271
+ declarativeShadowVariableDescriptors , variableIdToUpdater ,
272
+ (entity , declarativeShadowVariable , variableId ) -> variableIdToGroupedUpdater .get (variableId )
273
+ .getUpdatersForEntity (entity ));
274
+ return buildVariableReferenceGraph (declarativeShadowVariableDescriptors , variableReferenceGraphBuilder ,
275
+ declarativeShadowVariableToAliasMap ,
276
+ graphCreator , entities );
277
+ }
278
+
279
+ private static <Solution_ > Map <VariableMetaModel <?, ?, ?>, Set <VariableSourceReference >> createGraphNodes (
280
+ VariableReferenceGraphBuilder <Solution_ > graph , Object [] entities ,
281
+ List <DeclarativeShadowVariableDescriptor <Solution_ >> declarativeShadowVariableDescriptors ,
282
+ EntityVariableUpdaterLookup <Solution_ > variableIdToUpdaters ) {
283
+ return createGraphNodes (graph , entities , declarativeShadowVariableDescriptors , variableIdToUpdaters ,
284
+ (entity , declarativeShadowVariableDescriptor ,
285
+ variableId ) -> Collections .singletonList (new VariableUpdaterInfo <>(
286
+ variableId ,
287
+ variableIdToUpdaters .getNextId (),
288
+ declarativeShadowVariableDescriptor ,
289
+ declarativeShadowVariableDescriptor .getEntityDescriptor ().getShadowVariableLoopedDescriptor (),
290
+ declarativeShadowVariableDescriptor .getMemberAccessor (),
291
+ declarativeShadowVariableDescriptor .getCalculator ()::executeGetter )));
292
+ }
293
+
171
294
private static <Solution_ > Map <VariableMetaModel <?, ?, ?>, Set <VariableSourceReference >> createGraphNodes (
172
295
VariableReferenceGraphBuilder <Solution_ > graph , Object [] entities ,
173
296
List <DeclarativeShadowVariableDescriptor <Solution_ >> declarativeShadowVariableDescriptors ,
174
- Map <VariableMetaModel <?, ?, ?>, VariableUpdaterInfo <Solution_ >> variableIdToUpdater ) {
297
+ EntityVariableUpdaterLookup <Solution_ > variableIdToUpdaters ,
298
+ TriFunction <Object , DeclarativeShadowVariableDescriptor <Solution_ >, VariableMetaModel <Solution_ , ?, ?>, List <VariableUpdaterInfo <Solution_ >>> entityVariableToUpdatersMapper ) {
175
299
var result = new HashMap <VariableMetaModel <?, ?, ?>, Set <VariableSourceReference >>();
176
300
for (var entity : entities ) {
177
301
for (var declarativeShadowVariableDescriptor : declarativeShadowVariableDescriptors ) {
178
302
var entityClass = declarativeShadowVariableDescriptor .getEntityDescriptor ().getEntityClass ();
179
303
if (entityClass .isInstance (entity )) {
180
304
var variableId = declarativeShadowVariableDescriptor .getVariableMetaModel ();
181
- var updater = variableIdToUpdater .computeIfAbsent (variableId , ignored -> new VariableUpdaterInfo <>(
182
- variableId ,
183
- declarativeShadowVariableDescriptor ,
184
- declarativeShadowVariableDescriptor .getEntityDescriptor ().getShadowVariableLoopedDescriptor (),
185
- declarativeShadowVariableDescriptor .getMemberAccessor (),
186
- declarativeShadowVariableDescriptor .getCalculator ()::executeGetter ));
187
- graph .addVariableReferenceEntity (entity , updater );
305
+ var updaters = variableIdToUpdaters .computeUpdatersForVariableOnEntity (variableId ,
306
+ entity ,
307
+ () -> entityVariableToUpdatersMapper .apply (entity , declarativeShadowVariableDescriptor ,
308
+ variableId ));
309
+ graph .addVariableReferenceEntity (entity , updaters );
188
310
for (var sourceRoot : declarativeShadowVariableDescriptor .getSources ()) {
189
311
for (var source : sourceRoot .variableSourceReferences ()) {
190
312
if (source .downstreamDeclarativeVariableMetamodel () != null ) {
0 commit comments