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