Files
ortools-clone/ortools/sat/old_precedences_propagator.h
Corentin Le Molgat 1b4d75ceb3 sat: backport from main
2025-11-05 13:55:12 +01:00

358 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 ORTOOLS_SAT_OLD_PRECEDENCES_PROPAGATOR_H_
#define ORTOOLS_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. Return false
// on conflict.
bool 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 // ORTOOLS_SAT_OLD_PRECEDENCES_PROPAGATOR_H_