26 #include "absl/container/flat_hash_map.h"
27 #include "absl/strings/match.h"
28 #include "absl/strings/str_cat.h"
29 #include "absl/strings/str_format.h"
30 #include "absl/synchronization/mutex.h"
31 #include "google/protobuf/text_format.h"
38 #include "ortools/sat/cp_model.pb.h"
55 "Default max value for unbounded integer variables.");
70 int TrueLiteral(
int var) {
return var; }
71 int FalseLiteral(
int var) {
return -
var - 1; }
72 int NegatedCpModelVariable(
int var) {
return -
var - 1; }
75 struct CpModelProtoWithMapping {
77 int LookupConstant(int64_t
value);
81 int LookupVar(
const fz::Argument& argument);
82 LinearExpressionProto LookupExpr(
const fz::Argument& argument,
84 LinearExpressionProto LookupExprAt(
const fz::Argument& argument,
int pos,
86 std::vector<int> LookupVars(
const fz::Argument& argument);
87 std::vector<VarOrValue> LookupVarsOrValues(
const fz::Argument& argument);
92 std::vector<int> CreateIntervals(
const std::vector<int>& starts,
93 const std::vector<VarOrValue>& sizes);
100 int GetOrCreateOptionalInterval(
int start_var, VarOrValue size,
int opt_var);
104 ConstraintProto* AddEnforcedConstraint(
int literal);
107 void FillAMinusBInDomain(
const std::vector<int64_t>& domain,
108 const fz::Constraint& fz_ct, ConstraintProto*
ct);
109 void FillLinearConstraintWithGivenDomain(
const std::vector<int64_t>& domain,
110 const fz::Constraint& fz_ct,
111 ConstraintProto*
ct);
112 void FillConstraint(
const fz::Constraint& fz_ct, ConstraintProto*
ct);
113 void FillReifOrImpliedConstraint(
const fz::Constraint& fz_ct,
114 ConstraintProto*
ct);
118 void TranslateSearchAnnotations(
119 const std::vector<fz::Annotation>& search_annotations,
120 SolverLogger* logger);
129 absl::flat_hash_map<std::tuple<int, int, int>,
int>
131 absl::flat_hash_map<std::tuple<int, int64_t, int>,
int>
135 int CpModelProtoWithMapping::LookupConstant(int64_t
value) {
142 IntegerVariableProto* var_proto =
proto.add_variables();
143 var_proto->add_domain(
value);
144 var_proto->add_domain(
value);
149 int CpModelProtoWithMapping::LookupVar(
const fz::Argument& argument) {
150 if (argument.HasOneValue())
return LookupConstant(argument.Value());
155 LinearExpressionProto CpModelProtoWithMapping::LookupExpr(
156 const fz::Argument& argument,
bool negate) {
157 LinearExpressionProto expr;
158 if (argument.HasOneValue()) {
159 const int64_t
value = argument.Value();
162 expr.add_vars(LookupVar(argument));
163 expr.add_coeffs(negate ? -1 : 1);
168 LinearExpressionProto CpModelProtoWithMapping::LookupExprAt(
169 const fz::Argument& argument,
int pos,
bool negate) {
170 LinearExpressionProto expr;
171 if (argument.HasOneValueAt(pos)) {
172 const int64_t
value = argument.ValueAt(pos);
176 expr.add_coeffs(negate ? -1 : 1);
181 std::vector<int> CpModelProtoWithMapping::LookupVars(
182 const fz::Argument& argument) {
183 std::vector<int> result;
186 for (int64_t
value : argument.values) {
187 result.push_back(LookupConstant(
value));
190 result.push_back(LookupConstant(argument.Value()));
193 for (fz::Variable*
var : argument.variables) {
194 CHECK(
var !=
nullptr);
201 std::vector<VarOrValue> CpModelProtoWithMapping::LookupVarsOrValues(
202 const fz::Argument& argument) {
203 std::vector<VarOrValue> result;
204 const int no_var = kNoVar;
207 for (int64_t
value : argument.values) {
208 result.push_back({no_var,
value});
211 result.push_back({no_var, argument.Value()});
214 for (fz::Variable*
var : argument.variables) {
215 CHECK(
var !=
nullptr);
216 if (
var->domain.HasOneValue()) {
217 result.push_back({no_var,
var->domain.Value()});
226 ConstraintProto* CpModelProtoWithMapping::AddEnforcedConstraint(
int literal) {
227 ConstraintProto* result =
proto.add_constraints();
229 result->add_enforcement_literal(
literal);
234 int CpModelProtoWithMapping::GetOrCreateOptionalInterval(
int start_var,
238 if (size.var == kNoVar) {
239 const std::tuple<int, int64_t, int> key =
240 std::make_tuple(start_var, size.value, opt_var);
241 const auto [it, inserted] =
247 auto*
interval = AddEnforcedConstraint(opt_var)->mutable_interval();
248 interval->mutable_start()->add_vars(start_var);
249 interval->mutable_start()->add_coeffs(1);
250 interval->mutable_size()->set_offset(size.value);
251 interval->mutable_end()->add_vars(start_var);
252 interval->mutable_end()->add_coeffs(1);
253 interval->mutable_end()->set_offset(size.value);
257 const std::tuple<int, int, int> key =
258 std::make_tuple(start_var, size.var, opt_var);
259 const auto [it, inserted] =
265 const int end_var =
proto.variables_size();
269 proto.add_variables());
272 auto*
interval = AddEnforcedConstraint(opt_var)->mutable_interval();
273 interval->mutable_start()->add_vars(start_var);
274 interval->mutable_start()->add_coeffs(1);
275 interval->mutable_size()->add_vars(size.var);
276 interval->mutable_size()->add_coeffs(1);
277 interval->mutable_end()->add_vars(end_var);
278 interval->mutable_end()->add_coeffs(1);
282 auto* lin = AddEnforcedConstraint(opt_var)->mutable_linear();
283 lin->add_vars(start_var);
285 lin->add_vars(size.var);
287 lin->add_vars(end_var);
296 std::vector<int> CpModelProtoWithMapping::CreateIntervals(
297 const std::vector<int>& starts,
const std::vector<VarOrValue>& sizes) {
298 std::vector<int> intervals;
299 for (
int i = 0; i < starts.size(); ++i) {
301 GetOrCreateOptionalInterval(starts[i], sizes[i], kNoVar));
306 void CpModelProtoWithMapping::FillAMinusBInDomain(
307 const std::vector<int64_t>& domain,
const fz::Constraint& fz_ct,
308 ConstraintProto*
ct) {
309 auto* arg =
ct->mutable_linear();
311 const int64_t
value = fz_ct.arguments[1].Value();
312 const int var_a = LookupVar(fz_ct.arguments[0]);
313 for (
const int64_t domain_bound : domain) {
316 arg->add_domain(domain_bound);
318 arg->add_domain(domain_bound +
value);
321 arg->add_vars(var_a);
324 const int64_t
value = fz_ct.arguments[0].Value();
325 const int var_b = LookupVar(fz_ct.arguments[1]);
332 arg->add_domain(
value - domain_bound);
335 arg->add_vars(var_b);
338 for (
const int64_t domain_bound : domain) arg->add_domain(domain_bound);
339 arg->add_vars(LookupVar(fz_ct.arguments[0]));
341 arg->add_vars(LookupVar(fz_ct.arguments[1]));
346 void CpModelProtoWithMapping::FillLinearConstraintWithGivenDomain(
347 const std::vector<int64_t>& domain,
const fz::Constraint& fz_ct,
348 ConstraintProto*
ct) {
349 auto* arg =
ct->mutable_linear();
350 for (
const int64_t domain_bound : domain) arg->add_domain(domain_bound);
351 std::vector<int> vars = LookupVars(fz_ct.arguments[1]);
352 for (
int i = 0; i < vars.size(); ++i) {
353 arg->add_vars(vars[i]);
354 arg->add_coeffs(fz_ct.arguments[0].values[i]);
358 void CpModelProtoWithMapping::FillConstraint(
const fz::Constraint& fz_ct,
359 ConstraintProto*
ct) {
360 if (fz_ct.type ==
"false_constraint") {
362 ct->mutable_bool_or();
363 }
else if (fz_ct.type ==
"bool_clause") {
364 auto* arg =
ct->mutable_bool_or();
365 for (
const int var : LookupVars(fz_ct.arguments[0])) {
366 arg->add_literals(TrueLiteral(
var));
368 for (
const int var : LookupVars(fz_ct.arguments[1])) {
369 arg->add_literals(FalseLiteral(
var));
371 }
else if (fz_ct.type ==
"bool_xor") {
374 const int a = LookupVar(fz_ct.arguments[0]);
375 const int b = LookupVar(fz_ct.arguments[1]);
376 const int x = LookupVar(fz_ct.arguments[2]);
380 auto*
const refute =
ct->mutable_linear();
382 refute->add_coeffs(1);
384 refute->add_coeffs(-1);
385 refute->add_domain(0);
386 refute->add_domain(0);
389 auto* enforce = AddEnforcedConstraint(x)->mutable_linear();
390 enforce->add_vars(
a);
391 enforce->add_coeffs(1);
392 enforce->add_vars(
b);
393 enforce->add_coeffs(1);
394 enforce->add_domain(1);
395 enforce->add_domain(1);
396 }
else if (fz_ct.type ==
"array_bool_or") {
397 auto* arg =
ct->mutable_bool_or();
398 for (
const int var : LookupVars(fz_ct.arguments[0])) {
399 arg->add_literals(TrueLiteral(
var));
401 }
else if (fz_ct.type ==
"array_bool_or_negated") {
402 auto* arg =
ct->mutable_bool_and();
403 for (
const int var : LookupVars(fz_ct.arguments[0])) {
404 arg->add_literals(FalseLiteral(
var));
406 }
else if (fz_ct.type ==
"array_bool_and") {
407 auto* arg =
ct->mutable_bool_and();
408 for (
const int var : LookupVars(fz_ct.arguments[0])) {
409 arg->add_literals(TrueLiteral(
var));
411 }
else if (fz_ct.type ==
"array_bool_and_negated") {
412 auto* arg =
ct->mutable_bool_or();
413 for (
const int var : LookupVars(fz_ct.arguments[0])) {
414 arg->add_literals(FalseLiteral(
var));
416 }
else if (fz_ct.type ==
"array_bool_xor") {
417 auto* arg =
ct->mutable_bool_xor();
418 for (
const int var : LookupVars(fz_ct.arguments[0])) {
419 arg->add_literals(TrueLiteral(
var));
421 }
else if (fz_ct.type ==
"bool_le" || fz_ct.type ==
"int_le") {
423 }
else if (fz_ct.type ==
"bool_ge" || fz_ct.type ==
"int_ge") {
425 }
else if (fz_ct.type ==
"bool_lt" || fz_ct.type ==
"int_lt") {
427 }
else if (fz_ct.type ==
"bool_gt" || fz_ct.type ==
"int_gt") {
429 }
else if (fz_ct.type ==
"bool_eq" || fz_ct.type ==
"int_eq" ||
430 fz_ct.type ==
"bool2int") {
431 FillAMinusBInDomain({0, 0}, fz_ct,
ct);
432 }
else if (fz_ct.type ==
"bool_ne" || fz_ct.type ==
"bool_not") {
433 auto* arg =
ct->mutable_linear();
434 arg->add_vars(LookupVar(fz_ct.arguments[0]));
436 arg->add_vars(LookupVar(fz_ct.arguments[1]));
440 }
else if (fz_ct.type ==
"int_ne") {
444 }
else if (fz_ct.type ==
"int_lin_eq") {
445 const int64_t rhs = fz_ct.arguments[2].values[0];
446 FillLinearConstraintWithGivenDomain({rhs, rhs}, fz_ct,
ct);
447 }
else if (fz_ct.type ==
"bool_lin_eq") {
448 auto* arg =
ct->mutable_linear();
449 const std::vector<int> vars = LookupVars(fz_ct.arguments[1]);
450 for (
int i = 0; i < vars.size(); ++i) {
451 arg->add_vars(vars[i]);
452 arg->add_coeffs(fz_ct.arguments[0].values[i]);
454 if (fz_ct.arguments[2].IsVariable()) {
455 arg->add_vars(LookupVar(fz_ct.arguments[2]));
460 const int64_t v = fz_ct.arguments[2].Value();
464 }
else if (fz_ct.type ==
"int_lin_le" || fz_ct.type ==
"bool_lin_le") {
465 const int64_t rhs = fz_ct.arguments[2].values[0];
466 FillLinearConstraintWithGivenDomain(
468 }
else if (fz_ct.type ==
"int_lin_lt") {
469 const int64_t rhs = fz_ct.arguments[2].values[0];
470 FillLinearConstraintWithGivenDomain(
472 }
else if (fz_ct.type ==
"int_lin_ge") {
473 const int64_t rhs = fz_ct.arguments[2].values[0];
474 FillLinearConstraintWithGivenDomain(
476 }
else if (fz_ct.type ==
"int_lin_gt") {
477 const int64_t rhs = fz_ct.arguments[2].values[0];
478 FillLinearConstraintWithGivenDomain(
480 }
else if (fz_ct.type ==
"int_lin_ne") {
481 const int64_t rhs = fz_ct.arguments[2].values[0];
482 FillLinearConstraintWithGivenDomain(
486 }
else if (fz_ct.type ==
"set_in") {
487 auto* arg =
ct->mutable_linear();
488 arg->add_vars(LookupVar(fz_ct.arguments[0]));
492 fz_ct.arguments[1].values.begin(),
493 fz_ct.arguments[1].values.end()}),
497 Domain(fz_ct.arguments[1].values[0], fz_ct.arguments[1].values[1]),
500 LOG(FATAL) <<
"Wrong format";
502 }
else if (fz_ct.type ==
"set_in_negated") {
503 auto* arg =
ct->mutable_linear();
504 arg->add_vars(LookupVar(fz_ct.arguments[0]));
509 std::vector<int64_t>{fz_ct.arguments[1].values.begin(),
510 fz_ct.arguments[1].values.end()})
515 Domain(fz_ct.arguments[1].values[0], fz_ct.arguments[1].values[1])
519 LOG(FATAL) <<
"Wrong format";
521 }
else if (fz_ct.type ==
"int_min") {
522 auto* arg =
ct->mutable_lin_max();
523 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0],
true);
524 *arg->add_exprs() = LookupExpr(fz_ct.arguments[1],
true);
525 *arg->mutable_target() = LookupExpr(fz_ct.arguments[2],
true);
526 }
else if (fz_ct.type ==
"array_int_minimum" || fz_ct.type ==
"minimum_int") {
527 auto* arg =
ct->mutable_lin_max();
528 *arg->mutable_target() = LookupExpr(fz_ct.arguments[0],
true);
529 for (
int i = 0; i < fz_ct.arguments[1].Size(); ++i) {
530 *arg->add_exprs() = LookupExprAt(fz_ct.arguments[1], i,
true);
532 }
else if (fz_ct.type ==
"int_max") {
533 auto* arg =
ct->mutable_lin_max();
534 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
535 *arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
536 *arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
537 }
else if (fz_ct.type ==
"array_int_maximum" || fz_ct.type ==
"maximum_int") {
538 auto* arg =
ct->mutable_lin_max();
539 *arg->mutable_target() = LookupExpr(fz_ct.arguments[0]);
540 for (
int i = 0; i < fz_ct.arguments[1].Size(); ++i) {
541 *arg->add_exprs() = LookupExprAt(fz_ct.arguments[1], i);
543 }
else if (fz_ct.type ==
"int_times") {
544 auto* arg =
ct->mutable_int_prod();
545 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
546 *arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
547 *arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
548 }
else if (fz_ct.type ==
"int_abs") {
549 auto* arg =
ct->mutable_lin_max();
550 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
551 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0],
true);
552 *arg->mutable_target() = LookupExpr(fz_ct.arguments[1]);
553 }
else if (fz_ct.type ==
"int_plus") {
554 auto* arg =
ct->mutable_linear();
556 arg->add_vars(LookupVar(fz_ct.arguments[0]));
558 arg->add_vars(LookupVar(fz_ct.arguments[1]));
560 arg->add_vars(LookupVar(fz_ct.arguments[2]));
562 }
else if (fz_ct.type ==
"int_div") {
563 auto* arg =
ct->mutable_int_div();
564 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
565 *arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
566 *arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
567 }
else if (fz_ct.type ==
"int_mod") {
568 auto* arg =
ct->mutable_int_mod();
569 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
570 *arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
571 *arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
572 }
else if (fz_ct.type ==
"array_int_element" ||
573 fz_ct.type ==
"array_bool_element" ||
574 fz_ct.type ==
"array_var_int_element" ||
575 fz_ct.type ==
"array_var_bool_element" ||
576 fz_ct.type ==
"array_int_element_nonshifted") {
579 auto* arg =
ct->mutable_element();
580 arg->set_index(LookupVar(fz_ct.arguments[0]));
581 arg->set_target(LookupVar(fz_ct.arguments[2]));
583 if (!absl::EndsWith(fz_ct.type,
"_nonshifted")) {
587 arg->add_vars(LookupConstant(0));
589 for (
const int var : LookupVars(fz_ct.arguments[1])) arg->add_vars(
var);
593 CHECK(!absl::EndsWith(fz_ct.type,
"_nonshifted"));
594 auto* arg =
ct->mutable_table();
598 for (
const int var : LookupVars(fz_ct.arguments[0])) arg->add_vars(
var);
599 arg->add_vars(LookupVar(fz_ct.arguments[2]));
601 const std::vector<int64_t>& values = fz_ct.arguments[1].values;
602 const int64_t coeff1 = fz_ct.arguments[3].values[0];
603 const int64_t coeff2 = fz_ct.arguments[3].values[1];
604 const int64_t offset = fz_ct.arguments[4].values[0] - 1;
607 for (
const int64_t
b :
609 const int index = coeff1 *
a + coeff2 *
b + offset;
611 CHECK_LT(
index, values.size());
614 arg->add_values(values[
index]);
618 }
else if (fz_ct.type ==
"ortools_table_int") {
619 auto* arg =
ct->mutable_table();
620 for (
const int var : LookupVars(fz_ct.arguments[0])) arg->add_vars(
var);
621 for (
const int64_t
value : fz_ct.arguments[1].values)
622 arg->add_values(
value);
623 }
else if (fz_ct.type ==
"ortools_regular") {
624 auto* arg =
ct->mutable_automaton();
625 for (
const int var : LookupVars(fz_ct.arguments[0])) arg->add_vars(
var);
628 const int num_states = fz_ct.arguments[1].Value();
629 const int num_values = fz_ct.arguments[2].Value();
630 for (
int i = 1; i <= num_states; ++i) {
631 for (
int j = 1; j <= num_values; ++j) {
632 CHECK_LT(count, fz_ct.arguments[3].values.size());
633 const int next = fz_ct.arguments[3].values[count++];
634 if (
next == 0)
continue;
635 arg->add_transition_tail(i);
636 arg->add_transition_label(j);
637 arg->add_transition_head(
next);
641 arg->set_starting_state(fz_ct.arguments[4].Value());
642 switch (fz_ct.arguments[5].type) {
644 arg->add_final_states(fz_ct.arguments[5].values[0]);
648 for (
int v = fz_ct.arguments[5].values[0];
649 v <= fz_ct.arguments[5].values[1]; ++v) {
650 arg->add_final_states(v);
655 for (
const int v : fz_ct.arguments[5].values) {
656 arg->add_final_states(v);
661 LOG(FATAL) <<
"Wrong constraint " << fz_ct.DebugString();
664 }
else if (fz_ct.type ==
"fzn_all_different_int") {
665 auto* arg =
ct->mutable_all_diff();
666 for (
int i = 0; i < fz_ct.arguments[0].Size(); ++i) {
667 *arg->add_exprs() = LookupExprAt(fz_ct.arguments[0], i);
669 }
else if (fz_ct.type ==
"ortools_circuit" ||
670 fz_ct.type ==
"ortools_subcircuit") {
671 const int64_t min_index = fz_ct.arguments[1].Value();
672 const int size =
std::max(fz_ct.arguments[0].values.size(),
673 fz_ct.arguments[0].variables.size());
675 const int64_t max_index = min_index + size - 1;
677 auto* circuit_arg =
ct->mutable_circuit();
681 int64_t
index = min_index;
682 const bool is_circuit = (fz_ct.type ==
"ortools_circuit");
683 for (
const int var : LookupVars(fz_ct.arguments[0])) {
687 domain = domain.IntersectionWith(Domain(min_index, max_index));
696 for (
const ClosedInterval
interval : domain.intervals()) {
701 auto* new_var =
proto.add_variables();
702 new_var->add_domain(0);
703 new_var->add_domain(1);
707 circuit_arg->add_tails(
index);
708 circuit_arg->add_heads(
value);
709 circuit_arg->add_literals(
literal);
713 auto* lin = AddEnforcedConstraint(
literal)->mutable_linear();
716 lin->add_domain(
value);
717 lin->add_domain(
value);
727 lin->add_domain(
value - 1);
728 lin->add_domain(
value + 1);
736 }
else if (fz_ct.type ==
"ortools_inverse") {
737 auto* arg =
ct->mutable_inverse();
739 const auto direct_variables = LookupVars(fz_ct.arguments[0]);
740 const auto inverse_variables = LookupVars(fz_ct.arguments[1]);
741 const int base_direct = fz_ct.arguments[2].Value();
742 const int base_inverse = fz_ct.arguments[3].Value();
744 CHECK_EQ(direct_variables.size(), inverse_variables.size());
745 const int num_variables = direct_variables.size();
746 const int end_direct = base_direct + num_variables;
747 const int end_inverse = base_inverse + num_variables;
769 const int arity =
std::max(base_inverse, base_direct) + num_variables;
770 for (
int i = 0; i < arity; ++i) {
772 if (i < base_direct) {
773 if (i < base_inverse) {
774 arg->add_f_direct(LookupConstant(i));
775 }
else if (i >= base_inverse) {
776 arg->add_f_direct(LookupConstant(i + num_variables));
778 }
else if (i >= base_direct && i < end_direct) {
779 arg->add_f_direct(direct_variables[i - base_direct]);
781 arg->add_f_direct(LookupConstant(i - num_variables));
785 if (i < base_inverse) {
786 if (i < base_direct) {
787 arg->add_f_inverse(LookupConstant(i));
788 }
else if (i >= base_direct) {
789 arg->add_f_inverse(LookupConstant(i + num_variables));
791 }
else if (i >= base_inverse && i < end_inverse) {
792 arg->add_f_inverse(inverse_variables[i - base_inverse]);
794 arg->add_f_inverse(LookupConstant(i - num_variables));
797 }
else if (fz_ct.type ==
"fzn_cumulative") {
798 const std::vector<int> starts = LookupVars(fz_ct.arguments[0]);
799 const std::vector<VarOrValue> sizes =
800 LookupVarsOrValues(fz_ct.arguments[1]);
801 const std::vector<VarOrValue> demands =
802 LookupVarsOrValues(fz_ct.arguments[2]);
804 auto* arg =
ct->mutable_cumulative();
805 if (fz_ct.arguments[3].HasOneValue()) {
806 arg->mutable_capacity()->set_offset(fz_ct.arguments[3].Value());
808 arg->mutable_capacity()->add_vars(LookupVar(fz_ct.arguments[3]));
809 arg->mutable_capacity()->add_coeffs(1);
811 for (
int i = 0; i < starts.size(); ++i) {
814 if (demands[i].
var != kNoVar &&
815 proto.variables(demands[i].var).domain().size() == 2 &&
816 proto.variables(demands[i].var).domain(0) == 0 &&
817 proto.variables(demands[i].var).domain(1) == 1 &&
818 fz_ct.arguments[3].HasOneValue() && fz_ct.arguments[3].Value() == 1) {
820 GetOrCreateOptionalInterval(starts[i], sizes[i], demands[i].
var));
821 arg->add_demands()->set_offset(1);
824 GetOrCreateOptionalInterval(starts[i], sizes[i], kNoVar));
825 LinearExpressionProto*
demand = arg->add_demands();
826 if (demands[i].
var == kNoVar) {
834 }
else if (fz_ct.type ==
"fzn_diffn" || fz_ct.type ==
"fzn_diffn_nonstrict") {
835 const std::vector<int> x = LookupVars(fz_ct.arguments[0]);
836 const std::vector<int> y = LookupVars(fz_ct.arguments[1]);
837 const std::vector<VarOrValue> dx = LookupVarsOrValues(fz_ct.arguments[2]);
838 const std::vector<VarOrValue> dy = LookupVarsOrValues(fz_ct.arguments[3]);
839 const std::vector<int> x_intervals = CreateIntervals(x, dx);
840 const std::vector<int> y_intervals = CreateIntervals(y, dy);
841 auto* arg =
ct->mutable_no_overlap_2d();
842 for (
int i = 0; i < x.size(); ++i) {
843 arg->add_x_intervals(x_intervals[i]);
844 arg->add_y_intervals(y_intervals[i]);
846 arg->set_boxes_with_null_area_can_overlap(fz_ct.type ==
847 "fzn_diffn_nonstrict");
848 }
else if (fz_ct.type ==
"ortools_network_flow" ||
849 fz_ct.type ==
"ortools_network_flow_cost") {
852 const bool has_cost = fz_ct.type ==
"ortools_network_flow_cost";
853 const std::vector<int> flow = LookupVars(fz_ct.arguments[has_cost ? 3 : 2]);
856 const int num_nodes = fz_ct.arguments[1].values.size();
857 std::vector<std::vector<int>> flows_per_node(num_nodes);
858 std::vector<std::vector<int>> coeffs_per_node(num_nodes);
859 const int num_arcs = fz_ct.arguments[0].values.size() / 2;
860 for (
int arc = 0;
arc < num_arcs;
arc++) {
861 const int tail = fz_ct.arguments[0].values[2 *
arc] - 1;
862 const int head = fz_ct.arguments[0].values[2 *
arc + 1] - 1;
865 flows_per_node[
tail].push_back(flow[
arc]);
866 coeffs_per_node[
tail].push_back(1);
867 flows_per_node[
head].push_back(flow[
arc]);
868 coeffs_per_node[
head].push_back(-1);
870 for (
int node = 0; node < num_nodes; node++) {
871 auto* arg =
proto.add_constraints()->mutable_linear();
872 arg->add_domain(fz_ct.arguments[1].values[node]);
873 arg->add_domain(fz_ct.arguments[1].values[node]);
874 for (
int i = 0; i < flows_per_node[node].size(); ++i) {
875 arg->add_vars(flows_per_node[node][i]);
876 arg->add_coeffs(coeffs_per_node[node][i]);
881 auto* arg =
proto.add_constraints()->mutable_linear();
884 for (
int arc = 0;
arc < num_arcs;
arc++) {
885 const int64_t
weight = fz_ct.arguments[2].values[
arc];
887 arg->add_vars(flow[
arc]);
891 arg->add_vars(LookupVar(fz_ct.arguments[4]));
895 LOG(FATAL) <<
" Not supported " << fz_ct.type;
899 void CpModelProtoWithMapping::FillReifOrImpliedConstraint(
900 const fz::Constraint& fz_ct, ConstraintProto*
ct) {
902 std::string simplified_type;
903 if (absl::EndsWith(fz_ct.type,
"_reif")) {
905 simplified_type = fz_ct.type.substr(0, fz_ct.type.size() - 5);
906 }
else if (absl::EndsWith(fz_ct.type,
"_imp")) {
908 simplified_type = fz_ct.type.substr(0, fz_ct.type.size() - 4);
911 simplified_type = fz_ct.type;
915 fz::Constraint copy = fz_ct;
916 copy.type = simplified_type;
919 FillConstraint(copy,
ct);
922 std::string negated_type;
925 if (simplified_type ==
"array_bool_or") {
926 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[1])));
927 negated_type =
"array_bool_or_negated";
928 }
else if (simplified_type ==
"array_bool_and") {
929 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[1])));
930 negated_type =
"array_bool_and_negated";
931 }
else if (simplified_type ==
"set_in") {
932 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
933 negated_type =
"set_in_negated";
934 }
else if (simplified_type ==
"bool_eq" || simplified_type ==
"int_eq") {
935 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
936 negated_type =
"int_ne";
937 }
else if (simplified_type ==
"bool_ne" || simplified_type ==
"int_ne") {
938 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
939 negated_type =
"int_eq";
940 }
else if (simplified_type ==
"bool_le" || simplified_type ==
"int_le") {
941 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
942 negated_type =
"int_gt";
943 }
else if (simplified_type ==
"bool_lt" || simplified_type ==
"int_lt") {
944 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
945 negated_type =
"int_ge";
946 }
else if (simplified_type ==
"bool_ge" || simplified_type ==
"int_ge") {
947 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
948 negated_type =
"int_lt";
949 }
else if (simplified_type ==
"bool_gt" || simplified_type ==
"int_gt") {
950 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
951 negated_type =
"int_le";
952 }
else if (simplified_type ==
"int_lin_eq") {
953 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
954 negated_type =
"int_lin_ne";
955 }
else if (simplified_type ==
"int_lin_ne") {
956 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
957 negated_type =
"int_lin_eq";
958 }
else if (simplified_type ==
"int_lin_le") {
959 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
960 negated_type =
"int_lin_gt";
961 }
else if (simplified_type ==
"int_lin_ge") {
962 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
963 negated_type =
"int_lin_lt";
964 }
else if (simplified_type ==
"int_lin_lt") {
965 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
966 negated_type =
"int_lin_ge";
967 }
else if (simplified_type ==
"int_lin_gt") {
968 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
969 negated_type =
"int_lin_le";
971 LOG(FATAL) <<
"Unsupported " << simplified_type;
975 if (absl::EndsWith(fz_ct.type,
"_imp"))
return;
979 ConstraintProto* negated_ct =
proto.add_constraints();
980 negated_ct->set_name(fz_ct.type +
" (negated)");
981 negated_ct->add_enforcement_literal(
983 copy.type = negated_type;
984 FillConstraint(copy, negated_ct);
987 void CpModelProtoWithMapping::TranslateSearchAnnotations(
988 const std::vector<fz::Annotation>& search_annotations,
989 SolverLogger* logger) {
990 std::vector<fz::Annotation> flat_annotations;
991 for (
const fz::Annotation& annotation : search_annotations) {
996 absl::flat_hash_set<int> hinted_vars;
998 for (
const fz::Annotation& annotation : flat_annotations) {
999 if (annotation.IsFunctionCallWithIdentifier(
"warm_start")) {
1000 CHECK_EQ(2, annotation.annotations.size());
1001 const fz::Annotation& vars = annotation.annotations[0];
1002 const fz::Annotation& values = annotation.annotations[1];
1007 for (
int i = 0; i < vars.variables.size(); ++i) {
1008 fz::Variable* fz_var = vars.variables[i];
1010 const int64_t
value = values.values[i];
1011 if (hinted_vars.insert(
var).second) {
1012 proto.mutable_solution_hint()->add_vars(
var);
1013 proto.mutable_solution_hint()->add_values(
value);
1016 }
else if (annotation.IsFunctionCallWithIdentifier(
"int_search") ||
1017 annotation.IsFunctionCallWithIdentifier(
"bool_search")) {
1018 const std::vector<fz::Annotation>& args = annotation.annotations;
1019 std::vector<fz::Variable*> vars;
1020 args[0].AppendAllVariables(&vars);
1022 DecisionStrategyProto* strategy =
proto.add_search_strategy();
1023 for (fz::Variable* v : vars) {
1027 const fz::Annotation& choose = args[1];
1028 if (choose.id ==
"input_order") {
1029 strategy->set_variable_selection_strategy(
1030 DecisionStrategyProto::CHOOSE_FIRST);
1031 }
else if (choose.id ==
"first_fail") {
1032 strategy->set_variable_selection_strategy(
1033 DecisionStrategyProto::CHOOSE_MIN_DOMAIN_SIZE);
1034 }
else if (choose.id ==
"anti_first_fail") {
1035 strategy->set_variable_selection_strategy(
1036 DecisionStrategyProto::CHOOSE_MAX_DOMAIN_SIZE);
1037 }
else if (choose.id ==
"smallest") {
1038 strategy->set_variable_selection_strategy(
1039 DecisionStrategyProto::CHOOSE_LOWEST_MIN);
1040 }
else if (choose.id ==
"largest") {
1041 strategy->set_variable_selection_strategy(
1042 DecisionStrategyProto::CHOOSE_HIGHEST_MAX);
1044 SOLVER_LOG(logger,
"Unsupported variable selection strategy '",
1045 choose.id,
"', falling back to 'smallest'");
1046 strategy->set_variable_selection_strategy(
1047 DecisionStrategyProto::CHOOSE_LOWEST_MIN);
1050 const fz::Annotation& select = args[2];
1051 if (select.id ==
"indomain_min" || select.id ==
"indomain") {
1052 strategy->set_domain_reduction_strategy(
1053 DecisionStrategyProto::SELECT_MIN_VALUE);
1054 }
else if (select.id ==
"indomain_max") {
1055 strategy->set_domain_reduction_strategy(
1056 DecisionStrategyProto::SELECT_MAX_VALUE);
1057 }
else if (select.id ==
"indomain_split") {
1058 strategy->set_domain_reduction_strategy(
1059 DecisionStrategyProto::SELECT_LOWER_HALF);
1060 }
else if (select.id ==
"indomain_reverse_split") {
1061 strategy->set_domain_reduction_strategy(
1062 DecisionStrategyProto::SELECT_UPPER_HALF);
1063 }
else if (select.id ==
"indomain_median") {
1064 strategy->set_domain_reduction_strategy(
1065 DecisionStrategyProto::SELECT_MEDIAN_VALUE);
1067 SOLVER_LOG(logger,
"Unsupported value selection strategy '", select.id,
1068 "', falling back to 'indomain_min'");
1069 strategy->set_domain_reduction_strategy(
1070 DecisionStrategyProto::SELECT_MIN_VALUE);
1077 std::string SolutionString(
1078 const fz::SolutionOutputSpecs& output,
1079 const std::function<int64_t(fz::Variable*)>& value_func) {
1080 if (output.variable !=
nullptr) {
1081 const int64_t
value = value_func(output.variable);
1082 if (output.display_as_boolean) {
1083 return absl::StrCat(output.name,
" = ",
value == 1 ?
"true" :
"false",
1086 return absl::StrCat(output.name,
" = ",
value,
";");
1089 const int bound_size = output.bounds.size();
1090 std::string result =
1091 absl::StrCat(output.name,
" = array", bound_size,
"d(");
1092 for (
int i = 0; i < bound_size; ++i) {
1093 if (output.bounds[i].max_value >= output.bounds[i].min_value) {
1094 absl::StrAppend(&result, output.bounds[i].min_value,
"..",
1095 output.bounds[i].max_value,
", ");
1097 result.append(
"{},");
1101 for (
int i = 0; i < output.flat_variables.size(); ++i) {
1102 const int64_t
value = value_func(output.flat_variables[i]);
1103 if (output.display_as_boolean) {
1104 result.append(
value ?
"true" :
"false");
1106 absl::StrAppend(&result,
value);
1108 if (i != output.flat_variables.size() - 1) {
1109 result.append(
", ");
1112 result.append(
"]);");
1118 std::string SolutionString(
1119 const fz::Model&
model,
1120 const std::function<int64_t(fz::Variable*)>& value_func) {
1121 std::string solution_string;
1122 for (
const auto& output_spec :
model.output()) {
1123 solution_string.append(SolutionString(output_spec, value_func));
1124 solution_string.append(
"\n");
1126 return solution_string;
1129 void OutputFlatzincStats(
const CpSolverResponse&
response,
1130 SolverLogger* solution_logger) {
1132 "%%%mzn-stat: objective=",
response.objective_value());
1134 "%%%mzn-stat: objectiveBound=",
response.best_objective_bound());
1136 "%%%mzn-stat: boolVariables=",
response.num_booleans());
1138 "%%%mzn-stat: failures=",
response.num_conflicts());
1140 solution_logger,
"%%%mzn-stat: propagations=",
1149 const std::string& sat_params,
1152 CpModelProtoWithMapping m;
1153 m.proto.set_name(fz_model.
name());
1158 int num_variables = 0;
1160 if (!fz_var->active)
continue;
1161 CHECK(!fz_var->domain.is_float)
1162 <<
"CP-SAT does not support float variables";
1164 m.fz_var_to_index[fz_var] = num_variables++;
1165 IntegerVariableProto*
var = m.proto.add_variables();
1166 var->set_name(fz_var->name);
1167 if (fz_var->domain.is_interval) {
1168 if (fz_var->domain.values.empty()) {
1172 LOG_FIRST_N(WARNING, 1)
1173 <<
"Using flag --fz_int_max for unbounded integer variables.";
1174 LOG_FIRST_N(WARNING, 1)
1175 <<
" actual domain is [" << -absl::GetFlag(FLAGS_fz_int_max)
1176 <<
".." << absl::GetFlag(FLAGS_fz_int_max) <<
"]";
1177 var->add_domain(-absl::GetFlag(FLAGS_fz_int_max));
1178 var->add_domain(absl::GetFlag(FLAGS_fz_int_max));
1180 var->add_domain(fz_var->domain.values[0]);
1181 var->add_domain(fz_var->domain.values[1]);
1190 if (fz_ct ==
nullptr || !fz_ct->active)
continue;
1191 ConstraintProto*
ct = m.proto.add_constraints();
1192 ct->set_name(fz_ct->type);
1193 if (absl::EndsWith(fz_ct->type,
"_reif") ||
1194 absl::EndsWith(fz_ct->type,
"_imp") || fz_ct->type ==
"array_bool_or" ||
1195 fz_ct->type ==
"array_bool_and") {
1196 m.FillReifOrImpliedConstraint(*fz_ct,
ct);
1198 m.FillConstraint(*fz_ct,
ct);
1204 CpObjectiveProto* objective = m.proto.mutable_objective();
1205 objective->add_coeffs(1);
1207 objective->set_scaling_factor(-1);
1208 objective->add_vars(
1209 NegatedCpModelVariable(m.fz_var_to_index[fz_model.
objective()]));
1211 objective->add_vars(m.fz_var_to_index[fz_model.
objective()]);
1220 m.parameters.set_enumerate_all_solutions(
true);
1226 m.parameters.set_max_domain_size_when_encoding_eq_neq_constraints(32);
1229 int num_workers = 1;
1236 "Search for all solutions of a SAT problem in parallel is not "
1237 "supported. Switching back to sequential search.");
1245 "The number of search workers, is not specified. For better "
1246 "performances, please set the number of workers to 8, 16, or "
1247 "more depending on the number of cores of your computer.");
1253 if (num_workers == 1) {
1255 m.parameters.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
1256 m.parameters.set_interleave_search(
true);
1258 m.parameters.add_subsolvers(
"default_lp");
1259 m.parameters.add_subsolvers(
1260 m.proto.search_strategy().empty() ?
"quick_restart" :
"fixed");
1261 m.parameters.add_subsolvers(
"core_or_no_lp"),
1262 m.parameters.add_subsolvers(
"max_lp");
1265 m.parameters.add_subsolvers(
"default_lp");
1266 m.parameters.add_subsolvers(
1267 m.proto.search_strategy().empty() ?
"no_lp" :
"fixed");
1268 m.parameters.add_subsolvers(
"less_encoding");
1269 m.parameters.add_subsolvers(
"max_lp");
1270 m.parameters.add_subsolvers(
"quick_restart");
1273 m.parameters.set_search_branching(SatParameters::FIXED_SEARCH);
1274 m.parameters.set_keep_all_feasible_solutions_in_presolve(
true);
1276 }
else if (num_workers > 1 && num_workers < 8) {
1277 SOLVER_LOG(logger,
"Bumping number of workers from ", num_workers,
" to 8");
1280 m.parameters.set_num_search_workers(num_workers);
1289 sat::SatParameters flag_parameters;
1290 CHECK(google::protobuf::TextFormat::ParseFromString(sat_params,
1293 m.parameters.MergeFrom(flag_parameters);
1296 std::function<void(
const CpSolverResponse&)> solution_observer =
nullptr;
1298 solution_observer = [&fz_model, &m, &p,
1299 solution_logger](
const CpSolverResponse& r) {
1300 const std::string solution_string =
1302 return r.solution(m.fz_var_to_index.at(v));
1304 SOLVER_LOG(solution_logger, solution_string);
1305 if (p.display_statistics) {
1306 OutputFlatzincStats(r, solution_logger);
1314 if (solution_observer !=
nullptr) {
1318 sat_model.
GetOrCreate<SatParameters>()->set_log_to_stdout(
false);
1329 return response.solution(m.fz_var_to_index.at(v));
1339 const std::string solution_string =
1341 return response.solution(m.fz_var_to_index.at(v));
1343 SOLVER_LOG(solution_logger, solution_string);
1350 SOLVER_LOG(solution_logger,
"=====UNSATISFIABLE=====");
1353 VLOG(1) <<
"%% Error message = '" << error_message <<
"'";
1354 if (absl::StrContains(error_message,
"overflow")) {
1355 SOLVER_LOG(solution_logger,
"=====OVERFLOW=====");
1357 SOLVER_LOG(solution_logger,
"=====MODEL INVALID=====");
1363 OutputFlatzincStats(
response, solution_logger);
Domain AdditionWith(const Domain &domain) const
Returns {x ∈ Int64, ∃ a ∈ D, ∃ b ∈ domain, x = a + b}.
static Domain FromIntervals(absl::Span< const ClosedInterval > intervals)
Creates a domain from the union of an unsorted list of intervals.
static Domain FromValues(std::vector< int64_t > values)
Creates a domain from the union of an unsorted list of integer values.
bool LoggingIsEnabled() const
Variable * objective() const
const std::vector< Constraint * > & constraints() const
const std::vector< Annotation > & search_annotations() const
const std::string & name() const
const std::vector< Variable * > & variables() 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:
void Register(T *non_owned_class)
Register a non-owned class that will be "singleton" in the model.
T * GetOrCreate()
Returns an object of type T that is unique to this model (like a "local" singleton).
absl::flat_hash_map< std::tuple< int, int, int >, int > start_size_opt_tuple_to_interval
absl::flat_hash_map< int64_t, int > constant_value_to_index
absl::flat_hash_map< std::tuple< int, int64_t, int >, int > start_fixed_size_opt_tuple_to_interval
absl::flat_hash_map< fz::Variable *, int > fz_var_to_index
ABSL_FLAG(int64_t, fz_int_max, int64_t{1}<< 50, "Default max value for unbounded integer variables.")
SharedResponseManager * response
ReverseView< Container > reversed_view(const Container &c)
bool CheckSolution(const Model &model, const std::function< int64_t(Variable *)> &evaluator, SolverLogger *logger)
void FlattenAnnotations(const Annotation &ann, std::vector< Annotation > *out)
std::function< void(Model *)> NewFeasibleSolutionObserver(const std::function< void(const CpSolverResponse &response)> &observer)
Creates a solution observer with the model with model.Add(NewFeasibleSolutionObserver([](response){....
std::function< SatParameters(Model *)> NewSatParameters(const std::string ¶ms)
Creates parameters for the solver, which you can add to the model with.
std::vector< int64_t > AllValuesInDomain(const ProtoWithDomain &proto)
std::string ValidateCpModel(const CpModelProto &model, bool after_presolve)
void SolveFzWithCpModelProto(const fz::Model &fz_model, const fz::FlatzincSatParameters &p, const std::string &sat_params, SolverLogger *logger, SolverLogger *solution_logger)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
CpSolverResponse SolveCpModel(const CpModelProto &model_proto, Model *model)
Solves the given CpModelProto.
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Collection of objects used to extend the Constraint Solver library.
double max_time_in_seconds
bool display_all_solutions
#define SOLVER_LOG(logger,...)
#define VLOG(verboselevel)