27 #include "absl/container/btree_map.h"
28 #include "absl/container/btree_set.h"
29 #include "absl/container/flat_hash_set.h"
30 #include "absl/random/bit_gen_ref.h"
31 #include "absl/random/random.h"
32 #include "absl/strings/str_cat.h"
33 #include "absl/strings/str_format.h"
40 #include "ortools/sat/boolean_problem.pb.h"
48 #include "ortools/sat/sat_parameters.pb.h"
65 void Log(
const std::string&
message) {
79 std::string CnfObjectiveLine(
const LinearBooleanProblem& problem,
81 const double scaled_objective =
83 return absl::StrFormat(
"o %d",
static_cast<int64_t
>(scaled_objective));
86 struct LiteralWithCoreIndex {
95 template <
typename Vector>
96 void DeleteVectorIndices(
const std::vector<int>& indices, Vector* v) {
98 int indices_index = 0;
99 for (
int i = 0; i < v->size(); ++i) {
100 if (indices_index < indices.size() && i == indices[indices_index]) {
103 (*v)[new_size] = (*v)[i];
139 class FuMalikSymmetryBreaker {
141 FuMalikSymmetryBreaker() {}
144 void StartResolvingNewCore(
int new_core_index) {
145 literal_by_core_.resize(new_core_index);
146 for (
int i = 0; i < new_core_index; ++i) {
147 literal_by_core_[i].clear();
161 std::vector<Literal> ProcessLiteral(
int assumption_index, Literal
b) {
162 if (assumption_index >= info_by_assumption_index_.size()) {
163 info_by_assumption_index_.resize(assumption_index + 1);
170 std::vector<Literal> result;
171 for (LiteralWithCoreIndex data :
172 info_by_assumption_index_[assumption_index]) {
179 result.insert(result.end(), literal_by_core_[data.core_index].begin(),
180 literal_by_core_[data.core_index].end());
184 for (LiteralWithCoreIndex data :
185 info_by_assumption_index_[assumption_index]) {
186 literal_by_core_[data.core_index].push_back(data.literal);
188 info_by_assumption_index_[assumption_index].push_back(
189 LiteralWithCoreIndex(
b, literal_by_core_.size()));
194 void DeleteIndices(
const std::vector<int>& indices) {
195 DeleteVectorIndices(indices, &info_by_assumption_index_);
200 void ClearInfo(
int assumption_index) {
201 CHECK_LE(assumption_index, info_by_assumption_index_.size());
202 info_by_assumption_index_[assumption_index].clear();
206 void AddInfo(
int assumption_index, Literal
b) {
207 CHECK_GE(assumption_index, info_by_assumption_index_.size());
208 info_by_assumption_index_.resize(assumption_index + 1);
209 info_by_assumption_index_[assumption_index].push_back(
210 LiteralWithCoreIndex(
b, literal_by_core_.size()));
214 std::vector<std::vector<LiteralWithCoreIndex>> info_by_assumption_index_;
215 std::vector<std::vector<Literal>> literal_by_core_;
223 std::vector<Literal>* core) {
225 absl::btree_set<LiteralIndex> moved_last;
226 std::vector<Literal> candidate(core->begin(), core->end());
238 if (target_level == -1)
break;
256 if (candidate.empty() || solver->
ModelIsUnsat())
return;
257 moved_last.insert(candidate.back().Index());
262 if (candidate.size() < core->size()) {
263 VLOG(1) <<
"minimization " << core->size() <<
" -> " << candidate.size();
266 absl::flat_hash_set<LiteralIndex> set;
267 for (
const Literal l : candidate) set.insert(l.Index());
269 for (
const Literal l : *core) {
270 if (set.contains(l.Index())) {
271 (*core)[new_size++] = l;
274 core->resize(new_size);
284 const LinearBooleanProblem& problem,
286 std::vector<bool>* solution) {
288 FuMalikSymmetryBreaker symmetry;
309 std::vector<std::vector<Literal>> blocking_clauses;
310 std::vector<Literal> assumptions;
313 const LinearObjective& objective = problem.objective();
314 CHECK_GT(objective.coefficients_size(), 0);
315 const Coefficient unique_objective_coeff(std::abs(objective.coefficients(0)));
316 for (
int i = 0; i < objective.literals_size(); ++i) {
317 CHECK_EQ(std::abs(objective.coefficients(i)), unique_objective_coeff)
318 <<
"The basic Fu & Malik algorithm needs constant objective coeffs.";
324 blocking_clauses.push_back(std::vector<Literal>(1, min_literal));
327 assumptions.push_back(min_literal);
331 logger.Log(absl::StrFormat(
"c #weights:%u #vars:%d #constraints:%d",
332 assumptions.size(), problem.num_variables(),
333 problem.constraints_size()));
340 for (
int iter = 0;; ++iter) {
346 logger.Log(CnfObjectiveLine(problem, objective));
362 logger.Log(absl::StrFormat(
"c iter:%d core:%u", iter, core.size()));
365 if (core.size() == 1) {
369 std::find(assumptions.begin(), assumptions.end(), core[0]) -
371 CHECK_LT(
index, assumptions.size());
382 std::vector<int> to_delete(1,
index);
383 DeleteVectorIndices(to_delete, &assumptions);
384 DeleteVectorIndices(to_delete, &blocking_clauses);
385 symmetry.DeleteIndices(to_delete);
387 symmetry.StartResolvingNewCore(iter);
391 if (core.size() == 2) {
401 std::vector<LiteralWithCoeff> at_most_one_constraint;
402 std::vector<Literal> at_least_one_constraint;
410 for (
int i = 0; i < core.size(); ++i) {
415 std::find(assumptions.begin() +
index, assumptions.end(), core[i]) -
417 CHECK_LT(
index, assumptions.size());
420 const Literal a(BooleanVariable(old_num_variables + i),
true);
421 Literal b(BooleanVariable(old_num_variables + core.size() + i),
true);
422 if (core.size() == 2) {
423 b =
Literal(BooleanVariable(old_num_variables + 2),
true);
424 if (i == 1)
b =
b.Negated();
439 if (assumptions[
index].Variable() >= problem.num_variables()) {
444 blocking_clauses[
index].push_back(
b);
448 blocking_clauses[
index].push_back(
a);
450 blocking_clauses[
index].pop_back();
454 at_least_one_constraint.push_back(
b);
457 assumptions[
index] =
a.Negated();
463 &at_most_one_constraint);
473 LOG(INFO) <<
"Infeasible while adding a clause.";
481 const LinearBooleanProblem& problem,
483 std::vector<bool>* solution) {
485 FuMalikSymmetryBreaker symmetry;
493 std::vector<Literal> assumptions;
494 std::vector<Coefficient> costs;
495 std::vector<Literal> reference;
498 const LinearObjective& objective = problem.objective();
499 CHECK_GT(objective.coefficients_size(), 0);
500 for (
int i = 0; i < objective.literals_size(); ++i) {
502 const Coefficient coeff(objective.coefficients(i));
508 costs.push_back(coeff);
510 assumptions.push_back(
literal);
511 costs.push_back(-coeff);
515 reference = assumptions;
519 *std::max_element(costs.begin(), costs.end());
522 logger.Log(absl::StrFormat(
"c #weights:%u #vars:%d #constraints:%d",
523 assumptions.size(), problem.num_variables(),
524 problem.constraints_size()));
526 for (
int iter = 0;; ++iter) {
533 CHECK_GE(hardening_threshold, 0);
534 std::vector<int> to_delete;
535 int num_above_threshold = 0;
536 for (
int i = 0; i < assumptions.size(); ++i) {
537 if (costs[i] > hardening_threshold) {
541 to_delete.push_back(i);
542 ++num_above_threshold;
546 to_delete.push_back(i);
550 if (!to_delete.empty()) {
551 logger.Log(absl::StrFormat(
"c fixed %u assumptions, %d with cost > %d",
552 to_delete.size(), num_above_threshold,
553 hardening_threshold.value()));
554 DeleteVectorIndices(to_delete, &assumptions);
555 DeleteVectorIndices(to_delete, &costs);
556 DeleteVectorIndices(to_delete, &reference);
557 symmetry.DeleteIndices(to_delete);
562 std::vector<Literal> assumptions_subset;
563 for (
int i = 0; i < assumptions.size(); ++i) {
564 if (costs[i] >= stratified_lower_bound) {
565 assumptions_subset.push_back(assumptions[i]);
577 const Coefficient old_lower_bound = stratified_lower_bound;
579 if (
cost < old_lower_bound) {
580 if (stratified_lower_bound == old_lower_bound ||
581 cost > stratified_lower_bound) {
582 stratified_lower_bound =
cost;
590 static_cast<int64_t
>(problem.objective().offset()));
593 logger.Log(CnfObjectiveLine(problem, objective));
597 if (stratified_lower_bound < old_lower_bound)
continue;
617 for (
int i = 0; i < core.size(); ++i) {
619 std::find(assumptions.begin() +
index, assumptions.end(), core[i]) -
621 CHECK_LT(
index, assumptions.size());
628 logger.Log(absl::StrFormat(
629 "c iter:%d core:%u lb:%d min_cost:%d strat:%d", iter, core.size(),
630 lower_bound.value(), min_cost.value(), stratified_lower_bound.value()));
636 if (min_cost > stratified_lower_bound) {
637 stratified_lower_bound = min_cost;
641 if (core.size() == 1) {
645 std::find(assumptions.begin(), assumptions.end(), core[0]) -
647 CHECK_LT(
index, assumptions.size());
655 std::vector<int> to_delete(1,
index);
656 DeleteVectorIndices(to_delete, &assumptions);
657 DeleteVectorIndices(to_delete, &costs);
658 DeleteVectorIndices(to_delete, &reference);
659 symmetry.DeleteIndices(to_delete);
661 symmetry.StartResolvingNewCore(iter);
665 if (core.size() == 2) {
675 std::vector<LiteralWithCoeff> at_most_one_constraint;
676 std::vector<Literal> at_least_one_constraint;
684 for (
int i = 0; i < core.size(); ++i) {
689 std::find(assumptions.begin() +
index, assumptions.end(), core[i]) -
691 CHECK_LT(
index, assumptions.size());
694 const Literal a(BooleanVariable(old_num_variables + i),
true);
695 Literal b(BooleanVariable(old_num_variables + core.size() + i),
true);
696 if (core.size() == 2) {
697 b =
Literal(BooleanVariable(old_num_variables + 2),
true);
698 if (i == 1)
b =
b.Negated();
719 CHECK_GE(costs[
index], min_cost);
720 if (costs[
index] == min_cost) {
722 assumptions[
index] =
a.Negated();
732 symmetry.AddInfo(assumptions.size(),
b);
733 symmetry.ClearInfo(
index);
736 costs[
index] -= min_cost;
744 assumptions.push_back(
a.Negated());
745 costs.push_back(min_cost);
746 reference.push_back(reference[
index]);
758 at_least_one_constraint.push_back(reference[
index].Negated());
764 &at_most_one_constraint);
770 LOG(INFO) <<
"Unsat while adding a clause.";
778 LogBehavior log,
const LinearBooleanProblem& problem,
int num_times,
779 absl::BitGenRef random,
SatSolver* solver, std::vector<bool>* solution) {
781 const SatParameters initial_parameters = solver->
parameters();
783 SatParameters
parameters = initial_parameters;
788 int max_number_of_conflicts = 5;
794 for (
int i = 0; i < num_times; ++i) {
798 parameters.set_max_number_of_conflicts(max_number_of_conflicts);
804 const bool use_obj = absl::Bernoulli(random, 1.0 / 4);
823 std::vector<bool> candidate;
827 if (objective < best) {
828 *solution = candidate;
830 logger.Log(CnfObjectiveLine(problem, objective));
835 objective - 1, solver)) {
839 min_seen =
std::min(min_seen, objective);
840 max_seen =
std::max(max_seen, objective);
842 logger.Log(absl::StrCat(
843 "c ", objective.value(),
" [", min_seen.value(),
", ", max_seen.value(),
844 "] objective_preference: ", use_obj ?
"true" :
"false",
" ",
856 const LinearBooleanProblem& problem,
858 std::vector<bool>* solution) {
865 if (!solution->empty()) {
874 objective - 1, solver)) {
896 CHECK_LT(objective, old_objective);
897 logger.Log(CnfObjectiveLine(problem, objective));
903 std::vector<bool>* solution) {
905 std::deque<EncodingNode> repository;
909 std::vector<EncodingNode*>
nodes =
913 CHECK(!
nodes.empty());
920 if (!solution->empty()) {
927 logger.Log(absl::StrFormat(
"c #weights:%u #vars:%d #constraints:%d",
928 nodes.size(), problem.num_variables(),
929 problem.constraints_size()));
935 logger.Log(absl::StrFormat(
"c encoding depth:%d", root->
depth()));
941 const int index = offset.value() + objective.value();
964 CHECK_LT(objective, old_objective);
965 logger.Log(CnfObjectiveLine(problem, objective));
971 std::vector<bool>* solution) {
977 std::deque<EncodingNode> repository;
978 std::vector<EncodingNode*>
nodes =
985 if (!solution->empty()) {
991 logger.Log(absl::StrFormat(
"c #weights:%u #vars:%d #constraints:%d",
992 nodes.size(), problem.num_variables(),
993 problem.constraints_size()));
998 SatParameters::STRATIFICATION_DESCENT) {
1001 stratified_lower_bound =
std::max(stratified_lower_bound, n->weight());
1007 std::string previous_core_info =
"";
1008 for (
int iter = 0;; ++iter) {
1017 const std::string gap_string =
1022 absl::StrFormat(
"c iter:%d [%s] lb:%d%s assumptions:%u depth:%d", iter,
1025 static_cast<int64_t
>(problem.objective().offset()),
1026 gap_string,
nodes.size(), max_depth));
1033 std::vector<bool> temp_solution;
1038 *solution = temp_solution;
1039 logger.Log(CnfObjectiveLine(problem, obj));
1045 stratified_lower_bound =
1047 if (stratified_lower_bound > 0)
continue;
1059 previous_core_info =
1060 absl::StrFormat(
"core:%u mw:%d", core.size(), min_weight.value());
1063 if (stratified_lower_bound < min_weight &&
1065 SatParameters::STRATIFICATION_ASCENT) {
1066 stratified_lower_bound = min_weight;
1075 IntegerVariable objective_var,
1076 const std::function<
void()>& feasible_solution_observer,
Model*
model) {
1080 const SatParameters&
parameters = *(
model->GetOrCreate<SatParameters>());
1089 const IntegerValue objective = integer_trail->LowerBound(objective_var);
1092 if (feasible_solution_observer !=
nullptr) {
1093 feasible_solution_observer();
1095 if (
parameters.stop_after_first_solution()) {
1100 sat_solver->Backtrack(0);
1101 if (!integer_trail->Enqueue(
1110 IntegerVariable objective_var,
1111 const std::function<
void()>& feasible_solution_observer,
Model*
model) {
1112 const SatParameters old_params = *
model->GetOrCreate<SatParameters>();
1119 SatParameters new_params = old_params;
1120 new_params.set_max_number_of_conflicts(
1121 old_params.binary_search_num_conflicts());
1122 *
model->GetOrCreate<SatParameters>() = new_params;
1128 IntegerValue unknown_min = integer_trail->UpperBound(objective_var);
1129 IntegerValue unknown_max = integer_trail->LowerBound(objective_var);
1131 sat_solver->Backtrack(0);
1132 const IntegerValue lb = integer_trail->LowerBound(objective_var);
1133 const IntegerValue ub = integer_trail->UpperBound(objective_var);
1134 unknown_min =
std::min(unknown_min, ub);
1135 unknown_max =
std::max(unknown_max, lb);
1138 IntegerValue target;
1139 if (lb < unknown_min) {
1140 target = lb + (unknown_min - lb) / 2;
1141 }
else if (unknown_max < ub) {
1142 target = ub - (ub - unknown_max) / 2;
1144 VLOG(1) <<
"Binary-search, done.";
1147 VLOG(1) <<
"Binary-search, objective: [" << lb <<
"," << ub <<
"]"
1148 <<
" tried: [" << unknown_min <<
"," << unknown_max <<
"]"
1149 <<
" target: obj<=" << target;
1152 const Literal assumption = integer_encoder->GetOrCreateAssociatedLiteral(
1166 sat_solver->Backtrack(0);
1167 if (!integer_trail->Enqueue(
1176 const IntegerValue objective = integer_trail->LowerBound(objective_var);
1177 if (feasible_solution_observer !=
nullptr) {
1178 feasible_solution_observer();
1183 sat_solver->Backtrack(0);
1184 if (!integer_trail->Enqueue(
1192 unknown_min =
std::min(target, unknown_min);
1193 unknown_max =
std::max(target, unknown_max);
1199 sat_solver->Backtrack(0);
1200 *
model->GetOrCreate<SatParameters>() = old_params;
1227 std::vector<IntegerValue> assumption_weights,
1228 IntegerValue stratified_threshold, Model*
model,
1229 std::vector<std::vector<Literal>>* cores) {
1231 SatSolver* sat_solver =
model->GetOrCreate<SatSolver>();
1239 std::vector<Literal> core = sat_solver->GetLastIncompatibleDecisions();
1240 if (sat_solver->parameters().minimize_core()) {
1243 if (core.size() == 1) {
1244 if (!sat_solver->AddUnitClause(core[0].Negated())) {
1248 if (core.empty())
return sat_solver->UnsatStatus();
1249 cores->push_back(core);
1250 if (!sat_solver->parameters().find_multiple_cores())
break;
1254 std::vector<int> indices;
1256 absl::btree_set<Literal> temp(core.begin(), core.end());
1257 for (
int i = 0; i < assumptions.size(); ++i) {
1258 if (temp.contains(assumptions[i])) {
1259 indices.push_back(i);
1269 IntegerValue min_weight = assumption_weights[indices.front()];
1270 for (
const int i : indices) {
1271 min_weight =
std::min(min_weight, assumption_weights[i]);
1273 for (
const int i : indices) {
1274 assumption_weights[i] -= min_weight;
1280 for (
int i = 0; i < assumptions.size(); ++i) {
1281 if (assumption_weights[i] < stratified_threshold)
continue;
1282 assumptions[new_size] = assumptions[i];
1283 assumption_weights[new_size] = assumption_weights[i];
1286 assumptions.resize(new_size);
1287 assumption_weights.resize(new_size);
1288 }
while (!assumptions.empty());
1295 IntegerVariable objective_var,
1296 const std::vector<IntegerVariable>& variables,
1298 std::function<
void()> feasible_solution_observer,
Model*
model)
1299 : parameters_(
model->GetOrCreate<SatParameters>()),
1306 objective_var_(objective_var),
1307 feasible_solution_observer_(std::move(feasible_solution_observer)) {
1309 for (
int i = 0; i < variables.size(); ++i) {
1317 terms_.back().depth = 0;
1323 stratification_threshold_ = parameters_->max_sat_stratification() ==
1324 SatParameters::STRATIFICATION_NONE
1329 bool CoreBasedOptimizer::ProcessSolution() {
1332 IntegerValue objective(0);
1333 for (ObjectiveTerm& term : terms_) {
1335 objective += term.weight *
value;
1349 if (feasible_solution_observer_ !=
nullptr) {
1350 feasible_solution_observer_();
1352 if (parameters_->stop_after_first_solution()) {
1360 return integer_trail_->
Enqueue(
1364 bool CoreBasedOptimizer::PropagateObjectiveBounds() {
1366 bool some_bound_were_tightened =
true;
1367 while (some_bound_were_tightened) {
1368 some_bound_were_tightened =
false;
1373 IntegerValue implied_objective_lb(0);
1374 for (ObjectiveTerm& term : terms_) {
1375 const IntegerValue var_lb = integer_trail_->
LowerBound(term.var);
1376 term.old_var_lb = var_lb;
1377 implied_objective_lb += term.weight * var_lb.value();
1381 if (implied_objective_lb > integer_trail_->
LowerBound(objective_var_)) {
1383 objective_var_, implied_objective_lb),
1388 some_bound_were_tightened =
true;
1397 const IntegerValue gap =
1398 integer_trail_->
UpperBound(objective_var_) - implied_objective_lb;
1400 for (
const ObjectiveTerm& term : terms_) {
1401 if (term.weight == 0)
continue;
1402 const IntegerValue var_lb = integer_trail_->
LowerBound(term.var);
1403 const IntegerValue var_ub = integer_trail_->
UpperBound(term.var);
1404 if (var_lb == var_ub)
continue;
1411 if (gap / term.weight < var_ub - var_lb) {
1412 some_bound_were_tightened =
true;
1413 const IntegerValue new_ub = var_lb + gap / term.weight;
1414 DCHECK_LT(new_ub, var_ub);
1435 void CoreBasedOptimizer::ComputeNextStratificationThreshold() {
1436 std::vector<IntegerValue> weights;
1437 for (ObjectiveTerm& term : terms_) {
1438 if (term.weight >= stratification_threshold_)
continue;
1439 if (term.weight == 0)
continue;
1443 if (var_lb == var_ub)
continue;
1445 weights.push_back(term.weight);
1447 if (weights.empty()) {
1448 stratification_threshold_ = IntegerValue(0);
1453 stratification_threshold_ =
1454 weights[
static_cast<int>(std::floor(0.9 * weights.size()))];
1457 bool CoreBasedOptimizer::CoverOptimization() {
1462 constexpr
double max_dtime_per_core = 0.5;
1463 const double old_time_limit = parameters_->max_deterministic_time();
1464 parameters_->set_max_deterministic_time(max_dtime_per_core);
1466 parameters_->set_max_deterministic_time(old_time_limit);
1469 for (
const ObjectiveTerm& term : terms_) {
1473 if (term.depth == 0)
continue;
1479 const IntegerVariable
var = term.var;
1490 const double deterministic_limit =
1502 VLOG(1) <<
"cover_opt var:" <<
var <<
" domain:["
1504 if (!ProcessSolution())
return false;
1525 return PropagateObjectiveBounds();
1529 const std::vector<Literal>& literals,
1530 const std::vector<IntegerVariable>& vars,
1542 std::deque<EncodingNode> repository;
1543 std::vector<EncodingNode*>
nodes;
1546 for (
int i = 0; i < literals.size(); ++i) {
1548 repository.emplace_back(literals[i]);
1549 nodes.push_back(&repository.back());
1555 for (
int i = 0; i < vars.size(); ++i) {
1557 const IntegerVariable
var = vars[i];
1558 const IntegerValue var_lb = integer_trail_->
LowerBound(
var);
1559 const IntegerValue var_ub = integer_trail_->
UpperBound(
var);
1560 if (var_ub - var_lb == 1) {
1563 repository.emplace_back(lit);
1569 int ub =
static_cast<int>(var_ub.value() - var_lb.value());
1570 repository.emplace_back(lb, ub, [
var, var_lb,
this](
int x) {
1573 var_lb + IntegerValue(x + 1)));
1576 nodes.push_back(&repository.back());
1589 stratified_lower_bound =
std::max(stratified_lower_bound, n->weight());
1594 std::string previous_core_info =
"";
1595 for (
int iter = 0;;) {
1600 integer_trail_->
UpperBound(objective_var_).value() - offset.value());
1603 if (assumptions.empty()) {
1604 stratified_lower_bound =
1606 if (stratified_lower_bound > 0)
continue;
1611 const IntegerValue new_obj_lb(
lower_bound.value() + offset.value());
1612 if (new_obj_lb > integer_trail_->
LowerBound(objective_var_)) {
1625 absl::StrFormat(
"bool_core num_cores:%d [%s] assumptions:%u "
1626 "depth:%d fixed_bools:%d/%d",
1627 iter, previous_core_info,
nodes.size(), max_depth,
1628 num_fixed, num_bools),
1645 stratified_lower_bound =
1647 if (stratified_lower_bound > 0)
continue;
1654 if (parameters_->minimize_core()) {
1661 previous_core_info =
1662 absl::StrFormat(
"core:%u mw:%d d:%d", core.size(), min_weight.value(),
1663 nodes.back()->depth());
1680 std::vector<std::pair<LiteralIndex, Coefficient>> pairs;
1681 const int size = literals->size();
1682 for (
int i = 0; i < size; ++i) {
1683 pairs.push_back({(*literals)[i].Index(), (*
coefficients)[i]});
1685 std::sort(pairs.begin(), pairs.end());
1689 for (
const auto& [
index, coeff] : pairs) {
1691 if (pairs[new_size - 1].first ==
index) {
1692 pairs[new_size - 1].second += coeff;
1694 }
else if (pairs[new_size - 1].first ==
Literal(
index).NegatedIndex()) {
1696 pairs[new_size - 1].second -= coeff;
1701 pairs[new_size++] = {
index, coeff};
1703 pairs.resize(new_size);
1708 for (
const auto& [
index, coeff] : pairs) {
1712 }
else if (coeff < 0) {
1721 void CoreBasedOptimizer::PresolveObjectiveWithAtMostOne(
1722 std::vector<Literal>* literals, std::vector<Coefficient>*
coefficients,
1726 const int num_literals = implications_->
literal_size();
1741 std::vector<Literal> candidates;
1742 const int num_terms = literals->
size();
1743 for (
int i = 0; i < num_terms; ++i) {
1744 const Literal lit = (*literals)[i];
1750 weights[lit.Index()] = coeff;
1752 candidates.push_back(lit.Negated());
1753 is_candidate[lit.NegatedIndex()] =
true;
1756 int num_at_most_ones = 0;
1759 std::vector<Literal> at_most_one;
1760 std::vector<std::pair<Literal, Coefficient>> new_obj_terms;
1762 for (
const Literal root : candidates) {
1763 if (weights[root.NegatedIndex()] == 0)
continue;
1764 if (implications_->
WorkDone() > 1e8)
continue;
1767 CHECK_EQ(weights[root.Index()], 0);
1773 {root}, is_candidate, preferences);
1774 if (at_most_one.size() <= 1)
continue;
1782 for (
const Literal lit : at_most_one) {
1783 const Coefficient coeff = weights[lit.NegatedIndex()];
1784 lb_increase += coeff;
1785 max_coeff =
std::max(max_coeff, coeff);
1787 lb_increase -= max_coeff;
1789 *offset += lb_increase;
1790 overall_lb_increase += lb_increase;
1792 for (
const Literal lit : at_most_one) {
1793 is_candidate[lit.Index()] =
false;
1794 const Coefficient new_weight = max_coeff - weights[lit.NegatedIndex()];
1795 CHECK_EQ(weights[lit.Index()], 0);
1796 weights[lit.Index()] = new_weight;
1797 weights[lit.NegatedIndex()] = 0;
1798 if (new_weight > 0) {
1802 is_candidate[lit.NegatedIndex()] =
true;
1808 new_obj_terms.push_back({new_lit, max_coeff});
1811 at_most_one.push_back(new_lit);
1813 is_candidate.resize(implications_->
literal_size(),
false);
1817 if (overall_lb_increase > 0) {
1819 model_->
GetOrCreate<SharedResponseManager>()->UpdateInnerObjectiveBounds(
1820 absl::StrFormat(
"am1_presolve num_literals:%d num_am1:%d "
1821 "increase:%lld work_done:%lld",
1822 (
int)candidates.size(), num_at_most_ones,
1823 overall_lb_increase.value(), implications_->
WorkDone()),
1824 IntegerValue(offset->value()),
1831 for (
const Literal root : candidates) {
1832 if (weights[root.Index()] > 0) {
1833 CHECK_EQ(weights[root.NegatedIndex()], 0);
1834 literals->push_back(root);
1837 if (weights[root.NegatedIndex()] > 0) {
1838 CHECK_EQ(weights[root.Index()], 0);
1839 literals->push_back(root.Negated());
1843 for (
const auto& [lit, coeff] : new_obj_terms) {
1844 literals->push_back(lit);
1854 if (!parameters_->interleave_search()) {
1856 std::vector<Literal> literals;
1857 std::vector<IntegerVariable> vars;
1859 bool all_booleans =
true;
1860 IntegerValue
range(0);
1861 for (
const ObjectiveTerm& term : terms_) {
1862 const IntegerVariable
var = term.var;
1863 const IntegerValue coeff = term.weight;
1867 if (lb == ub)
continue;
1869 vars.push_back(
var);
1875 all_booleans =
false;
1894 PresolveObjectiveWithAtMostOne(&literals, &
coefficients, &offset);
1906 absl::btree_map<LiteralIndex, int> literal_to_term_index;
1920 if (parameters_->cover_optimization()) {
1926 std::vector<int> term_indices;
1927 std::vector<IntegerLiteral> integer_assumptions;
1928 std::vector<IntegerValue> assumption_weights;
1929 IntegerValue objective_offset(0);
1930 bool some_assumptions_were_skipped =
false;
1931 for (
int i = 0; i < terms_.size(); ++i) {
1932 const ObjectiveTerm term = terms_[i];
1935 if (term.weight == 0)
continue;
1941 const IntegerValue var_lb = integer_trail_->
LowerBound(term.var);
1942 const IntegerValue var_ub = integer_trail_->
UpperBound(term.var);
1943 if (var_lb == var_ub) {
1944 objective_offset += term.weight * var_lb.value();
1949 if (term.weight >= stratification_threshold_) {
1950 integer_assumptions.push_back(
1952 assumption_weights.push_back(term.weight);
1953 term_indices.push_back(i);
1955 some_assumptions_were_skipped =
true;
1960 if (term_indices.empty() && some_assumptions_were_skipped) {
1961 ComputeNextStratificationThreshold();
1966 if (term_indices.size() <= 2 && !some_assumptions_were_skipped) {
1967 VLOG(1) <<
"Switching to linear scan...";
1968 if (!already_switched_to_linear_scan_) {
1969 already_switched_to_linear_scan_ =
true;
1970 std::vector<IntegerVariable> constraint_vars;
1971 std::vector<int64_t> constraint_coeffs;
1972 for (
const int index : term_indices) {
1973 constraint_vars.push_back(terms_[
index].
var);
1974 constraint_coeffs.push_back(terms_[
index].
weight.value());
1976 constraint_vars.push_back(objective_var_);
1977 constraint_coeffs.push_back(-1);
1979 -objective_offset.value()));
1983 objective_var_, feasible_solution_observer_, model_);
1989 for (
const ObjectiveTerm& term : terms_) {
1990 max_depth =
std::max(max_depth, term.depth);
1992 const int64_t lb = integer_trail_->
LowerBound(objective_var_).value();
1993 const int64_t ub = integer_trail_->
UpperBound(objective_var_).value();
1997 :
static_cast<int>(std::ceil(
1998 100.0 * (ub - lb) /
std::max(std::abs(ub), std::abs(lb))));
1999 VLOG(1) << absl::StrCat(
"unscaled_next_obj_range:[", lb,
",", ub,
2002 gap,
"%",
" assumptions:", term_indices.size(),
2003 " strat:", stratification_threshold_.value(),
2004 " depth:", max_depth,
2009 std::vector<Literal> assumptions;
2010 literal_to_term_index.clear();
2011 for (
int i = 0; i < integer_assumptions.size(); ++i) {
2013 integer_assumptions[i]));
2021 literal_to_term_index[assumptions.back().Index()] = term_indices[i];
2029 std::vector<std::vector<Literal>> cores;
2031 FindCores(assumptions, assumption_weights, stratification_threshold_,
2037 if (cores.empty()) {
2038 ComputeNextStratificationThreshold();
2047 for (
const std::vector<Literal>& core : cores) {
2050 if (core.size() == 1) {
2060 bool ignore_this_core =
false;
2062 IntegerValue max_weight(0);
2063 IntegerValue new_var_lb(1);
2064 IntegerValue new_var_ub(0);
2066 for (
const Literal lit : core) {
2067 const int index = literal_to_term_index.at(lit.Index());
2071 if (terms_[
index].old_var_lb <
2073 ignore_this_core =
true;
2084 if (ignore_this_core)
continue;
2086 VLOG(1) << absl::StrFormat(
2087 "core:%u weight:[%d,%d] domain:[%d,%d] depth:%d", core.size(),
2088 min_weight.value(), max_weight.value(), new_var_lb.value(),
2089 new_var_ub.value(), new_depth);
2093 const IntegerVariable new_var =
2095 terms_.push_back({new_var, min_weight, new_depth});
2096 terms_.back().cover_ub = new_var_ub;
2100 std::vector<IntegerVariable> constraint_vars;
2101 std::vector<int64_t> constraint_coeffs;
2102 for (
const Literal lit : core) {
2103 const int index = literal_to_term_index.at(lit.Index());
2104 terms_[
index].weight -= min_weight;
2105 constraint_vars.push_back(terms_[
index].
var);
2106 constraint_coeffs.push_back(1);
2108 constraint_vars.push_back(new_var);
2109 constraint_coeffs.push_back(-1);
void resize(size_type new_size)
bool Contains(int64_t value) const
Returns true iff value is in Domain.
double GetTimeLeft() const
bool LimitReached() const
A simple class to enforce both an elapsed time limit and a deterministic time limit in the same threa...
bool LimitReached()
Returns true when the external limit is true, or the deterministic time is over the deterministic lim...
double GetElapsedDeterministicTime() const
Returns the elapsed deterministic time since the construction of this object.
std::vector< Literal > ExpandAtMostOneWithWeight(const absl::Span< const Literal > at_most_one, const absl::StrongVector< LiteralIndex, bool > &can_be_included, const absl::StrongVector< LiteralIndex, double > &expanded_lp_values)
int64_t literal_size() const
CoreBasedOptimizer(IntegerVariable objective_var, const std::vector< IntegerVariable > &variables, const std::vector< IntegerValue > &coefficients, std::function< void()> feasible_solution_observer, Model *model)
SatSolver::Status OptimizeWithSatEncoding(const std::vector< Literal > &literals, const std::vector< IntegerVariable > &vars, const std::vector< Coefficient > &coefficients, Coefficient offset)
SatSolver::Status Optimize()
Literal literal(int i) const
Literal GetOrCreateAssociatedLiteral(IntegerLiteral i_lit)
ABSL_MUST_USE_RESULT bool Enqueue(IntegerLiteral i_lit, absl::Span< const Literal > literal_reason, absl::Span< const IntegerLiteral > integer_reason)
bool IsCurrentlyIgnored(IntegerVariable i) const
IntegerValue UpperBound(IntegerVariable i) const
IntegerValue LevelZeroUpperBound(IntegerVariable var) const
IntegerVariable AddIntegerVariable(IntegerValue lower_bound, IntegerValue upper_bound)
IntegerValue LevelZeroLowerBound(IntegerVariable var) const
IntegerValue LowerBound(IntegerVariable i) const
const Domain & InitialVariableDomain(IntegerVariable var) const
Class that owns everything related to a particular optimization model.
T Add(std::function< T(Model *)> f)
This makes it possible to have a nicer API on the client side, and it allows both of these forms:
T * GetOrCreate()
Returns an object of type T that is unique to this model (like a "local" singleton).
bool AddLinearConstraint(bool use_lower_bound, Coefficient lower_bound, bool use_upper_bound, Coefficient upper_bound, std::vector< LiteralWithCoeff > *cst)
void SetNumVariables(int num_variables)
bool AddTernaryClause(Literal a, Literal b, Literal c)
const SatParameters & parameters() const
bool ModelIsUnsat() const
void ResetDecisionHeuristic()
Status ResetAndSolveWithGivenAssumptions(const std::vector< Literal > &assumptions)
BooleanVariable NewBooleanVariable()
int64_t NumFixedVariables() const
void SetAssumptionLevel(int assumption_level)
const VariablesAssignment & Assignment() const
int EnqueueDecisionAndBackjumpOnConflict(Literal true_literal)
void SetParameters(const SatParameters ¶meters)
bool AddBinaryClause(Literal a, Literal b)
void Backtrack(int target_level)
bool AddProblemClause(absl::Span< const Literal > literals, bool is_safe=true)
std::vector< Literal > GetLastIncompatibleDecisions()
int CurrentDecisionLevel() const
Status EnqueueDecisionAndBacktrackOnConflict(Literal true_literal, int *first_propagation_index=nullptr)
bool AddUnitClause(Literal true_literal)
bool LiteralIsTrue(Literal literal) const
bool LiteralIsFalse(Literal literal) const
ModelSharedTimeLimit * time_limit
absl::Span< const double > coefficients
#define DISALLOW_COPY_AND_ASSIGN(TypeName)
absl::Cleanup< absl::decay_t< Callback > > MakeCleanup(Callback &&callback)
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
std::tuple< int64_t, int64_t, const double > Coefficient
void RandomizeDecisionHeuristic(absl::BitGenRef random, SatParameters *parameters)
bool AddObjectiveConstraint(const LinearBooleanProblem &problem, bool use_lower_bound, Coefficient lower_bound, bool use_upper_bound, Coefficient upper_bound, SatSolver *solver)
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
void RestrictObjectiveDomainWithBinarySearch(IntegerVariable objective_var, const std::function< void()> &feasible_solution_observer, Model *model)
double AddOffsetAndScaleObjectiveValue(const LinearBooleanProblem &problem, Coefficient v)
SatSolver::Status ResetAndSolveIntegerProblem(const std::vector< Literal > &assumptions, Model *model)
SatSolver::Status SolveWithCardinalityEncodingAndCore(LogBehavior log, const LinearBooleanProblem &problem, SatSolver *solver, std::vector< bool > *solution)
Coefficient ComputeCoreMinWeight(const std::vector< EncodingNode * > &nodes, const std::vector< Literal > &core)
EncodingNode * MergeAllNodesWithDeque(Coefficient upper_bound, const std::vector< EncodingNode * > &nodes, SatSolver *solver, std::deque< EncodingNode > *repository)
void PresolveBooleanLinearExpression(std::vector< Literal > *literals, std::vector< Coefficient > *coefficients, Coefficient *offset)
std::vector< Literal > ReduceNodesAndExtractAssumptions(Coefficient upper_bound, Coefficient stratified_lower_bound, Coefficient *lower_bound, std::vector< EncodingNode * > *nodes, SatSolver *solver)
void UseObjectiveForSatAssignmentPreference(const LinearBooleanProblem &problem, SatSolver *solver)
SatSolver::Status SolveWithLinearScan(LogBehavior log, const LinearBooleanProblem &problem, SatSolver *solver, std::vector< bool > *solution)
SatSolver::Status SolveWithRandomParameters(LogBehavior log, const LinearBooleanProblem &problem, int num_times, absl::BitGenRef random, SatSolver *solver, std::vector< bool > *solution)
void MinimizeCore(SatSolver *solver, std::vector< Literal > *core)
std::function< void(Model *)> WeightedSumLowerOrEqual(const std::vector< IntegerVariable > &vars, const VectorInt &coefficients, int64_t upper_bound)
SatSolver::Status SolveWithWPM1(LogBehavior log, const LinearBooleanProblem &problem, SatSolver *solver, std::vector< bool > *solution)
bool IsAssignmentValid(const LinearBooleanProblem &problem, const std::vector< bool > &assignment)
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
void MinimizeCoreWithPropagation(TimeLimit *limit, SatSolver *solver, std::vector< Literal > *core)
Coefficient ComputeObjectiveValue(const LinearBooleanProblem &problem, const std::vector< bool > &assignment)
SatSolver::Status SolveWithFuMalik(LogBehavior log, const LinearBooleanProblem &problem, SatSolver *solver, std::vector< bool > *solution)
bool ProcessCore(const std::vector< Literal > &core, Coefficient min_weight, std::deque< EncodingNode > *repository, std::vector< EncodingNode * > *nodes, SatSolver *solver)
Coefficient MaxNodeWeightSmallerThan(const std::vector< EncodingNode * > &nodes, Coefficient upper_bound)
std::function< int64_t(const Model &)> LowerBound(IntegerVariable v)
int MoveOneUnprocessedLiteralLast(const absl::btree_set< LiteralIndex > &processed, int relevant_prefix_size, std::vector< Literal > *literals)
SatSolver::Status SolveWithCardinalityEncoding(LogBehavior log, const LinearBooleanProblem &problem, SatSolver *solver, std::vector< bool > *solution)
void ExtractAssignment(const LinearBooleanProblem &problem, const SatSolver &solver, std::vector< bool > *assignment)
std::vector< EncodingNode * > CreateInitialEncodingNodes(const std::vector< Literal > &literals, const std::vector< Coefficient > &coeffs, Coefficient *offset, std::deque< EncodingNode > *repository)
const Coefficient kCoefficientMax(std::numeric_limits< Coefficient::ValueType >::max())
SatSolver::Status MinimizeIntegerVariableWithLinearScanAndLazyEncoding(IntegerVariable objective_var, const std::function< void()> &feasible_solution_observer, Model *model)
Collection of objects used to extend the Constraint Solver library.
std::string ProtobufShortDebugString(const P &message)
const std::optional< Range > & range
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
static IntegerLiteral GreaterOrEqual(IntegerVariable i, IntegerValue bound)
#define VLOG(verboselevel)
#define VLOG_IS_ON(verboselevel)