OR-Tools  8.0
cp_model_fz_solver.cc
Go to the documentation of this file.
1 // Copyright 2010-2018 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 <limits>
19 #include <tuple>
20 
21 #include "absl/container/flat_hash_map.h"
22 #include "absl/strings/str_cat.h"
23 #include "absl/strings/str_format.h"
24 #include "absl/strings/str_split.h"
25 #include "absl/synchronization/mutex.h"
26 #include "google/protobuf/text_format.h"
28 #include "ortools/base/map_util.h"
30 #include "ortools/base/timer.h"
33 #include "ortools/flatzinc/model.h"
39 #include "ortools/sat/cumulative.h"
41 #include "ortools/sat/integer.h"
43 #include "ortools/sat/intervals.h"
44 #include "ortools/sat/model.h"
46 #include "ortools/sat/sat_solver.h"
47 #include "ortools/sat/table.h"
48 
49 DEFINE_bool(use_flatzinc_format, true, "Output uses the flatzinc format");
50 DEFINE_int64(fz_int_max, int64{1} << 50,
51  "Default max value for unbounded integer variables.");
52 
53 namespace operations_research {
54 namespace sat {
55 
56 namespace {
57 
58 // Returns the true/false literal corresponding to a CpModelProto variable.
59 int TrueLiteral(int var) { return var; }
60 int FalseLiteral(int var) { return -var - 1; }
61 int NegatedCpModelVariable(int var) { return -var - 1; }
62 
63 // Helper class to convert a flatzinc model to a CpModelProto.
64 struct CpModelProtoWithMapping {
65  // Returns a constant CpModelProto variable created on-demand.
66  int LookupConstant(int64 value);
67 
68  // Convert a flatzinc argument to a variable or a list of variable.
69  // Note that we always encode a constant argument with a constant variable.
70  int LookupVar(const fz::Argument& argument);
71  std::vector<int> LookupVars(const fz::Argument& argument);
72 
73  // Create and return the indices of the IntervalConstraint corresponding
74  // to the flatzinc "interval" specified by a start var and a duration var.
75  // This method will cache intervals with the key <start, duration>.
76  std::vector<int> CreateIntervals(const std::vector<int>& starts,
77  const std::vector<int>& durations);
78 
79  // Create and return the index of the IntervalConstraint corresponding
80  // to the flatzinc "interval" specified by a start var and a size var.
81  // This method will cache intervals with the key <start_var, size_var>.
82  int GetOrCreateInterval(int start_var, int size_var);
83 
84  // Create and return the index of the optional IntervalConstraint
85  // corresponding to the flatzinc "interval" specified by a start var, the
86  // size_var, and the Boolean opt_var. This method will cache intervals with
87  // the key <start, duration, opt_var>.
88  int GetOrCreateOptionalInterval(int start_var, int size_var, int opt_var);
89 
90  // Helpers to fill a ConstraintProto.
91  void FillAMinusBInDomain(const std::vector<int64>& domain,
92  const fz::Constraint& fz_ct, ConstraintProto* ct);
93  void FillLinearConstraintWithGivenDomain(const std::vector<int64>& domain,
94  const fz::Constraint& fz_ct,
95  ConstraintProto* ct);
96  void FillConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
97  void FillReifOrImpliedConstraint(const fz::Constraint& fz_ct,
98  ConstraintProto* ct);
99 
100  // Translates the flatzinc search annotations into the CpModelProto
101  // search_order field.
102  void TranslateSearchAnnotations(
103  const std::vector<fz::Annotation>& search_annotations);
104 
105  // The output proto.
106  CpModelProto proto;
107  SatParameters parameters;
108 
109  // Mapping from flatzinc variables to CpModelProto variables.
110  absl::flat_hash_map<fz::IntegerVariable*, int> fz_var_to_index;
111  absl::flat_hash_map<int64, int> constant_value_to_index;
112  absl::flat_hash_map<std::tuple<int, int, int>, int>
114 };
115 
116 int CpModelProtoWithMapping::LookupConstant(int64 value) {
119  }
120 
121  // Create the constant on the fly.
122  const int index = proto.variables_size();
123  IntegerVariableProto* var_proto = proto.add_variables();
124  var_proto->add_domain(value);
125  var_proto->add_domain(value);
127  return index;
128 }
129 
130 int CpModelProtoWithMapping::LookupVar(const fz::Argument& argument) {
131  if (argument.HasOneValue()) return LookupConstant(argument.Value());
132  CHECK_EQ(argument.type, fz::Argument::INT_VAR_REF);
133  return fz_var_to_index[argument.Var()];
134 }
135 
136 std::vector<int> CpModelProtoWithMapping::LookupVars(
137  const fz::Argument& argument) {
138  std::vector<int> result;
139  if (argument.type == fz::Argument::VOID_ARGUMENT) return result;
140  if (argument.type == fz::Argument::INT_LIST) {
141  for (int64 value : argument.values) {
142  result.push_back(LookupConstant(value));
143  }
144 
145  } else if (argument.type == fz::Argument::INT_VALUE) {
146  result.push_back(LookupConstant(argument.Value()));
147  } else {
148  CHECK_EQ(argument.type, fz::Argument::INT_VAR_REF_ARRAY);
149  for (fz::IntegerVariable* var : argument.variables) {
150  CHECK(var != nullptr);
151  result.push_back(fz_var_to_index[var]);
152  }
153  }
154  return result;
155 }
156 
157 int CpModelProtoWithMapping::GetOrCreateInterval(int start_var, int size_var) {
158  return GetOrCreateOptionalInterval(start_var, size_var, kint32max);
159 }
160 
161 int CpModelProtoWithMapping::GetOrCreateOptionalInterval(int start_var,
162  int size_var,
163  int opt_var) {
164  const std::tuple<int, int, int> key =
165  std::make_tuple(start_var, size_var, opt_var);
168  }
169  const int interval_index = proto.constraints_size();
170 
171  auto* ct = proto.add_constraints();
172  if (opt_var != kint32max) {
173  ct->add_enforcement_literal(opt_var);
174  }
175  auto* interval = ct->mutable_interval();
176  interval->set_start(start_var);
177  interval->set_size(size_var);
178 
179  interval->set_end(proto.variables_size());
180  auto* end_var = proto.add_variables();
181  const auto start_proto = proto.variables(start_var);
182  const auto size_proto = proto.variables(size_var);
183  end_var->add_domain(start_proto.domain(0) + size_proto.domain(0));
184  end_var->add_domain(start_proto.domain(start_proto.domain_size() - 1) +
185  size_proto.domain(size_proto.domain_size() - 1));
186  start_size_opt_tuple_to_interval[key] = interval_index;
187  return interval_index;
188 }
189 
190 std::vector<int> CpModelProtoWithMapping::CreateIntervals(
191  const std::vector<int>& starts, const std::vector<int>& durations) {
192  std::vector<int> intervals;
193  for (int i = 0; i < starts.size(); ++i) {
194  intervals.push_back(GetOrCreateInterval(starts[i], durations[i]));
195  }
196  return intervals;
197 }
198 
199 void CpModelProtoWithMapping::FillAMinusBInDomain(
200  const std::vector<int64>& domain, const fz::Constraint& fz_ct,
201  ConstraintProto* ct) {
202  auto* arg = ct->mutable_linear();
203  if (fz_ct.arguments[1].type == fz::Argument::INT_VALUE) {
204  const int64 value = fz_ct.arguments[1].Value();
205  const int var_a = LookupVar(fz_ct.arguments[0]);
206  for (const int64 domain_bound : domain) {
207  if (domain_bound == kint64min || domain_bound == kint64max) {
208  arg->add_domain(domain_bound);
209  } else {
210  arg->add_domain(domain_bound + value);
211  }
212  }
213  arg->add_vars(var_a);
214  arg->add_coeffs(1);
215  } else if (fz_ct.arguments[0].type == fz::Argument::INT_VALUE) {
216  const int64 value = fz_ct.arguments[0].Value();
217  const int var_b = LookupVar(fz_ct.arguments[1]);
218  for (int64 domain_bound : gtl::reversed_view(domain)) {
219  if (domain_bound == kint64min) {
220  arg->add_domain(kint64max);
221  } else if (domain_bound == kint64max) {
222  arg->add_domain(kint64min);
223  } else {
224  arg->add_domain(value - domain_bound);
225  }
226  }
227  arg->add_vars(var_b);
228  arg->add_coeffs(1);
229  } else {
230  for (const int64 domain_bound : domain) arg->add_domain(domain_bound);
231  arg->add_vars(LookupVar(fz_ct.arguments[0]));
232  arg->add_coeffs(1);
233  arg->add_vars(LookupVar(fz_ct.arguments[1]));
234  arg->add_coeffs(-1);
235  }
236 }
237 
238 void CpModelProtoWithMapping::FillLinearConstraintWithGivenDomain(
239  const std::vector<int64>& domain, const fz::Constraint& fz_ct,
240  ConstraintProto* ct) {
241  auto* arg = ct->mutable_linear();
242  for (const int64 domain_bound : domain) arg->add_domain(domain_bound);
243  std::vector<int> vars = LookupVars(fz_ct.arguments[1]);
244  for (int i = 0; i < vars.size(); ++i) {
245  arg->add_vars(vars[i]);
246  arg->add_coeffs(fz_ct.arguments[0].values[i]);
247  }
248 }
249 
250 void CpModelProtoWithMapping::FillConstraint(const fz::Constraint& fz_ct,
251  ConstraintProto* ct) {
252  if (fz_ct.type == "false_constraint") {
253  // An empty clause is always false.
254  ct->mutable_bool_or();
255  } else if (fz_ct.type == "bool_clause") {
256  auto* arg = ct->mutable_bool_or();
257  for (const int var : LookupVars(fz_ct.arguments[0])) {
258  arg->add_literals(TrueLiteral(var));
259  }
260  for (const int var : LookupVars(fz_ct.arguments[1])) {
261  arg->add_literals(FalseLiteral(var));
262  }
263  } else if (fz_ct.type == "bool_xor") {
264  // This is not the same semantics as the array_bool_xor as this constraint
265  // is actually a fully reified xor(a, b) <==> x.
266  const int a = LookupVar(fz_ct.arguments[0]);
267  const int b = LookupVar(fz_ct.arguments[1]);
268  const int x = LookupVar(fz_ct.arguments[2]);
269 
270  // not(x) => a == b
271  ct->add_enforcement_literal(NegatedRef(x));
272  auto* const refute = ct->mutable_linear();
273  refute->add_vars(a);
274  refute->add_coeffs(1);
275  refute->add_vars(b);
276  refute->add_coeffs(-1);
277  refute->add_domain(0);
278  refute->add_domain(0);
279 
280  // x => a + b == 1
281  auto* ct2 = proto.add_constraints();
282  ct2->add_enforcement_literal(x);
283  auto* const enforce = ct2->mutable_linear();
284  enforce->add_vars(a);
285  enforce->add_coeffs(1);
286  enforce->add_vars(b);
287  enforce->add_coeffs(1);
288  enforce->add_domain(1);
289  enforce->add_domain(1);
290  } else if (fz_ct.type == "array_bool_or") {
291  auto* arg = ct->mutable_bool_or();
292  for (const int var : LookupVars(fz_ct.arguments[0])) {
293  arg->add_literals(TrueLiteral(var));
294  }
295  } else if (fz_ct.type == "array_bool_or_negated") {
296  auto* arg = ct->mutable_bool_and();
297  for (const int var : LookupVars(fz_ct.arguments[0])) {
298  arg->add_literals(FalseLiteral(var));
299  }
300  } else if (fz_ct.type == "array_bool_and") {
301  auto* arg = ct->mutable_bool_and();
302  for (const int var : LookupVars(fz_ct.arguments[0])) {
303  arg->add_literals(TrueLiteral(var));
304  }
305  } else if (fz_ct.type == "array_bool_and_negated") {
306  auto* arg = ct->mutable_bool_or();
307  for (const int var : LookupVars(fz_ct.arguments[0])) {
308  arg->add_literals(FalseLiteral(var));
309  }
310  } else if (fz_ct.type == "array_bool_xor") {
311  auto* arg = ct->mutable_bool_xor();
312  for (const int var : LookupVars(fz_ct.arguments[0])) {
313  arg->add_literals(TrueLiteral(var));
314  }
315  } else if (fz_ct.type == "bool_le" || fz_ct.type == "int_le") {
316  FillAMinusBInDomain({kint64min, 0}, fz_ct, ct);
317  } else if (fz_ct.type == "bool_ge" || fz_ct.type == "int_ge") {
318  FillAMinusBInDomain({0, kint64max}, fz_ct, ct);
319  } else if (fz_ct.type == "bool_lt" || fz_ct.type == "int_lt") {
320  FillAMinusBInDomain({kint64min, -1}, fz_ct, ct);
321  } else if (fz_ct.type == "bool_gt" || fz_ct.type == "int_gt") {
322  FillAMinusBInDomain({1, kint64max}, fz_ct, ct);
323  } else if (fz_ct.type == "bool_eq" || fz_ct.type == "int_eq" ||
324  fz_ct.type == "bool2int") {
325  FillAMinusBInDomain({0, 0}, fz_ct, ct);
326  } else if (fz_ct.type == "bool_ne" || fz_ct.type == "bool_not") {
327  auto* arg = ct->mutable_linear();
328  arg->add_vars(LookupVar(fz_ct.arguments[0]));
329  arg->add_coeffs(1);
330  arg->add_vars(LookupVar(fz_ct.arguments[1]));
331  arg->add_coeffs(1);
332  arg->add_domain(1);
333  arg->add_domain(1);
334  } else if (fz_ct.type == "int_ne") {
335  FillAMinusBInDomain({kint64min, -1, 1, kint64max}, fz_ct, ct);
336  } else if (fz_ct.type == "int_lin_eq") {
337  const int64 rhs = fz_ct.arguments[2].values[0];
338  FillLinearConstraintWithGivenDomain({rhs, rhs}, fz_ct, ct);
339  } else if (fz_ct.type == "bool_lin_eq") {
340  auto* arg = ct->mutable_linear();
341  const std::vector<int> vars = LookupVars(fz_ct.arguments[1]);
342  for (int i = 0; i < vars.size(); ++i) {
343  arg->add_vars(vars[i]);
344  arg->add_coeffs(fz_ct.arguments[0].values[i]);
345  }
346  if (fz_ct.arguments[2].IsVariable()) {
347  arg->add_vars(LookupVar(fz_ct.arguments[2]));
348  arg->add_coeffs(-1);
349  arg->add_domain(0);
350  arg->add_domain(0);
351  } else {
352  const int64 v = fz_ct.arguments[2].Value();
353  arg->add_domain(v);
354  arg->add_domain(v);
355  }
356  } else if (fz_ct.type == "int_lin_le" || fz_ct.type == "bool_lin_le") {
357  const int64 rhs = fz_ct.arguments[2].values[0];
358  FillLinearConstraintWithGivenDomain({kint64min, rhs}, fz_ct, ct);
359  } else if (fz_ct.type == "int_lin_lt") {
360  const int64 rhs = fz_ct.arguments[2].values[0];
361  FillLinearConstraintWithGivenDomain({kint64min, rhs - 1}, fz_ct, ct);
362  } else if (fz_ct.type == "int_lin_ge") {
363  const int64 rhs = fz_ct.arguments[2].values[0];
364  FillLinearConstraintWithGivenDomain({rhs, kint64max}, fz_ct, ct);
365  } else if (fz_ct.type == "int_lin_gt") {
366  const int64 rhs = fz_ct.arguments[2].values[0];
367  FillLinearConstraintWithGivenDomain({rhs + 1, kint64max}, fz_ct, ct);
368  } else if (fz_ct.type == "int_lin_ne") {
369  const int64 rhs = fz_ct.arguments[2].values[0];
370  FillLinearConstraintWithGivenDomain(
371  {kint64min, rhs - 1, rhs + 1, kint64max}, fz_ct, ct);
372  } else if (fz_ct.type == "set_in") {
373  auto* arg = ct->mutable_linear();
374  arg->add_vars(LookupVar(fz_ct.arguments[0]));
375  arg->add_coeffs(1);
376  if (fz_ct.arguments[1].type == fz::Argument::INT_LIST) {
377  FillDomainInProto(Domain::FromValues(std::vector<int64>{
378  fz_ct.arguments[1].values.begin(),
379  fz_ct.arguments[1].values.end()}),
380  arg);
381  } else if (fz_ct.arguments[1].type == fz::Argument::INT_INTERVAL) {
383  Domain(fz_ct.arguments[1].values[0], fz_ct.arguments[1].values[1]),
384  arg);
385  } else {
386  LOG(FATAL) << "Wrong format";
387  }
388  } else if (fz_ct.type == "set_in_negated") {
389  auto* arg = ct->mutable_linear();
390  arg->add_vars(LookupVar(fz_ct.arguments[0]));
391  arg->add_coeffs(1);
392  if (fz_ct.arguments[1].type == fz::Argument::INT_LIST) {
395  std::vector<int64>{fz_ct.arguments[1].values.begin(),
396  fz_ct.arguments[1].values.end()})
397  .Complement(),
398  arg);
399  } else if (fz_ct.arguments[1].type == fz::Argument::INT_INTERVAL) {
401  Domain(fz_ct.arguments[1].values[0], fz_ct.arguments[1].values[1])
402  .Complement(),
403  arg);
404  } else {
405  LOG(FATAL) << "Wrong format";
406  }
407  } else if (fz_ct.type == "int_min") {
408  auto* arg = ct->mutable_int_min();
409  arg->set_target(LookupVar(fz_ct.arguments[2]));
410  arg->add_vars(LookupVar(fz_ct.arguments[0]));
411  arg->add_vars(LookupVar(fz_ct.arguments[1]));
412  } else if (fz_ct.type == "array_int_minimum" || fz_ct.type == "minimum_int") {
413  auto* arg = ct->mutable_int_min();
414  arg->set_target(LookupVar(fz_ct.arguments[0]));
415  for (const int var : LookupVars(fz_ct.arguments[1])) arg->add_vars(var);
416  } else if (fz_ct.type == "int_max") {
417  auto* arg = ct->mutable_int_max();
418  arg->set_target(LookupVar(fz_ct.arguments[2]));
419  arg->add_vars(LookupVar(fz_ct.arguments[0]));
420  arg->add_vars(LookupVar(fz_ct.arguments[1]));
421  } else if (fz_ct.type == "array_int_maximum" || fz_ct.type == "maximum_int") {
422  auto* arg = ct->mutable_int_max();
423  arg->set_target(LookupVar(fz_ct.arguments[0]));
424  for (const int var : LookupVars(fz_ct.arguments[1])) arg->add_vars(var);
425  } else if (fz_ct.type == "int_times") {
426  auto* arg = ct->mutable_int_prod();
427  arg->set_target(LookupVar(fz_ct.arguments[2]));
428  arg->add_vars(LookupVar(fz_ct.arguments[0]));
429  arg->add_vars(LookupVar(fz_ct.arguments[1]));
430  } else if (fz_ct.type == "int_abs") {
431  auto* arg = ct->mutable_int_max();
432  arg->set_target(LookupVar(fz_ct.arguments[1]));
433  arg->add_vars(LookupVar(fz_ct.arguments[0]));
434  arg->add_vars(-LookupVar(fz_ct.arguments[0]) - 1);
435  } else if (fz_ct.type == "int_plus") {
436  auto* arg = ct->mutable_linear();
437  FillDomainInProto(Domain(0, 0), arg);
438  arg->add_vars(LookupVar(fz_ct.arguments[0]));
439  arg->add_coeffs(1);
440  arg->add_vars(LookupVar(fz_ct.arguments[1]));
441  arg->add_coeffs(1);
442  arg->add_vars(LookupVar(fz_ct.arguments[2]));
443  arg->add_coeffs(-1);
444  } else if (fz_ct.type == "int_div") {
445  auto* arg = ct->mutable_int_div();
446  arg->add_vars(LookupVar(fz_ct.arguments[0]));
447  arg->add_vars(LookupVar(fz_ct.arguments[1]));
448  arg->set_target(LookupVar(fz_ct.arguments[2]));
449  } else if (fz_ct.type == "int_mod") {
450  auto* arg = ct->mutable_int_mod();
451  arg->set_target(LookupVar(fz_ct.arguments[2]));
452  arg->add_vars(LookupVar(fz_ct.arguments[0]));
453  arg->add_vars(LookupVar(fz_ct.arguments[1]));
454  } else if (fz_ct.type == "array_int_element" ||
455  fz_ct.type == "array_bool_element" ||
456  fz_ct.type == "array_var_int_element" ||
457  fz_ct.type == "array_var_bool_element" ||
458  fz_ct.type == "array_int_element_nonshifted") {
459  if (fz_ct.arguments[0].type == fz::Argument::INT_VAR_REF ||
460  fz_ct.arguments[0].type == fz::Argument::INT_VALUE) {
461  auto* arg = ct->mutable_element();
462  arg->set_index(LookupVar(fz_ct.arguments[0]));
463  arg->set_target(LookupVar(fz_ct.arguments[2]));
464 
465  if (!absl::EndsWith(fz_ct.type, "_nonshifted")) {
466  // Add a dummy variable at position zero because flatzinc index start
467  // at 1.
468  // TODO(user): Make sure that zero is not in the index domain...
469  arg->add_vars(arg->target());
470  }
471  for (const int var : LookupVars(fz_ct.arguments[1])) arg->add_vars(var);
472  } else {
473  // Special case added by the presolve or in flatzinc. We encode this
474  // as a table constraint.
475  CHECK(!absl::EndsWith(fz_ct.type, "_nonshifted"));
476  auto* arg = ct->mutable_table();
477 
478  // the constraint is:
479  // values[coeff1 * vars[0] + coeff2 * vars[1] + offset] == target.
480  for (const int var : LookupVars(fz_ct.arguments[0])) arg->add_vars(var);
481  arg->add_vars(LookupVar(fz_ct.arguments[2])); // the target
482 
483  const std::vector<int64>& values = fz_ct.arguments[1].values;
484  const int64 coeff1 = fz_ct.arguments[3].values[0];
485  const int64 coeff2 = fz_ct.arguments[3].values[1];
486  const int64 offset = fz_ct.arguments[4].values[0] - 1;
487 
488  for (const int64 a : AllValuesInDomain(proto.variables(arg->vars(0)))) {
489  for (const int64 b : AllValuesInDomain(proto.variables(arg->vars(1)))) {
490  const int index = coeff1 * a + coeff2 * b + offset;
491  CHECK_GE(index, 0);
492  CHECK_LT(index, values.size());
493  arg->add_values(a);
494  arg->add_values(b);
495  arg->add_values(values[index]);
496  }
497  }
498  }
499  } else if (fz_ct.type == "table_int") {
500  auto* arg = ct->mutable_table();
501  for (const int var : LookupVars(fz_ct.arguments[0])) arg->add_vars(var);
502  for (const int64 value : fz_ct.arguments[1].values) arg->add_values(value);
503  } else if (fz_ct.type == "regular") {
504  auto* arg = ct->mutable_automaton();
505  for (const int var : LookupVars(fz_ct.arguments[0])) arg->add_vars(var);
506 
507  int count = 0;
508  const int num_states = fz_ct.arguments[1].Value();
509  const int num_values = fz_ct.arguments[2].Value();
510  for (int i = 1; i <= num_states; ++i) {
511  for (int j = 1; j <= num_values; ++j) {
512  CHECK_LT(count, fz_ct.arguments[3].values.size());
513  const int next = fz_ct.arguments[3].values[count++];
514  if (next == 0) continue; // 0 is a failing state.
515  arg->add_transition_tail(i);
516  arg->add_transition_label(j);
517  arg->add_transition_head(next);
518  }
519  }
520 
521  arg->set_starting_state(fz_ct.arguments[4].Value());
522  switch (fz_ct.arguments[5].type) {
524  arg->add_final_states(fz_ct.arguments[5].values[0]);
525  break;
526  }
528  for (int v = fz_ct.arguments[5].values[0];
529  v <= fz_ct.arguments[5].values[1]; ++v) {
530  arg->add_final_states(v);
531  }
532  break;
533  }
534  case fz::Argument::INT_LIST: {
535  for (const int v : fz_ct.arguments[5].values) {
536  arg->add_final_states(v);
537  }
538  break;
539  }
540  default: {
541  LOG(FATAL) << "Wrong constraint " << fz_ct.DebugString();
542  }
543  }
544  } else if (fz_ct.type == "all_different_int") {
545  auto* arg = ct->mutable_all_diff();
546  for (const int var : LookupVars(fz_ct.arguments[0])) arg->add_vars(var);
547  } else if (fz_ct.type == "circuit" || fz_ct.type == "subcircuit") {
548  // Try to auto-detect if it is zero or one based.
549  bool found_zero = false;
550  bool found_size = false;
551  const int size = fz_ct.arguments[0].variables.size();
552  for (fz::IntegerVariable* const var : fz_ct.arguments[0].variables) {
553  if (var->domain.Min() == 0) found_zero = true;
554  if (var->domain.Max() == size) found_size = true;
555  }
556  const bool is_one_based = !found_zero || found_size;
557  const int min_index = is_one_based ? 1 : 0;
558  const int max_index = min_index + fz_ct.arguments[0].variables.size() - 1;
559 
560  // The arc-based mutable circuit.
561  auto* circuit_arg = ct->mutable_circuit();
562 
563  // We fully encode all variables so we can use the literal based circuit.
564  // TODO(user): avoid fully encoding more than once?
565  int index = min_index;
566  const bool is_circuit = (fz_ct.type == "circuit");
567  for (const int var : LookupVars(fz_ct.arguments[0])) {
568  Domain domain = ReadDomainFromProto(proto.variables(var));
569 
570  // Restrict the domain of var to [min_index, max_index]
571  domain = domain.IntersectionWith(Domain(min_index, max_index));
572  if (is_circuit) {
573  // We simply make sure that the variable cannot take the value index.
574  domain = domain.IntersectionWith(Domain::FromIntervals(
575  {{kint64min, index - 1}, {index + 1, kint64max}}));
576  }
577  FillDomainInProto(domain, proto.mutable_variables(var));
578 
579  for (const ClosedInterval interval : domain.intervals()) {
580  for (int64 value = interval.start; value <= interval.end; ++value) {
581  // Create one Boolean variable for this arc.
582  const int literal = proto.variables_size();
583  {
584  auto* new_var = proto.add_variables();
585  new_var->add_domain(0);
586  new_var->add_domain(1);
587  }
588 
589  // Add the arc.
590  circuit_arg->add_tails(index);
591  circuit_arg->add_heads(value);
592  circuit_arg->add_literals(literal);
593 
594  // literal => var == value.
595  {
596  auto* ct = proto.add_constraints();
597  ct->add_enforcement_literal(literal);
598  ct->mutable_linear()->add_coeffs(1);
599  ct->mutable_linear()->add_vars(var);
600  ct->mutable_linear()->add_domain(value);
601  ct->mutable_linear()->add_domain(value);
602  }
603 
604  // not(literal) => var != value
605  {
606  auto* ct = proto.add_constraints();
607  ct->add_enforcement_literal(NegatedRef(literal));
608  ct->mutable_linear()->add_coeffs(1);
609  ct->mutable_linear()->add_vars(var);
610  ct->mutable_linear()->add_domain(kint64min);
611  ct->mutable_linear()->add_domain(value - 1);
612  ct->mutable_linear()->add_domain(value + 1);
613  ct->mutable_linear()->add_domain(kint64max);
614  }
615  }
616  }
617 
618  ++index;
619  }
620  } else if (fz_ct.type == "inverse") {
621  auto* arg = ct->mutable_inverse();
622 
623  const auto direct_variables = LookupVars(fz_ct.arguments[0]);
624  const auto inverse_variables = LookupVars(fz_ct.arguments[1]);
625 
626  const int num_variables =
627  std::min(direct_variables.size(), inverse_variables.size());
628 
629  // Try to auto-detect if it is zero or one based.
630  bool found_zero = false;
631  bool found_size = false;
632  for (fz::IntegerVariable* const var : fz_ct.arguments[0].variables) {
633  if (var->domain.Min() == 0) found_zero = true;
634  if (var->domain.Max() == num_variables) found_size = true;
635  }
636  for (fz::IntegerVariable* const var : fz_ct.arguments[1].variables) {
637  if (var->domain.Min() == 0) found_zero = true;
638  if (var->domain.Max() == num_variables) found_size = true;
639  }
640 
641  // Add a dummy constant variable at zero if the indexing is one based.
642  const bool is_one_based = !found_zero || found_size;
643  const int offset = is_one_based ? 1 : 0;
644 
645  if (is_one_based) arg->add_f_direct(LookupConstant(0));
646  for (const int var : direct_variables) {
647  arg->add_f_direct(var);
648  // Intersect domains with offset + [0, num_variables).
650  ReadDomainFromProto(proto.variables(var))
651  .IntersectionWith(Domain(offset, num_variables - 1 + offset)),
652  proto.mutable_variables(var));
653  }
654 
655  if (is_one_based) arg->add_f_inverse(LookupConstant(0));
656  for (const int var : inverse_variables) {
657  arg->add_f_inverse(var);
658  // Intersect domains with offset + [0, num_variables).
660  ReadDomainFromProto(proto.variables(var))
661  .IntersectionWith(Domain(offset, num_variables - 1 + offset)),
662  proto.mutable_variables(var));
663  }
664  } else if (fz_ct.type == "cumulative") {
665  const std::vector<int> starts = LookupVars(fz_ct.arguments[0]);
666  const std::vector<int> durations = LookupVars(fz_ct.arguments[1]);
667  const std::vector<int> demands = LookupVars(fz_ct.arguments[2]);
668  const int capacity = LookupVar(fz_ct.arguments[3]);
669 
670  auto* arg = ct->mutable_cumulative();
671  arg->set_capacity(capacity);
672  for (int i = 0; i < starts.size(); ++i) {
673  // Special case for a 0-1 demand, we mark the interval as optional
674  // instead and fix the demand to 1.
675  if (proto.variables(demands[i]).domain().size() == 2 &&
676  proto.variables(demands[i]).domain(0) == 0 &&
677  proto.variables(demands[i]).domain(1) == 1 &&
678  proto.variables(capacity).domain(1) == 1) {
679  arg->add_intervals(
680  GetOrCreateOptionalInterval(starts[i], durations[i], demands[i]));
681  arg->add_demands(LookupConstant(1));
682  } else {
683  arg->add_intervals(GetOrCreateInterval(starts[i], durations[i]));
684  arg->add_demands(demands[i]);
685  }
686  }
687  } else if (fz_ct.type == "diffn" || fz_ct.type == "diffn_nonstrict") {
688  const std::vector<int> x = LookupVars(fz_ct.arguments[0]);
689  const std::vector<int> y = LookupVars(fz_ct.arguments[1]);
690  const std::vector<int> dx = LookupVars(fz_ct.arguments[2]);
691  const std::vector<int> dy = LookupVars(fz_ct.arguments[3]);
692  const std::vector<int> x_intervals = CreateIntervals(x, dx);
693  const std::vector<int> y_intervals = CreateIntervals(y, dy);
694  auto* arg = ct->mutable_no_overlap_2d();
695  for (int i = 0; i < x.size(); ++i) {
696  arg->add_x_intervals(x_intervals[i]);
697  arg->add_y_intervals(y_intervals[i]);
698  }
699  arg->set_boxes_with_null_area_can_overlap(fz_ct.type == "diffn_nonstrict");
700  } else if (fz_ct.type == "network_flow" ||
701  fz_ct.type == "network_flow_cost") {
702  // Note that we leave ct empty here (with just the name set).
703  // We simply do a linear encoding of this constraint.
704  const bool has_cost = fz_ct.type == "network_flow_cost";
705  const std::vector<int> flow = LookupVars(fz_ct.arguments[has_cost ? 3 : 2]);
706 
707  // Flow conservation constraints.
708  const int num_nodes = fz_ct.arguments[1].values.size();
709  std::vector<std::vector<int>> flows_per_node(num_nodes);
710  std::vector<std::vector<int>> coeffs_per_node(num_nodes);
711  const int num_arcs = fz_ct.arguments[0].values.size() / 2;
712  for (int arc = 0; arc < num_arcs; arc++) {
713  const int tail = fz_ct.arguments[0].values[2 * arc] - 1;
714  const int head = fz_ct.arguments[0].values[2 * arc + 1] - 1;
715  if (tail == head) continue;
716 
717  flows_per_node[tail].push_back(flow[arc]);
718  coeffs_per_node[tail].push_back(1);
719  flows_per_node[head].push_back(flow[arc]);
720  coeffs_per_node[head].push_back(-1);
721  }
722  for (int node = 0; node < num_nodes; node++) {
723  auto* arg = proto.add_constraints()->mutable_linear();
724  arg->add_domain(fz_ct.arguments[1].values[node]);
725  arg->add_domain(fz_ct.arguments[1].values[node]);
726  for (int i = 0; i < flows_per_node[node].size(); ++i) {
727  arg->add_vars(flows_per_node[node][i]);
728  arg->add_coeffs(coeffs_per_node[node][i]);
729  }
730  }
731 
732  if (has_cost) {
733  auto* arg = proto.add_constraints()->mutable_linear();
734  arg->add_domain(0);
735  arg->add_domain(0);
736  for (int arc = 0; arc < num_arcs; arc++) {
737  const int64 weight = fz_ct.arguments[2].values[arc];
738  if (weight != 0) {
739  arg->add_vars(flow[arc]);
740  arg->add_coeffs(weight);
741  }
742  }
743  arg->add_vars(LookupVar(fz_ct.arguments[4]));
744  arg->add_coeffs(-1);
745  }
746  } else {
747  LOG(FATAL) << " Not supported " << fz_ct.type;
748  }
749 }
750 
751 void CpModelProtoWithMapping::FillReifOrImpliedConstraint(
752  const fz::Constraint& fz_ct, ConstraintProto* ct) {
753  // Start by adding a non-reified version of the same constraint.
754  std::string simplified_type;
755  if (absl::EndsWith(fz_ct.type, "_reif")) {
756  // Remove _reif.
757  simplified_type = fz_ct.type.substr(0, fz_ct.type.size() - 5);
758  } else if (absl::EndsWith(fz_ct.type, "_imp")) {
759  // Remove _imp.
760  simplified_type = fz_ct.type.substr(0, fz_ct.type.size() - 4);
761  } else {
762  // Keep name as it is an implicit reified constraint.
763  simplified_type = fz_ct.type;
764  }
765 
766  // We need a copy to be able to change the type of the constraint.
767  fz::Constraint copy = fz_ct;
768  copy.type = simplified_type;
769 
770  // Create the CP-SAT constraint.
771  FillConstraint(copy, ct);
772 
773  // In case of reified constraints, the type of the opposite constraint.
774  std::string negated_type;
775 
776  // Fill enforcement_literal and set copy.type to the negated constraint.
777  if (simplified_type == "array_bool_or") {
778  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[1])));
779  negated_type = "array_bool_or_negated";
780  } else if (simplified_type == "array_bool_and") {
781  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[1])));
782  negated_type = "array_bool_and_negated";
783  } else if (simplified_type == "set_in") {
784  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
785  negated_type = "set_in_negated";
786  } else if (simplified_type == "bool_eq" || simplified_type == "int_eq") {
787  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
788  negated_type = "int_ne";
789  } else if (simplified_type == "bool_ne" || simplified_type == "int_ne") {
790  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
791  negated_type = "int_eq";
792  } else if (simplified_type == "bool_le" || simplified_type == "int_le") {
793  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
794  negated_type = "int_gt";
795  } else if (simplified_type == "bool_lt" || simplified_type == "int_lt") {
796  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
797  negated_type = "int_ge";
798  } else if (simplified_type == "bool_ge" || simplified_type == "int_ge") {
799  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
800  negated_type = "int_lt";
801  } else if (simplified_type == "bool_gt" || simplified_type == "int_gt") {
802  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[2])));
803  negated_type = "int_le";
804  } else if (simplified_type == "int_lin_eq") {
805  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
806  negated_type = "int_lin_ne";
807  } else if (simplified_type == "int_lin_ne") {
808  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
809  negated_type = "int_lin_eq";
810  } else if (simplified_type == "int_lin_le") {
811  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
812  negated_type = "int_lin_gt";
813  } else if (simplified_type == "int_lin_ge") {
814  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
815  negated_type = "int_lin_lt";
816  } else if (simplified_type == "int_lin_lt") {
817  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
818  negated_type = "int_lin_ge";
819  } else if (simplified_type == "int_lin_gt") {
820  ct->add_enforcement_literal(TrueLiteral(LookupVar(fz_ct.arguments[3])));
821  negated_type = "int_lin_le";
822  } else {
823  LOG(FATAL) << "Unsupported " << simplified_type;
824  }
825 
826  // One way implication. We can stop here.
827  if (absl::EndsWith(fz_ct.type, "_imp")) return;
828 
829  // Add the other side of the reification because CpModelProto only support
830  // half reification.
831  ConstraintProto* negated_ct = proto.add_constraints();
832  negated_ct->set_name(fz_ct.type + " (negated)");
833  negated_ct->add_enforcement_literal(
834  sat::NegatedRef(ct->enforcement_literal(0)));
835  copy.type = negated_type;
836  FillConstraint(copy, negated_ct);
837 }
838 
839 void CpModelProtoWithMapping::TranslateSearchAnnotations(
840  const std::vector<fz::Annotation>& search_annotations) {
841  std::vector<fz::Annotation> flat_annotations;
842  for (const fz::Annotation& annotation : search_annotations) {
843  fz::FlattenAnnotations(annotation, &flat_annotations);
844  }
845 
846  for (const fz::Annotation& annotation : flat_annotations) {
847  if (annotation.IsFunctionCallWithIdentifier("int_search") ||
848  annotation.IsFunctionCallWithIdentifier("bool_search")) {
849  const std::vector<fz::Annotation>& args = annotation.annotations;
850  std::vector<fz::IntegerVariable*> vars;
851  args[0].AppendAllIntegerVariables(&vars);
852 
853  DecisionStrategyProto* strategy = proto.add_search_strategy();
854  for (fz::IntegerVariable* v : vars) {
855  strategy->add_variables(gtl::FindOrDie(fz_var_to_index, v));
856  }
857 
858  const fz::Annotation& choose = args[1];
859  if (choose.id == "input_order") {
860  strategy->set_variable_selection_strategy(
861  DecisionStrategyProto::CHOOSE_FIRST);
862  } else if (choose.id == "first_fail") {
863  strategy->set_variable_selection_strategy(
864  DecisionStrategyProto::CHOOSE_MIN_DOMAIN_SIZE);
865  } else if (choose.id == "anti_first_fail") {
866  strategy->set_variable_selection_strategy(
867  DecisionStrategyProto::CHOOSE_MAX_DOMAIN_SIZE);
868  } else if (choose.id == "smallest") {
869  strategy->set_variable_selection_strategy(
870  DecisionStrategyProto::CHOOSE_LOWEST_MIN);
871  } else if (choose.id == "largest") {
872  strategy->set_variable_selection_strategy(
873  DecisionStrategyProto::CHOOSE_HIGHEST_MAX);
874  } else {
875  LOG(FATAL) << "Unsupported order: " << choose.id;
876  }
877 
878  const fz::Annotation& select = args[2];
879  if (select.id == "indomain_min" || select.id == "indomain") {
880  strategy->set_domain_reduction_strategy(
881  DecisionStrategyProto::SELECT_MIN_VALUE);
882  } else if (select.id == "indomain_max") {
883  strategy->set_domain_reduction_strategy(
884  DecisionStrategyProto::SELECT_MAX_VALUE);
885  } else if (select.id == "indomain_split") {
886  strategy->set_domain_reduction_strategy(
887  DecisionStrategyProto::SELECT_LOWER_HALF);
888  } else if (select.id == "indomain_reverse_split") {
889  strategy->set_domain_reduction_strategy(
890  DecisionStrategyProto::SELECT_UPPER_HALF);
891  } else if (select.id == "indomain_median") {
892  strategy->set_domain_reduction_strategy(
893  DecisionStrategyProto::SELECT_MEDIAN_VALUE);
894  } else {
895  LOG(FATAL) << "Unsupported select: " << select.id;
896  }
897  }
898  }
899 }
900 
901 // The format is fixed in the flatzinc specification.
902 std::string SolutionString(
903  const fz::SolutionOutputSpecs& output,
904  const std::function<int64(fz::IntegerVariable*)>& value_func) {
905  if (output.variable != nullptr) {
906  const int64 value = value_func(output.variable);
907  if (output.display_as_boolean) {
908  return absl::StrCat(output.name, " = ", value == 1 ? "true" : "false",
909  ";");
910  } else {
911  return absl::StrCat(output.name, " = ", value, ";");
912  }
913  } else {
914  const int bound_size = output.bounds.size();
915  std::string result =
916  absl::StrCat(output.name, " = array", bound_size, "d(");
917  for (int i = 0; i < bound_size; ++i) {
918  if (output.bounds[i].max_value != 0) {
919  absl::StrAppend(&result, output.bounds[i].min_value, "..",
920  output.bounds[i].max_value, ", ");
921  } else {
922  result.append("{},");
923  }
924  }
925  result.append("[");
926  for (int i = 0; i < output.flat_variables.size(); ++i) {
927  const int64 value = value_func(output.flat_variables[i]);
928  if (output.display_as_boolean) {
929  result.append(value ? "true" : "false");
930  } else {
931  absl::StrAppend(&result, value);
932  }
933  if (i != output.flat_variables.size() - 1) {
934  result.append(", ");
935  }
936  }
937  result.append("]);");
938  return result;
939  }
940  return "";
941 }
942 
943 std::string SolutionString(
944  const fz::Model& model,
945  const std::function<int64(fz::IntegerVariable*)>& value_func) {
946  std::string solution_string;
947  for (const auto& output_spec : model.output()) {
948  solution_string.append(SolutionString(output_spec, value_func));
949  solution_string.append("\n");
950  }
951  return solution_string;
952 }
953 
954 void LogInFlatzincFormat(const std::string& multi_line_input) {
955  std::vector<std::string> lines =
956  absl::StrSplit(multi_line_input, '\n', absl::SkipEmpty());
957  for (const std::string& line : lines) {
958  FZLOG << line << FZENDL;
959  }
960 }
961 
962 void OutputFlatzincStats(const CpSolverResponse& response) {
963  std::cout << "%%%mzn-stat: objective=" << response.objective_value()
964  << std::endl;
965  std::cout << "%%%mzn-stat: objectiveBound=" << response.best_objective_bound()
966  << std::endl;
967  std::cout << "%%%mzn-stat: boolVariables=" << response.num_booleans()
968  << std::endl;
969  std::cout << "%%%mzn-stat: failures=" << response.num_conflicts()
970  << std::endl;
971  std::cout << "%%%mzn-stat: propagations="
972  << response.num_binary_propagations() +
973  response.num_integer_propagations()
974  << std::endl;
975  std::cout << "%%%mzn-stat: solveTime=" << response.wall_time() << std::endl;
976 }
977 
978 } // namespace
979 
980 void SolveFzWithCpModelProto(const fz::Model& fz_model,
981  const fz::FlatzincSatParameters& p,
982  const std::string& sat_params) {
983  if (!FLAGS_use_flatzinc_format) {
984  LOG(INFO) << "*** Starting translation to CP-SAT";
985  } else if (p.verbose_logging) {
986  FZLOG << "*** Starting translation to CP-SAT" << FZENDL;
987  }
988 
989  CpModelProtoWithMapping m;
990  m.proto.set_name(fz_model.name());
991 
992  // The translation is easy, we create one variable per flatzinc variable,
993  // plus eventually a bunch of constant variables that will be created
994  // lazily.
995  int num_variables = 0;
996  for (fz::IntegerVariable* fz_var : fz_model.variables()) {
997  if (!fz_var->active) continue;
998  m.fz_var_to_index[fz_var] = num_variables++;
999  IntegerVariableProto* var = m.proto.add_variables();
1000  var->set_name(fz_var->name);
1001  if (fz_var->domain.is_interval) {
1002  if (fz_var->domain.values.empty()) {
1003  // The CP-SAT solver checks that constraints cannot overflow during
1004  // their propagation. Because of that, we trim undefined variable
1005  // domains (i.e. int in minizinc) to something hopefully large enough.
1006  LOG_FIRST_N(WARNING, 1)
1007  << "Using flag --fz_int_max for unbounded integer variables.";
1008  LOG_FIRST_N(WARNING, 1) << " actual domain is [" << -FLAGS_fz_int_max
1009  << ".." << FLAGS_fz_int_max << "]";
1010  var->add_domain(-FLAGS_fz_int_max);
1011  var->add_domain(FLAGS_fz_int_max);
1012  } else {
1013  var->add_domain(fz_var->domain.values[0]);
1014  var->add_domain(fz_var->domain.values[1]);
1015  }
1016  } else {
1017  FillDomainInProto(Domain::FromValues(fz_var->domain.values), var);
1018  }
1019  }
1020 
1021  // Translate the constraints.
1022  for (fz::Constraint* fz_ct : fz_model.constraints()) {
1023  if (fz_ct == nullptr || !fz_ct->active) continue;
1024  ConstraintProto* ct = m.proto.add_constraints();
1025  ct->set_name(fz_ct->type);
1026  if (absl::EndsWith(fz_ct->type, "_reif") ||
1027  absl::EndsWith(fz_ct->type, "_imp") || fz_ct->type == "array_bool_or" ||
1028  fz_ct->type == "array_bool_and") {
1029  m.FillReifOrImpliedConstraint(*fz_ct, ct);
1030  } else {
1031  m.FillConstraint(*fz_ct, ct);
1032  }
1033  }
1034 
1035  // Fill the objective.
1036  if (fz_model.objective() != nullptr) {
1037  CpObjectiveProto* objective = m.proto.mutable_objective();
1038  objective->add_coeffs(1);
1039  if (fz_model.maximize()) {
1040  objective->set_scaling_factor(-1);
1041  objective->add_vars(
1042  NegatedCpModelVariable(m.fz_var_to_index[fz_model.objective()]));
1043  } else {
1044  objective->add_vars(m.fz_var_to_index[fz_model.objective()]);
1045  }
1046  }
1047 
1048  // Fill the search order.
1049  m.TranslateSearchAnnotations(fz_model.search_annotations());
1050 
1051  // Print model statistics.
1052  if (FLAGS_use_flatzinc_format && p.verbose_logging) {
1053  LogInFlatzincFormat(CpModelStats(m.proto));
1054  }
1055 
1056  if (p.display_all_solutions && !m.proto.has_objective()) {
1057  // Enumerate all sat solutions.
1058  m.parameters.set_enumerate_all_solutions(true);
1059  }
1060  if (p.use_free_search) {
1061  m.parameters.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
1062  if (p.number_of_threads <= 1) {
1063  m.parameters.set_interleave_search(true);
1064  m.parameters.set_reduce_memory_usage_in_interleave_mode(true);
1065  }
1066  } else {
1067  m.parameters.set_search_branching(SatParameters::FIXED_SEARCH);
1068  }
1069  if (p.max_time_in_seconds > 0) {
1070  m.parameters.set_max_time_in_seconds(p.max_time_in_seconds);
1071  }
1072 
1073  // We don't support enumerating all solution in parallel for a SAT problem.
1074  // But note that we do support it for an optimization problem since the
1075  // meaning of p.all_solutions is not the same in this case.
1076  if (p.display_all_solutions && fz_model.objective() == nullptr) {
1077  m.parameters.set_num_search_workers(1);
1078  } else {
1079  m.parameters.set_num_search_workers(std::max(1, p.number_of_threads));
1080  }
1081 
1082  // The order is important, we want the flag parameters to overwrite anything
1083  // set in m.parameters.
1084  sat::SatParameters flag_parameters;
1085  CHECK(google::protobuf::TextFormat::ParseFromString(sat_params,
1086  &flag_parameters))
1087  << sat_params;
1088  m.parameters.MergeFrom(flag_parameters);
1089 
1090  // We only need an observer if 'p.all_solutions' is true.
1091  std::function<void(const CpSolverResponse&)> solution_observer = nullptr;
1092  if (p.display_all_solutions && FLAGS_use_flatzinc_format) {
1093  solution_observer = [&fz_model, &m, &p](const CpSolverResponse& r) {
1094  const std::string solution_string =
1095  SolutionString(fz_model, [&m, &r](fz::IntegerVariable* v) {
1096  return r.solution(gtl::FindOrDie(m.fz_var_to_index, v));
1097  });
1098  std::cout << solution_string << std::endl;
1099  if (p.display_statistics && FLAGS_use_flatzinc_format) {
1100  OutputFlatzincStats(r);
1101  }
1102  std::cout << "----------" << std::endl;
1103  };
1104  }
1105 
1106  Model sat_model;
1107  sat_model.Add(NewSatParameters(m.parameters));
1108  if (solution_observer != nullptr) {
1109  sat_model.Add(NewFeasibleSolutionObserver(solution_observer));
1110  }
1111  const CpSolverResponse response = SolveCpModel(m.proto, &sat_model);
1112 
1113  // Check the returned solution with the fz model checker.
1114  if (response.status() == CpSolverStatus::FEASIBLE ||
1115  response.status() == CpSolverStatus::OPTIMAL) {
1116  CHECK(CheckSolution(fz_model, [&response, &m](fz::IntegerVariable* v) {
1117  return response.solution(gtl::FindOrDie(m.fz_var_to_index, v));
1118  }));
1119  }
1120 
1121  // Output the solution in the flatzinc official format.
1122  if (FLAGS_use_flatzinc_format) {
1123  if (response.status() == CpSolverStatus::FEASIBLE ||
1124  response.status() == CpSolverStatus::OPTIMAL) {
1125  if (!p.display_all_solutions) { // Already printed otherwise.
1126  const std::string solution_string =
1127  SolutionString(fz_model, [&response, &m](fz::IntegerVariable* v) {
1128  return response.solution(gtl::FindOrDie(m.fz_var_to_index, v));
1129  });
1130  std::cout << solution_string << std::endl;
1131  std::cout << "----------" << std::endl;
1132  }
1133  if (response.status() == CpSolverStatus::OPTIMAL) {
1134  std::cout << "==========" << std::endl;
1135  }
1136  } else if (response.status() == CpSolverStatus::INFEASIBLE) {
1137  std::cout << "=====UNSATISFIABLE=====" << std::endl;
1138  } else {
1139  std::cout << "%% TIMEOUT" << std::endl;
1140  }
1141  if (p.display_statistics && FLAGS_use_flatzinc_format) {
1142  OutputFlatzincStats(response);
1143  }
1144  }
1145 }
1146 
1147 } // namespace sat
1148 } // namespace operations_research
var
IntVar * var
Definition: expr_array.cc:1858
tail
int64 tail
Definition: routing_flow.cc:127
response
SharedResponseManager * response
Definition: cp_model_solver.cc:2027
operations_research::sat::FEASIBLE
@ FEASIBLE
Definition: cp_model.pb.h:230
min
int64 min
Definition: alldiff_cst.cc:138
map_util.h
threadpool.h
cp_model.pb.h
max
int64 max
Definition: alldiff_cst.cc:139
operations_research::fz::Argument::INT_VAR_REF
@ INT_VAR_REF
Definition: flatzinc/model.h:149
FZENDL
#define FZENDL
Definition: flatzinc/logging.h:31
operations_research::fz::FlatzincSatParameters::verbose_logging
bool verbose_logging
Definition: cp_model_fz_solver.h:25
operations_research::fz::Model::name
const std::string & name() const
Definition: flatzinc/model.h:370
fz_var_to_index
absl::flat_hash_map< fz::IntegerVariable *, int > fz_var_to_index
Definition: cp_model_fz_solver.cc:110
operations_research::sat::NewFeasibleSolutionObserver
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){....
Definition: cp_model_solver.cc:862
operations_research::fz::FlatzincSatParameters::display_statistics
bool display_statistics
Definition: cp_model_fz_solver.h:26
model.h
disjunctive.h
operations_research::fz::Model::maximize
bool maximize() const
Definition: flatzinc/model.h:363
value
int64 value
Definition: demon_profiler.cc:43
weight
int64 weight
Definition: pack.cc:509
model.h
operations_research
The vehicle routing library lets one model and solve generic vehicle routing problems ranging from th...
Definition: dense_doubly_linked_list.h:21
kint64min
static const int64 kint64min
Definition: integral_types.h:60
start_size_opt_tuple_to_interval
absl::flat_hash_map< std::tuple< int, int, int >, int > start_size_opt_tuple_to_interval
Definition: cp_model_fz_solver.cc:113
operations_research::Domain::FromIntervals
static Domain FromIntervals(absl::Span< const ClosedInterval > intervals)
Creates a domain from the union of an unsorted list of intervals.
Definition: sorted_interval_list.cc:150
int64
int64_t int64
Definition: integral_types.h:34
logging.h
constant_value_to_index
absl::flat_hash_map< int64, int > constant_value_to_index
Definition: cp_model_fz_solver.cc:111
sat_solver.h
operations_research::sat::Model
Class that owns everything related to a particular optimization model.
Definition: sat/model.h:38
operations_research::fz::Model::objective
IntegerVariable * objective() const
Definition: flatzinc/model.h:364
index
int index
Definition: pack.cc:508
operations_research::sat::AllValuesInDomain
std::vector< int64 > AllValuesInDomain(const ProtoWithDomain &proto)
Definition: cp_model_utils.h:116
FZLOG
#define FZLOG
Definition: flatzinc/logging.h:32
gtl::FindOrDie
const Collection::value_type::second_type & FindOrDie(const Collection &collection, const typename Collection::value_type::first_type &key)
Definition: map_util.h:176
operations_research::fz::Argument::INT_INTERVAL
@ INT_INTERVAL
Definition: flatzinc/model.h:146
operations_research::Domain::IntersectionWith
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
Definition: sorted_interval_list.cc:282
optimization.h
a
int64 a
Definition: constraint_solver/table.cc:42
operations_research::fz::FlatzincSatParameters
Definition: cp_model_fz_solver.h:22
operations_research::sat::INFEASIBLE
@ INFEASIBLE
Definition: cp_model.pb.h:231
operations_research::fz::Argument::INT_VAR_REF_ARRAY
@ INT_VAR_REF_ARRAY
Definition: flatzinc/model.h:150
operations_research::fz::FlatzincSatParameters::number_of_threads
int number_of_threads
Definition: cp_model_fz_solver.h:29
operations_research::Domain::FromValues
static Domain FromValues(std::vector< int64 > values)
Creates a domain from the union of an unsorted list of integer values.
Definition: sorted_interval_list.cc:137
kint32max
static const int32 kint32max
Definition: integral_types.h:59
DEFINE_bool
DEFINE_bool(use_flatzinc_format, true, "Output uses the flatzinc format")
timer.h
intervals.h
operations_research::sat::CpModelStats
std::string CpModelStats(const CpModelProto &model_proto)
Returns a string with some statistics on the given CpModelProto.
Definition: cp_model_solver.cc:147
integer_expr.h
operations_research::fz::Constraint
Definition: flatzinc/model.h:196
operations_research::fz::Argument::VOID_ARGUMENT
@ VOID_ARGUMENT
Definition: flatzinc/model.h:151
operations_research::fz::FlatzincSatParameters::display_all_solutions
bool display_all_solutions
Definition: cp_model_fz_solver.h:23
operations_research::fz::IntegerVariable
Definition: flatzinc/model.h:107
ct
const Constraint * ct
Definition: demon_profiler.cc:42
operations_research::sat::OPTIMAL
@ OPTIMAL
Definition: cp_model.pb.h:232
operations_research::sat::NewSatParameters
std::function< SatParameters(Model *)> NewSatParameters(const std::string &params)
Creates parameters for the solver, which you can add to the model with.
Definition: cp_model_solver.cc:871
cp_constraints.h
operations_research::fz::Argument::INT_LIST
@ INT_LIST
Definition: flatzinc/model.h:147
operations_research::sat::NegatedRef
int NegatedRef(int ref)
Definition: cp_model_utils.h:32
model
GRBmodel * model
Definition: gurobi_interface.cc:195
operations_research::sat::SolveCpModel
CpSolverResponse SolveCpModel(const CpModelProto &model_proto, Model *model)
Solves the given CpModelProto.
Definition: cp_model_solver.cc:2822
operations_research::sat::FillDomainInProto
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Definition: cp_model_utils.h:91
operations_research::fz::Model
Definition: flatzinc/model.h:315
cp_model_search.h
gtl::reversed_view
ReverseView< Container > reversed_view(const Container &c)
Definition: iterator_adaptors.h:33
iterator_adaptors.h
operations_research::fz::Model::search_annotations
const std::vector< Annotation > & search_annotations() const
Definition: flatzinc/model.h:349
DEFINE_int64
DEFINE_int64(fz_int_max, int64{1}<< 50, "Default max value for unbounded integer variables.")
operations_research::fz::FlattenAnnotations
void FlattenAnnotations(const Annotation &ann, std::vector< Annotation > *out)
Definition: model.cc:953
b
int64 b
Definition: constraint_solver/table.cc:43
operations_research::fz::Argument::INT_VALUE
@ INT_VALUE
Definition: flatzinc/model.h:145
capacity
int64 capacity
Definition: routing_flow.cc:129
interval
IntervalVar * interval
Definition: resource.cc:98
next
Block * next
Definition: constraint_solver.cc:667
cp_model_solver.h
operations_research::sat::SolveFzWithCpModelProto
void SolveFzWithCpModelProto(const fz::Model &fz_model, const fz::FlatzincSatParameters &p, const std::string &sat_params)
Definition: cp_model_fz_solver.cc:980
proto
CpModelProto proto
Definition: cp_model_fz_solver.cc:106
operations_research::fz::Model::constraints
const std::vector< Constraint * > & constraints() const
Definition: flatzinc/model.h:348
operations_research::sat::Model::Add
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
operations_research::fz::FlatzincSatParameters::max_time_in_seconds
double max_time_in_seconds
Definition: cp_model_fz_solver.h:30
head
int64 head
Definition: routing_flow.cc:128
operations_research::fz::FlatzincSatParameters::use_free_search
bool use_free_search
Definition: cp_model_fz_solver.h:24
literal
Literal literal
Definition: optimization.cc:84
operations_research::sat::ReadDomainFromProto
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Definition: cp_model_utils.h:102
cumulative.h
operations_research::fz::Model::variables
const std::vector< IntegerVariable * > & variables() const
Definition: flatzinc/model.h:347
checker.h
parameters
SatParameters parameters
Definition: cp_model_fz_solver.cc:107
table.h
integer.h
operations_research::fz::CheckSolution
bool CheckSolution(const Model &model, const std::function< int64(IntegerVariable *)> &evaluator)
Definition: checker.cc:1256
kint64max
static const int64 kint64max
Definition: integral_types.h:62
cp_model_fz_solver.h
cp_model_utils.h
gtl::ContainsKey
bool ContainsKey(const Collection &collection, const Key &key)
Definition: map_util.h:170