23#include "absl/container/flat_hash_map.h"
24#include "absl/status/status.h"
25#include "absl/strings/str_format.h"
29#if !defined(__PORTABLE_PLATFORM__)
41ABSL_FLAG(std::string, debug_dump_symmetry_graph_to_file,
"",
42 "If this flag is non-empty, an undirected graph whose"
43 " automorphism group is in one-to-one correspondence with the"
44 " symmetries of the SAT problem will be dumped to a file every"
45 " time FindLinearBooleanProblemSymmetries() is called.");
53 const SatSolver& solver, std::vector<bool>* assignment) {
56 assignment->push_back(
67template <
typename LinearTerms>
68std::string ValidateLinearTerms(
const LinearTerms& terms,
69 std::vector<bool>* variable_seen) {
73 const int max_num_errs = 100;
74 for (
int i = 0; i < terms.literals_size(); ++i) {
75 if (terms.literals(i) == 0) {
76 if (++num_errs <= max_num_errs) {
77 err_str += absl::StrFormat(
"Zero literal at position %d\n", i);
80 if (terms.coefficients(i) == 0) {
81 if (++num_errs <= max_num_errs) {
82 err_str += absl::StrFormat(
"Literal %d has a zero coefficient\n",
86 const int var = Literal(terms.literals(i)).Variable().value();
87 if (
var >= variable_seen->size()) {
88 if (++num_errs <= max_num_errs) {
89 err_str += absl::StrFormat(
"Out of bound variable %d\n",
var);
92 if ((*variable_seen)[
var]) {
93 if (++num_errs <= max_num_errs) {
94 err_str += absl::StrFormat(
"Duplicated variable %d\n",
var);
97 (*variable_seen)[
var] =
true;
100 for (
int i = 0; i < terms.literals_size(); ++i) {
101 const int var = Literal(terms.literals(i)).Variable().value();
102 (*variable_seen)[
var] =
false;
105 if (num_errs <= max_num_errs) {
106 err_str = absl::StrFormat(
"%d validation errors:\n", num_errs) + err_str;
109 absl::StrFormat(
"%d validation errors; here are the first %d:\n",
110 num_errs, max_num_errs) +
119template <
typename ProtoFormat>
120std::vector<LiteralWithCoeff> ConvertLinearExpression(
121 const ProtoFormat&
input) {
122 std::vector<LiteralWithCoeff> cst;
123 cst.reserve(
input.literals_size());
124 for (
int i = 0; i <
input.literals_size(); ++i) {
126 cst.push_back(LiteralWithCoeff(
literal,
input.coefficients(i)));
134 std::vector<bool> variable_seen(problem.
num_variables(),
false);
137 const std::string error = ValidateLinearTerms(constraint, &variable_seen);
138 if (!error.empty()) {
140 absl::StatusCode::kInvalidArgument,
141 absl::StrFormat(
"Invalid constraint %i: ", i) + error);
144 const std::string error =
145 ValidateLinearTerms(problem.
objective(), &variable_seen);
146 if (!error.empty()) {
147 return absl::Status(absl::StatusCode::kInvalidArgument,
148 absl::StrFormat(
"Invalid objective: ") + error);
150 return ::absl::OkStatus();
165 ct->set_name(constraint.name());
168 for (
int i = 0; i < constraint.literals_size(); ++i) {
170 const int lit = constraint.literals(i);
171 const int64_t coeff = constraint.coefficients(i);
182 linear->
add_domain(constraint.has_lower_bound()
183 ? constraint.lower_bound() + offset
185 linear->
add_domain(constraint.has_upper_bound()
186 ? constraint.upper_bound() + offset
192 for (
int i = 0; i < problem.
objective().literals_size(); ++i) {
217 coefficients_ref = -coefficients_ref;
229 LOG(
WARNING) <<
"The given problem is invalid!";
233 LOG(
INFO) <<
"Loading problem '" << problem.
name() <<
"', "
238 std::vector<LiteralWithCoeff> cst;
239 int64_t num_terms = 0;
240 int num_constraints = 0;
242 num_terms += constraint.literals_size();
243 cst = ConvertLinearExpression(constraint);
245 constraint.has_lower_bound(),
Coefficient(constraint.lower_bound()),
246 constraint.has_upper_bound(),
Coefficient(constraint.upper_bound()),
248 LOG(
INFO) <<
"Problem detected to be UNSAT when "
249 <<
"adding the constraint #" << num_constraints
250 <<
" with name '" << constraint.name() <<
"'";
256 LOG(
INFO) <<
"The problem contains " << num_terms <<
" terms.";
265 LOG(
WARNING) <<
"The given problem is invalid! " << status.message();
268#if !defined(__PORTABLE_PLATFORM__)
269 LOG(
INFO) <<
"LinearBooleanProblem memory: " << problem->SpaceUsedLong();
271 LOG(
INFO) <<
"Loading problem '" << problem->
name() <<
"', "
276 std::vector<LiteralWithCoeff> cst;
277 int64_t num_terms = 0;
278 int num_constraints = 0;
288 cst = ConvertLinearExpression(constraint);
293 LOG(
INFO) <<
"Problem detected to be UNSAT when "
294 <<
"adding the constraint #" << num_constraints
295 <<
" with name '" << constraint.
name() <<
"'";
304 LOG(
INFO) <<
"The problem contains " << num_terms <<
" terms.";
313 int64_t max_abs_weight = 0;
317 const double max_abs_weight_double = max_abs_weight;
321 const double abs_weight = std::abs(
coefficient) / max_abs_weight_double;
332 std::vector<LiteralWithCoeff> cst =
333 ConvertLinearExpression(problem.
objective());
342 std::vector<LiteralWithCoeff> cst =
343 ConvertLinearExpression(problem.
objective());
349 const std::vector<bool>& assignment) {
363 const std::vector<bool>& assignment) {
369 for (
int i = 0; i < constraint.literals_size(); ++i) {
372 sum += constraint.coefficients(i);
375 if (constraint.has_lower_bound() && sum < constraint.lower_bound()) {
376 LOG(
WARNING) <<
"Unsatisfied constraint! sum: " << sum <<
"\n"
380 if (constraint.has_upper_bound() && sum > constraint.upper_bound()) {
381 LOG(
WARNING) <<
"Unsatisfied constraint! sum: " << sum <<
"\n"
405 absl::flat_hash_map<int, int64_t> literal_to_weight;
406 std::vector<std::pair<int, int64_t>> non_slack_objective;
411 int64_t hard_weight = 1;
416 int signed_literal = objective.
literals(i);
425 signed_literal = -signed_literal;
429 if (
Literal(signed_literal).Variable() < first_slack_variable) {
430 non_slack_objective.push_back(std::make_pair(signed_literal,
weight));
435 output += absl::StrFormat(
"p wcnf %d %d %d\n", first_slack_variable,
437 non_slack_objective.size()),
440 output += absl::StrFormat(
"p cnf %d %d\n", problem.
num_variables(),
444 std::string constraint_output;
446 if (constraint.literals_size() == 0)
return "";
447 constraint_output.clear();
448 int64_t
weight = hard_weight;
449 for (
int i = 0; i < constraint.literals_size(); ++i) {
450 if (constraint.coefficients(i) != 1)
return "";
451 if (is_wcnf && abs(constraint.literals(i)) - 1 >= first_slack_variable) {
452 weight = literal_to_weight[constraint.literals(i)];
454 if (i > 0) constraint_output +=
" ";
459 output += absl::StrFormat(
"%d ",
weight);
461 output += constraint_output +
" 0\n";
466 for (std::pair<int, int64_t> p : non_slack_objective) {
489 const std::vector<int>& constraint_indices,
491 *subproblem = problem;
492 subproblem->
set_name(
"Subproblem of " + problem.
name());
494 for (
int index : constraint_indices) {
510 const std::pair<int, int64_t> key(type,
coefficient.value());
515 absl::flat_hash_map<std::pair<int, int64_t>,
int> id_map_;
533template <
typename Graph>
536 std::vector<int>* initial_equivalence_classes) {
540 std::vector<LiteralWithCoeff> cst;
542 cst = ConvertLinearExpression(constraint);
544 constraint.has_lower_bound(),
Coefficient(constraint.lower_bound()),
545 constraint.has_upper_bound(),
Coefficient(constraint.upper_bound()),
552 initial_equivalence_classes->clear();
556 enum NodeType { LITERAL_NODE, CONSTRAINT_NODE, CONSTRAINT_COEFFICIENT_NODE };
557 IdGenerator id_generator;
561 for (
int i = 0; i < num_variables; ++i) {
572 initial_equivalence_classes->assign(
574 id_generator.GetId(NodeType::LITERAL_NODE,
Coefficient(0)));
584 std::vector<LiteralWithCoeff> expr =
585 ConvertLinearExpression(problem.
objective());
588 (*initial_equivalence_classes)[term.literal.Index().value()] =
589 id_generator.GetId(NodeType::LITERAL_NODE, term.coefficient);
599 const int constraint_node_index = initial_equivalence_classes->size();
600 initial_equivalence_classes->push_back(id_generator.GetId(
601 NodeType::CONSTRAINT_NODE, canonical_problem.
Rhs(i)));
609 int current_node_index = constraint_node_index;
612 if (term.coefficient != previous_coefficient) {
613 current_node_index = initial_equivalence_classes->size();
614 initial_equivalence_classes->push_back(id_generator.GetId(
615 NodeType::CONSTRAINT_COEFFICIENT_NODE, term.coefficient));
616 previous_coefficient = term.coefficient;
621 graph->AddArc(constraint_node_index, current_node_index);
622 graph->AddArc(current_node_index, constraint_node_index);
628 graph->AddArc(current_node_index, term.literal.Index().value());
629 graph->AddArc(term.literal.Index().value(), current_node_index);
633 DCHECK_EQ(graph->num_nodes(), initial_equivalence_classes->size());
640 int64_t objective_offset = 0;
641 for (
int i = 0; i < mutable_objective->
literals_size(); ++i) {
642 const int signed_literal = mutable_objective->
literals(i);
643 if (signed_literal < 0) {
650 mutable_objective->
set_offset(mutable_objective->
offset() + objective_offset);
656 for (
int i = 0; i < constraint.literals_size(); ++i) {
657 if (constraint.literals(i) < 0) {
658 sum += constraint.coefficients(i);
659 constraint.set_literals(i, -constraint.literals(i));
660 constraint.set_coefficients(i, -constraint.coefficients(i));
663 if (constraint.has_lower_bound()) {
664 constraint.set_lower_bound(constraint.lower_bound() - sum);
666 if (constraint.has_upper_bound()) {
667 constraint.set_upper_bound(constraint.upper_bound() - sum);
674 std::vector<std::unique_ptr<SparsePermutation>>* generators) {
676 std::vector<int> equivalence_classes;
677 std::unique_ptr<Graph> graph(
678 GenerateGraphForSymmetryDetection<Graph>(problem, &equivalence_classes));
679 LOG(
INFO) <<
"Graph has " << graph->num_nodes() <<
" nodes and "
680 << graph->num_arcs() / 2 <<
" edges.";
681#if !defined(__PORTABLE_PLATFORM__)
682 if (!absl::GetFlag(FLAGS_debug_dump_symmetry_graph_to_file).empty()) {
684 std::vector<int> new_node_index(graph->num_nodes(), -1);
685 const int num_classes = 1 + *std::max_element(equivalence_classes.begin(),
686 equivalence_classes.end());
687 std::vector<int> class_size(num_classes, 0);
688 for (
const int c : equivalence_classes) ++class_size[c];
689 std::vector<int> next_index_by_class(num_classes, 0);
690 std::partial_sum(class_size.begin(), class_size.end() - 1,
691 next_index_by_class.begin() + 1);
692 for (
int node = 0; node < graph->num_nodes(); ++node) {
693 new_node_index[node] = next_index_by_class[equivalence_classes[node]]++;
695 std::unique_ptr<Graph> remapped_graph =
RemapGraph(*graph, new_node_index);
697 *remapped_graph, absl::GetFlag(FLAGS_debug_dump_symmetry_graph_to_file),
700 LOG(DFATAL) <<
"Error when writing the symmetry graph to file: "
707 std::vector<int> factorized_automorphism_group_size;
710 &factorized_automorphism_group_size));
715 double average_support_size = 0.0;
716 int num_generators = 0;
717 for (
int i = 0; i < generators->size(); ++i) {
719 std::vector<int> to_delete;
720 for (
int j = 0; j < permutation->
NumCycles(); ++j) {
722 to_delete.push_back(j);
725 for (
const int node : permutation->
Cycle(j)) {
732 if (!permutation->
Support().empty()) {
733 average_support_size += permutation->
Support().size();
734 swap((*generators)[num_generators], (*generators)[i]);
738 generators->resize(num_generators);
739 average_support_size /= num_generators;
740 LOG(
INFO) <<
"# of generators: " << num_generators;
741 LOG(
INFO) <<
"Average support size: " << average_support_size;
749 std::vector<LiteralWithCoeff> cst;
752 cst = ConvertLinearExpression(problem->
objective());
758 bound_shift.value());
760 mutable_objective->
add_literals(entry.literal.SignedValue());
766 cst = ConvertLinearExpression(constraint);
767 constraint.clear_literals();
768 constraint.clear_coefficients();
772 if (constraint.has_upper_bound()) {
773 constraint.set_upper_bound(constraint.upper_bound() +
774 bound_shift.value());
775 if (max_value <= constraint.upper_bound()) {
776 constraint.clear_upper_bound();
779 if (constraint.has_lower_bound()) {
780 constraint.set_lower_bound(constraint.lower_bound() +
781 bound_shift.value());
783 if (constraint.lower_bound() <= 0) {
784 constraint.clear_lower_bound();
789 if (constraint.has_lower_bound() || constraint.has_upper_bound()) {
791 constraint.add_literals(entry.literal.SignedValue());
792 constraint.add_coefficients(entry.coefficient.value());
800 for (
int i = 0; i < num_constraints; ++i) {
807 num_constraints - new_index);
811 for (LiteralIndex
index : mapping) {
829 for (
int iter = 0; iter < 6; ++iter) {
832 LOG(
INFO) <<
"UNSAT when loading the problem.";
842 if (equiv_map.
empty()) {
860 BooleanVariable new_var(0);
874 if (equiv_map[
index] >= 0) {
876 const BooleanVariable image = var_map[l.
Variable()];
877 CHECK_NE(image, BooleanVariable(-1));
#define CHECK_LT(val1, val2)
#define CHECK_EQ(val1, val2)
#define DCHECK_GE(val1, val2)
#define CHECK_NE(val1, val2)
#define DCHECK_EQ(val1, val2)
ABSL_FLAG(std::string, debug_dump_symmetry_graph_to_file, "", "If this flag is non-empty, an undirected graph whose" " automorphism group is in one-to-one correspondence with the" " symmetries of the SAT problem will be dumped to a file every" " time FindLinearBooleanProblemSymmetries() is called.")
void push_back(const value_type &x)
absl::Status FindSymmetries(std::vector< int > *node_equivalence_classes_io, std::vector< std::unique_ptr< SparsePermutation > > *generators, std::vector< int > *factorized_automorphism_group_size, TimeLimit *time_limit=nullptr)
const std::vector< int > & Support() const
void RemoveCycles(const std::vector< int > &cycle_indices)
Iterator Cycle(int i) const
void add_literals(::PROTOBUF_NAMESPACE_ID::int32 value)
int NumConstraints() const
bool AddLinearConstraint(bool use_lower_bound, Coefficient lower_bound, bool use_upper_bound, Coefficient upper_bound, std::vector< LiteralWithCoeff > *cst)
const std::vector< LiteralWithCoeff > & Constraint(int i) const
const Coefficient Rhs(int i) const
::operations_research::sat::ConstraintProto * add_constraints()
::operations_research::sat::IntegerVariableProto * add_variables()
::operations_research::sat::CpObjectiveProto * mutable_objective()
void add_coeffs(::PROTOBUF_NAMESPACE_ID::int64 value)
void set_offset(double value)
void add_vars(::PROTOBUF_NAMESPACE_ID::int32 value)
void set_scaling_factor(double value)
const std::string & name() const
::PROTOBUF_NAMESPACE_ID::int64 upper_bound() const
bool has_upper_bound() const
int literals_size() const
::PROTOBUF_NAMESPACE_ID::int64 lower_bound() const
void Swap(LinearBooleanConstraint *other)
void MergeFrom(const LinearBooleanConstraint &from)
bool has_lower_bound() const
::operations_research::sat::LinearBooleanConstraint * mutable_constraints(int index)
std::string * mutable_var_names(int index)
const std::string & name() const
::operations_research::sat::LinearBooleanConstraint * add_constraints()
void set_name(ArgT0 &&arg0, ArgT... args)
bool has_objective() const
int var_names_size() const
void set_num_variables(::PROTOBUF_NAMESPACE_ID::int32 value)
const ::operations_research::sat::LinearObjective & objective() const
::PROTOBUF_NAMESPACE_ID::int32 num_variables() const
const ::operations_research::sat::LinearBooleanConstraint & constraints(int index) const
const std::string & var_names(int index) const
::PROTOBUF_NAMESPACE_ID::int32 original_num_variables() const
::operations_research::sat::LinearObjective * mutable_objective()
int constraints_size() const
void add_domain(::PROTOBUF_NAMESPACE_ID::int64 value)
void add_coeffs(::PROTOBUF_NAMESPACE_ID::int64 value)
void add_vars(::PROTOBUF_NAMESPACE_ID::int32 value)
void add_coefficients(::PROTOBUF_NAMESPACE_ID::int64 value)
void clear_coefficients()
::PROTOBUF_NAMESPACE_ID::int64 coefficients(int index) const
int literals_size() const
void set_coefficients(int index, ::PROTOBUF_NAMESPACE_ID::int64 value)
void set_literals(int index, ::PROTOBUF_NAMESPACE_ID::int32 value)
void add_literals(::PROTOBUF_NAMESPACE_ID::int32 value)
::PROTOBUF_NAMESPACE_ID::RepeatedField< ::PROTOBUF_NAMESPACE_ID::int64 > * mutable_coefficients()
double scaling_factor() const
void set_offset(double value)
::PROTOBUF_NAMESPACE_ID::int32 literals(int index) const
void set_scaling_factor(double value)
int coefficients_size() const
LiteralIndex NegatedIndex() const
LiteralIndex Index() const
BooleanVariable Variable() const
std::string DebugString() const
bool log_search_progress() const
void FixVariable(Literal x)
void ApplyMapping(const absl::StrongVector< BooleanVariable, BooleanVariable > &mapping)
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)
const SatParameters & parameters() const
const VariablesAssignment & Assignment() const
const Trail & LiteralTrail() const
void SetAssignmentPreference(Literal literal, double weight)
void Backtrack(int target_level)
bool VariableIsAssigned(BooleanVariable var) const
bool LiteralIsTrue(Literal literal) const
Literal GetTrueLiteralForAssignedVariable(BooleanVariable var) const
int NumberOfVariables() const
Collection::value_type::second_type & LookupOrInsert(Collection *const collection, const typename Collection::value_type::first_type &key, const typename Collection::value_type::second_type &value)
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
std::tuple< int64_t, int64_t, const double > Coefficient
bool AddObjectiveConstraint(const LinearBooleanProblem &problem, bool use_lower_bound, Coefficient lower_bound, bool use_upper_bound, Coefficient upper_bound, SatSolver *solver)
void StoreAssignment(const VariablesAssignment &assignment, BooleanAssignment *output)
void UseObjectiveForSatAssignmentPreference(const LinearBooleanProblem &problem, SatSolver *solver)
bool ApplyLiteralMapping(const absl::StrongVector< LiteralIndex, LiteralIndex > &mapping, std::vector< LiteralWithCoeff > *cst, Coefficient *bound_shift, Coefficient *max_value)
void ExtractSubproblem(const LinearBooleanProblem &problem, const std::vector< int > &constraint_indices, LinearBooleanProblem *subproblem)
absl::Status ValidateBooleanProblem(const LinearBooleanProblem &problem)
bool AddObjectiveUpperBound(const LinearBooleanProblem &problem, Coefficient upper_bound, SatSolver *solver)
const LiteralIndex kTrueLiteralIndex(-2)
bool ComputeBooleanLinearExpressionCanonicalForm(std::vector< LiteralWithCoeff > *cst, Coefficient *bound_shift, Coefficient *max_value)
const LiteralIndex kFalseLiteralIndex(-3)
bool LoadAndConsumeBooleanProblem(LinearBooleanProblem *problem, SatSolver *solver)
void ApplyLiteralMappingToBooleanProblem(const absl::StrongVector< LiteralIndex, LiteralIndex > &mapping, LinearBooleanProblem *problem)
bool IsAssignmentValid(const LinearBooleanProblem &problem, const std::vector< bool > &assignment)
void ChangeOptimizationDirection(LinearBooleanProblem *problem)
void ProbeAndSimplifyProblem(SatPostsolver *postsolver, LinearBooleanProblem *problem)
void FindLinearBooleanProblemSymmetries(const LinearBooleanProblem &problem, std::vector< std::unique_ptr< SparsePermutation > > *generators)
Coefficient ComputeObjectiveValue(const LinearBooleanProblem &problem, const std::vector< bool > &assignment)
void ProbeAndFindEquivalentLiteral(SatSolver *solver, SatPostsolver *postsolver, DratProofHandler *drat_proof_handler, absl::StrongVector< LiteralIndex, LiteralIndex > *mapping)
CpModelProto BooleanProblemToCpModelproto(const LinearBooleanProblem &problem)
void MakeAllLiteralsPositive(LinearBooleanProblem *problem)
bool LoadBooleanProblem(const LinearBooleanProblem &problem, SatSolver *solver)
std::string LinearBooleanProblemToCnfString(const LinearBooleanProblem &problem)
void ExtractAssignment(const LinearBooleanProblem &problem, const SatSolver &solver, std::vector< bool > *assignment)
Graph * GenerateGraphForSymmetryDetection(const LinearBooleanProblem &problem, std::vector< int > *initial_equivalence_classes)
Collection of objects used to extend the Constraint Solver library.
std::string ProtobufDebugString(const P &message)
absl::Status WriteGraphToFile(const Graph &graph, const std::string &filename, bool directed, const std::vector< int > &num_nodes_with_color)
std::unique_ptr< Graph > RemapGraph(const Graph &graph, const std::vector< int > &new_node_index)
static int input(yyscan_t yyscanner)
std::vector< int >::const_iterator begin() const