25#include "absl/container/flat_hash_map.h"
26#include "absl/container/flat_hash_set.h"
27#include "absl/memory/memory.h"
28#include "absl/meta/type_traits.h"
29#include "absl/status/status.h"
30#include "absl/strings/str_cat.h"
31#include "absl/strings/str_join.h"
32#include "google/protobuf/message.h"
39#include "ortools/sat/cp_model.pb.h"
45#include "ortools/sat/sat_parameters.pb.h"
57 std::size_t operator()(
const std::vector<int64_t>& values)
const {
59 for (
const int64_t
value : values) {
74 int GetId(
const std::vector<int64_t>& color) {
78 int NextFreeId()
const {
return id_map_.size(); }
81 absl::flat_hash_map<std::vector<int64_t>, int, VectorHash> id_map_;
87template <
typename FieldInt64Type>
89 const google::protobuf::RepeatedField<FieldInt64Type>& repeated_field,
90 std::vector<int64_t>* vector) {
91 CHECK(vector !=
nullptr);
92 for (
const FieldInt64Type
value : repeated_field) {
93 vector->push_back(
value);
111template <
typename Graph>
113 const CpModelProto& problem, std::vector<int>* initial_equivalence_classes,
114 SolverLogger* logger) {
115 CHECK(initial_equivalence_classes !=
nullptr);
117 const int num_variables = problem.variables_size();
118 auto graph = absl::make_unique<Graph>();
128 VAR_COEFFICIENT_NODE,
131 IdGenerator color_id_generator;
132 initial_equivalence_classes->clear();
133 auto new_node = [&initial_equivalence_classes, &graph,
134 &color_id_generator](
const std::vector<int64_t>& color) {
137 const int node = initial_equivalence_classes->size();
138 initial_equivalence_classes->push_back(color_id_generator.GetId(color));
142 graph->AddNode(node);
152 std::vector<int64_t> objective_by_var(num_variables, 0);
153 for (
int i = 0; i < problem.objective().vars_size(); ++i) {
154 const int ref = problem.objective().vars(i);
156 const int64_t
coeff = problem.objective().coeffs(i);
162 std::vector<int64_t> tmp_color;
163 for (
int v = 0; v < num_variables; ++v) {
164 tmp_color = {VARIABLE_NODE, objective_by_var[v]};
165 Append(problem.variables(v).domain(), &tmp_color);
171 absl::flat_hash_map<std::pair<int64_t, int64_t>,
int> coefficient_nodes;
172 auto get_coefficient_node = [&new_node, &graph, &coefficient_nodes,
173 &tmp_color](
int var, int64_t
coeff) {
174 const int var_node =
var;
180 if (
coeff == 1)
return var_node;
183 coefficient_nodes.insert({std::make_pair(
var,
coeff), 0});
184 if (!insert.second)
return insert.first->second;
186 tmp_color = {VAR_COEFFICIENT_NODE,
coeff};
187 const int secondary_node = new_node(tmp_color);
188 graph->AddArc(var_node, secondary_node);
189 insert.first->second = secondary_node;
190 return secondary_node;
196 auto get_literal_node = [&get_coefficient_node](
int ref) {
212 absl::flat_hash_set<std::pair<int, int>> implications;
213 auto get_implication_node = [&new_node, &graph, &coefficient_nodes,
214 &tmp_color](
int ref) {
218 coefficient_nodes.insert({std::make_pair(
var,
coeff), 0});
219 if (!insert.second)
return insert.first->second;
220 tmp_color = {VAR_COEFFICIENT_NODE,
coeff};
221 const int secondary_node = new_node(tmp_color);
222 graph->AddArc(
var, secondary_node);
223 insert.first->second = secondary_node;
224 return secondary_node;
226 auto add_implication = [&get_implication_node, &graph, &implications](
227 int ref_a,
int ref_b) {
228 const auto insert = implications.insert({ref_a, ref_b});
229 if (!insert.second)
return;
230 graph->AddArc(get_implication_node(ref_a), get_implication_node(ref_b));
234 graph->AddArc(get_implication_node(
NegatedRef(ref_b)),
239 absl::flat_hash_map<int, int> interval_constraint_index_to_node;
242 for (
int constraint_index = 0; constraint_index < problem.constraints_size();
243 ++constraint_index) {
244 const ConstraintProto& constraint = problem.constraints(constraint_index);
245 const int constraint_node = initial_equivalence_classes->size();
246 std::vector<int64_t> color = {CONSTRAINT_NODE,
247 constraint.constraint_case()};
249 switch (constraint.constraint_case()) {
250 case ConstraintProto::CONSTRAINT_NOT_SET:
255 case ConstraintProto::kLinear: {
260 Append(constraint.linear().domain(), &color);
261 CHECK_EQ(constraint_node, new_node(color));
262 for (
int i = 0; i < constraint.linear().vars_size(); ++i) {
263 const int ref = constraint.linear().vars(i);
266 ? constraint.linear().coeffs(i)
267 : -constraint.linear().coeffs(i);
268 graph->AddArc(get_coefficient_node(variable_node,
coeff),
273 case ConstraintProto::kBoolOr: {
274 CHECK_EQ(constraint_node, new_node(color));
275 for (
const int ref : constraint.bool_or().literals()) {
276 graph->AddArc(get_literal_node(ref), constraint_node);
280 case ConstraintProto::kAtMostOne: {
281 if (constraint.at_most_one().literals().size() == 2) {
283 add_implication(constraint.at_most_one().literals(0),
284 NegatedRef(constraint.at_most_one().literals(1)));
288 CHECK_EQ(constraint_node, new_node(color));
289 for (
const int ref : constraint.at_most_one().literals()) {
290 graph->AddArc(get_literal_node(ref), constraint_node);
294 case ConstraintProto::kExactlyOne: {
295 CHECK_EQ(constraint_node, new_node(color));
296 for (
const int ref : constraint.exactly_one().literals()) {
297 graph->AddArc(get_literal_node(ref), constraint_node);
301 case ConstraintProto::kBoolXor: {
302 CHECK_EQ(constraint_node, new_node(color));
303 for (
const int ref : constraint.bool_xor().literals()) {
304 graph->AddArc(get_literal_node(ref), constraint_node);
308 case ConstraintProto::kBoolAnd: {
311 if (constraint.enforcement_literal_size() != 1) {
314 "[Symmetry] BoolAnd with multiple enforcement literal are not "
315 "supported in symmetry code:",
316 constraint.ShortDebugString());
320 CHECK_EQ(constraint.enforcement_literal_size(), 1);
321 const int ref_a = constraint.enforcement_literal(0);
322 for (
const int ref_b : constraint.bool_and().literals()) {
323 add_implication(ref_a, ref_b);
327 case ConstraintProto::kInterval: {
330 std::vector<int>
nodes;
331 for (
int indicator = 0; indicator <= 2; ++indicator) {
332 const LinearExpressionProto& expr =
333 indicator == 0 ? constraint.interval().start()
334 : indicator == 1 ? constraint.interval().size()
335 : constraint.interval().end();
337 std::vector<int64_t> local_color = color;
338 local_color.push_back(indicator);
339 local_color.push_back(expr.offset());
340 const int local_node = new_node(local_color);
341 nodes.push_back(local_node);
343 for (
int i = 0; i < expr.vars().size(); ++i) {
344 const int ref = expr.vars(i);
346 const int64_t
coeff =
348 graph->AddArc(get_coefficient_node(var_node,
coeff), local_node);
354 interval_constraint_index_to_node[constraint_index] = constraint_node;
364 case ConstraintProto::kNoOverlap: {
368 CHECK_EQ(constraint_node, new_node(color));
369 for (
const int interval : constraint.no_overlap().intervals()) {
370 graph->AddArc(interval_constraint_index_to_node.at(
interval),
375 case ConstraintProto::kNoOverlap2D: {
385 CHECK_EQ(constraint_node, new_node(color));
386 const int size = constraint.no_overlap_2d().x_intervals().size();
387 for (
int i = 0; i < size; ++i) {
388 const int x = constraint.no_overlap_2d().x_intervals(i);
389 const int y = constraint.no_overlap_2d().y_intervals(i);
390 graph->AddArc(interval_constraint_index_to_node.at(x),
392 graph->AddArc(interval_constraint_index_to_node.at(x),
393 interval_constraint_index_to_node.at(y));
404 VLOG(1) <<
"Unsupported constraint type "
414 if (constraint.constraint_case() != ConstraintProto::kBoolAnd) {
415 for (
const int ref : constraint.enforcement_literal()) {
416 graph->AddArc(constraint_node, get_literal_node(ref));
422 DCHECK_EQ(graph->num_nodes(), initial_equivalence_classes->size());
429 const int num_nodes = graph->num_nodes();
430 std::vector<int> in_degree(num_nodes, 0);
431 std::vector<int> out_degree(num_nodes, 0);
432 for (
int i = 0; i < num_nodes; ++i) {
433 out_degree[i] = graph->OutDegree(i);
434 for (
const int head : (*graph)[i]) {
438 for (
int i = 0; i < num_nodes; ++i) {
439 if (in_degree[i] >= num_nodes || out_degree[i] >= num_nodes) {
440 SOLVER_LOG(logger,
"[Symmetry] Too many multi-arcs in symmetry code.");
453 int next_id = color_id_generator.NextFreeId();
454 for (
int i = 0; i < num_variables; ++i) {
455 if ((*graph)[i].empty()) {
456 (*initial_equivalence_classes)[i] = next_id++;
462 std::vector<int> mapping(next_id, -1);
463 for (
int& ref : *initial_equivalence_classes) {
464 if (mapping[ref] == -1) {
465 ref = mapping[ref] =
id++;
476 const SatParameters& params,
const CpModelProto& problem,
477 std::vector<std::unique_ptr<SparsePermutation>>* generators,
479 CHECK(generators !=
nullptr);
482 if (params.symmetry_level() < 3 && problem.variables().size() > 1e6 &&
483 problem.constraints().size() > 1e6) {
485 "[Symmetry] Problem too large. Skipping. You can use "
486 "symmetry_level:3 or more to force it.");
492 std::vector<int> equivalence_classes;
493 std::unique_ptr<Graph> graph(GenerateGraphForSymmetryDetection<Graph>(
494 problem, &equivalence_classes, logger));
495 if (graph ==
nullptr)
return;
497 SOLVER_LOG(logger,
"[Symmetry] Graph for symmetry has ", graph->num_nodes(),
498 " nodes and ", graph->num_arcs(),
" arcs.");
499 if (graph->num_nodes() == 0)
return;
502 std::vector<int> factorized_automorphism_group_size;
506 &equivalence_classes, generators, &factorized_automorphism_group_size,
513 "[Symmetry] GraphSymmetryFinder error: ",
status.message());
519 double average_support_size = 0.0;
520 int num_generators = 0;
521 int num_duplicate_constraints = 0;
522 for (
int i = 0; i < generators->size(); ++i) {
524 std::vector<int> to_delete;
525 for (
int j = 0; j < permutation->
NumCycles(); ++j) {
529 if (*(permutation->
Cycle(j).
begin()) >= problem.variables_size()) {
530 to_delete.push_back(j);
533 for (
const int node : permutation->
Cycle(j)) {
534 DCHECK_GE(node, problem.variables_size());
541 if (!permutation->
Support().empty()) {
542 average_support_size += permutation->
Support().size();
543 swap((*generators)[num_generators], (*generators)[i]);
546 ++num_duplicate_constraints;
549 generators->resize(num_generators);
550 average_support_size /= num_generators;
551 SOLVER_LOG(logger,
"[Symmetry] Symmetry computation done. time: ",
554 if (num_generators > 0) {
555 SOLVER_LOG(logger,
"[Symmetry] #generators: ", num_generators,
556 ", average support size: ", average_support_size);
557 if (num_duplicate_constraints > 0) {
558 SOLVER_LOG(logger,
"[Symmetry] The model contains ",
559 num_duplicate_constraints,
" duplicate constraints !");
566 SymmetryProto* symmetry =
proto->mutable_symmetry();
569 std::vector<std::unique_ptr<SparsePermutation>> generators;
572 if (generators.empty()) {
573 proto->clear_symmetry();
577 for (
const std::unique_ptr<SparsePermutation>& perm : generators) {
578 SparsePermutationProto* perm_proto = symmetry->add_permutations();
579 const int num_cycle = perm->NumCycles();
580 for (
int i = 0; i < num_cycle; ++i) {
581 const int old_size = perm_proto->support().size();
582 for (
const int var : perm->Cycle(i)) {
583 perm_proto->add_support(
var);
585 perm_proto->add_cycle_sizes(perm_proto->support().size() - old_size);
590 if (orbitope.empty())
return;
591 SOLVER_LOG(logger,
"[Symmetry] Found orbitope of size ", orbitope.size(),
592 " x ", orbitope[0].size());
593 DenseMatrixProto* matrix = symmetry->add_orbitopes();
594 matrix->set_num_rows(orbitope.size());
595 matrix->set_num_cols(orbitope[0].size());
596 for (
const std::vector<int>&
row : orbitope) {
597 for (
const int entry :
row) {
598 matrix->add_entries(entry);
619void OrbitAndPropagation(
const std::vector<int>& orbits,
int var,
620 std::vector<int>* can_be_fixed_to_false,
625 if (!
context->CanBeUsedAsLiteral(
var))
return;
641 auto* sat_solver =
model.GetOrCreate<SatSolver>();
642 auto* mapping =
model.GetOrCreate<CpModelMapping>();
643 const Literal to_propagate = mapping->Literal(
var);
645 const VariablesAssignment& assignment = sat_solver->Assignment();
646 if (assignment.LiteralIsAssigned(to_propagate))
return;
647 sat_solver->EnqueueDecisionAndBackjumpOnConflict(to_propagate);
648 if (sat_solver->CurrentDecisionLevel() != 1)
return;
651 can_be_fixed_to_false->clear();
653 const int orbit_index = orbits[
var];
654 const int num_variables = orbits.size();
655 for (
int var = 0;
var < num_variables; ++
var) {
656 if (orbits[
var] != orbit_index)
continue;
663 if (assignment.LiteralIsFalse(mapping->Literal(
var))) {
664 can_be_fixed_to_false->push_back(
var);
667 if (!can_be_fixed_to_false->empty()) {
669 "[Symmetry] Num fixable by binary propagation in orbit: ",
670 can_be_fixed_to_false->size(),
" / ", orbit_size);
677 const SatParameters& params =
context->params();
681 if (
context->working_model->has_objective()) {
682 context->WriteObjectiveToProto();
684 context->WriteVariableDomainsToProto();
688 int64_t num_added = 0;
689 const int initial_ct_index =
proto.constraints().size();
690 const int num_vars =
proto.variables_size();
691 for (
int var = 0;
var < num_vars; ++
var) {
693 if (
context->VariableWasRemoved(
var))
continue;
694 if (
context->VariableIsNotUsedAnymore(
var))
continue;
700 ConstraintProto*
ct =
context->working_model->add_constraints();
701 auto* arg =
ct->mutable_linear();
705 arg->add_coeffs(-r.
coeff);
706 arg->add_domain(r.
offset);
707 arg->add_domain(r.
offset);
710 std::vector<std::unique_ptr<SparsePermutation>> generators;
715 context->working_model->mutable_constraints()->DeleteSubrange(
716 initial_ct_index, num_added);
718 if (generators.empty())
return true;
725 std::vector<const google::protobuf::RepeatedField<int32_t>*> at_most_ones;
726 for (
int i = 0; i <
proto.constraints_size(); ++i) {
727 if (
proto.constraints(i).constraint_case() == ConstraintProto::kAtMostOne) {
728 at_most_ones.push_back(&
proto.constraints(i).at_most_one().literals());
730 if (
proto.constraints(i).constraint_case() ==
731 ConstraintProto::kExactlyOne) {
732 at_most_ones.push_back(&
proto.constraints(i).exactly_one().literals());
740 int distinguished_var = -1;
741 std::vector<int> can_be_fixed_to_false;
744 const std::vector<int> orbits =
GetOrbits(num_vars, generators);
745 std::vector<int> orbit_sizes;
746 int max_orbit_size = 0;
747 for (
int var = 0;
var < num_vars; ++
var) {
748 const int rep = orbits[
var];
749 if (rep == -1)
continue;
750 if (rep >= orbit_sizes.size()) orbit_sizes.resize(rep + 1, 0);
752 if (orbit_sizes[rep] > max_orbit_size) {
753 distinguished_var =
var;
754 max_orbit_size = orbit_sizes[rep];
759 if (
context->logger()->LoggingIsEnabled()) {
760 std::vector<int> sorted_sizes;
761 for (
const int s : orbit_sizes) {
762 if (s != 0) sorted_sizes.push_back(s);
764 std::sort(sorted_sizes.begin(), sorted_sizes.end(), std::greater<int>());
765 const int num_orbits = sorted_sizes.size();
766 if (num_orbits > 10) sorted_sizes.resize(10);
768 " orbits with sizes: ", absl::StrJoin(sorted_sizes,
","),
769 (num_orbits > sorted_sizes.size() ?
",..." :
""));
773 if (max_orbit_size > 2) {
774 OrbitAndPropagation(orbits, distinguished_var, &can_be_fixed_to_false,
777 const int first_heuristic_size = can_be_fixed_to_false.size();
790 std::vector<int> tmp_to_clear;
791 std::vector<int> tmp_sizes(num_vars, 0);
792 for (
const google::protobuf::RepeatedField<int32_t>* literals :
794 tmp_to_clear.clear();
798 for (
const int literal : *literals) {
803 const int rep = orbits[
var];
804 if (rep == -1)
continue;
807 if (tmp_sizes[rep] == 0) tmp_to_clear.push_back(rep);
808 if (tmp_sizes[rep] > 0) ++num_fixable;
813 if (num_fixable > can_be_fixed_to_false.size()) {
814 distinguished_var = -1;
815 can_be_fixed_to_false.clear();
816 for (
const int literal : *literals) {
821 const int rep = orbits[
var];
822 if (rep == -1)
continue;
823 if (distinguished_var == -1 ||
824 orbit_sizes[rep] > orbit_sizes[orbits[distinguished_var]]) {
825 distinguished_var =
var;
829 if (tmp_sizes[rep] == 0) can_be_fixed_to_false.push_back(
var);
834 for (
const int rep : tmp_to_clear) tmp_sizes[rep] = 0;
838 if (can_be_fixed_to_false.size() > first_heuristic_size) {
841 "[Symmetry] Num fixable by intersecting at_most_one with orbits: ",
842 can_be_fixed_to_false.size(),
" largest_orbit: ", max_orbit_size);
862 if (!orbitope.empty()) {
864 orbitope.size(),
" x ", orbitope[0].size());
874 int max_num_fixed_in_orbitope = 0;
875 if (!orbitope.empty()) {
876 const int num_rows = orbitope[0].size();
877 int size_left = num_rows;
878 for (
int col = 0; size_left > 1 &&
col < orbitope.size(); ++
col) {
879 max_num_fixed_in_orbitope += size_left - 1;
883 if (max_num_fixed_in_orbitope < can_be_fixed_to_false.size()) {
884 const int orbit_index = orbits[distinguished_var];
885 int num_in_orbit = 0;
886 for (
int i = 0; i < can_be_fixed_to_false.size(); ++i) {
887 const int var = can_be_fixed_to_false[i];
888 if (orbits[
var] == orbit_index) ++num_in_orbit;
889 context->UpdateRuleStats(
"symmetry: fixed to false in general orbit");
890 if (!
context->SetLiteralToFalse(
var))
return false;
895 if (orbit_sizes[orbit_index] > num_in_orbit + 1) {
897 "symmetry: added orbit symmetry breaking implications");
898 auto*
ct =
context->working_model->add_constraints();
899 auto* bool_and =
ct->mutable_bool_and();
900 ct->add_enforcement_literal(
NegatedRef(distinguished_var));
901 for (
int var = 0;
var < num_vars; ++
var) {
902 if (orbits[
var] != orbit_index)
continue;
903 if (
var == distinguished_var)
continue;
907 context->UpdateNewConstraintsVariableUsage();
911 if (orbitope.empty())
return true;
914 std::vector<int> tmp_to_clear;
915 std::vector<int> tmp_sizes(num_vars, 0);
916 std::vector<int> tmp_num_positive(num_vars, 0);
921 for (
const google::protobuf::RepeatedField<int32_t>* literals :
923 for (
const int lit : *literals) {
928 for (
const int lit : *literals) {
933 while (!orbitope.empty() && orbitope[0].size() > 1) {
934 const int num_cols = orbitope[0].size();
965 std::vector<bool> all_equivalent_rows(orbitope.size(),
false);
971 bool at_most_one_in_best_rows;
972 int64_t best_score = 0;
973 std::vector<int> best_rows;
975 std::vector<int> rows_in_at_most_one;
976 for (
const google::protobuf::RepeatedField<int32_t>* literals :
978 tmp_to_clear.clear();
979 for (
const int literal : *literals) {
982 const int rep = orbits[
var];
983 if (rep == -1)
continue;
985 if (tmp_sizes[rep] == 0) tmp_to_clear.push_back(rep);
990 int num_positive_direction = 0;
991 int num_negative_direction = 0;
996 bool possible_extension =
false;
998 rows_in_at_most_one.clear();
999 for (
const int row : tmp_to_clear) {
1000 const int size = tmp_sizes[
row];
1001 const int num_positive = tmp_num_positive[
row];
1002 const int num_negative = tmp_sizes[
row] - tmp_num_positive[
row];
1004 tmp_num_positive[
row] = 0;
1006 if (num_positive > 1 && num_negative == 0) {
1007 if (size < num_cols) possible_extension =
true;
1008 rows_in_at_most_one.push_back(
row);
1009 ++num_positive_direction;
1010 }
else if (num_positive == 0 && num_negative > 1) {
1011 if (size < num_cols) possible_extension =
true;
1012 rows_in_at_most_one.push_back(
row);
1013 ++num_negative_direction;
1014 }
else if (num_positive > 0 && num_negative > 0) {
1015 all_equivalent_rows[
row] =
true;
1019 if (possible_extension) {
1021 "TODO symmetry: possible at most one extension.");
1024 if (num_positive_direction > 0 && num_negative_direction > 0) {
1025 return context->NotifyThatModelIsUnsat(
"Symmetry and at most ones");
1027 const bool direction = num_positive_direction > 0;
1039 for (
const int row : rows_in_at_most_one) {
1043 if (score > best_score) {
1044 at_most_one_in_best_rows = direction;
1046 best_rows = rows_in_at_most_one;
1055 for (
int i = 0; i < all_equivalent_rows.size(); ++i) {
1056 if (all_equivalent_rows[i]) {
1057 for (
int j = 1; j < num_cols; ++j) {
1058 context->StoreBooleanEqualityRelation(orbitope[i][0], orbitope[i][j]);
1059 context->UpdateRuleStats(
"symmetry: all equivalent in orbit");
1060 if (
context->ModelIsUnsat())
return false;
1073 if (best_score == 0) {
1075 "TODO symmetry: add symmetry breaking inequalities?");
1082 for (
const int i : best_rows) {
1083 for (
int j = 0; j < num_cols; ++j) {
1084 const int var = orbitope[i][j];
1085 if ((at_most_one_in_best_rows &&
context->LiteralIsTrue(
var)) ||
1086 (!at_most_one_in_best_rows &&
context->LiteralIsFalse(
var))) {
1087 return context->NotifyThatModelIsUnsat(
"Symmetry and at most one");
1096 const int best_col = 0;
1097 for (
const int i : best_rows) {
1098 for (
int j = 0; j < num_cols; ++j) {
1099 if (j == best_col)
continue;
1100 const int var = orbitope[i][j];
1101 if (at_most_one_in_best_rows) {
1102 context->UpdateRuleStats(
"symmetry: fixed to false");
1103 if (!
context->SetLiteralToFalse(
var))
return false;
1105 context->UpdateRuleStats(
"symmetry: fixed to true");
1106 if (!
context->SetLiteralToTrue(
var))
return false;
1112 for (
const int i : best_rows) orbitope[i].clear();
1114 for (
int i = 0; i < orbitope.size(); ++i) {
1115 if (!orbitope[i].empty()) orbitope[new_size++] = orbitope[i];
1117 CHECK_LT(new_size, orbitope.size());
1118 orbitope.resize(new_size);
1121 for (
int i = 0; i < orbitope.size(); ++i) {
1122 std::swap(orbitope[i][best_col], orbitope[i].back());
1123 orbitope[i].pop_back();
1129 if (orbitope.size() == 1) {
1130 const int num_cols = orbitope[0].size();
1131 for (
int i = 0; i + 1 < num_cols; ++i) {
1133 ConstraintProto*
ct =
context->working_model->add_constraints();
1134 ct->mutable_linear()->add_coeffs(1);
1135 ct->mutable_linear()->add_vars(orbitope[0][i]);
1136 ct->mutable_linear()->add_coeffs(-1);
1137 ct->mutable_linear()->add_vars(orbitope[0][i + 1]);
1138 ct->mutable_linear()->add_domain(0);
1140 context->UpdateRuleStats(
"symmetry: added symmetry breaking inequality");
1142 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.
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,...)