OR-Tools  9.2
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"
29 #include "ortools/base/map_util.h"
31 #include "ortools/base/timer.h"
33 #include "ortools/flatzinc/model.h"
40 #include "ortools/sat/cumulative.h"
42 #include "ortools/sat/integer.h"
44 #include "ortools/sat/intervals.h"
45 #include "ortools/sat/model.h"
47 #include "ortools/sat/sat_solver.h"
48 #include "ortools/sat/table.h"
49 #include "ortools/util/logging.h"
50 
51 ABSL_FLAG(int64_t, fz_int_max, int64_t{1} << 50,
52  "Default max value for unbounded integer variables.");
53 
54 namespace operations_research {
55 namespace sat {
56 
57 namespace {
58 
59 static const int kNoVar = std::numeric_limits<int>::min();
60 
61 struct VarOrValue {
62  int var = kNoVar;
63  int64_t value = 0;
64 };
65 
66 // Returns the true/false literal corresponding to a CpModelProto variable.
67 int TrueLiteral(int var) { return var; }
68 int FalseLiteral(int var) { return -var - 1; }
69 int NegatedCpModelVariable(int var) { return -var - 1; }
70 
71 // Helper class to convert a flatzinc model to a CpModelProto.
72 struct CpModelProtoWithMapping {
73  // Returns a constant CpModelProto variable created on-demand.
74  int LookupConstant(int64_t value);
75 
76  // Convert a flatzinc argument to a variable or a list of variable.
77  // Note that we always encode a constant argument with a constant variable.
78  int LookupVar(const fz::Argument& argument);
79  LinearExpressionProto LookupExpr(const fz::Argument& argument,
80  bool negate = false);
81  LinearExpressionProto LookupExprAt(const fz::Argument& argument, int pos,
82  bool negate = false);
83  std::vector<int> LookupVars(const fz::Argument& argument);
84  std::vector<VarOrValue> LookupVarsOrValues(const fz::Argument& argument);
85 
86  // Create and return the indices of the IntervalConstraint corresponding
87  // to the flatzinc "interval" specified by a start var and a size var.
88  // This method will cache intervals with the key <start, size>.
89  std::vector<int> CreateIntervals(const std::vector<int>& starts,
90  const std::vector<VarOrValue>& sizes);
91 
92  // Create and return the index of the optional IntervalConstraint
93  // corresponding to the flatzinc "interval" specified by a start var, the
94  // size_var, and the Boolean opt_var. This method will cache intervals with
95  // the key <start, size, opt_var>. If opt_var == kNoVar, the interval will not
96  // be optional.
97  int GetOrCreateOptionalInterval(int start_var, VarOrValue size, int opt_var);
98 
99  // Adds a constraint to the model, add the enforcement literal if it is
100  // different from kNoVar, and returns a ptr to the ConstraintProto.
101  ConstraintProto* AddEnforcedConstraint(int literal);
102 
103  // Helpers to fill a ConstraintProto.
104  void FillAMinusBInDomain(const std::vector<int64_t>& domain,
105  const fz::Constraint& fz_ct, ConstraintProto* ct);
106  void FillLinearConstraintWithGivenDomain(const std::vector<int64_t>& domain,
107  const fz::Constraint& fz_ct,
108  ConstraintProto* ct);
109  void FillConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
110  void FillReifOrImpliedConstraint(const fz::Constraint& fz_ct,
111  ConstraintProto* ct);
112 
113  // Translates the flatzinc search annotations into the CpModelProto
114  // search_order field.
115  void TranslateSearchAnnotations(
116  const std::vector<fz::Annotation>& search_annotations);
117 
118  // The output proto.
119  CpModelProto proto;
120  SatParameters parameters;
121 
122  // Mapping from flatzinc variables to CpModelProto variables.
123  absl::flat_hash_map<fz::Variable*, int> fz_var_to_index;
124  absl::flat_hash_map<int64_t, int> constant_value_to_index;
125  absl::flat_hash_map<std::tuple<int, int, int>, int>
127  absl::flat_hash_map<std::tuple<int, int64_t, int>, int>
129 };
130 
131 int CpModelProtoWithMapping::LookupConstant(int64_t value) {
132  if (constant_value_to_index.contains(value)) {
134  }
135 
136  // Create the constant on the fly.
137  const int index = proto.variables_size();
138  IntegerVariableProto* var_proto = proto.add_variables();
139  var_proto->add_domain(value);
140  var_proto->add_domain(value);
142  return index;
143 }
144 
145 int CpModelProtoWithMapping::LookupVar(const fz::Argument& argument) {
146  if (argument.HasOneValue()) return LookupConstant(argument.Value());
147  CHECK_EQ(argument.type, fz::Argument::VAR_REF);
148  return fz_var_to_index[argument.Var()];
149 }
150 
151 LinearExpressionProto CpModelProtoWithMapping::LookupExpr(
152  const fz::Argument& argument, bool negate) {
153  LinearExpressionProto expr;
154  if (argument.HasOneValue()) {
155  const int64_t value = argument.Value();
156  expr.set_offset(negate ? -value : value);
157  } else {
158  expr.add_vars(LookupVar(argument));
159  expr.add_coeffs(negate ? -1 : 1);
160  }
161  return expr;
162 }
163 
164 LinearExpressionProto CpModelProtoWithMapping::LookupExprAt(
165  const fz::Argument& argument, int pos, bool negate) {
166  LinearExpressionProto expr;
167  if (argument.HasOneValueAt(pos)) {
168  const int64_t value = argument.ValueAt(pos);
169  expr.set_offset(negate ? -value : value);
170  } else {
171  expr.add_vars(fz_var_to_index[argument.VarAt(pos)]);
172  expr.add_coeffs(negate ? -1 : 1);
173  }
174  return expr;
175 }
176 
177 std::vector<int> CpModelProtoWithMapping::LookupVars(
178  const fz::Argument& argument) {
179  std::vector<int> result;
180  if (argument.type == fz::Argument::VOID_ARGUMENT) return result;
181  if (argument.type == fz::Argument::INT_LIST) {
182  for (int64_t value : argument.values) {
183  result.push_back(LookupConstant(value));
184  }
185  } else if (argument.type == fz::Argument::INT_VALUE) {
186  result.push_back(LookupConstant(argument.Value()));
187  } else {
188  CHECK_EQ(argument.type, fz::Argument::VAR_REF_ARRAY);
189  for (fz::Variable* var : argument.variables) {
190  CHECK(var != nullptr);
191  result.push_back(fz_var_to_index[var]);
192  }
193  }
194  return result;
195 }
196 
197 std::vector<VarOrValue> CpModelProtoWithMapping::LookupVarsOrValues(
198  const fz::Argument& argument) {
199  std::vector<VarOrValue> result;
200  const int no_var = kNoVar;
201  if (argument.type == fz::Argument::VOID_ARGUMENT) return result;
202  if (argument.type == fz::Argument::INT_LIST) {
203  for (int64_t value : argument.values) {
204  result.push_back({no_var, value});
205  }
206  } else if (argument.type == fz::Argument::INT_VALUE) {
207  result.push_back({no_var, argument.Value()});
208  } else {
209  CHECK_EQ(argument.type, fz::Argument::VAR_REF_ARRAY);
210  for (fz::Variable* var : argument.variables) {
211  CHECK(var != nullptr);
212  if (var->domain.HasOneValue()) {
213  result.push_back({no_var, var->domain.Value()});
214  } else {
215  result.push_back({fz_var_to_index[var], 0});
216  }
217  }
218  }
219  return result;
220 }
221 
222 ConstraintProto* CpModelProtoWithMapping::AddEnforcedConstraint(int literal) {
223  ConstraintProto* result = proto.add_constraints();
224  if (literal != kNoVar) {
226  }
227  return result;
228 }
229 
230 int CpModelProtoWithMapping::GetOrCreateOptionalInterval(int start_var,
231  VarOrValue size,
232  int opt_var) {
233  const int interval_index = proto.constraints_size();
234  if (size.var == kNoVar) { // Size is fixed.
235  const std::tuple<int, int64_t, int> key =
236  std::make_tuple(start_var, size.value, opt_var);
237  const auto [it, inserted] =
238  start_fixed_size_opt_tuple_to_interval.insert({key, interval_index});
239  if (!inserted) {
240  return it->second;
241  }
242 
243  auto* interval = AddEnforcedConstraint(opt_var)->mutable_interval();
244  interval->mutable_start()->add_vars(start_var);
245  interval->mutable_start()->add_coeffs(1);
246  interval->mutable_size()->set_offset(size.value);
247  interval->mutable_end()->add_vars(start_var);
248  interval->mutable_end()->add_coeffs(1);
249  interval->mutable_end()->set_offset(size.value);
250 
251  return interval_index;
252  } else { // Size is variable.
253  const std::tuple<int, int, int> key =
254  std::make_tuple(start_var, size.var, opt_var);
255  const auto [it, inserted] =
256  start_size_opt_tuple_to_interval.insert({key, interval_index});
257  if (!inserted) {
258  return it->second;
259  }
260 
261  const int end_var = proto.variables_size();
264  .AdditionWith(ReadDomainFromProto(proto.variables(size.var))),
265  proto.add_variables());
266 
267  // Create the interval.
268  auto* interval = AddEnforcedConstraint(opt_var)->mutable_interval();
269  interval->mutable_start()->add_vars(start_var);
270  interval->mutable_start()->add_coeffs(1);
271  interval->mutable_size()->add_vars(size.var);
272  interval->mutable_size()->add_coeffs(1);
273  interval->mutable_end()->add_vars(end_var);
274  interval->mutable_end()->add_coeffs(1);
275 
276  // Add the linear constraint (after the interval constraint as we have
277  // stored its index).
278  auto* lin = AddEnforcedConstraint(opt_var)->mutable_linear();
279  lin->add_vars(start_var);
280  lin->add_coeffs(1);
281  lin->add_vars(size.var);
282  lin->add_coeffs(1);
283  lin->add_vars(end_var);
284  lin->add_coeffs(-1);
285  lin->add_domain(0);
286  lin->add_domain(0);
287 
288  return interval_index;
289  }
290 }
291 
292 std::vector<int> CpModelProtoWithMapping::CreateIntervals(
293  const std::vector<int>& starts, const std::vector<VarOrValue>& sizes) {
294  std::vector<int> intervals;
295  for (int i = 0; i < starts.size(); ++i) {
296  intervals.push_back(
297  GetOrCreateOptionalInterval(starts[i], sizes[i], kNoVar));
298  }
299  return intervals;
300 }
301 
302 void CpModelProtoWithMapping::FillAMinusBInDomain(
303  const std::vector<int64_t>& domain, const fz::Constraint& fz_ct,
304  ConstraintProto* ct) {
305  auto* arg = ct->mutable_linear();
306  if (fz_ct.arguments[1].type == fz::Argument::INT_VALUE) {
307  const int64_t value = fz_ct.arguments[1].Value();
308  const int var_a = LookupVar(fz_ct.arguments[0]);
309  for (const int64_t domain_bound : domain) {
310  if (domain_bound == std::numeric_limits<int64_t>::min() ||
311  domain_bound == std::numeric_limits<int64_t>::max()) {
312  arg->add_domain(domain_bound);
313  } else {
314  arg->add_domain(domain_bound + value);
315  }
316  }
317  arg->add_vars(var_a);
318  arg->add_coeffs(1);
319  } else if (fz_ct.arguments[0].type == fz::Argument::INT_VALUE) {
320  const int64_t value = fz_ct.arguments[0].Value();
321  const int var_b = LookupVar(fz_ct.arguments[1]);
322  for (int64_t domain_bound : gtl::reversed_view(domain)) {
323  if (domain_bound == std::numeric_limits<int64_t>::min()) {
324  arg->add_domain(std::numeric_limits<int64_t>::max());
325  } else if (domain_bound == std::numeric_limits<int64_t>::max()) {
326  arg->add_domain(std::numeric_limits<int64_t>::min());
327  } else {
328  arg->add_domain(value - domain_bound);
329  }
330  }
331  arg->add_vars(var_b);
332  arg->add_coeffs(1);
333  } else {
334  for (const int64_t domain_bound : domain) arg->add_domain(domain_bound);
335  arg->add_vars(LookupVar(fz_ct.arguments[0]));
336  arg->add_coeffs(1);
337  arg->add_vars(LookupVar(fz_ct.arguments[1]));
338  arg->add_coeffs(-1);
339  }
340 }
341 
342 void CpModelProtoWithMapping::FillLinearConstraintWithGivenDomain(
343  const std::vector<int64_t>& domain, const fz::Constraint& fz_ct,
344  ConstraintProto* ct) {
345  auto* arg = ct->mutable_linear();
346  for (const int64_t domain_bound : domain) arg->add_domain(domain_bound);
347  std::vector<int> vars = LookupVars(fz_ct.arguments[1]);
348  for (int i = 0; i < vars.size(); ++i) {
349  arg->add_vars(vars[i]);
350  arg->add_coeffs(fz_ct.arguments[0].values[i]);
351  }
352 }
353 
354 void CpModelProtoWithMapping::FillConstraint(const fz::Constraint& fz_ct,
355  ConstraintProto* ct) {
356  if (fz_ct.type == "false_constraint") {
357  // An empty clause is always false.
358  ct->mutable_bool_or();
359  } else if (fz_ct.type == "bool_clause") {
360  auto* arg = ct->mutable_bool_or();
361  for (const int var : LookupVars(fz_ct.arguments[0])) {
362  arg->add_literals(TrueLiteral(var));
363  }
364  for (const int var : LookupVars(fz_ct.arguments[1])) {
365  arg->add_literals(FalseLiteral(var));
366  }
367  } else if (fz_ct.type == "bool_xor") {
368  // This is not the same semantics as the array_bool_xor as this constraint
369  // is actually a fully reified xor(a, b) <==> x.
370  const int a = LookupVar(fz_ct.arguments[0]);
371  const int b = LookupVar(fz_ct.arguments[1]);
372  const int x = LookupVar(fz_ct.arguments[2]);
373 
374  // not(x) => a == b
375  ct->add_enforcement_literal(NegatedRef(x));
376  auto* const refute = ct->mutable_linear();
377  refute->add_vars(a);
378  refute->add_coeffs(1);
379  refute->add_vars(b);
380  refute->add_coeffs(-1);
381  refute->add_domain(0);
382  refute->add_domain(0);
383 
384  // x => a + b == 1
385  auto* enforce = AddEnforcedConstraint(x)->mutable_linear();
386  enforce->add_vars(a);
387  enforce->add_coeffs(1);
388  enforce->add_vars(b);
389  enforce->add_coeffs(1);
390  enforce->add_domain(1);
391  enforce->add_domain(1);
392  } else if (fz_ct.type == "array_bool_or") {
393  auto* arg = ct->mutable_bool_or();
394  for (const int var : LookupVars(fz_ct.arguments[0])) {
395  arg->add_literals(TrueLiteral(var));
396  }
397  } else if (fz_ct.type == "array_bool_or_negated") {
398  auto* arg = ct->mutable_bool_and();
399  for (const int var : LookupVars(fz_ct.arguments[0])) {
400  arg->add_literals(FalseLiteral(var));
401  }
402  } else if (fz_ct.type == "array_bool_and") {
403  auto* arg = ct->mutable_bool_and();
404  for (const int var : LookupVars(fz_ct.arguments[0])) {
405  arg->add_literals(TrueLiteral(var));
406  }
407  } else if (fz_ct.type == "array_bool_and_negated") {
408  auto* arg = ct->mutable_bool_or();
409  for (const int var : LookupVars(fz_ct.arguments[0])) {
410  arg->add_literals(FalseLiteral(var));
411  }
412  } else if (fz_ct.type == "array_bool_xor") {
413  auto* arg = ct->mutable_bool_xor();
414  for (const int var : LookupVars(fz_ct.arguments[0])) {
415  arg->add_literals(TrueLiteral(var));
416  }
417  } else if (fz_ct.type == "bool_le" || fz_ct.type == "int_le") {
418  FillAMinusBInDomain({std::numeric_limits<int64_t>::min(), 0}, fz_ct, ct);
419  } else if (fz_ct.type == "bool_ge" || fz_ct.type == "int_ge") {
420  FillAMinusBInDomain({0, std::numeric_limits<int64_t>::max()}, fz_ct, ct);
421  } else if (fz_ct.type == "bool_lt" || fz_ct.type == "int_lt") {
422  FillAMinusBInDomain({std::numeric_limits<int64_t>::min(), -1}, fz_ct, ct);
423  } else if (fz_ct.type == "bool_gt" || fz_ct.type == "int_gt") {
424  FillAMinusBInDomain({1, std::numeric_limits<int64_t>::max()}, fz_ct, ct);
425  } else if (fz_ct.type == "bool_eq" || fz_ct.type == "int_eq" ||
426  fz_ct.type == "bool2int") {
427  FillAMinusBInDomain({0, 0}, fz_ct, ct);
428  } else if (fz_ct.type == "bool_ne" || fz_ct.type == "bool_not") {
429  auto* arg = ct->mutable_linear();
430  arg->add_vars(LookupVar(fz_ct.arguments[0]));
431  arg->add_coeffs(1);
432  arg->add_vars(LookupVar(fz_ct.arguments[1]));
433  arg->add_coeffs(1);
434  arg->add_domain(1);
435  arg->add_domain(1);
436  } else if (fz_ct.type == "int_ne") {
437  FillAMinusBInDomain({std::numeric_limits<int64_t>::min(), -1, 1,
439  fz_ct, ct);
440  } else if (fz_ct.type == "int_lin_eq") {
441  const int64_t rhs = fz_ct.arguments[2].values[0];
442  FillLinearConstraintWithGivenDomain({rhs, rhs}, fz_ct, ct);
443  } else if (fz_ct.type == "bool_lin_eq") {
444  auto* arg = ct->mutable_linear();
445  const std::vector<int> vars = LookupVars(fz_ct.arguments[1]);
446  for (int i = 0; i < vars.size(); ++i) {
447  arg->add_vars(vars[i]);
448  arg->add_coeffs(fz_ct.arguments[0].values[i]);
449  }
450  if (fz_ct.arguments[2].IsVariable()) {
451  arg->add_vars(LookupVar(fz_ct.arguments[2]));
452  arg->add_coeffs(-1);
453  arg->add_domain(0);
454  arg->add_domain(0);
455  } else {
456  const int64_t v = fz_ct.arguments[2].Value();
457  arg->add_domain(v);
458  arg->add_domain(v);
459  }
460  } else if (fz_ct.type == "int_lin_le" || fz_ct.type == "bool_lin_le") {
461  const int64_t rhs = fz_ct.arguments[2].values[0];
462  FillLinearConstraintWithGivenDomain(
463  {std::numeric_limits<int64_t>::min(), rhs}, fz_ct, ct);
464  } else if (fz_ct.type == "int_lin_lt") {
465  const int64_t rhs = fz_ct.arguments[2].values[0];
466  FillLinearConstraintWithGivenDomain(
467  {std::numeric_limits<int64_t>::min(), rhs - 1}, fz_ct, ct);
468  } else if (fz_ct.type == "int_lin_ge") {
469  const int64_t rhs = fz_ct.arguments[2].values[0];
470  FillLinearConstraintWithGivenDomain(
471  {rhs, std::numeric_limits<int64_t>::max()}, fz_ct, ct);
472  } else if (fz_ct.type == "int_lin_gt") {
473  const int64_t rhs = fz_ct.arguments[2].values[0];
474  FillLinearConstraintWithGivenDomain(
475  {rhs + 1, std::numeric_limits<int64_t>::max()}, fz_ct, ct);
476  } else if (fz_ct.type == "int_lin_ne") {
477  const int64_t rhs = fz_ct.arguments[2].values[0];
478  FillLinearConstraintWithGivenDomain(
479  {std::numeric_limits<int64_t>::min(), rhs - 1, rhs + 1,
481  fz_ct, ct);
482  } else if (fz_ct.type == "set_in") {
483  auto* arg = ct->mutable_linear();
484  arg->add_vars(LookupVar(fz_ct.arguments[0]));
485  arg->add_coeffs(1);
486  if (fz_ct.arguments[1].type == fz::Argument::INT_LIST) {
487  FillDomainInProto(Domain::FromValues(std::vector<int64_t>{
488  fz_ct.arguments[1].values.begin(),
489  fz_ct.arguments[1].values.end()}),
490  arg);
491  } else if (fz_ct.arguments[1].type == fz::Argument::INT_INTERVAL) {
493  Domain(fz_ct.arguments[1].values[0], fz_ct.arguments[1].values[1]),
494  arg);
495  } else {
496  LOG(FATAL) << "Wrong format";
497  }
498  } else if (fz_ct.type == "set_in_negated") {
499  auto* arg = ct->mutable_linear();
500  arg->add_vars(LookupVar(fz_ct.arguments[0]));
501  arg->add_coeffs(1);
502  if (fz_ct.arguments[1].type == fz::Argument::INT_LIST) {
505  std::vector<int64_t>{fz_ct.arguments[1].values.begin(),
506  fz_ct.arguments[1].values.end()})
507  .Complement(),
508  arg);
509  } else if (fz_ct.arguments[1].type == fz::Argument::INT_INTERVAL) {
511  Domain(fz_ct.arguments[1].values[0], fz_ct.arguments[1].values[1])
512  .Complement(),
513  arg);
514  } else {
515  LOG(FATAL) << "Wrong format";
516  }
517  } else if (fz_ct.type == "int_min") {
518  auto* arg = ct->mutable_lin_max();
519  *arg->add_exprs() = LookupExpr(fz_ct.arguments[0], /*negate=*/true);
520  *arg->add_exprs() = LookupExpr(fz_ct.arguments[1], /*negate=*/true);
521  *arg->mutable_target() = LookupExpr(fz_ct.arguments[2], /*negate=*/true);
522  } else if (fz_ct.type == "array_int_minimum" || fz_ct.type == "minimum_int") {
523  auto* arg = ct->mutable_lin_max();
524  *arg->mutable_target() = LookupExpr(fz_ct.arguments[0], /*negate=*/true);
525  for (int i = 0; i < fz_ct.arguments[1].Size(); ++i) {
526  *arg->add_exprs() = LookupExprAt(fz_ct.arguments[1], i, /*negate=*/true);
527  }
528  } else if (fz_ct.type == "int_max") {
529  auto* arg = ct->mutable_lin_max();
530  *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
531  *arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
532  *arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
533  } else if (fz_ct.type == "array_int_maximum" || fz_ct.type == "maximum_int") {
534  auto* arg = ct->mutable_lin_max();
535  *arg->mutable_target() = LookupExpr(fz_ct.arguments[0]);
536  for (int i = 0; i < fz_ct.arguments[1].Size(); ++i) {
537  *arg->add_exprs() = LookupExprAt(fz_ct.arguments[1], i);
538  }
539  } else if (fz_ct.type == "int_times") {
540  auto* arg = ct->mutable_int_prod();
541  *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
542  *arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
543  *arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
544  } else if (fz_ct.type == "int_abs") {
545  auto* arg = ct->mutable_lin_max();
546  *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
547  *arg->add_exprs() = LookupExpr(fz_ct.arguments[0], /*negate=*/true);
548  *arg->mutable_target() = LookupExpr(fz_ct.arguments[1]);
549  } else if (fz_ct.type == "int_plus") {
550  auto* arg = ct->mutable_linear();
551  FillDomainInProto(Domain(0, 0), arg);
552  arg->add_vars(LookupVar(fz_ct.arguments[0]));
553  arg->add_coeffs(1);
554  arg->add_vars(LookupVar(fz_ct.arguments[1]));
555  arg->add_coeffs(1);
556  arg->add_vars(LookupVar(fz_ct.arguments[2]));
557  arg->add_coeffs(-1);
558  } else if (fz_ct.type == "int_div") {
559  auto* arg = ct->mutable_int_div();
560  *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
561  *arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
562  *arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
563  } else if (fz_ct.type == "int_mod") {
564  auto* arg = ct->mutable_int_mod();
565  *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
566  *arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
567  *arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
568  } else if (fz_ct.type == "array_int_element" ||
569  fz_ct.type == "array_bool_element" ||
570  fz_ct.type == "array_var_int_element" ||
571  fz_ct.type == "array_var_bool_element" ||
572  fz_ct.type == "array_int_element_nonshifted") {
573  if (fz_ct.arguments[0].type == fz::Argument::VAR_REF ||
574  fz_ct.arguments[0].type == fz::Argument::INT_VALUE) {
575  auto* arg = ct->mutable_element();
576  arg->set_index(LookupVar(fz_ct.arguments[0]));
577  arg->set_target(LookupVar(fz_ct.arguments[2]));
578 
579  if (!absl::EndsWith(fz_ct.type, "_nonshifted")) {
580  // Add a dummy variable at position zero because flatzinc index start
581  // at 1.
582  // TODO(user): Make sure that zero is not in the index domain...
583  arg->add_vars(LookupConstant(0));
584  }
585  for (const int var : LookupVars(fz_ct.arguments[1])) arg->add_vars(var);
586  } else {
587  // Special case added by the presolve or in flatzinc. We encode this
588  // as a table constraint.
589  CHECK(!absl::EndsWith(fz_ct.type, "_nonshifted"));
590  auto* arg = ct->mutable_table();
591 
592  // the constraint is:
593  // values[coeff1 * vars[0] + coeff2 * vars[1] + offset] == target.
594  for (const int var : LookupVars(fz_ct.arguments[0])) arg->add_vars(var);
595  arg->add_vars(LookupVar(fz_ct.arguments[2])); // the target
596 
597  const std::vector<int64_t>& values = fz_ct.arguments[1].values;
598  const int64_t coeff1 = fz_ct.arguments[3].values[0];
599  const int64_t coeff2 = fz_ct.arguments[3].values[1];
600  const int64_t offset = fz_ct.arguments[4].values[0] - 1;
601 
602  for (const int64_t a : AllValuesInDomain(proto.variables(arg->vars(0)))) {
603  for (const int64_t b :
604  AllValuesInDomain(proto.variables(arg->vars(1)))) {
605  const int index = coeff1 * a + coeff2 * b + offset;
606  CHECK_GE(index, 0);
607  CHECK_LT(index, values.size());
608  arg->add_values(a);
609  arg->add_values(b);
610  arg->add_values(values[index]);
611  }
612  }
613  }
614  } else if (fz_ct.type == "ortools_table_int") {
615  auto* arg = ct->mutable_table();
616  for (const int var : LookupVars(fz_ct.arguments[0])) arg->add_vars(var);
617  for (const int64_t value : fz_ct.arguments[1].values)
618  arg->add_values(value);
619  } else if (fz_ct.type == "ortools_regular") {
620  auto* arg = ct->mutable_automaton();
621  for (const int var : LookupVars(fz_ct.arguments[0])) arg->add_vars(var);
622 
623  int count = 0;
624  const int num_states = fz_ct.arguments[1].Value();
625  const int num_values = fz_ct.arguments[2].Value();
626  for (int i = 1; i <= num_states; ++i) {
627  for (int j = 1; j <= num_values; ++j) {
628  CHECK_LT(count, fz_ct.arguments[3].values.size());
629  const int next = fz_ct.arguments[3].values[count++];
630  if (next == 0) continue; // 0 is a failing state.
631  arg->add_transition_tail(i);
632  arg->add_transition_label(j);
633  arg->add_transition_head(next);
634  }
635  }
636 
637  arg->set_starting_state(fz_ct.arguments[4].Value());
638  switch (fz_ct.arguments[5].type) {
640  arg->add_final_states(fz_ct.arguments[5].values[0]);
641  break;
642  }
644  for (int v = fz_ct.arguments[5].values[0];
645  v <= fz_ct.arguments[5].values[1]; ++v) {
646  arg->add_final_states(v);
647  }
648  break;
649  }
650  case fz::Argument::INT_LIST: {
651  for (const int v : fz_ct.arguments[5].values) {
652  arg->add_final_states(v);
653  }
654  break;
655  }
656  default: {
657  LOG(FATAL) << "Wrong constraint " << fz_ct.DebugString();
658  }
659  }
660  } else if (fz_ct.type == "fzn_all_different_int") {
661  auto* arg = ct->mutable_all_diff();
662  for (int i = 0; i < fz_ct.arguments[0].Size(); ++i) {
663  *arg->add_exprs() = LookupExprAt(fz_ct.arguments[0], i);
664  }
665  } else if (fz_ct.type == "ortools_circuit" ||
666  fz_ct.type == "ortools_subcircuit") {
667  const int64_t min_index = fz_ct.arguments[1].Value();
668  const int size = std::max(fz_ct.arguments[0].values.size(),
669  fz_ct.arguments[0].variables.size());
670 
671  const int64_t max_index = min_index + size - 1;
672  // The arc-based mutable circuit.
673  auto* circuit_arg = ct->mutable_circuit();
674 
675  // We fully encode all variables so we can use the literal based circuit.
676  // TODO(user): avoid fully encoding more than once?
677  int64_t index = min_index;
678  const bool is_circuit = (fz_ct.type == "ortools_circuit");
679  for (const int var : LookupVars(fz_ct.arguments[0])) {
680  Domain domain = ReadDomainFromProto(proto.variables(var));
681 
682  // Restrict the domain of var to [min_index, max_index]
683  domain = domain.IntersectionWith(Domain(min_index, max_index));
684  if (is_circuit) {
685  // We simply make sure that the variable cannot take the value index.
686  domain = domain.IntersectionWith(Domain::FromIntervals(
689  }
691 
692  for (const ClosedInterval interval : domain.intervals()) {
693  for (int64_t value = interval.start; value <= interval.end; ++value) {
694  // Create one Boolean variable for this arc.
695  const int literal = proto.variables_size();
696  {
697  auto* new_var = proto.add_variables();
698  new_var->add_domain(0);
699  new_var->add_domain(1);
700  }
701 
702  // Add the arc.
703  circuit_arg->add_tails(index);
704  circuit_arg->add_heads(value);
705  circuit_arg->add_literals(literal);
706 
707  // literal => var == value.
708  {
709  auto* lin = AddEnforcedConstraint(literal)->mutable_linear();
710  lin->add_coeffs(1);
711  lin->add_vars(var);
712  lin->add_domain(value);
713  lin->add_domain(value);
714  }
715 
716  // not(literal) => var != value
717  {
718  auto* lin =
719  AddEnforcedConstraint(NegatedRef(literal))->mutable_linear();
720  lin->add_coeffs(1);
721  lin->add_vars(var);
722  lin->add_domain(std::numeric_limits<int64_t>::min());
723  lin->add_domain(value - 1);
724  lin->add_domain(value + 1);
725  lin->add_domain(std::numeric_limits<int64_t>::max());
726  }
727  }
728  }
729 
730  ++index;
731  }
732  } else if (fz_ct.type == "ortools_inverse") {
733  auto* arg = ct->mutable_inverse();
734 
735  const auto direct_variables = LookupVars(fz_ct.arguments[0]);
736  const auto inverse_variables = LookupVars(fz_ct.arguments[1]);
737  const int base_direct = fz_ct.arguments[2].Value();
738  const int base_inverse = fz_ct.arguments[3].Value();
739 
740  CHECK_EQ(direct_variables.size(), inverse_variables.size());
741  const int num_variables = direct_variables.size();
742  const int end_direct = base_direct + num_variables;
743  const int end_inverse = base_inverse + num_variables;
744 
745  // Any convention that maps the "fixed values" to the one of the inverse and
746  // back works. We decided to follow this one:
747  // There are 3 cases:
748  // (A) base_direct == base_inverse, we fill the arrays
749  // direct = [0, .., base_direct - 1] U [direct_vars]
750  // inverse = [0, .., base_direct - 1] U [inverse_vars]
751  // (B) base_direct == base_inverse + offset (> 0), we fill the arrays
752  // direct = [0, .., base_inverse - 1] U
753  // [end_inverse, .., end_inverse + offset - 1] U
754  // [direct_vars]
755  // inverse = [0, .., base_inverse - 1] U
756  // [inverse_vars] U
757  // [base_inverse, .., base_base_inverse + offset - 1]
758  // (C): base_inverse == base_direct + offset (> 0), we fill the arrays
759  // direct = [0, .., base_direct - 1] U
760  // [direct_vars] U
761  // [base_direct, .., base_direct + offset - 1]
762  // inverse [0, .., base_direct - 1] U
763  // [end_direct, .., end_direct + offset - 1] U
764  // [inverse_vars]
765  const int arity = std::max(base_inverse, base_direct) + num_variables;
766  for (int i = 0; i < arity; ++i) {
767  // Fill the direct array.
768  if (i < base_direct) {
769  if (i < base_inverse) {
770  arg->add_f_direct(LookupConstant(i));
771  } else if (i >= base_inverse) {
772  arg->add_f_direct(LookupConstant(i + num_variables));
773  }
774  } else if (i >= base_direct && i < end_direct) {
775  arg->add_f_direct(direct_variables[i - base_direct]);
776  } else {
777  arg->add_f_direct(LookupConstant(i - num_variables));
778  }
779 
780  // Fill the inverse array.
781  if (i < base_inverse) {
782  if (i < base_direct) {
783  arg->add_f_inverse(LookupConstant(i));
784  } else if (i >= base_direct) {
785  arg->add_f_inverse(LookupConstant(i + num_variables));
786  }
787  } else if (i >= base_inverse && i < end_inverse) {
788  arg->add_f_inverse(inverse_variables[i - base_inverse]);
789  } else {
790  arg->add_f_inverse(LookupConstant(i - num_variables));
791  }
792  }
793  } else if (fz_ct.type == "fzn_cumulative") {
794  const std::vector<int> starts = LookupVars(fz_ct.arguments[0]);
795  const std::vector<VarOrValue> sizes =
796  LookupVarsOrValues(fz_ct.arguments[1]);
797  const std::vector<VarOrValue> demands =
798  LookupVarsOrValues(fz_ct.arguments[2]);
799 
800  auto* arg = ct->mutable_cumulative();
801  if (fz_ct.arguments[3].HasOneValue()) {
802  arg->mutable_capacity()->set_offset(fz_ct.arguments[3].Value());
803  } else {
804  arg->mutable_capacity()->add_vars(LookupVar(fz_ct.arguments[3]));
805  arg->mutable_capacity()->add_coeffs(1);
806  }
807  for (int i = 0; i < starts.size(); ++i) {
808  // Special case for a 0-1 demand, we mark the interval as optional
809  // instead and fix the demand to 1.
810  if (demands[i].var != kNoVar &&
811  proto.variables(demands[i].var).domain().size() == 2 &&
812  proto.variables(demands[i].var).domain(0) == 0 &&
813  proto.variables(demands[i].var).domain(1) == 1 &&
814  fz_ct.arguments[3].HasOneValue() && fz_ct.arguments[3].Value() == 1) {
815  arg->add_intervals(
816  GetOrCreateOptionalInterval(starts[i], sizes[i], demands[i].var));
817  arg->add_demands()->set_offset(1);
818  } else {
819  arg->add_intervals(
820  GetOrCreateOptionalInterval(starts[i], sizes[i], kNoVar));
821  LinearExpressionProto* demand = arg->add_demands();
822  if (demands[i].var == kNoVar) {
823  demand->set_offset(demands[i].value);
824  } else {
825  demand->add_vars(demands[i].var);
826  demand->add_coeffs(1);
827  }
828  }
829  }
830  } else if (fz_ct.type == "fzn_diffn" || fz_ct.type == "fzn_diffn_nonstrict") {
831  const std::vector<int> x = LookupVars(fz_ct.arguments[0]);
832  const std::vector<int> y = LookupVars(fz_ct.arguments[1]);
833  const std::vector<VarOrValue> dx = LookupVarsOrValues(fz_ct.arguments[2]);
834  const std::vector<VarOrValue> dy = LookupVarsOrValues(fz_ct.arguments[3]);
835  const std::vector<int> x_intervals = CreateIntervals(x, dx);
836  const std::vector<int> y_intervals = CreateIntervals(y, dy);
837  auto* arg = ct->mutable_no_overlap_2d();
838  for (int i = 0; i < x.size(); ++i) {
839  arg->add_x_intervals(x_intervals[i]);
840  arg->add_y_intervals(y_intervals[i]);
841  }
842  arg->set_boxes_with_null_area_can_overlap(fz_ct.type ==
843  "fzn_diffn_nonstrict");
844  } else if (fz_ct.type == "ortools_network_flow" ||
845  fz_ct.type == "ortools_network_flow_cost") {
846  // Note that we leave ct empty here (with just the name set).
847  // We simply do a linear encoding of this constraint.
848  const bool has_cost = fz_ct.type == "ortools_network_flow_cost";
849  const std::vector<int> flow = LookupVars(fz_ct.arguments[has_cost ? 3 : 2]);
850 
851  // Flow conservation constraints.
852  const int num_nodes = fz_ct.arguments[1].values.size();
853  std::vector<std::vector<int>> flows_per_node(num_nodes);
854  std::vector<std::vector<int>> coeffs_per_node(num_nodes);
855  const int num_arcs = fz_ct.arguments[0].values.size() / 2;
856  for (int arc = 0; arc < num_arcs; arc++) {
857  const int tail = fz_ct.arguments[0].values[2 * arc] - 1;
858  const int head = fz_ct.arguments[0].values[2 * arc + 1] - 1;
859  if (tail == head) continue;
860 
861  flows_per_node[tail].push_back(flow[arc]);
862  coeffs_per_node[tail].push_back(1);
863  flows_per_node[head].push_back(flow[arc]);
864  coeffs_per_node[head].push_back(-1);
865  }
866  for (int node = 0; node < num_nodes; node++) {
867  auto* arg = proto.add_constraints()->mutable_linear();
868  arg->add_domain(fz_ct.arguments[1].values[node]);
869  arg->add_domain(fz_ct.arguments[1].values[node]);
870  for (int i = 0; i < flows_per_node[node].size(); ++i) {
871  arg->add_vars(flows_per_node[node][i]);
872  arg->add_coeffs(coeffs_per_node[node][i]);
873  }
874  }
875 
876  if (has_cost) {
877  auto* arg = proto.add_constraints()->mutable_linear();
878  arg->add_domain(0);
879  arg->add_domain(0);
880  for (int arc = 0; arc < num_arcs; arc++) {
881  const int64_t weight = fz_ct.arguments[2].values[arc];
882  if (weight != 0) {
883  arg->add_vars(flow[arc]);
884  arg->add_coeffs(weight);
885  }
886  }
887  arg->add_vars(LookupVar(fz_ct.arguments[4]));
888  arg->add_coeffs(-1);
889  }
890  } else {
891  LOG(FATAL) << " Not supported " << fz_ct.type;
892  }
893 }
894 
895 void CpModelProtoWithMapping::FillReifOrImpliedConstraint(
896  const fz::Constraint& fz_ct, ConstraintProto* ct) {
897  // Start by adding a non-reified version of the same constraint.
898  std::string simplified_type;
899  if (absl::EndsWith(fz_ct.type, "_reif")) {
900  // Remove _reif.
901  simplified_type = fz_ct.type.substr(0, fz_ct.type.size() - 5);
902  } else if (absl::EndsWith(fz_ct.type, "_imp")) {
903  // Remove _imp.
904  simplified_type = fz_ct.type.substr(0, fz_ct.type.size() - 4);
905  } else {
906  // Keep name as it is an implicit reified constraint.
907  simplified_type = fz_ct.type;
908  }
909 
910  // We need a copy to be able to change the type of the constraint.
911  fz::Constraint copy = fz_ct;
912  copy.type = simplified_type;
913 
914  // Create the CP-SAT constraint.
915  FillConstraint(copy, ct);
916 
917  // In case of reified constraints, the type of the opposite constraint.
918  std::string negated_type;
919 
920  // Fill enforcement_literal and set copy.type to the negated constraint.
921  if (simplified_type == "array_bool_or") {
922  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[1])));
923  negated_type = "array_bool_or_negated";
924  } else if (simplified_type == "array_bool_and") {
925  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[1])));
926  negated_type = "array_bool_and_negated";
927  } else if (simplified_type == "set_in") {
928  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
929  negated_type = "set_in_negated";
930  } else if (simplified_type == "bool_eq" || simplified_type == "int_eq") {
931  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
932  negated_type = "int_ne";
933  } else if (simplified_type == "bool_ne" || simplified_type == "int_ne") {
934  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
935  negated_type = "int_eq";
936  } else if (simplified_type == "bool_le" || simplified_type == "int_le") {
937  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
938  negated_type = "int_gt";
939  } else if (simplified_type == "bool_lt" || simplified_type == "int_lt") {
940  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
941  negated_type = "int_ge";
942  } else if (simplified_type == "bool_ge" || simplified_type == "int_ge") {
943  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
944  negated_type = "int_lt";
945  } else if (simplified_type == "bool_gt" || simplified_type == "int_gt") {
946  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
947  negated_type = "int_le";
948  } else if (simplified_type == "int_lin_eq") {
949  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
950  negated_type = "int_lin_ne";
951  } else if (simplified_type == "int_lin_ne") {
952  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
953  negated_type = "int_lin_eq";
954  } else if (simplified_type == "int_lin_le") {
955  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
956  negated_type = "int_lin_gt";
957  } else if (simplified_type == "int_lin_ge") {
958  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
959  negated_type = "int_lin_lt";
960  } else if (simplified_type == "int_lin_lt") {
961  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
962  negated_type = "int_lin_ge";
963  } else if (simplified_type == "int_lin_gt") {
964  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
965  negated_type = "int_lin_le";
966  } else {
967  LOG(FATAL) << "Unsupported " << simplified_type;
968  }
969 
970  // One way implication. We can stop here.
971  if (absl::EndsWith(fz_ct.type, "_imp")) return;
972 
973  // Add the other side of the reification because CpModelProto only support
974  // half reification.
975  ConstraintProto* negated_ct = proto.add_constraints();
976  negated_ct->set_name(fz_ct.type + " (negated)");
977  negated_ct->add_enforcement_literal(
978  sat::NegatedRef(ct->enforcement_literal(0)));
979  copy.type = negated_type;
980  FillConstraint(copy, negated_ct);
981 }
982 
983 void CpModelProtoWithMapping::TranslateSearchAnnotations(
984  const std::vector<fz::Annotation>& search_annotations) {
985  std::vector<fz::Annotation> flat_annotations;
986  for (const fz::Annotation& annotation : search_annotations) {
987  fz::FlattenAnnotations(annotation, &flat_annotations);
988  }
989 
990  for (const fz::Annotation& annotation : flat_annotations) {
991  if (annotation.IsFunctionCallWithIdentifier("int_search") ||
992  annotation.IsFunctionCallWithIdentifier("bool_search")) {
993  const std::vector<fz::Annotation>& args = annotation.annotations;
994  std::vector<fz::Variable*> vars;
995  args[0].AppendAllVariables(&vars);
996 
997  DecisionStrategyProto* strategy = proto.add_search_strategy();
998  for (fz::Variable* v : vars) {
1000  }
1001 
1002  const fz::Annotation& choose = args[1];
1003  if (choose.id == "input_order") {
1004  strategy->set_variable_selection_strategy(
1006  } else if (choose.id == "first_fail") {
1007  strategy->set_variable_selection_strategy(
1009  } else if (choose.id == "anti_first_fail") {
1010  strategy->set_variable_selection_strategy(
1012  } else if (choose.id == "smallest") {
1013  strategy->set_variable_selection_strategy(
1015  } else if (choose.id == "largest") {
1016  strategy->set_variable_selection_strategy(
1018  } else {
1019  LOG(FATAL) << "Unsupported order: " << choose.id;
1020  }
1021 
1022  const fz::Annotation& select = args[2];
1023  if (select.id == "indomain_min" || select.id == "indomain") {
1024  strategy->set_domain_reduction_strategy(
1026  } else if (select.id == "indomain_max") {
1027  strategy->set_domain_reduction_strategy(
1029  } else if (select.id == "indomain_split") {
1030  strategy->set_domain_reduction_strategy(
1032  } else if (select.id == "indomain_reverse_split") {
1033  strategy->set_domain_reduction_strategy(
1035  } else if (select.id == "indomain_median") {
1036  strategy->set_domain_reduction_strategy(
1038  } else {
1039  LOG(FATAL) << "Unsupported select: " << select.id;
1040  }
1041  }
1042  }
1043 }
1044 
1045 // The format is fixed in the flatzinc specification.
1046 std::string SolutionString(
1047  const fz::SolutionOutputSpecs& output,
1048  const std::function<int64_t(fz::Variable*)>& value_func) {
1049  if (output.variable != nullptr) {
1050  const int64_t value = value_func(output.variable);
1051  if (output.display_as_boolean) {
1052  return absl::StrCat(output.name, " = ", value == 1 ? "true" : "false",
1053  ";");
1054  } else {
1055  return absl::StrCat(output.name, " = ", value, ";");
1056  }
1057  } else {
1058  const int bound_size = output.bounds.size();
1059  std::string result =
1060  absl::StrCat(output.name, " = array", bound_size, "d(");
1061  for (int i = 0; i < bound_size; ++i) {
1062  if (output.bounds[i].max_value >= output.bounds[i].min_value) {
1063  absl::StrAppend(&result, output.bounds[i].min_value, "..",
1064  output.bounds[i].max_value, ", ");
1065  } else {
1066  result.append("{},");
1067  }
1068  }
1069  result.append("[");
1070  for (int i = 0; i < output.flat_variables.size(); ++i) {
1071  const int64_t value = value_func(output.flat_variables[i]);
1072  if (output.display_as_boolean) {
1073  result.append(value ? "true" : "false");
1074  } else {
1075  absl::StrAppend(&result, value);
1076  }
1077  if (i != output.flat_variables.size() - 1) {
1078  result.append(", ");
1079  }
1080  }
1081  result.append("]);");
1082  return result;
1083  }
1084  return "";
1085 }
1086 
1087 std::string SolutionString(
1088  const fz::Model& model,
1089  const std::function<int64_t(fz::Variable*)>& value_func) {
1090  std::string solution_string;
1091  for (const auto& output_spec : model.output()) {
1092  solution_string.append(SolutionString(output_spec, value_func));
1093  solution_string.append("\n");
1094  }
1095  return solution_string;
1096 }
1097 
1098 void OutputFlatzincStats(const CpSolverResponse& response,
1099  SolverLogger* solution_logger) {
1100  SOLVER_LOG(solution_logger,
1101  "%%%mzn-stat: objective=", response.objective_value());
1102  SOLVER_LOG(solution_logger,
1103  "%%%mzn-stat: objectiveBound=", response.best_objective_bound());
1104  SOLVER_LOG(solution_logger,
1105  "%%%mzn-stat: boolVariables=", response.num_booleans());
1106  SOLVER_LOG(solution_logger,
1107  "%%%mzn-stat: failures=", response.num_conflicts());
1108  SOLVER_LOG(
1109  solution_logger, "%%%mzn-stat: propagations=",
1110  response.num_binary_propagations() + response.num_integer_propagations());
1111  SOLVER_LOG(solution_logger, "%%%mzn-stat: solveTime=", response.wall_time());
1112 }
1113 
1114 } // namespace
1115 
1116 void SolveFzWithCpModelProto(const fz::Model& fz_model,
1117  const fz::FlatzincSatParameters& p,
1118  const std::string& sat_params,
1119  SolverLogger* logger,
1120  SolverLogger* solution_logger) {
1121  CpModelProtoWithMapping m;
1122  m.proto.set_name(fz_model.name());
1123 
1124  // The translation is easy, we create one variable per flatzinc variable,
1125  // plus eventually a bunch of constant variables that will be created
1126  // lazily.
1127  int num_variables = 0;
1128  for (fz::Variable* fz_var : fz_model.variables()) {
1129  if (!fz_var->active) continue;
1130  CHECK(!fz_var->domain.is_float)
1131  << "CP-SAT does not support float variables";
1132 
1133  m.fz_var_to_index[fz_var] = num_variables++;
1134  IntegerVariableProto* var = m.proto.add_variables();
1135  var->set_name(fz_var->name);
1136  if (fz_var->domain.is_interval) {
1137  if (fz_var->domain.values.empty()) {
1138  // The CP-SAT solver checks that constraints cannot overflow during
1139  // their propagation. Because of that, we trim undefined variable
1140  // domains (i.e. int in minizinc) to something hopefully large enough.
1141  LOG_FIRST_N(WARNING, 1)
1142  << "Using flag --fz_int_max for unbounded integer variables.";
1143  LOG_FIRST_N(WARNING, 1)
1144  << " actual domain is [" << -absl::GetFlag(FLAGS_fz_int_max)
1145  << ".." << absl::GetFlag(FLAGS_fz_int_max) << "]";
1146  var->add_domain(-absl::GetFlag(FLAGS_fz_int_max));
1147  var->add_domain(absl::GetFlag(FLAGS_fz_int_max));
1148  } else {
1149  var->add_domain(fz_var->domain.values[0]);
1150  var->add_domain(fz_var->domain.values[1]);
1151  }
1152  } else {
1153  FillDomainInProto(Domain::FromValues(fz_var->domain.values), var);
1154  }
1155  }
1156 
1157  // Translate the constraints.
1158  for (fz::Constraint* fz_ct : fz_model.constraints()) {
1159  if (fz_ct == nullptr || !fz_ct->active) continue;
1160  ConstraintProto* ct = m.proto.add_constraints();
1161  ct->set_name(fz_ct->type);
1162  if (absl::EndsWith(fz_ct->type, "_reif") ||
1163  absl::EndsWith(fz_ct->type, "_imp") || fz_ct->type == "array_bool_or" ||
1164  fz_ct->type == "array_bool_and") {
1165  m.FillReifOrImpliedConstraint(*fz_ct, ct);
1166  } else {
1167  m.FillConstraint(*fz_ct, ct);
1168  }
1169  }
1170 
1171  // Fill the objective.
1172  if (fz_model.objective() != nullptr) {
1173  CpObjectiveProto* objective = m.proto.mutable_objective();
1174  objective->add_coeffs(1);
1175  if (fz_model.maximize()) {
1176  objective->set_scaling_factor(-1);
1177  objective->add_vars(
1178  NegatedCpModelVariable(m.fz_var_to_index[fz_model.objective()]));
1179  } else {
1180  objective->add_vars(m.fz_var_to_index[fz_model.objective()]);
1181  }
1182  }
1183 
1184  // Fill the search order.
1185  m.TranslateSearchAnnotations(fz_model.search_annotations());
1186 
1187  if (p.display_all_solutions && !m.proto.has_objective()) {
1188  // Enumerate all sat solutions.
1189  m.parameters.set_enumerate_all_solutions(true);
1190  }
1191 
1192  m.parameters.set_log_search_progress(p.log_search_progress);
1193 
1194  // Helps with challenge unit tests.
1195  m.parameters.set_max_domain_size_when_encoding_eq_neq_constraints(32);
1196 
1197  // Computes the number of workers.
1198  int num_workers = 1;
1199  if (p.display_all_solutions && fz_model.objective() == nullptr) {
1200  if (p.number_of_threads > 1) {
1201  // We don't support enumerating all solution in parallel for a SAT
1202  // problem. But note that we do support it for an optimization problem
1203  // since the meaning of p.all_solutions is not the same in this case.
1204  SOLVER_LOG(logger,
1205  "Search for all solutions of a SAT problem in parallel is not "
1206  "supported. Switching back to sequential search.");
1207  }
1208  } else if (p.number_of_threads <= 0) {
1209  // TODO(user): Supports setting the number of workers to 0, which will
1210  // then query the number of cores available. This is complex now as we
1211  // need to still support the expected behabior (no flags -> 1 thread
1212  // fixed search, -f -> 1 thread free search).
1213  SOLVER_LOG(logger,
1214  "The number of search workers, is not specified. For better "
1215  "performances, please set the number of workers to 8, 16, or "
1216  "more depending on the number of cores of your computer.");
1217  } else {
1218  num_workers = p.number_of_threads;
1219  }
1220  m.parameters.set_num_search_workers(num_workers);
1221 
1222  // Specifies single thread specific search modes.
1223  if (num_workers == 1) {
1224  if (p.use_free_search) {
1225  m.parameters.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
1226  m.parameters.set_interleave_search(true);
1227  m.parameters.set_reduce_memory_usage_in_interleave_mode(true);
1228  } else {
1229  m.parameters.set_search_branching(SatParameters::FIXED_SEARCH);
1230  m.parameters.set_keep_all_feasible_solutions_in_presolve(true);
1231  }
1232  }
1233 
1234  // Time limit.
1235  if (p.max_time_in_seconds > 0) {
1236  m.parameters.set_max_time_in_seconds(p.max_time_in_seconds);
1237  }
1238 
1239  // The order is important, we want the flag parameters to overwrite anything
1240  // set in m.parameters.
1241  sat::SatParameters flag_parameters;
1242  CHECK(google::protobuf::TextFormat::ParseFromString(sat_params,
1243  &flag_parameters))
1244  << sat_params;
1245  m.parameters.MergeFrom(flag_parameters);
1246 
1247  // We only need an observer if 'p.all_solutions' is true.
1248  std::function<void(const CpSolverResponse&)> solution_observer = nullptr;
1249  if (p.display_all_solutions) {
1250  solution_observer = [&fz_model, &m, &p,
1251  solution_logger](const CpSolverResponse& r) {
1252  const std::string solution_string =
1253  SolutionString(fz_model, [&m, &r](fz::Variable* v) {
1254  return r.solution(gtl::FindOrDie(m.fz_var_to_index, v));
1255  });
1256  SOLVER_LOG(solution_logger, solution_string);
1257  if (p.display_statistics) {
1258  OutputFlatzincStats(r, solution_logger);
1259  }
1260  SOLVER_LOG(solution_logger, "----------");
1261  };
1262  }
1263 
1264  Model sat_model;
1265  sat_model.Add(NewSatParameters(m.parameters));
1266  if (solution_observer != nullptr) {
1267  sat_model.Add(NewFeasibleSolutionObserver(solution_observer));
1268  }
1269  // Setup logging.
1270  sat_model.GetOrCreate<SatParameters>()->set_log_to_stdout(false);
1271  sat_model.Register<SolverLogger>(logger);
1272 
1273  const CpSolverResponse response = SolveCpModel(m.proto, &sat_model);
1274 
1275  // Check the returned solution with the fz model checker.
1276  if (response.status() == CpSolverStatus::FEASIBLE ||
1277  response.status() == CpSolverStatus::OPTIMAL) {
1279  fz_model,
1280  [&response, &m](fz::Variable* v) {
1281  return response.solution(gtl::FindOrDie(m.fz_var_to_index, v));
1282  },
1283  logger));
1284  }
1285 
1286  // Output the solution in the flatzinc official format.
1287  if (solution_logger->LoggingIsEnabled()) {
1288  if (response.status() == CpSolverStatus::FEASIBLE ||
1289  response.status() == CpSolverStatus::OPTIMAL) {
1290  if (!p.display_all_solutions) { // Already printed otherwise.
1291  const std::string solution_string =
1292  SolutionString(fz_model, [&response, &m](fz::Variable* v) {
1293  return response.solution(gtl::FindOrDie(m.fz_var_to_index, v));
1294  });
1295  SOLVER_LOG(solution_logger, solution_string);
1296  SOLVER_LOG(solution_logger, "----------");
1297  }
1298  if (response.status() == CpSolverStatus::OPTIMAL) {
1299  SOLVER_LOG(solution_logger, "==========");
1300  }
1301  } else if (response.status() == CpSolverStatus::INFEASIBLE) {
1302  SOLVER_LOG(solution_logger, "=====UNSATISFIABLE=====");
1303  } else if (response.status() == CpSolverStatus::MODEL_INVALID) {
1304  const std::string error_message = ValidateCpModel(m.proto);
1305  VLOG(1) << "%% Error message = '" << error_message << "'";
1306  if (absl::StrContains(error_message, "overflow")) {
1307  SOLVER_LOG(solution_logger, "=====OVERFLOW=====");
1308  } else {
1309  SOLVER_LOG(solution_logger, "=====MODEL INVALID=====");
1310  }
1311  } else {
1312  SOLVER_LOG(solution_logger, "%% TIMEOUT");
1313  }
1314  if (p.display_statistics) {
1315  OutputFlatzincStats(response, solution_logger);
1316  }
1317  }
1318 }
1319 
1320 } // namespace sat
1321 } // namespace operations_research
int64_t head
#define CHECK(condition)
Definition: base/logging.h:495
static constexpr DomainReductionStrategy SELECT_MIN_VALUE
Definition: cp_model.pb.h:5266
void SolveFzWithCpModelProto(const fz::Model &fz_model, const fz::FlatzincSatParameters &p, const std::string &sat_params, SolverLogger *logger, SolverLogger *solution_logger)
int64_t min
Definition: alldiff_cst.cc:139
int var
#define SOLVER_LOG(logger,...)
Definition: util/logging.h:69
#define CHECK_GE(val1, val2)
Definition: base/logging.h:706
const int FATAL
Definition: log_severity.h:32
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
static constexpr DomainReductionStrategy SELECT_MEDIAN_VALUE
Definition: cp_model.pb.h:5274
static constexpr VariableSelectionStrategy CHOOSE_LOWEST_MIN
Definition: cp_model.pb.h:5232
static Domain FromIntervals(absl::Span< const ClosedInterval > intervals)
Creates a domain from the union of an unsorted list of intervals.
ABSL_FLAG(int64_t, fz_int_max, int64_t{1}<< 50, "Default max value for unbounded integer variables.")
#define VLOG(verboselevel)
Definition: base/logging.h:983
std::vector< int64_t > AllValuesInDomain(const ProtoWithDomain &proto)
void FlattenAnnotations(const Annotation &ann, std::vector< Annotation > *out)
Definition: model.cc:1140
T * GetOrCreate()
Returns an object of type T that is unique to this model (like a "local" singleton).
Definition: sat/model.h:106
#define LOG(severity)
Definition: base/logging.h:420
GRBmodel * model
static constexpr VariableSelectionStrategy CHOOSE_HIGHEST_MAX
Definition: cp_model.pb.h:5234
::operations_research::sat::IntegerVariableProto * add_variables()
int64_t tail
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){....
static constexpr VariableSelectionStrategy CHOOSE_FIRST
Definition: cp_model.pb.h:5230
int64_t b
#define CHECK_LT(val1, val2)
Definition: base/logging.h:705
ReverseView< Container > reversed_view(const Container &c)
int64_t max
Definition: alldiff_cst.cc:140
Block * next
static constexpr VariableSelectionStrategy CHOOSE_MAX_DOMAIN_SIZE
Definition: cp_model.pb.h:5238
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
int64_t weight
Definition: pack.cc:510
CpModelProto proto
const int WARNING
Definition: log_severity.h:31
static Domain FromValues(std::vector< int64_t > values)
Creates a domain from the union of an unsorted list of integer values.
std::string ValidateCpModel(const CpModelProto &model, bool after_presolve)
const std::string & name() const
::operations_research::sat::LinearConstraintProto * mutable_linear()
int64_t demand
Definition: resource.cc:125
::operations_research::sat::ConstraintProto * add_constraints()
const Collection::value_type::second_type & FindOrDie(const Collection &collection, const typename Collection::value_type::first_type &key)
Definition: map_util.h:206
int index
Definition: pack.cc:509
absl::flat_hash_map< int64_t, int > constant_value_to_index
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
const std::vector< Constraint * > & constraints() const
SharedResponseManager * response
static constexpr DomainReductionStrategy SELECT_MAX_VALUE
Definition: cp_model.pb.h:5268
static constexpr SearchBranching AUTOMATIC_SEARCH
absl::flat_hash_map< std::tuple< int, int, int >, int > start_size_opt_tuple_to_interval
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:702
::operations_research::sat::DecisionStrategyProto * add_search_strategy()
static constexpr DomainReductionStrategy SELECT_UPPER_HALF
Definition: cp_model.pb.h:5272
void set_name(ArgT0 &&arg0, ArgT... args)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
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:81
static constexpr VariableSelectionStrategy CHOOSE_MIN_DOMAIN_SIZE
Definition: cp_model.pb.h:5236
#define LOG_FIRST_N(severity, n)
Definition: base/logging.h:854
absl::flat_hash_map< fz::Variable *, int > fz_var_to_index
int64_t value
bool CheckSolution(const Model &model, const std::function< int64_t(Variable *)> &evaluator, SolverLogger *logger)
Definition: checker.cc:1236
static constexpr SearchBranching FIXED_SEARCH
Collection of objects used to extend the Constraint Solver library.
SatParameters parameters
const std::vector< Annotation > & search_annotations() const
static constexpr DomainReductionStrategy SELECT_LOWER_HALF
Definition: cp_model.pb.h:5270
const std::vector< Variable * > & variables() const
absl::flat_hash_map< std::tuple< int, int64_t, int >, int > start_fixed_size_opt_tuple_to_interval
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
void Register(T *non_owned_class)
Register a non-owned class that will be "singleton" in the model.
Definition: sat/model.h:169
::operations_research::sat::IntegerVariableProto * mutable_variables(int index)
Literal literal
Definition: optimization.cc:85
IntervalVar * interval
Definition: resource.cc:100
const Constraint * ct
std::function< SatParameters(Model *)> NewSatParameters(const std::string &params)
Creates parameters for the solver, which you can add to the model with.
int64_t a
CpSolverResponse SolveCpModel(const CpModelProto &model_proto, Model *model)
Solves the given CpModelProto.