276 lines
12 KiB
C++
276 lines
12 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_SAT_DECISION_H_
|
|
#define ORTOOLS_SAT_SAT_DECISION_H_
|
|
|
|
#include <cstdint>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "absl/types/span.h"
|
|
#include "ortools/base/strong_vector.h"
|
|
#include "ortools/sat/model.h"
|
|
#include "ortools/sat/sat_base.h"
|
|
#include "ortools/sat/sat_parameters.pb.h"
|
|
#include "ortools/sat/synchronization.h"
|
|
#include "ortools/sat/util.h"
|
|
#include "ortools/util/bitset.h"
|
|
#include "ortools/util/integer_pq.h"
|
|
#include "ortools/util/strong_integers.h"
|
|
|
|
namespace operations_research {
|
|
namespace sat {
|
|
|
|
// Implement the SAT branching policy responsible for deciding the next Boolean
|
|
// variable to branch on, and its polarity (true or false).
|
|
class SatDecisionPolicy {
|
|
public:
|
|
explicit SatDecisionPolicy(Model* model);
|
|
|
|
// Notifies that more variables are now present. Note that currently this may
|
|
// change the current variable order because the priority queue need to be
|
|
// reconstructed.
|
|
void IncreaseNumVariables(int num_variables);
|
|
|
|
// Reinitializes the decision heuristics (which variables to choose with which
|
|
// polarity) according to the current parameters. Note that this also resets
|
|
// the activity of the variables to 0. Note that this function is lazy, and
|
|
// the work will only happen on the first NextBranch() to cover the cases when
|
|
// this policy is not used at all.
|
|
void ResetDecisionHeuristic();
|
|
|
|
// Returns next decision to branch upon. This shouldn't be called if all the
|
|
// variables are assigned.
|
|
Literal NextBranch();
|
|
|
|
// Bumps the activity of all variables appearing in the conflict. All literals
|
|
// must be currently assigned. See VSIDS decision heuristic: Chaff:
|
|
// Engineering an Efficient SAT Solver. M.W. Moskewicz et al. ANNUAL ACM IEEE
|
|
// DESIGN AUTOMATION CONFERENCE 2001.
|
|
void BumpVariableActivities(absl::Span<const Literal> literals);
|
|
|
|
// Updates the increment used for activity bumps. This is basically the same
|
|
// as decaying all the variable activities, but it is a lot more efficient.
|
|
void UpdateVariableActivityIncrement();
|
|
|
|
// Called on Untrail() so that we can update the set of possible decisions.
|
|
void Untrail(int target_trail_index);
|
|
|
|
// Called on a new conflict before Untrail(). The trail before the given index
|
|
// is used in the phase saving heuristic as a partial assignment.
|
|
void BeforeConflict(int trail_index);
|
|
|
|
// By default, we alternate between a stable phase (better suited for finding
|
|
// SAT solution) and a more restart heavy phase more suited for proving UNSAT.
|
|
// This changes a bit the polarity heuristics and is controlled from within
|
|
// SatRestartPolicy.
|
|
void SetStablePhase(bool is_stable) { in_stable_phase_ = is_stable; }
|
|
bool InStablePhase() const { return in_stable_phase_; }
|
|
|
|
// This is used to temporarily disable phase_saving when we do some probing
|
|
// during search for instance.
|
|
void MaybeEnablePhaseSaving(bool save_phase) {
|
|
maybe_enable_phase_saving_ = save_phase;
|
|
}
|
|
|
|
// Gives a hint so the solver tries to find a solution with the given literal
|
|
// set to true. Currently this take precedence over the phase saving heuristic
|
|
// and a variable with a preference will always be branched on according to
|
|
// this preference.
|
|
//
|
|
// The weight is used as a tie-breaker between variable with the same
|
|
// activities. Larger weight will be selected first. A weight of zero is the
|
|
// default value for the other variables.
|
|
//
|
|
// Note(user): Having a lot of different weights may slow down the priority
|
|
// queue operations if there is millions of variables.
|
|
void SetAssignmentPreference(Literal literal, float weight);
|
|
|
|
// Returns the vector of the current assignment preferences.
|
|
std::vector<std::pair<Literal, float>> AllPreferences() const;
|
|
|
|
// Returns the current activity of a BooleanVariable.
|
|
double Activity(Literal l) const {
|
|
if (l.Variable() < activities_.size()) return activities_[l.Variable()];
|
|
return 0.0;
|
|
}
|
|
|
|
// Like SetAssignmentPreference() but it can be overridden by phase-saving.
|
|
void SetTargetPolarityIfUnassigned(Literal l) {
|
|
if (trail_.Assignment().VariableIsAssigned(l.Variable())) return;
|
|
has_target_polarity_[l.Variable()] = true;
|
|
target_polarity_[l.Variable()] = var_polarity_[l.Variable()] =
|
|
l.IsPositive();
|
|
best_partial_assignment_.push_back(l);
|
|
target_length_++;
|
|
}
|
|
absl::Span<const Literal> GetBestPartialAssignment() const {
|
|
return best_partial_assignment_;
|
|
}
|
|
void ClearBestPartialAssignment() {
|
|
target_length_ = 0;
|
|
has_target_polarity_.assign(has_target_polarity_.size(), false);
|
|
best_partial_assignment_.clear();
|
|
}
|
|
|
|
// Increases activities of variables in the best partial assignment to ensure
|
|
// they are branched on first in the same order until the next conflict.
|
|
// Activities before this call are scaled to become disambiguation terms.
|
|
// Future conflicts will bump activity by the largest increase applied by this
|
|
// method.
|
|
// This acts as a soft-reset of the decision policy, useful when exploring a
|
|
// new region of the search space.
|
|
void ResetActivitiesToFollowBestPartialAssignment();
|
|
|
|
private:
|
|
// Computes an initial variable ordering.
|
|
void InitializeVariableOrdering();
|
|
|
|
// Rescales activity value of all variables when one of them reached the max.
|
|
void RescaleVariableActivities(double scaling_factor);
|
|
|
|
// Reinitializes the initial polarity of all the variables with an index
|
|
// greater than or equal to the given one.
|
|
void ResetInitialPolarity(int from, bool inverted = false);
|
|
|
|
// Code used for resetting the initial polarity at the beginning of each
|
|
// phase.
|
|
void RephaseIfNeeded();
|
|
void UseLongestAssignmentAsInitialPolarity();
|
|
void FlipCurrentPolarity();
|
|
void RandomizeCurrentPolarity();
|
|
|
|
// This one returns false if there is no such solution to use.
|
|
bool UseLsSolutionAsInitialPolarity();
|
|
|
|
// Adds the given variable to var_ordering_ or updates its priority if it is
|
|
// already present.
|
|
void PqInsertOrUpdate(BooleanVariable var);
|
|
|
|
// Singleton model objects.
|
|
const SatParameters& parameters_;
|
|
const Trail& trail_;
|
|
ModelRandomGenerator* random_;
|
|
|
|
// TODO(user): This is in term of proto indices. Ideally we would need
|
|
// CpModelMapping to map that to Booleans but this currently lead to cyclic
|
|
// dependencies. For now we just assume one to one correspondence for the
|
|
// first entries. This will only work on pure Boolean problems.
|
|
SharedLsSolutionRepository* ls_hints_;
|
|
|
|
// Variable ordering (priority will be adjusted dynamically). queue_elements_
|
|
// holds the elements used by var_ordering_ (it uses pointers).
|
|
//
|
|
// Note that we recover the variable that a WeightedVarQueueElement refers to
|
|
// by its position in the queue_elements_ vector, and we can recover the later
|
|
// using (pointer - &queue_elements_[0]).
|
|
struct WeightedVarQueueElement {
|
|
// Interface for the IntegerPriorityQueue.
|
|
int Index() const { return var.value(); }
|
|
|
|
// Priority order. The IntegerPriorityQueue returns the largest element
|
|
// first.
|
|
//
|
|
// Note(user): We used to also break ties using the variable index, however
|
|
// this has two drawbacks:
|
|
// - On problem with many variables, this slow down quite a lot the priority
|
|
// queue operations (which do as little work as possible and hence benefit
|
|
// from having the majority of elements with a priority of 0).
|
|
// - It seems to be a bad heuristics. One reason could be that the priority
|
|
// queue will automatically diversify the choice of the top variables
|
|
// amongst the ones with the same priority.
|
|
//
|
|
// Note(user): For the same reason as explained above, it is probably a good
|
|
// idea not to have too many different values for the tie_breaker field. I
|
|
// am not even sure we should have such a field...
|
|
bool operator<(const WeightedVarQueueElement& other) const {
|
|
return weight < other.weight ||
|
|
(weight == other.weight && (tie_breaker < other.tie_breaker));
|
|
}
|
|
|
|
BooleanVariable var;
|
|
float tie_breaker;
|
|
|
|
// TODO(user): Experiment with float. In the rest of the code, we use
|
|
// double, but maybe we don't need that much precision. Using float here may
|
|
// save memory and make the PQ operations faster.
|
|
double weight;
|
|
};
|
|
static_assert(sizeof(WeightedVarQueueElement) == 16,
|
|
"ERROR_WeightedVarQueueElement_is_not_well_compacted");
|
|
|
|
bool var_ordering_is_initialized_ = false;
|
|
IntegerPriorityQueue<WeightedVarQueueElement> var_ordering_;
|
|
|
|
// This is used for the branching heuristic described in "Learning Rate Based
|
|
// Branching Heuristic for SAT solvers", J.H.Liang, V. Ganesh, P. Poupart,
|
|
// K.Czarnecki, SAT 2016.
|
|
//
|
|
// The entries are sorted by trail index, and one can get the number of
|
|
// conflicts during which a variable at a given trail index i was assigned by
|
|
// summing the entry.count for all entries with a trail index greater than i.
|
|
struct NumConflictsStackEntry {
|
|
int trail_index;
|
|
int64_t count;
|
|
};
|
|
int64_t num_conflicts_ = 0;
|
|
std::vector<NumConflictsStackEntry> num_conflicts_stack_;
|
|
|
|
// Whether the priority of the given variable needs to be updated in
|
|
// var_ordering_. Note that this is only accessed for assigned variables and
|
|
// that for efficiency it is indexed by trail indices. If
|
|
// pq_need_update_for_var_at_trail_index_[trail_->Info(var).trail_index] is
|
|
// true when we untrail var, then either var need to be inserted in the queue,
|
|
// or we need to notify that its priority has changed.
|
|
BitQueue64 pq_need_update_for_var_at_trail_index_;
|
|
|
|
// Increment used to bump the variable activities.
|
|
double variable_activity_increment_ = 1.0;
|
|
|
|
// Stores variable activity and the number of time each variable was "bumped".
|
|
// The later is only used with the ERWA heuristic.
|
|
util_intops::StrongVector<BooleanVariable, double> activities_;
|
|
util_intops::StrongVector<BooleanVariable, float> tie_breakers_;
|
|
util_intops::StrongVector<BooleanVariable, int64_t> num_bumps_;
|
|
|
|
// If the polarity if forced (externally) we always use this first.
|
|
util_intops::StrongVector<BooleanVariable, bool> has_forced_polarity_;
|
|
util_intops::StrongVector<BooleanVariable, bool> forced_polarity_;
|
|
|
|
// If we are in a stable phase, we follow the current target.
|
|
bool in_stable_phase_ = false;
|
|
int target_length_ = 0;
|
|
util_intops::StrongVector<BooleanVariable, bool> has_target_polarity_;
|
|
util_intops::StrongVector<BooleanVariable, bool> target_polarity_;
|
|
|
|
// Otherwise we follow var_polarity_ which is reset at the beginning of
|
|
// each new polarity phase. This is also overwritten by phase saving.
|
|
// Each phase last for an arithmetically increasing number of conflicts.
|
|
util_intops::StrongVector<BooleanVariable, bool> var_polarity_;
|
|
bool maybe_enable_phase_saving_ = true;
|
|
int64_t polarity_phase_ = 0;
|
|
int64_t num_conflicts_until_rephase_ = 1000;
|
|
|
|
// The longest partial assignment since the last reset.
|
|
std::vector<Literal> best_partial_assignment_;
|
|
|
|
// Used in InitializeVariableOrdering().
|
|
std::vector<BooleanVariable> tmp_variables_;
|
|
};
|
|
|
|
} // namespace sat
|
|
} // namespace operations_research
|
|
|
|
#endif // ORTOOLS_SAT_SAT_DECISION_H_
|