@@ -15,8 +15,8 @@ use crate::engine::propagation::Propagator;
15
15
use crate :: engine:: propagation:: PropagatorInitialisationContext ;
16
16
use crate :: engine:: DomainEvents ;
17
17
use crate :: predicate;
18
- use crate :: predicates:: Predicate ;
19
18
use crate :: predicates:: PropositionalConjunction ;
19
+ use crate :: pumpkin_assert_simple;
20
20
use crate :: variables:: IntegerVariable ;
21
21
use crate :: variables:: TransformableVariable ;
22
22
@@ -30,12 +30,24 @@ use crate::variables::TransformableVariable;
30
30
/// # Bibliography
31
31
/// \[1\] P. Vilím, ‘Filtering algorithms for the unary resource constraint’, Archives of Control
32
32
/// Sciences, vol. 18, no. 2, pp. 159–202, 2008.
33
- pub ( crate ) struct Disjunctive < Var : IntegerVariable + ' static > {
33
+ pub ( crate ) struct Disjunctive < Var : IntegerVariable > {
34
34
/// The tasks which serve as the input to the disjunctive constraint
35
35
tasks : Box < [ DisjunctiveTask < Var > ] > ,
36
36
/// An additional list of tasks which allows us to sort them (we require [`Disjunctive::tasks`]
37
37
/// to keep track of the right indices).
38
38
sorted_tasks : Vec < DisjunctiveTask < Var > > ,
39
+ /// The tasks which are used to propagate the upper-bounds; these are the same as
40
+ /// [`Disjunctive::tasks`] but reversed (i.e. instead of being from [EST, LCT] these tasks go
41
+ /// from [-LCT, -EST])
42
+ reverse_tasks : Box <
43
+ [ DisjunctiveTask < <<Var as IntegerVariable >:: AffineView as IntegerVariable >:: AffineView > ] ,
44
+ > ,
45
+ /// An additional list of tasks which allows us to sort them (we require
46
+ /// [`Disjunctive::reverse_tasks`]
47
+ /// to keep track of the right indices).
48
+ sorted_reverse_tasks :
49
+ Vec < DisjunctiveTask < <<Var as IntegerVariable >:: AffineView as IntegerVariable >:: AffineView > > ,
50
+
39
51
/// The elements which are currently present in the set Theta used for edge-finding.
40
52
elements_in_theta : FixedBitSet ,
41
53
}
@@ -51,32 +63,151 @@ impl<Var: IntegerVariable + 'static> Disjunctive<Var> {
51
63
id : LocalId :: from ( index as u32 ) ,
52
64
} )
53
65
. collect :: < Vec < _ > > ( ) ;
66
+ let reverse_tasks = tasks
67
+ . iter ( )
68
+ . map ( |task| DisjunctiveTask {
69
+ start_variable : task. start_variable . offset ( task. processing_time ) . scaled ( -1 ) ,
70
+ processing_time : task. processing_time ,
71
+ id : task. id ,
72
+ } )
73
+ . collect :: < Vec < _ > > ( ) ;
54
74
55
75
let num_tasks = tasks. len ( ) ;
76
+
56
77
Self {
57
78
tasks : tasks. clone ( ) . into_boxed_slice ( ) ,
58
79
sorted_tasks : tasks,
80
+ reverse_tasks : reverse_tasks. clone ( ) . into_boxed_slice ( ) ,
81
+ sorted_reverse_tasks : reverse_tasks,
59
82
elements_in_theta : FixedBitSet :: with_capacity ( num_tasks) ,
60
83
}
61
84
}
62
85
}
63
86
64
- /// Creates an explanation consisting of the current bounds for all elements in Theta
87
+ /// Creates an explanation consisting of the tasks in the theta-lambda tree which were responsible
88
+ /// for the conflict based on \[1\].
89
+ ///
90
+ /// # Bibliography
91
+ /// \[1\] P. Vilím, ‘Computing explanations for the unary resource constraint’, in International
92
+ /// Conference on Integration of Artificial Intelligence (AI) and Operations Research (OR)
93
+ /// Techniques in Constraint Programming, 2005, pp. 396–409.
94
+ fn create_conflict_explanation < ' a , Var : IntegerVariable > (
95
+ tasks : & ' a [ DisjunctiveTask < Var > ] ,
96
+ theta_lambda_tree : & mut ThetaLambdaTree ,
97
+ context : & ' a PropagationContextMut ,
98
+ ) -> PropositionalConjunction {
99
+ // First we calculate some data about the set of tasks that caused the overflow
100
+ let mut est_theta = i32:: MAX ;
101
+ let mut lct_theta = i32:: MIN ;
102
+ let mut p_theta = 0 ;
103
+
104
+ let reponsible_tasks = theta_lambda_tree
105
+ . responsible_ect ( )
106
+ . inspect ( |element| {
107
+ let task = & tasks[ element. index ( ) ] ;
108
+ est_theta = est_theta. min ( context. lower_bound ( & task. start_variable ) ) ;
109
+ lct_theta =
110
+ lct_theta. max ( context. upper_bound ( & task. start_variable ) + task. processing_time ) ;
111
+ p_theta += task. processing_time ;
112
+ } )
113
+ . collect :: < Vec < _ > > ( ) ;
114
+
115
+ // We check whether we indeed overflow the interval
116
+ pumpkin_assert_simple ! ( p_theta > lct_theta - est_theta) ;
117
+
118
+ // Then we calculate the amount by which we can lift the interval; i.e. how much does the
119
+ // processing time of theta overflow the interval
120
+ let delta = p_theta - ( lct_theta - est_theta) - 1 ;
121
+
122
+ let mut explanation = Vec :: new ( ) ;
123
+
124
+ // Then for each element in the responsible tasks, we add that they need to be in this interval
125
+ // together
126
+ for element in reponsible_tasks {
127
+ let task = & tasks[ element. index ( ) ] ;
128
+ explanation. push ( predicate ! ( task. start_variable >= est_theta - delta / 2 ) ) ;
129
+ explanation. push ( predicate ! (
130
+ task. start_variable
131
+ <= lct_theta - ( delta as f64 / 2.0 ) . ceil( ) as i32 - task. processing_time
132
+ ) )
133
+ }
134
+
135
+ explanation. into ( )
136
+ }
137
+
138
+ /// Creates an explanation consisting of the tasks in the theta-lambda tree which were responsible
139
+ /// for the propagation of `propagated_task` based on \[1\].
65
140
///
66
- /// TODO: this explanation could be lifted and should take into account which subset of tasks was
67
- /// responsible for the propagation itself.
68
- fn create_theta_explanation < ' a , Var : IntegerVariable > (
141
+ /// # Bibliography
142
+ /// \[1\] P. Vilím, ‘Computing explanations for the unary resource constraint’, in International
143
+ /// Conference on Integration of Artificial Intelligence (AI) and Operations Research (OR)
144
+ /// Techniques in Constraint Programming, 2005, pp. 396–409.
145
+ fn create_propagation_explanation < ' a , Var : IntegerVariable > (
69
146
tasks : & ' a [ DisjunctiveTask < Var > ] ,
147
+ propagated_task_id : LocalId ,
148
+ new_bound : i32 ,
149
+ theta_lambda_tree : & mut ThetaLambdaTree ,
70
150
elements_in_theta : & ' a FixedBitSet ,
71
151
context : & ' a PropagationContextMut ,
72
- ) -> impl Iterator < Item = Predicate > + ' a {
73
- elements_in_theta. ones ( ) . flat_map ( move |index| {
74
- let task = & tasks[ index] ;
75
- [
76
- predicate ! ( task. start_variable >= context. lower_bound( & task. start_variable) ) ,
77
- predicate ! ( task. start_variable <= context. upper_bound( & task. start_variable) ) ,
78
- ]
79
- } )
152
+ ) -> PropositionalConjunction {
153
+ let propagated_task = & tasks[ propagated_task_id. index ( ) ] ;
154
+
155
+ // First we calculate the required information for the explanations of all tasks in theta
156
+ let mut earliest_release_time = context. lower_bound ( & propagated_task. start_variable ) ;
157
+ let mut p_theta = 0 ;
158
+ let theta = elements_in_theta
159
+ . ones ( )
160
+ . inspect ( |theta_element| {
161
+ let task = & tasks[ * theta_element] ;
162
+ p_theta += task. processing_time ;
163
+ earliest_release_time =
164
+ earliest_release_time. min ( context. lower_bound ( & task. start_variable ) ) ;
165
+ } )
166
+ . collect :: < Vec < _ > > ( ) ;
167
+
168
+ // Then we look at the subset which was responsible for the actual bound on EST
169
+ let mut p_theta_prime = 0 ;
170
+ let theta_prime = theta_lambda_tree
171
+ . responsible_ect ( )
172
+ . inspect ( |theta_prime_element| {
173
+ let task = & tasks[ theta_prime_element. index ( ) ] ;
174
+ p_theta_prime += task. processing_time ;
175
+ } )
176
+ . collect :: < Vec < _ > > ( ) ;
177
+
178
+ let mut explanation = Vec :: new ( ) ;
179
+
180
+ // Now we go over each element in theta prime and explain it using lifted intervals
181
+ for j in theta_prime. iter ( ) {
182
+ let task = & tasks[ j. index ( ) ] ;
183
+ explanation. push ( predicate ! ( task. start_variable >= new_bound - p_theta_prime) ) ;
184
+ explanation. push ( predicate ! (
185
+ task. start_variable <= earliest_release_time + p_theta - 1
186
+ ) ) ;
187
+ }
188
+
189
+ // then we go over each element which is in theta but not in theta prime and explin it using
190
+ // lifted intervals
191
+ for j in theta {
192
+ if theta_prime. contains ( & LocalId :: from ( j as u32 ) ) {
193
+ continue ;
194
+ }
195
+
196
+ let task = & tasks[ j] ;
197
+
198
+ explanation. push ( predicate ! ( task. start_variable >= earliest_release_time) ) ;
199
+ explanation. push ( predicate ! (
200
+ task. start_variable <= earliest_release_time + p_theta - 1
201
+ ) ) ;
202
+ }
203
+
204
+ // Finally, we add the bound on the propagated task since this is required to entail the
205
+ // propagation
206
+ explanation. push ( predicate ! (
207
+ propagated_task. start_variable >= earliest_release_time
208
+ ) ) ;
209
+
210
+ explanation. into ( )
80
211
}
81
212
82
213
/// Performs the edge-finding algorithm (see [`Disjunctive`] for an intuition and the work on which
@@ -111,9 +242,11 @@ fn edge_finding<Var: IntegerVariable, SortedTaskVar: IntegerVariable>(
111
242
// (which takes into account `j`) is larger than the LCT of `j` then we can report an
112
243
// overflow
113
244
if theta_lambda_tree. ect ( ) > lct_j {
114
- return Err ( Inconsistency :: Conflict (
115
- create_theta_explanation ( tasks, elements_in_theta, context) . collect ( ) ,
116
- ) ) ;
245
+ return Err ( Inconsistency :: Conflict ( create_conflict_explanation (
246
+ tasks,
247
+ & mut theta_lambda_tree,
248
+ context,
249
+ ) ) ) ;
117
250
}
118
251
119
252
// If there was no overflow then we continue by checking whether we can find a propagation
@@ -146,21 +279,14 @@ fn edge_finding<Var: IntegerVariable, SortedTaskVar: IntegerVariable>(
146
279
context. set_lower_bound (
147
280
& tasks[ i. index ( ) ] . start_variable ,
148
281
theta_lambda_tree. ect ( ) ,
149
- create_theta_explanation ( tasks, elements_in_theta, context)
150
- . chain ( {
151
- let task_i = & tasks[ i. index ( ) ] ;
152
- [
153
- predicate ! (
154
- task_i. start_variable
155
- >= context. lower_bound( & task_i. start_variable)
156
- ) ,
157
- predicate ! (
158
- task_i. start_variable
159
- <= context. upper_bound( & task_i. start_variable)
160
- ) ,
161
- ]
162
- } )
163
- . collect :: < PropositionalConjunction > ( ) ,
282
+ create_propagation_explanation (
283
+ tasks,
284
+ i,
285
+ theta_lambda_tree. ect ( ) ,
286
+ & mut theta_lambda_tree,
287
+ elements_in_theta,
288
+ context,
289
+ ) ,
164
290
) ?;
165
291
}
166
292
// Then we remove the element from consideration entirely by removing it from Lambda
@@ -189,19 +315,10 @@ impl<Var: IntegerVariable + 'static> Propagator for Disjunctive<Var> {
189
315
// Now we want to also update the upper-bounds
190
316
//
191
317
// We do this by "reversing" the tasks to make the LCT be represented as the EST of a task
192
- let reverse_tasks = self
193
- . tasks
194
- . iter ( )
195
- . map ( |task| DisjunctiveTask {
196
- start_variable : task. start_variable . offset ( task. processing_time ) . scaled ( -1 ) ,
197
- processing_time : task. processing_time ,
198
- id : task. id ,
199
- } )
200
- . collect :: < Vec < _ > > ( ) ;
201
318
edge_finding (
202
319
& mut context,
203
- & reverse_tasks,
204
- & mut reverse_tasks . clone ( ) ,
320
+ & self . reverse_tasks ,
321
+ & mut self . sorted_reverse_tasks ,
205
322
& mut self . elements_in_theta ,
206
323
)
207
324
}
@@ -218,19 +335,12 @@ impl<Var: IntegerVariable + 'static> Propagator for Disjunctive<Var> {
218
335
& mut sorted_tasks,
219
336
& mut elements_in_theta,
220
337
) ?;
221
- let reverse_tasks = self
222
- . tasks
223
- . iter ( )
224
- . map ( |task| DisjunctiveTask {
225
- start_variable : task. start_variable . clone ( ) ,
226
- processing_time : task. processing_time ,
227
- id : task. id ,
228
- } )
229
- . collect :: < Vec < _ > > ( ) ;
338
+
339
+ let mut sorted_reverse_tasks = self . sorted_reverse_tasks . clone ( ) ;
230
340
edge_finding (
231
341
& mut context,
232
- & reverse_tasks,
233
- & mut reverse_tasks . clone ( ) ,
342
+ & self . reverse_tasks ,
343
+ & mut sorted_reverse_tasks ,
234
344
& mut elements_in_theta,
235
345
)
236
346
}
0 commit comments