OR-Tools  9.3
cp_model_fz_solver.cc
Go to the documentation of this file.
1// Copyright 2010-2021 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 <atomic>
17#include <cmath>
18#include <cstdint>
19#include <limits>
20#include <tuple>
21
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"
30#include "ortools/base/timer.h"
34#include "ortools/sat/cp_model.pb.h"
41#include "ortools/sat/integer.h"
44#include "ortools/sat/model.h"
47#include "ortools/sat/table.h"
49
50ABSL_FLAG(int64_t, fz_int_max, int64_t{1} << 50,
51 "Default max value for unbounded integer variables.");
52
53namespace operations_research {
54namespace sat {
55
56namespace {
57
58static const int kNoVar = std::numeric_limits<int>::min();
59
60struct VarOrValue {
61 int var = kNoVar;
62 int64_t value = 0;
63};
64
65// Returns the true/false literal corresponding to a CpModelProto variable.
66int TrueLiteral(int var) { return var; }
67int FalseLiteral(int var) { return -var - 1; }
68int NegatedCpModelVariable(int var) { return -var - 1; }
69
70// Helper class to convert a flatzinc model to a CpModelProto.
71struct CpModelProtoWithMapping {
72 // Returns a constant CpModelProto variable created on-demand.
73 int LookupConstant(int64_t value);
74
75 // Convert a flatzinc argument to a variable or a list of variable.
76 // Note that we always encode a constant argument with a constant variable.
77 int LookupVar(const fz::Argument& argument);
78 LinearExpressionProto LookupExpr(const fz::Argument& argument,
79 bool negate = false);
80 LinearExpressionProto LookupExprAt(const fz::Argument& argument, int pos,
81 bool negate = false);
82 std::vector<int> LookupVars(const fz::Argument& argument);
83 std::vector<VarOrValue> LookupVarsOrValues(const fz::Argument& argument);
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 std::vector<int> CreateIntervals(const std::vector<int>& starts,
89 const std::vector<VarOrValue>& sizes);
90
91 // Create and return the index of the optional IntervalConstraint
92 // corresponding to the flatzinc "interval" specified by a start var, the
93 // size_var, and the Boolean opt_var. This method will cache intervals with
94 // the key <start, size, opt_var>. If opt_var == kNoVar, the interval will not
95 // be optional.
96 int GetOrCreateOptionalInterval(int start_var, VarOrValue size, int opt_var);
97
98 // Adds a constraint to the model, add the enforcement literal if it is
99 // different from kNoVar, and returns a ptr to the ConstraintProto.
100 ConstraintProto* AddEnforcedConstraint(int literal);
101
102 // Helpers to fill a ConstraintProto.
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);
111
112 // Translates the flatzinc search annotations into the CpModelProto
113 // search_order field.
114 void TranslateSearchAnnotations(
115 const std::vector<fz::Annotation>& search_annotations);
116
117 // The output proto.
118 CpModelProto proto;
119 SatParameters parameters;
120
121 // Mapping from flatzinc variables to CpModelProto variables.
122 absl::flat_hash_map<fz::Variable*, int> fz_var_to_index;
123 absl::flat_hash_map<int64_t, int> constant_value_to_index;
124 absl::flat_hash_map<std::tuple<int, int, int>, int>
126 absl::flat_hash_map<std::tuple<int, int64_t, int>, int>
128};
129
130int CpModelProtoWithMapping::LookupConstant(int64_t value) {
131 if (constant_value_to_index.contains(value)) {
133 }
134
135 // Create the constant on the fly.
136 const int index = proto.variables_size();
137 IntegerVariableProto* var_proto = proto.add_variables();
138 var_proto->add_domain(value);
139 var_proto->add_domain(value);
141 return index;
142}
143
144int CpModelProtoWithMapping::LookupVar(const fz::Argument& argument) {
145 if (argument.HasOneValue()) return LookupConstant(argument.Value());
146 CHECK_EQ(argument.type, fz::Argument::VAR_REF);
147 return fz_var_to_index[argument.Var()];
148}
149
150LinearExpressionProto CpModelProtoWithMapping::LookupExpr(
151 const fz::Argument& argument, bool negate) {
152 LinearExpressionProto expr;
153 if (argument.HasOneValue()) {
154 const int64_t value = argument.Value();
155 expr.set_offset(negate ? -value : value);
156 } else {
157 expr.add_vars(LookupVar(argument));
158 expr.add_coeffs(negate ? -1 : 1);
159 }
160 return expr;
161}
162
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);
168 expr.set_offset(negate ? -value : value);
169 } else {
170 expr.add_vars(fz_var_to_index[argument.VarAt(pos)]);
171 expr.add_coeffs(negate ? -1 : 1);
172 }
173 return expr;
174}
175
176std::vector<int> CpModelProtoWithMapping::LookupVars(
177 const fz::Argument& argument) {
178 std::vector<int> result;
179 if (argument.type == fz::Argument::VOID_ARGUMENT) return result;
180 if (argument.type == fz::Argument::INT_LIST) {
181 for (int64_t value : argument.values) {
182 result.push_back(LookupConstant(value));
183 }
184 } else if (argument.type == fz::Argument::INT_VALUE) {
185 result.push_back(LookupConstant(argument.Value()));
186 } else {
188 for (fz::Variable* var : argument.variables) {
189 CHECK(var != nullptr);
190 result.push_back(fz_var_to_index[var]);
191 }
192 }
193 return result;
194}
195
196std::vector<VarOrValue> CpModelProtoWithMapping::LookupVarsOrValues(
197 const fz::Argument& argument) {
198 std::vector<VarOrValue> result;
199 const int no_var = kNoVar;
200 if (argument.type == fz::Argument::VOID_ARGUMENT) return result;
201 if (argument.type == fz::Argument::INT_LIST) {
202 for (int64_t value : argument.values) {
203 result.push_back({no_var, value});
204 }
205 } else if (argument.type == fz::Argument::INT_VALUE) {
206 result.push_back({no_var, argument.Value()});
207 } else {
209 for (fz::Variable* var : argument.variables) {
210 CHECK(var != nullptr);
211 if (var->domain.HasOneValue()) {
212 result.push_back({no_var, var->domain.Value()});
213 } else {
214 result.push_back({fz_var_to_index[var], 0});
215 }
216 }
217 }
218 return result;
219}
220
221ConstraintProto* CpModelProtoWithMapping::AddEnforcedConstraint(int literal) {
222 ConstraintProto* result = proto.add_constraints();
223 if (literal != kNoVar) {
224 result->add_enforcement_literal(literal);
225 }
226 return result;
227}
228
229int CpModelProtoWithMapping::GetOrCreateOptionalInterval(int start_var,
230 VarOrValue size,
231 int opt_var) {
232 const int interval_index = proto.constraints_size();
233 if (size.var == kNoVar) { // Size is fixed.
234 const std::tuple<int, int64_t, int> key =
235 std::make_tuple(start_var, size.value, opt_var);
236 const auto [it, inserted] =
237 start_fixed_size_opt_tuple_to_interval.insert({key, interval_index});
238 if (!inserted) {
239 return it->second;
240 }
241
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);
249
250 return interval_index;
251 } else { // Size is variable.
252 const std::tuple<int, int, int> key =
253 std::make_tuple(start_var, size.var, opt_var);
254 const auto [it, inserted] =
255 start_size_opt_tuple_to_interval.insert({key, interval_index});
256 if (!inserted) {
257 return it->second;
258 }
259
260 const int end_var = proto.variables_size();
262 ReadDomainFromProto(proto.variables(start_var))
263 .AdditionWith(ReadDomainFromProto(proto.variables(size.var))),
264 proto.add_variables());
265
266 // Create the interval.
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);
274
275 // Add the linear constraint (after the interval constraint as we have
276 // stored its index).
277 auto* lin = AddEnforcedConstraint(opt_var)->mutable_linear();
278 lin->add_vars(start_var);
279 lin->add_coeffs(1);
280 lin->add_vars(size.var);
281 lin->add_coeffs(1);
282 lin->add_vars(end_var);
283 lin->add_coeffs(-1);
284 lin->add_domain(0);
285 lin->add_domain(0);
286
287 return interval_index;
288 }
289}
290
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) {
295 intervals.push_back(
296 GetOrCreateOptionalInterval(starts[i], sizes[i], kNoVar));
297 }
298 return intervals;
299}
300
301void CpModelProtoWithMapping::FillAMinusBInDomain(
302 const std::vector<int64_t>& domain, const fz::Constraint& fz_ct,
303 ConstraintProto* ct) {
304 auto* arg = ct->mutable_linear();
305 if (fz_ct.arguments[1].type == fz::Argument::INT_VALUE) {
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) {
309 if (domain_bound == std::numeric_limits<int64_t>::min() ||
310 domain_bound == std::numeric_limits<int64_t>::max()) {
311 arg->add_domain(domain_bound);
312 } else {
313 arg->add_domain(domain_bound + value);
314 }
315 }
316 arg->add_vars(var_a);
317 arg->add_coeffs(1);
318 } else if (fz_ct.arguments[0].type == fz::Argument::INT_VALUE) {
319 const int64_t value = fz_ct.arguments[0].Value();
320 const int var_b = LookupVar(fz_ct.arguments[1]);
321 for (int64_t domain_bound : gtl::reversed_view(domain)) {
322 if (domain_bound == std::numeric_limits<int64_t>::min()) {
323 arg->add_domain(std::numeric_limits<int64_t>::max());
324 } else if (domain_bound == std::numeric_limits<int64_t>::max()) {
325 arg->add_domain(std::numeric_limits<int64_t>::min());
326 } else {
327 arg->add_domain(value - domain_bound);
328 }
329 }
330 arg->add_vars(var_b);
331 arg->add_coeffs(1);
332 } else {
333 for (const int64_t domain_bound : domain) arg->add_domain(domain_bound);
334 arg->add_vars(LookupVar(fz_ct.arguments[0]));
335 arg->add_coeffs(1);
336 arg->add_vars(LookupVar(fz_ct.arguments[1]));
337 arg->add_coeffs(-1);
338 }
339}
340
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]);
350 }
351}
352
353void CpModelProtoWithMapping::FillConstraint(const fz::Constraint& fz_ct,
354 ConstraintProto* ct) {
355 if (fz_ct.type == "false_constraint") {
356 // An empty clause is always false.
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));
362 }
363 for (const int var : LookupVars(fz_ct.arguments[1])) {
364 arg->add_literals(FalseLiteral(var));
365 }
366 } else if (fz_ct.type == "bool_xor") {
367 // This is not the same semantics as the array_bool_xor as this constraint
368 // is actually a fully reified xor(a, b) <==> x.
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]);
372
373 // not(x) => a == b
374 ct->add_enforcement_literal(NegatedRef(x));
375 auto* const refute = ct->mutable_linear();
376 refute->add_vars(a);
377 refute->add_coeffs(1);
378 refute->add_vars(b);
379 refute->add_coeffs(-1);
380 refute->add_domain(0);
381 refute->add_domain(0);
382
383 // x => a + b == 1
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));
395 }
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));
400 }
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));
405 }
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));
410 }
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));
415 }
416 } else if (fz_ct.type == "bool_le" || fz_ct.type == "int_le") {
417 FillAMinusBInDomain({std::numeric_limits<int64_t>::min(), 0}, fz_ct, ct);
418 } else if (fz_ct.type == "bool_ge" || fz_ct.type == "int_ge") {
419 FillAMinusBInDomain({0, std::numeric_limits<int64_t>::max()}, fz_ct, ct);
420 } else if (fz_ct.type == "bool_lt" || fz_ct.type == "int_lt") {
421 FillAMinusBInDomain({std::numeric_limits<int64_t>::min(), -1}, fz_ct, ct);
422 } else if (fz_ct.type == "bool_gt" || fz_ct.type == "int_gt") {
423 FillAMinusBInDomain({1, std::numeric_limits<int64_t>::max()}, fz_ct, ct);
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]));
430 arg->add_coeffs(1);
431 arg->add_vars(LookupVar(fz_ct.arguments[1]));
432 arg->add_coeffs(1);
433 arg->add_domain(1);
434 arg->add_domain(1);
435 } else if (fz_ct.type == "int_ne") {
436 FillAMinusBInDomain({std::numeric_limits<int64_t>::min(), -1, 1,
438 fz_ct, ct);
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]);
448 }
449 if (fz_ct.arguments[2].IsVariable()) {
450 arg->add_vars(LookupVar(fz_ct.arguments[2]));
451 arg->add_coeffs(-1);
452 arg->add_domain(0);
453 arg->add_domain(0);
454 } else {
455 const int64_t v = fz_ct.arguments[2].Value();
456 arg->add_domain(v);
457 arg->add_domain(v);
458 }
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(
462 {std::numeric_limits<int64_t>::min(), rhs}, fz_ct, ct);
463 } else if (fz_ct.type == "int_lin_lt") {
464 const int64_t rhs = fz_ct.arguments[2].values[0];
465 FillLinearConstraintWithGivenDomain(
466 {std::numeric_limits<int64_t>::min(), rhs - 1}, fz_ct, ct);
467 } else if (fz_ct.type == "int_lin_ge") {
468 const int64_t rhs = fz_ct.arguments[2].values[0];
469 FillLinearConstraintWithGivenDomain(
470 {rhs, std::numeric_limits<int64_t>::max()}, fz_ct, ct);
471 } else if (fz_ct.type == "int_lin_gt") {
472 const int64_t rhs = fz_ct.arguments[2].values[0];
473 FillLinearConstraintWithGivenDomain(
474 {rhs + 1, std::numeric_limits<int64_t>::max()}, fz_ct, ct);
475 } else if (fz_ct.type == "int_lin_ne") {
476 const int64_t rhs = fz_ct.arguments[2].values[0];
477 FillLinearConstraintWithGivenDomain(
478 {std::numeric_limits<int64_t>::min(), rhs - 1, rhs + 1,
480 fz_ct, ct);
481 } else if (fz_ct.type == "set_in") {
482 auto* arg = ct->mutable_linear();
483 arg->add_vars(LookupVar(fz_ct.arguments[0]));
484 arg->add_coeffs(1);
485 if (fz_ct.arguments[1].type == fz::Argument::INT_LIST) {
486 FillDomainInProto(Domain::FromValues(std::vector<int64_t>{
487 fz_ct.arguments[1].values.begin(),
488 fz_ct.arguments[1].values.end()}),
489 arg);
490 } else if (fz_ct.arguments[1].type == fz::Argument::INT_INTERVAL) {
492 Domain(fz_ct.arguments[1].values[0], fz_ct.arguments[1].values[1]),
493 arg);
494 } else {
495 LOG(FATAL) << "Wrong format";
496 }
497 } else if (fz_ct.type == "set_in_negated") {
498 auto* arg = ct->mutable_linear();
499 arg->add_vars(LookupVar(fz_ct.arguments[0]));
500 arg->add_coeffs(1);
501 if (fz_ct.arguments[1].type == fz::Argument::INT_LIST) {
504 std::vector<int64_t>{fz_ct.arguments[1].values.begin(),
505 fz_ct.arguments[1].values.end()})
506 .Complement(),
507 arg);
508 } else if (fz_ct.arguments[1].type == fz::Argument::INT_INTERVAL) {
510 Domain(fz_ct.arguments[1].values[0], fz_ct.arguments[1].values[1])
511 .Complement(),
512 arg);
513 } else {
514 LOG(FATAL) << "Wrong format";
515 }
516 } else if (fz_ct.type == "int_min") {
517 auto* arg = ct->mutable_lin_max();
518 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0], /*negate=*/true);
519 *arg->add_exprs() = LookupExpr(fz_ct.arguments[1], /*negate=*/true);
520 *arg->mutable_target() = LookupExpr(fz_ct.arguments[2], /*negate=*/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], /*negate=*/true);
524 for (int i = 0; i < fz_ct.arguments[1].Size(); ++i) {
525 *arg->add_exprs() = LookupExprAt(fz_ct.arguments[1], i, /*negate=*/true);
526 }
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);
537 }
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], /*negate=*/true);
547 *arg->mutable_target() = LookupExpr(fz_ct.arguments[1]);
548 } else if (fz_ct.type == "int_plus") {
549 auto* arg = ct->mutable_linear();
550 FillDomainInProto(Domain(0, 0), arg);
551 arg->add_vars(LookupVar(fz_ct.arguments[0]));
552 arg->add_coeffs(1);
553 arg->add_vars(LookupVar(fz_ct.arguments[1]));
554 arg->add_coeffs(1);
555 arg->add_vars(LookupVar(fz_ct.arguments[2]));
556 arg->add_coeffs(-1);
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") {
572 if (fz_ct.arguments[0].type == fz::Argument::VAR_REF ||
573 fz_ct.arguments[0].type == fz::Argument::INT_VALUE) {
574 auto* arg = ct->mutable_element();
575 arg->set_index(LookupVar(fz_ct.arguments[0]));
576 arg->set_target(LookupVar(fz_ct.arguments[2]));
577
578 if (!absl::EndsWith(fz_ct.type, "_nonshifted")) {
579 // Add a dummy variable at position zero because flatzinc index start
580 // at 1.
581 // TODO(user): Make sure that zero is not in the index domain...
582 arg->add_vars(LookupConstant(0));
583 }
584 for (const int var : LookupVars(fz_ct.arguments[1])) arg->add_vars(var);
585 } else {
586 // Special case added by the presolve or in flatzinc. We encode this
587 // as a table constraint.
588 CHECK(!absl::EndsWith(fz_ct.type, "_nonshifted"));
589 auto* arg = ct->mutable_table();
590
591 // the constraint is:
592 // values[coeff1 * vars[0] + coeff2 * vars[1] + offset] == target.
593 for (const int var : LookupVars(fz_ct.arguments[0])) arg->add_vars(var);
594 arg->add_vars(LookupVar(fz_ct.arguments[2])); // the target
595
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;
600
601 for (const int64_t a : AllValuesInDomain(proto.variables(arg->vars(0)))) {
602 for (const int64_t b :
603 AllValuesInDomain(proto.variables(arg->vars(1)))) {
604 const int index = coeff1 * a + coeff2 * b + offset;
605 CHECK_GE(index, 0);
606 CHECK_LT(index, values.size());
607 arg->add_values(a);
608 arg->add_values(b);
609 arg->add_values(values[index]);
610 }
611 }
612 }
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);
621
622 int count = 0;
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; // 0 is a failing state.
630 arg->add_transition_tail(i);
631 arg->add_transition_label(j);
632 arg->add_transition_head(next);
633 }
634 }
635
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]);
640 break;
641 }
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);
646 }
647 break;
648 }
650 for (const int v : fz_ct.arguments[5].values) {
651 arg->add_final_states(v);
652 }
653 break;
654 }
655 default: {
656 LOG(FATAL) << "Wrong constraint " << fz_ct.DebugString();
657 }
658 }
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);
663 }
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());
669
670 const int64_t max_index = min_index + size - 1;
671 // The arc-based mutable circuit.
672 auto* circuit_arg = ct->mutable_circuit();
673
674 // We fully encode all variables so we can use the literal based circuit.
675 // TODO(user): avoid fully encoding more than once?
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])) {
679 Domain domain = ReadDomainFromProto(proto.variables(var));
680
681 // Restrict the domain of var to [min_index, max_index]
682 domain = domain.IntersectionWith(Domain(min_index, max_index));
683 if (is_circuit) {
684 // We simply make sure that the variable cannot take the value index.
685 domain = domain.IntersectionWith(Domain::FromIntervals(
688 }
689 FillDomainInProto(domain, proto.mutable_variables(var));
690
691 for (const ClosedInterval interval : domain.intervals()) {
692 for (int64_t value = interval.start; value <= interval.end; ++value) {
693 // Create one Boolean variable for this arc.
694 const int literal = proto.variables_size();
695 {
696 auto* new_var = proto.add_variables();
697 new_var->add_domain(0);
698 new_var->add_domain(1);
699 }
700
701 // Add the arc.
702 circuit_arg->add_tails(index);
703 circuit_arg->add_heads(value);
704 circuit_arg->add_literals(literal);
705
706 // literal => var == value.
707 {
708 auto* lin = AddEnforcedConstraint(literal)->mutable_linear();
709 lin->add_coeffs(1);
710 lin->add_vars(var);
711 lin->add_domain(value);
712 lin->add_domain(value);
713 }
714
715 // not(literal) => var != value
716 {
717 auto* lin =
718 AddEnforcedConstraint(NegatedRef(literal))->mutable_linear();
719 lin->add_coeffs(1);
720 lin->add_vars(var);
721 lin->add_domain(std::numeric_limits<int64_t>::min());
722 lin->add_domain(value - 1);
723 lin->add_domain(value + 1);
724 lin->add_domain(std::numeric_limits<int64_t>::max());
725 }
726 }
727 }
728
729 ++index;
730 }
731 } else if (fz_ct.type == "ortools_inverse") {
732 auto* arg = ct->mutable_inverse();
733
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();
738
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;
743
744 // Any convention that maps the "fixed values" to the one of the inverse and
745 // back works. We decided to follow this one:
746 // There are 3 cases:
747 // (A) base_direct == base_inverse, we fill the arrays
748 // direct = [0, .., base_direct - 1] U [direct_vars]
749 // inverse = [0, .., base_direct - 1] U [inverse_vars]
750 // (B) base_direct == base_inverse + offset (> 0), we fill the arrays
751 // direct = [0, .., base_inverse - 1] U
752 // [end_inverse, .., end_inverse + offset - 1] U
753 // [direct_vars]
754 // inverse = [0, .., base_inverse - 1] U
755 // [inverse_vars] U
756 // [base_inverse, .., base_base_inverse + offset - 1]
757 // (C): base_inverse == base_direct + offset (> 0), we fill the arrays
758 // direct = [0, .., base_direct - 1] U
759 // [direct_vars] U
760 // [base_direct, .., base_direct + offset - 1]
761 // inverse [0, .., base_direct - 1] U
762 // [end_direct, .., end_direct + offset - 1] U
763 // [inverse_vars]
764 const int arity = std::max(base_inverse, base_direct) + num_variables;
765 for (int i = 0; i < arity; ++i) {
766 // Fill the direct array.
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));
772 }
773 } else if (i >= base_direct && i < end_direct) {
774 arg->add_f_direct(direct_variables[i - base_direct]);
775 } else {
776 arg->add_f_direct(LookupConstant(i - num_variables));
777 }
778
779 // Fill the inverse array.
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));
785 }
786 } else if (i >= base_inverse && i < end_inverse) {
787 arg->add_f_inverse(inverse_variables[i - base_inverse]);
788 } else {
789 arg->add_f_inverse(LookupConstant(i - num_variables));
790 }
791 }
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]);
798
799 auto* arg = ct->mutable_cumulative();
800 if (fz_ct.arguments[3].HasOneValue()) {
801 arg->mutable_capacity()->set_offset(fz_ct.arguments[3].Value());
802 } else {
803 arg->mutable_capacity()->add_vars(LookupVar(fz_ct.arguments[3]));
804 arg->mutable_capacity()->add_coeffs(1);
805 }
806 for (int i = 0; i < starts.size(); ++i) {
807 // Special case for a 0-1 demand, we mark the interval as optional
808 // instead and fix the demand to 1.
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) {
814 arg->add_intervals(
815 GetOrCreateOptionalInterval(starts[i], sizes[i], demands[i].var));
816 arg->add_demands()->set_offset(1);
817 } else {
818 arg->add_intervals(
819 GetOrCreateOptionalInterval(starts[i], sizes[i], kNoVar));
820 LinearExpressionProto* demand = arg->add_demands();
821 if (demands[i].var == kNoVar) {
822 demand->set_offset(demands[i].value);
823 } else {
824 demand->add_vars(demands[i].var);
825 demand->add_coeffs(1);
826 }
827 }
828 }
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]);
840 }
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") {
845 // Note that we leave ct empty here (with just the name set).
846 // We simply do a linear encoding of this constraint.
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]);
849
850 // Flow conservation constraints.
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;
858 if (tail == head) continue;
859
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);
864 }
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]);
872 }
873 }
874
875 if (has_cost) {
876 auto* arg = proto.add_constraints()->mutable_linear();
877 arg->add_domain(0);
878 arg->add_domain(0);
879 for (int arc = 0; arc < num_arcs; arc++) {
880 const int64_t weight = fz_ct.arguments[2].values[arc];
881 if (weight != 0) {
882 arg->add_vars(flow[arc]);
883 arg->add_coeffs(weight);
884 }
885 }
886 arg->add_vars(LookupVar(fz_ct.arguments[4]));
887 arg->add_coeffs(-1);
888 }
889 } else {
890 LOG(FATAL) << " Not supported " << fz_ct.type;
891 }
892}
893
894void CpModelProtoWithMapping::FillReifOrImpliedConstraint(
895 const fz::Constraint& fz_ct, ConstraintProto* ct) {
896 // Start by adding a non-reified version of the same constraint.
897 std::string simplified_type;
898 if (absl::EndsWith(fz_ct.type, "_reif")) {
899 // Remove _reif.
900 simplified_type = fz_ct.type.substr(0, fz_ct.type.size() - 5);
901 } else if (absl::EndsWith(fz_ct.type, "_imp")) {
902 // Remove _imp.
903 simplified_type = fz_ct.type.substr(0, fz_ct.type.size() - 4);
904 } else {
905 // Keep name as it is an implicit reified constraint.
906 simplified_type = fz_ct.type;
907 }
908
909 // We need a copy to be able to change the type of the constraint.
910 fz::Constraint copy = fz_ct;
911 copy.type = simplified_type;
912
913 // Create the CP-SAT constraint.
914 FillConstraint(copy, ct);
915
916 // In case of reified constraints, the type of the opposite constraint.
917 std::string negated_type;
918
919 // Fill enforcement_literal and set copy.type to the negated constraint.
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";
965 } else {
966 LOG(FATAL) << "Unsupported " << simplified_type;
967 }
968
969 // One way implication. We can stop here.
970 if (absl::EndsWith(fz_ct.type, "_imp")) return;
971
972 // Add the other side of the reification because CpModelProto only support
973 // half reification.
974 ConstraintProto* negated_ct = proto.add_constraints();
975 negated_ct->set_name(fz_ct.type + " (negated)");
976 negated_ct->add_enforcement_literal(
977 sat::NegatedRef(ct->enforcement_literal(0)));
978 copy.type = negated_type;
979 FillConstraint(copy, negated_ct);
980}
981
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) {
986 fz::FlattenAnnotations(annotation, &flat_annotations);
987 }
988
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);
995
996 DecisionStrategyProto* strategy = proto.add_search_strategy();
997 for (fz::Variable* v : vars) {
998 strategy->add_variables(fz_var_to_index.at(v));
999 }
1000
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);
1017 } else {
1018 LOG(FATAL) << "Unsupported order: " << choose.id;
1019 }
1020
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);
1037 } else {
1038 LOG(FATAL) << "Unsupported select: " << select.id;
1039 }
1040 }
1041 }
1042}
1043
1044// The format is fixed in the flatzinc specification.
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",
1052 ";");
1053 } else {
1054 return absl::StrCat(output.name, " = ", value, ";");
1055 }
1056 } else {
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, ", ");
1064 } else {
1065 result.append("{},");
1066 }
1067 }
1068 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");
1073 } else {
1074 absl::StrAppend(&result, value);
1075 }
1076 if (i != output.flat_variables.size() - 1) {
1077 result.append(", ");
1078 }
1079 }
1080 result.append("]);");
1081 return result;
1082 }
1083 return "";
1084}
1085
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");
1093 }
1094 return solution_string;
1095}
1096
1097void OutputFlatzincStats(const CpSolverResponse& response,
1098 SolverLogger* solution_logger) {
1099 SOLVER_LOG(solution_logger,
1100 "%%%mzn-stat: objective=", response.objective_value());
1101 SOLVER_LOG(solution_logger,
1102 "%%%mzn-stat: objectiveBound=", response.best_objective_bound());
1103 SOLVER_LOG(solution_logger,
1104 "%%%mzn-stat: boolVariables=", response.num_booleans());
1105 SOLVER_LOG(solution_logger,
1106 "%%%mzn-stat: failures=", response.num_conflicts());
1107 SOLVER_LOG(
1108 solution_logger, "%%%mzn-stat: propagations=",
1109 response.num_binary_propagations() + response.num_integer_propagations());
1110 SOLVER_LOG(solution_logger, "%%%mzn-stat: solveTime=", response.wall_time());
1111}
1112
1113} // namespace
1114
1117 const std::string& sat_params,
1118 SolverLogger* logger,
1119 SolverLogger* solution_logger) {
1120 CpModelProtoWithMapping m;
1121 m.proto.set_name(fz_model.name());
1122
1123 // The translation is easy, we create one variable per flatzinc variable,
1124 // plus eventually a bunch of constant variables that will be created
1125 // lazily.
1126 int num_variables = 0;
1127 for (fz::Variable* fz_var : fz_model.variables()) {
1128 if (!fz_var->active) continue;
1129 CHECK(!fz_var->domain.is_float)
1130 << "CP-SAT does not support float variables";
1131
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()) {
1137 // The CP-SAT solver checks that constraints cannot overflow during
1138 // their propagation. Because of that, we trim undefined variable
1139 // domains (i.e. int in minizinc) to something hopefully large enough.
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));
1147 } else {
1148 var->add_domain(fz_var->domain.values[0]);
1149 var->add_domain(fz_var->domain.values[1]);
1150 }
1151 } else {
1152 FillDomainInProto(Domain::FromValues(fz_var->domain.values), var);
1153 }
1154 }
1155
1156 // Translate the constraints.
1157 for (fz::Constraint* fz_ct : fz_model.constraints()) {
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);
1165 } else {
1166 m.FillConstraint(*fz_ct, ct);
1167 }
1168 }
1169
1170 // Fill the objective.
1171 if (fz_model.objective() != nullptr) {
1172 CpObjectiveProto* objective = m.proto.mutable_objective();
1173 objective->add_coeffs(1);
1174 if (fz_model.maximize()) {
1175 objective->set_scaling_factor(-1);
1176 objective->add_vars(
1177 NegatedCpModelVariable(m.fz_var_to_index[fz_model.objective()]));
1178 } else {
1179 objective->add_vars(m.fz_var_to_index[fz_model.objective()]);
1180 }
1181 }
1182
1183 // Fill the search order.
1184 m.TranslateSearchAnnotations(fz_model.search_annotations());
1185
1186 if (p.display_all_solutions && !m.proto.has_objective()) {
1187 // Enumerate all sat solutions.
1188 m.parameters.set_enumerate_all_solutions(true);
1189 }
1190
1191 m.parameters.set_log_search_progress(p.log_search_progress);
1192
1193 // Helps with challenge unit tests.
1194 m.parameters.set_max_domain_size_when_encoding_eq_neq_constraints(32);
1195
1196 // Computes the number of workers.
1197 int num_workers = 1;
1198 if (p.display_all_solutions && fz_model.objective() == nullptr) {
1199 if (p.number_of_threads > 1) {
1200 // We don't support enumerating all solution in parallel for a SAT
1201 // problem. But note that we do support it for an optimization problem
1202 // since the meaning of p.all_solutions is not the same in this case.
1203 SOLVER_LOG(logger,
1204 "Search for all solutions of a SAT problem in parallel is not "
1205 "supported. Switching back to sequential search.");
1206 }
1207 } else if (p.number_of_threads <= 0) {
1208 // TODO(user): Supports setting the number of workers to 0, which will
1209 // then query the number of cores available. This is complex now as we
1210 // need to still support the expected behabior (no flags -> 1 thread
1211 // fixed search, -f -> 1 thread free search).
1212 SOLVER_LOG(logger,
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.");
1216 } else {
1217 num_workers = p.number_of_threads;
1218 }
1219 m.parameters.set_num_search_workers(num_workers);
1220
1221 // Specifies single thread specific search modes.
1222 if (num_workers == 1) {
1223 if (p.use_free_search) {
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);
1227 } else {
1228 m.parameters.set_search_branching(SatParameters::FIXED_SEARCH);
1229 m.parameters.set_keep_all_feasible_solutions_in_presolve(true);
1230 }
1231 }
1232
1233 // Time limit.
1234 if (p.max_time_in_seconds > 0) {
1235 m.parameters.set_max_time_in_seconds(p.max_time_in_seconds);
1236 }
1237
1238 // The order is important, we want the flag parameters to overwrite anything
1239 // set in m.parameters.
1240 sat::SatParameters flag_parameters;
1241 CHECK(google::protobuf::TextFormat::ParseFromString(sat_params,
1242 &flag_parameters))
1243 << sat_params;
1244 m.parameters.MergeFrom(flag_parameters);
1245
1246 // We only need an observer if 'p.all_solutions' is true.
1247 std::function<void(const CpSolverResponse&)> solution_observer = nullptr;
1248 if (p.display_all_solutions) {
1249 solution_observer = [&fz_model, &m, &p,
1250 solution_logger](const CpSolverResponse& r) {
1251 const std::string solution_string =
1252 SolutionString(fz_model, [&m, &r](fz::Variable* v) {
1253 return r.solution(m.fz_var_to_index.at(v));
1254 });
1255 SOLVER_LOG(solution_logger, solution_string);
1256 if (p.display_statistics) {
1257 OutputFlatzincStats(r, solution_logger);
1258 }
1259 SOLVER_LOG(solution_logger, "----------");
1260 };
1261 }
1262
1263 Model sat_model;
1264 sat_model.Add(NewSatParameters(m.parameters));
1265 if (solution_observer != nullptr) {
1266 sat_model.Add(NewFeasibleSolutionObserver(solution_observer));
1267 }
1268 // Setup logging.
1269 sat_model.GetOrCreate<SatParameters>()->set_log_to_stdout(false);
1270 sat_model.Register<SolverLogger>(logger);
1271
1272 const CpSolverResponse response = SolveCpModel(m.proto, &sat_model);
1273
1274 // Check the returned solution with the fz model checker.
1275 if (response.status() == CpSolverStatus::FEASIBLE ||
1276 response.status() == CpSolverStatus::OPTIMAL) {
1278 fz_model,
1279 [&response, &m](fz::Variable* v) {
1280 return response.solution(m.fz_var_to_index.at(v));
1281 },
1282 logger));
1283 }
1284
1285 // Output the solution in the flatzinc official format.
1286 if (solution_logger->LoggingIsEnabled()) {
1287 if (response.status() == CpSolverStatus::FEASIBLE ||
1288 response.status() == CpSolverStatus::OPTIMAL) {
1289 if (!p.display_all_solutions) { // Already printed otherwise.
1290 const std::string solution_string =
1291 SolutionString(fz_model, [&response, &m](fz::Variable* v) {
1292 return response.solution(m.fz_var_to_index.at(v));
1293 });
1294 SOLVER_LOG(solution_logger, solution_string);
1295 SOLVER_LOG(solution_logger, "----------");
1296 }
1297 if (response.status() == CpSolverStatus::OPTIMAL) {
1298 SOLVER_LOG(solution_logger, "==========");
1299 }
1300 } else if (response.status() == CpSolverStatus::INFEASIBLE) {
1301 SOLVER_LOG(solution_logger, "=====UNSATISFIABLE=====");
1302 } else if (response.status() == CpSolverStatus::MODEL_INVALID) {
1303 const std::string error_message = ValidateCpModel(m.proto);
1304 VLOG(1) << "%% Error message = '" << error_message << "'";
1305 if (absl::StrContains(error_message, "overflow")) {
1306 SOLVER_LOG(solution_logger, "=====OVERFLOW=====");
1307 } else {
1308 SOLVER_LOG(solution_logger, "=====MODEL INVALID=====");
1309 }
1310 } else {
1311 SOLVER_LOG(solution_logger, "%% TIMEOUT");
1312 }
1313 if (p.display_statistics) {
1314 OutputFlatzincStats(response, solution_logger);
1315 }
1316 }
1317}
1318
1319} // namespace sat
1320} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define LOG_FIRST_N(severity, n)
Definition: base/logging.h:855
#define CHECK(condition)
Definition: base/logging.h:495
#define CHECK_LT(val1, val2)
Definition: base/logging.h:706
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:703
#define CHECK_GE(val1, val2)
Definition: base/logging.h:707
#define LOG(severity)
Definition: base/logging.h:420
#define VLOG(verboselevel)
Definition: base/logging.h:984
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.
const std::string & name() const
const std::vector< Annotation > & search_annotations() const
const std::vector< Constraint * > & constraints() const
const std::vector< Variable * > & variables() const
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:42
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:
Definition: sat/model.h:85
void Register(T *non_owned_class)
Register a non-owned class that will be "singleton" in the model.
Definition: sat/model.h:173
T * GetOrCreate()
Returns an object of type T that is unique to this model (like a "local" singleton).
Definition: sat/model.h:110
int64_t b
int64_t a
Block * next
SatParameters parameters
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.")
int var
int64_t value
CpModelProto proto
SharedResponseManager * response
const Constraint * ct
GRBmodel * model
int arc
int index
const int WARNING
Definition: log_severity.h:31
const int FATAL
Definition: log_severity.h:32
ReverseView< Container > reversed_view(const Container &c)
bool CheckSolution(const Model &model, const std::function< int64_t(Variable *)> &evaluator, SolverLogger *logger)
Definition: checker.cc:1237
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 &params)
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.
Literal literal
Definition: optimization.cc:89
int64_t weight
Definition: pack.cc:510
int64_t demand
Definition: resource.cc:125
IntervalVar * interval
Definition: resource.cc:100
int64_t tail
int64_t head
#define SOLVER_LOG(logger,...)
Definition: util/logging.h:69