20#include "absl/container/flat_hash_map.h"
21#include "absl/memory/memory.h"
22#include "absl/strings/str_join.h"
23#include "google/protobuf/repeated_field.h"
37 std::size_t operator()(
const std::vector<int64_t>& values)
const {
39 for (
const int64_t
value : values) {
54 int GetId(
const std::vector<int64_t>& color) {
58 int NextFreeId()
const {
return id_map_.size(); }
61 absl::flat_hash_map<std::vector<int64_t>, int, VectorHash> id_map_;
67template <
typename FieldInt64Type>
69 const google::protobuf::RepeatedField<FieldInt64Type>& repeated_field,
70 std::vector<int64_t>* vector) {
71 CHECK(vector !=
nullptr);
72 for (
const FieldInt64Type
value : repeated_field) {
73 vector->push_back(
value);
91template <
typename Graph>
93 const CpModelProto& problem, std::vector<int>* initial_equivalence_classes,
94 SolverLogger* logger) {
95 CHECK(initial_equivalence_classes !=
nullptr);
97 const int num_variables = problem.variables_size();
98 auto graph = absl::make_unique<Graph>();
108 VAR_COEFFICIENT_NODE,
111 IdGenerator color_id_generator;
112 initial_equivalence_classes->clear();
113 auto new_node = [&initial_equivalence_classes, &graph,
114 &color_id_generator](
const std::vector<int64_t>& color) {
117 const int node = initial_equivalence_classes->size();
118 initial_equivalence_classes->push_back(color_id_generator.GetId(color));
122 graph->AddNode(node);
132 std::vector<int64_t> objective_by_var(num_variables, 0);
133 for (
int i = 0; i < problem.objective().vars_size(); ++i) {
134 const int ref = problem.objective().vars(i);
136 const int64_t
coeff = problem.objective().coeffs(i);
142 std::vector<int64_t> tmp_color;
143 for (
int v = 0; v < num_variables; ++v) {
144 tmp_color = {VARIABLE_NODE, objective_by_var[v]};
145 Append(problem.variables(v).domain(), &tmp_color);
151 absl::flat_hash_map<std::pair<int64_t, int64_t>,
int> coefficient_nodes;
152 auto get_coefficient_node = [&new_node, &graph, &coefficient_nodes,
153 &tmp_color](
int var, int64_t
coeff) {
154 const int var_node =
var;
160 if (
coeff == 1)
return var_node;
163 coefficient_nodes.insert({std::make_pair(
var,
coeff), 0});
164 if (!insert.second)
return insert.first->second;
166 tmp_color = {VAR_COEFFICIENT_NODE,
coeff};
167 const int secondary_node = new_node(tmp_color);
168 graph->AddArc(var_node, secondary_node);
169 insert.first->second = secondary_node;
170 return secondary_node;
176 auto get_literal_node = [&get_coefficient_node](
int ref) {
192 absl::flat_hash_set<std::pair<int, int>> implications;
193 auto get_implication_node = [&new_node, &graph, &coefficient_nodes,
194 &tmp_color](
int ref) {
198 coefficient_nodes.insert({std::make_pair(
var,
coeff), 0});
199 if (!insert.second)
return insert.first->second;
200 tmp_color = {VAR_COEFFICIENT_NODE,
coeff};
201 const int secondary_node = new_node(tmp_color);
202 graph->AddArc(
var, secondary_node);
203 insert.first->second = secondary_node;
204 return secondary_node;
206 auto add_implication = [&get_implication_node, &graph, &implications](
207 int ref_a,
int ref_b) {
208 const auto insert = implications.insert({ref_a, ref_b});
209 if (!insert.second)
return;
210 graph->AddArc(get_implication_node(ref_a), get_implication_node(ref_b));
214 graph->AddArc(get_implication_node(
NegatedRef(ref_b)),
219 absl::flat_hash_map<int, int> interval_constraint_index_to_node;
222 for (
int constraint_index = 0; constraint_index < problem.constraints_size();
223 ++constraint_index) {
224 const ConstraintProto& constraint = problem.constraints(constraint_index);
225 const int constraint_node = initial_equivalence_classes->size();
226 std::vector<int64_t> color = {CONSTRAINT_NODE,
227 constraint.constraint_case()};
229 switch (constraint.constraint_case()) {
240 Append(constraint.linear().domain(), &color);
241 CHECK_EQ(constraint_node, new_node(color));
242 for (
int i = 0; i < constraint.linear().vars_size(); ++i) {
243 const int ref = constraint.linear().vars(i);
246 ? constraint.linear().coeffs(i)
247 : -constraint.linear().coeffs(i);
248 graph->AddArc(get_coefficient_node(variable_node,
coeff),
254 CHECK_EQ(constraint_node, new_node(color));
255 for (
const int ref : constraint.bool_or().literals()) {
256 graph->AddArc(get_literal_node(ref), constraint_node);
261 if (constraint.at_most_one().literals().size() == 2) {
263 add_implication(constraint.at_most_one().literals(0),
264 NegatedRef(constraint.at_most_one().literals(1)));
268 CHECK_EQ(constraint_node, new_node(color));
269 for (
const int ref : constraint.at_most_one().literals()) {
270 graph->AddArc(get_literal_node(ref), constraint_node);
275 CHECK_EQ(constraint_node, new_node(color));
276 for (
const int ref : constraint.exactly_one().literals()) {
277 graph->AddArc(get_literal_node(ref), constraint_node);
282 CHECK_EQ(constraint_node, new_node(color));
283 for (
const int ref : constraint.bool_xor().literals()) {
284 graph->AddArc(get_literal_node(ref), constraint_node);
291 if (constraint.enforcement_literal_size() != 1) {
294 "[Symmetry] BoolAnd with multiple enforcement literal are not "
295 "supported in symmetry code:",
296 constraint.ShortDebugString());
300 CHECK_EQ(constraint.enforcement_literal_size(), 1);
301 const int ref_a = constraint.enforcement_literal(0);
302 for (
const int ref_b : constraint.bool_and().literals()) {
303 add_implication(ref_a, ref_b);
310 std::vector<int>
nodes;
311 for (
int indicator = 0; indicator <= 2; ++indicator) {
312 const LinearExpressionProto& expr =
313 indicator == 0 ? constraint.interval().start()
314 : indicator == 1 ? constraint.interval().size()
315 : constraint.interval().end();
317 std::vector<int64_t> local_color = color;
318 local_color.push_back(indicator);
319 local_color.push_back(expr.offset());
320 const int local_node = new_node(local_color);
321 nodes.push_back(local_node);
323 for (
int i = 0; i < expr.vars().size(); ++i) {
324 const int ref = expr.vars(i);
326 const int64_t
coeff =
328 graph->AddArc(get_coefficient_node(var_node,
coeff), local_node);
334 interval_constraint_index_to_node[constraint_index] = constraint_node;
348 CHECK_EQ(constraint_node, new_node(color));
349 for (
const int interval : constraint.no_overlap().intervals()) {
350 graph->AddArc(interval_constraint_index_to_node.at(
interval),
365 CHECK_EQ(constraint_node, new_node(color));
366 const int size = constraint.no_overlap_2d().x_intervals().size();
367 for (
int i = 0; i < size; ++i) {
368 const int x = constraint.no_overlap_2d().x_intervals(i);
369 const int y = constraint.no_overlap_2d().y_intervals(i);
370 graph->AddArc(interval_constraint_index_to_node.at(x),
372 graph->AddArc(interval_constraint_index_to_node.at(x),
373 interval_constraint_index_to_node.at(y));
384 VLOG(1) <<
"Unsupported constraint type "
395 for (
const int ref : constraint.enforcement_literal()) {
396 graph->AddArc(constraint_node, get_literal_node(ref));
402 DCHECK_EQ(graph->num_nodes(), initial_equivalence_classes->size());
409 const int num_nodes = graph->num_nodes();
410 std::vector<int> in_degree(num_nodes, 0);
411 std::vector<int> out_degree(num_nodes, 0);
412 for (
int i = 0; i < num_nodes; ++i) {
413 out_degree[i] = graph->OutDegree(i);
414 for (
const int head : (*graph)[i]) {
418 for (
int i = 0; i < num_nodes; ++i) {
419 if (in_degree[i] >= num_nodes || out_degree[i] >= num_nodes) {
420 SOLVER_LOG(logger,
"[Symmetry] Too many multi-arcs in symmetry code.");
433 int next_id = color_id_generator.NextFreeId();
434 for (
int i = 0; i < num_variables; ++i) {
435 if ((*graph)[i].empty()) {
436 (*initial_equivalence_classes)[i] = next_id++;
442 std::vector<int> mapping(next_id, -1);
443 for (
int& ref : *initial_equivalence_classes) {
444 if (mapping[ref] == -1) {
445 ref = mapping[ref] =
id++;
457 std::vector<std::unique_ptr<SparsePermutation>>* generators,
459 CHECK(generators !=
nullptr);
464 std::vector<int> equivalence_classes;
465 std::unique_ptr<Graph> graph(GenerateGraphForSymmetryDetection<Graph>(
466 problem, &equivalence_classes, logger));
467 if (graph ==
nullptr)
return;
469 SOLVER_LOG(logger,
"[Symmetry] Graph for symmetry has ", graph->num_nodes(),
470 " nodes and ", graph->num_arcs(),
" arcs.");
471 if (graph->num_nodes() == 0)
return;
474 std::vector<int> factorized_automorphism_group_size;
478 &equivalence_classes, generators, &factorized_automorphism_group_size,
485 "[Symmetry] GraphSymmetryFinder error: ",
status.message());
491 double average_support_size = 0.0;
492 int num_generators = 0;
493 int num_duplicate_constraints = 0;
494 for (
int i = 0; i < generators->size(); ++i) {
496 std::vector<int> to_delete;
497 for (
int j = 0; j < permutation->
NumCycles(); ++j) {
502 to_delete.push_back(j);
505 for (
const int node : permutation->
Cycle(j)) {
513 if (!permutation->
Support().empty()) {
514 average_support_size += permutation->
Support().size();
515 swap((*generators)[num_generators], (*generators)[i]);
518 ++num_duplicate_constraints;
521 generators->resize(num_generators);
522 average_support_size /= num_generators;
523 SOLVER_LOG(logger,
"[Symmetry] Symmetry computation done. time: ",
526 if (num_generators > 0) {
527 SOLVER_LOG(logger,
"[Symmetry] #generators: ", num_generators,
528 ", average support size: ", average_support_size);
529 if (num_duplicate_constraints > 0) {
530 SOLVER_LOG(logger,
"[Symmetry] The model contains ",
531 num_duplicate_constraints,
" duplicate constraints !");
541 std::vector<std::unique_ptr<SparsePermutation>> generators;
544 if (generators.empty()) {
549 for (
const std::unique_ptr<SparsePermutation>& perm : generators) {
551 const int num_cycle = perm->NumCycles();
552 for (
int i = 0; i < num_cycle; ++i) {
553 const int old_size = perm_proto->
support().size();
554 for (
const int var : perm->Cycle(i)) {
562 if (orbitope.empty())
return;
563 SOLVER_LOG(logger,
"[Symmetry] Found orbitope of size ", orbitope.size(),
564 " x ", orbitope[0].size());
568 for (
const std::vector<int>&
row : orbitope) {
569 for (
const int entry :
row) {
591void OrbitAndPropagation(
const std::vector<int>& orbits,
int var,
592 std::vector<int>* can_be_fixed_to_false,
597 if (!
context->CanBeUsedAsLiteral(
var))
return;
613 auto* sat_solver =
model.GetOrCreate<SatSolver>();
614 auto* mapping =
model.GetOrCreate<CpModelMapping>();
615 const Literal to_propagate = mapping->Literal(
var);
617 const VariablesAssignment& assignment = sat_solver->Assignment();
618 if (assignment.LiteralIsAssigned(to_propagate))
return;
619 sat_solver->EnqueueDecisionAndBackjumpOnConflict(to_propagate);
620 if (sat_solver->CurrentDecisionLevel() != 1)
return;
623 can_be_fixed_to_false->clear();
625 const int orbit_index = orbits[
var];
626 const int num_variables = orbits.size();
627 for (
int var = 0;
var < num_variables; ++
var) {
628 if (orbits[
var] != orbit_index)
continue;
635 if (assignment.LiteralIsFalse(mapping->Literal(
var))) {
636 can_be_fixed_to_false->push_back(
var);
639 if (!can_be_fixed_to_false->empty()) {
641 "[Symmetry] Num fixable by binary propagation in orbit: ",
642 can_be_fixed_to_false->size(),
" / ", orbit_size);
653 if (
context->working_model->has_objective()) {
654 context->WriteObjectiveToProto();
656 context->WriteVariableDomainsToProto();
660 int64_t num_added = 0;
663 for (
int var = 0;
var < num_vars; ++
var) {
665 if (
context->VariableWasRemoved(
var))
continue;
666 if (
context->VariableIsNotUsedAnymore(
var))
continue;
673 auto* arg =
ct->mutable_linear();
677 arg->add_coeffs(-r.
coeff);
678 arg->add_domain(r.
offset);
679 arg->add_domain(r.
offset);
682 std::vector<std::unique_ptr<SparsePermutation>> generators;
687 context->working_model->mutable_constraints()->DeleteSubrange(
688 initial_ct_index, num_added);
690 if (generators.empty())
return true;
697 std::vector<const google::protobuf::RepeatedField<int32_t>*> at_most_ones;
712 int distinguished_var = -1;
713 std::vector<int> can_be_fixed_to_false;
716 const std::vector<int> orbits =
GetOrbits(num_vars, generators);
717 std::vector<int> orbit_sizes;
718 int max_orbit_size = 0;
719 for (
int var = 0;
var < num_vars; ++
var) {
720 const int rep = orbits[
var];
721 if (rep == -1)
continue;
722 if (rep >= orbit_sizes.size()) orbit_sizes.resize(rep + 1, 0);
724 if (orbit_sizes[rep] > max_orbit_size) {
725 distinguished_var =
var;
726 max_orbit_size = orbit_sizes[rep];
731 if (
context->logger()->LoggingIsEnabled()) {
732 std::vector<int> sorted_sizes;
733 for (
const int s : orbit_sizes) {
734 if (s != 0) sorted_sizes.push_back(s);
736 std::sort(sorted_sizes.begin(), sorted_sizes.end(), std::greater<int>());
737 const int num_orbits = sorted_sizes.size();
738 if (num_orbits > 10) sorted_sizes.resize(10);
740 " orbits with sizes: ", absl::StrJoin(sorted_sizes,
","),
741 (num_orbits > sorted_sizes.size() ?
",..." :
""));
745 if (max_orbit_size > 2) {
746 OrbitAndPropagation(orbits, distinguished_var, &can_be_fixed_to_false,
749 const int first_heuristic_size = can_be_fixed_to_false.size();
762 std::vector<int> tmp_to_clear;
763 std::vector<int> tmp_sizes(num_vars, 0);
764 for (
const google::protobuf::RepeatedField<int32_t>* literals :
766 tmp_to_clear.clear();
770 for (
const int literal : *literals) {
775 const int rep = orbits[
var];
776 if (rep == -1)
continue;
779 if (tmp_sizes[rep] == 0) tmp_to_clear.push_back(rep);
780 if (tmp_sizes[rep] > 0) ++num_fixable;
785 if (num_fixable > can_be_fixed_to_false.size()) {
786 distinguished_var = -1;
787 can_be_fixed_to_false.clear();
788 for (
const int literal : *literals) {
793 const int rep = orbits[
var];
794 if (rep == -1)
continue;
795 if (distinguished_var == -1 ||
796 orbit_sizes[rep] > orbit_sizes[orbits[distinguished_var]]) {
797 distinguished_var =
var;
801 if (tmp_sizes[rep] == 0) can_be_fixed_to_false.push_back(
var);
806 for (
const int rep : tmp_to_clear) tmp_sizes[rep] = 0;
810 if (can_be_fixed_to_false.size() > first_heuristic_size) {
813 "[Symmetry] Num fixable by intersecting at_most_one with orbits: ",
814 can_be_fixed_to_false.size(),
" largest_orbit: ", max_orbit_size);
834 if (!orbitope.empty()) {
836 orbitope.size(),
" x ", orbitope[0].size());
846 int max_num_fixed_in_orbitope = 0;
847 if (!orbitope.empty()) {
848 const int num_rows = orbitope[0].size();
849 int size_left = num_rows;
850 for (
int col = 0; size_left > 1 &&
col < orbitope.size(); ++
col) {
851 max_num_fixed_in_orbitope += size_left - 1;
855 if (max_num_fixed_in_orbitope < can_be_fixed_to_false.size()) {
856 const int orbit_index = orbits[distinguished_var];
857 int num_in_orbit = 0;
858 for (
int i = 0; i < can_be_fixed_to_false.size(); ++i) {
859 const int var = can_be_fixed_to_false[i];
860 if (orbits[
var] == orbit_index) ++num_in_orbit;
861 context->UpdateRuleStats(
"symmetry: fixed to false in general orbit");
862 if (!
context->SetLiteralToFalse(
var))
return false;
867 if (orbit_sizes[orbit_index] > num_in_orbit + 1) {
869 "symmetry: added orbit symmetry breaking implications");
870 auto*
ct =
context->working_model->add_constraints();
871 auto* bool_and =
ct->mutable_bool_and();
872 ct->add_enforcement_literal(
NegatedRef(distinguished_var));
873 for (
int var = 0;
var < num_vars; ++
var) {
874 if (orbits[
var] != orbit_index)
continue;
875 if (
var == distinguished_var)
continue;
879 context->UpdateNewConstraintsVariableUsage();
883 if (orbitope.empty())
return true;
886 std::vector<int> tmp_to_clear;
887 std::vector<int> tmp_sizes(num_vars, 0);
888 std::vector<int> tmp_num_positive(num_vars, 0);
893 for (
const google::protobuf::RepeatedField<int32_t>* literals :
895 for (
const int lit : *literals) {
900 for (
const int lit : *literals) {
905 while (!orbitope.empty() && orbitope[0].size() > 1) {
906 const int num_cols = orbitope[0].size();
937 std::vector<bool> all_equivalent_rows(orbitope.size(),
false);
943 bool at_most_one_in_best_rows;
944 int64_t best_score = 0;
945 std::vector<int> best_rows;
947 std::vector<int> rows_in_at_most_one;
948 for (
const google::protobuf::RepeatedField<int32_t>* literals :
950 tmp_to_clear.clear();
951 for (
const int literal : *literals) {
954 const int rep = orbits[
var];
955 if (rep == -1)
continue;
957 if (tmp_sizes[rep] == 0) tmp_to_clear.push_back(rep);
962 int num_positive_direction = 0;
963 int num_negative_direction = 0;
968 bool possible_extension =
false;
970 rows_in_at_most_one.clear();
971 for (
const int row : tmp_to_clear) {
972 const int size = tmp_sizes[
row];
973 const int num_positive = tmp_num_positive[
row];
974 const int num_negative = tmp_sizes[
row] - tmp_num_positive[
row];
976 tmp_num_positive[
row] = 0;
978 if (num_positive > 1 && num_negative == 0) {
979 if (size < num_cols) possible_extension =
true;
980 rows_in_at_most_one.push_back(
row);
981 ++num_positive_direction;
982 }
else if (num_positive == 0 && num_negative > 1) {
983 if (size < num_cols) possible_extension =
true;
984 rows_in_at_most_one.push_back(
row);
985 ++num_negative_direction;
986 }
else if (num_positive > 0 && num_negative > 0) {
987 all_equivalent_rows[
row] =
true;
991 if (possible_extension) {
993 "TODO symmetry: possible at most one extension.");
996 if (num_positive_direction > 0 && num_negative_direction > 0) {
997 return context->NotifyThatModelIsUnsat(
"Symmetry and at most ones");
999 const bool direction = num_positive_direction > 0;
1011 for (
const int row : rows_in_at_most_one) {
1015 if (score > best_score) {
1016 at_most_one_in_best_rows = direction;
1018 best_rows = rows_in_at_most_one;
1027 for (
int i = 0; i < all_equivalent_rows.size(); ++i) {
1028 if (all_equivalent_rows[i]) {
1029 for (
int j = 1; j < num_cols; ++j) {
1030 context->StoreBooleanEqualityRelation(orbitope[i][0], orbitope[i][j]);
1031 context->UpdateRuleStats(
"symmetry: all equivalent in orbit");
1032 if (
context->ModelIsUnsat())
return false;
1045 if (best_score == 0) {
1047 "TODO symmetry: add symmetry breaking inequalities?");
1054 for (
const int i : best_rows) {
1055 for (
int j = 0; j < num_cols; ++j) {
1056 const int var = orbitope[i][j];
1057 if ((at_most_one_in_best_rows &&
context->LiteralIsTrue(
var)) ||
1058 (!at_most_one_in_best_rows &&
context->LiteralIsFalse(
var))) {
1059 return context->NotifyThatModelIsUnsat(
"Symmetry and at most one");
1068 const int best_col = 0;
1069 for (
const int i : best_rows) {
1070 for (
int j = 0; j < num_cols; ++j) {
1071 if (j == best_col)
continue;
1072 const int var = orbitope[i][j];
1073 if (at_most_one_in_best_rows) {
1074 context->UpdateRuleStats(
"symmetry: fixed to false");
1075 if (!
context->SetLiteralToFalse(
var))
return false;
1077 context->UpdateRuleStats(
"symmetry: fixed to true");
1078 if (!
context->SetLiteralToTrue(
var))
return false;
1084 for (
const int i : best_rows) orbitope[i].clear();
1086 for (
int i = 0; i < orbitope.size(); ++i) {
1087 if (!orbitope[i].empty()) orbitope[new_size++] = orbitope[i];
1089 CHECK_LT(new_size, orbitope.size());
1090 orbitope.resize(new_size);
1093 for (
int i = 0; i < orbitope.size(); ++i) {
1094 std::swap(orbitope[i][best_col], orbitope[i].back());
1095 orbitope[i].pop_back();
1101 if (orbitope.size() == 1) {
1102 const int num_cols = orbitope[0].size();
1103 for (
int i = 0; i + 1 < num_cols; ++i) {
1106 ct->mutable_linear()->add_coeffs(1);
1107 ct->mutable_linear()->add_vars(orbitope[0][i]);
1108 ct->mutable_linear()->add_coeffs(-1);
1109 ct->mutable_linear()->add_vars(orbitope[0][i + 1]);
1110 ct->mutable_linear()->add_domain(0);
1112 context->UpdateRuleStats(
"symmetry: added symmetry breaking inequality");
1114 context->UpdateNewConstraintsVariableUsage();
#define CHECK_LT(val1, val2)
#define CHECK_EQ(val1, val2)
#define DCHECK_GE(val1, val2)
#define CHECK_NE(val1, val2)
#define DCHECK(condition)
#define DCHECK_EQ(val1, val2)
#define VLOG(verboselevel)
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)
double GetElapsedDeterministicTime() const
const std::vector< int > & Support() const
void RemoveCycles(const std::vector< int > &cycle_indices)
Iterator Cycle(int i) const
static std::unique_ptr< TimeLimit > FromDeterministicTime(double deterministic_limit)
Creates a time limit object that puts limit only on the deterministic time.
int32_t literals(int index) const
const ::operations_research::sat::BoolArgumentProto & at_most_one() const
ConstraintCase constraint_case() const
const ::operations_research::sat::BoolArgumentProto & exactly_one() const
::operations_research::sat::SymmetryProto * mutable_symmetry()
int variables_size() const
int constraints_size() const
const ::operations_research::sat::ConstraintProto & constraints(int index) const
void set_num_rows(int32_t value)
void set_num_cols(int32_t value)
void add_entries(int32_t value)
int32_t support(int index) const
void add_cycle_sizes(int32_t value)
void add_support(int32_t value)
::operations_research::sat::DenseMatrixProto * add_orbitopes()
PROTOBUF_ATTRIBUTE_REINITIALIZES void Clear() final
::operations_research::sat::SparsePermutationProto * add_permutations()
ModelSharedTimeLimit * time_limit
GurobiMPCallbackContext * context
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)
void DetectAndAddSymmetryToProto(const SatParameters ¶ms, CpModelProto *proto, SolverLogger *logger)
bool DetectAndExploitSymmetriesInPresolve(PresolveContext *context)
std::vector< int > GetOrbitopeOrbits(int n, const std::vector< std::vector< int > > &orbitope)
bool RefIsPositive(int ref)
bool LoadModelForProbing(PresolveContext *context, Model *local_model)
void FindCpModelSymmetries(const SatParameters ¶ms, const CpModelProto &problem, std::vector< std::unique_ptr< SparsePermutation > > *generators, double deterministic_limit, SolverLogger *logger)
std::string ConstraintCaseName(ConstraintProto::ConstraintCase constraint_case)
std::vector< int > GetOrbits(int n, const std::vector< std::unique_ptr< SparsePermutation > > &generators)
std::vector< std::vector< int > > BasicOrbitopeExtraction(const std::vector< std::unique_ptr< SparsePermutation > > &generators)
Graph * GenerateGraphForSymmetryDetection(const LinearBooleanProblem &problem, std::vector< int > *initial_equivalence_classes)
Collection of objects used to extend the Constraint Solver library.
uint64_t Hash(uint64_t num, uint64_t c)
std::vector< int >::const_iterator begin() const
#define SOLVER_LOG(logger,...)