Files
ortools-clone/ortools/bop/bop_util.cc
2025-04-03 16:18:34 +02:00

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