Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
cp_model_fz_solver.cc
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <cstdint>
18#include <functional>
19#include <limits>
20#include <string>
21#include <tuple>
22#include <vector>
23
24#include "absl/container/flat_hash_map.h"
25#include "absl/container/flat_hash_set.h"
26#include "absl/flags/flag.h"
27#include "absl/log/check.h"
28#include "absl/strings/match.h"
29#include "absl/strings/str_cat.h"
30#include "absl/types/span.h"
31#include "google/protobuf/text_format.h"
36#include "ortools/sat/cp_model.pb.h"
40#include "ortools/sat/model.h"
41#include "ortools/sat/sat_parameters.pb.h"
44
45ABSL_FLAG(int64_t, fz_int_max, int64_t{1} << 50,
46 "Default max value for unbounded integer variables.");
47
48namespace operations_research {
49namespace sat {
50
51namespace {
52
53static const int kNoVar = std::numeric_limits<int>::min();
54
55struct VarOrValue {
56 int var = kNoVar;
57 int64_t value = 0;
58};
59
60// Returns the true/false literal corresponding to a CpModelProto variable.
61int TrueLiteral(int var) { return var; }
62int FalseLiteral(int var) { return -var - 1; }
63
64// Helper class to convert a flatzinc model to a CpModelProto.
65struct CpModelProtoWithMapping {
66 // Returns a constant CpModelProto variable created on-demand.
67 int LookupConstant(int64_t value);
68
69 // Convert a flatzinc argument to a variable or a list of variable.
70 // Note that we always encode a constant argument with a constant variable.
71 int LookupVar(const fz::Argument& argument);
72 LinearExpressionProto LookupExpr(const fz::Argument& argument,
73 bool negate = false);
74 LinearExpressionProto LookupExprAt(const fz::Argument& argument, int pos,
75 bool negate = false);
76 std::vector<int> LookupVars(const fz::Argument& argument);
77 std::vector<VarOrValue> LookupVarsOrValues(const fz::Argument& argument);
78
79 // Create and return the indices of the IntervalConstraint corresponding
80 // to the flatzinc "interval" specified by a start var and a size var.
81 // This method will cache intervals with the key <start, size>.
82 std::vector<int> CreateIntervals(absl::Span<const VarOrValue> starts,
83 absl::Span<const VarOrValue> sizes);
84
85 // Create and return the indices of the IntervalConstraint corresponding
86 // to the flatzinc "interval" specified by a start var and a size var.
87 // This method will cache intervals with the key <start, size>.
88 // This interval will be optional if the size can be 0.
89 // It also adds a constraint linking the enforcement literal with the size,
90 // stating that the interval will be performed if and only if the size is
91 // greater than 0.
92 std::vector<int> CreateNonZeroOrOptionalIntervals(
93 absl::Span<const VarOrValue> starts, absl::Span<const VarOrValue> sizes);
94
95 // Create and return the index of the optional IntervalConstraint
96 // corresponding to the flatzinc "interval" specified by a start var, the
97 // size_var, and the Boolean opt_var. This method will cache intervals with
98 // the key <start, size, opt_var>. If opt_var == kNoVar, the interval will not
99 // be optional.
100 int GetOrCreateOptionalInterval(VarOrValue start_var, VarOrValue size,
101 int opt_var);
102
103 // Get or Create a literal that implies the variable is > 0.
104 // Its negation implies the variable is == 0.
105 int NonZeroLiteralFrom(VarOrValue size);
106
107 // Adds a constraint to the model, add the enforcement literal if it is
108 // different from kNoVar, and returns a ptr to the ConstraintProto.
109 ConstraintProto* AddEnforcedConstraint(int literal);
110
111 // Helpers to fill a ConstraintProto.
112 void FillAMinusBInDomain(const std::vector<int64_t>& domain,
113 const fz::Constraint& fz_ct, ConstraintProto* ct);
114 void FillLinearConstraintWithGivenDomain(absl::Span<const int64_t> domain,
115 const fz::Constraint& fz_ct,
116 ConstraintProto* ct);
117 void FillConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
118 void FillReifOrImpliedConstraint(const fz::Constraint& fz_ct,
119 ConstraintProto* ct);
120
121 // Translates the flatzinc search annotations into the CpModelProto
122 // search_order field.
123 void TranslateSearchAnnotations(
124 absl::Span<const fz::Annotation> search_annotations,
125 SolverLogger* logger);
126
127 // The output proto.
128 CpModelProto proto;
129 SatParameters parameters;
130
131 // Mapping from flatzinc variables to CpModelProto variables.
132 absl::flat_hash_map<fz::Variable*, int> fz_var_to_index;
133 absl::flat_hash_map<int64_t, int> constant_value_to_index;
134 absl::flat_hash_map<std::tuple<int, int64_t, int, int64_t, int>, int>
136 absl::flat_hash_map<int, int> var_to_lit_implies_greater_than_zero;
137};
138
139int CpModelProtoWithMapping::LookupConstant(int64_t value) {
140 if (constant_value_to_index.contains(value)) {
142 }
143
144 // Create the constant on the fly.
145 const int index = proto.variables_size();
146 IntegerVariableProto* var_proto = proto.add_variables();
147 var_proto->add_domain(value);
148 var_proto->add_domain(value);
150 return index;
151}
152
153int CpModelProtoWithMapping::LookupVar(const fz::Argument& argument) {
154 if (argument.HasOneValue()) return LookupConstant(argument.Value());
155 CHECK_EQ(argument.type, fz::Argument::VAR_REF);
156 return fz_var_to_index[argument.Var()];
157}
158
159LinearExpressionProto CpModelProtoWithMapping::LookupExpr(
160 const fz::Argument& argument, bool negate) {
161 LinearExpressionProto expr;
162 if (argument.HasOneValue()) {
163 const int64_t value = argument.Value();
164 expr.set_offset(negate ? -value : value);
165 } else {
166 expr.add_vars(LookupVar(argument));
167 expr.add_coeffs(negate ? -1 : 1);
168 }
169 return expr;
170}
171
172LinearExpressionProto CpModelProtoWithMapping::LookupExprAt(
173 const fz::Argument& argument, int pos, bool negate) {
174 LinearExpressionProto expr;
175 if (argument.HasOneValueAt(pos)) {
176 const int64_t value = argument.ValueAt(pos);
177 expr.set_offset(negate ? -value : value);
178 } else {
179 expr.add_vars(fz_var_to_index[argument.VarAt(pos)]);
180 expr.add_coeffs(negate ? -1 : 1);
181 }
182 return expr;
183}
184
185std::vector<int> CpModelProtoWithMapping::LookupVars(
186 const fz::Argument& argument) {
187 std::vector<int> result;
188 if (argument.type == fz::Argument::VOID_ARGUMENT) return result;
189 if (argument.type == fz::Argument::INT_LIST) {
190 for (int64_t value : argument.values) {
191 result.push_back(LookupConstant(value));
192 }
193 } else if (argument.type == fz::Argument::INT_VALUE) {
194 result.push_back(LookupConstant(argument.Value()));
195 } else {
196 CHECK_EQ(argument.type, fz::Argument::VAR_REF_ARRAY);
197 for (fz::Variable* var : argument.variables) {
198 CHECK(var != nullptr);
199 result.push_back(fz_var_to_index[var]);
200 }
201 }
202 return result;
203}
204
205std::vector<VarOrValue> CpModelProtoWithMapping::LookupVarsOrValues(
206 const fz::Argument& argument) {
207 std::vector<VarOrValue> result;
208 const int no_var = kNoVar;
209 if (argument.type == fz::Argument::VOID_ARGUMENT) return result;
210 if (argument.type == fz::Argument::INT_LIST) {
211 for (int64_t value : argument.values) {
212 result.push_back({no_var, value});
213 }
214 } else if (argument.type == fz::Argument::INT_VALUE) {
215 result.push_back({no_var, argument.Value()});
216 } else {
217 CHECK_EQ(argument.type, fz::Argument::VAR_REF_ARRAY);
218 for (fz::Variable* var : argument.variables) {
219 CHECK(var != nullptr);
220 if (var->domain.HasOneValue()) {
221 result.push_back({no_var, var->domain.Value()});
222 } else {
223 result.push_back({fz_var_to_index[var], 0});
224 }
225 }
226 }
227 return result;
228}
229
230ConstraintProto* CpModelProtoWithMapping::AddEnforcedConstraint(int literal) {
231 ConstraintProto* result = proto.add_constraints();
232 if (literal != kNoVar) {
233 result->add_enforcement_literal(literal);
234 }
235 return result;
236}
237
238int CpModelProtoWithMapping::GetOrCreateOptionalInterval(VarOrValue start,
239 VarOrValue size,
240 int opt_var) {
241 const int interval_index = proto.constraints_size();
242 const std::tuple<int, int64_t, int, int64_t, int> key =
243 std::make_tuple(start.var, start.value, size.var, size.value, opt_var);
244 const auto [it, inserted] =
246 if (!inserted) {
247 return it->second;
248 }
249
250 if (size.var == kNoVar || start.var == kNoVar) { // Size or start fixed.
251 auto* interval = AddEnforcedConstraint(opt_var)->mutable_interval();
252 if (start.var != kNoVar) {
253 interval->mutable_start()->add_vars(start.var);
254 interval->mutable_start()->add_coeffs(1);
255 interval->mutable_end()->add_vars(start.var);
256 interval->mutable_end()->add_coeffs(1);
257 } else {
258 interval->mutable_start()->set_offset(start.value);
259 interval->mutable_end()->set_offset(start.value);
260 }
261
262 if (size.var != kNoVar) {
263 interval->mutable_size()->add_vars(size.var);
264 interval->mutable_size()->add_coeffs(1);
265 interval->mutable_end()->add_vars(size.var);
266 interval->mutable_end()->add_coeffs(1);
267 } else {
268 interval->mutable_size()->set_offset(size.value);
269 interval->mutable_end()->set_offset(size.value +
270 interval->end().offset());
271 }
272 return interval_index;
273 } else { // start and size are variable.
274 const int end_var = proto.variables_size();
276 ReadDomainFromProto(proto.variables(start.var))
277 .AdditionWith(ReadDomainFromProto(proto.variables(size.var))),
278 proto.add_variables());
279
280 // Create the interval.
281 auto* interval = AddEnforcedConstraint(opt_var)->mutable_interval();
282 interval->mutable_start()->add_vars(start.var);
283 interval->mutable_start()->add_coeffs(1);
284 interval->mutable_size()->add_vars(size.var);
285 interval->mutable_size()->add_coeffs(1);
286 interval->mutable_end()->add_vars(end_var);
287 interval->mutable_end()->add_coeffs(1);
288
289 // Add the linear constraint (after the interval constraint as we have
290 // stored its index).
291 auto* lin = AddEnforcedConstraint(opt_var)->mutable_linear();
292 lin->add_vars(start.var);
293 lin->add_coeffs(1);
294 lin->add_vars(size.var);
295 lin->add_coeffs(1);
296 lin->add_vars(end_var);
297 lin->add_coeffs(-1);
298 lin->add_domain(0);
299 lin->add_domain(0);
300
301 return interval_index;
302 }
303}
304
305std::vector<int> CpModelProtoWithMapping::CreateIntervals(
306 absl::Span<const VarOrValue> starts, absl::Span<const VarOrValue> sizes) {
307 std::vector<int> intervals;
308 for (int i = 0; i < starts.size(); ++i) {
309 intervals.push_back(
310 GetOrCreateOptionalInterval(starts[i], sizes[i], kNoVar));
311 }
312 return intervals;
313}
314
315std::vector<int> CpModelProtoWithMapping::CreateNonZeroOrOptionalIntervals(
316 absl::Span<const VarOrValue> starts, absl::Span<const VarOrValue> sizes) {
317 std::vector<int> intervals;
318 for (int i = 0; i < starts.size(); ++i) {
319 const int opt_var = NonZeroLiteralFrom(sizes[i]);
320 intervals.push_back(
321 GetOrCreateOptionalInterval(starts[i], sizes[i], opt_var));
322 }
323 return intervals;
324}
325
326int CpModelProtoWithMapping::NonZeroLiteralFrom(VarOrValue size) {
327 if (size.var == kNoVar) {
328 return LookupConstant(size.value > 0);
329 }
330 const auto it = var_to_lit_implies_greater_than_zero.find(size.var);
331 if (it != var_to_lit_implies_greater_than_zero.end()) {
332 return it->second;
333 }
334
335 const IntegerVariableProto& var_proto = proto.variables(size.var);
336 const Domain domain = ReadDomainFromProto(var_proto);
337 DCHECK_GE(domain.Min(), 0);
338 if (domain.Min() > 0) return LookupConstant(1);
339 if (domain.Max() == 0) {
340 return LookupConstant(0);
341 }
342
343 const int var_greater_than_zero_lit = proto.variables_size();
344 IntegerVariableProto* lit_proto = proto.add_variables();
345 lit_proto->add_domain(0);
346 lit_proto->add_domain(1);
347
348 ConstraintProto* is_greater =
349 AddEnforcedConstraint(TrueLiteral(var_greater_than_zero_lit));
350 is_greater->mutable_linear()->add_vars(size.var);
351 is_greater->mutable_linear()->add_coeffs(1);
352 const Domain positive = domain.IntersectionWith({1, domain.Max()});
353 FillDomainInProto(positive, is_greater->mutable_linear());
354
355 ConstraintProto* is_null =
356 AddEnforcedConstraint(FalseLiteral(var_greater_than_zero_lit));
357 is_null->mutable_linear()->add_vars(size.var);
358 is_null->mutable_linear()->add_coeffs(1);
359 is_null->mutable_linear()->add_domain(0);
360 is_null->mutable_linear()->add_domain(0);
361
362 var_to_lit_implies_greater_than_zero[size.var] = var_greater_than_zero_lit;
363 return var_greater_than_zero_lit;
364}
365
366void CpModelProtoWithMapping::FillAMinusBInDomain(
367 const std::vector<int64_t>& domain, const fz::Constraint& fz_ct,
368 ConstraintProto* ct) {
369 auto* arg = ct->mutable_linear();
370 if (fz_ct.arguments[1].type == fz::Argument::INT_VALUE) {
371 const int64_t value = fz_ct.arguments[1].Value();
372 const int var_a = LookupVar(fz_ct.arguments[0]);
373 for (const int64_t domain_bound : domain) {
374 if (domain_bound == std::numeric_limits<int64_t>::min() ||
375 domain_bound == std::numeric_limits<int64_t>::max()) {
376 arg->add_domain(domain_bound);
377 } else {
378 arg->add_domain(domain_bound + value);
379 }
380 }
381 arg->add_vars(var_a);
382 arg->add_coeffs(1);
383 } else if (fz_ct.arguments[0].type == fz::Argument::INT_VALUE) {
384 const int64_t value = fz_ct.arguments[0].Value();
385 const int var_b = LookupVar(fz_ct.arguments[1]);
386 for (int64_t domain_bound : gtl::reversed_view(domain)) {
387 if (domain_bound == std::numeric_limits<int64_t>::min()) {
388 arg->add_domain(std::numeric_limits<int64_t>::max());
389 } else if (domain_bound == std::numeric_limits<int64_t>::max()) {
390 arg->add_domain(std::numeric_limits<int64_t>::min());
391 } else {
392 arg->add_domain(value - domain_bound);
393 }
394 }
395 arg->add_vars(var_b);
396 arg->add_coeffs(1);
397 } else {
398 for (const int64_t domain_bound : domain) arg->add_domain(domain_bound);
399 arg->add_vars(LookupVar(fz_ct.arguments[0]));
400 arg->add_coeffs(1);
401 arg->add_vars(LookupVar(fz_ct.arguments[1]));
402 arg->add_coeffs(-1);
403 }
404}
405
406void CpModelProtoWithMapping::FillLinearConstraintWithGivenDomain(
407 absl::Span<const int64_t> domain, const fz::Constraint& fz_ct,
408 ConstraintProto* ct) {
409 auto* arg = ct->mutable_linear();
410 for (const int64_t domain_bound : domain) arg->add_domain(domain_bound);
411 std::vector<int> vars = LookupVars(fz_ct.arguments[1]);
412 for (int i = 0; i < vars.size(); ++i) {
413 arg->add_vars(vars[i]);
414 arg->add_coeffs(fz_ct.arguments[0].values[i]);
415 }
416}
417
418void CpModelProtoWithMapping::FillConstraint(const fz::Constraint& fz_ct,
419 ConstraintProto* ct) {
420 if (fz_ct.type == "false_constraint") {
421 // An empty clause is always false.
422 ct->mutable_bool_or();
423 } else if (fz_ct.type == "bool_clause") {
424 auto* arg = ct->mutable_bool_or();
425 for (const int var : LookupVars(fz_ct.arguments[0])) {
426 arg->add_literals(TrueLiteral(var));
427 }
428 for (const int var : LookupVars(fz_ct.arguments[1])) {
429 arg->add_literals(FalseLiteral(var));
430 }
431 } else if (fz_ct.type == "bool_xor") {
432 // This is not the same semantics as the array_bool_xor as this constraint
433 // is actually a fully reified xor(a, b) <==> x.
434 const int a = LookupVar(fz_ct.arguments[0]);
435 const int b = LookupVar(fz_ct.arguments[1]);
436 const int x = LookupVar(fz_ct.arguments[2]);
437
438 // not(x) => a == b
439 ct->add_enforcement_literal(NegatedRef(x));
440 auto* const refute = ct->mutable_linear();
441 refute->add_vars(a);
442 refute->add_coeffs(1);
443 refute->add_vars(b);
444 refute->add_coeffs(-1);
445 refute->add_domain(0);
446 refute->add_domain(0);
447
448 // x => a + b == 1
449 auto* enforce = AddEnforcedConstraint(x)->mutable_linear();
450 enforce->add_vars(a);
451 enforce->add_coeffs(1);
452 enforce->add_vars(b);
453 enforce->add_coeffs(1);
454 enforce->add_domain(1);
455 enforce->add_domain(1);
456 } else if (fz_ct.type == "array_bool_or") {
457 auto* arg = ct->mutable_bool_or();
458 for (const int var : LookupVars(fz_ct.arguments[0])) {
459 arg->add_literals(TrueLiteral(var));
460 }
461 } else if (fz_ct.type == "array_bool_or_negated") {
462 auto* arg = ct->mutable_bool_and();
463 for (const int var : LookupVars(fz_ct.arguments[0])) {
464 arg->add_literals(FalseLiteral(var));
465 }
466 } else if (fz_ct.type == "array_bool_and") {
467 auto* arg = ct->mutable_bool_and();
468 for (const int var : LookupVars(fz_ct.arguments[0])) {
469 arg->add_literals(TrueLiteral(var));
470 }
471 } else if (fz_ct.type == "array_bool_and_negated") {
472 auto* arg = ct->mutable_bool_or();
473 for (const int var : LookupVars(fz_ct.arguments[0])) {
474 arg->add_literals(FalseLiteral(var));
475 }
476 } else if (fz_ct.type == "array_bool_xor") {
477 auto* arg = ct->mutable_bool_xor();
478 for (const int var : LookupVars(fz_ct.arguments[0])) {
479 arg->add_literals(TrueLiteral(var));
480 }
481 } else if (fz_ct.type == "bool_le" || fz_ct.type == "int_le") {
482 FillAMinusBInDomain({std::numeric_limits<int64_t>::min(), 0}, fz_ct, ct);
483 } else if (fz_ct.type == "bool_ge" || fz_ct.type == "int_ge") {
484 FillAMinusBInDomain({0, std::numeric_limits<int64_t>::max()}, fz_ct, ct);
485 } else if (fz_ct.type == "bool_lt" || fz_ct.type == "int_lt") {
486 FillAMinusBInDomain({std::numeric_limits<int64_t>::min(), -1}, fz_ct, ct);
487 } else if (fz_ct.type == "bool_gt" || fz_ct.type == "int_gt") {
488 FillAMinusBInDomain({1, std::numeric_limits<int64_t>::max()}, fz_ct, ct);
489 } else if (fz_ct.type == "bool_eq" || fz_ct.type == "int_eq" ||
490 fz_ct.type == "bool2int") {
491 FillAMinusBInDomain({0, 0}, fz_ct, ct);
492 } else if (fz_ct.type == "bool_ne" || fz_ct.type == "bool_not") {
493 auto* arg = ct->mutable_linear();
494 arg->add_vars(LookupVar(fz_ct.arguments[0]));
495 arg->add_coeffs(1);
496 arg->add_vars(LookupVar(fz_ct.arguments[1]));
497 arg->add_coeffs(1);
498 arg->add_domain(1);
499 arg->add_domain(1);
500 } else if (fz_ct.type == "int_ne") {
501 FillAMinusBInDomain({std::numeric_limits<int64_t>::min(), -1, 1,
502 std::numeric_limits<int64_t>::max()},
503 fz_ct, ct);
504 } else if (fz_ct.type == "int_lin_eq") {
505 const int64_t rhs = fz_ct.arguments[2].values[0];
506 FillLinearConstraintWithGivenDomain({rhs, rhs}, fz_ct, ct);
507 } else if (fz_ct.type == "bool_lin_eq") {
508 auto* arg = ct->mutable_linear();
509 const std::vector<int> vars = LookupVars(fz_ct.arguments[1]);
510 for (int i = 0; i < vars.size(); ++i) {
511 arg->add_vars(vars[i]);
512 arg->add_coeffs(fz_ct.arguments[0].values[i]);
513 }
514 if (fz_ct.arguments[2].IsVariable()) {
515 arg->add_vars(LookupVar(fz_ct.arguments[2]));
516 arg->add_coeffs(-1);
517 arg->add_domain(0);
518 arg->add_domain(0);
519 } else {
520 const int64_t v = fz_ct.arguments[2].Value();
521 arg->add_domain(v);
522 arg->add_domain(v);
523 }
524 } else if (fz_ct.type == "int_lin_le" || fz_ct.type == "bool_lin_le") {
525 const int64_t rhs = fz_ct.arguments[2].values[0];
526 FillLinearConstraintWithGivenDomain(
527 {std::numeric_limits<int64_t>::min(), rhs}, fz_ct, ct);
528 } else if (fz_ct.type == "int_lin_lt") {
529 const int64_t rhs = fz_ct.arguments[2].values[0];
530 FillLinearConstraintWithGivenDomain(
531 {std::numeric_limits<int64_t>::min(), rhs - 1}, fz_ct, ct);
532 } else if (fz_ct.type == "int_lin_ge") {
533 const int64_t rhs = fz_ct.arguments[2].values[0];
534 FillLinearConstraintWithGivenDomain(
535 {rhs, std::numeric_limits<int64_t>::max()}, fz_ct, ct);
536 } else if (fz_ct.type == "int_lin_gt") {
537 const int64_t rhs = fz_ct.arguments[2].values[0];
538 FillLinearConstraintWithGivenDomain(
539 {rhs + 1, std::numeric_limits<int64_t>::max()}, fz_ct, ct);
540 } else if (fz_ct.type == "int_lin_ne") {
541 const int64_t rhs = fz_ct.arguments[2].values[0];
542 FillLinearConstraintWithGivenDomain(
543 {std::numeric_limits<int64_t>::min(), rhs - 1, rhs + 1,
544 std::numeric_limits<int64_t>::max()},
545 fz_ct, ct);
546 } else if (fz_ct.type == "set_in") {
547 auto* arg = ct->mutable_linear();
548 arg->add_vars(LookupVar(fz_ct.arguments[0]));
549 arg->add_coeffs(1);
550 if (fz_ct.arguments[1].type == fz::Argument::INT_LIST) {
551 FillDomainInProto(Domain::FromValues(std::vector<int64_t>{
552 fz_ct.arguments[1].values.begin(),
553 fz_ct.arguments[1].values.end()}),
554 arg);
555 } else if (fz_ct.arguments[1].type == fz::Argument::INT_INTERVAL) {
557 Domain(fz_ct.arguments[1].values[0], fz_ct.arguments[1].values[1]),
558 arg);
559 } else {
560 LOG(FATAL) << "Wrong format";
561 }
562 } else if (fz_ct.type == "set_in_negated") {
563 auto* arg = ct->mutable_linear();
564 arg->add_vars(LookupVar(fz_ct.arguments[0]));
565 arg->add_coeffs(1);
566 if (fz_ct.arguments[1].type == fz::Argument::INT_LIST) {
568 Domain::FromValues(
569 std::vector<int64_t>{fz_ct.arguments[1].values.begin(),
570 fz_ct.arguments[1].values.end()})
571 .Complement(),
572 arg);
573 } else if (fz_ct.arguments[1].type == fz::Argument::INT_INTERVAL) {
575 Domain(fz_ct.arguments[1].values[0], fz_ct.arguments[1].values[1])
576 .Complement(),
577 arg);
578 } else {
579 LOG(FATAL) << "Wrong format";
580 }
581 } else if (fz_ct.type == "int_min") {
582 auto* arg = ct->mutable_lin_max();
583 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0], /*negate=*/true);
584 *arg->add_exprs() = LookupExpr(fz_ct.arguments[1], /*negate=*/true);
585 *arg->mutable_target() = LookupExpr(fz_ct.arguments[2], /*negate=*/true);
586 } else if (fz_ct.type == "array_int_minimum" || fz_ct.type == "minimum_int") {
587 auto* arg = ct->mutable_lin_max();
588 *arg->mutable_target() = LookupExpr(fz_ct.arguments[0], /*negate=*/true);
589 for (int i = 0; i < fz_ct.arguments[1].Size(); ++i) {
590 *arg->add_exprs() = LookupExprAt(fz_ct.arguments[1], i, /*negate=*/true);
591 }
592 } else if (fz_ct.type == "int_max") {
593 auto* arg = ct->mutable_lin_max();
594 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
595 *arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
596 *arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
597 } else if (fz_ct.type == "array_int_maximum" || fz_ct.type == "maximum_int") {
598 auto* arg = ct->mutable_lin_max();
599 *arg->mutable_target() = LookupExpr(fz_ct.arguments[0]);
600 for (int i = 0; i < fz_ct.arguments[1].Size(); ++i) {
601 *arg->add_exprs() = LookupExprAt(fz_ct.arguments[1], i);
602 }
603 } else if (fz_ct.type == "int_times") {
604 auto* arg = ct->mutable_int_prod();
605 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
606 *arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
607 *arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
608 } else if (fz_ct.type == "int_abs") {
609 auto* arg = ct->mutable_lin_max();
610 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
611 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0], /*negate=*/true);
612 *arg->mutable_target() = LookupExpr(fz_ct.arguments[1]);
613 } else if (fz_ct.type == "int_plus") {
614 auto* arg = ct->mutable_linear();
615 FillDomainInProto(Domain(0, 0), arg);
616 arg->add_vars(LookupVar(fz_ct.arguments[0]));
617 arg->add_coeffs(1);
618 arg->add_vars(LookupVar(fz_ct.arguments[1]));
619 arg->add_coeffs(1);
620 arg->add_vars(LookupVar(fz_ct.arguments[2]));
621 arg->add_coeffs(-1);
622 } else if (fz_ct.type == "int_div") {
623 auto* arg = ct->mutable_int_div();
624 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
625 *arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
626 *arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
627 } else if (fz_ct.type == "int_mod") {
628 auto* arg = ct->mutable_int_mod();
629 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
630 *arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
631 *arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
632 } else if (fz_ct.type == "array_int_element" ||
633 fz_ct.type == "array_bool_element" ||
634 fz_ct.type == "array_var_int_element" ||
635 fz_ct.type == "array_var_bool_element" ||
636 fz_ct.type == "array_int_element_nonshifted") {
637 if (fz_ct.arguments[0].type == fz::Argument::VAR_REF ||
638 fz_ct.arguments[0].type == fz::Argument::INT_VALUE) {
639 auto* arg = ct->mutable_element();
640 arg->set_index(LookupVar(fz_ct.arguments[0]));
641 arg->set_target(LookupVar(fz_ct.arguments[2]));
642
643 if (!absl::EndsWith(fz_ct.type, "_nonshifted")) {
644 // Add a dummy variable at position zero because flatzinc index start
645 // at 1.
646 // TODO(user): Make sure that zero is not in the index domain...
647 arg->add_vars(LookupConstant(0));
648 }
649 for (const int var : LookupVars(fz_ct.arguments[1])) arg->add_vars(var);
650 } else {
651 // Special case added by the presolve or in flatzinc. We encode this
652 // as a table constraint.
653 CHECK(!absl::EndsWith(fz_ct.type, "_nonshifted"));
654 auto* arg = ct->mutable_table();
655
656 // the constraint is:
657 // values[coeff1 * vars[0] + coeff2 * vars[1] + offset] == target.
658 for (const int var : LookupVars(fz_ct.arguments[0])) arg->add_vars(var);
659 arg->add_vars(LookupVar(fz_ct.arguments[2])); // the target
660
661 const std::vector<int64_t>& values = fz_ct.arguments[1].values;
662 const int64_t coeff1 = fz_ct.arguments[3].values[0];
663 const int64_t coeff2 = fz_ct.arguments[3].values[1];
664 const int64_t offset = fz_ct.arguments[4].values[0] - 1;
665
666 for (const int64_t a : AllValuesInDomain(proto.variables(arg->vars(0)))) {
667 for (const int64_t b :
668 AllValuesInDomain(proto.variables(arg->vars(1)))) {
669 const int index = coeff1 * a + coeff2 * b + offset;
670 CHECK_GE(index, 0);
671 CHECK_LT(index, values.size());
672 arg->add_values(a);
673 arg->add_values(b);
674 arg->add_values(values[index]);
675 }
676 }
677 }
678 } else if (fz_ct.type == "ortools_table_int") {
679 auto* arg = ct->mutable_table();
680 for (const int var : LookupVars(fz_ct.arguments[0])) arg->add_vars(var);
681 for (const int64_t value : fz_ct.arguments[1].values)
682 arg->add_values(value);
683 } else if (fz_ct.type == "ortools_regular") {
684 auto* arg = ct->mutable_automaton();
685 for (const int var : LookupVars(fz_ct.arguments[0])) arg->add_vars(var);
686
687 int count = 0;
688 const int num_states = fz_ct.arguments[1].Value();
689 const int num_values = fz_ct.arguments[2].Value();
690 for (int i = 1; i <= num_states; ++i) {
691 for (int j = 1; j <= num_values; ++j) {
692 CHECK_LT(count, fz_ct.arguments[3].values.size());
693 const int next = fz_ct.arguments[3].values[count++];
694 if (next == 0) continue; // 0 is a failing state.
695 arg->add_transition_tail(i);
696 arg->add_transition_label(j);
697 arg->add_transition_head(next);
698 }
699 }
700
701 arg->set_starting_state(fz_ct.arguments[4].Value());
702 switch (fz_ct.arguments[5].type) {
703 case fz::Argument::INT_VALUE: {
704 arg->add_final_states(fz_ct.arguments[5].values[0]);
705 break;
706 }
707 case fz::Argument::INT_INTERVAL: {
708 for (int v = fz_ct.arguments[5].values[0];
709 v <= fz_ct.arguments[5].values[1]; ++v) {
710 arg->add_final_states(v);
711 }
712 break;
713 }
714 case fz::Argument::INT_LIST: {
715 for (const int v : fz_ct.arguments[5].values) {
716 arg->add_final_states(v);
717 }
718 break;
719 }
720 default: {
721 LOG(FATAL) << "Wrong constraint " << fz_ct.DebugString();
722 }
723 }
724 } else if (fz_ct.type == "fzn_all_different_int") {
725 auto* arg = ct->mutable_all_diff();
726 for (int i = 0; i < fz_ct.arguments[0].Size(); ++i) {
727 *arg->add_exprs() = LookupExprAt(fz_ct.arguments[0], i);
728 }
729 } else if (fz_ct.type == "ortools_circuit" ||
730 fz_ct.type == "ortools_subcircuit") {
731 const int64_t min_index = fz_ct.arguments[1].Value();
732 const int size = std::max(fz_ct.arguments[0].values.size(),
733 fz_ct.arguments[0].variables.size());
734
735 const int64_t max_index = min_index + size - 1;
736 // The arc-based mutable circuit.
737 auto* circuit_arg = ct->mutable_circuit();
738
739 // We fully encode all variables so we can use the literal based circuit.
740 // TODO(user): avoid fully encoding more than once?
741 int64_t index = min_index;
742 const bool is_circuit = (fz_ct.type == "ortools_circuit");
743 for (const int var : LookupVars(fz_ct.arguments[0])) {
744 Domain domain = ReadDomainFromProto(proto.variables(var));
745
746 // Restrict the domain of var to [min_index, max_index]
747 domain = domain.IntersectionWith(Domain(min_index, max_index));
748 if (is_circuit) {
749 // We simply make sure that the variable cannot take the value index.
750 domain = domain.IntersectionWith(Domain::FromIntervals(
751 {{std::numeric_limits<int64_t>::min(), index - 1},
752 {index + 1, std::numeric_limits<int64_t>::max()}}));
753 }
754 FillDomainInProto(domain, proto.mutable_variables(var));
755
756 for (const ClosedInterval interval : domain) {
757 for (int64_t value = interval.start; value <= interval.end; ++value) {
758 // Create one Boolean variable for this arc.
759 const int literal = proto.variables_size();
760 {
761 auto* new_var = proto.add_variables();
762 new_var->add_domain(0);
763 new_var->add_domain(1);
764 }
765
766 // Add the arc.
767 circuit_arg->add_tails(index);
768 circuit_arg->add_heads(value);
769 circuit_arg->add_literals(literal);
770
771 // literal => var == value.
772 {
773 auto* lin = AddEnforcedConstraint(literal)->mutable_linear();
774 lin->add_coeffs(1);
775 lin->add_vars(var);
776 lin->add_domain(value);
777 lin->add_domain(value);
778 }
779
780 // not(literal) => var != value
781 {
782 auto* lin =
783 AddEnforcedConstraint(NegatedRef(literal))->mutable_linear();
784 lin->add_coeffs(1);
785 lin->add_vars(var);
786 lin->add_domain(std::numeric_limits<int64_t>::min());
787 lin->add_domain(value - 1);
788 lin->add_domain(value + 1);
789 lin->add_domain(std::numeric_limits<int64_t>::max());
790 }
791 }
792 }
793
794 ++index;
795 }
796 } else if (fz_ct.type == "ortools_inverse") {
797 auto* arg = ct->mutable_inverse();
798
799 const auto direct_variables = LookupVars(fz_ct.arguments[0]);
800 const auto inverse_variables = LookupVars(fz_ct.arguments[1]);
801 const int base_direct = fz_ct.arguments[2].Value();
802 const int base_inverse = fz_ct.arguments[3].Value();
803
804 CHECK_EQ(direct_variables.size(), inverse_variables.size());
805 const int num_variables = direct_variables.size();
806 const int end_direct = base_direct + num_variables;
807 const int end_inverse = base_inverse + num_variables;
808
809 // Any convention that maps the "fixed values" to the one of the inverse and
810 // back works. We decided to follow this one:
811 // There are 3 cases:
812 // (A) base_direct == base_inverse, we fill the arrays
813 // direct = [0, .., base_direct - 1] U [direct_vars]
814 // inverse = [0, .., base_direct - 1] U [inverse_vars]
815 // (B) base_direct == base_inverse + offset (> 0), we fill the arrays
816 // direct = [0, .., base_inverse - 1] U
817 // [end_inverse, .., end_inverse + offset - 1] U
818 // [direct_vars]
819 // inverse = [0, .., base_inverse - 1] U
820 // [inverse_vars] U
821 // [base_inverse, .., base_base_inverse + offset - 1]
822 // (C): base_inverse == base_direct + offset (> 0), we fill the arrays
823 // direct = [0, .., base_direct - 1] U
824 // [direct_vars] U
825 // [base_direct, .., base_direct + offset - 1]
826 // inverse [0, .., base_direct - 1] U
827 // [end_direct, .., end_direct + offset - 1] U
828 // [inverse_vars]
829 const int arity = std::max(base_inverse, base_direct) + num_variables;
830 for (int i = 0; i < arity; ++i) {
831 // Fill the direct array.
832 if (i < base_direct) {
833 if (i < base_inverse) {
834 arg->add_f_direct(LookupConstant(i));
835 } else if (i >= base_inverse) {
836 arg->add_f_direct(LookupConstant(i + num_variables));
837 }
838 } else if (i >= base_direct && i < end_direct) {
839 arg->add_f_direct(direct_variables[i - base_direct]);
840 } else {
841 arg->add_f_direct(LookupConstant(i - num_variables));
842 }
843
844 // Fill the inverse array.
845 if (i < base_inverse) {
846 if (i < base_direct) {
847 arg->add_f_inverse(LookupConstant(i));
848 } else if (i >= base_direct) {
849 arg->add_f_inverse(LookupConstant(i + num_variables));
850 }
851 } else if (i >= base_inverse && i < end_inverse) {
852 arg->add_f_inverse(inverse_variables[i - base_inverse]);
853 } else {
854 arg->add_f_inverse(LookupConstant(i - num_variables));
855 }
856 }
857 } else if (fz_ct.type == "fzn_disjunctive") {
858 const std::vector<VarOrValue> starts =
859 LookupVarsOrValues(fz_ct.arguments[0]);
860 const std::vector<VarOrValue> sizes =
861 LookupVarsOrValues(fz_ct.arguments[1]);
862
863 auto* arg = ct->mutable_cumulative();
864 arg->mutable_capacity()->set_offset(1);
865 for (int i = 0; i < starts.size(); ++i) {
866 arg->add_intervals(
867 GetOrCreateOptionalInterval(starts[i], sizes[i], kNoVar));
868 arg->add_demands()->set_offset(1);
869 }
870 } else if (fz_ct.type == "fzn_disjunctive_strict") {
871 const std::vector<VarOrValue> starts =
872 LookupVarsOrValues(fz_ct.arguments[0]);
873 const std::vector<VarOrValue> sizes =
874 LookupVarsOrValues(fz_ct.arguments[1]);
875
876 auto* arg = ct->mutable_no_overlap();
877 for (int i = 0; i < starts.size(); ++i) {
878 arg->add_intervals(
879 GetOrCreateOptionalInterval(starts[i], sizes[i], kNoVar));
880 }
881 } else if (fz_ct.type == "fzn_cumulative") {
882 const std::vector<VarOrValue> starts =
883 LookupVarsOrValues(fz_ct.arguments[0]);
884 const std::vector<VarOrValue> sizes =
885 LookupVarsOrValues(fz_ct.arguments[1]);
886 const std::vector<VarOrValue> demands =
887 LookupVarsOrValues(fz_ct.arguments[2]);
888
889 auto* arg = ct->mutable_cumulative();
890 if (fz_ct.arguments[3].HasOneValue()) {
891 arg->mutable_capacity()->set_offset(fz_ct.arguments[3].Value());
892 } else {
893 arg->mutable_capacity()->add_vars(LookupVar(fz_ct.arguments[3]));
894 arg->mutable_capacity()->add_coeffs(1);
895 }
896 for (int i = 0; i < starts.size(); ++i) {
897 // Special case for a 0-1 demand, we mark the interval as optional
898 // instead and fix the demand to 1.
899 if (demands[i].var != kNoVar &&
900 proto.variables(demands[i].var).domain().size() == 2 &&
901 proto.variables(demands[i].var).domain(0) == 0 &&
902 proto.variables(demands[i].var).domain(1) == 1 &&
903 fz_ct.arguments[3].HasOneValue() && fz_ct.arguments[3].Value() == 1) {
904 arg->add_intervals(
905 GetOrCreateOptionalInterval(starts[i], sizes[i], demands[i].var));
906 arg->add_demands()->set_offset(1);
907 } else {
908 arg->add_intervals(
909 GetOrCreateOptionalInterval(starts[i], sizes[i], kNoVar));
910 LinearExpressionProto* demand = arg->add_demands();
911 if (demands[i].var == kNoVar) {
912 demand->set_offset(demands[i].value);
913 } else {
914 demand->add_vars(demands[i].var);
915 demand->add_coeffs(1);
916 }
917 }
918 }
919 } else if (fz_ct.type == "ortools_cumulative_opt") {
920 const std::vector<int> occurs = LookupVars(fz_ct.arguments[0]);
921 const std::vector<VarOrValue> starts =
922 LookupVarsOrValues(fz_ct.arguments[1]);
923 const std::vector<VarOrValue> durations =
924 LookupVarsOrValues(fz_ct.arguments[2]);
925 const std::vector<VarOrValue> demands =
926 LookupVarsOrValues(fz_ct.arguments[3]);
927
928 auto* arg = ct->mutable_cumulative();
929 if (fz_ct.arguments[3].HasOneValue()) {
930 arg->mutable_capacity()->set_offset(fz_ct.arguments[4].Value());
931 } else {
932 arg->mutable_capacity()->add_vars(LookupVar(fz_ct.arguments[4]));
933 arg->mutable_capacity()->add_coeffs(1);
934 }
935 for (int i = 0; i < starts.size(); ++i) {
936 arg->add_intervals(
937 GetOrCreateOptionalInterval(starts[i], durations[i], occurs[i]));
938 LinearExpressionProto* demand = arg->add_demands();
939 if (demands[i].var == kNoVar) {
940 demand->set_offset(demands[i].value);
941 } else {
942 demand->add_vars(demands[i].var);
943 demand->add_coeffs(1);
944 }
945 }
946 } else if (fz_ct.type == "ortools_disjunctive_strict_opt") {
947 const std::vector<int> occurs = LookupVars(fz_ct.arguments[0]);
948 const std::vector<VarOrValue> starts =
949 LookupVarsOrValues(fz_ct.arguments[1]);
950 const std::vector<VarOrValue> durations =
951 LookupVarsOrValues(fz_ct.arguments[2]);
952
953 auto* arg = ct->mutable_no_overlap();
954 for (int i = 0; i < starts.size(); ++i) {
955 arg->add_intervals(
956 GetOrCreateOptionalInterval(starts[i], durations[i], occurs[i]));
957 }
958 } else if (fz_ct.type == "fzn_diffn" || fz_ct.type == "fzn_diffn_nonstrict") {
959 const bool is_strict = fz_ct.type == "fzn_diffn";
960 const std::vector<VarOrValue> x = LookupVarsOrValues(fz_ct.arguments[0]);
961 const std::vector<VarOrValue> y = LookupVarsOrValues(fz_ct.arguments[1]);
962 const std::vector<VarOrValue> dx = LookupVarsOrValues(fz_ct.arguments[2]);
963 const std::vector<VarOrValue> dy = LookupVarsOrValues(fz_ct.arguments[3]);
964 const std::vector<int> x_intervals =
965 is_strict ? CreateIntervals(x, dx)
966 : CreateNonZeroOrOptionalIntervals(x, dx);
967 const std::vector<int> y_intervals =
968 is_strict ? CreateIntervals(y, dy)
969 : CreateNonZeroOrOptionalIntervals(y, dy);
970 auto* arg = ct->mutable_no_overlap_2d();
971 for (int i = 0; i < x.size(); ++i) {
972 arg->add_x_intervals(x_intervals[i]);
973 arg->add_y_intervals(y_intervals[i]);
974 }
975 } else if (fz_ct.type == "ortools_network_flow" ||
976 fz_ct.type == "ortools_network_flow_cost") {
977 // Note that we leave ct empty here (with just the name set).
978 // We simply do a linear encoding of this constraint.
979 const bool has_cost = fz_ct.type == "ortools_network_flow_cost";
980 const std::vector<int> flow = LookupVars(fz_ct.arguments[3]);
981
982 // Flow conservation constraints.
983 const int num_nodes = fz_ct.arguments[1].values.size();
984 const int base_node = fz_ct.arguments[2].Value();
985 std::vector<std::vector<int>> flows_per_node(num_nodes);
986 std::vector<std::vector<int>> coeffs_per_node(num_nodes);
987
988 const int num_arcs = fz_ct.arguments[0].values.size() / 2;
989 for (int arc = 0; arc < num_arcs; arc++) {
990 const int tail = fz_ct.arguments[0].values[2 * arc] - base_node;
991 const int head = fz_ct.arguments[0].values[2 * arc + 1] - base_node;
992 if (tail == head) continue;
993
994 flows_per_node[tail].push_back(flow[arc]);
995 coeffs_per_node[tail].push_back(1);
996 flows_per_node[head].push_back(flow[arc]);
997 coeffs_per_node[head].push_back(-1);
998 }
999 for (int node = 0; node < num_nodes; node++) {
1000 auto* arg = proto.add_constraints()->mutable_linear();
1001 arg->add_domain(fz_ct.arguments[1].values[node]);
1002 arg->add_domain(fz_ct.arguments[1].values[node]);
1003 for (int i = 0; i < flows_per_node[node].size(); ++i) {
1004 arg->add_vars(flows_per_node[node][i]);
1005 arg->add_coeffs(coeffs_per_node[node][i]);
1006 }
1007 }
1008
1009 if (has_cost) {
1010 auto* arg = proto.add_constraints()->mutable_linear();
1011 arg->add_domain(0);
1012 arg->add_domain(0);
1013 for (int arc = 0; arc < num_arcs; arc++) {
1014 const int64_t weight = fz_ct.arguments[4].values[arc];
1015 if (weight != 0) {
1016 arg->add_vars(flow[arc]);
1017 arg->add_coeffs(weight);
1018 }
1019 }
1020 arg->add_vars(LookupVar(fz_ct.arguments[5]));
1021 arg->add_coeffs(-1);
1022 }
1023 } else {
1024 LOG(FATAL) << " Not supported " << fz_ct.type;
1025 }
1026} // NOLINT(readability/fn_size)
1027
1028void CpModelProtoWithMapping::FillReifOrImpliedConstraint(
1029 const fz::Constraint& fz_ct, ConstraintProto* ct) {
1030 // Start by adding a non-reified version of the same constraint.
1031 std::string simplified_type;
1032 if (absl::EndsWith(fz_ct.type, "_reif")) {
1033 // Remove _reif.
1034 simplified_type = fz_ct.type.substr(0, fz_ct.type.size() - 5);
1035 } else if (absl::EndsWith(fz_ct.type, "_imp")) {
1036 // Remove _imp.
1037 simplified_type = fz_ct.type.substr(0, fz_ct.type.size() - 4);
1038 } else {
1039 // Keep name as it is an implicit reified constraint.
1040 simplified_type = fz_ct.type;
1041 }
1042
1043 // We need a copy to be able to change the type of the constraint.
1044 fz::Constraint copy = fz_ct;
1045 copy.type = simplified_type;
1046
1047 // Create the CP-SAT constraint.
1048 FillConstraint(copy, ct);
1049
1050 // In case of reified constraints, the type of the opposite constraint.
1051 std::string negated_type;
1052
1053 // Fill enforcement_literal and set copy.type to the negated constraint.
1054 if (simplified_type == "array_bool_or") {
1055 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[1])));
1056 negated_type = "array_bool_or_negated";
1057 } else if (simplified_type == "array_bool_and") {
1058 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[1])));
1059 negated_type = "array_bool_and_negated";
1060 } else if (simplified_type == "set_in") {
1061 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
1062 negated_type = "set_in_negated";
1063 } else if (simplified_type == "bool_eq" || simplified_type == "int_eq") {
1064 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
1065 negated_type = "int_ne";
1066 } else if (simplified_type == "bool_ne" || simplified_type == "int_ne") {
1067 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
1068 negated_type = "int_eq";
1069 } else if (simplified_type == "bool_le" || simplified_type == "int_le") {
1070 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
1071 negated_type = "int_gt";
1072 } else if (simplified_type == "bool_lt" || simplified_type == "int_lt") {
1073 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
1074 negated_type = "int_ge";
1075 } else if (simplified_type == "bool_ge" || simplified_type == "int_ge") {
1076 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
1077 negated_type = "int_lt";
1078 } else if (simplified_type == "bool_gt" || simplified_type == "int_gt") {
1079 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
1080 negated_type = "int_le";
1081 } else if (simplified_type == "int_lin_eq") {
1082 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
1083 negated_type = "int_lin_ne";
1084 } else if (simplified_type == "int_lin_ne") {
1085 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
1086 negated_type = "int_lin_eq";
1087 } else if (simplified_type == "int_lin_le") {
1088 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
1089 negated_type = "int_lin_gt";
1090 } else if (simplified_type == "int_lin_ge") {
1091 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
1092 negated_type = "int_lin_lt";
1093 } else if (simplified_type == "int_lin_lt") {
1094 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
1095 negated_type = "int_lin_ge";
1096 } else if (simplified_type == "int_lin_gt") {
1097 ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
1098 negated_type = "int_lin_le";
1099 } else {
1100 LOG(FATAL) << "Unsupported " << simplified_type;
1101 }
1102
1103 // One way implication. We can stop here.
1104 if (absl::EndsWith(fz_ct.type, "_imp")) return;
1105
1106 // Add the other side of the reification because CpModelProto only support
1107 // half reification.
1108 ConstraintProto* negated_ct = proto.add_constraints();
1109 negated_ct->set_name(fz_ct.type + " (negated)");
1110 negated_ct->add_enforcement_literal(
1111 sat::NegatedRef(ct->enforcement_literal(0)));
1112 copy.type = negated_type;
1113 FillConstraint(copy, negated_ct);
1114}
1115
1116void CpModelProtoWithMapping::TranslateSearchAnnotations(
1117 absl::Span<const fz::Annotation> search_annotations, SolverLogger* logger) {
1118 std::vector<fz::Annotation> flat_annotations;
1119 for (const fz::Annotation& annotation : search_annotations) {
1120 fz::FlattenAnnotations(annotation, &flat_annotations);
1121 }
1122
1123 // CP-SAT rejects models containing variables duplicated in hints.
1124 absl::flat_hash_set<int> hinted_vars;
1125
1126 for (const fz::Annotation& annotation : flat_annotations) {
1127 if (annotation.IsFunctionCallWithIdentifier("warm_start")) {
1128 CHECK_EQ(2, annotation.annotations.size());
1129 const fz::Annotation& vars = annotation.annotations[0];
1130 const fz::Annotation& values = annotation.annotations[1];
1131 if (vars.type != fz::Annotation::VAR_REF_ARRAY ||
1132 values.type != fz::Annotation::INT_LIST) {
1133 continue;
1134 }
1135 for (int i = 0; i < vars.variables.size(); ++i) {
1136 fz::Variable* fz_var = vars.variables[i];
1137 const int var = fz_var_to_index.at(fz_var);
1138 const int64_t value = values.values[i];
1139 if (hinted_vars.insert(var).second) {
1140 proto.mutable_solution_hint()->add_vars(var);
1141 proto.mutable_solution_hint()->add_values(value);
1142 }
1143 }
1144 } else if (annotation.IsFunctionCallWithIdentifier("int_search") ||
1145 annotation.IsFunctionCallWithIdentifier("bool_search")) {
1146 const std::vector<fz::Annotation>& args = annotation.annotations;
1147 std::vector<fz::Variable*> vars;
1148 args[0].AppendAllVariables(&vars);
1149
1150 DecisionStrategyProto* strategy = proto.add_search_strategy();
1151 for (fz::Variable* v : vars) {
1152 strategy->add_variables(fz_var_to_index.at(v));
1153 }
1154
1155 const fz::Annotation& choose = args[1];
1156 if (choose.id == "input_order") {
1157 strategy->set_variable_selection_strategy(
1158 DecisionStrategyProto::CHOOSE_FIRST);
1159 } else if (choose.id == "first_fail") {
1160 strategy->set_variable_selection_strategy(
1161 DecisionStrategyProto::CHOOSE_MIN_DOMAIN_SIZE);
1162 } else if (choose.id == "anti_first_fail") {
1163 strategy->set_variable_selection_strategy(
1164 DecisionStrategyProto::CHOOSE_MAX_DOMAIN_SIZE);
1165 } else if (choose.id == "smallest") {
1166 strategy->set_variable_selection_strategy(
1167 DecisionStrategyProto::CHOOSE_LOWEST_MIN);
1168 } else if (choose.id == "largest") {
1169 strategy->set_variable_selection_strategy(
1170 DecisionStrategyProto::CHOOSE_HIGHEST_MAX);
1171 } else {
1172 SOLVER_LOG(logger, "Unsupported variable selection strategy '",
1173 choose.id, "', falling back to 'smallest'");
1174 strategy->set_variable_selection_strategy(
1175 DecisionStrategyProto::CHOOSE_LOWEST_MIN);
1176 }
1177
1178 const fz::Annotation& select = args[2];
1179 if (select.id == "indomain_min" || select.id == "indomain") {
1180 strategy->set_domain_reduction_strategy(
1181 DecisionStrategyProto::SELECT_MIN_VALUE);
1182 } else if (select.id == "indomain_max") {
1183 strategy->set_domain_reduction_strategy(
1184 DecisionStrategyProto::SELECT_MAX_VALUE);
1185 } else if (select.id == "indomain_split") {
1186 strategy->set_domain_reduction_strategy(
1187 DecisionStrategyProto::SELECT_LOWER_HALF);
1188 } else if (select.id == "indomain_reverse_split") {
1189 strategy->set_domain_reduction_strategy(
1190 DecisionStrategyProto::SELECT_UPPER_HALF);
1191 } else if (select.id == "indomain_median") {
1192 strategy->set_domain_reduction_strategy(
1193 DecisionStrategyProto::SELECT_MEDIAN_VALUE);
1194 } else {
1195 SOLVER_LOG(logger, "Unsupported value selection strategy '", select.id,
1196 "', falling back to 'indomain_min'");
1197 strategy->set_domain_reduction_strategy(
1198 DecisionStrategyProto::SELECT_MIN_VALUE);
1199 }
1200 }
1201 }
1202}
1203
1204// The format is fixed in the flatzinc specification.
1205std::string SolutionString(
1206 const fz::SolutionOutputSpecs& output,
1207 const std::function<int64_t(fz::Variable*)>& value_func,
1208 double objective_value) {
1209 if (output.variable != nullptr && !output.variable->domain.is_float) {
1210 const int64_t value = value_func(output.variable);
1211 if (output.display_as_boolean) {
1212 return absl::StrCat(output.name, " = ", value == 1 ? "true" : "false",
1213 ";");
1214 } else {
1215 return absl::StrCat(output.name, " = ", value, ";");
1216 }
1217 } else if (output.variable != nullptr && output.variable->domain.is_float) {
1218 return absl::StrCat(output.name, " = ", objective_value, ";");
1219 } else {
1220 const int bound_size = output.bounds.size();
1221 std::string result =
1222 absl::StrCat(output.name, " = array", bound_size, "d(");
1223 for (int i = 0; i < bound_size; ++i) {
1224 if (output.bounds[i].max_value >= output.bounds[i].min_value) {
1225 absl::StrAppend(&result, output.bounds[i].min_value, "..",
1226 output.bounds[i].max_value, ", ");
1227 } else {
1228 result.append("{},");
1229 }
1230 }
1231 result.append("[");
1232 for (int i = 0; i < output.flat_variables.size(); ++i) {
1233 const int64_t value = value_func(output.flat_variables[i]);
1234 if (output.display_as_boolean) {
1235 result.append(value ? "true" : "false");
1236 } else {
1237 absl::StrAppend(&result, value);
1238 }
1239 if (i != output.flat_variables.size() - 1) {
1240 result.append(", ");
1241 }
1242 }
1243 result.append("]);");
1244 return result;
1245 }
1246 return "";
1247}
1248
1249std::string SolutionString(
1250 const fz::Model& model,
1251 const std::function<int64_t(fz::Variable*)>& value_func,
1252 double objective_value) {
1253 std::string solution_string;
1254 for (const auto& output_spec : model.output()) {
1255 solution_string.append(
1256 SolutionString(output_spec, value_func, objective_value));
1257 solution_string.append("\n");
1258 }
1259 return solution_string;
1260}
1261
1262void OutputFlatzincStats(const CpSolverResponse& response,
1263 SolverLogger* solution_logger) {
1264 SOLVER_LOG(solution_logger,
1265 "%%%mzn-stat: objective=", response.objective_value());
1266 SOLVER_LOG(solution_logger,
1267 "%%%mzn-stat: objectiveBound=", response.best_objective_bound());
1268 SOLVER_LOG(solution_logger,
1269 "%%%mzn-stat: boolVariables=", response.num_booleans());
1270 SOLVER_LOG(solution_logger,
1271 "%%%mzn-stat: failures=", response.num_conflicts());
1272 SOLVER_LOG(
1273 solution_logger, "%%%mzn-stat: propagations=",
1274 response.num_binary_propagations() + response.num_integer_propagations());
1275 SOLVER_LOG(solution_logger, "%%%mzn-stat: solveTime=", response.wall_time());
1276}
1277
1278} // namespace
1279
1282 const std::string& sat_params,
1283 SolverLogger* logger,
1284 SolverLogger* solution_logger) {
1285 CpModelProtoWithMapping m;
1286 m.proto.set_name(fz_model.name());
1287
1288 // The translation is easy, we create one variable per flatzinc variable,
1289 // plus eventually a bunch of constant variables that will be created
1290 // lazily.
1291 int num_variables = 0;
1292 for (fz::Variable* fz_var : fz_model.variables()) {
1293 if (!fz_var->active) continue;
1294 CHECK(!fz_var->domain.is_float)
1295 << "CP-SAT does not support float variables";
1296
1297 m.fz_var_to_index[fz_var] = num_variables++;
1298 IntegerVariableProto* var = m.proto.add_variables();
1299 var->set_name(fz_var->name);
1300 if (fz_var->domain.is_interval) {
1301 if (fz_var->domain.values.empty()) {
1302 // The CP-SAT solver checks that constraints cannot overflow during
1303 // their propagation. Because of that, we trim undefined variable
1304 // domains (i.e. int in minizinc) to something hopefully large enough.
1305 LOG_FIRST_N(WARNING, 1)
1306 << "Using flag --fz_int_max for unbounded integer variables.";
1307 LOG_FIRST_N(WARNING, 1)
1308 << " actual domain is [" << -absl::GetFlag(FLAGS_fz_int_max)
1309 << ".." << absl::GetFlag(FLAGS_fz_int_max) << "]";
1310 var->add_domain(-absl::GetFlag(FLAGS_fz_int_max));
1311 var->add_domain(absl::GetFlag(FLAGS_fz_int_max));
1312 } else {
1313 var->add_domain(fz_var->domain.values[0]);
1314 var->add_domain(fz_var->domain.values[1]);
1315 }
1316 } else {
1317 FillDomainInProto(Domain::FromValues(fz_var->domain.values), var);
1318 }
1319 }
1320
1321 // Translate the constraints.
1322 for (fz::Constraint* fz_ct : fz_model.constraints()) {
1323 if (fz_ct == nullptr || !fz_ct->active) continue;
1324 ConstraintProto* ct = m.proto.add_constraints();
1325 ct->set_name(fz_ct->type);
1326 if (absl::EndsWith(fz_ct->type, "_reif") ||
1327 absl::EndsWith(fz_ct->type, "_imp") || fz_ct->type == "array_bool_or" ||
1328 fz_ct->type == "array_bool_and") {
1329 m.FillReifOrImpliedConstraint(*fz_ct, ct);
1330 } else {
1331 m.FillConstraint(*fz_ct, ct);
1332 }
1333 }
1334
1335 // Fill the objective.
1336 if (fz_model.objective() != nullptr) {
1337 CpObjectiveProto* objective = m.proto.mutable_objective();
1338 if (fz_model.maximize()) {
1339 objective->set_scaling_factor(-1);
1340 objective->add_coeffs(-1);
1341 objective->add_vars(m.fz_var_to_index[fz_model.objective()]);
1342 } else {
1343 objective->add_coeffs(1);
1344 objective->add_vars(m.fz_var_to_index[fz_model.objective()]);
1345 }
1346 } else if (!fz_model.float_objective_variables().empty()) {
1347 FloatObjectiveProto* objective = m.proto.mutable_floating_point_objective();
1348 for (int i = 0; i < fz_model.float_objective_variables().size(); ++i) {
1349 objective->add_vars(
1350 m.fz_var_to_index[fz_model.float_objective_variables()[i]]);
1351 objective->add_coeffs(fz_model.float_objective_coefficients()[i]);
1352 }
1353 objective->set_offset(fz_model.float_objective_offset());
1354 objective->set_maximize(fz_model.maximize());
1355 }
1356
1357 // Fill the search order.
1358 m.TranslateSearchAnnotations(fz_model.search_annotations(), logger);
1359
1360 if (p.search_all_solutions && !m.proto.has_objective()) {
1361 // Enumerate all sat solutions.
1362 m.parameters.set_enumerate_all_solutions(true);
1363 }
1364
1365 m.parameters.set_log_search_progress(p.log_search_progress);
1366 m.parameters.set_log_to_stdout(!p.ortools_mode);
1367
1368 // Helps with challenge unit tests.
1369 m.parameters.set_max_domain_size_when_encoding_eq_neq_constraints(32);
1370
1371 // Computes the number of workers.
1372 int num_workers = 1;
1373 if (p.search_all_solutions && fz_model.objective() == nullptr) {
1374 if (p.number_of_threads > 1) {
1375 // We don't support enumerating all solution in parallel for a SAT
1376 // problem. But note that we do support it for an optimization problem
1377 // since the meaning of p.all_solutions is not the same in this case.
1378 SOLVER_LOG(logger,
1379 "Search for all solutions of a SAT problem in parallel is not "
1380 "supported. Switching back to sequential search.");
1381 }
1382 } else if (p.number_of_threads <= 0) {
1383 // TODO(user): Supports setting the number of workers to 0, which will
1384 // then query the number of cores available. This is complex now as we
1385 // need to still support the expected behabior (no flags -> 1 thread
1386 // fixed search, -f -> 1 thread free search).
1387 SOLVER_LOG(logger,
1388 "The number of search workers, is not specified. For better "
1389 "performances, please set the number of workers to 8, 16, or "
1390 "more depending on the number of cores of your computer.");
1391 } else {
1392 num_workers = p.number_of_threads;
1393 }
1394
1395 // Specifies single thread specific search modes.
1396 if (num_workers == 1 && !p.use_free_search) { // Fixed search.
1397 m.parameters.set_search_branching(SatParameters::FIXED_SEARCH);
1398 m.parameters.set_keep_all_feasible_solutions_in_presolve(true);
1399 } else if (num_workers == 1 && p.use_free_search) { // Free search.
1400 m.parameters.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
1401 if (!p.search_all_solutions && p.ortools_mode) {
1402 m.parameters.set_interleave_search(true);
1403 if (fz_model.objective() != nullptr) {
1404 m.parameters.add_subsolvers("default_lp");
1405 m.parameters.add_subsolvers(
1406 m.proto.search_strategy().empty() ? "quick_restart" : "fixed");
1407 m.parameters.add_subsolvers("core_or_no_lp"),
1408 m.parameters.add_subsolvers("max_lp");
1409
1410 } else {
1411 m.parameters.add_subsolvers("default_lp");
1412 m.parameters.add_subsolvers(
1413 m.proto.search_strategy().empty() ? "no_lp" : "fixed");
1414 m.parameters.add_subsolvers("less_encoding");
1415 m.parameters.add_subsolvers("max_lp");
1416 m.parameters.add_subsolvers("quick_restart");
1417 }
1418 }
1419 } else if (num_workers > 1 && num_workers < 8 && p.ortools_mode) {
1420 SOLVER_LOG(logger, "Bumping number of workers from ", num_workers, " to 8");
1421 num_workers = 8;
1422 }
1423 m.parameters.set_num_workers(num_workers);
1424
1425 // Time limit.
1426 if (p.max_time_in_seconds > 0) {
1427 m.parameters.set_max_time_in_seconds(p.max_time_in_seconds);
1428 }
1429
1430 // The order is important, we want the flag parameters to overwrite anything
1431 // set in m.parameters.
1432 sat::SatParameters flag_parameters;
1433 CHECK(google::protobuf::TextFormat::ParseFromString(sat_params,
1434 &flag_parameters))
1435 << sat_params;
1436 m.parameters.MergeFrom(flag_parameters);
1437
1438 // We only need an observer if 'p.display_all_solutions' or
1439 // 'p.search_all_solutions' are true.
1440 std::function<void(const CpSolverResponse&)> solution_observer = nullptr;
1442 solution_observer = [&fz_model, &m, &p,
1443 solution_logger](const CpSolverResponse& r) {
1444 const std::string solution_string = SolutionString(
1445 fz_model,
1446 [&m, &r](fz::Variable* v) {
1447 return r.solution(m.fz_var_to_index.at(v));
1448 },
1449 r.objective_value());
1450 SOLVER_LOG(solution_logger, solution_string);
1451 if (p.display_statistics) {
1452 OutputFlatzincStats(r, solution_logger);
1453 }
1454 SOLVER_LOG(solution_logger, "----------");
1455 };
1456 }
1457
1458 Model sat_model;
1459
1460 // Setup logging.
1461 // Note that we need to do that before we start calling the sat functions
1462 // below that might create a SolverLogger() themselves.
1463 sat_model.Register<SolverLogger>(logger);
1464
1465 sat_model.Add(NewSatParameters(m.parameters));
1466 if (solution_observer != nullptr) {
1467 sat_model.Add(NewFeasibleSolutionObserver(solution_observer));
1468 }
1469
1470 const CpSolverResponse response = SolveCpModel(m.proto, &sat_model);
1471
1472 // Check the returned solution with the fz model checker.
1473 if (response.status() == CpSolverStatus::FEASIBLE ||
1474 response.status() == CpSolverStatus::OPTIMAL) {
1475 CHECK(CheckSolution(
1476 fz_model,
1477 [&response, &m](fz::Variable* v) {
1478 return response.solution(m.fz_var_to_index.at(v));
1479 },
1480 logger));
1481 }
1482
1483 // Output the solution in the flatzinc official format.
1484 if (p.ortools_mode) {
1485 if (response.status() == CpSolverStatus::FEASIBLE ||
1486 response.status() == CpSolverStatus::OPTIMAL) {
1488 // Already printed otherwise.
1489 const std::string solution_string = SolutionString(
1490 fz_model,
1491 [&response, &m](fz::Variable* v) {
1492 return response.solution(m.fz_var_to_index.at(v));
1493 },
1494 response.objective_value());
1495 SOLVER_LOG(solution_logger, solution_string);
1496 SOLVER_LOG(solution_logger, "----------");
1497 }
1498 if (response.status() == CpSolverStatus::OPTIMAL) {
1499 SOLVER_LOG(solution_logger, "==========");
1500 }
1501 } else if (response.status() == CpSolverStatus::INFEASIBLE) {
1502 SOLVER_LOG(solution_logger, "=====UNSATISFIABLE=====");
1503 } else if (response.status() == CpSolverStatus::MODEL_INVALID) {
1504 const std::string error_message = ValidateCpModel(m.proto);
1505 VLOG(1) << "%% Error message = '" << error_message << "'";
1506 if (absl::StrContains(error_message, "overflow")) {
1507 SOLVER_LOG(solution_logger, "=====OVERFLOW=====");
1508 } else {
1509 SOLVER_LOG(solution_logger, "=====MODEL INVALID=====");
1510 }
1511 } else {
1512 SOLVER_LOG(solution_logger, "%% TIMEOUT");
1513 }
1514 if (p.display_statistics) {
1515 OutputFlatzincStats(response, solution_logger);
1516 }
1517 }
1518}
1519
1520} // namespace sat
1521} // namespace operations_research
IntegerValue y
IntegerValue size
static Domain FromValues(std::vector< int64_t > values)
Domain AdditionWith(const Domain &domain) const
const std::vector< double > & float_objective_coefficients() const
Definition model.h:393
const std::vector< Variable * > & float_objective_variables() const
Definition model.h:390
const std::vector< Constraint * > & constraints() const
Definition model.h:373
const std::vector< Variable * > & variables() const
--— Accessors and mutators --—
Definition model.h:372
double float_objective_offset() const
Definition model.h:396
const std::string & name() const
Definition model.h:410
const std::vector< Annotation > & search_annotations() const
Definition model.h:374
Variable * objective() const
Definition model.h:389
T Add(std::function< T(Model *)> f)
Definition model.h:89
void Register(T *non_owned_class)
Definition model.h:179
int64_t b
Definition table.cc:45
int64_t a
Definition table.cc:44
Block * next
absl::flat_hash_map< int, int > var_to_lit_implies_greater_than_zero
SatParameters parameters
absl::flat_hash_map< std::tuple< int, int64_t, int, int64_t, int >, int > interval_key_to_index
absl::flat_hash_map< int64_t, int > constant_value_to_index
absl::flat_hash_map< fz::Variable *, int > fz_var_to_index
Mapping from flatzinc variables to CpModelProto variables.
ABSL_FLAG(int64_t, fz_int_max, int64_t{1}<< 50, "Default max value for unbounded integer variables.")
CpModelProto proto
The output proto.
int interval_index
const Constraint * ct
int64_t value
IntVar * var
GRBmodel * model
int literal
int arc
int index
ReverseView< Container > reversed_view(const Container &c)
std::function< SatParameters(Model *)> NewSatParameters(const std::string &params)
std::string ValidateCpModel(const CpModelProto &model, bool after_presolve)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Serializes a Domain into the domain field of a proto.
CpSolverResponse SolveCpModel(const CpModelProto &model_proto, Model *model)
void SolveFzWithCpModelProto(const fz::Model &fz_model, const fz::FlatzincSatParameters &p, const std::string &sat_params, SolverLogger *logger, SolverLogger *solution_logger)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Reads a Domain from the domain field of a proto.
std::function< void(Model *)> NewFeasibleSolutionObserver(const std::function< void(const CpSolverResponse &response)> &callback)
int NegatedRef(int ref)
Small utility functions to deal with negative variable/literal references.
std::vector< int64_t > AllValuesInDomain(const ProtoWithDomain &proto)
In SWIG mode, we don't want anything besides these top-level includes.
int64_t weight
Definition pack.cc:510
const Variable x
Definition qp_tests.cc:127
int64_t demand
Definition resource.cc:126
IntervalVar * interval
Definition resource.cc:101
int head
int tail
int64_t start
double objective_value
The value objective_vector^T * (solution - center_point).
#define SOLVER_LOG(logger,...)
Definition logging.h:109