11package ai .timefold .solver .core .impl .domain .variable .declarative ;
22
33import java .util .ArrayList ;
4+ import java .util .Arrays ;
45import java .util .BitSet ;
56import java .util .Collections ;
67import java .util .Comparator ;
1516import java .util .Set ;
1617import java .util .function .IntFunction ;
1718
19+ import ai .timefold .solver .core .api .function .TriFunction ;
1820import ai .timefold .solver .core .impl .domain .solution .descriptor .SolutionDescriptor ;
1921import ai .timefold .solver .core .impl .domain .variable .descriptor .VariableDescriptor ;
2022import ai .timefold .solver .core .impl .score .director .InnerScoreDirector ;
23+ import ai .timefold .solver .core .impl .util .MutableInt ;
2124import ai .timefold .solver .core .preview .api .domain .metamodel .VariableMetaModel ;
2225
23- import org .jspecify .annotations .NonNull ;
2426import org .jspecify .annotations .NullMarked ;
2527import org .slf4j .Logger ;
2628import org .slf4j .LoggerFactory ;
@@ -59,6 +61,8 @@ yield buildSingleDirectionalParentGraph(solutionDescriptor,
5961 graphStructureAndDirection ,
6062 entities );
6163 }
64+ case ARBITRARY_SINGLE_ENTITY_SINGLE_DIRECTIONAL_PARENT_TYPE ->
65+ buildArbitrarySingleEntityGraph (solutionDescriptor , variableReferenceGraphBuilder , entities , graphCreator );
6266 case NO_DYNAMIC_EDGES , ARBITRARY ->
6367 buildArbitraryGraph (solutionDescriptor , variableReferenceGraphBuilder , entities , graphCreator );
6468 };
@@ -81,7 +85,7 @@ static <Solution_> VariableReferenceGraph buildSingleDirectionalParentGraph(
8185 topologicalSorter , changedVariableNotifier , entities );
8286 }
8387
84- private static <Solution_ > @ NonNull List <DeclarativeShadowVariableDescriptor <Solution_ >>
88+ private static <Solution_ > List <DeclarativeShadowVariableDescriptor <Solution_ >>
8589 topologicallySortedDeclarativeShadowVariables (
8690 List <DeclarativeShadowVariableDescriptor <Solution_ >> declarativeShadowVariables ) {
8791 Map <String , Integer > nameToIndex = new LinkedHashMap <>();
@@ -94,8 +98,10 @@ static <Solution_> VariableReferenceGraph buildSingleDirectionalParentGraph(
9498 var visited = new HashSet <Integer >();
9599 for (var source : declarativeShadowVariable .getSources ()) {
96100 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
99105 continue ;
100106 }
101107 var variableReference = variableReferences .get (0 );
@@ -143,7 +149,7 @@ private static <Solution_> VariableReferenceGraph buildArbitraryGraph(
143149 VariableReferenceGraphBuilder <Solution_ > variableReferenceGraphBuilder , Object [] entities ,
144150 IntFunction <TopologicalOrderGraph > graphCreator ) {
145151 var declarativeShadowVariableDescriptors = solutionDescriptor .getDeclarativeShadowVariableDescriptors ();
146- var variableIdToUpdater = new HashMap < VariableMetaModel <?, ?, ?>, VariableUpdaterInfo < Solution_ >> ();
152+ var variableIdToUpdater = EntityVariableUpdaterLookup .< Solution_ > entityIndependentLookup ();
147153
148154 // Create graph node for each entity/declarative shadow variable pair.
149155 // Maps a variable id to its source aliases;
@@ -152,7 +158,16 @@ private static <Solution_> VariableReferenceGraph buildArbitraryGraph(
152158 // to "startTime" of some visit, and thus alias it.
153159 var declarativeShadowVariableToAliasMap = createGraphNodes (variableReferenceGraphBuilder , entities ,
154160 declarativeShadowVariableDescriptors , variableIdToUpdater );
161+ return buildVariableReferenceGraph (declarativeShadowVariableDescriptors , variableReferenceGraphBuilder ,
162+ declarativeShadowVariableToAliasMap ,
163+ graphCreator , entities );
164+ }
155165
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 ) {
156171 // Create variable processors for each declarative shadow variable descriptor
157172 for (var declarativeShadowVariable : declarativeShadowVariableDescriptors ) {
158173 var fromVariableId = declarativeShadowVariable .getVariableMetaModel ();
@@ -168,23 +183,130 @@ private static <Solution_> VariableReferenceGraph buildArbitraryGraph(
168183 return variableReferenceGraphBuilder .build (graphCreator );
169184 }
170185
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+
171294 private static <Solution_ > Map <VariableMetaModel <?, ?, ?>, Set <VariableSourceReference >> createGraphNodes (
172295 VariableReferenceGraphBuilder <Solution_ > graph , Object [] entities ,
173296 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 ) {
175299 var result = new HashMap <VariableMetaModel <?, ?, ?>, Set <VariableSourceReference >>();
176300 for (var entity : entities ) {
177301 for (var declarativeShadowVariableDescriptor : declarativeShadowVariableDescriptors ) {
178302 var entityClass = declarativeShadowVariableDescriptor .getEntityDescriptor ().getEntityClass ();
179303 if (entityClass .isInstance (entity )) {
180304 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 );
188310 for (var sourceRoot : declarativeShadowVariableDescriptor .getSources ()) {
189311 for (var source : sourceRoot .variableSourceReferences ()) {
190312 if (source .downstreamDeclarativeVariableMetamodel () != null ) {
0 commit comments