212 lines
7.3 KiB
C++
212 lines
7.3 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.
|
|
|
|
#include "ortools/bop/bop_util.h"
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <algorithm>
|
|
#include <limits>
|
|
#include <vector>
|
|
|
|
#include "absl/log/check.h"
|
|
#include "ortools/base/strong_vector.h"
|
|
#include "ortools/bop/bop_base.h"
|
|
#include "ortools/bop/bop_solution.h"
|
|
#include "ortools/bop/bop_types.h"
|
|
#include "ortools/sat/boolean_problem.h"
|
|
#include "ortools/sat/boolean_problem.pb.h"
|
|
#include "ortools/sat/pb_constraint.h"
|
|
#include "ortools/sat/restart.h"
|
|
#include "ortools/sat/sat_base.h"
|
|
#include "ortools/sat/sat_solver.h"
|
|
#include "ortools/util/bitset.h"
|
|
#include "ortools/util/strong_integers.h"
|
|
|
|
namespace operations_research {
|
|
namespace bop {
|
|
namespace {
|
|
static const int kMaxLubyIndex = 30;
|
|
static const int kMaxBoost = 30;
|
|
|
|
// Loads the problem state into the SAT solver. If the problem has already been
|
|
// loaded in the sat_solver, fixed variables and objective bounds are updated.
|
|
// Returns false when the problem is proved UNSAT.
|
|
bool InternalLoadStateProblemToSatSolver(const ProblemState& problem_state,
|
|
sat::SatSolver* sat_solver) {
|
|
const bool first_time = (sat_solver->NumVariables() == 0);
|
|
if (first_time) {
|
|
sat_solver->SetNumVariables(
|
|
problem_state.original_problem().num_variables());
|
|
} else {
|
|
// Backtrack the solver to be able to add new constraints.
|
|
sat_solver->Backtrack(0);
|
|
}
|
|
|
|
// Set the fixed variables first so that loading the problem will be faster.
|
|
for (VariableIndex var(0); var < problem_state.is_fixed().size(); ++var) {
|
|
if (problem_state.is_fixed()[var]) {
|
|
if (!sat_solver->AddUnitClause(
|
|
sat::Literal(sat::BooleanVariable(var.value()),
|
|
problem_state.fixed_values()[var]))) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Load the problem if not done yet.
|
|
if (first_time &&
|
|
!LoadBooleanProblem(problem_state.original_problem(), sat_solver)) {
|
|
return false;
|
|
}
|
|
|
|
// Constrain the objective cost to be greater or equal to the lower bound,
|
|
// and to be smaller than the upper bound. If enforcing the strictier upper
|
|
// bound constraint leads to an UNSAT problem, it means the current solution
|
|
// is proved optimal (if the solution is feasible, else the problem is proved
|
|
// infeasible).
|
|
if (!AddObjectiveConstraint(
|
|
problem_state.original_problem(),
|
|
problem_state.lower_bound() != std::numeric_limits<int64_t>::min(),
|
|
sat::Coefficient(problem_state.lower_bound()),
|
|
problem_state.upper_bound() != std::numeric_limits<int64_t>::max(),
|
|
sat::Coefficient(problem_state.upper_bound() - 1), sat_solver)) {
|
|
return false;
|
|
}
|
|
|
|
// Adds the new binary clauses.
|
|
sat_solver->TrackBinaryClauses(true);
|
|
if (!sat_solver->AddBinaryClauses(problem_state.NewlyAddedBinaryClauses())) {
|
|
return false;
|
|
}
|
|
sat_solver->ClearNewlyAddedBinaryClauses();
|
|
|
|
return true;
|
|
}
|
|
} // anonymous namespace
|
|
|
|
BopOptimizerBase::Status LoadStateProblemToSatSolver(
|
|
const ProblemState& problem_state, sat::SatSolver* sat_solver) {
|
|
if (InternalLoadStateProblemToSatSolver(problem_state, sat_solver)) {
|
|
return BopOptimizerBase::CONTINUE;
|
|
}
|
|
|
|
return problem_state.solution().IsFeasible()
|
|
? BopOptimizerBase::OPTIMAL_SOLUTION_FOUND
|
|
: BopOptimizerBase::INFEASIBLE;
|
|
}
|
|
|
|
void ExtractLearnedInfoFromSatSolver(sat::SatSolver* solver,
|
|
LearnedInfo* info) {
|
|
CHECK(nullptr != solver);
|
|
CHECK(nullptr != info);
|
|
|
|
// This should never be called if the problem is UNSAT.
|
|
CHECK(!solver->ModelIsUnsat());
|
|
|
|
// Fixed variables.
|
|
info->fixed_literals.clear();
|
|
const sat::Trail& propagation_trail = solver->LiteralTrail();
|
|
const int root_size = solver->CurrentDecisionLevel() == 0
|
|
? propagation_trail.Index()
|
|
: solver->Decisions().front().trail_index;
|
|
for (int trail_index = 0; trail_index < root_size; ++trail_index) {
|
|
info->fixed_literals.push_back(propagation_trail[trail_index]);
|
|
}
|
|
|
|
// Binary clauses.
|
|
info->binary_clauses = solver->NewlyAddedBinaryClauses();
|
|
solver->ClearNewlyAddedBinaryClauses();
|
|
}
|
|
|
|
void SatAssignmentToBopSolution(const sat::VariablesAssignment& assignment,
|
|
BopSolution* solution) {
|
|
CHECK(solution != nullptr);
|
|
|
|
// Only extract the variables of the initial problem.
|
|
CHECK_LE(solution->Size(), assignment.NumberOfVariables());
|
|
for (sat::BooleanVariable var(0); var < solution->Size(); ++var) {
|
|
CHECK(assignment.VariableIsAssigned(var));
|
|
const bool value = assignment.LiteralIsTrue(sat::Literal(var, true));
|
|
const VariableIndex bop_var_id(var.value());
|
|
solution->SetValue(bop_var_id, value);
|
|
}
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// AdaptiveParameterValue
|
|
//------------------------------------------------------------------------------
|
|
AdaptiveParameterValue::AdaptiveParameterValue(double initial_value)
|
|
: value_(initial_value), num_changes_(0) {}
|
|
|
|
void AdaptiveParameterValue::Reset() { num_changes_ = 0; }
|
|
|
|
void AdaptiveParameterValue::Increase() {
|
|
++num_changes_;
|
|
const double factor = 1.0 + 1.0 / (num_changes_ / 2.0 + 1);
|
|
value_ = std::min(1.0 - (1.0 - value_) / factor, value_ * factor);
|
|
}
|
|
|
|
void AdaptiveParameterValue::Decrease() {
|
|
++num_changes_;
|
|
const double factor = 1.0 + 1.0 / (num_changes_ / 2.0 + 1);
|
|
value_ = std::max(value_ / factor, 1.0 - (1.0 - value_) * factor);
|
|
}
|
|
|
|
//------------------------------------------------------------------------------
|
|
// LubyAdaptiveParameterValue
|
|
//------------------------------------------------------------------------------
|
|
LubyAdaptiveParameterValue::LubyAdaptiveParameterValue(double initial_value)
|
|
: luby_id_(0),
|
|
luby_boost_(0),
|
|
luby_value_(0),
|
|
difficulties_(kMaxLubyIndex, AdaptiveParameterValue(initial_value)) {
|
|
Reset();
|
|
}
|
|
|
|
void LubyAdaptiveParameterValue::Reset() {
|
|
luby_id_ = 0;
|
|
luby_boost_ = 0;
|
|
luby_value_ = 0;
|
|
for (int i = 0; i < difficulties_.size(); ++i) {
|
|
difficulties_[i].Reset();
|
|
}
|
|
}
|
|
|
|
void LubyAdaptiveParameterValue::IncreaseParameter() {
|
|
const int luby_msb = MostSignificantBitPosition64(luby_value_);
|
|
difficulties_[luby_msb].Increase();
|
|
}
|
|
|
|
void LubyAdaptiveParameterValue::DecreaseParameter() {
|
|
const int luby_msb = MostSignificantBitPosition64(luby_value_);
|
|
difficulties_[luby_msb].Decrease();
|
|
}
|
|
|
|
double LubyAdaptiveParameterValue::GetParameterValue() const {
|
|
const int luby_msb = MostSignificantBitPosition64(luby_value_);
|
|
return difficulties_[luby_msb].value();
|
|
}
|
|
|
|
bool LubyAdaptiveParameterValue::BoostLuby() {
|
|
++luby_boost_;
|
|
return luby_boost_ >= kMaxBoost;
|
|
}
|
|
|
|
void LubyAdaptiveParameterValue::UpdateLuby() {
|
|
++luby_id_;
|
|
luby_value_ = sat::SUniv(luby_id_) << luby_boost_;
|
|
}
|
|
} // namespace bop
|
|
} // namespace operations_research
|