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