25 #include "absl/container/flat_hash_map.h"
26 #include "absl/container/flat_hash_set.h"
27 #include "absl/meta/type_traits.h"
28 #include "absl/status/status.h"
29 #include "absl/strings/str_cat.h"
30 #include "absl/strings/str_join.h"
31 #include "google/protobuf/message.h"
37 #include "ortools/sat/cp_model.pb.h"
43 #include "ortools/sat/sat_parameters.pb.h"
55 std::size_t operator()(
const std::vector<int64_t>& values)
const {
57 for (
const int64_t
value : values) {
72 int GetId(
const std::vector<int64_t>& color) {
73 return id_map_.emplace(color, id_map_.size()).first->second;
76 int NextFreeId()
const {
return id_map_.size(); }
79 absl::flat_hash_map<std::vector<int64_t>, int, VectorHash> id_map_;
85 template <
typename FieldInt64Type>
87 const google::protobuf::RepeatedField<FieldInt64Type>& repeated_field,
88 std::vector<int64_t>* vector) {
89 CHECK(vector !=
nullptr);
90 for (
const FieldInt64Type
value : repeated_field) {
91 vector->push_back(
value);
109 template <
typename Graph>
111 const CpModelProto& problem, std::vector<int>* initial_equivalence_classes,
112 SolverLogger* logger) {
113 CHECK(initial_equivalence_classes !=
nullptr);
115 const int num_variables = problem.variables_size();
116 auto graph = std::make_unique<Graph>();
126 VAR_COEFFICIENT_NODE,
129 IdGenerator color_id_generator;
130 initial_equivalence_classes->clear();
131 auto new_node = [&initial_equivalence_classes, &graph,
132 &color_id_generator](
const std::vector<int64_t>& color) {
135 const int node = initial_equivalence_classes->size();
136 initial_equivalence_classes->push_back(color_id_generator.GetId(color));
140 graph->AddNode(node);
150 std::vector<int64_t> objective_by_var(num_variables, 0);
151 for (
int i = 0; i < problem.objective().vars_size(); ++i) {
152 const int ref = problem.objective().vars(i);
154 const int64_t coeff = problem.objective().coeffs(i);
160 std::vector<int64_t> tmp_color;
161 for (
int v = 0; v < num_variables; ++v) {
162 tmp_color = {VARIABLE_NODE, objective_by_var[v]};
163 Append(problem.variables(v).domain(), &tmp_color);
164 CHECK_EQ(v, new_node(tmp_color));
169 absl::flat_hash_map<std::pair<int64_t, int64_t>,
int> coefficient_nodes;
170 auto get_coefficient_node = [&new_node, &graph, &coefficient_nodes,
171 &tmp_color](
int var, int64_t coeff) {
172 const int var_node =
var;
178 if (coeff == 1)
return var_node;
181 coefficient_nodes.insert({std::make_pair(
var, coeff), 0});
182 if (!insert.second)
return insert.first->second;
184 tmp_color = {VAR_COEFFICIENT_NODE, coeff};
185 const int secondary_node = new_node(tmp_color);
186 graph->AddArc(var_node, secondary_node);
187 insert.first->second = secondary_node;
188 return secondary_node;
194 auto get_literal_node = [&get_coefficient_node](
int ref) {
210 absl::flat_hash_set<std::pair<int, int>> implications;
211 auto get_implication_node = [&new_node, &graph, &coefficient_nodes,
212 &tmp_color](
int ref) {
216 coefficient_nodes.insert({std::make_pair(
var, coeff), 0});
217 if (!insert.second)
return insert.first->second;
218 tmp_color = {VAR_COEFFICIENT_NODE, coeff};
219 const int secondary_node = new_node(tmp_color);
220 graph->AddArc(
var, secondary_node);
221 insert.first->second = secondary_node;
222 return secondary_node;
224 auto add_implication = [&get_implication_node, &graph, &implications](
225 int ref_a,
int ref_b) {
226 const auto insert = implications.insert({ref_a, ref_b});
227 if (!insert.second)
return;
228 graph->AddArc(get_implication_node(ref_a), get_implication_node(ref_b));
232 graph->AddArc(get_implication_node(
NegatedRef(ref_b)),
237 absl::flat_hash_map<int, int> interval_constraint_index_to_node;
240 for (
int constraint_index = 0; constraint_index < problem.constraints_size();
241 ++constraint_index) {
242 const ConstraintProto& constraint = problem.constraints(constraint_index);
243 const int constraint_node = initial_equivalence_classes->size();
244 std::vector<int64_t> color = {CONSTRAINT_NODE,
245 constraint.constraint_case()};
247 switch (constraint.constraint_case()) {
248 case ConstraintProto::CONSTRAINT_NOT_SET:
253 case ConstraintProto::kLinear: {
258 Append(constraint.linear().domain(), &color);
259 CHECK_EQ(constraint_node, new_node(color));
260 for (
int i = 0; i < constraint.linear().vars_size(); ++i) {
261 const int ref = constraint.linear().vars(i);
264 ? constraint.linear().coeffs(i)
265 : -constraint.linear().coeffs(i);
266 graph->AddArc(get_coefficient_node(variable_node, coeff),
271 case ConstraintProto::kBoolOr: {
272 CHECK_EQ(constraint_node, new_node(color));
273 for (
const int ref : constraint.bool_or().literals()) {
274 graph->AddArc(get_literal_node(ref), constraint_node);
278 case ConstraintProto::kAtMostOne: {
279 if (constraint.at_most_one().literals().size() == 2) {
281 add_implication(constraint.at_most_one().literals(0),
282 NegatedRef(constraint.at_most_one().literals(1)));
286 CHECK_EQ(constraint_node, new_node(color));
287 for (
const int ref : constraint.at_most_one().literals()) {
288 graph->AddArc(get_literal_node(ref), constraint_node);
292 case ConstraintProto::kExactlyOne: {
293 CHECK_EQ(constraint_node, new_node(color));
294 for (
const int ref : constraint.exactly_one().literals()) {
295 graph->AddArc(get_literal_node(ref), constraint_node);
299 case ConstraintProto::kBoolXor: {
300 CHECK_EQ(constraint_node, new_node(color));
301 for (
const int ref : constraint.bool_xor().literals()) {
302 graph->AddArc(get_literal_node(ref), constraint_node);
306 case ConstraintProto::kBoolAnd: {
307 if (constraint.enforcement_literal_size() > 1) {
308 CHECK_EQ(constraint_node, new_node(color));
309 for (
const int ref : constraint.bool_and().literals()) {
310 graph->AddArc(get_literal_node(ref), constraint_node);
315 CHECK_EQ(constraint.enforcement_literal_size(), 1);
316 const int ref_a = constraint.enforcement_literal(0);
317 for (
const int ref_b : constraint.bool_and().literals()) {
318 add_implication(ref_a, ref_b);
322 case ConstraintProto::kInterval: {
325 std::vector<int>
nodes;
326 for (
int indicator = 0; indicator <= 2; ++indicator) {
327 const LinearExpressionProto& expr =
328 indicator == 0 ? constraint.interval().start()
329 : indicator == 1 ? constraint.interval().size()
330 : constraint.interval().end();
332 std::vector<int64_t> local_color = color;
333 local_color.push_back(indicator);
334 local_color.push_back(expr.offset());
335 const int local_node = new_node(local_color);
336 nodes.push_back(local_node);
338 for (
int i = 0; i < expr.vars().size(); ++i) {
339 const int ref = expr.vars(i);
341 const int64_t coeff =
343 graph->AddArc(get_coefficient_node(var_node, coeff), local_node);
349 interval_constraint_index_to_node[constraint_index] = constraint_node;
350 CHECK_EQ(
nodes[0], constraint_node);
359 case ConstraintProto::kNoOverlap: {
363 CHECK_EQ(constraint_node, new_node(color));
364 for (
const int interval : constraint.no_overlap().intervals()) {
365 graph->AddArc(interval_constraint_index_to_node.at(
interval),
370 case ConstraintProto::kNoOverlap2D: {
380 CHECK_EQ(constraint_node, new_node(color));
381 const int size = constraint.no_overlap_2d().x_intervals().size();
382 for (
int i = 0; i < size; ++i) {
383 const int x = constraint.no_overlap_2d().x_intervals(i);
384 const int y = constraint.no_overlap_2d().y_intervals(i);
385 graph->AddArc(interval_constraint_index_to_node.at(x),
387 graph->AddArc(interval_constraint_index_to_node.at(x),
388 interval_constraint_index_to_node.at(y));
399 VLOG(1) <<
"Unsupported constraint type "
409 if (constraint.constraint_case() != ConstraintProto::kBoolAnd ||
410 constraint.enforcement_literal().size() > 1) {
411 for (
const int ref : constraint.enforcement_literal()) {
412 graph->AddArc(constraint_node, get_literal_node(ref));
418 DCHECK_EQ(graph->num_nodes(), initial_equivalence_classes->size());
425 const int num_nodes = graph->num_nodes();
426 std::vector<int> in_degree(num_nodes, 0);
427 std::vector<int> out_degree(num_nodes, 0);
428 for (
int i = 0; i < num_nodes; ++i) {
429 out_degree[i] = graph->OutDegree(i);
430 for (
const int head : (*graph)[i]) {
434 for (
int i = 0; i < num_nodes; ++i) {
435 if (in_degree[i] >= num_nodes || out_degree[i] >= num_nodes) {
436 SOLVER_LOG(logger,
"[Symmetry] Too many multi-arcs in symmetry code.");
449 int next_id = color_id_generator.NextFreeId();
450 for (
int i = 0; i < num_variables; ++i) {
451 if ((*graph)[i].empty()) {
452 (*initial_equivalence_classes)[i] = next_id++;
458 std::vector<int> mapping(next_id, -1);
459 for (
int& ref : *initial_equivalence_classes) {
460 if (mapping[ref] == -1) {
461 ref = mapping[ref] =
id++;
472 const SatParameters& params,
const CpModelProto& problem,
473 std::vector<std::unique_ptr<SparsePermutation>>* generators,
475 CHECK(generators !=
nullptr);
478 if (params.symmetry_level() < 3 && problem.variables().size() > 1e6 &&
479 problem.constraints().size() > 1e6) {
481 "[Symmetry] Problem too large. Skipping. You can use "
482 "symmetry_level:3 or more to force it.");
488 std::vector<int> equivalence_classes;
489 std::unique_ptr<Graph> graph(GenerateGraphForSymmetryDetection<Graph>(
490 problem, &equivalence_classes, logger));
491 if (graph ==
nullptr)
return;
493 SOLVER_LOG(logger,
"[Symmetry] Graph for symmetry has ", graph->num_nodes(),
494 " nodes and ", graph->num_arcs(),
" arcs.");
495 if (graph->num_nodes() == 0)
return;
497 if (params.symmetry_level() < 3 && graph->num_nodes() > 1e6 &&
498 graph->num_arcs() > 1e6) {
500 "[Symmetry] Graph too large. Skipping. You can use "
501 "symmetry_level:3 or more to force it.");
506 std::vector<int> factorized_automorphism_group_size;
510 &equivalence_classes, generators, &factorized_automorphism_group_size,
517 "[Symmetry] GraphSymmetryFinder error: ",
status.message());
523 double average_support_size = 0.0;
524 int num_generators = 0;
525 int num_duplicate_constraints = 0;
526 for (
int i = 0; i < generators->size(); ++i) {
528 std::vector<int> to_delete;
529 for (
int j = 0; j < permutation->
NumCycles(); ++j) {
533 if (*(permutation->
Cycle(j).
begin()) >= problem.variables_size()) {
534 to_delete.push_back(j);
537 for (
const int node : permutation->
Cycle(j)) {
538 DCHECK_GE(node, problem.variables_size());
545 if (!permutation->
Support().empty()) {
546 average_support_size += permutation->
Support().size();
547 swap((*generators)[num_generators], (*generators)[i]);
550 ++num_duplicate_constraints;
553 generators->resize(num_generators);
554 average_support_size /= num_generators;
555 SOLVER_LOG(logger,
"[Symmetry] Symmetry computation done. time: ",
558 if (num_generators > 0) {
559 SOLVER_LOG(logger,
"[Symmetry] #generators: ", num_generators,
560 ", average support size: ", average_support_size);
561 if (num_duplicate_constraints > 0) {
562 SOLVER_LOG(logger,
"[Symmetry] The model contains ",
563 num_duplicate_constraints,
" duplicate constraints !");
570 SymmetryProto* symmetry =
proto->mutable_symmetry();
573 std::vector<std::unique_ptr<SparsePermutation>> generators;
576 if (generators.empty()) {
577 proto->clear_symmetry();
581 for (
const std::unique_ptr<SparsePermutation>& perm : generators) {
582 SparsePermutationProto* perm_proto = symmetry->add_permutations();
583 const int num_cycle = perm->NumCycles();
584 for (
int i = 0; i < num_cycle; ++i) {
585 const int old_size = perm_proto->support().size();
586 for (
const int var : perm->Cycle(i)) {
587 perm_proto->add_support(
var);
589 perm_proto->add_cycle_sizes(perm_proto->support().size() - old_size);
594 if (orbitope.empty())
return;
595 SOLVER_LOG(logger,
"[Symmetry] Found orbitope of size ", orbitope.size(),
596 " x ", orbitope[0].size());
597 DenseMatrixProto* matrix = symmetry->add_orbitopes();
598 matrix->set_num_rows(orbitope.size());
599 matrix->set_num_cols(orbitope[0].size());
600 for (
const std::vector<int>&
row : orbitope) {
601 for (
const int entry :
row) {
602 matrix->add_entries(entry);
623 void OrbitAndPropagation(
const std::vector<int>& orbits,
int var,
624 std::vector<int>* can_be_fixed_to_false,
629 if (!
context->CanBeUsedAsLiteral(
var))
return;
645 auto* sat_solver =
model.GetOrCreate<SatSolver>();
646 auto* mapping =
model.GetOrCreate<CpModelMapping>();
647 const Literal to_propagate = mapping->Literal(
var);
649 const VariablesAssignment& assignment = sat_solver->Assignment();
650 if (assignment.LiteralIsAssigned(to_propagate))
return;
651 sat_solver->EnqueueDecisionAndBackjumpOnConflict(to_propagate);
652 if (sat_solver->CurrentDecisionLevel() != 1)
return;
655 can_be_fixed_to_false->clear();
657 const int orbit_index = orbits[
var];
658 const int num_variables = orbits.size();
659 for (
int var = 0;
var < num_variables; ++
var) {
660 if (orbits[
var] != orbit_index)
continue;
667 if (assignment.LiteralIsFalse(mapping->Literal(
var))) {
668 can_be_fixed_to_false->push_back(
var);
671 if (!can_be_fixed_to_false->empty()) {
673 "[Symmetry] Num fixable by binary propagation in orbit: ",
674 can_be_fixed_to_false->size(),
" / ", orbit_size);
681 const SatParameters& params =
context->params();
685 if (
context->working_model->has_objective()) {
686 context->WriteObjectiveToProto();
688 context->WriteVariableDomainsToProto();
692 int64_t num_added = 0;
693 const int initial_ct_index =
proto.constraints().size();
694 const int num_vars =
proto.variables_size();
695 for (
int var = 0;
var < num_vars; ++
var) {
697 if (
context->VariableWasRemoved(
var))
continue;
698 if (
context->VariableIsNotUsedAnymore(
var))
continue;
704 ConstraintProto*
ct =
context->working_model->add_constraints();
705 auto* arg =
ct->mutable_linear();
709 arg->add_coeffs(-r.
coeff);
710 arg->add_domain(r.
offset);
711 arg->add_domain(r.
offset);
714 std::vector<std::unique_ptr<SparsePermutation>> generators;
719 context->working_model->mutable_constraints()->DeleteSubrange(
720 initial_ct_index, num_added);
722 if (generators.empty())
return true;
729 std::vector<const google::protobuf::RepeatedField<int32_t>*> at_most_ones;
730 for (
int i = 0; i <
proto.constraints_size(); ++i) {
731 if (
proto.constraints(i).constraint_case() == ConstraintProto::kAtMostOne) {
732 at_most_ones.push_back(&
proto.constraints(i).at_most_one().literals());
734 if (
proto.constraints(i).constraint_case() ==
735 ConstraintProto::kExactlyOne) {
736 at_most_ones.push_back(&
proto.constraints(i).exactly_one().literals());
744 int distinguished_var = -1;
745 std::vector<int> can_be_fixed_to_false;
748 const std::vector<int> orbits =
GetOrbits(num_vars, generators);
749 std::vector<int> orbit_sizes;
750 int max_orbit_size = 0;
751 for (
int var = 0;
var < num_vars; ++
var) {
752 const int rep = orbits[
var];
753 if (rep == -1)
continue;
754 if (rep >= orbit_sizes.size()) orbit_sizes.resize(rep + 1, 0);
756 if (orbit_sizes[rep] > max_orbit_size) {
757 distinguished_var =
var;
758 max_orbit_size = orbit_sizes[rep];
763 if (
context->logger()->LoggingIsEnabled()) {
764 std::vector<int> sorted_sizes;
765 for (
const int s : orbit_sizes) {
766 if (s != 0) sorted_sizes.push_back(s);
768 std::sort(sorted_sizes.begin(), sorted_sizes.end(), std::greater<int>());
769 const int num_orbits = sorted_sizes.size();
770 if (num_orbits > 10) sorted_sizes.resize(10);
772 " orbits with sizes: ", absl::StrJoin(sorted_sizes,
","),
773 (num_orbits > sorted_sizes.size() ?
",..." :
""));
777 if (max_orbit_size > 2) {
778 OrbitAndPropagation(orbits, distinguished_var, &can_be_fixed_to_false,
781 const int first_heuristic_size = can_be_fixed_to_false.size();
794 std::vector<int> tmp_to_clear;
795 std::vector<int> tmp_sizes(num_vars, 0);
796 for (
const google::protobuf::RepeatedField<int32_t>* literals :
798 tmp_to_clear.clear();
802 for (
const int literal : *literals) {
807 const int rep = orbits[
var];
808 if (rep == -1)
continue;
811 if (tmp_sizes[rep] == 0) tmp_to_clear.push_back(rep);
812 if (tmp_sizes[rep] > 0) ++num_fixable;
817 if (num_fixable > can_be_fixed_to_false.size()) {
818 distinguished_var = -1;
819 can_be_fixed_to_false.clear();
820 for (
const int literal : *literals) {
825 const int rep = orbits[
var];
826 if (rep == -1)
continue;
827 if (distinguished_var == -1 ||
828 orbit_sizes[rep] > orbit_sizes[orbits[distinguished_var]]) {
829 distinguished_var =
var;
833 if (tmp_sizes[rep] == 0) can_be_fixed_to_false.push_back(
var);
838 for (
const int rep : tmp_to_clear) tmp_sizes[rep] = 0;
842 if (can_be_fixed_to_false.size() > first_heuristic_size) {
845 "[Symmetry] Num fixable by intersecting at_most_one with orbits: ",
846 can_be_fixed_to_false.size(),
" largest_orbit: ", max_orbit_size);
866 if (!orbitope.empty()) {
868 orbitope.size(),
" x ", orbitope[0].size());
886 if (!orbitope.empty() &&
context->working_model->has_objective()) {
887 const int num_objective_terms =
context->ObjectiveMap().size();
888 if (orbitope[0].size() == num_objective_terms) {
889 int num_in_column = 0;
890 for (
const std::vector<int>&
row : orbitope) {
891 if (
context->ObjectiveMap().contains(
row[0])) ++num_in_column;
893 if (num_in_column == 1) {
894 context->WriteObjectiveToProto();
895 const auto& obj =
context->working_model->objective();
896 CHECK_EQ(num_objective_terms, obj.vars().size());
897 for (
int i = 1; i < num_objective_terms; ++i) {
899 context->working_model->add_constraints()->mutable_linear();
900 new_ct->add_vars(obj.vars(i - 1));
901 new_ct->add_vars(obj.vars(i));
902 new_ct->add_coeffs(1);
903 new_ct->add_coeffs(-1);
904 new_ct->add_domain(0);
907 context->UpdateNewConstraintsVariableUsage();
908 context->UpdateRuleStats(
"symmetry: objective is one orbitope row.");
921 int max_num_fixed_in_orbitope = 0;
922 if (!orbitope.empty()) {
923 const int num_rows = orbitope[0].size();
924 int size_left = num_rows;
925 for (
int col = 0; size_left > 1 &&
col < orbitope.size(); ++
col) {
926 max_num_fixed_in_orbitope += size_left - 1;
930 if (max_num_fixed_in_orbitope < can_be_fixed_to_false.size()) {
931 const int orbit_index = orbits[distinguished_var];
932 int num_in_orbit = 0;
933 for (
int i = 0; i < can_be_fixed_to_false.size(); ++i) {
934 const int var = can_be_fixed_to_false[i];
935 if (orbits[
var] == orbit_index) ++num_in_orbit;
936 context->UpdateRuleStats(
"symmetry: fixed to false in general orbit");
937 if (!
context->SetLiteralToFalse(
var))
return false;
942 if (orbit_sizes[orbit_index] > num_in_orbit + 1) {
944 "symmetry: added orbit symmetry breaking implications");
945 auto*
ct =
context->working_model->add_constraints();
946 auto* bool_and =
ct->mutable_bool_and();
947 ct->add_enforcement_literal(
NegatedRef(distinguished_var));
948 for (
int var = 0;
var < num_vars; ++
var) {
949 if (orbits[
var] != orbit_index)
continue;
950 if (
var == distinguished_var)
continue;
954 context->UpdateNewConstraintsVariableUsage();
958 if (orbitope.empty())
return true;
961 std::vector<int> tmp_to_clear;
962 std::vector<int> tmp_sizes(num_vars, 0);
963 std::vector<int> tmp_num_positive(num_vars, 0);
968 for (
const google::protobuf::RepeatedField<int32_t>* literals :
970 for (
const int lit : *literals) {
972 CHECK_NE(tmp_sizes[
var], 1);
975 for (
const int lit : *literals) {
980 while (!orbitope.empty() && orbitope[0].size() > 1) {
981 const int num_cols = orbitope[0].size();
1012 std::vector<bool> all_equivalent_rows(orbitope.size(),
false);
1018 bool at_most_one_in_best_rows;
1019 int64_t best_score = 0;
1020 std::vector<int> best_rows;
1022 std::vector<int> rows_in_at_most_one;
1023 for (
const google::protobuf::RepeatedField<int32_t>* literals :
1025 tmp_to_clear.clear();
1026 for (
const int literal : *literals) {
1029 const int rep = orbits[
var];
1030 if (rep == -1)
continue;
1032 if (tmp_sizes[rep] == 0) tmp_to_clear.push_back(rep);
1037 int num_positive_direction = 0;
1038 int num_negative_direction = 0;
1043 bool possible_extension =
false;
1045 rows_in_at_most_one.clear();
1046 for (
const int row : tmp_to_clear) {
1047 const int size = tmp_sizes[
row];
1048 const int num_positive = tmp_num_positive[
row];
1049 const int num_negative = tmp_sizes[
row] - tmp_num_positive[
row];
1051 tmp_num_positive[
row] = 0;
1053 if (num_positive > 1 && num_negative == 0) {
1054 if (size < num_cols) possible_extension =
true;
1055 rows_in_at_most_one.push_back(
row);
1056 ++num_positive_direction;
1057 }
else if (num_positive == 0 && num_negative > 1) {
1058 if (size < num_cols) possible_extension =
true;
1059 rows_in_at_most_one.push_back(
row);
1060 ++num_negative_direction;
1061 }
else if (num_positive > 0 && num_negative > 0) {
1062 all_equivalent_rows[
row] =
true;
1066 if (possible_extension) {
1068 "TODO symmetry: possible at most one extension.");
1071 if (num_positive_direction > 0 && num_negative_direction > 0) {
1072 return context->NotifyThatModelIsUnsat(
"Symmetry and at most ones");
1074 const bool direction = num_positive_direction > 0;
1086 for (
const int row : rows_in_at_most_one) {
1090 if (score > best_score) {
1091 at_most_one_in_best_rows = direction;
1093 best_rows = rows_in_at_most_one;
1102 for (
int i = 0; i < all_equivalent_rows.size(); ++i) {
1103 if (all_equivalent_rows[i]) {
1104 for (
int j = 1; j < num_cols; ++j) {
1105 context->StoreBooleanEqualityRelation(orbitope[i][0], orbitope[i][j]);
1106 context->UpdateRuleStats(
"symmetry: all equivalent in orbit");
1107 if (
context->ModelIsUnsat())
return false;
1120 if (best_score == 0) {
1122 "TODO symmetry: add symmetry breaking inequalities?");
1129 for (
const int i : best_rows) {
1130 for (
int j = 0; j < num_cols; ++j) {
1131 const int var = orbitope[i][j];
1132 if ((at_most_one_in_best_rows &&
context->LiteralIsTrue(
var)) ||
1133 (!at_most_one_in_best_rows &&
context->LiteralIsFalse(
var))) {
1134 return context->NotifyThatModelIsUnsat(
"Symmetry and at most one");
1143 const int best_col = 0;
1144 for (
const int i : best_rows) {
1145 for (
int j = 0; j < num_cols; ++j) {
1146 if (j == best_col)
continue;
1147 const int var = orbitope[i][j];
1148 if (at_most_one_in_best_rows) {
1149 context->UpdateRuleStats(
"symmetry: fixed to false");
1150 if (!
context->SetLiteralToFalse(
var))
return false;
1152 context->UpdateRuleStats(
"symmetry: fixed to true");
1153 if (!
context->SetLiteralToTrue(
var))
return false;
1159 for (
const int i : best_rows) orbitope[i].clear();
1161 for (
int i = 0; i < orbitope.size(); ++i) {
1162 if (!orbitope[i].empty()) orbitope[new_size++] = orbitope[i];
1164 CHECK_LT(new_size, orbitope.size());
1165 orbitope.resize(new_size);
1168 for (
int i = 0; i < orbitope.size(); ++i) {
1169 std::swap(orbitope[i][best_col], orbitope[i].back());
1170 orbitope[i].pop_back();
1176 if (orbitope.size() == 1) {
1177 const int num_cols = orbitope[0].size();
1178 for (
int i = 0; i + 1 < num_cols; ++i) {
1180 ConstraintProto*
ct =
context->working_model->add_constraints();
1181 ct->mutable_linear()->add_coeffs(1);
1182 ct->mutable_linear()->add_vars(orbitope[0][i]);
1183 ct->mutable_linear()->add_coeffs(-1);
1184 ct->mutable_linear()->add_vars(orbitope[0][i + 1]);
1185 ct->mutable_linear()->add_domain(0);
1187 context->UpdateRuleStats(
"symmetry: added symmetry breaking inequality");
1189 context->UpdateNewConstraintsVariableUsage();
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
void RemoveCycles(const std::vector< int > &cycle_indices)
const std::vector< int > & Support() const
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
void swap(IdMap< K, V > &a, IdMap< K, V > &b)
void DetectAndAddSymmetryToProto(const SatParameters ¶ms, CpModelProto *proto, SolverLogger *logger)
bool DetectAndExploitSymmetriesInPresolve(PresolveContext *context)
bool RefIsPositive(int ref)
std::vector< int > GetOrbitopeOrbits(int n, const std::vector< std::vector< int >> &orbitope)
Graph * GenerateGraphForSymmetryDetection(const LinearBooleanProblem &problem, std::vector< int > *initial_equivalence_classes)
void FindCpModelSymmetries(const SatParameters ¶ms, const CpModelProto &problem, std::vector< std::unique_ptr< SparsePermutation >> *generators, double deterministic_limit, SolverLogger *logger)
bool LoadModelForProbing(PresolveContext *context, Model *local_model)
std::string ConstraintCaseName(ConstraintProto::ConstraintCase constraint_case)
std::vector< std::vector< int > > BasicOrbitopeExtraction(const std::vector< std::unique_ptr< SparsePermutation >> &generators)
std::vector< int > GetOrbits(int n, const std::vector< std::unique_ptr< SparsePermutation >> &generators)
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,...)
#define VLOG(verboselevel)