357 lines
15 KiB
C++
357 lines
15 KiB
C++
// Copyright 2010-2025 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_OLD_PRECEDENCES_PROPAGATOR_H_
|
|
#define OR_TOOLS_SAT_OLD_PRECEDENCES_PROPAGATOR_H_
|
|
|
|
#include <cstdint>
|
|
#include <deque>
|
|
#include <functional>
|
|
#include <vector>
|
|
|
|
#include "absl/container/inlined_vector.h"
|
|
#include "absl/log/check.h"
|
|
#include "absl/types/span.h"
|
|
#include "ortools/base/strong_vector.h"
|
|
#include "ortools/sat/integer.h"
|
|
#include "ortools/sat/integer_base.h"
|
|
#include "ortools/sat/model.h"
|
|
#include "ortools/sat/precedences.h"
|
|
#include "ortools/sat/sat_base.h"
|
|
#include "ortools/sat/sat_solver.h"
|
|
#include "ortools/sat/synchronization.h"
|
|
#include "ortools/util/bitset.h"
|
|
#include "ortools/util/strong_integers.h"
|
|
|
|
namespace operations_research {
|
|
namespace sat {
|
|
|
|
// =============================================================================
|
|
// Old precedences propagator.
|
|
//
|
|
// This is superseded by the new LinearPropagator and should only be used if the
|
|
// option 'new_linear_propagation' is false. We still keep it around to
|
|
// benchmark and test the new code vs this one.
|
|
// =============================================================================
|
|
|
|
// 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 works 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"),
|
|
relations_(model->GetOrCreate<EnforcedLinear2Bounds>()),
|
|
trail_(model->GetOrCreate<Trail>()),
|
|
integer_trail_(model->GetOrCreate<IntegerTrail>()),
|
|
shared_stats_(model->Mutable<SharedStatistics>()),
|
|
watcher_(model->GetOrCreate<GenericLiteralWatcher>()),
|
|
watcher_id_(watcher_->Register(this)) {
|
|
model->GetOrCreate<SatSolver>()->AddPropagator(this);
|
|
integer_trail_->RegisterWatcher(&modified_vars_);
|
|
watcher_->SetPropagatorPriority(watcher_id_, 0);
|
|
}
|
|
|
|
// This type is neither copyable nor movable.
|
|
PrecedencesPropagator(const PrecedencesPropagator&) = delete;
|
|
PrecedencesPropagator& operator=(const PrecedencesPropagator&) = delete;
|
|
~PrecedencesPropagator() override;
|
|
|
|
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);
|
|
|
|
// This version check current precedence. It is however "slow".
|
|
bool AddPrecedenceWithOffsetIfNew(IntegerVariable i1, IntegerVariable i2,
|
|
IntegerValue offset);
|
|
|
|
private:
|
|
DEFINE_STRONG_INDEX_TYPE(ArcIndex);
|
|
DEFINE_STRONG_INDEX_TYPE(OptionalArcIndex);
|
|
|
|
// 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;
|
|
|
|
// Update relations_.
|
|
void PushConditionalRelations(const ArcInfo& arc);
|
|
|
|
// External class needed to get the IntegerVariable lower bounds and Enqueue
|
|
// new ones.
|
|
EnforcedLinear2Bounds* relations_;
|
|
Trail* trail_;
|
|
IntegerTrail* integer_trail_;
|
|
SharedStatistics* shared_stats_ = nullptr;
|
|
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.
|
|
util_intops::StrongVector<IntegerVariable, absl::InlinedVector<ArcIndex, 6>>
|
|
impacted_arcs_;
|
|
util_intops::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_.
|
|
util_intops::StrongVector<IntegerVariable,
|
|
absl::InlinedVector<OptionalArcIndex, 6>>
|
|
impacted_potential_arcs_;
|
|
util_intops::StrongVector<OptionalArcIndex, ArcInfo> potential_arcs_;
|
|
|
|
// 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.
|
|
util_intops::StrongVector<LiteralIndex, absl::InlinedVector<ArcIndex, 6>>
|
|
literal_to_new_impacted_arcs_;
|
|
util_intops::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_;
|
|
|
|
// Stats.
|
|
int64_t num_cycles_ = 0;
|
|
int64_t num_pushes_ = 0;
|
|
int64_t num_enforcement_pushes_ = 0;
|
|
};
|
|
|
|
// =============================================================================
|
|
// 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.
|
|
// =============================================================================
|
|
|
|
// l => (a + b <= ub).
|
|
inline void AddConditionalSum2LowerOrEqual(
|
|
absl::Span<const Literal> enforcement_literals, IntegerVariable a,
|
|
IntegerVariable b, int64_t ub, Model* model) {
|
|
// TODO(user): Refactor to be sure we do not miss any level zero relations.
|
|
if (enforcement_literals.empty()) {
|
|
LinearExpression2 expr(a, b, 1, 1);
|
|
model->GetOrCreate<RootLevelLinear2Bounds>()->AddUpperBound(
|
|
expr, IntegerValue(ub));
|
|
}
|
|
|
|
PrecedencesPropagator* p = model->GetOrCreate<PrecedencesPropagator>();
|
|
p->AddPrecedenceWithAllOptions(a, NegationOf(b), IntegerValue(-ub),
|
|
kNoIntegerVariable, enforcement_literals);
|
|
}
|
|
|
|
// l => (a + b + c <= ub).
|
|
//
|
|
// TODO(user): Use level zero bounds to infer binary precedence relations?
|
|
inline void AddConditionalSum3LowerOrEqual(
|
|
absl::Span<const Literal> enforcement_literals, IntegerVariable a,
|
|
IntegerVariable b, IntegerVariable c, int64_t ub, Model* model) {
|
|
PrecedencesPropagator* p = model->GetOrCreate<PrecedencesPropagator>();
|
|
p->AddPrecedenceWithAllOptions(a, NegationOf(c), IntegerValue(-ub), b,
|
|
enforcement_literals);
|
|
}
|
|
|
|
// a == b.
|
|
//
|
|
// ABSL_DEPRECATED("Use linear constraint API instead")
|
|
inline std::function<void(Model*)> Equality(IntegerVariable a,
|
|
IntegerVariable b) {
|
|
return [=](Model* model) {
|
|
auto* precedences = model->GetOrCreate<PrecedencesPropagator>();
|
|
precedences->AddPrecedence(a, b);
|
|
precedences->AddPrecedence(b, a);
|
|
};
|
|
}
|
|
|
|
// is_le => (a + offset <= b).
|
|
//
|
|
// ABSL_DEPRECATED("Use linear constraint API instead")
|
|
inline std::function<void(Model*)> ConditionalLowerOrEqualWithOffset(
|
|
IntegerVariable a, IntegerVariable b, int64_t offset, Literal is_le) {
|
|
return [=](Model* model) {
|
|
PrecedencesPropagator* p = model->GetOrCreate<PrecedencesPropagator>();
|
|
p->AddConditionalPrecedenceWithOffset(a, b, IntegerValue(offset), is_le);
|
|
};
|
|
}
|
|
|
|
} // namespace sat
|
|
} // namespace operations_research
|
|
|
|
#endif // OR_TOOLS_SAT_OLD_PRECEDENCES_PROPAGATOR_H_
|