forked from google/or-tools
-
Notifications
You must be signed in to change notification settings - Fork 3
/
precedences.h
510 lines (449 loc) · 22.1 KB
/
precedences.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
// Copyright 2010-2018 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef OR_TOOLS_SAT_PRECEDENCES_H_
#define OR_TOOLS_SAT_PRECEDENCES_H_
#include <deque>
#include <functional>
#include <vector>
#include "absl/container/inlined_vector.h"
#include "ortools/base/int_type.h"
#include "ortools/base/integral_types.h"
#include "ortools/base/macros.h"
#include "ortools/base/strong_vector.h"
#include "ortools/sat/integer.h"
#include "ortools/sat/model.h"
#include "ortools/sat/sat_base.h"
#include "ortools/sat/sat_solver.h"
#include "ortools/util/bitset.h"
namespace operations_research {
namespace sat {
// This class implement a propagator on simple inequalities between integer
// variables of the form (i1 + offset <= i2). The offset can be constant or
// given by the value of a third integer variable. Offsets can also be negative.
//
// The algorithm work by mapping the problem onto a graph where the edges carry
// the offset and the nodes correspond to one of the two bounds of an integer
// variable (lower_bound or -upper_bound). It then find the fixed point using an
// incremental variant of the Bellman-Ford(-Tarjan) algorithm.
//
// This is also known as an "integer difference logic theory" in the SMT world.
// Another word is "separation logic".
//
// TODO(user): We could easily generalize the code to support any relation of
// the form a*X + b*Y + c*Z >= rhs (or <=). Do that since this class should be
// a lot faster at propagating small linear inequality than the generic
// propagator and the overhead of supporting coefficient should not be too bad.
class PrecedencesPropagator : public SatPropagator, PropagatorInterface {
public:
explicit PrecedencesPropagator(Model* model)
: SatPropagator("PrecedencesPropagator"),
trail_(model->GetOrCreate<Trail>()),
integer_trail_(model->GetOrCreate<IntegerTrail>()),
watcher_(model->GetOrCreate<GenericLiteralWatcher>()),
watcher_id_(watcher_->Register(this)) {
model->GetOrCreate<SatSolver>()->AddPropagator(this);
integer_trail_->RegisterWatcher(&modified_vars_);
watcher_->SetPropagatorPriority(watcher_id_, 0);
}
bool Propagate() final;
bool Propagate(Trail* trail) final;
void Untrail(const Trail& trail, int trail_index) final;
// Propagates all the outgoing arcs of the given variable (and only those). It
// is more efficient to do all these propagation in one go by calling
// Propagate(), but for scheduling problem, we wants to propagate right away
// the end of an interval when its start moved.
bool PropagateOutgoingArcs(IntegerVariable var);
// Add a precedence relation (i1 + offset <= i2) between integer variables.
//
// Important: The optionality of the variable should be marked BEFORE this
// is called.
void AddPrecedence(IntegerVariable i1, IntegerVariable i2);
void AddPrecedenceWithOffset(IntegerVariable i1, IntegerVariable i2,
IntegerValue offset);
void AddPrecedenceWithVariableOffset(IntegerVariable i1, IntegerVariable i2,
IntegerVariable offset_var);
// Same as above, but the relation is only true when the given literal is.
void AddConditionalPrecedence(IntegerVariable i1, IntegerVariable i2,
Literal l);
void AddConditionalPrecedenceWithOffset(IntegerVariable i1,
IntegerVariable i2,
IntegerValue offset, Literal l);
// Generic function that cover all of the above case and more.
void AddPrecedenceWithAllOptions(IntegerVariable i1, IntegerVariable i2,
IntegerValue offset,
IntegerVariable offset_var,
absl::Span<const Literal> presence_literals);
// Finds all the IntegerVariable that are "after" at least two of the
// IntegerVariable in vars. Returns a vector of these precedences relation
// sorted by IntegerPrecedences.var so that it is efficient to find all the
// IntegerVariable "before" another one.
//
// Note that we only consider direct precedences here. Given our usage, it may
// be better to compute the full reachability in the precedence graph, but in
// pratice that may be too slow.
//
// Note that the IntegerVariable in the vector are also returned in
// topological order for a more efficient propagation in
// DisjunctivePrecedences::Propagate() where this is used.
//
// Important: For identical vars, the entry are sorted by index.
struct IntegerPrecedences {
int index; // position in vars.
IntegerVariable var; // An IntegerVariable that is >= to vars[index].
int arc_index; // Used by AddPrecedenceReason().
IntegerValue offset; // we have: vars[index] + offset <= var
};
void ComputePrecedences(const std::vector<IntegerVariable>& vars,
std::vector<IntegerPrecedences>* output);
void AddPrecedenceReason(int arc_index, IntegerValue min_offset,
std::vector<Literal>* literal_reason,
std::vector<IntegerLiteral>* integer_reason) const;
// Advanced usage. To be called once all the constraints have been added to
// the model. This will loop over all "node" in this class, and if one of its
// optional incoming arcs must be chosen, it will add a corresponding
// GreaterThanAtLeastOneOfConstraint(). Returns the number of added
// constraint.
//
// TODO(user): This can be quite slow, add some kind of deterministic limit
// so that we can use it all the time.
int AddGreaterThanAtLeastOneOfConstraints(Model* model);
private:
DEFINE_INT_TYPE(ArcIndex, int);
DEFINE_INT_TYPE(OptionalArcIndex, int);
// Given an existing clause, sees if it can be used to add "greater than at
// least one of" type of constraints. Returns the number of such constraint
// added.
int AddGreaterThanAtLeastOneOfConstraintsFromClause(
const absl::Span<const Literal> clause, Model* model);
// Another approach for AddGreaterThanAtLeastOneOfConstraints(), this one
// might be a bit slow as it relies on the propagation engine to detect
// clauses between incoming arcs presence literals.
// Returns the number of added constraints.
int AddGreaterThanAtLeastOneOfConstraintsWithClauseAutoDetection(
Model* model);
// Information about an individual arc.
struct ArcInfo {
IntegerVariable tail_var;
IntegerVariable head_var;
IntegerValue offset;
IntegerVariable offset_var; // kNoIntegerVariable if none.
// This arc is "present" iff all these literals are true.
absl::InlinedVector<Literal, 6> presence_literals;
// Used temporarily by our implementation of the Bellman-Ford algorithm. It
// should be false at the beginning of BellmanFordTarjan().
mutable bool is_marked;
};
// Internal functions to add new precedence relations.
//
// Note that internally, we only propagate lower bounds, so each time we add
// an arc, we actually create two of them: one on the given variables, and one
// on their negation.
void AdjustSizeFor(IntegerVariable i);
void AddArc(IntegerVariable tail, IntegerVariable head, IntegerValue offset,
IntegerVariable offset_var,
absl::Span<const Literal> presence_literals);
// Enqueue a new lower bound for the variable arc.head_lb that was deduced
// from the current value of arc.tail_lb and the offset of this arc.
bool EnqueueAndCheck(const ArcInfo& arc, IntegerValue new_head_lb,
Trail* trail);
IntegerValue ArcOffset(const ArcInfo& arc) const;
// Inspect all the optional arcs that needs inspection (to stay sparse) and
// check if their presence literal can be propagated to false.
void PropagateOptionalArcs(Trail* trail);
// The core algorithm implementation is split in these functions. One must
// first call InitializeBFQueueWithModifiedNodes() that will push all the
// IntegerVariable whose lower bound has been modified since the last call.
// Then, BellmanFordTarjan() will take care of all the propagation and returns
// false in case of conflict. Internally, it uses DisassembleSubtree() which
// is the Tarjan variant to detect a possible positive cycle. Before exiting,
// it will call CleanUpMarkedArcsAndParents().
//
// The Tarjan version of the Bellam-Ford algorithm is really nice in our
// context because it was really easy to make it incremental. Moreover, it
// supports batch increment!
//
// This implementation is kind of unique because of our context and the fact
// that it is incremental, but a good reference is "Negative-cycle detection
// algorithms", Boris V. Cherkassky, Andrew V. Goldberg, 1996,
// http://people.cs.nctu.edu.tw/~tjshen/doc/ne.pdf
void InitializeBFQueueWithModifiedNodes();
bool BellmanFordTarjan(Trail* trail);
bool DisassembleSubtree(int source, int target,
std::vector<bool>* can_be_skipped);
void AnalyzePositiveCycle(ArcIndex first_arc, Trail* trail,
std::vector<Literal>* must_be_all_true,
std::vector<Literal>* literal_reason,
std::vector<IntegerLiteral>* integer_reason);
void CleanUpMarkedArcsAndParents();
// Loops over all the arcs and verify that there is no propagation left.
// This is only meant to be used in a DCHECK() and is not optimized.
bool NoPropagationLeft(const Trail& trail) const;
// External class needed to get the IntegerVariable lower bounds and Enqueue
// new ones.
Trail* trail_;
IntegerTrail* integer_trail_;
GenericLiteralWatcher* watcher_;
int watcher_id_;
// The key to our incrementality. This will be cleared once the propagation
// is done, and automatically updated by the integer_trail_ with all the
// IntegerVariable that changed since the last clear.
SparseBitset<IntegerVariable> modified_vars_;
// An arc needs to be inspected for propagation (i.e. is impacted) if its
// tail_var changed. If an arc has 3 variables (tail, offset, head), it will
// appear as 6 different entries in the arcs_ vector, one for each variable
// and its negation, each time with a different tail.
//
// TODO(user): rearranging the index so that the arc of the same node are
// consecutive like in StaticGraph should have a big performance impact.
//
// TODO(user): We do not need to store ArcInfo.tail_var here.
absl::StrongVector<IntegerVariable, absl::InlinedVector<ArcIndex, 6>>
impacted_arcs_;
absl::StrongVector<ArcIndex, ArcInfo> arcs_;
// This is similar to impacted_arcs_/arcs_ but it is only used to propagate
// one of the presence literals when the arc cannot be present. An arc needs
// to appear only once in potential_arcs_, but it will be referenced by
// all its variable in impacted_potential_arcs_.
absl::StrongVector<IntegerVariable, absl::InlinedVector<OptionalArcIndex, 6>>
impacted_potential_arcs_;
absl::StrongVector<OptionalArcIndex, ArcInfo> potential_arcs_;
// Temporary vectors used by ComputePrecedences().
absl::StrongVector<IntegerVariable, int> var_to_degree_;
absl::StrongVector<IntegerVariable, int> var_to_last_index_;
struct SortedVar {
IntegerVariable var;
IntegerValue lower_bound;
bool operator<(const SortedVar& other) const {
return lower_bound < other.lower_bound;
}
};
std::vector<SortedVar> tmp_sorted_vars_;
std::vector<IntegerPrecedences> tmp_precedences_;
// Each time a literal becomes true, this list the set of arcs for which we
// need to decrement their count. When an arc count reach zero, it must be
// added to the set of impacted_arcs_. Note that counts never becomes
// negative.
//
// TODO(user): Try a one-watcher approach instead. Note that in most cases
// arc should be controlled by 1 or 2 literals, so not sure it is worth it.
absl::StrongVector<LiteralIndex, absl::InlinedVector<ArcIndex, 6>>
literal_to_new_impacted_arcs_;
absl::StrongVector<ArcIndex, int> arc_counts_;
// Temp vectors to hold the reason of an assignment.
std::vector<Literal> literal_reason_;
std::vector<IntegerLiteral> integer_reason_;
// Temp vectors for the Bellman-Ford algorithm. The graph in which this
// algorithm works is in one to one correspondence with the IntegerVariable in
// impacted_arcs_.
std::deque<int> bf_queue_;
std::vector<bool> bf_in_queue_;
std::vector<bool> bf_can_be_skipped_;
std::vector<ArcIndex> bf_parent_arc_of_;
// Temp vector used by the tree traversal in DisassembleSubtree().
std::vector<int> tmp_vector_;
DISALLOW_COPY_AND_ASSIGN(PrecedencesPropagator);
};
// =============================================================================
// Implementation of the small API functions below.
// =============================================================================
inline void PrecedencesPropagator::AddPrecedence(IntegerVariable i1,
IntegerVariable i2) {
AddArc(i1, i2, /*offset=*/IntegerValue(0), /*offset_var=*/kNoIntegerVariable,
{});
}
inline void PrecedencesPropagator::AddPrecedenceWithOffset(
IntegerVariable i1, IntegerVariable i2, IntegerValue offset) {
AddArc(i1, i2, offset, /*offset_var=*/kNoIntegerVariable, {});
}
inline void PrecedencesPropagator::AddConditionalPrecedence(IntegerVariable i1,
IntegerVariable i2,
Literal l) {
AddArc(i1, i2, /*offset=*/IntegerValue(0), /*offset_var=*/kNoIntegerVariable,
{l});
}
inline void PrecedencesPropagator::AddConditionalPrecedenceWithOffset(
IntegerVariable i1, IntegerVariable i2, IntegerValue offset, Literal l) {
AddArc(i1, i2, offset, /*offset_var=*/kNoIntegerVariable, {l});
}
inline void PrecedencesPropagator::AddPrecedenceWithVariableOffset(
IntegerVariable i1, IntegerVariable i2, IntegerVariable offset_var) {
AddArc(i1, i2, /*offset=*/IntegerValue(0), offset_var, {});
}
inline void PrecedencesPropagator::AddPrecedenceWithAllOptions(
IntegerVariable i1, IntegerVariable i2, IntegerValue offset,
IntegerVariable offset_var, absl::Span<const Literal> presence_literals) {
AddArc(i1, i2, offset, offset_var, presence_literals);
}
// =============================================================================
// Model based functions.
// =============================================================================
// a <= b.
inline std::function<void(Model*)> LowerOrEqual(IntegerVariable a,
IntegerVariable b) {
return [=](Model* model) {
return model->GetOrCreate<PrecedencesPropagator>()->AddPrecedence(a, b);
};
}
// a + offset <= b.
inline std::function<void(Model*)> LowerOrEqualWithOffset(IntegerVariable a,
IntegerVariable b,
int64 offset) {
return [=](Model* model) {
return model->GetOrCreate<PrecedencesPropagator>()->AddPrecedenceWithOffset(
a, b, IntegerValue(offset));
};
}
// a + b <= ub.
inline std::function<void(Model*)> Sum2LowerOrEqual(IntegerVariable a,
IntegerVariable b,
int64 ub) {
return LowerOrEqualWithOffset(a, NegationOf(b), -ub);
}
// l => (a + b <= ub).
inline std::function<void(Model*)> ConditionalSum2LowerOrEqual(
IntegerVariable a, IntegerVariable b, int64 ub,
const std::vector<Literal>& enforcement_literals) {
return [=](Model* model) {
PrecedencesPropagator* p = model->GetOrCreate<PrecedencesPropagator>();
p->AddPrecedenceWithAllOptions(a, NegationOf(b), IntegerValue(-ub),
kNoIntegerVariable, enforcement_literals);
};
}
// a + b + c <= ub.
inline std::function<void(Model*)> Sum3LowerOrEqual(IntegerVariable a,
IntegerVariable b,
IntegerVariable c,
int64 ub) {
return [=](Model* model) {
PrecedencesPropagator* p = model->GetOrCreate<PrecedencesPropagator>();
p->AddPrecedenceWithAllOptions(a, NegationOf(c), IntegerValue(-ub), b, {});
};
}
// l => (a + b + c <= ub).
inline std::function<void(Model*)> ConditionalSum3LowerOrEqual(
IntegerVariable a, IntegerVariable b, IntegerVariable c, int64 ub,
const std::vector<Literal>& enforcement_literals) {
return [=](Model* model) {
PrecedencesPropagator* p = model->GetOrCreate<PrecedencesPropagator>();
p->AddPrecedenceWithAllOptions(a, NegationOf(c), IntegerValue(-ub), b,
enforcement_literals);
};
}
// a >= b.
inline std::function<void(Model*)> GreaterOrEqual(IntegerVariable a,
IntegerVariable b) {
return [=](Model* model) {
return model->GetOrCreate<PrecedencesPropagator>()->AddPrecedence(b, a);
};
}
// a == b.
inline std::function<void(Model*)> Equality(IntegerVariable a,
IntegerVariable b) {
return [=](Model* model) {
model->Add(LowerOrEqual(a, b));
model->Add(LowerOrEqual(b, a));
};
}
// a + offset == b.
inline std::function<void(Model*)> EqualityWithOffset(IntegerVariable a,
IntegerVariable b,
int64 offset) {
return [=](Model* model) {
model->Add(LowerOrEqualWithOffset(a, b, offset));
model->Add(LowerOrEqualWithOffset(b, a, -offset));
};
}
// is_le => (a + offset <= b).
inline std::function<void(Model*)> ConditionalLowerOrEqualWithOffset(
IntegerVariable a, IntegerVariable b, int64 offset, Literal is_le) {
return [=](Model* model) {
PrecedencesPropagator* p = model->GetOrCreate<PrecedencesPropagator>();
p->AddConditionalPrecedenceWithOffset(a, b, IntegerValue(offset), is_le);
};
}
// is_le => (a <= b).
inline std::function<void(Model*)> ConditionalLowerOrEqual(IntegerVariable a,
IntegerVariable b,
Literal is_le) {
return ConditionalLowerOrEqualWithOffset(a, b, 0, is_le);
}
// literals => (a <= b).
inline std::function<void(Model*)> ConditionalLowerOrEqual(
IntegerVariable a, IntegerVariable b, absl::Span<const Literal> literals) {
return [=](Model* model) {
PrecedencesPropagator* p = model->GetOrCreate<PrecedencesPropagator>();
p->AddPrecedenceWithAllOptions(a, b, IntegerValue(0),
/*offset_var*/ kNoIntegerVariable, literals);
};
}
// is_le <=> (a + offset <= b).
inline std::function<void(Model*)> ReifiedLowerOrEqualWithOffset(
IntegerVariable a, IntegerVariable b, int64 offset, Literal is_le) {
return [=](Model* model) {
PrecedencesPropagator* p = model->GetOrCreate<PrecedencesPropagator>();
p->AddConditionalPrecedenceWithOffset(a, b, IntegerValue(offset), is_le);
// The negation of (a + offset <= b) is (a + offset > b) which can be
// rewritten as (b + 1 - offset <= a).
p->AddConditionalPrecedenceWithOffset(b, a, IntegerValue(1 - offset),
is_le.Negated());
};
}
// is_eq <=> (a == b).
inline std::function<void(Model*)> ReifiedEquality(IntegerVariable a,
IntegerVariable b,
Literal is_eq) {
return [=](Model* model) {
// We creates two extra Boolean variables in this case.
//
// TODO(user): Avoid creating them if we already have some literal that
// have the same meaning. For instance if a client also wanted to know if
// a <= b, he would have called ReifiedLowerOrEqualWithOffset() directly.
const Literal is_le = Literal(model->Add(NewBooleanVariable()), true);
const Literal is_ge = Literal(model->Add(NewBooleanVariable()), true);
model->Add(ReifiedBoolAnd({is_le, is_ge}, is_eq));
model->Add(ReifiedLowerOrEqualWithOffset(a, b, 0, is_le));
model->Add(ReifiedLowerOrEqualWithOffset(b, a, 0, is_ge));
};
}
// is_eq <=> (a + offset == b).
inline std::function<void(Model*)> ReifiedEqualityWithOffset(IntegerVariable a,
IntegerVariable b,
int64 offset,
Literal is_eq) {
return [=](Model* model) {
// We creates two extra Boolean variables in this case.
//
// TODO(user): Avoid creating them if we already have some literal that
// have the same meaning. For instance if a client also wanted to know if
// a <= b, he would have called ReifiedLowerOrEqualWithOffset() directly.
const Literal is_le = Literal(model->Add(NewBooleanVariable()), true);
const Literal is_ge = Literal(model->Add(NewBooleanVariable()), true);
model->Add(ReifiedBoolAnd({is_le, is_ge}, is_eq));
model->Add(ReifiedLowerOrEqualWithOffset(a, b, offset, is_le));
model->Add(ReifiedLowerOrEqualWithOffset(b, a, -offset, is_ge));
};
}
// a != b.
inline std::function<void(Model*)> NotEqual(IntegerVariable a,
IntegerVariable b) {
return [=](Model* model) {
// We have two options (is_gt or is_lt) and one must be true.
const Literal is_lt = Literal(model->Add(NewBooleanVariable()), true);
const Literal is_gt = is_lt.Negated();
model->Add(ConditionalLowerOrEqualWithOffset(a, b, 1, is_lt));
model->Add(ConditionalLowerOrEqualWithOffset(b, a, 1, is_gt));
};
}
} // namespace sat
} // namespace operations_research
#endif // OR_TOOLS_SAT_PRECEDENCES_H_