1616import ai .timefold .solver .core .api .score .stream .Constraint ;
1717import ai .timefold .solver .core .api .score .stream .ConstraintFactory ;
1818import ai .timefold .solver .core .api .score .stream .ConstraintProvider ;
19+ import ai .timefold .solver .core .api .score .stream .PrecomputeFactory ;
20+ import ai .timefold .solver .core .api .score .stream .tri .TriConstraintStream ;
1921
2022public class MeetingSchedulingConstraintProvider implements ConstraintProvider {
2123
@@ -63,12 +65,9 @@ protected Constraint avoidOvertime(ConstraintFactory constraintFactory) {
6365 }
6466
6567 protected Constraint requiredAttendanceConflict (ConstraintFactory constraintFactory ) {
66- return constraintFactory
67- .forEachUniquePair (RequiredAttendance .class ,
68- equal (RequiredAttendance ::getPerson ))
69- .join (MeetingAssignment .class ,
70- equal ((leftRequiredAttendance , rightRequiredAttendance ) -> leftRequiredAttendance .getMeeting (),
71- MeetingAssignment ::getMeeting ))
68+ return constraintFactory .precompute (MeetingSchedulingConstraintProvider ::requiredAttendanceAssignmentLeft )
69+ .filter ((leftRequiredAttendance , rightRequiredAttendance ,
70+ assignment ) -> assignment .getStartingTimeGrain () != null )
7271 .join (MeetingAssignment .class ,
7372 equal ((leftRequiredAttendance , rightRequiredAttendance , leftAssignment ) -> rightRequiredAttendance
7473 .getMeeting (),
@@ -85,6 +84,15 @@ protected Constraint requiredAttendanceConflict(ConstraintFactory constraintFact
8584 .asConstraint ("Required attendance conflict" );
8685 }
8786
87+ private static TriConstraintStream <RequiredAttendance , RequiredAttendance , MeetingAssignment >
88+ requiredAttendanceAssignmentLeft (PrecomputeFactory factory ) {
89+ return factory .forEachUnfilteredUniquePair (RequiredAttendance .class ,
90+ equal (RequiredAttendance ::getPerson ))
91+ .join (MeetingAssignment .class ,
92+ equal ((leftRequiredAttendance , rightRequiredAttendance ) -> leftRequiredAttendance .getMeeting (),
93+ MeetingAssignment ::getMeeting ));
94+ }
95+
8896 protected Constraint requiredRoomCapacity (ConstraintFactory constraintFactory ) {
8997 return constraintFactory .forEachIncludingUnassigned (MeetingAssignment .class )
9098 .filter (meetingAssignment -> meetingAssignment .getRequiredCapacity () > meetingAssignment .getRoomCapacity ())
@@ -109,15 +117,8 @@ protected Constraint startAndEndOnSameDay(ConstraintFactory constraintFactory) {
109117 // ************************************************************************
110118
111119 protected Constraint requiredAndPreferredAttendanceConflict (ConstraintFactory constraintFactory ) {
112- return constraintFactory
113- .forEach (RequiredAttendance .class )
114- .join (PreferredAttendance .class ,
115- equal (RequiredAttendance ::getPerson ,
116- PreferredAttendance ::getPerson ))
117- .join (constraintFactory .forEachIncludingUnassigned (MeetingAssignment .class )
118- .filter (assignment -> assignment .getStartingTimeGrain () != null ),
119- equal ((requiredAttendance , preferredAttendance ) -> requiredAttendance .getMeeting (),
120- MeetingAssignment ::getMeeting ))
120+ return constraintFactory .precompute (MeetingSchedulingConstraintProvider ::requiredAndPreferredAttendanceAssignmentLeft )
121+ .filter ((requiredAttendance , preferredAttendance , assignment ) -> assignment .getStartingTimeGrain () != null )
121122 .join (constraintFactory .forEachIncludingUnassigned (MeetingAssignment .class )
122123 .filter (assignment -> assignment .getStartingTimeGrain () != null ),
123124 equal ((requiredAttendance , preferredAttendance , leftAssignment ) -> preferredAttendance .getMeeting (),
@@ -134,14 +135,20 @@ protected Constraint requiredAndPreferredAttendanceConflict(ConstraintFactory co
134135 .asConstraint ("Required and preferred attendance conflict" );
135136 }
136137
138+ private static TriConstraintStream <RequiredAttendance , PreferredAttendance , MeetingAssignment >
139+ requiredAndPreferredAttendanceAssignmentLeft (PrecomputeFactory factory ) {
140+ return factory .forEachUnfiltered (RequiredAttendance .class )
141+ .join (PreferredAttendance .class ,
142+ equal (RequiredAttendance ::getPerson , PreferredAttendance ::getPerson ))
143+ .join (MeetingAssignment .class ,
144+ equal ((requiredAttendance , preferredAttendance ) -> requiredAttendance .getMeeting (),
145+ MeetingAssignment ::getMeeting ));
146+ }
147+
137148 protected Constraint preferredAttendanceConflict (ConstraintFactory constraintFactory ) {
138- return constraintFactory
139- .forEachUniquePair (PreferredAttendance .class ,
140- equal (PreferredAttendance ::getPerson ))
141- .join (constraintFactory .forEachIncludingUnassigned (MeetingAssignment .class )
142- .filter (assignment -> assignment .getStartingTimeGrain () != null ),
143- equal ((leftAttendance , rightAttendance ) -> leftAttendance .getMeeting (),
144- MeetingAssignment ::getMeeting ))
149+ return constraintFactory .precompute (MeetingSchedulingConstraintProvider ::preferredAttendanceAssignmentLeft )
150+ .filter ((leftPreferredAttendance , rightPreferredAttendance ,
151+ assignment ) -> assignment .getStartingTimeGrain () != null )
145152 .join (constraintFactory .forEachIncludingUnassigned (MeetingAssignment .class )
146153 .filter (assignment -> assignment .getStartingTimeGrain () != null ),
147154 equal ((leftAttendance , rightAttendance , leftAssignment ) -> rightAttendance .getMeeting (),
@@ -158,6 +165,14 @@ protected Constraint preferredAttendanceConflict(ConstraintFactory constraintFac
158165 .asConstraint ("Preferred attendance conflict" );
159166 }
160167
168+ private static TriConstraintStream <PreferredAttendance , PreferredAttendance , MeetingAssignment >
169+ preferredAttendanceAssignmentLeft (PrecomputeFactory factory ) {
170+ return factory .forEachUnfilteredUniquePair (PreferredAttendance .class ,
171+ equal (PreferredAttendance ::getPerson ))
172+ .join (MeetingAssignment .class ,
173+ equal ((leftAttendance , rightAttendance ) -> leftAttendance .getMeeting (), MeetingAssignment ::getMeeting ));
174+ }
175+
161176 // ************************************************************************
162177 // Soft constraints
163178 // ************************************************************************
@@ -205,14 +220,9 @@ protected Constraint assignLargerRoomsFirst(ConstraintFactory constraintFactory)
205220 }
206221
207222 protected Constraint roomStability (ConstraintFactory constraintFactory ) {
208- return constraintFactory .forEach (Attendance .class )
209- .join (Attendance .class ,
210- equal (Attendance ::getPerson ),
211- filtering ((leftAttendance ,
212- rightAttendance ) -> leftAttendance .getMeeting () != rightAttendance .getMeeting ()))
213- .join (MeetingAssignment .class ,
214- equal ((leftAttendance , rightAttendance ) -> leftAttendance .getMeeting (),
215- MeetingAssignment ::getMeeting ))
223+ return constraintFactory .precompute (MeetingSchedulingConstraintProvider ::roomStabilityJoin )
224+ .filter ((leftAttendance , rightAttendance , assignment ) -> assignment .getStartingTimeGrain () != null
225+ && assignment .getRoom () != null )
216226 .join (MeetingAssignment .class ,
217227 equal ((leftAttendance , rightAttendance , leftAssignment ) -> rightAttendance .getMeeting (),
218228 MeetingAssignment ::getMeeting ),
@@ -227,4 +237,15 @@ protected Constraint roomStability(ConstraintFactory constraintFactory) {
227237 .penalize (HardMediumSoftScore .ONE_SOFT )
228238 .asConstraint ("Room stability" );
229239 }
240+
241+ private static TriConstraintStream <Attendance , Attendance , MeetingAssignment > roomStabilityJoin (PrecomputeFactory factory ) {
242+ return factory .forEachUnfiltered (Attendance .class )
243+ .join (Attendance .class ,
244+ equal (Attendance ::getPerson ),
245+ filtering ((leftAttendance ,
246+ rightAttendance ) -> leftAttendance .getMeeting () != rightAttendance .getMeeting ()))
247+ .join (MeetingAssignment .class ,
248+ equal ((leftAttendance , rightAttendance ) -> leftAttendance .getMeeting (), MeetingAssignment ::getMeeting ));
249+ }
250+
230251}
0 commit comments