Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
old_precedences_propagator.h
Go to the documentation of this file.
1// Copyright 2010-2025 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 ORTOOLS_SAT_OLD_PRECEDENCES_PROPAGATOR_H_
15#define ORTOOLS_SAT_OLD_PRECEDENCES_PROPAGATOR_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/log/check.h"
24#include "absl/types/span.h"
26#include "ortools/sat/integer.h"
28#include "ortools/sat/model.h"
33#include "ortools/util/bitset.h"
35
36namespace operations_research {
37namespace sat {
38
39// =============================================================================
40// Old precedences propagator.
41//
42// This is superseded by the new LinearPropagator and should only be used if the
43// option 'new_linear_propagation' is false. We still keep it around to
44// benchmark and test the new code vs this one.
45// =============================================================================
46
47// This class implement a propagator on simple inequalities between integer
48// variables of the form (i1 + offset <= i2). The offset can be constant or
49// given by the value of a third integer variable. Offsets can also be negative.
50//
51// The algorithm works by mapping the problem onto a graph where the edges carry
52// the offset and the nodes correspond to one of the two bounds of an integer
53// variable (lower_bound or -upper_bound). It then find the fixed point using an
54// incremental variant of the Bellman-Ford(-Tarjan) algorithm.
55//
56// This is also known as an "integer difference logic theory" in the SMT world.
57// Another word is "separation logic".
58//
59// TODO(user): We could easily generalize the code to support any relation of
60// the form a*X + b*Y + c*Z >= rhs (or <=). Do that since this class should be
61// a lot faster at propagating small linear inequality than the generic
62// propagator and the overhead of supporting coefficient should not be too bad.
64 public:
66 : SatPropagator("PrecedencesPropagator"),
67 relations_(model->GetOrCreate<EnforcedLinear2Bounds>()),
68 trail_(model->GetOrCreate<Trail>()),
69 integer_trail_(model->GetOrCreate<IntegerTrail>()),
70 shared_stats_(model->Mutable<SharedStatistics>()),
71 watcher_(model->GetOrCreate<GenericLiteralWatcher>()),
72 watcher_id_(watcher_->Register(this)) {
73 model->GetOrCreate<SatSolver>()->AddPropagator(this);
74 integer_trail_->RegisterWatcher(&modified_vars_);
75 watcher_->SetPropagatorPriority(watcher_id_, 0);
76 }
77
78 // This type is neither copyable nor movable.
83 bool Propagate() final;
84 bool Propagate(Trail* trail) final;
85 void Untrail(const Trail& trail, int trail_index) final;
86
87 // Propagates all the outgoing arcs of the given variable (and only those). It
88 // is more efficient to do all these propagation in one go by calling
89 // Propagate(), but for scheduling problem, we wants to propagate right away
90 // the end of an interval when its start moved.
91 bool PropagateOutgoingArcs(IntegerVariable var);
92
93 // Add a precedence relation (i1 + offset <= i2) between integer variables.
94 //
95 // Important: The optionality of the variable should be marked BEFORE this
96 // is called.
97 void AddPrecedence(IntegerVariable i1, IntegerVariable i2);
98 void AddPrecedenceWithOffset(IntegerVariable i1, IntegerVariable i2,
99 IntegerValue offset);
100 void AddPrecedenceWithVariableOffset(IntegerVariable i1, IntegerVariable i2,
101 IntegerVariable offset_var);
102
103 // Same as above, but the relation is only true when the given literal is.
104 void AddConditionalPrecedence(IntegerVariable i1, IntegerVariable i2,
105 Literal l);
106 void AddConditionalPrecedenceWithOffset(IntegerVariable i1,
107 IntegerVariable i2,
108 IntegerValue offset, Literal l);
109
110 // Generic function that cover all of the above case and more.
111 void AddPrecedenceWithAllOptions(IntegerVariable i1, IntegerVariable i2,
112 IntegerValue offset,
113 IntegerVariable offset_var,
114 absl::Span<const Literal> presence_literals);
115
116 // This version check current precedence. It is however "slow".
117 bool AddPrecedenceWithOffsetIfNew(IntegerVariable i1, IntegerVariable i2,
118 IntegerValue offset);
119
120 private:
121 DEFINE_STRONG_INDEX_TYPE(ArcIndex);
122 DEFINE_STRONG_INDEX_TYPE(OptionalArcIndex);
123
124 // Information about an individual arc.
125 struct ArcInfo {
126 IntegerVariable tail_var;
127 IntegerVariable head_var;
128
129 IntegerValue offset;
130 IntegerVariable offset_var; // kNoIntegerVariable if none.
131
132 // This arc is "present" iff all these literals are true.
133 absl::InlinedVector<Literal, 6> presence_literals;
134
135 // Used temporarily by our implementation of the Bellman-Ford algorithm. It
136 // should be false at the beginning of BellmanFordTarjan().
137 mutable bool is_marked;
138 };
139
140 // Internal functions to add new precedence relations.
141 //
142 // Note that internally, we only propagate lower bounds, so each time we add
143 // an arc, we actually create two of them: one on the given variables, and one
144 // on their negation.
145 void AdjustSizeFor(IntegerVariable i);
146 void AddArc(IntegerVariable tail, IntegerVariable head, IntegerValue offset,
147 IntegerVariable offset_var,
148 absl::Span<const Literal> presence_literals);
149
150 // Enqueue a new lower bound for the variable arc.head_lb that was deduced
151 // from the current value of arc.tail_lb and the offset of this arc.
152 bool EnqueueAndCheck(const ArcInfo& arc, IntegerValue new_head_lb,
153 Trail* trail);
154 IntegerValue ArcOffset(const ArcInfo& arc) const;
155
156 // Inspect all the optional arcs that needs inspection (to stay sparse) and
157 // check if their presence literal can be propagated to false. Return false
158 // on conflict.
159 bool PropagateOptionalArcs(Trail* trail);
160
161 // The core algorithm implementation is split in these functions. One must
162 // first call InitializeBFQueueWithModifiedNodes() that will push all the
163 // IntegerVariable whose lower bound has been modified since the last call.
164 // Then, BellmanFordTarjan() will take care of all the propagation and returns
165 // false in case of conflict. Internally, it uses DisassembleSubtree() which
166 // is the Tarjan variant to detect a possible positive cycle. Before exiting,
167 // it will call CleanUpMarkedArcsAndParents().
168 //
169 // The Tarjan version of the Bellam-Ford algorithm is really nice in our
170 // context because it was really easy to make it incremental. Moreover, it
171 // supports batch increment!
172 //
173 // This implementation is kind of unique because of our context and the fact
174 // that it is incremental, but a good reference is "Negative-cycle detection
175 // algorithms", Boris V. Cherkassky, Andrew V. Goldberg, 1996,
176 // http://people.cs.nctu.edu.tw/~tjshen/doc/ne.pdf
177 void InitializeBFQueueWithModifiedNodes();
178 bool BellmanFordTarjan(Trail* trail);
179 bool DisassembleSubtree(int source, int target,
180 std::vector<bool>* can_be_skipped);
181 void AnalyzePositiveCycle(ArcIndex first_arc, Trail* trail,
182 std::vector<Literal>* must_be_all_true,
183 std::vector<Literal>* literal_reason,
184 std::vector<IntegerLiteral>* integer_reason);
185 void CleanUpMarkedArcsAndParents();
186
187 // Loops over all the arcs and verify that there is no propagation left.
188 // This is only meant to be used in a DCHECK() and is not optimized.
189 bool NoPropagationLeft(const Trail& trail) const;
190
191 // Update relations_.
192 void PushConditionalRelations(const ArcInfo& arc);
193
194 // External class needed to get the IntegerVariable lower bounds and Enqueue
195 // new ones.
196 EnforcedLinear2Bounds* relations_;
197 Trail* trail_;
198 IntegerTrail* integer_trail_;
199 SharedStatistics* shared_stats_ = nullptr;
200 GenericLiteralWatcher* watcher_;
201 int watcher_id_;
202
203 // The key to our incrementality. This will be cleared once the propagation
204 // is done, and automatically updated by the integer_trail_ with all the
205 // IntegerVariable that changed since the last clear.
206 SparseBitset<IntegerVariable> modified_vars_;
207
208 // An arc needs to be inspected for propagation (i.e. is impacted) if its
209 // tail_var changed. If an arc has 3 variables (tail, offset, head), it will
210 // appear as 6 different entries in the arcs_ vector, one for each variable
211 // and its negation, each time with a different tail.
212 //
213 // TODO(user): rearranging the index so that the arc of the same node are
214 // consecutive like in StaticGraph should have a big performance impact.
215 //
216 // TODO(user): We do not need to store ArcInfo.tail_var here.
218 impacted_arcs_;
220
221 // This is similar to impacted_arcs_/arcs_ but it is only used to propagate
222 // one of the presence literals when the arc cannot be present. An arc needs
223 // to appear only once in potential_arcs_, but it will be referenced by
224 // all its variable in impacted_potential_arcs_.
225 util_intops::StrongVector<IntegerVariable,
226 absl::InlinedVector<OptionalArcIndex, 6>>
227 impacted_potential_arcs_;
229
230 // Each time a literal becomes true, this list the set of arcs for which we
231 // need to decrement their count. When an arc count reach zero, it must be
232 // added to the set of impacted_arcs_. Note that counts never becomes
233 // negative.
234 //
235 // TODO(user): Try a one-watcher approach instead. Note that in most cases
236 // arc should be controlled by 1 or 2 literals, so not sure it is worth it.
238 literal_to_new_impacted_arcs_;
240
241 // Temp vectors to hold the reason of an assignment.
242 std::vector<Literal> literal_reason_;
243 std::vector<IntegerLiteral> integer_reason_;
244
245 // Temp vectors for the Bellman-Ford algorithm. The graph in which this
246 // algorithm works is in one to one correspondence with the IntegerVariable in
247 // impacted_arcs_.
248 std::deque<int> bf_queue_;
249 std::vector<bool> bf_in_queue_;
250 std::vector<bool> bf_can_be_skipped_;
251 std::vector<ArcIndex> bf_parent_arc_of_;
252
253 // Temp vector used by the tree traversal in DisassembleSubtree().
254 std::vector<int> tmp_vector_;
255
256 // Stats.
257 int64_t num_cycles_ = 0;
258 int64_t num_pushes_ = 0;
259 int64_t num_enforcement_pushes_ = 0;
260};
261
262// =============================================================================
263// Implementation of the small API functions below.
264// =============================================================================
265
266inline void PrecedencesPropagator::AddPrecedence(IntegerVariable i1,
267 IntegerVariable i2) {
268 AddArc(i1, i2, /*offset=*/IntegerValue(0), /*offset_var=*/kNoIntegerVariable,
269 {});
271
273 IntegerVariable i1, IntegerVariable i2, IntegerValue offset) {
274 AddArc(i1, i2, offset, /*offset_var=*/kNoIntegerVariable, {});
275}
277inline void PrecedencesPropagator::AddConditionalPrecedence(IntegerVariable i1,
278 IntegerVariable i2,
279 Literal l) {
280 AddArc(i1, i2, /*offset=*/IntegerValue(0), /*offset_var=*/kNoIntegerVariable,
281 {l});
282}
283
285 IntegerVariable i1, IntegerVariable i2, IntegerValue offset, Literal l) {
286 AddArc(i1, i2, offset, /*offset_var=*/kNoIntegerVariable, {l});
287}
290 IntegerVariable i1, IntegerVariable i2, IntegerVariable offset_var) {
291 AddArc(i1, i2, /*offset=*/IntegerValue(0), offset_var, {});
292}
295 IntegerVariable i1, IntegerVariable i2, IntegerValue offset,
296 IntegerVariable offset_var, absl::Span<const Literal> presence_literals) {
297 AddArc(i1, i2, offset, offset_var, presence_literals);
299
300// =============================================================================
301// Model based functions.
302// =============================================================================
303
304// l => (a + b <= ub).
306 absl::Span<const Literal> enforcement_literals, IntegerVariable a,
307 IntegerVariable b, int64_t ub, Model* model) {
308 // TODO(user): Refactor to be sure we do not miss any level zero relations.
309 if (enforcement_literals.empty()) {
310 LinearExpression2 expr(a, b, 1, 1);
311 model->GetOrCreate<RootLevelLinear2Bounds>()->AddUpperBound(
312 expr, IntegerValue(ub));
313 }
314
315 PrecedencesPropagator* p = model->GetOrCreate<PrecedencesPropagator>();
316 p->AddPrecedenceWithAllOptions(a, NegationOf(b), IntegerValue(-ub),
317 kNoIntegerVariable, enforcement_literals);
318}
319
320// l => (a + b + c <= ub).
321//
322// TODO(user): Use level zero bounds to infer binary precedence relations?
324 absl::Span<const Literal> enforcement_literals, IntegerVariable a,
325 IntegerVariable b, IntegerVariable c, int64_t ub, Model* model) {
326 PrecedencesPropagator* p = model->GetOrCreate<PrecedencesPropagator>();
327 p->AddPrecedenceWithAllOptions(a, NegationOf(c), IntegerValue(-ub), b,
328 enforcement_literals);
330
331// a == b.
332//
333// ABSL_DEPRECATED("Use linear constraint API instead")
334inline std::function<void(Model*)> Equality(IntegerVariable a,
335 IntegerVariable b) {
336 return [=](Model* model) {
337 auto* precedences = model->GetOrCreate<PrecedencesPropagator>();
338 precedences->AddPrecedence(a, b);
339 precedences->AddPrecedence(b, a);
340 };
341}
342
343// is_le => (a + offset <= b).
344//
345// ABSL_DEPRECATED("Use linear constraint API instead")
346inline std::function<void(Model*)> ConditionalLowerOrEqualWithOffset(
347 IntegerVariable a, IntegerVariable b, int64_t offset, Literal is_le) {
348 return [=](Model* model) {
349 PrecedencesPropagator* p = model->GetOrCreate<PrecedencesPropagator>();
350 p->AddConditionalPrecedenceWithOffset(a, b, IntegerValue(offset), is_le);
351 };
353
354} // namespace sat
355} // namespace operations_research
356
357#endif // ORTOOLS_SAT_OLD_PRECEDENCES_PROPAGATOR_H_
Definition model.h:345
bool AddPrecedenceWithOffsetIfNew(IntegerVariable i1, IntegerVariable i2, IntegerValue offset)
void AddPrecedence(IntegerVariable i1, IntegerVariable i2)
void AddPrecedenceWithOffset(IntegerVariable i1, IntegerVariable i2, IntegerValue offset)
void AddPrecedenceWithAllOptions(IntegerVariable i1, IntegerVariable i2, IntegerValue offset, IntegerVariable offset_var, absl::Span< const Literal > presence_literals)
void AddPrecedenceWithVariableOffset(IntegerVariable i1, IntegerVariable i2, IntegerVariable offset_var)
PrecedencesPropagator & operator=(const PrecedencesPropagator &)=delete
void AddConditionalPrecedenceWithOffset(IntegerVariable i1, IntegerVariable i2, IntegerValue offset, Literal l)
void Untrail(const Trail &trail, int trail_index) final
void AddConditionalPrecedence(IntegerVariable i1, IntegerVariable i2, Literal l)
SatPropagator(const std::string &name)
Definition sat_base.h:786
void AddPropagator(SatPropagator *propagator)
std::function< void(Model *)> Equality(IntegerVariable v, int64_t value)
Definition integer.h:1876
std::vector< IntegerVariable > NegationOf(absl::Span< const IntegerVariable > vars)
Definition integer.cc:52
const IntegerVariable kNoIntegerVariable(-1)
void AddConditionalSum3LowerOrEqual(absl::Span< const Literal > enforcement_literals, IntegerVariable a, IntegerVariable b, IntegerVariable c, int64_t ub, Model *model)
void AddConditionalSum2LowerOrEqual(absl::Span< const Literal > enforcement_literals, IntegerVariable a, IntegerVariable b, int64_t ub, Model *model)
std::function< void(Model *)> ConditionalLowerOrEqualWithOffset(IntegerVariable a, IntegerVariable b, int64_t offset, Literal is_le)
OR-Tools root namespace.