22#include "absl/container/flat_hash_map.h"
23#include "absl/strings/match.h"
24#include "absl/strings/str_cat.h"
25#include "absl/strings/str_format.h"
26#include "absl/synchronization/mutex.h"
27#include "google/protobuf/text_format.h"
34#include "ortools/sat/cp_model.pb.h"
51 "Default max value for unbounded integer variables.");
66int TrueLiteral(
int var) {
return var; }
67int FalseLiteral(
int var) {
return -
var - 1; }
68int NegatedCpModelVariable(
int var) {
return -
var - 1; }
71struct CpModelProtoWithMapping {
73 int LookupConstant(int64_t
value);
77 int LookupVar(
const fz::Argument& argument);
78 LinearExpressionProto LookupExpr(
const fz::Argument& argument,
80 LinearExpressionProto LookupExprAt(
const fz::Argument& argument,
int pos,
82 std::vector<int> LookupVars(
const fz::Argument& argument);
83 std::vector<VarOrValue> LookupVarsOrValues(
const fz::Argument& argument);
88 std::vector<int> CreateIntervals(
const std::vector<int>& starts,
89 const std::vector<VarOrValue>& sizes);
96 int GetOrCreateOptionalInterval(
int start_var, VarOrValue size,
int opt_var);
100 ConstraintProto* AddEnforcedConstraint(
int literal);
103 void FillAMinusBInDomain(
const std::vector<int64_t>& domain,
104 const fz::Constraint& fz_ct, ConstraintProto*
ct);
105 void FillLinearConstraintWithGivenDomain(
const std::vector<int64_t>& domain,
106 const fz::Constraint& fz_ct,
107 ConstraintProto*
ct);
108 void FillConstraint(
const fz::Constraint& fz_ct, ConstraintProto*
ct);
109 void FillReifOrImpliedConstraint(
const fz::Constraint& fz_ct,
110 ConstraintProto*
ct);
114 void TranslateSearchAnnotations(
115 const std::vector<fz::Annotation>& search_annotations);
124 absl::flat_hash_map<std::tuple<int, int, int>,
int>
126 absl::flat_hash_map<std::tuple<int, int64_t, int>,
int>
130int CpModelProtoWithMapping::LookupConstant(int64_t
value) {
137 IntegerVariableProto* var_proto =
proto.add_variables();
138 var_proto->add_domain(
value);
139 var_proto->add_domain(
value);
144int CpModelProtoWithMapping::LookupVar(
const fz::Argument& argument) {
145 if (argument.HasOneValue())
return LookupConstant(argument.Value());
150LinearExpressionProto CpModelProtoWithMapping::LookupExpr(
151 const fz::Argument& argument,
bool negate) {
152 LinearExpressionProto expr;
153 if (argument.HasOneValue()) {
154 const int64_t
value = argument.Value();
157 expr.add_vars(LookupVar(argument));
158 expr.add_coeffs(negate ? -1 : 1);
163LinearExpressionProto CpModelProtoWithMapping::LookupExprAt(
164 const fz::Argument& argument,
int pos,
bool negate) {
165 LinearExpressionProto expr;
166 if (argument.HasOneValueAt(pos)) {
167 const int64_t
value = argument.ValueAt(pos);
171 expr.add_coeffs(negate ? -1 : 1);
176std::vector<int> CpModelProtoWithMapping::LookupVars(
177 const fz::Argument& argument) {
178 std::vector<int> result;
181 for (int64_t
value : argument.values) {
182 result.push_back(LookupConstant(
value));
185 result.push_back(LookupConstant(argument.Value()));
188 for (fz::Variable*
var : argument.variables) {
196std::vector<VarOrValue> CpModelProtoWithMapping::LookupVarsOrValues(
197 const fz::Argument& argument) {
198 std::vector<VarOrValue> result;
199 const int no_var = kNoVar;
202 for (int64_t
value : argument.values) {
203 result.push_back({no_var,
value});
206 result.push_back({no_var, argument.Value()});
209 for (fz::Variable*
var : argument.variables) {
211 if (
var->domain.HasOneValue()) {
212 result.push_back({no_var,
var->domain.Value()});
221ConstraintProto* CpModelProtoWithMapping::AddEnforcedConstraint(
int literal) {
222 ConstraintProto* result =
proto.add_constraints();
224 result->add_enforcement_literal(
literal);
229int CpModelProtoWithMapping::GetOrCreateOptionalInterval(
int start_var,
232 const int interval_index =
proto.constraints_size();
233 if (size.var == kNoVar) {
234 const std::tuple<int, int64_t, int> key =
235 std::make_tuple(start_var, size.value, opt_var);
236 const auto [it, inserted] =
242 auto*
interval = AddEnforcedConstraint(opt_var)->mutable_interval();
243 interval->mutable_start()->add_vars(start_var);
244 interval->mutable_start()->add_coeffs(1);
245 interval->mutable_size()->set_offset(size.value);
246 interval->mutable_end()->add_vars(start_var);
247 interval->mutable_end()->add_coeffs(1);
248 interval->mutable_end()->set_offset(size.value);
250 return interval_index;
252 const std::tuple<int, int, int> key =
253 std::make_tuple(start_var, size.var, opt_var);
254 const auto [it, inserted] =
260 const int end_var =
proto.variables_size();
264 proto.add_variables());
267 auto*
interval = AddEnforcedConstraint(opt_var)->mutable_interval();
268 interval->mutable_start()->add_vars(start_var);
269 interval->mutable_start()->add_coeffs(1);
270 interval->mutable_size()->add_vars(size.var);
271 interval->mutable_size()->add_coeffs(1);
272 interval->mutable_end()->add_vars(end_var);
273 interval->mutable_end()->add_coeffs(1);
277 auto* lin = AddEnforcedConstraint(opt_var)->mutable_linear();
278 lin->add_vars(start_var);
280 lin->add_vars(size.var);
282 lin->add_vars(end_var);
287 return interval_index;
291std::vector<int> CpModelProtoWithMapping::CreateIntervals(
292 const std::vector<int>& starts,
const std::vector<VarOrValue>& sizes) {
293 std::vector<int> intervals;
294 for (
int i = 0; i < starts.size(); ++i) {
296 GetOrCreateOptionalInterval(starts[i], sizes[i], kNoVar));
301void CpModelProtoWithMapping::FillAMinusBInDomain(
302 const std::vector<int64_t>& domain,
const fz::Constraint& fz_ct,
303 ConstraintProto*
ct) {
304 auto* arg =
ct->mutable_linear();
306 const int64_t
value = fz_ct.arguments[1].Value();
307 const int var_a = LookupVar(fz_ct.arguments[0]);
308 for (
const int64_t domain_bound : domain) {
311 arg->add_domain(domain_bound);
313 arg->add_domain(domain_bound +
value);
316 arg->add_vars(var_a);
319 const int64_t
value = fz_ct.arguments[0].Value();
320 const int var_b = LookupVar(fz_ct.arguments[1]);
327 arg->add_domain(
value - domain_bound);
330 arg->add_vars(var_b);
333 for (
const int64_t domain_bound : domain) arg->add_domain(domain_bound);
334 arg->add_vars(LookupVar(fz_ct.arguments[0]));
336 arg->add_vars(LookupVar(fz_ct.arguments[1]));
341void CpModelProtoWithMapping::FillLinearConstraintWithGivenDomain(
342 const std::vector<int64_t>& domain,
const fz::Constraint& fz_ct,
343 ConstraintProto*
ct) {
344 auto* arg =
ct->mutable_linear();
345 for (
const int64_t domain_bound : domain) arg->add_domain(domain_bound);
346 std::vector<int> vars = LookupVars(fz_ct.arguments[1]);
347 for (
int i = 0; i < vars.size(); ++i) {
348 arg->add_vars(vars[i]);
349 arg->add_coeffs(fz_ct.arguments[0].values[i]);
353void CpModelProtoWithMapping::FillConstraint(
const fz::Constraint& fz_ct,
354 ConstraintProto*
ct) {
355 if (fz_ct.type ==
"false_constraint") {
357 ct->mutable_bool_or();
358 }
else if (fz_ct.type ==
"bool_clause") {
359 auto* arg =
ct->mutable_bool_or();
360 for (
const int var : LookupVars(fz_ct.arguments[0])) {
361 arg->add_literals(TrueLiteral(
var));
363 for (
const int var : LookupVars(fz_ct.arguments[1])) {
364 arg->add_literals(FalseLiteral(
var));
366 }
else if (fz_ct.type ==
"bool_xor") {
369 const int a = LookupVar(fz_ct.arguments[0]);
370 const int b = LookupVar(fz_ct.arguments[1]);
371 const int x = LookupVar(fz_ct.arguments[2]);
375 auto*
const refute =
ct->mutable_linear();
377 refute->add_coeffs(1);
379 refute->add_coeffs(-1);
380 refute->add_domain(0);
381 refute->add_domain(0);
384 auto* enforce = AddEnforcedConstraint(x)->mutable_linear();
385 enforce->add_vars(
a);
386 enforce->add_coeffs(1);
387 enforce->add_vars(
b);
388 enforce->add_coeffs(1);
389 enforce->add_domain(1);
390 enforce->add_domain(1);
391 }
else if (fz_ct.type ==
"array_bool_or") {
392 auto* arg =
ct->mutable_bool_or();
393 for (
const int var : LookupVars(fz_ct.arguments[0])) {
394 arg->add_literals(TrueLiteral(
var));
396 }
else if (fz_ct.type ==
"array_bool_or_negated") {
397 auto* arg =
ct->mutable_bool_and();
398 for (
const int var : LookupVars(fz_ct.arguments[0])) {
399 arg->add_literals(FalseLiteral(
var));
401 }
else if (fz_ct.type ==
"array_bool_and") {
402 auto* arg =
ct->mutable_bool_and();
403 for (
const int var : LookupVars(fz_ct.arguments[0])) {
404 arg->add_literals(TrueLiteral(
var));
406 }
else if (fz_ct.type ==
"array_bool_and_negated") {
407 auto* arg =
ct->mutable_bool_or();
408 for (
const int var : LookupVars(fz_ct.arguments[0])) {
409 arg->add_literals(FalseLiteral(
var));
411 }
else if (fz_ct.type ==
"array_bool_xor") {
412 auto* arg =
ct->mutable_bool_xor();
413 for (
const int var : LookupVars(fz_ct.arguments[0])) {
414 arg->add_literals(TrueLiteral(
var));
416 }
else if (fz_ct.type ==
"bool_le" || fz_ct.type ==
"int_le") {
418 }
else if (fz_ct.type ==
"bool_ge" || fz_ct.type ==
"int_ge") {
420 }
else if (fz_ct.type ==
"bool_lt" || fz_ct.type ==
"int_lt") {
422 }
else if (fz_ct.type ==
"bool_gt" || fz_ct.type ==
"int_gt") {
424 }
else if (fz_ct.type ==
"bool_eq" || fz_ct.type ==
"int_eq" ||
425 fz_ct.type ==
"bool2int") {
426 FillAMinusBInDomain({0, 0}, fz_ct,
ct);
427 }
else if (fz_ct.type ==
"bool_ne" || fz_ct.type ==
"bool_not") {
428 auto* arg =
ct->mutable_linear();
429 arg->add_vars(LookupVar(fz_ct.arguments[0]));
431 arg->add_vars(LookupVar(fz_ct.arguments[1]));
435 }
else if (fz_ct.type ==
"int_ne") {
439 }
else if (fz_ct.type ==
"int_lin_eq") {
440 const int64_t rhs = fz_ct.arguments[2].values[0];
441 FillLinearConstraintWithGivenDomain({rhs, rhs}, fz_ct,
ct);
442 }
else if (fz_ct.type ==
"bool_lin_eq") {
443 auto* arg =
ct->mutable_linear();
444 const std::vector<int> vars = LookupVars(fz_ct.arguments[1]);
445 for (
int i = 0; i < vars.size(); ++i) {
446 arg->add_vars(vars[i]);
447 arg->add_coeffs(fz_ct.arguments[0].values[i]);
449 if (fz_ct.arguments[2].IsVariable()) {
450 arg->add_vars(LookupVar(fz_ct.arguments[2]));
455 const int64_t v = fz_ct.arguments[2].Value();
459 }
else if (fz_ct.type ==
"int_lin_le" || fz_ct.type ==
"bool_lin_le") {
460 const int64_t rhs = fz_ct.arguments[2].values[0];
461 FillLinearConstraintWithGivenDomain(
463 }
else if (fz_ct.type ==
"int_lin_lt") {
464 const int64_t rhs = fz_ct.arguments[2].values[0];
465 FillLinearConstraintWithGivenDomain(
467 }
else if (fz_ct.type ==
"int_lin_ge") {
468 const int64_t rhs = fz_ct.arguments[2].values[0];
469 FillLinearConstraintWithGivenDomain(
471 }
else if (fz_ct.type ==
"int_lin_gt") {
472 const int64_t rhs = fz_ct.arguments[2].values[0];
473 FillLinearConstraintWithGivenDomain(
475 }
else if (fz_ct.type ==
"int_lin_ne") {
476 const int64_t rhs = fz_ct.arguments[2].values[0];
477 FillLinearConstraintWithGivenDomain(
481 }
else if (fz_ct.type ==
"set_in") {
482 auto* arg =
ct->mutable_linear();
483 arg->add_vars(LookupVar(fz_ct.arguments[0]));
487 fz_ct.arguments[1].values.begin(),
488 fz_ct.arguments[1].values.end()}),
492 Domain(fz_ct.arguments[1].values[0], fz_ct.arguments[1].values[1]),
497 }
else if (fz_ct.type ==
"set_in_negated") {
498 auto* arg =
ct->mutable_linear();
499 arg->add_vars(LookupVar(fz_ct.arguments[0]));
504 std::vector<int64_t>{fz_ct.arguments[1].values.begin(),
505 fz_ct.arguments[1].values.end()})
510 Domain(fz_ct.arguments[1].values[0], fz_ct.arguments[1].values[1])
516 }
else if (fz_ct.type ==
"int_min") {
517 auto* arg =
ct->mutable_lin_max();
518 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0],
true);
519 *arg->add_exprs() = LookupExpr(fz_ct.arguments[1],
true);
520 *arg->mutable_target() = LookupExpr(fz_ct.arguments[2],
true);
521 }
else if (fz_ct.type ==
"array_int_minimum" || fz_ct.type ==
"minimum_int") {
522 auto* arg =
ct->mutable_lin_max();
523 *arg->mutable_target() = LookupExpr(fz_ct.arguments[0],
true);
524 for (
int i = 0; i < fz_ct.arguments[1].Size(); ++i) {
525 *arg->add_exprs() = LookupExprAt(fz_ct.arguments[1], i,
true);
527 }
else if (fz_ct.type ==
"int_max") {
528 auto* arg =
ct->mutable_lin_max();
529 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
530 *arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
531 *arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
532 }
else if (fz_ct.type ==
"array_int_maximum" || fz_ct.type ==
"maximum_int") {
533 auto* arg =
ct->mutable_lin_max();
534 *arg->mutable_target() = LookupExpr(fz_ct.arguments[0]);
535 for (
int i = 0; i < fz_ct.arguments[1].Size(); ++i) {
536 *arg->add_exprs() = LookupExprAt(fz_ct.arguments[1], i);
538 }
else if (fz_ct.type ==
"int_times") {
539 auto* arg =
ct->mutable_int_prod();
540 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
541 *arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
542 *arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
543 }
else if (fz_ct.type ==
"int_abs") {
544 auto* arg =
ct->mutable_lin_max();
545 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
546 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0],
true);
547 *arg->mutable_target() = LookupExpr(fz_ct.arguments[1]);
548 }
else if (fz_ct.type ==
"int_plus") {
549 auto* arg =
ct->mutable_linear();
551 arg->add_vars(LookupVar(fz_ct.arguments[0]));
553 arg->add_vars(LookupVar(fz_ct.arguments[1]));
555 arg->add_vars(LookupVar(fz_ct.arguments[2]));
557 }
else if (fz_ct.type ==
"int_div") {
558 auto* arg =
ct->mutable_int_div();
559 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
560 *arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
561 *arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
562 }
else if (fz_ct.type ==
"int_mod") {
563 auto* arg =
ct->mutable_int_mod();
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 ==
"array_int_element" ||
568 fz_ct.type ==
"array_bool_element" ||
569 fz_ct.type ==
"array_var_int_element" ||
570 fz_ct.type ==
"array_var_bool_element" ||
571 fz_ct.type ==
"array_int_element_nonshifted") {
574 auto* arg =
ct->mutable_element();
575 arg->set_index(LookupVar(fz_ct.arguments[0]));
576 arg->set_target(LookupVar(fz_ct.arguments[2]));
578 if (!absl::EndsWith(fz_ct.type,
"_nonshifted")) {
582 arg->add_vars(LookupConstant(0));
584 for (
const int var : LookupVars(fz_ct.arguments[1])) arg->add_vars(
var);
588 CHECK(!absl::EndsWith(fz_ct.type,
"_nonshifted"));
589 auto* arg =
ct->mutable_table();
593 for (
const int var : LookupVars(fz_ct.arguments[0])) arg->add_vars(
var);
594 arg->add_vars(LookupVar(fz_ct.arguments[2]));
596 const std::vector<int64_t>& values = fz_ct.arguments[1].values;
597 const int64_t coeff1 = fz_ct.arguments[3].values[0];
598 const int64_t coeff2 = fz_ct.arguments[3].values[1];
599 const int64_t offset = fz_ct.arguments[4].values[0] - 1;
602 for (
const int64_t
b :
604 const int index = coeff1 *
a + coeff2 *
b + offset;
609 arg->add_values(values[
index]);
613 }
else if (fz_ct.type ==
"ortools_table_int") {
614 auto* arg =
ct->mutable_table();
615 for (
const int var : LookupVars(fz_ct.arguments[0])) arg->add_vars(
var);
616 for (
const int64_t
value : fz_ct.arguments[1].values)
617 arg->add_values(
value);
618 }
else if (fz_ct.type ==
"ortools_regular") {
619 auto* arg =
ct->mutable_automaton();
620 for (
const int var : LookupVars(fz_ct.arguments[0])) arg->add_vars(
var);
623 const int num_states = fz_ct.arguments[1].Value();
624 const int num_values = fz_ct.arguments[2].Value();
625 for (
int i = 1; i <= num_states; ++i) {
626 for (
int j = 1; j <= num_values; ++j) {
627 CHECK_LT(count, fz_ct.arguments[3].values.size());
628 const int next = fz_ct.arguments[3].values[count++];
629 if (
next == 0)
continue;
630 arg->add_transition_tail(i);
631 arg->add_transition_label(j);
632 arg->add_transition_head(
next);
636 arg->set_starting_state(fz_ct.arguments[4].Value());
637 switch (fz_ct.arguments[5].type) {
639 arg->add_final_states(fz_ct.arguments[5].values[0]);
643 for (
int v = fz_ct.arguments[5].values[0];
644 v <= fz_ct.arguments[5].values[1]; ++v) {
645 arg->add_final_states(v);
650 for (
const int v : fz_ct.arguments[5].values) {
651 arg->add_final_states(v);
656 LOG(
FATAL) <<
"Wrong constraint " << fz_ct.DebugString();
659 }
else if (fz_ct.type ==
"fzn_all_different_int") {
660 auto* arg =
ct->mutable_all_diff();
661 for (
int i = 0; i < fz_ct.arguments[0].Size(); ++i) {
662 *arg->add_exprs() = LookupExprAt(fz_ct.arguments[0], i);
664 }
else if (fz_ct.type ==
"ortools_circuit" ||
665 fz_ct.type ==
"ortools_subcircuit") {
666 const int64_t min_index = fz_ct.arguments[1].Value();
667 const int size =
std::max(fz_ct.arguments[0].values.size(),
668 fz_ct.arguments[0].variables.size());
670 const int64_t max_index = min_index + size - 1;
672 auto* circuit_arg =
ct->mutable_circuit();
676 int64_t
index = min_index;
677 const bool is_circuit = (fz_ct.type ==
"ortools_circuit");
678 for (
const int var : LookupVars(fz_ct.arguments[0])) {
682 domain = domain.IntersectionWith(Domain(min_index, max_index));
691 for (
const ClosedInterval
interval : domain.intervals()) {
696 auto* new_var =
proto.add_variables();
697 new_var->add_domain(0);
698 new_var->add_domain(1);
702 circuit_arg->add_tails(
index);
703 circuit_arg->add_heads(
value);
704 circuit_arg->add_literals(
literal);
708 auto* lin = AddEnforcedConstraint(
literal)->mutable_linear();
711 lin->add_domain(
value);
712 lin->add_domain(
value);
722 lin->add_domain(
value - 1);
723 lin->add_domain(
value + 1);
731 }
else if (fz_ct.type ==
"ortools_inverse") {
732 auto* arg =
ct->mutable_inverse();
734 const auto direct_variables = LookupVars(fz_ct.arguments[0]);
735 const auto inverse_variables = LookupVars(fz_ct.arguments[1]);
736 const int base_direct = fz_ct.arguments[2].Value();
737 const int base_inverse = fz_ct.arguments[3].Value();
739 CHECK_EQ(direct_variables.size(), inverse_variables.size());
740 const int num_variables = direct_variables.size();
741 const int end_direct = base_direct + num_variables;
742 const int end_inverse = base_inverse + num_variables;
764 const int arity =
std::max(base_inverse, base_direct) + num_variables;
765 for (
int i = 0; i < arity; ++i) {
767 if (i < base_direct) {
768 if (i < base_inverse) {
769 arg->add_f_direct(LookupConstant(i));
770 }
else if (i >= base_inverse) {
771 arg->add_f_direct(LookupConstant(i + num_variables));
773 }
else if (i >= base_direct && i < end_direct) {
774 arg->add_f_direct(direct_variables[i - base_direct]);
776 arg->add_f_direct(LookupConstant(i - num_variables));
780 if (i < base_inverse) {
781 if (i < base_direct) {
782 arg->add_f_inverse(LookupConstant(i));
783 }
else if (i >= base_direct) {
784 arg->add_f_inverse(LookupConstant(i + num_variables));
786 }
else if (i >= base_inverse && i < end_inverse) {
787 arg->add_f_inverse(inverse_variables[i - base_inverse]);
789 arg->add_f_inverse(LookupConstant(i - num_variables));
792 }
else if (fz_ct.type ==
"fzn_cumulative") {
793 const std::vector<int> starts = LookupVars(fz_ct.arguments[0]);
794 const std::vector<VarOrValue> sizes =
795 LookupVarsOrValues(fz_ct.arguments[1]);
796 const std::vector<VarOrValue> demands =
797 LookupVarsOrValues(fz_ct.arguments[2]);
799 auto* arg =
ct->mutable_cumulative();
800 if (fz_ct.arguments[3].HasOneValue()) {
801 arg->mutable_capacity()->set_offset(fz_ct.arguments[3].Value());
803 arg->mutable_capacity()->add_vars(LookupVar(fz_ct.arguments[3]));
804 arg->mutable_capacity()->add_coeffs(1);
806 for (
int i = 0; i < starts.size(); ++i) {
809 if (demands[i].
var != kNoVar &&
810 proto.variables(demands[i].var).domain().size() == 2 &&
811 proto.variables(demands[i].var).domain(0) == 0 &&
812 proto.variables(demands[i].var).domain(1) == 1 &&
813 fz_ct.arguments[3].HasOneValue() && fz_ct.arguments[3].Value() == 1) {
815 GetOrCreateOptionalInterval(starts[i], sizes[i], demands[i].
var));
816 arg->add_demands()->set_offset(1);
819 GetOrCreateOptionalInterval(starts[i], sizes[i], kNoVar));
820 LinearExpressionProto*
demand = arg->add_demands();
821 if (demands[i].
var == kNoVar) {
829 }
else if (fz_ct.type ==
"fzn_diffn" || fz_ct.type ==
"fzn_diffn_nonstrict") {
830 const std::vector<int> x = LookupVars(fz_ct.arguments[0]);
831 const std::vector<int> y = LookupVars(fz_ct.arguments[1]);
832 const std::vector<VarOrValue> dx = LookupVarsOrValues(fz_ct.arguments[2]);
833 const std::vector<VarOrValue> dy = LookupVarsOrValues(fz_ct.arguments[3]);
834 const std::vector<int> x_intervals = CreateIntervals(x, dx);
835 const std::vector<int> y_intervals = CreateIntervals(y, dy);
836 auto* arg =
ct->mutable_no_overlap_2d();
837 for (
int i = 0; i < x.size(); ++i) {
838 arg->add_x_intervals(x_intervals[i]);
839 arg->add_y_intervals(y_intervals[i]);
841 arg->set_boxes_with_null_area_can_overlap(fz_ct.type ==
842 "fzn_diffn_nonstrict");
843 }
else if (fz_ct.type ==
"ortools_network_flow" ||
844 fz_ct.type ==
"ortools_network_flow_cost") {
847 const bool has_cost = fz_ct.type ==
"ortools_network_flow_cost";
848 const std::vector<int> flow = LookupVars(fz_ct.arguments[has_cost ? 3 : 2]);
851 const int num_nodes = fz_ct.arguments[1].values.size();
852 std::vector<std::vector<int>> flows_per_node(num_nodes);
853 std::vector<std::vector<int>> coeffs_per_node(num_nodes);
854 const int num_arcs = fz_ct.arguments[0].values.size() / 2;
855 for (
int arc = 0;
arc < num_arcs;
arc++) {
856 const int tail = fz_ct.arguments[0].values[2 *
arc] - 1;
857 const int head = fz_ct.arguments[0].values[2 *
arc + 1] - 1;
860 flows_per_node[
tail].push_back(flow[
arc]);
861 coeffs_per_node[
tail].push_back(1);
862 flows_per_node[
head].push_back(flow[
arc]);
863 coeffs_per_node[
head].push_back(-1);
865 for (
int node = 0; node < num_nodes; node++) {
866 auto* arg =
proto.add_constraints()->mutable_linear();
867 arg->add_domain(fz_ct.arguments[1].values[node]);
868 arg->add_domain(fz_ct.arguments[1].values[node]);
869 for (
int i = 0; i < flows_per_node[node].size(); ++i) {
870 arg->add_vars(flows_per_node[node][i]);
871 arg->add_coeffs(coeffs_per_node[node][i]);
876 auto* arg =
proto.add_constraints()->mutable_linear();
879 for (
int arc = 0;
arc < num_arcs;
arc++) {
880 const int64_t
weight = fz_ct.arguments[2].values[
arc];
882 arg->add_vars(flow[
arc]);
886 arg->add_vars(LookupVar(fz_ct.arguments[4]));
890 LOG(
FATAL) <<
" Not supported " << fz_ct.type;
894void CpModelProtoWithMapping::FillReifOrImpliedConstraint(
895 const fz::Constraint& fz_ct, ConstraintProto*
ct) {
897 std::string simplified_type;
898 if (absl::EndsWith(fz_ct.type,
"_reif")) {
900 simplified_type = fz_ct.type.substr(0, fz_ct.type.size() - 5);
901 }
else if (absl::EndsWith(fz_ct.type,
"_imp")) {
903 simplified_type = fz_ct.type.substr(0, fz_ct.type.size() - 4);
906 simplified_type = fz_ct.type;
910 fz::Constraint copy = fz_ct;
911 copy.type = simplified_type;
914 FillConstraint(copy,
ct);
917 std::string negated_type;
920 if (simplified_type ==
"array_bool_or") {
921 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[1])));
922 negated_type =
"array_bool_or_negated";
923 }
else if (simplified_type ==
"array_bool_and") {
924 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[1])));
925 negated_type =
"array_bool_and_negated";
926 }
else if (simplified_type ==
"set_in") {
927 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
928 negated_type =
"set_in_negated";
929 }
else if (simplified_type ==
"bool_eq" || simplified_type ==
"int_eq") {
930 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
931 negated_type =
"int_ne";
932 }
else if (simplified_type ==
"bool_ne" || simplified_type ==
"int_ne") {
933 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
934 negated_type =
"int_eq";
935 }
else if (simplified_type ==
"bool_le" || simplified_type ==
"int_le") {
936 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
937 negated_type =
"int_gt";
938 }
else if (simplified_type ==
"bool_lt" || simplified_type ==
"int_lt") {
939 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
940 negated_type =
"int_ge";
941 }
else if (simplified_type ==
"bool_ge" || simplified_type ==
"int_ge") {
942 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
943 negated_type =
"int_lt";
944 }
else if (simplified_type ==
"bool_gt" || simplified_type ==
"int_gt") {
945 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
946 negated_type =
"int_le";
947 }
else if (simplified_type ==
"int_lin_eq") {
948 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
949 negated_type =
"int_lin_ne";
950 }
else if (simplified_type ==
"int_lin_ne") {
951 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
952 negated_type =
"int_lin_eq";
953 }
else if (simplified_type ==
"int_lin_le") {
954 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
955 negated_type =
"int_lin_gt";
956 }
else if (simplified_type ==
"int_lin_ge") {
957 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
958 negated_type =
"int_lin_lt";
959 }
else if (simplified_type ==
"int_lin_lt") {
960 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
961 negated_type =
"int_lin_ge";
962 }
else if (simplified_type ==
"int_lin_gt") {
963 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
964 negated_type =
"int_lin_le";
966 LOG(
FATAL) <<
"Unsupported " << simplified_type;
970 if (absl::EndsWith(fz_ct.type,
"_imp"))
return;
974 ConstraintProto* negated_ct =
proto.add_constraints();
975 negated_ct->set_name(fz_ct.type +
" (negated)");
976 negated_ct->add_enforcement_literal(
978 copy.type = negated_type;
979 FillConstraint(copy, negated_ct);
982void CpModelProtoWithMapping::TranslateSearchAnnotations(
983 const std::vector<fz::Annotation>& search_annotations) {
984 std::vector<fz::Annotation> flat_annotations;
985 for (
const fz::Annotation& annotation : search_annotations) {
989 for (
const fz::Annotation& annotation : flat_annotations) {
990 if (annotation.IsFunctionCallWithIdentifier(
"int_search") ||
991 annotation.IsFunctionCallWithIdentifier(
"bool_search")) {
992 const std::vector<fz::Annotation>& args = annotation.annotations;
993 std::vector<fz::Variable*> vars;
994 args[0].AppendAllVariables(&vars);
996 DecisionStrategyProto* strategy =
proto.add_search_strategy();
997 for (fz::Variable* v : vars) {
1001 const fz::Annotation& choose = args[1];
1002 if (choose.id ==
"input_order") {
1003 strategy->set_variable_selection_strategy(
1004 DecisionStrategyProto::CHOOSE_FIRST);
1005 }
else if (choose.id ==
"first_fail") {
1006 strategy->set_variable_selection_strategy(
1007 DecisionStrategyProto::CHOOSE_MIN_DOMAIN_SIZE);
1008 }
else if (choose.id ==
"anti_first_fail") {
1009 strategy->set_variable_selection_strategy(
1010 DecisionStrategyProto::CHOOSE_MAX_DOMAIN_SIZE);
1011 }
else if (choose.id ==
"smallest") {
1012 strategy->set_variable_selection_strategy(
1013 DecisionStrategyProto::CHOOSE_LOWEST_MIN);
1014 }
else if (choose.id ==
"largest") {
1015 strategy->set_variable_selection_strategy(
1016 DecisionStrategyProto::CHOOSE_HIGHEST_MAX);
1018 LOG(
FATAL) <<
"Unsupported order: " << choose.id;
1021 const fz::Annotation& select = args[2];
1022 if (select.id ==
"indomain_min" || select.id ==
"indomain") {
1023 strategy->set_domain_reduction_strategy(
1024 DecisionStrategyProto::SELECT_MIN_VALUE);
1025 }
else if (select.id ==
"indomain_max") {
1026 strategy->set_domain_reduction_strategy(
1027 DecisionStrategyProto::SELECT_MAX_VALUE);
1028 }
else if (select.id ==
"indomain_split") {
1029 strategy->set_domain_reduction_strategy(
1030 DecisionStrategyProto::SELECT_LOWER_HALF);
1031 }
else if (select.id ==
"indomain_reverse_split") {
1032 strategy->set_domain_reduction_strategy(
1033 DecisionStrategyProto::SELECT_UPPER_HALF);
1034 }
else if (select.id ==
"indomain_median") {
1035 strategy->set_domain_reduction_strategy(
1036 DecisionStrategyProto::SELECT_MEDIAN_VALUE);
1038 LOG(
FATAL) <<
"Unsupported select: " << select.id;
1045std::string SolutionString(
1046 const fz::SolutionOutputSpecs& output,
1047 const std::function<int64_t(fz::Variable*)>& value_func) {
1048 if (output.variable !=
nullptr) {
1049 const int64_t
value = value_func(output.variable);
1050 if (output.display_as_boolean) {
1051 return absl::StrCat(output.name,
" = ",
value == 1 ?
"true" :
"false",
1054 return absl::StrCat(output.name,
" = ",
value,
";");
1057 const int bound_size = output.bounds.size();
1058 std::string result =
1059 absl::StrCat(output.name,
" = array", bound_size,
"d(");
1060 for (
int i = 0; i < bound_size; ++i) {
1061 if (output.bounds[i].max_value >= output.bounds[i].min_value) {
1062 absl::StrAppend(&result, output.bounds[i].min_value,
"..",
1063 output.bounds[i].max_value,
", ");
1065 result.append(
"{},");
1069 for (
int i = 0; i < output.flat_variables.size(); ++i) {
1070 const int64_t
value = value_func(output.flat_variables[i]);
1071 if (output.display_as_boolean) {
1072 result.append(
value ?
"true" :
"false");
1074 absl::StrAppend(&result,
value);
1076 if (i != output.flat_variables.size() - 1) {
1077 result.append(
", ");
1080 result.append(
"]);");
1086std::string SolutionString(
1087 const fz::Model&
model,
1088 const std::function<int64_t(fz::Variable*)>& value_func) {
1089 std::string solution_string;
1090 for (
const auto& output_spec :
model.output()) {
1091 solution_string.append(SolutionString(output_spec, value_func));
1092 solution_string.append(
"\n");
1094 return solution_string;
1097void OutputFlatzincStats(
const CpSolverResponse&
response,
1098 SolverLogger* solution_logger) {
1100 "%%%mzn-stat: objective=",
response.objective_value());
1102 "%%%mzn-stat: objectiveBound=",
response.best_objective_bound());
1104 "%%%mzn-stat: boolVariables=",
response.num_booleans());
1106 "%%%mzn-stat: failures=",
response.num_conflicts());
1108 solution_logger,
"%%%mzn-stat: propagations=",
1117 const std::string& sat_params,
1120 CpModelProtoWithMapping m;
1121 m.proto.set_name(fz_model.
name());
1126 int num_variables = 0;
1128 if (!fz_var->active)
continue;
1129 CHECK(!fz_var->domain.is_float)
1130 <<
"CP-SAT does not support float variables";
1132 m.fz_var_to_index[fz_var] = num_variables++;
1133 IntegerVariableProto*
var = m.proto.add_variables();
1134 var->set_name(fz_var->name);
1135 if (fz_var->domain.is_interval) {
1136 if (fz_var->domain.values.empty()) {
1141 <<
"Using flag --fz_int_max for unbounded integer variables.";
1143 <<
" actual domain is [" << -absl::GetFlag(FLAGS_fz_int_max)
1144 <<
".." << absl::GetFlag(FLAGS_fz_int_max) <<
"]";
1145 var->add_domain(-absl::GetFlag(FLAGS_fz_int_max));
1146 var->add_domain(absl::GetFlag(FLAGS_fz_int_max));
1148 var->add_domain(fz_var->domain.values[0]);
1149 var->add_domain(fz_var->domain.values[1]);
1158 if (fz_ct ==
nullptr || !fz_ct->active)
continue;
1159 ConstraintProto*
ct = m.proto.add_constraints();
1160 ct->set_name(fz_ct->type);
1161 if (absl::EndsWith(fz_ct->type,
"_reif") ||
1162 absl::EndsWith(fz_ct->type,
"_imp") || fz_ct->type ==
"array_bool_or" ||
1163 fz_ct->type ==
"array_bool_and") {
1164 m.FillReifOrImpliedConstraint(*fz_ct,
ct);
1166 m.FillConstraint(*fz_ct,
ct);
1172 CpObjectiveProto* objective = m.proto.mutable_objective();
1173 objective->add_coeffs(1);
1175 objective->set_scaling_factor(-1);
1176 objective->add_vars(
1177 NegatedCpModelVariable(m.fz_var_to_index[fz_model.
objective()]));
1179 objective->add_vars(m.fz_var_to_index[fz_model.
objective()]);
1188 m.parameters.set_enumerate_all_solutions(
true);
1194 m.parameters.set_max_domain_size_when_encoding_eq_neq_constraints(32);
1197 int num_workers = 1;
1204 "Search for all solutions of a SAT problem in parallel is not "
1205 "supported. Switching back to sequential search.");
1213 "The number of search workers, is not specified. For better "
1214 "performances, please set the number of workers to 8, 16, or "
1215 "more depending on the number of cores of your computer.");
1219 m.parameters.set_num_search_workers(num_workers);
1222 if (num_workers == 1) {
1224 m.parameters.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
1225 m.parameters.set_interleave_search(
true);
1226 m.parameters.set_reduce_memory_usage_in_interleave_mode(
true);
1228 m.parameters.set_search_branching(SatParameters::FIXED_SEARCH);
1229 m.parameters.set_keep_all_feasible_solutions_in_presolve(
true);
1240 sat::SatParameters flag_parameters;
1241 CHECK(google::protobuf::TextFormat::ParseFromString(sat_params,
1244 m.parameters.MergeFrom(flag_parameters);
1247 std::function<void(
const CpSolverResponse&)> solution_observer =
nullptr;
1249 solution_observer = [&fz_model, &m, &p,
1250 solution_logger](
const CpSolverResponse& r) {
1251 const std::string solution_string =
1253 return r.solution(m.fz_var_to_index.at(v));
1255 SOLVER_LOG(solution_logger, solution_string);
1256 if (p.display_statistics) {
1257 OutputFlatzincStats(r, solution_logger);
1265 if (solution_observer !=
nullptr) {
1269 sat_model.
GetOrCreate<SatParameters>()->set_log_to_stdout(
false);
1275 if (
response.status() == CpSolverStatus::FEASIBLE ||
1276 response.status() == CpSolverStatus::OPTIMAL) {
1280 return response.solution(m.fz_var_to_index.at(v));
1287 if (
response.status() == CpSolverStatus::FEASIBLE ||
1288 response.status() == CpSolverStatus::OPTIMAL) {
1290 const std::string solution_string =
1292 return response.solution(m.fz_var_to_index.at(v));
1294 SOLVER_LOG(solution_logger, solution_string);
1297 if (
response.status() == CpSolverStatus::OPTIMAL) {
1300 }
else if (
response.status() == CpSolverStatus::INFEASIBLE) {
1301 SOLVER_LOG(solution_logger,
"=====UNSATISFIABLE=====");
1302 }
else if (
response.status() == CpSolverStatus::MODEL_INVALID) {
1304 VLOG(1) <<
"%% Error message = '" << error_message <<
"'";
1305 if (absl::StrContains(error_message,
"overflow")) {
1306 SOLVER_LOG(solution_logger,
"=====OVERFLOW=====");
1308 SOLVER_LOG(solution_logger,
"=====MODEL INVALID=====");
1314 OutputFlatzincStats(
response, solution_logger);
#define LOG_FIRST_N(severity, n)
#define CHECK_LT(val1, val2)
#define CHECK_EQ(val1, val2)
#define CHECK_GE(val1, val2)
#define VLOG(verboselevel)
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
const std::string & name() const
const std::vector< Annotation > & search_annotations() const
Variable * objective() const
const std::vector< Constraint * > & constraints() 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::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)
std::vector< int64_t > AllValuesInDomain(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,...)