Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
cp_model_fz_solver.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <cstdint>
18#include <cstdlib>
19#include <functional>
20#include <limits>
21#include <memory>
22#include <optional>
23#include <string>
24#include <string_view>
25#include <tuple>
26#include <utility>
27#include <vector>
28
29#include "absl/algorithm/container.h"
30#include "absl/base/no_destructor.h"
31#include "absl/container/btree_map.h"
32#include "absl/container/btree_set.h"
33#include "absl/container/flat_hash_map.h"
34#include "absl/container/flat_hash_set.h"
35#include "absl/flags/flag.h"
36#include "absl/log/check.h"
37#include "absl/log/log.h"
38#include "absl/strings/match.h"
39#include "absl/strings/str_cat.h"
40#include "absl/strings/str_join.h"
41#include "absl/types/span.h"
42#include "google/protobuf/arena.h"
51#include "ortools/sat/model.h"
55
56ABSL_FLAG(int64_t, fz_int_max, int64_t{1} << 40,
57 "Default max value for unbounded integer variables.");
58ABSL_FLAG(bool, force_interleave_search, false,
59 "If true, enable interleaved workers when num_workers is 1.");
60ABSL_FLAG(bool, fz_use_light_encoding, false,
61 "Use lighter encodings for the model");
62
63// TODO(user): SetIn forces card to be > 0.
64
65namespace operations_research {
66namespace sat {
67
68namespace {
69
70static const int kNoVar = std::numeric_limits<int>::min();
71
72struct VarOrValue {
73 int var = kNoVar;
74 int64_t value = 0;
75};
76
77struct SetVariable {
78 std::vector<int> var_indices;
79 std::vector<int64_t> sorted_values;
80 int card_var_index = kNoVar;
81 std::optional<int> fixed_card = std::nullopt;
82
83 int find_value_index(int64_t value) const {
84 const auto it =
85 std::lower_bound(sorted_values.begin(), sorted_values.end(), value);
86 if (it == sorted_values.end() || *it != value) return -1;
87 return it - sorted_values.begin();
88 }
89};
90
91// Helper class to convert a flatzinc model to a CpModelProto.
92struct CpModelProtoWithMapping {
93 CpModelProtoWithMapping(bool sat_enumeration)
94 : arena(std::make_unique<google::protobuf::Arena>()),
95 proto(*google::protobuf::Arena::Create<CpModelProto>(arena.get())),
96 sat_enumeration_(sat_enumeration) {}
97
98 // Returns the index of a new boolean variable.
99 int NewBoolVar();
100
101 // Returns the index of a new integer variable.
102 int NewIntVar(int64_t min_value, int64_t max_value);
103
104 // Returns a constant CpModelProto variable created on-demand.
105 int LookupConstant(int64_t value);
106
107 void AddImplication(absl::Span<const int> e, int l) {
108 ConstraintProto* ct = proto.add_constraints();
109 for (int e_lit : e) {
110 ct->add_enforcement_literal(e_lit);
111 }
112 ct->mutable_bool_and()->add_literals(l);
113 }
114
115 // Convert a flatzinc argument to a variable or a list of variable.
116 // Note that we always encode a constant argument with a constant variable.
117 int LookupVar(const fz::Argument& argument);
118 LinearExpressionProto LookupExpr(const fz::Argument& argument,
119 bool negate = false);
120 LinearExpressionProto LookupExprAt(const fz::Argument& argument, int pos,
121 bool negate = false);
122 std::vector<int> LookupVars(const fz::Argument& argument);
123 VarOrValue LookupVarOrValue(const fz::Argument& argument);
124 std::vector<VarOrValue> LookupVarsOrValues(const fz::Argument& argument);
125
126 // Checks is the domain of the variable is included in [0, 1].
127 bool VariableIsBoolean(int var) const;
128
129 // Set variables.
130 void ExtractSetConstraint(const fz::Constraint& fz_ct);
131 bool ConstraintContainsSetVariables(const fz::Constraint& constraint) const;
132 std::shared_ptr<SetVariable> LookupSetVar(const fz::Argument& argument);
133 std::shared_ptr<SetVariable> LookupSetVarAt(const fz::Argument& argument,
134 int pos);
135
136 // literal <=> (var op value).
137 int GetOrCreateEncodingLiteral(int var, int64_t value);
138 std::optional<int> GetEncodingLiteral(int var, int64_t value);
139 void AddVarEqValueLiteral(int var, int64_t value, int literal);
140 void AddVarGeValueLiteral(int var, int64_t value, int literal);
141 void AddVarGtValueLiteral(int var, int64_t value, int literal);
142 void AddVarLeValueLiteral(int var, int64_t value, int literal);
143 void AddVarLtValueLiteral(int var, int64_t value, int literal);
144
145 // literal <=> (var1 op var2).
146 int GetOrCreateLiteralForVarEqVar(int var1, int var2);
147 void AddVarEqVarLiteral(int var1, int var2, int literal);
148 std::optional<int> GetVarEqVarLiteral(int var1, int var2);
149 int GetOrCreateLiteralForVarNeVar(int var1, int var2);
150 std::optional<int> GetVarNeVarLiteral(int var1, int var2);
151 void AddVarNeVarLiteral(int var1, int var2, int literal);
152 int GetOrCreateLiteralForVarLeVar(int var1, int var2);
153 std::optional<int> GetVarLeVarLiteral(int var1, int var2);
154 void AddVarLeVarLiteral(int var1, int var2, int literal);
155 int GetOrCreateLiteralForVarLtVar(int var1, int var2);
156 std::optional<int> GetVarLtVarLiteral(int var1, int var2);
157 void AddVarLtVarLiteral(int var1, int var2, int literal);
158 void AddVarOrVarLiteral(int var1, int var2, int literal);
159 void AddVarAndVarLiteral(int var1, int var2, int literal);
160
161 // Returns the list of literals corresponding to the domain of the variable.
162 absl::btree_map<int64_t, int> GetFullEncoding(int var);
163
164 // Create and return the indices of the IntervalConstraint corresponding
165 // to the flatzinc "interval" specified by a start var and a size var.
166 // This method will cache intervals with the key <start, size>.
167 std::vector<int> CreateIntervals(absl::Span<const VarOrValue> starts,
168 absl::Span<const VarOrValue> sizes);
169
170 // Create and return the indices of the IntervalConstraint corresponding
171 // to the flatzinc "interval" specified by a start var and a size var.
172 // This method will cache intervals with the key <start, size>.
173 // This interval will be optional if the size can be 0.
174 // It also adds a constraint linking the enforcement literal with the size,
175 // stating that the interval will be performed if and only if the size is
176 // greater than 0.
177 std::vector<int> CreateNonZeroOrOptionalIntervals(
178 absl::Span<const VarOrValue> starts, absl::Span<const VarOrValue> sizes);
179
180 // Create and return the index of the optional IntervalConstraint
181 // corresponding to the flatzinc "interval" specified by a start var, the
182 // size_var, and the Boolean opt_var. This method will cache intervals with
183 // the key <start, size, opt_var>. If opt_var == kNoVar, the interval will not
184 // be optional.
185 int GetOrCreateOptionalInterval(VarOrValue start_var, VarOrValue size,
186 int opt_var);
187
188 // Get or Create a literal that implies the variable is > 0.
189 // Its negation implies the variable is == 0.
190 int NonZeroLiteralFrom(VarOrValue size);
191
192 // Adds a constraint to the model, add the enforcement literal if it is
193 // different from kNoVar, and returns a ptr to the ConstraintProto.
194 ConstraintProto* AddEnforcedConstraint(int literal);
195
196 // Adds a enforced linear constraint to the model with the given domain.
197 LinearConstraintProto* AddLinearConstraint(
198 absl::Span<const int> enforcement_literals, const Domain& domain,
199 absl::Span<const std::pair<int, int64_t>> terms = {});
200
201 // Adds a lex_less{eq} constraint to the model.
202 void AddLexOrdering(absl::Span<const int> x, absl::Span<const int> y,
203 int enforcement_literal, bool accepts_equals);
204
205 // Enforces that x_array != y_array.
206 void AddLiteralVectorsNotEqual(absl::Span<const int> x_array,
207 absl::Span<const int> y_array);
208
209 // This one must be called after the domain has been set.
210 void AddTermToLinearConstraint(int var, int64_t coeff,
211 LinearConstraintProto* ct);
212 void FillAMinusBInDomain(const std::vector<int64_t>& domain,
213 const fz::Constraint& fz_ct, ConstraintProto* ct);
214 void FillLinearConstraintWithGivenDomain(absl::Span<const int64_t> domain,
215 const fz::Constraint& fz_ct,
216 ConstraintProto* ct);
217 void BuildTableFromDomainIntLinEq(const fz::Constraint& fz_ct,
218 ConstraintProto* ct);
219
220 void FirstPass(const fz::Constraint& fz_ct);
221 void FillConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
222 void FillReifiedOrImpliedConstraint(const fz::Constraint& fz_ct);
223 void AddAllEncodingConstraints();
224
225 // Methods to handle specific constraints.
226 void AlwaysFalseConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
227 void BoolClauseConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
228 void BoolXorConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
229 void ArrayBoolXorConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
230 void BoolOrIntLeConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
231 void BoolOrIntGeConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
232 void BoolOrIntLtConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
233 void BoolOrIntGtConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
234 void BoolEqConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
235 void BoolNeConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
236 void IntNeConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
237 void IntLinEqConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
238 void BoolLinEqConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
239 void BoolOrIntLinLeConstraint(const fz::Constraint& fz_ct,
240 ConstraintProto* ct);
241 void IntLinLtConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
242 void IntLinGeConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
243 void IntLinGtConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
244 void IntLinNeConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
245 void SetCardConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
246 void SetInConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
247 void SetInNegatedConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
248 void IntMinConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
249 void ArrayIntMinConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
250 void IntMaxConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
251 void ArrayIntMaxConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
252 void IntTimesConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
253 void IntAbsConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
254 void IntPlusConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
255 void IntDivConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
256 void IntModConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
257 void ArrayElementConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
258 void OrToolsConstantElementConstraint(const fz::Constraint& fz_ct,
259 ConstraintProto* ct);
260 void OrToolsVariableElementConstraint(const fz::Constraint& fz_ct,
261 ConstraintProto* ct);
262 void OrToolsTableIntConstraint(const fz::Constraint& fz_ct,
263 ConstraintProto* ct);
264 void OrToolsRegular(const fz::Constraint& fz_ct, ConstraintProto* ct);
265 void OrToolsArgMax(const fz::Constraint& fz_ct, ConstraintProto* ct);
266 void FznAllDifferentInt(const fz::Constraint& fz_ct, ConstraintProto* ct);
267 void FznValuePrecedeInt(const fz::Constraint& fz_ct, ConstraintProto* ct);
268 void OrToolsCountEqCst(const fz::Constraint& fz_ct, ConstraintProto* ct);
269 void OrToolsCountEq(const fz::Constraint& fz_ct, ConstraintProto* ct);
270 void OrToolsCircuit(const fz::Constraint& fz_ct, ConstraintProto* ct);
271 void OrToolsInverse(const fz::Constraint& fz_ct, ConstraintProto* ct);
272 void OrToolsLexOrdering(const fz::Constraint& fz_ct, ConstraintProto* ct);
273 void OrToolsPrecedeChainInt(const fz::Constraint& fz_ct, ConstraintProto* ct);
274 void FznDisjunctive(const fz::Constraint& fz_ct, ConstraintProto* ct);
275 void FznDisjunctiveStrict(const fz::Constraint& fz_ct, ConstraintProto* ct);
276 void FznCumulative(const fz::Constraint& fz_ct, ConstraintProto* ct);
277 void OrToolsCumulativeOpt(const fz::Constraint& fz_ct, ConstraintProto* ct);
278 void OrToolsDisjunctiveStrictOpt(const fz::Constraint& fz_ct,
279 ConstraintProto* ct);
280 void FznDiffn(const fz::Constraint& fz_ct, ConstraintProto* ct);
281 void OrToolsNetworkFlow(const fz::Constraint& fz_ct, ConstraintProto* ct);
282 void OrToolsBinPacking(const fz::Constraint& fz_ct, ConstraintProto* ct);
283 void OrToolsBinPackingCapa(const fz::Constraint& fz_ct, ConstraintProto* ct);
284 void OrToolsBinPackingLoad(const fz::Constraint& fz_ct, ConstraintProto* ct);
285 void OrToolsNvalue(const fz::Constraint& fz_ct, ConstraintProto* ct);
286 void OrToolsGlobalCardinality(const fz::Constraint& fz_ct,
287 ConstraintProto* ct);
288 void OrToolsGlobalCardinalityLowUp(const fz::Constraint& fz_ct,
289 ConstraintProto* ct);
290
291 // Methods to handle constraints on set variables.
292 void ArraySetElementConstraint(const fz::Constraint& fz_ct);
293 void ArrayVarSetElementConstraint(const fz::Constraint& fz_ct);
294 void FznAllDifferentSetConstraint(const fz::Constraint& fz_ct);
295 void FznAllDisjointConstraint(const fz::Constraint& fz_ct);
296 void FznDisjointConstraint(const fz::Constraint& fz_ct);
297 void FznPartitionSetConstraint(const fz::Constraint& fz_ct);
298 void SetCardConstraint(const fz::Constraint& fz_ct);
299 void SetInConstraint(const fz::Constraint& fz_ct);
300 void SetInReifConstraint(const fz::Constraint& fz_ct);
301 void SetOpConstraint(const fz::Constraint& fz_ct);
302 void SetSubSupersetEqConstraint(const fz::Constraint& fz_ct);
303 void SetNeConstraint(const fz::Constraint& fz_ct);
304 void SetSubSupersetEqReifConstraint(const fz::Constraint& fz_ct);
305 void SetLexOrderingConstraint(const fz::Constraint& fz_ct);
306
307 // This function takes a list of set variables, normalize them to have all the
308 // same domain then fill var_booleans[i][j] with the booleans encoding the
309 // j-th possible value of the i-th set variable.
310 void PutSetBooleansInCommonDomain(
311 absl::Span<const std::shared_ptr<SetVariable>> set_vars,
312 absl::Span<std::vector<int>* const> var_booleans);
313
314 // Translates the flatzinc search annotations into the CpModelProto
315 // search_order field.
316 void TranslateSearchAnnotations(
317 absl::Span<const fz::Annotation> search_annotations,
318 SolverLogger* logger);
319
320 // The output proto.
321 std::unique_ptr<google::protobuf::Arena> arena;
322 CpModelProto& proto;
323 SatParameters parameters;
324 const bool sat_enumeration_;
325
326 // Mapping from flatzinc variables to CpModelProto variables.
327 absl::flat_hash_map<fz::Variable*, int> fz_var_to_index;
328 absl::flat_hash_map<int64_t, int> constant_value_to_index;
329 absl::flat_hash_map<std::tuple<int, int64_t, int, int64_t, int>, int>
330 interval_key_to_index;
331 absl::flat_hash_map<int, int> var_to_lit_implies_greater_than_zero;
332 // Var op value encoding literals.
333 absl::btree_map<int, absl::btree_map<int64_t, int>> encoding_literals;
334 absl::btree_map<int, absl::btree_map<int64_t, int>> var_le_value_to_literal;
335 // Var op var encoding literals.
336 absl::flat_hash_map<std::pair<int, int>, int> var_eq_var_to_literal;
337 absl::flat_hash_map<std::pair<int, int>, int> var_lt_var_to_literal;
338 absl::flat_hash_map<std::pair<int, int>, int> var_or_var_to_literal;
339 absl::flat_hash_map<std::pair<int, int>, int> var_and_var_to_literal;
340 // Set variables.
341 absl::flat_hash_map<fz::Variable*, std::shared_ptr<SetVariable>>
342 set_variables;
343 // Statistics.
344 int num_var_op_value_reif_cached = 0;
345 int num_var_op_var_reif_cached = 0;
346 int num_var_op_var_imp_cached = 0;
347 int num_full_encodings = 0;
348 int num_light_encodings = 0;
349 int num_partial_encodings = 0;
350};
351
352int CpModelProtoWithMapping::NewBoolVar() { return NewIntVar(0, 1); }
353
354int CpModelProtoWithMapping::NewIntVar(int64_t min_value, int64_t max_value) {
355 const int index = proto.variables_size();
356 FillDomainInProto(min_value, max_value, proto.add_variables());
357 return index;
358}
359
360int CpModelProtoWithMapping::LookupConstant(int64_t value) {
361 if (constant_value_to_index.contains(value)) {
362 return constant_value_to_index[value];
363 }
364
365 // Create the constant on the fly.
366 const int index = NewIntVar(value, value);
367 constant_value_to_index[value] = index;
368 return index;
369}
370
371int CpModelProtoWithMapping::LookupVar(const fz::Argument& argument) {
372 if (argument.HasOneValue()) return LookupConstant(argument.Value());
373 CHECK_EQ(argument.type, fz::Argument::VAR_REF);
374 return fz_var_to_index[argument.Var()];
375}
376
377LinearExpressionProto CpModelProtoWithMapping::LookupExpr(
378 const fz::Argument& argument, bool negate) {
379 LinearExpressionProto expr;
380 if (argument.HasOneValue()) {
381 const int64_t value = argument.Value();
382 expr.set_offset(negate ? -value : value);
383 } else {
384 expr.add_vars(LookupVar(argument));
385 expr.add_coeffs(negate ? -1 : 1);
386 }
387 return expr;
388}
389
390LinearExpressionProto CpModelProtoWithMapping::LookupExprAt(
391 const fz::Argument& argument, int pos, bool negate) {
392 LinearExpressionProto expr;
393 if (argument.HasOneValueAt(pos)) {
394 const int64_t value = argument.ValueAt(pos);
395 expr.set_offset(negate ? -value : value);
396 } else {
397 expr.add_vars(fz_var_to_index[argument.VarAt(pos)]);
398 expr.add_coeffs(negate ? -1 : 1);
399 }
400 return expr;
401}
402
403std::vector<int> CpModelProtoWithMapping::LookupVars(
404 const fz::Argument& argument) {
405 std::vector<int> result;
406 if (argument.type == fz::Argument::VOID_ARGUMENT) return result;
407 if (argument.type == fz::Argument::INT_LIST) {
408 for (int64_t value : argument.values) {
409 result.push_back(LookupConstant(value));
410 }
411 } else if (argument.type == fz::Argument::INT_VALUE) {
412 result.push_back(LookupConstant(argument.Value()));
413 } else {
414 CHECK_EQ(argument.type, fz::Argument::VAR_REF_ARRAY);
415 for (fz::Variable* var : argument.variables) {
416 CHECK(var != nullptr);
417 result.push_back(fz_var_to_index[var]);
418 }
419 }
420 return result;
421}
422
423VarOrValue CpModelProtoWithMapping::LookupVarOrValue(
424 const fz::Argument& argument) {
425 if (argument.type == fz::Argument::INT_VALUE) {
426 return {kNoVar, argument.Value()};
427 } else {
428 CHECK_EQ(argument.type, fz::Argument::VAR_REF);
429 fz::Variable* var = argument.Var();
430 CHECK(var != nullptr);
431 if (var->domain.HasOneValue()) {
432 return {kNoVar, var->domain.Value()};
433 } else {
434 return {fz_var_to_index[var], 0};
435 }
436 }
437}
438
439std::vector<VarOrValue> CpModelProtoWithMapping::LookupVarsOrValues(
440 const fz::Argument& argument) {
441 std::vector<VarOrValue> result;
442 const int no_var = kNoVar;
443 if (argument.type == fz::Argument::VOID_ARGUMENT) return result;
444 if (argument.type == fz::Argument::INT_LIST) {
445 for (int64_t value : argument.values) {
446 result.push_back({no_var, value});
447 }
448 } else if (argument.type == fz::Argument::INT_VALUE) {
449 result.push_back({no_var, argument.Value()});
450 } else {
451 CHECK_EQ(argument.type, fz::Argument::VAR_REF_ARRAY);
452 for (fz::Variable* var : argument.variables) {
453 CHECK(var != nullptr);
454 if (var->domain.HasOneValue()) {
455 result.push_back({no_var, var->domain.Value()});
456 } else {
457 result.push_back({fz_var_to_index[var], 0});
458 }
459 }
460 }
461 return result;
462}
463
464bool CpModelProtoWithMapping::VariableIsBoolean(int var) const {
465 if (var < 0) var = NegatedRef(var);
466 const IntegerVariableProto& var_proto = proto.variables(var);
467 return var_proto.domain_size() == 2 && var_proto.domain(0) >= 0 &&
468 var_proto.domain(1) <= 1;
469}
470
471bool CpModelProtoWithMapping::ConstraintContainsSetVariables(
472 const fz::Constraint& constraint) const {
473 for (const fz::Argument& argument : constraint.arguments) {
474 for (fz::Variable* var : argument.variables) {
475 if (var != nullptr && var->domain.is_a_set) return true;
476 }
477 }
478 return false;
479}
480
481std::shared_ptr<SetVariable> CpModelProtoWithMapping::LookupSetVar(
482 const fz::Argument& argument) {
483 if (argument.type == fz::Argument::VAR_REF) {
484 CHECK(argument.Var()->domain.is_a_set);
485 return set_variables[argument.Var()];
486 } else {
487 std::shared_ptr<SetVariable> result = std::make_shared<SetVariable>();
488 const int true_literal = LookupConstant(1);
489 if (argument.type == fz::Argument::INT_INTERVAL) {
490 for (int64_t value = argument.values[0]; value <= argument.values[1];
491 ++value) {
492 result->sorted_values.push_back(value);
493 result->var_indices.push_back(true_literal);
494 }
495 } else if (argument.type == fz::Argument::INT_LIST) {
496 for (int64_t value : argument.values) {
497 result->sorted_values.push_back(value);
498 result->var_indices.push_back(true_literal);
499 }
500 } else {
501 LOG(FATAL) << "LookupSetVar:Unsupported argument type '" << argument.type
502 << "'";
503 }
504 return result;
505 }
506}
507
508std::shared_ptr<SetVariable> CpModelProtoWithMapping::LookupSetVarAt(
509 const fz::Argument& argument, int pos) {
510 if (argument.type == fz::Argument::VAR_REF_ARRAY) {
511 CHECK(argument.variables[pos]->domain.is_a_set);
512 return set_variables[argument.variables[pos]];
513 } else {
514 LOG(FATAL) << "LookupSetVarAt:Unsupported argument type '" << argument.type
515 << "'";
516 }
517}
518
519ConstraintProto* CpModelProtoWithMapping::AddEnforcedConstraint(int literal) {
520 ConstraintProto* result = proto.add_constraints();
521 if (literal != kNoVar) {
522 result->add_enforcement_literal(literal);
523 }
524 return result;
525}
526
527LinearConstraintProto* CpModelProtoWithMapping::AddLinearConstraint(
528 absl::Span<const int> enforcement_literals, const Domain& domain,
529 absl::Span<const std::pair<int, int64_t>> terms) {
530 ConstraintProto* result = proto.add_constraints();
531 for (const int literal : enforcement_literals) {
532 result->add_enforcement_literal(literal);
533 }
534 FillDomainInProto(domain, result->mutable_linear());
535 for (const auto& [var, coeff] : terms) {
536 AddTermToLinearConstraint(var, coeff, result->mutable_linear());
537 }
538 return result->mutable_linear();
539}
540
541void CpModelProtoWithMapping::AddTermToLinearConstraint(
542 int var, int64_t coeff, LinearConstraintProto* ct) {
543 CHECK(!ct->domain().empty());
544 if (coeff == 0) return;
545 if (var >= 0) {
546 ct->add_vars(var);
547 ct->add_coeffs(coeff);
548 } else {
549 ct->add_vars(NegatedRef(var));
550 ct->add_coeffs(-coeff);
551 // We need to update the domain of the constraint.
552 for (int i = 0; i < ct->domain_size(); ++i) {
553 const int64_t b = ct->domain(i);
554 if (b == std::numeric_limits<int64_t>::min() ||
555 b == std::numeric_limits<int64_t>::max()) {
556 continue;
557 }
558 ct->set_domain(i, b - coeff);
559 }
560 }
561}
562
563void CpModelProtoWithMapping::AddLexOrdering(absl::Span<const int> x,
564 absl::Span<const int> y,
565 int enforcement_literal,
566 bool accepts_equals) {
567 const int min_size = std::min(x.size(), y.size());
568 std::vector<int> hold(min_size + 1); // Variables indices.
569 hold[0] = enforcement_literal;
570 for (int i = 1; i < min_size; ++i) hold[i] = NewBoolVar();
571 const bool hold_sentinel_is_true =
572 x.size() < y.size() || (x.size() == y.size() && accepts_equals);
573 hold[min_size] = LookupConstant(hold_sentinel_is_true ? 1 : 0);
574
575 if (!sat_enumeration_ && absl::GetFlag(FLAGS_fz_use_light_encoding)) {
576 // Faster version, but can produce duplicate solutions.
577 const Domain le_domain(std::numeric_limits<int64_t>::min(), 0);
578 const Domain lt_domain(std::numeric_limits<int64_t>::min(), -1);
579 for (int i = 0; i < min_size; ++i) {
580 // hold[i] => x[i] <= y[i].
581 AddLinearConstraint({hold[i]}, le_domain, {{x[i], 1}, {y[i], -1}});
582
583 // hold[i] => x[i] < y[i] || hold[i + 1]
584 const int is_lt = NewBoolVar();
585 AddLinearConstraint({is_lt}, lt_domain, {{x[i], 1}, {y[i], -1}});
586
587 ConstraintProto* chain = AddEnforcedConstraint(hold[i]);
588 chain->mutable_bool_or()->add_literals(is_lt);
589 chain->mutable_bool_or()->add_literals(hold[i + 1]);
590 }
591 ++num_light_encodings;
592 } else {
593 std::vector<int> is_lt(min_size);
594 std::vector<int> is_le(min_size);
595 for (int i = 0; i < min_size; ++i) {
596 is_le[i] = GetOrCreateLiteralForVarLeVar(x[i], y[i]);
597 is_lt[i] = GetOrCreateLiteralForVarLtVar(x[i], y[i]);
598 }
599
600 for (int i = 0; i < min_size; ++i) {
601 // hold[i] => x[i] <= y[i].
602 AddImplication({hold[i]}, is_le[i]);
603
604 // hold[i] => x[i] < y[i] || hold[i + 1]
605 ConstraintProto* chain = AddEnforcedConstraint(hold[i]);
606 chain->mutable_bool_or()->add_literals(is_lt[i]);
607 chain->mutable_bool_or()->add_literals(hold[i + 1]);
608
609 // Optimization.
610 if (i + 1 < min_size) {
611 // is_lt[i] => no need to look at further hold variables.
612 AddImplication({is_lt[i]}, NegatedRef(hold[i + 1]));
613
614 // Propagate the negation of hold[i] to hold[i + 1] to avoid unnecessary
615 // branching and disable the chain constraints.
616 AddImplication({NegatedRef(hold[i])}, NegatedRef(hold[i + 1]));
617 }
618 }
619 }
620}
621
622void CpModelProtoWithMapping::AddLiteralVectorsNotEqual(
623 absl::Span<const int> x_array, absl::Span<const int> y_array) {
624 const int size = x_array.size();
625 CHECK_EQ(size, y_array.size());
626 if (sat_enumeration_ || !absl::GetFlag(FLAGS_fz_use_light_encoding)) {
627 BoolArgumentProto* at_least_one_different =
629 for (int i = 0; i < x_array.size(); ++i) {
630 at_least_one_different->add_literals(
631 GetOrCreateLiteralForVarNeVar(x_array[i], y_array[i]));
632 }
633 ++num_light_encodings;
634 } else {
635 // This version is way faster, but can produce duplicates solution when
636 // enumerating solutions in a SAT model.
637 std::vector<int> is_ne(size);
638 for (int i = 0; i < size; ++i) {
639 // Using a simple implication is faster, but it forbids the optimization
640 // of the lex ordering.
641 is_ne[i] = NewBoolVar();
642 AddLinearConstraint({is_ne[i]}, Domain(1),
643 {{x_array[i], 1}, {y_array[i], 1}});
644 }
645
646 std::vector<int> hold(size + 1);
647 hold[0] = LookupConstant(1);
648 for (int i = 1; i < size; ++i) hold[i] = NewBoolVar();
649 hold[size] = LookupConstant(0);
650
651 for (int i = 0; i < size; ++i) {
652 // hold[i] => x[i] != y[i] || hold[i + 1]
653 ConstraintProto* chain = AddEnforcedConstraint(hold[i]);
654 chain->mutable_bool_or()->add_literals(is_ne[i]);
655 chain->mutable_bool_or()->add_literals(hold[i + 1]);
656 }
657 }
658}
659
660// lit <=> var op value
661
662std::optional<int> CpModelProtoWithMapping::GetEncodingLiteral(int var,
663 int64_t value) {
664 CHECK_GE(var, 0);
665 const IntegerVariableProto& var_proto = proto.variables(var);
666 const Domain domain = ReadDomainFromProto(var_proto);
667
668 // We have a few shortcuts where we do not create an encoding.
669
670 // Shortcut 1:Rule out values that are not in the domain.
671 if (!domain.Contains(value)) return LookupConstant(0);
672
673 // Shortcut 2: As we have ruled out values that are not in the domain, we can
674 // return a true literal if the domain has only one value.
675 if (domain.Size() == 1) return LookupConstant(1);
676
677 // Shortcut 3: The encoding of a Boolean variable is itself.
678 if (domain.Size() == 2 && domain.Min() == 0 && domain.Max() == 1) {
679 return value == 1 ? var : NegatedRef(var);
680 }
681
682 const auto it = encoding_literals.find(var);
683 if (it != encoding_literals.end()) {
684 const auto it2 = it->second.find(value);
685 if (it2 != it->second.end()) return it2->second;
686 }
687 return std::nullopt;
688}
689
690int CpModelProtoWithMapping::GetOrCreateEncodingLiteral(int var,
691 int64_t value) {
692 CHECK_GE(var, 0);
693 const std::optional<int> existing_literal = GetEncodingLiteral(var, value);
694 if (existing_literal.has_value()) {
695 CHECK_NE(existing_literal.value(), kNoVar);
696 return existing_literal.value();
697 }
698
699 const IntegerVariableProto& var_proto = proto.variables(var);
700 const Domain domain = ReadDomainFromProto(var_proto);
701 CHECK(domain.Contains(value));
702 CHECK(domain.Size() > 2 ||
703 (domain.Size() == 2 && (domain.Min() != 0 || domain.Max() != 1)));
704
705 const int lit = NewBoolVar();
706 if (domain.Size() == 2) { // We fully encode the variable.
707 encoding_literals[var][domain.Min()] = NegatedRef(lit);
708 encoding_literals[var][domain.Max()] = lit;
709 return value == domain.Min() ? NegatedRef(lit) : lit;
710 } else {
711 encoding_literals[var][value] = lit;
712 return lit;
713 }
714}
715
716void CpModelProtoWithMapping::AddVarEqValueLiteral(int var, int64_t value,
717 int lit) {
718 CHECK_GE(var, 0);
719 const std::optional<int> existing_literal = GetEncodingLiteral(var, value);
720 if (existing_literal.has_value()) {
721 AddLinearConstraint({}, Domain(0),
722 {{lit, 1}, {existing_literal.value(), -1}});
723 ++num_var_op_value_reif_cached;
724 } else {
725 const IntegerVariableProto& var_proto = proto.variables(var);
726 const Domain domain = ReadDomainFromProto(var_proto);
727 CHECK(domain.Contains(value));
728 if (domain.Size() == 2) {
729 CHECK(domain.Min() != 0 || domain.Max() != 1);
730 if (value == domain.Min()) {
731 encoding_literals[var][domain.Min()] = lit;
732 encoding_literals[var][domain.Max()] = NegatedRef(lit);
733 } else {
734 encoding_literals[var][domain.Min()] = NegatedRef(lit);
735 encoding_literals[var][domain.Max()] = lit;
736 }
737 } else {
738 encoding_literals[var][value] = lit;
739 }
740 }
741}
742
743void CpModelProtoWithMapping::AddVarGeValueLiteral(int var, int64_t value,
744 int lit) {
745 AddVarLeValueLiteral(var, value - 1, NegatedRef(lit));
746}
747
748void CpModelProtoWithMapping::AddVarGtValueLiteral(int var, int64_t value,
749 int lit) {
750 AddVarLeValueLiteral(var, value, NegatedRef(lit));
751}
752
753void CpModelProtoWithMapping::AddVarLtValueLiteral(int var, int64_t value,
754 int lit) {
755 AddVarLeValueLiteral(var, value - 1, lit);
756}
757
758void CpModelProtoWithMapping::AddVarLeValueLiteral(int var, int64_t value,
759 int lit) {
760 const Domain domain = ReadDomainFromProto(proto.variables(var));
761 const auto it = var_le_value_to_literal.find(var);
762 if (it != var_le_value_to_literal.end()) {
763 const auto it2 = it->second.find(value);
764 if (it2 != it->second.end()) {
765 AddLinearConstraint({}, Domain(0), {{lit, 1}, {it2->second, -1}});
766 ++num_var_op_value_reif_cached;
767 return;
768 }
769 }
770 var_le_value_to_literal[var][value] = lit;
771}
772
773absl::btree_map<int64_t, int> CpModelProtoWithMapping::GetFullEncoding(
774 int var) {
775 absl::btree_map<int64_t, int> result;
776 const Domain domain = ReadDomainFromProto(proto.variables(var));
777 for (int64_t value : domain.Values()) {
778 result[value] = GetOrCreateEncodingLiteral(var, value);
779 }
780 return result;
781}
782
783void CpModelProtoWithMapping::AddAllEncodingConstraints() {
784 const bool light_encoding = absl::GetFlag(FLAGS_fz_use_light_encoding);
785 for (const auto& [var, encoding] : encoding_literals) {
786 const Domain domain = ReadDomainFromProto(proto.variables(var));
787 CHECK(domain.Size() > 2 ||
788 (domain.Size() == 2 && (domain.Min() != 0 || domain.Max() != 1)));
789
790 if (domain.Size() == encoding.size()) ++num_full_encodings;
791
792 if (domain.Size() == 2) {
793 CHECK_EQ(encoding.size(), 2);
794 const int lit = encoding.at(domain.Max());
795 CHECK_EQ(encoding.at(domain.Min()), NegatedRef(lit));
796 AddLinearConstraint({}, Domain(domain.Min()),
797 {{var, 1}, {lit, domain.Min() - domain.Max()}});
798 continue;
799 }
800
801 if (domain.Size() == encoding.size() && light_encoding) {
802 BoolArgumentProto* exo = proto.add_constraints()->mutable_exactly_one();
803 for (const auto& [value, lit] : encoding) {
804 exo->add_literals(lit);
805 AddLinearConstraint({lit}, Domain(value), {{var, 1}});
806 }
807 } else if (encoding.size() > domain.Size() / 2 && light_encoding) {
808 BoolArgumentProto* exo = proto.add_constraints()->mutable_exactly_one();
809 // We iterate on the domain to make sure we visit all values.
810 for (const int64_t value : domain.Values()) {
811 const int lit = GetOrCreateEncodingLiteral(var, value);
812 exo->add_literals(lit);
813 AddLinearConstraint({lit}, Domain(value), {{var, 1}});
814 }
815 ++num_partial_encodings;
816 } else {
817 for (const auto& [value, lit] : encoding) {
818 AddLinearConstraint({lit}, Domain(value), {{var, 1}});
819 AddLinearConstraint({NegatedRef(lit)}, Domain(value).Complement(),
820 {{var, 1}});
821 }
822 }
823 }
824
825 for (const auto& [var, encoding] : var_le_value_to_literal) {
826 const Domain domain = ReadDomainFromProto(proto.variables(var));
827 for (const auto& [value, lit] : encoding) {
828 // If the value is the min of the max of the domain, it is equivalent to
829 // the encoding literal of the variable bounds. In that case, we can
830 // link it to the corresponding encoding literal if it exists.
831 if (value == domain.Min()) {
832 const std::optional<int> encoding =
833 GetEncodingLiteral(var, domain.Min());
834 if (encoding.has_value()) {
835 AddLinearConstraint({}, Domain(0),
836 {{lit, 1}, {encoding.value(), -1}});
837 ++num_var_op_value_reif_cached;
838 }
839 }
840 if (value == domain.Max() - 1 && !light_encoding) {
841 // In the case of light encoding, !encoding_lit does not propagate.
842 const std::optional<int> encoding_lit =
843 GetEncodingLiteral(var, domain.Max());
844 if (encoding_lit.has_value()) {
845 AddLinearConstraint(
846 {}, Domain(0),
847 {{lit, 1}, {NegatedRef(encoding_lit.value()), -1}});
848 ++num_var_op_value_reif_cached;
849 continue;
850 }
851 }
852
853 const Domain le_domain(std::numeric_limits<int64_t>::min(), value);
854 AddLinearConstraint({lit}, le_domain, {{var, 1}});
855 AddLinearConstraint({NegatedRef(lit)}, le_domain.Complement(),
856 {{var, 1}});
857 }
858 }
859}
860
861// literal <=> var1 op var2
862
863int CpModelProtoWithMapping::GetOrCreateLiteralForVarEqVar(int var1, int var2) {
864 CHECK_NE(var1, kNoVar);
865 CHECK_NE(var2, kNoVar);
866 if (var1 > var2) std::swap(var1, var2);
867 if (var1 == var2) return LookupConstant(1);
868
869 const std::pair<int, int> key = {var1, var2};
870 const auto it = var_eq_var_to_literal.find(key);
871 if (it != var_eq_var_to_literal.end()) return it->second;
872
873 const int lit = NewBoolVar();
874 AddVarEqVarLiteral(var1, var2, lit);
875 return lit;
876}
877
878int CpModelProtoWithMapping::GetOrCreateLiteralForVarNeVar(int var1, int var2) {
879 return NegatedRef(GetOrCreateLiteralForVarEqVar(var1, var2));
880}
881
882void CpModelProtoWithMapping::AddVarEqVarLiteral(int var1, int var2, int lit) {
883 CHECK_NE(var1, kNoVar);
884 CHECK_NE(var2, kNoVar);
885 if (var1 == var2) {
886 AddImplication({}, lit);
887 return;
888 }
889 if (var1 > var2) std::swap(var1, var2);
890
891 const std::pair<int, int> key = {var1, var2};
892 const auto it = var_eq_var_to_literal.find(key);
893 if (it != var_eq_var_to_literal.end()) {
894 AddLinearConstraint({}, Domain(0), {{lit, 1}, {it->second, -1}});
895 ++num_var_op_var_reif_cached;
896 } else {
897 if (VariableIsBoolean(var1) && VariableIsBoolean(var2)) {
898 AddLinearConstraint({lit}, Domain(0), {{var1, 1}, {var2, -1}});
899 AddLinearConstraint({NegatedRef(lit)}, Domain(1), {{var1, 1}, {var2, 1}});
900 } else {
901 AddLinearConstraint({lit}, Domain(0), {{var1, 1}, {var2, -1}});
902 AddLinearConstraint({NegatedRef(lit)}, Domain(0).Complement(),
903 {{var1, 1}, {var2, -1}});
904 }
905 var_eq_var_to_literal[key] = lit;
906 }
907}
908
909void CpModelProtoWithMapping::AddVarNeVarLiteral(int var1, int var2, int lit) {
910 AddVarEqVarLiteral(var1, var2, NegatedRef(lit));
911}
912
913std::optional<int> CpModelProtoWithMapping::GetVarEqVarLiteral(int var1,
914 int var2) {
915 CHECK_NE(var1, kNoVar);
916 CHECK_NE(var2, kNoVar);
917 if (var1 == var2) return LookupConstant(1);
918 if (var1 > var2) std::swap(var1, var2);
919 const std::pair<int, int> key = {var1, var2};
920 const auto it = var_eq_var_to_literal.find(key);
921 if (it != var_eq_var_to_literal.end()) return it->second;
922 return std::nullopt;
923}
924
925std::optional<int> CpModelProtoWithMapping::GetVarNeVarLiteral(int var1,
926 int var2) {
927 const std::optional<int> lit = GetVarEqVarLiteral(var1, var2);
928 if (lit.has_value()) return NegatedRef(lit.value());
929 return std::nullopt;
930}
931
932int CpModelProtoWithMapping::GetOrCreateLiteralForVarLtVar(int var1, int var2) {
933 CHECK_NE(var1, kNoVar);
934 CHECK_NE(var2, kNoVar);
935 if (var1 == var2) return LookupConstant(0);
936
937 const std::pair<int, int> key = {var1, var2};
938 const auto it = var_lt_var_to_literal.find(key);
939 if (it != var_lt_var_to_literal.end()) return it->second;
940
941 const int lit = NewBoolVar();
942 AddVarLtVarLiteral(var1, var2, lit);
943 return lit;
944}
945
946void CpModelProtoWithMapping::AddVarLtVarLiteral(int var1, int var2, int lit) {
947 CHECK_NE(var1, kNoVar);
948 CHECK_NE(var2, kNoVar);
949 if (var1 == var2) {
950 AddImplication({}, NegatedRef(lit));
951 return;
952 }
953
954 const std::pair<int, int> key = {var1, var2};
955 const auto it = var_lt_var_to_literal.find(key);
956 if (it != var_lt_var_to_literal.end()) {
957 AddLinearConstraint({}, Domain(0), {{lit, 1}, {it->second, -1}});
958 ++num_var_op_var_reif_cached;
959 } else {
960 var_lt_var_to_literal[key] = lit;
961
962 if (VariableIsBoolean(var1) && VariableIsBoolean(var2)) {
963 // bool_var => var1 < var2 => !var1 && var2
964 BoolArgumentProto* is_lt = AddEnforcedConstraint(lit)->mutable_bool_and();
965 is_lt->add_literals(NegatedRef(var1));
966 is_lt->add_literals(var2);
967
968 // !bool_var => var1 >= var2 => var1 || !var2
969 BoolArgumentProto* is_ge =
970 AddEnforcedConstraint(NegatedRef(lit))->mutable_bool_or();
971 is_ge->add_literals(var1);
972 is_ge->add_literals(NegatedRef(var2));
973 } else {
974 AddLinearConstraint({lit},
975 Domain(std::numeric_limits<int64_t>::min(), -1),
976 {{var1, 1}, {var2, -1}});
977 AddLinearConstraint({NegatedRef(lit)},
978 Domain(0, std::numeric_limits<int64_t>::max()),
979 {{var1, 1}, {var2, -1}});
980 }
981 }
982}
983
984std::optional<int> CpModelProtoWithMapping::GetVarLtVarLiteral(int var1,
985 int var2) {
986 CHECK_NE(var1, kNoVar);
987 CHECK_NE(var2, kNoVar);
988 if (var1 == var2) return LookupConstant(0);
989 const std::pair<int, int> key = {var1, var2};
990 const auto it = var_lt_var_to_literal.find(key);
991 if (it != var_lt_var_to_literal.end()) return it->second;
992 return std::nullopt;
993}
994
995int CpModelProtoWithMapping::GetOrCreateLiteralForVarLeVar(int var1, int var2) {
996 CHECK_NE(var1, kNoVar);
997 CHECK_NE(var2, kNoVar);
998 if (var1 == var2) return LookupConstant(1);
999 return NegatedRef(GetOrCreateLiteralForVarLtVar(var2, var1));
1000}
1001
1002void CpModelProtoWithMapping::AddVarLeVarLiteral(int var1, int var2, int lit) {
1003 CHECK_NE(var1, kNoVar);
1004 CHECK_NE(var2, kNoVar);
1005 if (var1 == var2) {
1006 AddImplication({}, lit);
1007 return;
1008 }
1009
1010 AddVarLtVarLiteral(var2, var1, NegatedRef(lit));
1011}
1012
1013std::optional<int> CpModelProtoWithMapping::GetVarLeVarLiteral(int var1,
1014 int var2) {
1015 CHECK_NE(var1, kNoVar);
1016 CHECK_NE(var2, kNoVar);
1017 if (var1 == var2) return LookupConstant(1);
1018
1019 const std::optional<int> lit = GetVarLtVarLiteral(var2, var1);
1020 if (lit.has_value()) return NegatedRef(lit.value());
1021 return std::nullopt;
1022}
1023
1024void CpModelProtoWithMapping::AddVarOrVarLiteral(int var1, int var2, int lit) {
1025 CHECK_NE(var1, kNoVar);
1026 CHECK_NE(var2, kNoVar);
1027 CHECK(VariableIsBoolean(var1));
1028 CHECK(VariableIsBoolean(var2));
1029 if (var1 == var2) {
1030 AddLinearConstraint({}, Domain(0), {{var1, 1}, {lit, -1}});
1031 return;
1032 }
1033 if (var1 > var2) std::swap(var1, var2);
1034
1035 const std::pair<int, int> key = {var1, var2};
1036 const auto it = var_or_var_to_literal.find(key);
1037 if (it != var_or_var_to_literal.end()) {
1038 AddLinearConstraint({}, Domain(0), {{lit, 1}, {it->second, -1}});
1039 ++num_var_op_var_reif_cached;
1040 } else {
1041 AddImplication({var1}, lit);
1042 AddImplication({var2}, lit);
1043 AddImplication({NegatedRef(var1), NegatedRef(var2)}, NegatedRef(lit));
1044 var_or_var_to_literal[key] = lit;
1045 }
1046}
1047
1048void CpModelProtoWithMapping::AddVarAndVarLiteral(int var1, int var2, int lit) {
1049 CHECK_NE(var1, kNoVar);
1050 CHECK_NE(var2, kNoVar);
1051 CHECK(VariableIsBoolean(var1));
1052 CHECK(VariableIsBoolean(var2));
1053 if (var1 == var2) {
1054 AddLinearConstraint({}, Domain(0), {{var1, 1}, {lit, -1}});
1055 return;
1056 }
1057 if (var1 > var2) std::swap(var1, var2);
1058
1059 const std::pair<int, int> key = {var1, var2};
1060 const auto it = var_and_var_to_literal.find(key);
1061 if (it != var_and_var_to_literal.end()) {
1062 AddLinearConstraint({}, Domain(0), {{lit, 1}, {it->second, -1}});
1063 ++num_var_op_var_reif_cached;
1064 } else {
1065 AddImplication({lit}, var1);
1066 AddImplication({lit}, var2);
1067 AddImplication({var1, var2}, lit);
1068 var_and_var_to_literal[key] = lit;
1069 }
1070}
1071
1072int CpModelProtoWithMapping::GetOrCreateOptionalInterval(VarOrValue start,
1073 VarOrValue size,
1074 int opt_var) {
1075 const int interval_index = proto.constraints_size();
1076 const std::tuple<int, int64_t, int, int64_t, int> key =
1077 std::make_tuple(start.var, start.value, size.var, size.value, opt_var);
1078 const auto [it, inserted] =
1079 interval_key_to_index.insert({key, interval_index});
1080 if (!inserted) {
1081 return it->second;
1082 }
1083
1084 if (size.var == kNoVar || start.var == kNoVar) { // Size or start fixed.
1085 auto* interval = AddEnforcedConstraint(opt_var)->mutable_interval();
1086 if (start.var != kNoVar) {
1087 interval->mutable_start()->add_vars(start.var);
1088 interval->mutable_start()->add_coeffs(1);
1089 interval->mutable_end()->add_vars(start.var);
1090 interval->mutable_end()->add_coeffs(1);
1091 } else {
1092 interval->mutable_start()->set_offset(start.value);
1093 interval->mutable_end()->set_offset(start.value);
1094 }
1095
1096 if (size.var != kNoVar) {
1097 interval->mutable_size()->add_vars(size.var);
1098 interval->mutable_size()->add_coeffs(1);
1099 interval->mutable_end()->add_vars(size.var);
1100 interval->mutable_end()->add_coeffs(1);
1101 } else {
1102 interval->mutable_size()->set_offset(size.value);
1103 interval->mutable_end()->set_offset(size.value +
1104 interval->end().offset());
1105 }
1106 return interval_index;
1107 } else { // start and size are variable.
1108 const int end_var = proto.variables_size();
1110 ReadDomainFromProto(proto.variables(start.var))
1111 .AdditionWith(ReadDomainFromProto(proto.variables(size.var))),
1112 proto.add_variables());
1113
1114 // Create the interval.
1115 auto* interval = AddEnforcedConstraint(opt_var)->mutable_interval();
1116 interval->mutable_start()->add_vars(start.var);
1117 interval->mutable_start()->add_coeffs(1);
1118 interval->mutable_size()->add_vars(size.var);
1119 interval->mutable_size()->add_coeffs(1);
1120 interval->mutable_end()->add_vars(end_var);
1121 interval->mutable_end()->add_coeffs(1);
1122 return interval_index;
1123 }
1124}
1125
1126std::vector<int> CpModelProtoWithMapping::CreateIntervals(
1127 absl::Span<const VarOrValue> starts, absl::Span<const VarOrValue> sizes) {
1128 std::vector<int> intervals;
1129 intervals.reserve(starts.size());
1130 for (int i = 0; i < starts.size(); ++i) {
1131 intervals.push_back(
1132 GetOrCreateOptionalInterval(starts[i], sizes[i], kNoVar));
1133 }
1134 return intervals;
1135}
1136
1137std::vector<int> CpModelProtoWithMapping::CreateNonZeroOrOptionalIntervals(
1138 absl::Span<const VarOrValue> starts, absl::Span<const VarOrValue> sizes) {
1139 std::vector<int> intervals;
1140 for (int i = 0; i < starts.size(); ++i) {
1141 const int opt_var = NonZeroLiteralFrom(sizes[i]);
1142 intervals.push_back(
1143 GetOrCreateOptionalInterval(starts[i], sizes[i], opt_var));
1144 }
1145 return intervals;
1146}
1147
1148int CpModelProtoWithMapping::NonZeroLiteralFrom(VarOrValue size) {
1149 if (size.var == kNoVar) {
1150 return LookupConstant(size.value > 0);
1151 }
1152 const auto it = var_to_lit_implies_greater_than_zero.find(size.var);
1153 if (it != var_to_lit_implies_greater_than_zero.end()) {
1154 return it->second;
1155 }
1156
1157 const IntegerVariableProto& var_proto = proto.variables(size.var);
1158 const Domain domain = ReadDomainFromProto(var_proto);
1159 CHECK_GE(domain.Min(), 0);
1160 if (domain.Min() > 0) return LookupConstant(1);
1161 if (domain.Max() == 0) {
1162 return LookupConstant(0);
1163 }
1164
1165 const int var_greater_than_zero_lit = NewBoolVar();
1166
1167 AddLinearConstraint({var_greater_than_zero_lit},
1168 domain.IntersectionWith({1, domain.Max()}),
1169 {{size.var, 1}});
1170 AddLinearConstraint({NegatedRef(var_greater_than_zero_lit)}, Domain(0),
1171 {{size.var, 1}});
1172
1173 var_to_lit_implies_greater_than_zero[size.var] = var_greater_than_zero_lit;
1174 return var_greater_than_zero_lit;
1175}
1176
1177void CpModelProtoWithMapping::FillAMinusBInDomain(
1178 const std::vector<int64_t>& domain, const fz::Constraint& fz_ct,
1179 ConstraintProto* ct) {
1180 auto* arg = ct->mutable_linear();
1181 if (fz_ct.arguments[1].type == fz::Argument::INT_VALUE) {
1182 const int64_t value = fz_ct.arguments[1].Value();
1183 const int var_a = LookupVar(fz_ct.arguments[0]);
1184 for (const int64_t domain_bound : domain) {
1185 if (domain_bound == std::numeric_limits<int64_t>::min() ||
1186 domain_bound == std::numeric_limits<int64_t>::max()) {
1187 arg->add_domain(domain_bound);
1188 } else {
1189 arg->add_domain(domain_bound + value);
1190 }
1191 }
1192 AddTermToLinearConstraint(var_a, 1, arg);
1193 } else if (fz_ct.arguments[0].type == fz::Argument::INT_VALUE) {
1194 const int64_t value = fz_ct.arguments[0].Value();
1195 const int var_b = LookupVar(fz_ct.arguments[1]);
1196 for (int64_t domain_bound : gtl::reversed_view(domain)) {
1197 if (domain_bound == std::numeric_limits<int64_t>::min()) {
1198 arg->add_domain(std::numeric_limits<int64_t>::max());
1199 } else if (domain_bound == std::numeric_limits<int64_t>::max()) {
1200 arg->add_domain(std::numeric_limits<int64_t>::min());
1201 } else {
1202 arg->add_domain(value - domain_bound);
1203 }
1204 }
1205 AddTermToLinearConstraint(var_b, 1, arg);
1206 } else {
1207 for (const int64_t domain_bound : domain) arg->add_domain(domain_bound);
1208 AddTermToLinearConstraint(LookupVar(fz_ct.arguments[0]), 1, arg);
1209 AddTermToLinearConstraint(LookupVar(fz_ct.arguments[1]), -1, arg);
1210 }
1211}
1212
1213void CpModelProtoWithMapping::FillLinearConstraintWithGivenDomain(
1214 absl::Span<const int64_t> domain, const fz::Constraint& fz_ct,
1215 ConstraintProto* ct) {
1216 auto* arg = ct->mutable_linear();
1217 for (const int64_t domain_bound : domain) arg->add_domain(domain_bound);
1218 std::vector<int> vars = LookupVars(fz_ct.arguments[1]);
1219 for (int i = 0; i < vars.size(); ++i) {
1220 AddTermToLinearConstraint(vars[i], fz_ct.arguments[0].values[i], arg);
1221 }
1222}
1223
1224void CpModelProtoWithMapping::BuildTableFromDomainIntLinEq(
1225 const fz::Constraint& fz_ct, ConstraintProto* ct) {
1226 const std::vector<int64_t>& coeffs = fz_ct.arguments[0].values;
1227 const std::vector<int> vars = LookupVars(fz_ct.arguments[1]);
1228 const int rhs = fz_ct.arguments[2].Value();
1229 CHECK_EQ(coeffs.back(), -1);
1230 for (const int var : vars) {
1231 LinearExpressionProto* expr = ct->mutable_table()->add_exprs();
1232 expr->add_vars(var);
1233 expr->add_coeffs(1);
1234 }
1235
1236 switch (vars.size()) {
1237 case 3: {
1238 const Domain domain0 = ReadDomainFromProto(proto.variables(vars[0]));
1239 const Domain domain1 = ReadDomainFromProto(proto.variables(vars[1]));
1240 for (const int64_t v0 : domain0.Values()) {
1241 for (const int64_t v1 : domain1.Values()) {
1242 const int64_t v2 = coeffs[0] * v0 + coeffs[1] * v1 - rhs;
1243 ct->mutable_table()->add_values(v0);
1244 ct->mutable_table()->add_values(v1);
1245 ct->mutable_table()->add_values(v2);
1246 }
1247 }
1248 break;
1249 }
1250 case 4: {
1251 const Domain domain0 = ReadDomainFromProto(proto.variables(vars[0]));
1252 const Domain domain1 = ReadDomainFromProto(proto.variables(vars[1]));
1253 const Domain domain2 = ReadDomainFromProto(proto.variables(vars[2]));
1254 for (const int64_t v0 : domain0.Values()) {
1255 for (const int64_t v1 : domain1.Values()) {
1256 for (const int64_t v2 : domain2.Values()) {
1257 const int64_t v3 =
1258 coeffs[0] * v0 + coeffs[1] * v1 + coeffs[2] * v2 - rhs;
1259 ct->mutable_table()->add_values(v0);
1260 ct->mutable_table()->add_values(v1);
1261 ct->mutable_table()->add_values(v2);
1262 ct->mutable_table()->add_values(v3);
1263 }
1264 }
1265 }
1266 break;
1267 }
1268 default:
1269 LOG(FATAL) << "Unsupported size";
1270 }
1271}
1272
1273void CpModelProtoWithMapping::AlwaysFalseConstraint(const fz::Constraint& fz_ct,
1274 ConstraintProto* ct) {
1275 // An empty clause is always false.
1276 ct->mutable_bool_or();
1277}
1278
1279void CpModelProtoWithMapping::BoolClauseConstraint(const fz::Constraint& fz_ct,
1280 ConstraintProto* ct) {
1281 auto* arg = ct->mutable_bool_or();
1282 for (const int var : LookupVars(fz_ct.arguments[0])) {
1283 arg->add_literals(var);
1284 }
1285 for (const int var : LookupVars(fz_ct.arguments[1])) {
1286 arg->add_literals(NegatedRef(var));
1287 }
1288}
1289void CpModelProtoWithMapping::BoolXorConstraint(const fz::Constraint& fz_ct,
1290 ConstraintProto* ct) {
1291 // This is not the same semantics as the array_bool_xor as this constraint
1292 // is actually a fully reified xor(a, b) <==> x.
1293 const int a = LookupVar(fz_ct.arguments[0]);
1294 const int b = LookupVar(fz_ct.arguments[1]);
1295 const int x = LookupVar(fz_ct.arguments[2]);
1296
1297 // not(x) => a == b
1298 ct->add_enforcement_literal(NegatedRef(x));
1299 auto* const refute = ct->mutable_linear();
1300 FillDomainInProto(0, refute);
1301 AddTermToLinearConstraint(a, 1, refute);
1302 AddTermToLinearConstraint(b, -1, refute);
1303
1304 // x => a + b == 1
1305 AddLinearConstraint({x}, Domain(1), {{a, 1}, {b, 1}});
1306}
1307void CpModelProtoWithMapping::ArrayBoolXorConstraint(
1308 const fz::Constraint& fz_ct, ConstraintProto* ct) {
1309 auto* arg = ct->mutable_bool_xor();
1310 for (const int var : LookupVars(fz_ct.arguments[0])) {
1311 arg->add_literals(var);
1312 }
1313}
1314
1315void CpModelProtoWithMapping::BoolOrIntLeConstraint(const fz::Constraint& fz_ct,
1316 ConstraintProto* ct) {
1317 FillAMinusBInDomain({std::numeric_limits<int64_t>::min(), 0}, fz_ct, ct);
1318}
1319
1320void CpModelProtoWithMapping::BoolOrIntGeConstraint(const fz::Constraint& fz_ct,
1321 ConstraintProto* ct) {
1322 FillAMinusBInDomain({0, std::numeric_limits<int64_t>::max()}, fz_ct, ct);
1323}
1324
1325void CpModelProtoWithMapping::BoolOrIntLtConstraint(const fz::Constraint& fz_ct,
1326 ConstraintProto* ct) {
1327 FillAMinusBInDomain({std::numeric_limits<int64_t>::min(), -1}, fz_ct, ct);
1328}
1329
1330void CpModelProtoWithMapping::BoolOrIntGtConstraint(const fz::Constraint& fz_ct,
1331 ConstraintProto* ct) {
1332 FillAMinusBInDomain({1, std::numeric_limits<int64_t>::max()}, fz_ct, ct);
1333}
1334
1335void CpModelProtoWithMapping::BoolEqConstraint(const fz::Constraint& fz_ct,
1336 ConstraintProto* ct) {
1337 FillAMinusBInDomain({0, 0}, fz_ct, ct);
1338}
1339
1340void CpModelProtoWithMapping::BoolNeConstraint(const fz::Constraint& fz_ct,
1341 ConstraintProto* ct) {
1342 auto* arg = ct->mutable_linear();
1343 FillDomainInProto(1, arg);
1344 AddTermToLinearConstraint(LookupVar(fz_ct.arguments[0]), 1, arg);
1345 AddTermToLinearConstraint(LookupVar(fz_ct.arguments[1]), 1, arg);
1346}
1347
1348void CpModelProtoWithMapping::IntNeConstraint(const fz::Constraint& fz_ct,
1349 ConstraintProto* ct) {
1350 FillAMinusBInDomain({std::numeric_limits<int64_t>::min(), -1, 1,
1351 std::numeric_limits<int64_t>::max()},
1352 fz_ct, ct);
1353}
1354
1355void CpModelProtoWithMapping::IntLinEqConstraint(const fz::Constraint& fz_ct,
1356 ConstraintProto* ct) {
1357 // Special case for the index of element 2D and element 3D constraints.
1358 if (fz_ct.strong_propagation && fz_ct.arguments[0].Size() >= 3 &&
1359 fz_ct.arguments[0].Size() <= 4 &&
1360 fz_ct.arguments[0].values.back() == -1) {
1361 BuildTableFromDomainIntLinEq(fz_ct, ct);
1362 } else {
1363 const int64_t rhs = fz_ct.arguments[2].values[0];
1364 FillLinearConstraintWithGivenDomain({rhs, rhs}, fz_ct, ct);
1365 }
1366}
1367
1368void CpModelProtoWithMapping::BoolLinEqConstraint(const fz::Constraint& fz_ct,
1369 ConstraintProto* ct) {
1370 auto* arg = ct->mutable_linear();
1371 const std::vector<int> vars = LookupVars(fz_ct.arguments[1]);
1372 if (fz_ct.arguments[2].IsVariable()) {
1373 FillDomainInProto(0, arg);
1374 AddTermToLinearConstraint(LookupVar(fz_ct.arguments[2]), -1, arg);
1375 } else {
1376 const int64_t v = fz_ct.arguments[2].Value();
1377 FillDomainInProto(v, arg);
1378 }
1379 for (int i = 0; i < vars.size(); ++i) {
1380 AddTermToLinearConstraint(vars[i], fz_ct.arguments[0].values[i], arg);
1381 }
1382}
1383
1384void CpModelProtoWithMapping::BoolOrIntLinLeConstraint(
1385 const fz::Constraint& fz_ct, ConstraintProto* ct) {
1386 const int64_t rhs = fz_ct.arguments[2].values[0];
1387 FillLinearConstraintWithGivenDomain(
1388 {std::numeric_limits<int64_t>::min(), rhs}, fz_ct, ct);
1389}
1390
1391void CpModelProtoWithMapping::IntLinLtConstraint(const fz::Constraint& fz_ct,
1392 ConstraintProto* ct) {
1393 const int64_t rhs = fz_ct.arguments[2].values[0];
1394 FillLinearConstraintWithGivenDomain(
1395 {std::numeric_limits<int64_t>::min(), rhs - 1}, fz_ct, ct);
1396}
1397
1398void CpModelProtoWithMapping::IntLinGeConstraint(const fz::Constraint& fz_ct,
1399 ConstraintProto* ct) {
1400 const int64_t rhs = fz_ct.arguments[2].values[0];
1401 FillLinearConstraintWithGivenDomain(
1402 {rhs, std::numeric_limits<int64_t>::max()}, fz_ct, ct);
1403}
1404
1405void CpModelProtoWithMapping::IntLinGtConstraint(const fz::Constraint& fz_ct,
1406 ConstraintProto* ct) {
1407 const int64_t rhs = fz_ct.arguments[2].values[0];
1408 FillLinearConstraintWithGivenDomain(
1409 {rhs + 1, std::numeric_limits<int64_t>::max()}, fz_ct, ct);
1410}
1411
1412void CpModelProtoWithMapping::IntLinNeConstraint(const fz::Constraint& fz_ct,
1413 ConstraintProto* ct) {
1414 const int64_t rhs = fz_ct.arguments[2].values[0];
1415 FillLinearConstraintWithGivenDomain(
1416 {std::numeric_limits<int64_t>::min(), rhs - 1, rhs + 1,
1417 std::numeric_limits<int64_t>::max()},
1418 fz_ct, ct);
1419}
1420
1421void CpModelProtoWithMapping::SetCardConstraint(const fz::Constraint& fz_ct,
1422 ConstraintProto* ct) {
1423 int64_t set_size = 0;
1424 if (fz_ct.arguments[0].type == fz::Argument::INT_LIST) {
1425 set_size = fz_ct.arguments[0].values.size();
1426 } else if (fz_ct.arguments[0].type == fz::Argument::INT_INTERVAL) {
1427 set_size = fz_ct.arguments[0].values[1] - fz_ct.arguments[0].values[0] + 1;
1428 } else {
1429 LOG(FATAL) << "Wrong format" << fz_ct.DebugString();
1430 }
1431
1432 auto* arg = ct->mutable_linear();
1433 FillDomainInProto(set_size, arg);
1434 AddTermToLinearConstraint(LookupVar(fz_ct.arguments[1]), 1, arg);
1435}
1436
1437void CpModelProtoWithMapping::SetInConstraint(const fz::Constraint& fz_ct,
1438 ConstraintProto* ct) {
1439 auto* arg = ct->mutable_linear();
1440 arg->add_vars(LookupVar(fz_ct.arguments[0]));
1441 arg->add_coeffs(1);
1442 if (fz_ct.arguments[1].type == fz::Argument::INT_LIST) {
1443 FillDomainInProto(Domain::FromValues(std::vector<int64_t>{
1444 fz_ct.arguments[1].values.begin(),
1445 fz_ct.arguments[1].values.end()}),
1446 arg);
1447 } else if (fz_ct.arguments[1].type == fz::Argument::INT_INTERVAL) {
1448 FillDomainInProto(fz_ct.arguments[1].values[0],
1449 fz_ct.arguments[1].values[1], arg);
1450 } else {
1451 LOG(FATAL) << "Wrong format";
1452 }
1453}
1454
1455void CpModelProtoWithMapping::SetInNegatedConstraint(
1456 const fz::Constraint& fz_ct, ConstraintProto* ct) {
1457 auto* arg = ct->mutable_linear();
1458 if (fz_ct.arguments[1].type == fz::Argument::INT_LIST) {
1459 FillDomainInProto(Domain::FromValues(std::vector<int64_t>{
1460 fz_ct.arguments[1].values.begin(),
1461 fz_ct.arguments[1].values.end()})
1462 .Complement(),
1463 arg);
1464 } else if (fz_ct.arguments[1].type == fz::Argument::INT_INTERVAL) {
1466 Domain(fz_ct.arguments[1].values[0], fz_ct.arguments[1].values[1])
1467 .Complement(),
1468 arg);
1469 } else {
1470 LOG(FATAL) << "Wrong format";
1471 }
1472 AddTermToLinearConstraint(LookupVar(fz_ct.arguments[0]), 1, arg);
1473}
1474
1475void CpModelProtoWithMapping::IntMinConstraint(const fz::Constraint& fz_ct,
1476 ConstraintProto* ct) {
1477 auto* arg = ct->mutable_lin_max();
1478 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0], /*negate=*/true);
1479 *arg->add_exprs() = LookupExpr(fz_ct.arguments[1], /*negate=*/true);
1480 *arg->mutable_target() = LookupExpr(fz_ct.arguments[2], /*negate=*/true);
1481}
1482
1483void CpModelProtoWithMapping::ArrayIntMinConstraint(const fz::Constraint& fz_ct,
1484 ConstraintProto* ct) {
1485 auto* arg = ct->mutable_lin_max();
1486 *arg->mutable_target() = LookupExpr(fz_ct.arguments[0], /*negate=*/true);
1487 for (int i = 0; i < fz_ct.arguments[1].Size(); ++i) {
1488 *arg->add_exprs() = LookupExprAt(fz_ct.arguments[1], i, /*negate=*/true);
1489 }
1490}
1491
1492void CpModelProtoWithMapping::IntMaxConstraint(const fz::Constraint& fz_ct,
1493 ConstraintProto* ct) {
1494 auto* arg = ct->mutable_lin_max();
1495 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
1496 *arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
1497 *arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
1498}
1499
1500void CpModelProtoWithMapping::ArrayIntMaxConstraint(const fz::Constraint& fz_ct,
1501 ConstraintProto* ct) {
1502 auto* arg = ct->mutable_lin_max();
1503 *arg->mutable_target() = LookupExpr(fz_ct.arguments[0]);
1504 for (int i = 0; i < fz_ct.arguments[1].Size(); ++i) {
1505 *arg->add_exprs() = LookupExprAt(fz_ct.arguments[1], i);
1506 }
1507}
1508
1509void CpModelProtoWithMapping::IntTimesConstraint(const fz::Constraint& fz_ct,
1510 ConstraintProto* ct) {
1511 auto* arg = ct->mutable_int_prod();
1512 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
1513 *arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
1514 *arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
1515}
1516
1517void CpModelProtoWithMapping::IntAbsConstraint(const fz::Constraint& fz_ct,
1518 ConstraintProto* ct) {
1519 auto* arg = ct->mutable_lin_max();
1520 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
1521 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0], /*negate=*/true);
1522 *arg->mutable_target() = LookupExpr(fz_ct.arguments[1]);
1523}
1524
1525void CpModelProtoWithMapping::IntPlusConstraint(const fz::Constraint& fz_ct,
1526 ConstraintProto* ct) {
1527 auto* arg = ct->mutable_linear();
1528 FillDomainInProto(0, arg);
1529 AddTermToLinearConstraint(LookupVar(fz_ct.arguments[0]), 1, arg);
1530 AddTermToLinearConstraint(LookupVar(fz_ct.arguments[1]), 1, arg);
1531 AddTermToLinearConstraint(LookupVar(fz_ct.arguments[2]), -1, arg);
1532}
1533
1534void CpModelProtoWithMapping::IntDivConstraint(const fz::Constraint& fz_ct,
1535 ConstraintProto* ct) {
1536 auto* arg = ct->mutable_int_div();
1537 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
1538 *arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
1539 *arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
1540}
1541
1542void CpModelProtoWithMapping::IntModConstraint(const fz::Constraint& fz_ct,
1543 ConstraintProto* ct) {
1544 auto* arg = ct->mutable_int_mod();
1545 *arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
1546 *arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
1547 *arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
1548}
1549
1550void CpModelProtoWithMapping::ArrayElementConstraint(
1551 const fz::Constraint& fz_ct, ConstraintProto* ct) {
1552 auto* arg = ct->mutable_element();
1553 *arg->mutable_linear_index() = LookupExpr(fz_ct.arguments[0]);
1554 if (!absl::EndsWith(fz_ct.type, "_nonshifted")) {
1555 arg->mutable_linear_index()->set_offset(arg->linear_index().offset() - 1);
1556 }
1557 *arg->mutable_linear_target() = LookupExpr(fz_ct.arguments[2]);
1558
1559 for (const VarOrValue elem : LookupVarsOrValues(fz_ct.arguments[1])) {
1560 auto* elem_proto = ct->mutable_element()->add_exprs();
1561 if (elem.var != kNoVar) {
1562 elem_proto->add_vars(elem.var);
1563 elem_proto->add_coeffs(1);
1564 } else {
1565 elem_proto->set_offset(elem.value);
1566 }
1567 }
1568}
1569
1570void CpModelProtoWithMapping::OrToolsConstantElementConstraint(
1571 const fz::Constraint& fz_ct, ConstraintProto* ct) {
1572 auto* arg = ct->mutable_element();
1573
1574 // Index.
1575 *arg->mutable_linear_index() = LookupExpr(fz_ct.arguments[0]);
1576 const int64_t index_min = fz_ct.arguments[1].values[0];
1577 arg->mutable_linear_index()->set_offset(arg->linear_index().offset() -
1578 index_min);
1579
1580 // Target.
1581 *arg->mutable_linear_target() = LookupExpr(fz_ct.arguments[3]);
1582
1583 // Values.
1584 for (const int64_t value : fz_ct.arguments[2].values) {
1585 ct->mutable_element()->add_exprs()->set_offset(value);
1586 }
1587}
1588
1589void CpModelProtoWithMapping::OrToolsVariableElementConstraint(
1590 const fz::Constraint& fz_ct, ConstraintProto* ct) {
1591 auto* arg = ct->mutable_element();
1592
1593 // Index.
1594 *arg->mutable_linear_index() = LookupExpr(fz_ct.arguments[0]);
1595 const int64_t index_min = fz_ct.arguments[1].values[0];
1596 arg->mutable_linear_index()->set_offset(arg->linear_index().offset() -
1597 index_min);
1598
1599 // Target.
1600 *arg->mutable_linear_target() = LookupExpr(fz_ct.arguments[3]);
1601
1602 // Expressions.
1603 for (const VarOrValue elem : LookupVarsOrValues(fz_ct.arguments[2])) {
1604 auto* elem_proto = ct->mutable_element()->add_exprs();
1605 if (elem.var != kNoVar) {
1606 elem_proto->add_vars(elem.var);
1607 elem_proto->add_coeffs(1);
1608 } else {
1609 elem_proto->set_offset(elem.value);
1610 }
1611 }
1612}
1613
1614void CpModelProtoWithMapping::OrToolsTableIntConstraint(
1615 const fz::Constraint& fz_ct, ConstraintProto* ct) {
1616 auto* arg = ct->mutable_table();
1617 for (const VarOrValue v : LookupVarsOrValues(fz_ct.arguments[0])) {
1618 LinearExpressionProto* expr = arg->add_exprs();
1619 if (v.var != kNoVar) {
1620 expr->add_vars(v.var);
1621 expr->add_coeffs(1);
1622 } else {
1623 expr->set_offset(v.value);
1624 }
1625 }
1626 for (const int64_t value : fz_ct.arguments[1].values) arg->add_values(value);
1627}
1628
1629void CpModelProtoWithMapping::OrToolsRegular(const fz::Constraint& fz_ct,
1630 ConstraintProto* ct) {
1631 auto* arg = ct->mutable_automaton();
1632 for (const VarOrValue v : LookupVarsOrValues(fz_ct.arguments[0])) {
1633 LinearExpressionProto* expr = arg->add_exprs();
1634 if (v.var != kNoVar) {
1635 expr->add_vars(v.var);
1636 expr->add_coeffs(1);
1637 } else {
1638 expr->set_offset(v.value);
1639 }
1640 }
1641
1642 int count = 0;
1643 const int num_states = fz_ct.arguments[1].Value();
1644 const int num_values = fz_ct.arguments[2].Value();
1645 for (int i = 1; i <= num_states; ++i) {
1646 for (int j = 1; j <= num_values; ++j) {
1647 CHECK_LT(count, fz_ct.arguments[3].values.size());
1648 const int next = fz_ct.arguments[3].values[count++];
1649 if (next == 0) continue; // 0 is a failing state.
1650 arg->add_transition_tail(i);
1651 arg->add_transition_label(j);
1652 arg->add_transition_head(next);
1653 }
1654 }
1655
1656 arg->set_starting_state(fz_ct.arguments[4].Value());
1657 switch (fz_ct.arguments[5].type) {
1658 case fz::Argument::INT_VALUE: {
1659 arg->add_final_states(fz_ct.arguments[5].values[0]);
1660 break;
1661 }
1662 case fz::Argument::INT_INTERVAL: {
1663 for (int v = fz_ct.arguments[5].values[0];
1664 v <= fz_ct.arguments[5].values[1]; ++v) {
1665 arg->add_final_states(v);
1666 }
1667 break;
1668 }
1669 case fz::Argument::INT_LIST: {
1670 for (const int v : fz_ct.arguments[5].values) {
1671 arg->add_final_states(v);
1672 }
1673 break;
1674 }
1675 default: {
1676 LOG(FATAL) << "Wrong constraint " << fz_ct.DebugString();
1677 }
1678 }
1679}
1680
1681void CpModelProtoWithMapping::OrToolsArgMax(const fz::Constraint& fz_ct,
1682 ConstraintProto* ct) {
1683 const std::vector<int> x = LookupVars(fz_ct.arguments[0]);
1684 const int num_vars = x.size();
1685 const int z = LookupVar(fz_ct.arguments[1]);
1686 const int64_t min_index = fz_ct.arguments[2].Value();
1687 const int64_t multiplier = fz_ct.arguments[3].Value();
1688 DCHECK_EQ(std::abs(multiplier), 1);
1689 int64_t min_range = std::numeric_limits<int64_t>::max();
1690 int64_t max_range = std::numeric_limits<int64_t>::min();
1691 auto* max_array = ct->mutable_lin_max();
1692 for (int i = 0; i < num_vars; ++i) {
1693 const IntegerVariableProto& var_proto = proto.variables(x[i]);
1694 const int64_t min_var = var_proto.domain(0);
1695 const int64_t max_var = var_proto.domain(var_proto.domain_size() - 1);
1696 const int64_t offset = num_vars - i;
1697 int64_t min_expr_range = min_var * multiplier * num_vars + offset;
1698 int64_t max_expr_range = max_var * multiplier * num_vars + offset;
1699 if (multiplier == -1) std::swap(min_expr_range, max_expr_range);
1700 min_range = std::min(min_range, min_expr_range);
1701 max_range = std::max(max_range, max_expr_range);
1702 auto* exprs = max_array->add_exprs();
1703 exprs->add_vars(x[i]);
1704 exprs->add_coeffs(num_vars * multiplier);
1705 exprs->set_offset(offset);
1706 }
1707 const int max_var = NewIntVar(min_range, max_range);
1708 // (argmax(x) == z) <=> (x[z] == max_i(x[i]))
1709 //
1710 // With tie - breakers :
1711 // (argmax(x) == z) <=>
1712 // (num_vars * x[z] + num_vars - z) = max_i(num_vars * x[i] + num_vars - i)
1713 max_array->mutable_target()->add_vars(max_var);
1714 max_array->mutable_target()->add_coeffs(1);
1715
1716 for (const auto& [value, literal] : GetFullEncoding(z)) {
1717 if (value < min_index || value >= min_index + num_vars) {
1718 AddImplication({}, NegatedRef(literal));
1719 }
1720 const int64_t index = value - min_index;
1721 AddLinearConstraint({literal}, Domain(index - num_vars),
1722 {{x[index], num_vars * multiplier}, {max_var, -1}});
1723 AddLinearConstraint(
1724 {NegatedRef(literal)},
1725 {std::numeric_limits<int64_t>::min(), index - num_vars - 1},
1726 {{x[index], num_vars * multiplier}, {max_var, -1}});
1727 }
1728}
1729
1730void CpModelProtoWithMapping::FznAllDifferentInt(const fz::Constraint& fz_ct,
1731 ConstraintProto* ct) {
1732 auto* arg = ct->mutable_all_diff();
1733 for (int i = 0; i < fz_ct.arguments[0].Size(); ++i) {
1734 *arg->add_exprs() = LookupExprAt(fz_ct.arguments[0], i);
1735 }
1736}
1737
1738void CpModelProtoWithMapping::FznValuePrecedeInt(const fz::Constraint& fz_ct,
1739 ConstraintProto* ct) {
1740 const int64_t before = fz_ct.arguments[0].Value();
1741 const int64_t after = fz_ct.arguments[1].Value();
1742 const std::vector<int> x = LookupVars(fz_ct.arguments[2]);
1743 if (x.empty()) return;
1744
1745 std::vector<int> hold(x.size());
1746 hold[0] = LookupConstant(0);
1747 for (int i = 1; i < x.size(); ++i) hold[i] = NewBoolVar();
1748
1749 if (absl::GetFlag(FLAGS_fz_use_light_encoding) && !sat_enumeration_) {
1750 for (int i = 0; i < x.size(); ++i) {
1751 const int is_before = GetOrCreateEncodingLiteral(x[i], before);
1752 if (i + 1 < x.size()) {
1753 AddImplication({is_before}, hold[i + 1]);
1754 AddLinearConstraint({NegatedRef(is_before)}, Domain(0),
1755 {{hold[i], 1}, {hold[i + 1], -1}});
1756 }
1757 AddLinearConstraint({NegatedRef(hold[i])}, Domain(after).Complement(),
1758 {{x[i], 1}});
1759 }
1760 ++num_light_encodings;
1761 } else {
1762 for (int i = 0; i < x.size(); ++i) {
1763 const int is_before = GetOrCreateEncodingLiteral(x[i], before);
1764 const int is_after = GetOrCreateEncodingLiteral(x[i], after);
1765 if (i + 1 < x.size()) {
1766 AddImplication({is_before}, hold[i + 1]);
1767 AddLinearConstraint({NegatedRef(is_before)}, Domain(0),
1768 {{hold[i], 1}, {hold[i + 1], -1}});
1769 }
1770 AddImplication({NegatedRef(hold[i])}, NegatedRef(is_after));
1771 }
1772 }
1773}
1774
1775void CpModelProtoWithMapping::OrToolsCountEqCst(const fz::Constraint& fz_ct,
1776 ConstraintProto* ct) {
1777 const std::vector<VarOrValue> counts = LookupVarsOrValues(fz_ct.arguments[0]);
1778 const int64_t value = fz_ct.arguments[1].Value();
1779 const VarOrValue target = LookupVarOrValue(fz_ct.arguments[2]);
1780 LinearConstraintProto* arg = ct->mutable_linear();
1781 int64_t fixed_contributions = 0;
1782 for (const VarOrValue& count : counts) {
1783 if (count.var == kNoVar) {
1784 fixed_contributions += count.value == value ? 1 : 0;
1785 }
1786 }
1787
1788 if (target.var == kNoVar) {
1789 FillDomainInProto(target.value - fixed_contributions, arg);
1790 } else {
1791 FillDomainInProto(-fixed_contributions, arg);
1792 AddTermToLinearConstraint(target.var, -1, arg);
1793 }
1794
1795 for (const VarOrValue& count : counts) {
1796 if (count.var != kNoVar) {
1797 AddTermToLinearConstraint(GetOrCreateEncodingLiteral(count.var, value), 1,
1798 arg);
1799 }
1800 }
1801}
1802
1803void CpModelProtoWithMapping::OrToolsCountEq(const fz::Constraint& fz_ct,
1804 ConstraintProto* ct) {
1805 const std::vector<VarOrValue> counts = LookupVarsOrValues(fz_ct.arguments[0]);
1806 const int var = LookupVar(fz_ct.arguments[1]);
1807 const VarOrValue target = LookupVarOrValue(fz_ct.arguments[2]);
1808 LinearConstraintProto* arg = ct->mutable_linear();
1809 if (target.var == kNoVar) {
1810 FillDomainInProto(target.value, arg);
1811 } else {
1812 FillDomainInProto(0, arg);
1813 AddTermToLinearConstraint(target.var, -1, arg);
1814 }
1815 for (const VarOrValue& count : counts) {
1816 const int bool_var = count.var == kNoVar
1817 ? GetOrCreateEncodingLiteral(var, count.value)
1818 : GetOrCreateLiteralForVarEqVar(var, count.var);
1819 AddTermToLinearConstraint(bool_var, 1, arg);
1820 }
1821}
1822
1823void CpModelProtoWithMapping::OrToolsCircuit(const fz::Constraint& fz_ct,
1824 ConstraintProto* ct) {
1825 const int64_t min_index = fz_ct.arguments[1].Value();
1826 const int size = std::max(fz_ct.arguments[0].values.size(),
1827 fz_ct.arguments[0].variables.size());
1828
1829 const int64_t max_index = min_index + size - 1;
1830 // The arc-based mutable circuit.
1831 auto* circuit_arg = ct->mutable_circuit();
1832
1833 // We fully encode all variables so we can use the literal based circuit.
1834 // TODO(user): avoid fully encoding more than once?
1835 int64_t index = min_index;
1836 const bool is_circuit = (fz_ct.type == "ortools_circuit");
1837 for (const int var : LookupVars(fz_ct.arguments[0])) {
1838 Domain domain = ReadDomainFromProto(proto.variables(var));
1839
1840 // Restrict the domain of var to [min_index, max_index]
1841 domain = domain.IntersectionWith({min_index, max_index});
1842 if (is_circuit) {
1843 // We simply make sure that the variable cannot take the value index.
1844 domain = domain.IntersectionWith(Domain::FromIntervals(
1845 {{std::numeric_limits<int64_t>::min(), index - 1},
1846 {index + 1, std::numeric_limits<int64_t>::max()}}));
1847 }
1848 FillDomainInProto(domain, proto.mutable_variables(var));
1849
1850 for (const ClosedInterval interval : domain) {
1851 for (int64_t value = interval.start; value <= interval.end; ++value) {
1852 // Create one Boolean variable for this arc.
1853 const int literal = proto.variables_size();
1854 {
1855 auto* new_var = proto.add_variables();
1856 FillDomainInProto(0, 1, new_var);
1857 }
1858
1859 // Add the arc.
1860 circuit_arg->add_tails(index);
1861 circuit_arg->add_heads(value);
1862 circuit_arg->add_literals(literal);
1863
1864 AddLinearConstraint({literal}, Domain(value), {{var, 1}});
1865 AddLinearConstraint({NegatedRef(literal)}, Domain(value).Complement(),
1866 {{var, 1}});
1867 }
1868 }
1869
1870 ++index;
1871 }
1872}
1873
1874void CpModelProtoWithMapping::OrToolsInverse(const fz::Constraint& fz_ct,
1875 ConstraintProto* ct) {
1876 auto* arg = ct->mutable_inverse();
1877
1878 const auto direct_variables = LookupVars(fz_ct.arguments[0]);
1879 const auto inverse_variables = LookupVars(fz_ct.arguments[1]);
1880 const int base_direct = fz_ct.arguments[2].Value();
1881 const int base_inverse = fz_ct.arguments[3].Value();
1882
1883 CHECK_EQ(direct_variables.size(), inverse_variables.size());
1884 const int num_variables = direct_variables.size();
1885 const int end_direct = base_direct + num_variables;
1886 const int end_inverse = base_inverse + num_variables;
1887
1888 // Any convention that maps the "fixed values" to the one of the inverse and
1889 // back works. We decided to follow this one:
1890 // There are 3 cases:
1891 // (A) base_direct == base_inverse, we fill the arrays
1892 // direct = [0, .., base_direct - 1] U [direct_vars]
1893 // inverse = [0, .., base_direct - 1] U [inverse_vars]
1894 // (B) base_direct == base_inverse + offset (> 0), we fill the arrays
1895 // direct = [0, .., base_inverse - 1] U
1896 // [end_inverse, .., end_inverse + offset - 1] U
1897 // [direct_vars]
1898 // inverse = [0, .., base_inverse - 1] U
1899 // [inverse_vars] U
1900 // [base_inverse, .., base_base_inverse + offset - 1]
1901 // (C): base_inverse == base_direct + offset (> 0), we fill the arrays
1902 // direct = [0, .., base_direct - 1] U
1903 // [direct_vars] U
1904 // [base_direct, .., base_direct + offset - 1]
1905 // inverse [0, .., base_direct - 1] U
1906 // [end_direct, .., end_direct + offset - 1] U
1907 // [inverse_vars]
1908 const int arity = std::max(base_inverse, base_direct) + num_variables;
1909 for (int i = 0; i < arity; ++i) {
1910 // Fill the direct array.
1911 if (i < base_direct) {
1912 if (i < base_inverse) {
1913 arg->add_f_direct(LookupConstant(i));
1914 } else if (i >= base_inverse) {
1915 arg->add_f_direct(LookupConstant(i + num_variables));
1916 }
1917 } else if (i >= base_direct && i < end_direct) {
1918 arg->add_f_direct(direct_variables[i - base_direct]);
1919 } else {
1920 arg->add_f_direct(LookupConstant(i - num_variables));
1921 }
1922
1923 // Fill the inverse array.
1924 if (i < base_inverse) {
1925 if (i < base_direct) {
1926 arg->add_f_inverse(LookupConstant(i));
1927 } else if (i >= base_direct) {
1928 arg->add_f_inverse(LookupConstant(i + num_variables));
1929 }
1930 } else if (i >= base_inverse && i < end_inverse) {
1931 arg->add_f_inverse(inverse_variables[i - base_inverse]);
1932 } else {
1933 arg->add_f_inverse(LookupConstant(i - num_variables));
1934 }
1935 }
1936}
1937
1938void CpModelProtoWithMapping::OrToolsLexOrdering(const fz::Constraint& fz_ct,
1939 ConstraintProto* ct) {
1940 const std::vector<int> x = LookupVars(fz_ct.arguments[0]);
1941 const std::vector<int> y = LookupVars(fz_ct.arguments[1]);
1942 const bool accepts_equals = fz_ct.type == "ortools_lex_lesseq_bool" ||
1943 fz_ct.type == "ortools_lex_lesseq_int";
1944 const int false_literal = LookupConstant(0);
1945 AddLexOrdering(x, y, NegatedRef(false_literal), accepts_equals);
1946}
1947
1948void CpModelProtoWithMapping::OrToolsPrecedeChainInt(
1949 const fz::Constraint& fz_ct, ConstraintProto* ct) {
1950 std::vector<int64_t> values;
1951 if (fz_ct.arguments[0].type == fz::Argument::INT_INTERVAL) {
1952 values.reserve(fz_ct.arguments[0].values[1] - fz_ct.arguments[0].values[0] +
1953 1);
1954 for (int64_t value = fz_ct.arguments[0].values[0];
1955 value <= fz_ct.arguments[0].values[1]; ++value) {
1956 values.push_back(value);
1957 }
1958 } else if (fz_ct.arguments[0].type == fz::Argument::INT_LIST) {
1959 values = fz_ct.arguments[0].values;
1960 } else {
1961 LOG(FATAL) << "Unsupported argument type: " << fz_ct.arguments[0].type
1962 << " for " << fz_ct.arguments[0].DebugString();
1963 }
1964 const std::vector<int> x = LookupVars(fz_ct.arguments[1]);
1965
1966 if (x.empty()) return;
1967 if (values.size() <= 1) return;
1968
1969 std::vector<int> row;
1970 for (int r = 0; r + 1 < values.size(); ++r) {
1971 row.clear();
1972 const int64_t before = values[r];
1973 const int64_t after = values[r + 1];
1974 row.resize(x.size());
1975 for (int i = 0; i < x.size(); ++i) {
1976 row[i] = (i <= r ? LookupConstant(0) : NewBoolVar());
1977 }
1978
1979 for (int i = 0; i < x.size(); ++i) {
1980 const int is_before = GetOrCreateEncodingLiteral(x[i], before);
1981 const int is_after = GetOrCreateEncodingLiteral(x[i], after);
1982 if (i + 1 < x.size()) {
1983 AddImplication({is_before}, row[i + 1]);
1984 AddLinearConstraint({NegatedRef(is_before)}, Domain(0),
1985 {{row[i], 1}, {row[i + 1], -1}});
1986 }
1987 AddImplication({NegatedRef(row[i])}, NegatedRef(is_after));
1988 }
1989 }
1990}
1991
1992void CpModelProtoWithMapping::FznDisjunctive(const fz::Constraint& fz_ct,
1993 ConstraintProto* ct) {
1994 const std::vector<VarOrValue> starts = LookupVarsOrValues(fz_ct.arguments[0]);
1995 const std::vector<VarOrValue> sizes = LookupVarsOrValues(fz_ct.arguments[1]);
1996
1997 auto* arg = ct->mutable_cumulative();
1998 arg->mutable_capacity()->set_offset(1);
1999 for (int i = 0; i < starts.size(); ++i) {
2000 arg->add_intervals(
2001 GetOrCreateOptionalInterval(starts[i], sizes[i], kNoVar));
2002 arg->add_demands()->set_offset(1);
2003 }
2004}
2005
2006void CpModelProtoWithMapping::FznDisjunctiveStrict(const fz::Constraint& fz_ct,
2007 ConstraintProto* ct) {
2008 const std::vector<VarOrValue> starts = LookupVarsOrValues(fz_ct.arguments[0]);
2009 const std::vector<VarOrValue> sizes = LookupVarsOrValues(fz_ct.arguments[1]);
2010
2011 auto* arg = ct->mutable_no_overlap();
2012 for (int i = 0; i < starts.size(); ++i) {
2013 arg->add_intervals(
2014 GetOrCreateOptionalInterval(starts[i], sizes[i], kNoVar));
2015 }
2016}
2017
2018void CpModelProtoWithMapping::FznCumulative(const fz::Constraint& fz_ct,
2019 ConstraintProto* ct) {
2020 const std::vector<VarOrValue> starts = LookupVarsOrValues(fz_ct.arguments[0]);
2021 const std::vector<VarOrValue> sizes = LookupVarsOrValues(fz_ct.arguments[1]);
2022 const std::vector<VarOrValue> demands =
2023 LookupVarsOrValues(fz_ct.arguments[2]);
2024
2025 auto* arg = ct->mutable_cumulative();
2026 if (fz_ct.arguments[3].HasOneValue()) {
2027 arg->mutable_capacity()->set_offset(fz_ct.arguments[3].Value());
2028 } else {
2029 arg->mutable_capacity()->add_vars(LookupVar(fz_ct.arguments[3]));
2030 arg->mutable_capacity()->add_coeffs(1);
2031 }
2032 for (int i = 0; i < starts.size(); ++i) {
2033 // Special case for a 0-1 demand, we mark the interval as optional
2034 // instead and fix the demand to 1.
2035 if (demands[i].var != kNoVar &&
2036 proto.variables(demands[i].var).domain().size() == 2 &&
2037 proto.variables(demands[i].var).domain(0) == 0 &&
2038 proto.variables(demands[i].var).domain(1) == 1 &&
2039 fz_ct.arguments[3].HasOneValue() && fz_ct.arguments[3].Value() == 1) {
2040 arg->add_intervals(
2041 GetOrCreateOptionalInterval(starts[i], sizes[i], demands[i].var));
2042 arg->add_demands()->set_offset(1);
2043 } else {
2044 arg->add_intervals(
2045 GetOrCreateOptionalInterval(starts[i], sizes[i], kNoVar));
2046 LinearExpressionProto* demand = arg->add_demands();
2047 if (demands[i].var == kNoVar) {
2048 demand->set_offset(demands[i].value);
2049 } else {
2050 demand->add_vars(demands[i].var);
2051 demand->add_coeffs(1);
2052 }
2053 }
2054 }
2055}
2056
2057void CpModelProtoWithMapping::OrToolsCumulativeOpt(const fz::Constraint& fz_ct,
2058 ConstraintProto* ct) {
2059 const std::vector<int> occurs = LookupVars(fz_ct.arguments[0]);
2060 const std::vector<VarOrValue> starts = LookupVarsOrValues(fz_ct.arguments[1]);
2061 const std::vector<VarOrValue> durations =
2062 LookupVarsOrValues(fz_ct.arguments[2]);
2063 const std::vector<VarOrValue> demands =
2064 LookupVarsOrValues(fz_ct.arguments[3]);
2065
2066 auto* arg = ct->mutable_cumulative();
2067 if (fz_ct.arguments[3].HasOneValue()) {
2068 arg->mutable_capacity()->set_offset(fz_ct.arguments[4].Value());
2069 } else {
2070 arg->mutable_capacity()->add_vars(LookupVar(fz_ct.arguments[4]));
2071 arg->mutable_capacity()->add_coeffs(1);
2072 }
2073 for (int i = 0; i < starts.size(); ++i) {
2074 arg->add_intervals(
2075 GetOrCreateOptionalInterval(starts[i], durations[i], occurs[i]));
2076 LinearExpressionProto* demand = arg->add_demands();
2077 if (demands[i].var == kNoVar) {
2078 demand->set_offset(demands[i].value);
2079 } else {
2080 demand->add_vars(demands[i].var);
2081 demand->add_coeffs(1);
2082 }
2083 }
2084}
2085
2086void CpModelProtoWithMapping::OrToolsDisjunctiveStrictOpt(
2087 const fz::Constraint& fz_ct, ConstraintProto* ct) {
2088 const std::vector<int> occurs = LookupVars(fz_ct.arguments[0]);
2089 const std::vector<VarOrValue> starts = LookupVarsOrValues(fz_ct.arguments[1]);
2090 const std::vector<VarOrValue> durations =
2091 LookupVarsOrValues(fz_ct.arguments[2]);
2092
2093 auto* arg = ct->mutable_no_overlap();
2094 for (int i = 0; i < starts.size(); ++i) {
2095 arg->add_intervals(
2096 GetOrCreateOptionalInterval(starts[i], durations[i], occurs[i]));
2097 }
2098}
2099
2100void CpModelProtoWithMapping::FznDiffn(const fz::Constraint& fz_ct,
2101 ConstraintProto* ct) {
2102 const bool is_strict = fz_ct.type == "fzn_diffn";
2103 const std::vector<VarOrValue> x = LookupVarsOrValues(fz_ct.arguments[0]);
2104 const std::vector<VarOrValue> y = LookupVarsOrValues(fz_ct.arguments[1]);
2105 const std::vector<VarOrValue> dx = LookupVarsOrValues(fz_ct.arguments[2]);
2106 const std::vector<VarOrValue> dy = LookupVarsOrValues(fz_ct.arguments[3]);
2107 const std::vector<int> x_intervals =
2108 is_strict ? CreateIntervals(x, dx)
2109 : CreateNonZeroOrOptionalIntervals(x, dx);
2110 const std::vector<int> y_intervals =
2111 is_strict ? CreateIntervals(y, dy)
2112 : CreateNonZeroOrOptionalIntervals(y, dy);
2113 auto* arg = ct->mutable_no_overlap_2d();
2114 for (int i = 0; i < x.size(); ++i) {
2115 arg->add_x_intervals(x_intervals[i]);
2116 arg->add_y_intervals(y_intervals[i]);
2117 }
2118}
2119
2120void CpModelProtoWithMapping::OrToolsNetworkFlow(const fz::Constraint& fz_ct,
2121 ConstraintProto* ct) {
2122 // Note that we leave ct empty here (with just the name set).
2123 // We simply do a linear encoding of this constraint.
2124 const bool has_cost = fz_ct.type == "ortools_network_flow_cost";
2125 const std::vector<int> flow = LookupVars(fz_ct.arguments[3]);
2126
2127 // Flow conservation constraints.
2128 const int num_nodes = fz_ct.arguments[1].values.size();
2129 const int base_node = fz_ct.arguments[2].Value();
2130 std::vector<std::vector<int>> flows_per_node(num_nodes);
2131 std::vector<std::vector<int>> coeffs_per_node(num_nodes);
2132
2133 const int num_arcs = fz_ct.arguments[0].values.size() / 2;
2134 for (int arc = 0; arc < num_arcs; arc++) {
2135 const int tail = fz_ct.arguments[0].values[2 * arc] - base_node;
2136 const int head = fz_ct.arguments[0].values[2 * arc + 1] - base_node;
2137 if (tail == head) continue;
2138
2139 flows_per_node[tail].push_back(flow[arc]);
2140 coeffs_per_node[tail].push_back(1);
2141 flows_per_node[head].push_back(flow[arc]);
2142 coeffs_per_node[head].push_back(-1);
2143 }
2144 for (int node = 0; node < num_nodes; node++) {
2145 auto* arg =
2146 AddLinearConstraint({}, Domain(fz_ct.arguments[1].values[node]));
2147 for (int i = 0; i < flows_per_node[node].size(); ++i) {
2148 AddTermToLinearConstraint(flows_per_node[node][i],
2149 coeffs_per_node[node][i], arg);
2150 }
2151 }
2152
2153 if (has_cost) {
2154 auto* arg = AddLinearConstraint({}, Domain(0));
2155 for (int arc = 0; arc < num_arcs; arc++) {
2156 AddTermToLinearConstraint(flow[arc], fz_ct.arguments[4].values[arc], arg);
2157 }
2158 AddTermToLinearConstraint(LookupVar(fz_ct.arguments[5]), -1, arg);
2159 }
2160}
2161
2162void CpModelProtoWithMapping::OrToolsBinPacking(const fz::Constraint& fz_ct,
2163 ConstraintProto* ct) {
2164 const int capacity = fz_ct.arguments[0].Value();
2165 const std::vector<int> positions = LookupVars(fz_ct.arguments[1]);
2166 CHECK_EQ(fz_ct.arguments[2].type, fz::Argument::INT_LIST);
2167 const std::vector<int64_t> weights = fz_ct.arguments[2].values;
2168
2169 std::vector<absl::btree_map<int64_t, int>> bin_encodings;
2170 absl::btree_set<int64_t> all_bin_indices;
2171 bin_encodings.reserve(positions.size());
2172 for (int p = 0; p < positions.size(); ++p) {
2173 absl::btree_map<int64_t, int> encoding = GetFullEncoding(positions[p]);
2174 for (const auto& [value, literal] : encoding) {
2175 all_bin_indices.insert(value);
2176 }
2177 bin_encodings.push_back(std::move(encoding));
2178 }
2179
2180 for (const int b : all_bin_indices) {
2181 LinearConstraintProto* lin = AddLinearConstraint({}, {0, capacity});
2182 for (int p = 0; p < positions.size(); ++p) {
2183 const auto it = bin_encodings[p].find(b);
2184 if (it != bin_encodings[p].end()) {
2185 AddTermToLinearConstraint(it->second, weights[p], lin);
2186 }
2187 }
2188 }
2189}
2190
2191void CpModelProtoWithMapping::OrToolsBinPackingCapa(const fz::Constraint& fz_ct,
2192 ConstraintProto* ct) {
2193 const std::vector<int64_t>& capacities = fz_ct.arguments[0].values;
2194 const std::vector<int> bins = LookupVars(fz_ct.arguments[1]);
2195 CHECK_EQ(fz_ct.arguments[2].type, fz::Argument::INT_INTERVAL);
2196 const int first_bin = fz_ct.arguments[2].values[0];
2197 const int last_bin = fz_ct.arguments[2].values[1];
2198 CHECK_EQ(fz_ct.arguments[3].type, fz::Argument::INT_LIST);
2199 const std::vector<int64_t> weights = fz_ct.arguments[3].values;
2200
2201 std::vector<absl::btree_map<int64_t, int>> bin_encodings;
2202 bin_encodings.reserve(bins.size());
2203 for (int bin = 0; bin < bins.size(); ++bin) {
2204 absl::btree_map<int64_t, int> encoding = GetFullEncoding(bins[bin]);
2205 for (const auto& [value, literal] : encoding) {
2206 if (value < first_bin || value > last_bin) {
2207 AddImplication({}, NegatedRef(literal)); // not a valid index.
2208 }
2209 }
2210 bin_encodings.push_back(std::move(encoding));
2211 }
2212
2213 for (int b = first_bin; b <= last_bin; ++b) {
2214 LinearConstraintProto* lin =
2215 AddLinearConstraint({}, {0, capacities[b - first_bin]});
2216 for (int bin = 0; bin < bins.size(); ++bin) {
2217 const auto it = bin_encodings[bin].find(b);
2218 if (it != bin_encodings[bin].end()) {
2219 AddTermToLinearConstraint(it->second, weights[bin], lin);
2220 }
2221 }
2222 }
2223}
2224
2225void CpModelProtoWithMapping::OrToolsBinPackingLoad(const fz::Constraint& fz_ct,
2226 ConstraintProto* ct) {
2227 const std::vector<int>& loads = LookupVars(fz_ct.arguments[0]);
2228 const std::vector<int> positions = LookupVars(fz_ct.arguments[1]);
2229 if (fz_ct.arguments[3].values.empty()) { // No items.
2230 for (const int load : loads) {
2231 AddLinearConstraint({}, {0, 0}, {{load, 1}});
2232 }
2233 return;
2234 }
2235 CHECK_EQ(fz_ct.arguments[2].type, fz::Argument::INT_INTERVAL);
2236 const int first_bin = fz_ct.arguments[2].values[0];
2237 const int last_bin = fz_ct.arguments[2].values[1];
2238 CHECK_EQ(fz_ct.arguments[3].type, fz::Argument::INT_LIST);
2239 const std::vector<int64_t> weights = fz_ct.arguments[3].values;
2240 CHECK_EQ(weights.size(), positions.size());
2241 CHECK_EQ(loads.size(), last_bin - first_bin + 1);
2242
2243 std::vector<absl::btree_map<int64_t, int>> bin_encodings;
2244 bin_encodings.reserve(positions.size());
2245 for (int p = 0; p < positions.size(); ++p) {
2246 absl::btree_map<int64_t, int> encoding = GetFullEncoding(positions[p]);
2247 for (const auto& [value, literal] : encoding) {
2248 if (value < first_bin || value > last_bin) {
2249 AddImplication({}, NegatedRef(literal)); // not a valid index.
2250 }
2251 }
2252 bin_encodings.push_back(std::move(encoding));
2253 }
2254
2255 for (int b = first_bin; b <= last_bin; ++b) {
2256 LinearConstraintProto* lin = AddLinearConstraint({}, Domain(0));
2257 AddTermToLinearConstraint(loads[b - first_bin], -1, lin);
2258 for (int p = 0; p < positions.size(); ++p) {
2259 const auto it = bin_encodings[p].find(b);
2260 if (it != bin_encodings[p].end()) {
2261 AddTermToLinearConstraint(it->second, weights[p], lin);
2262 }
2263 }
2264 }
2265
2266 // Redundant constraint.
2267 int64_t total_load = 0;
2268 for (int64_t weight : weights) {
2269 total_load += weight;
2270 }
2271 LinearConstraintProto* lin = AddLinearConstraint({}, Domain(total_load));
2272 for (int i = 0; i < loads.size(); ++i) {
2273 AddTermToLinearConstraint(loads[i], 1, lin);
2274 }
2275}
2276
2277void CpModelProtoWithMapping::OrToolsNvalue(const fz::Constraint& fz_ct,
2278 ConstraintProto* ct) {
2279 const int card = LookupVar(fz_ct.arguments[0]);
2280 const std::vector<int>& x = LookupVars(fz_ct.arguments[1]);
2281
2282 LinearConstraintProto* global_cardinality = ct->mutable_linear();
2283 FillDomainInProto(x.size(), global_cardinality);
2284
2285 absl::btree_map<int64_t, std::vector<int>> value_to_literals;
2286 for (int i = 0; i < x.size(); ++i) {
2287 const absl::btree_map<int64_t, int> encoding = GetFullEncoding(x[i]);
2288 for (const auto& [value, literal] : encoding) {
2289 value_to_literals[value].push_back(literal);
2290 AddTermToLinearConstraint(literal, 1, global_cardinality);
2291 }
2292 }
2293
2294 // Constrain the range of the card variable.
2295 const int64_t max_size = std::min(x.size(), value_to_literals.size());
2296 AddLinearConstraint({}, {1, max_size}, {{card, 1}});
2297
2298 LinearConstraintProto* lin = AddLinearConstraint({}, Domain(0), {{card, -1}});
2299 for (const auto& [value, literals] : value_to_literals) {
2300 const int is_present = NewBoolVar();
2301 AddTermToLinearConstraint(is_present, 1, lin);
2302
2303 BoolArgumentProto* is_present_constraint =
2304 AddEnforcedConstraint(is_present)->mutable_bool_or();
2305 for (const int literal : literals) {
2306 AddImplication({literal}, is_present);
2307 is_present_constraint->add_literals(literal);
2308 }
2309 }
2310}
2311
2312void CpModelProtoWithMapping::OrToolsGlobalCardinality(
2313 const fz::Constraint& fz_ct, ConstraintProto* ct) {
2314 const std::vector<int> x = LookupVars(fz_ct.arguments[0]);
2315 CHECK_EQ(fz_ct.arguments[1].type, fz::Argument::INT_LIST);
2316 const std::vector<int64_t>& values = fz_ct.arguments[1].values;
2317 const std::vector<int> cards = LookupVars(fz_ct.arguments[2]);
2318 const bool is_closed = fz_ct.arguments[3].Value() != 0;
2319
2320 const absl::flat_hash_set<int64_t> all_values(values.begin(), values.end());
2321 absl::flat_hash_map<int64_t, std::vector<int>> value_to_literals;
2322 bool exact_cover = true;
2323
2324 for (const int x_var : x) {
2325 const absl::btree_map<int64_t, int> encoding = GetFullEncoding(x_var);
2326 for (const auto& [value, literal] : encoding) {
2327 if (all_values.contains(value)) {
2328 value_to_literals[value].push_back(literal);
2329 } else if (is_closed) {
2330 AddImplication({}, NegatedRef(literal));
2331 } else {
2332 exact_cover = false;
2333 }
2334 }
2335 }
2336
2337 for (int i = 0; i < cards.size(); ++i) {
2338 const int64_t value = values[i];
2339 const int card = cards[i];
2340 LinearConstraintProto* lin =
2341 AddLinearConstraint({}, Domain(0), {{card, -1}});
2342 for (const int literal : value_to_literals[value]) {
2343 AddTermToLinearConstraint(literal, 1, lin);
2344 }
2345 }
2346
2347 if (exact_cover) {
2348 LinearConstraintProto* cover = AddLinearConstraint({}, Domain(x.size()));
2349 for (const int card : cards) {
2350 AddTermToLinearConstraint(card, 1, cover);
2351 }
2352 }
2353}
2354
2355void CpModelProtoWithMapping::OrToolsGlobalCardinalityLowUp(
2356 const fz::Constraint& fz_ct, ConstraintProto* ct) {
2357 const std::vector<int> x = LookupVars(fz_ct.arguments[0]);
2358 CHECK(fz_ct.arguments[1].type == fz::Argument::INT_LIST ||
2359 fz_ct.arguments[1].type == fz::Argument::VOID_ARGUMENT);
2360 const std::vector<int64_t>& values = fz_ct.arguments[1].values;
2361 absl::Span<const int64_t> lbs = fz_ct.arguments[2].values;
2362 absl::Span<const int64_t> ubs = fz_ct.arguments[3].values;
2363 const bool is_closed = fz_ct.arguments[4].Value() != 0;
2364
2365 const absl::flat_hash_set<int64_t> all_values(values.begin(), values.end());
2366 absl::flat_hash_map<int64_t, std::vector<int>> value_to_literals;
2367 bool exact_cover = true;
2368
2369 for (const int x_var : x) {
2370 const absl::btree_map<int64_t, int> encoding = GetFullEncoding(x_var);
2371 for (const auto& [value, literal] : encoding) {
2372 if (all_values.contains(value)) {
2373 value_to_literals[value].push_back(literal);
2374 } else if (is_closed) {
2375 AddImplication({}, NegatedRef(literal));
2376 } else {
2377 exact_cover = false;
2378 }
2379 }
2380 }
2381
2382 // Optimization: if sum(lbs) == length(x), then we can reduce ubs to lbs,
2383 // and vice versa. the constraint is redundant.
2384 const int64_t sum_lbs = absl::c_accumulate(lbs, 0);
2385 const int64_t sum_ubs = absl::c_accumulate(ubs, 0);
2386 if (exact_cover && sum_lbs == x.size()) {
2387 ubs = lbs;
2388 } else if (exact_cover && sum_ubs == x.size()) {
2389 lbs = ubs;
2390 }
2391
2392 for (int i = 0; i < values.size(); ++i) {
2393 LinearConstraintProto* lin = AddLinearConstraint({}, {lbs[i], ubs[i]});
2394 for (const int literal : value_to_literals[values[i]]) {
2395 AddTermToLinearConstraint(literal, 1, lin);
2396 }
2397 }
2398}
2399
2400using ConstraintToMethodMapType =
2401 absl::flat_hash_map<std::string_view,
2402 void (CpModelProtoWithMapping::*)(const fz::Constraint&,
2403 ConstraintProto*)>;
2404
2405const ConstraintToMethodMapType& GetConstraintMap() {
2406 using MPMap = CpModelProtoWithMapping;
2407 static const absl::NoDestructor<ConstraintToMethodMapType> kConstraintMap({
2408 {"false_constraint", &MPMap::AlwaysFalseConstraint},
2409 {"bool_clause", &MPMap::BoolClauseConstraint},
2410 {"bool_xor", &MPMap::BoolXorConstraint},
2411 {"array_bool_xor", &MPMap::ArrayBoolXorConstraint},
2412 {"bool_le", &MPMap::BoolOrIntLeConstraint},
2413 {"int_le", &MPMap::BoolOrIntLeConstraint},
2414 {"bool_ge", &MPMap::BoolOrIntGeConstraint},
2415 {"int_ge", &MPMap::BoolOrIntGeConstraint},
2416 {"bool_lt", &MPMap::BoolOrIntLtConstraint},
2417 {"int_lt", &MPMap::BoolOrIntLtConstraint},
2418 {"bool_gt", &MPMap::BoolOrIntGtConstraint},
2419 {"int_gt", &MPMap::BoolOrIntGtConstraint},
2420 {"bool_eq", &MPMap::BoolEqConstraint},
2421 {"int_eq", &MPMap::BoolEqConstraint},
2422 {"bool2int", &MPMap::BoolEqConstraint},
2423 {"bool_ne", &MPMap::BoolNeConstraint},
2424 {"bool_not", &MPMap::BoolNeConstraint},
2425 {"int_ne", &MPMap::IntNeConstraint},
2426 {"int_lin_eq", &MPMap::IntLinEqConstraint},
2427 {"bool_lin_eq", &MPMap::BoolLinEqConstraint},
2428 {"int_lin_le", &MPMap::BoolOrIntLinLeConstraint},
2429 {"bool_lin_le", &MPMap::BoolOrIntLinLeConstraint},
2430 {"int_lin_lt", &MPMap::IntLinLtConstraint},
2431 {"int_lin_ge", &MPMap::IntLinGeConstraint},
2432 {"int_lin_gt", &MPMap::IntLinGtConstraint},
2433 {"int_lin_ne", &MPMap::IntLinNeConstraint},
2434 {"set_card", &MPMap::SetCardConstraint},
2435 {"set_in", &MPMap::SetInConstraint},
2436 {"set_in_negated", &MPMap::SetInNegatedConstraint},
2437 {"int_min", &MPMap::IntMinConstraint},
2438 {"array_int_minimum", &MPMap::ArrayIntMinConstraint},
2439 {"minimum_int", &MPMap::ArrayIntMinConstraint},
2440 {"int_max", &MPMap::IntMaxConstraint},
2441 {"array_int_maximum", &MPMap::ArrayIntMaxConstraint},
2442 {"maximum_int", &MPMap::ArrayIntMaxConstraint},
2443 {"int_times", &MPMap::IntTimesConstraint},
2444 {"int_abs", &MPMap::IntAbsConstraint},
2445 {"int_plus", &MPMap::IntPlusConstraint},
2446 {"int_div", &MPMap::IntDivConstraint},
2447 {"int_mod", &MPMap::IntModConstraint},
2448 {"array_int_element", &MPMap::ArrayElementConstraint},
2449 {"array_bool_element", &MPMap::ArrayElementConstraint},
2450 {"array_var_int_element", &MPMap::ArrayElementConstraint},
2451 {"array_var_bool_element", &MPMap::ArrayElementConstraint},
2452 {"array_int_element_nonshifted", &MPMap::ArrayElementConstraint},
2453 {"ortools_array_int_element", &MPMap::OrToolsConstantElementConstraint},
2454 {"ortools_array_bool_element", &MPMap::OrToolsConstantElementConstraint},
2455 {"ortools_array_var_int_element",
2456 &MPMap::OrToolsVariableElementConstraint},
2457 {"ortools_array_var_bool_element",
2458 &MPMap::OrToolsVariableElementConstraint},
2459 {"ortools_table_bool", &MPMap::OrToolsTableIntConstraint},
2460 {"ortools_table_int", &MPMap::OrToolsTableIntConstraint},
2461 {"ortools_regular", &MPMap::OrToolsRegular},
2462 {"ortools_arg_max_int", &MPMap::OrToolsArgMax},
2463 {"ortools_arg_max_bool", &MPMap::OrToolsArgMax},
2464 {"fzn_all_different_int", &MPMap::FznAllDifferentInt},
2465 {"fzn_value_precede_int", &MPMap::FznValuePrecedeInt},
2466 {"ortools_count_eq_cst", &MPMap::OrToolsCountEqCst},
2467 {"ortools_count_eq", &MPMap::OrToolsCountEq},
2468 {"ortools_circuit", &MPMap::OrToolsCircuit},
2469 {"ortools_subcircuit", &MPMap::OrToolsCircuit},
2470 {"ortools_inverse", &MPMap::OrToolsInverse},
2471 {"ortools_lex_less_int", &MPMap::OrToolsLexOrdering},
2472 {"ortools_lex_less_bool", &MPMap::OrToolsLexOrdering},
2473 {"ortools_lex_lesseq_int", &MPMap::OrToolsLexOrdering},
2474 {"ortools_lex_lesseq_bool", &MPMap::OrToolsLexOrdering},
2475 {"ortools_precede_chain_int", &MPMap::OrToolsPrecedeChainInt},
2476 {"fzn_disjunctive", &MPMap::FznDisjunctive},
2477 {"fzn_disjunctive_strict", &MPMap::FznDisjunctiveStrict},
2478 {"fzn_cumulative", &MPMap::FznCumulative},
2479 {"ortools_cumulative_opt", &MPMap::OrToolsCumulativeOpt},
2480 {"ortools_disjunctive_strict_opt", &MPMap::OrToolsDisjunctiveStrictOpt},
2481 {"fzn_diffn", &MPMap::FznDiffn},
2482 {"fzn_diffn_nonstrict", &MPMap::FznDiffn},
2483 {"ortools_network_flow", &MPMap::OrToolsNetworkFlow},
2484 {"ortools_network_flow_cost", &MPMap::OrToolsNetworkFlow},
2485 {"ortools_bin_packing", &MPMap::OrToolsBinPacking},
2486 {"ortools_bin_packing_capa", &MPMap::OrToolsBinPackingCapa},
2487 {"ortools_bin_packing_load", &MPMap::OrToolsBinPackingLoad},
2488 {"ortools_nvalue", &MPMap::OrToolsNvalue},
2489 {"ortools_global_cardinality", &MPMap::OrToolsGlobalCardinality},
2490 {"ortools_global_cardinality_low_up",
2491 &MPMap::OrToolsGlobalCardinalityLowUp},
2492 });
2493 return *kConstraintMap;
2494}
2495
2496void CpModelProtoWithMapping::FillConstraint(const fz::Constraint& fz_ct,
2497 ConstraintProto* ct) {
2498 auto it = GetConstraintMap().find(fz_ct.type);
2499 if (it != GetConstraintMap().end()) {
2500 auto method_ptr = it->second;
2501 (this->*method_ptr)(fz_ct, ct);
2502 } else {
2503 LOG(FATAL) << " Not supported " << fz_ct.type;
2504 }
2505}
2506
2507void CpModelProtoWithMapping::PutSetBooleansInCommonDomain(
2508 absl::Span<const std::shared_ptr<SetVariable>> set_vars,
2509 absl::Span<std::vector<int>* const> var_booleans) {
2510 CHECK_EQ(set_vars.size(), var_booleans.size());
2511 std::vector<int64_t> all_values;
2512 for (const std::shared_ptr<SetVariable>& set_var : set_vars) {
2513 all_values.insert(all_values.end(), set_var->sorted_values.begin(),
2514 set_var->sorted_values.end());
2515 }
2517 absl::flat_hash_map<int64_t, int> value_to_index;
2518 for (int i = 0; i < all_values.size(); ++i) {
2519 value_to_index[all_values[i]] = i;
2520 }
2521 const int false_literal = LookupConstant(0);
2522 for (int set_var_index = 0; set_var_index < var_booleans.size();
2523 ++set_var_index) {
2524 std::vector<int>* literals = var_booleans[set_var_index];
2525 std::shared_ptr<SetVariable> set_var = set_vars[set_var_index];
2526 *literals = std::vector<int>(all_values.size(), false_literal);
2527 for (int i = 0; i < set_var->sorted_values.size(); ++i) {
2528 (*literals)[value_to_index[set_var->sorted_values[i]]] =
2529 set_var->var_indices[i];
2530 }
2531 }
2532}
2533
2534void CpModelProtoWithMapping::FirstPass(const fz::Constraint& fz_ct) {
2535 if (fz_ct.type == "set_card") {
2536 if (!fz_ct.arguments[0].IsVariable() ||
2537 !fz_ct.arguments[0].Var()->domain.is_a_set ||
2538 !fz_ct.arguments[1].HasOneValue()) {
2539 return;
2540 }
2541 std::shared_ptr<SetVariable> set_var = LookupSetVar(fz_ct.arguments[0]);
2542 const int64_t fixed_card = fz_ct.arguments[1].Value();
2543 set_var->fixed_card = fixed_card;
2544 }
2545}
2546
2547void CpModelProtoWithMapping::ArraySetElementConstraint(
2548 const fz::Constraint& fz_ct) {
2549 const int index = LookupVar(fz_ct.arguments[0]);
2550 const Domain index_domain = ReadDomainFromProto(proto.variables(index));
2551 const std::shared_ptr<SetVariable> target_var =
2552 LookupSetVar(fz_ct.arguments[2]);
2553 const absl::flat_hash_set<int64_t> target_values(
2554 target_var->sorted_values.begin(), target_var->sorted_values.end());
2555 absl::flat_hash_map<int64_t, std::vector<int64_t>> value_to_supports;
2556 const int64_t min_index = index_domain.Min();
2557 for (const int64_t value : index_domain.Values()) {
2558 const int64_t offset = value - min_index;
2559 bool index_is_valid = true;
2560 const fz::Domain& set_domain = fz_ct.arguments[1].domains[offset];
2561 if (set_domain.is_interval) {
2562 for (int64_t v = set_domain.values[0]; v <= set_domain.values[1]; ++v) {
2563 if (!target_values.contains(v)) {
2564 index_is_valid = false;
2565 break;
2566 }
2567 }
2568 if (!index_is_valid) continue;
2569
2570 for (int64_t v = set_domain.values[0]; v <= set_domain.values[1]; ++v) {
2571 value_to_supports[v].push_back(value);
2572 }
2573 } else {
2574 for (const int64_t v : set_domain.values) {
2575 if (!target_values.contains(v)) {
2576 index_is_valid = false;
2577 break;
2578 }
2579 }
2580 if (!index_is_valid) continue;
2581
2582 for (const int64_t v : set_domain.values) {
2583 value_to_supports[v].push_back(value);
2584 }
2585 }
2586 }
2587
2588 for (int i = 0; i < target_var->sorted_values.size(); ++i) {
2589 const int64_t target_value = target_var->sorted_values[i];
2590 const int target_literal = target_var->var_indices[i];
2591 const auto it = value_to_supports.find(target_value);
2592 if (it == value_to_supports.end()) {
2594 NegatedRef(target_literal));
2595 } else if (it->second.size() == index_domain.Size()) {
2596 proto.add_constraints()->mutable_bool_and()->add_literals(target_literal);
2597 } else {
2598 const Domain true_indices = Domain::FromValues(it->second);
2599 AddLinearConstraint({target_literal}, true_indices, {{index, 1}});
2600 AddLinearConstraint({NegatedRef(target_literal)},
2601 true_indices.Complement(), {{index, 1}});
2602 }
2603 }
2604}
2605
2606void CpModelProtoWithMapping::ArrayVarSetElementConstraint(
2607 const fz::Constraint& fz_ct) {
2608 const int index = LookupVar(fz_ct.arguments[0]);
2609 const Domain index_domain = ReadDomainFromProto(proto.variables(index));
2610 const int64_t min_index = index_domain.Min();
2611
2612 const std::shared_ptr<SetVariable> target_var =
2613 LookupSetVar(fz_ct.arguments[2]);
2614 absl::btree_map<int64_t, int> target_values_to_literals;
2615 for (int i = 0; i < target_var->sorted_values.size(); ++i) {
2616 target_values_to_literals[target_var->sorted_values[i]] =
2617 target_var->var_indices[i];
2618 }
2619
2620 BoolArgumentProto* exactly_one =
2622 for (const int64_t value : index_domain.Values()) {
2623 const int index_literal = GetOrCreateEncodingLiteral(index, value);
2624 exactly_one->add_literals(index_literal);
2625
2626 std::shared_ptr<SetVariable> set_var =
2627 LookupSetVarAt(fz_ct.arguments[1], value - min_index);
2628 absl::btree_map<int64_t, int> set_values_to_literals;
2629 for (int i = 0; i < set_var->sorted_values.size(); ++i) {
2630 set_values_to_literals[set_var->sorted_values[i]] =
2631 set_var->var_indices[i];
2632 }
2633
2634 for (const auto& [value, set_literal] : set_values_to_literals) {
2635 const auto it = target_values_to_literals.find(value);
2636 if (it == target_values_to_literals.end()) {
2637 // index is selected => set_literal[value] is false.
2638 AddImplication({index_literal}, NegatedRef(set_literal));
2639 } else {
2640 // index is selected => set_literal[value] == target_literal[value].
2641 AddImplication({index_literal, set_literal}, it->second);
2642 AddImplication({index_literal, it->second}, set_literal);
2643 }
2644 }
2645
2646 // Properly handle the case where not all target literals are reached.
2647 for (const auto& [value, target_literal] : target_values_to_literals) {
2648 if (!set_values_to_literals.contains(value)) {
2649 AddImplication({index_literal}, NegatedRef(target_literal));
2650 }
2651 }
2652 }
2653}
2654
2655void CpModelProtoWithMapping::FznAllDifferentSetConstraint(
2656 const fz::Constraint& fz_ct) {
2657 const int num_vars = fz_ct.arguments[0].Size();
2658 if (num_vars == 0) return;
2659
2660 std::vector<std::shared_ptr<SetVariable>> set_vars;
2661 set_vars.reserve(num_vars);
2662 for (int i = 0; i < num_vars; ++i) {
2663 set_vars.push_back(LookupSetVarAt(fz_ct.arguments[0], i));
2664 }
2665 std::vector<std::vector<int>> var_booleans(num_vars);
2666 std::vector<std::vector<int>*> var_booleans_ptrs(num_vars);
2667 for (int i = 0; i < set_vars.size(); ++i) {
2668 var_booleans_ptrs[i] = &var_booleans[i];
2669 }
2670 PutSetBooleansInCommonDomain(set_vars, var_booleans_ptrs);
2671 for (int i = 0; i + 1 < num_vars; ++i) {
2672 for (int j = i + 1; j < num_vars; ++j) {
2673 AddLiteralVectorsNotEqual(var_booleans[i], var_booleans[j]);
2674 }
2675 }
2676}
2677
2678void CpModelProtoWithMapping::FznAllDisjointConstraint(
2679 const fz::Constraint& fz_ct) {
2680 const int num_vars = fz_ct.arguments[0].Size();
2681 if (num_vars == 0) return;
2682
2683 absl::btree_map<int64_t, std::vector<int>> value_to_candidates;
2684 for (int i = 0; i < num_vars; ++i) {
2685 const std::shared_ptr<SetVariable> set_var =
2686 LookupSetVarAt(fz_ct.arguments[0], i);
2687 for (int j = 0; j < set_var->sorted_values.size(); ++j) {
2688 const int64_t value = set_var->sorted_values[j];
2689 const int literal = set_var->var_indices[j];
2690 value_to_candidates[value].push_back(literal);
2691 }
2692 }
2693 for (const auto& [value, candidates] : value_to_candidates) {
2694 BoolArgumentProto* amo = proto.add_constraints()->mutable_at_most_one();
2695 for (const int literal : candidates) {
2696 amo->add_literals(literal);
2697 }
2698 }
2699}
2700
2701void CpModelProtoWithMapping::FznDisjointConstraint(
2702 const fz::Constraint& fz_ct) {
2703 std::vector<int> x_literals, y_literals;
2704 PutSetBooleansInCommonDomain(
2705 {LookupSetVar(fz_ct.arguments[0]), LookupSetVar(fz_ct.arguments[1])},
2706 {&x_literals, &y_literals});
2707 for (int i = 0; i < x_literals.size(); ++i) {
2708 BoolArgumentProto* at_least_one_false =
2710 at_least_one_false->add_literals(NegatedRef(x_literals[i]));
2711 at_least_one_false->add_literals(NegatedRef(y_literals[i]));
2712 }
2713}
2714
2715void CpModelProtoWithMapping::FznPartitionSetConstraint(
2716 const fz::Constraint& fz_ct) {
2717 const int num_vars = fz_ct.arguments[0].Size();
2718 if (num_vars == 0) return;
2719
2720 absl::flat_hash_set<int64_t> universe;
2721 if (fz_ct.arguments[1].type == fz::Argument::INT_INTERVAL) {
2722 for (int64_t v = fz_ct.arguments[1].values[0];
2723 v <= fz_ct.arguments[1].values[1]; ++v) {
2724 universe.insert(v);
2725 }
2726 } else {
2727 CHECK_EQ(fz_ct.arguments[1].type, fz::Argument::INT_LIST);
2728 universe = absl::flat_hash_set<int64_t>(fz_ct.arguments[1].values.begin(),
2729 fz_ct.arguments[1].values.end());
2730 }
2731
2732 absl::btree_map<int64_t, std::vector<int>> value_to_candidates;
2733 for (int i = 0; i < num_vars; ++i) {
2734 const std::shared_ptr<SetVariable> set_var =
2735 LookupSetVarAt(fz_ct.arguments[0], i);
2736 for (int j = 0; j < set_var->sorted_values.size(); ++j) {
2737 const int64_t value = set_var->sorted_values[j];
2738 const int literal = set_var->var_indices[j];
2739 if (!universe.contains(value)) {
2740 AddImplication({}, NegatedRef(literal));
2741 } else {
2742 value_to_candidates[value].push_back(literal);
2743 }
2744 }
2745 }
2746 for (const auto& [value, candidates] : value_to_candidates) {
2747 BoolArgumentProto* ex1 = proto.add_constraints()->mutable_exactly_one();
2748 for (const int literal : candidates) {
2749 ex1->add_literals(literal);
2750 }
2751 }
2752}
2753
2754void CpModelProtoWithMapping::SetCardConstraint(const fz::Constraint& fz_ct) {
2755 std::shared_ptr<SetVariable> set_var = LookupSetVar(fz_ct.arguments[0]);
2756 VarOrValue var_or_value = LookupVarOrValue(fz_ct.arguments[1]);
2757 if (set_var->card_var_index == kNoVar) {
2758 ConstraintProto* ct = proto.add_constraints();
2759 if (var_or_value.var == kNoVar) {
2760 set_var->card_var_index = LookupConstant(var_or_value.value);
2761 FillDomainInProto(var_or_value.value, ct->mutable_linear());
2762 } else {
2763 set_var->card_var_index = var_or_value.var;
2764 FillDomainInProto(0, ct->mutable_linear());
2765 AddTermToLinearConstraint(var_or_value.var, -1, ct->mutable_linear());
2766 }
2767 for (const int bool_var : set_var->var_indices) {
2768 AddTermToLinearConstraint(bool_var, 1, ct->mutable_linear());
2769 }
2770 } else {
2771 if (var_or_value.var == kNoVar) {
2772 AddLinearConstraint({}, Domain(var_or_value.value),
2773 {{set_var->card_var_index, 1}});
2774 } else {
2775 AddLinearConstraint(
2776 {}, Domain(0),
2777 {{set_var->card_var_index, 1}, {var_or_value.var, -1}});
2778 }
2779 }
2780}
2781
2782void CpModelProtoWithMapping::SetInConstraint(const fz::Constraint& fz_ct) {
2783 const VarOrValue var_or_value = LookupVarOrValue(fz_ct.arguments[0]);
2784 std::shared_ptr<SetVariable> set_var = LookupSetVar(fz_ct.arguments[1]);
2785 if (var_or_value.var == kNoVar) {
2786 const int index = set_var->find_value_index(var_or_value.value);
2787 // TODO(user): Improve behavior and reporting when failing.
2788 CHECK_NE(index, -1);
2789 AddImplication({}, set_var->var_indices[index]);
2790 } else if (set_var->fixed_card.has_value() &&
2791 set_var->fixed_card.value() == 1) {
2792 // We have a one to one mapping between the set_var and the full encoding
2793 // of the variable.
2794 //
2795 // Cache the mapping of set_var values to literals.
2796 absl::flat_hash_map<int64_t, int> set_value_to_literal;
2797 for (int i = 0; i < set_var->sorted_values.size(); ++i) {
2798 set_value_to_literal[set_var->sorted_values[i]] = set_var->var_indices[i];
2799 }
2800
2801 // Reduce the domain of the target variable.
2802 AddLinearConstraint({}, Domain::FromValues(set_var->sorted_values),
2803 {{var_or_value.var, 1}});
2804
2805 absl::flat_hash_set<int> var_values;
2806 const Domain var_domain =
2807 ReadDomainFromProto(proto.variables(var_or_value.var));
2808 for (const int64_t value : var_domain.Values()) {
2809 const auto it = set_value_to_literal.find(value);
2810 // Non-covered values are fixed to 0 by the above domain reduction.
2811 if (it == set_value_to_literal.end()) continue;
2812 AddVarEqValueLiteral(var_or_value.var, value, it->second);
2813 var_values.insert(value);
2814 }
2815
2816 // Zero out set literals not covered by the full encoding.
2817 for (int i = 0; i < set_var->sorted_values.size(); ++i) {
2818 if (!var_values.contains(set_var->sorted_values[i])) {
2819 AddImplication({}, NegatedRef(set_var->var_indices[i]));
2820 }
2821 }
2822 } else {
2823 // We express `v \in set({v_i}, {b_i})` by N+1 constraints:
2824 // v \in {v_i}
2825 // (v == v_i) => b_i
2826 //
2827 // Reduce the domain of the target variable.
2828 AddLinearConstraint({}, Domain::FromValues(set_var->sorted_values),
2829 {{var_or_value.var, 1}});
2830
2831 // Then propagate any remove from the set domain to the variable domain.
2832 for (int i = 0; i < set_var->sorted_values.size(); ++i) {
2833 AddLinearConstraint({NegatedRef(set_var->var_indices[i])},
2834 {Domain(set_var->sorted_values[i]).Complement()},
2835 {{var_or_value.var, 1}});
2836 }
2837 }
2838}
2839
2840void CpModelProtoWithMapping::SetInReifConstraint(const fz::Constraint& fz_ct) {
2841 VarOrValue var_or_value = LookupVarOrValue(fz_ct.arguments[0]);
2842 std::shared_ptr<const SetVariable> set_var = LookupSetVar(fz_ct.arguments[1]);
2843 const int enforcement_literal = LookupVar(fz_ct.arguments[2]);
2844 if (var_or_value.var == kNoVar) {
2845 const int index = set_var->find_value_index(var_or_value.value);
2846 if (index == -1) {
2847 AddImplication({}, NegatedRef(enforcement_literal));
2848 } else {
2849 AddLinearConstraint(
2850 {}, Domain(0),
2851 {{set_var->var_indices[index], 1}, {enforcement_literal, -1}});
2852 }
2853 } else {
2854 // Reduce the domain of the target variable.
2855 Domain set_domain = Domain::FromValues(set_var->sorted_values);
2856 AddLinearConstraint({enforcement_literal}, set_domain,
2857 {{var_or_value.var, 1}});
2858
2859 // Then propagate any remove from the set domain to the variable domain.
2860 // Note that the reif part is an equivalence. We do not want false
2861 // implies var in set (which contains the value of the var).
2862 for (int i = 0; i < set_var->sorted_values.size(); ++i) {
2863 const int64_t value = set_var->sorted_values[i];
2864 const int set_value_literal = set_var->var_indices[i];
2865 // Let's create the literal as we are going to reuse it.
2866 const int not_var_value_literal =
2867 NegatedRef(GetOrCreateEncodingLiteral(var_or_value.var, value));
2868
2869 AddImplication({enforcement_literal, NegatedRef(set_value_literal)},
2870 not_var_value_literal);
2871 AddImplication({NegatedRef(enforcement_literal), set_value_literal},
2872 not_var_value_literal);
2873 }
2874 }
2875}
2876
2877void CpModelProtoWithMapping::SetOpConstraint(const fz::Constraint& fz_ct) {
2878 std::vector<int> x_literals, y_literals, r_literals;
2879 PutSetBooleansInCommonDomain(
2880 {LookupSetVar(fz_ct.arguments[0]), LookupSetVar(fz_ct.arguments[1]),
2881 LookupSetVar(fz_ct.arguments[2])},
2882 {&x_literals, &y_literals, &r_literals});
2883 if (fz_ct.type == "set_intersect") {
2884 for (int i = 0; i < x_literals.size(); ++i) {
2885 AddVarAndVarLiteral(x_literals[i], y_literals[i], r_literals[i]);
2886 }
2887 } else if (fz_ct.type == "set_union") {
2888 for (int i = 0; i < x_literals.size(); ++i) {
2889 AddVarOrVarLiteral(x_literals[i], y_literals[i], r_literals[i]);
2890 }
2891 } else if (fz_ct.type == "set_symdiff") {
2892 for (int i = 0; i < x_literals.size(); ++i) {
2893 AddVarEqVarLiteral(x_literals[i], y_literals[i],
2894 NegatedRef(r_literals[i]));
2895 }
2896 } else if (fz_ct.type == "set_diff") {
2897 for (int i = 0; i < x_literals.size(); ++i) {
2898 AddVarLtVarLiteral(y_literals[i], x_literals[i], r_literals[i]);
2899 }
2900 }
2901}
2902
2903void CpModelProtoWithMapping::SetSubSupersetEqConstraint(
2904 const fz::Constraint& fz_ct) {
2905 std::vector<int> x_literals, y_literals;
2906 PutSetBooleansInCommonDomain(
2907 {LookupSetVar(fz_ct.arguments[0]), LookupSetVar(fz_ct.arguments[1])},
2908 {&x_literals, &y_literals});
2909
2910 // Implement the comparison logic.
2911 for (int i = 0; i < x_literals.size(); ++i) {
2912 const int x_lit = x_literals[i];
2913 const int y_lit = y_literals[i];
2914 if (fz_ct.type == "set_subset") {
2915 AddImplication({x_lit}, y_lit);
2916 } else if (fz_ct.type == "set_superset") {
2917 AddImplication({y_lit}, x_lit);
2918 } else if (fz_ct.type == "set_eq") {
2919 // We use the linear as it is easier for the presolve.
2920 AddLinearConstraint({}, Domain(0), {{x_lit, 1}, {y_lit, -1}});
2921 } else {
2922 LOG(FATAL) << "Unsupported " << fz_ct.type;
2923 }
2924 }
2925}
2926
2927void CpModelProtoWithMapping::SetNeConstraint(const fz::Constraint& fz_ct) {
2928 std::vector<int> x_literals, y_literals;
2929 PutSetBooleansInCommonDomain(
2930 {LookupSetVar(fz_ct.arguments[0]), LookupSetVar(fz_ct.arguments[1])},
2931 {&x_literals, &y_literals});
2932 AddLiteralVectorsNotEqual(x_literals, y_literals);
2933}
2934
2935void CpModelProtoWithMapping::SetSubSupersetEqReifConstraint(
2936 const fz::Constraint& fz_ct) {
2937 std::vector<int> x_literals, y_literals;
2938 PutSetBooleansInCommonDomain(
2939 {LookupSetVar(fz_ct.arguments[0]), LookupSetVar(fz_ct.arguments[1])},
2940 {&x_literals, &y_literals});
2941 const int enforcement_literal = LookupVar(fz_ct.arguments[2]);
2942 const int num_values = x_literals.size();
2943
2944 int e = enforcement_literal;
2945 if (fz_ct.type == "set_ne_reif") {
2946 e = NegatedRef(enforcement_literal);
2947 }
2948 BoolArgumentProto* all_true = AddEnforcedConstraint(e)->mutable_bool_and();
2949 BoolArgumentProto* one_false =
2950 AddEnforcedConstraint(NegatedRef(e))->mutable_bool_or();
2951 for (int i = 0; i < num_values; ++i) {
2952 int lit;
2953 if (fz_ct.type == "set_eq_reif" || fz_ct.type == "set_ne_reif") {
2954 lit = GetOrCreateLiteralForVarEqVar(x_literals[i], y_literals[i]);
2955 } else if (fz_ct.type == "set_subset_reif") {
2956 lit = GetOrCreateLiteralForVarLeVar(x_literals[i], y_literals[i]);
2957 } else {
2958 DCHECK_EQ(fz_ct.type, "set_superset_reif");
2959 lit = GetOrCreateLiteralForVarLeVar(y_literals[i], x_literals[i]);
2960 }
2961 all_true->add_literals(lit);
2962 one_false->add_literals(NegatedRef(lit));
2963 }
2964}
2965
2966void CpModelProtoWithMapping::SetLexOrderingConstraint(
2967 const fz::Constraint& fz_ct) {
2968 // set_le is tricky. Let's see all possible sets of size four in their
2969 // lexicographical order and their bit representation:
2970 // {} 0000
2971 // {1} 1000
2972 // {1,2} 1100
2973 // {1,2,3} 1110
2974 // {1,2,3,4} 1111
2975 // {1,2,4} 1101
2976 // {1,3} 1010
2977 // {1,3,4} 1011
2978 // {1,4} 1001
2979 // {2} 0100
2980 // {2,3} 0110
2981 // {2,3,4} 0111
2982 // {2,4} 0101
2983 // {3} 0010
2984 // {3,4} 0011
2985 // {4} 0001
2986 //
2987 // The example above clearly show that we cannot simply force the bit
2988 // representation to be in lexicographical order, which would be
2989 // relatively easy to do. The underlying reason is that the empty
2990 // (sub-)set compares before other sets. To work-around this, we define a
2991 // larger bit representation where between each two bits we add a new bit
2992 // saying whether all the bits to its right are zero or not. This way the
2993 // empty set is mapped from 0000 to 10101010, since every time the bits to
2994 // the right are zero. For the example above, we get:
2995 // {} 10101010
2996 // {1} 01101010
2997 // {1,2} 01011010
2998 // {1,2,3} 01010110
2999 // {1,2,3,4} 01010101
3000 // {1,2,4} 01010001
3001 // {1,3} 01000110
3002 // {1,3,4} 01000101
3003 // {1,4} 01000001
3004 // {2} 00011010
3005 // {2,3} 00010110
3006 // {2,3,4} 00010101
3007 // {2,4} 00010001
3008 // {3} 00000110
3009 // {3,4} 00000101
3010 // {4} 00000001
3011 //
3012 // After this trick, we can just apply the reverse lexicographic ordering
3013 // on the bit representation.
3014
3015 std::vector<int> orig_x_literals, orig_y_literals;
3016 PutSetBooleansInCommonDomain(
3017 {LookupSetVar(fz_ct.arguments[0]), LookupSetVar(fz_ct.arguments[1])},
3018 {&orig_x_literals, &orig_y_literals});
3019
3020 std::vector<int> x_literals, y_literals;
3021 for (int set_idx = 0; set_idx < 2; ++set_idx) {
3022 const std::vector<int>& orig_literals =
3023 set_idx == 0 ? orig_x_literals : orig_y_literals;
3024 std::vector<int>& literals = set_idx == 0 ? x_literals : y_literals;
3025 literals.reserve(2 * orig_literals.size());
3026
3027 for (int i = 0; i < orig_literals.size(); ++i) {
3028 const int empty_suffix_lit = NewBoolVar();
3029 literals.push_back(empty_suffix_lit);
3030 literals.push_back(orig_literals[i]);
3031
3032 ConstraintProto* empty_suffix_ct = proto.add_constraints();
3033 ConstraintProto* empty_suffix_ct_rev = proto.add_constraints();
3034 empty_suffix_ct->add_enforcement_literal(empty_suffix_lit);
3035 empty_suffix_ct_rev->mutable_bool_and()->add_literals(empty_suffix_lit);
3036 for (int j = i; j < orig_literals.size(); ++j) {
3037 empty_suffix_ct->mutable_bool_and()->add_literals(
3038 NegatedRef(orig_literals[j]));
3039 empty_suffix_ct_rev->add_enforcement_literal(
3040 NegatedRef(orig_literals[j]));
3041 }
3042 }
3043 }
3044
3045 const bool accept_equals =
3046 fz_ct.type == "set_le" || fz_ct.type == "set_le_reif";
3047 const bool is_reif =
3048 fz_ct.type == "set_le_reif" || fz_ct.type == "set_lt_reif";
3049 const int enforcement_literal =
3050 is_reif ? LookupVar(fz_ct.arguments[2]) : LookupConstant(1);
3051 AddLexOrdering(y_literals, x_literals, enforcement_literal, accept_equals);
3052 if (is_reif) {
3053 // set_le_reif:
3054 // - l => x <= y
3055 // - ~l => y < x
3056 // set_lt_reif:
3057 // - l => x < y
3058 // - ~l => y <= x
3059 AddLexOrdering(x_literals, y_literals, NegatedRef(enforcement_literal),
3060 !accept_equals);
3061 }
3062}
3063
3064using ConstraintToSetMethodMapType =
3065 absl::flat_hash_map<std::string_view, void (CpModelProtoWithMapping::*)(
3066 const fz::Constraint&)>;
3067
3068const ConstraintToSetMethodMapType& GetSetConstraintMap() {
3069 using MPMap = CpModelProtoWithMapping;
3070 static const absl::NoDestructor<ConstraintToSetMethodMapType> kConstraintMap({
3071 {"array_set_element", &MPMap::ArraySetElementConstraint},
3072 {"array_var_set_element", &MPMap::ArrayVarSetElementConstraint},
3073 {"fzn_all_different_set", &MPMap::FznAllDifferentSetConstraint},
3074 {"fzn_all_disjoint", &MPMap::FznAllDisjointConstraint},
3075 {"fzn_disjoint", &MPMap::FznDisjointConstraint},
3076 {"fzn_partition_set", &MPMap::FznPartitionSetConstraint},
3077 {"set_card", &MPMap::SetCardConstraint},
3078 {"set_in", &MPMap::SetInConstraint},
3079 {"set_in_reif", &MPMap::SetInReifConstraint},
3080 {"set_intersect", &MPMap::SetOpConstraint},
3081 {"set_union", &MPMap::SetOpConstraint},
3082 {"set_symdiff", &MPMap::SetOpConstraint},
3083 {"set_diff", &MPMap::SetOpConstraint},
3084 {"set_subset", &MPMap::SetSubSupersetEqConstraint},
3085 {"set_superset", &MPMap::SetSubSupersetEqConstraint},
3086 {"set_eq", &MPMap::SetSubSupersetEqConstraint},
3087 {"set_ne", &MPMap::SetNeConstraint},
3088 {"set_eq_reif", &MPMap::SetSubSupersetEqReifConstraint},
3089 {"set_ne_reif", &MPMap::SetSubSupersetEqReifConstraint},
3090 {"set_subset_reif", &MPMap::SetSubSupersetEqReifConstraint},
3091 {"set_superset_reif", &MPMap::SetSubSupersetEqReifConstraint},
3092 {"set_le", &MPMap::SetLexOrderingConstraint},
3093 {"set_lt", &MPMap::SetLexOrderingConstraint},
3094 {"set_le_reif", &MPMap::SetLexOrderingConstraint},
3095 {"set_lt_reif", &MPMap::SetLexOrderingConstraint},
3096 });
3097 return *kConstraintMap;
3098}
3099
3100void CpModelProtoWithMapping::ExtractSetConstraint(
3101 const fz::Constraint& fz_ct) {
3102 auto it = GetSetConstraintMap().find(fz_ct.type);
3103 if (it != GetSetConstraintMap().end()) {
3104 auto method_ptr = it->second;
3105 (this->*method_ptr)(fz_ct);
3106 } else {
3107 LOG(FATAL) << "Not supported " << fz_ct.DebugString();
3108 }
3109} // NOLINT(readability/fn_size)
3110
3111void CpModelProtoWithMapping::FillReifiedOrImpliedConstraint(
3112 const fz::Constraint& fz_ct) {
3113 // Start by processing the constraints that are cached.
3114 if (fz_ct.type == "int_eq_reif" || fz_ct.type == "bool_eq_reif") {
3115 VarOrValue left = LookupVarOrValue(fz_ct.arguments[0]);
3116 VarOrValue right = LookupVarOrValue(fz_ct.arguments[1]);
3117 if (left.var == kNoVar) std::swap(left, right);
3118 const int e = LookupVar(fz_ct.arguments[2]);
3119 if (left.var == kNoVar) {
3120 CHECK_EQ(right.var, kNoVar);
3121 if (left.value == right.value) {
3122 AddImplication({}, e);
3123 } else {
3124 AddImplication({}, NegatedRef(e));
3125 }
3126 } else if (right.var == kNoVar) {
3127 AddVarEqValueLiteral(left.var, right.value, e);
3128 } else {
3129 AddVarEqVarLiteral(left.var, right.var, e);
3130 }
3131 } else if (fz_ct.type == "int_eq_imp" || fz_ct.type == "bool_eq_imp") {
3132 VarOrValue left = LookupVarOrValue(fz_ct.arguments[0]);
3133 VarOrValue right = LookupVarOrValue(fz_ct.arguments[1]);
3134 if (left.var == kNoVar) std::swap(left, right);
3135 const int e = LookupVar(fz_ct.arguments[2]);
3136 if (left.var == kNoVar) {
3137 CHECK_EQ(right.var, kNoVar);
3138 if (left.value != right.value) {
3139 AddImplication({}, NegatedRef(e));
3140 }
3141 } else if (right.var == kNoVar) {
3142 const std::optional<int> lit = GetEncodingLiteral(left.var, right.value);
3143 if (lit.has_value()) {
3144 AddImplication({e}, lit.value());
3145 ++num_var_op_var_imp_cached;
3146 } else {
3147 AddLinearConstraint({e}, Domain(right.value), {{left.var, 1}});
3148 }
3149 } else {
3150 const std::optional<int> lit = GetVarEqVarLiteral(left.var, right.var);
3151 if (lit.has_value()) {
3152 AddImplication({e}, lit.value());
3153 ++num_var_op_var_imp_cached;
3154 } else {
3155 AddLinearConstraint({e}, Domain(0), {{left.var, 1}, {right.var, -1}});
3156 }
3157 }
3158 } else if (fz_ct.type == "int_ne_reif" || fz_ct.type == "bool_ne_reif") {
3159 VarOrValue left = LookupVarOrValue(fz_ct.arguments[0]);
3160 VarOrValue right = LookupVarOrValue(fz_ct.arguments[1]);
3161 if (left.var == kNoVar) std::swap(left, right);
3162 const int e = LookupVar(fz_ct.arguments[2]);
3163 if (left.var == kNoVar) {
3164 CHECK_EQ(right.var, kNoVar);
3165 if (left.value != right.value) {
3166 AddImplication({}, e);
3167 } else {
3168 AddImplication({}, NegatedRef(e));
3169 }
3170 } else if (right.var == kNoVar) {
3171 AddVarEqValueLiteral(left.var, right.value, NegatedRef(e));
3172 } else {
3173 AddVarEqVarLiteral(left.var, right.var, NegatedRef(e));
3174 }
3175 } else if (fz_ct.type == "int_ne_imp" || fz_ct.type == "bool_ne_imp") {
3176 VarOrValue left = LookupVarOrValue(fz_ct.arguments[0]);
3177 VarOrValue right = LookupVarOrValue(fz_ct.arguments[1]);
3178 if (left.var == kNoVar) std::swap(left, right);
3179 const int e = LookupVar(fz_ct.arguments[2]);
3180 if (left.var == kNoVar) {
3181 CHECK_EQ(right.var, kNoVar);
3182 if (left.value == right.value) {
3183 AddImplication({}, NegatedRef(e));
3184 }
3185 } else if (right.var == kNoVar) {
3186 const std::optional<int> lit = GetEncodingLiteral(left.var, right.value);
3187 if (lit.has_value()) {
3188 AddImplication({e}, NegatedRef(lit.value()));
3189 ++num_var_op_var_imp_cached;
3190 } else {
3191 AddLinearConstraint({e}, Domain(right.value).Complement(),
3192 {{left.var, 1}});
3193 }
3194 } else {
3195 const std::optional<int> lit = GetVarNeVarLiteral(left.var, right.var);
3196 if (lit.has_value()) {
3197 AddImplication({e}, lit.value());
3198 ++num_var_op_var_imp_cached;
3199 } else {
3200 AddLinearConstraint({e}, Domain(0).Complement(),
3201 {{left.var, 1}, {right.var, -1}});
3202 }
3203 }
3204 } else if (fz_ct.type == "int_le_reif" || fz_ct.type == "bool_le_reif") {
3205 VarOrValue left = LookupVarOrValue(fz_ct.arguments[0]);
3206 VarOrValue right = LookupVarOrValue(fz_ct.arguments[1]);
3207 const int e = LookupVar(fz_ct.arguments[2]);
3208 if (left.var == kNoVar) {
3209 if (right.var == kNoVar) {
3210 if (left.value <= right.value) {
3211 AddImplication({}, e);
3212 } else {
3213 AddImplication({}, NegatedRef(e));
3214 }
3215 } else {
3216 AddVarGeValueLiteral(right.var, left.value, e);
3217 }
3218 } else if (right.var == kNoVar) {
3219 AddVarLeValueLiteral(left.var, right.value, e);
3220 } else {
3221 AddVarLeVarLiteral(left.var, right.var, e);
3222 }
3223 } else if (fz_ct.type == "int_le_imp" || fz_ct.type == "bool_le_imp") {
3224 VarOrValue left = LookupVarOrValue(fz_ct.arguments[0]);
3225 VarOrValue right = LookupVarOrValue(fz_ct.arguments[1]);
3226 const int e = LookupVar(fz_ct.arguments[2]);
3227 if (left.var == kNoVar) {
3228 if (right.var == kNoVar) {
3229 if (left.value > right.value) {
3230 AddImplication({}, NegatedRef(e));
3231 }
3232 } else {
3233 AddLinearConstraint({e},
3234 {left.value, std::numeric_limits<int64_t>::max()},
3235 {{right.var, 1}});
3236 }
3237 } else if (right.var == kNoVar) {
3238 AddLinearConstraint({e},
3239 {std::numeric_limits<int64_t>::min(), right.value},
3240 {{left.var, 1}});
3241 } else {
3242 const std::optional<int> lit = GetVarLeVarLiteral(left.var, right.var);
3243 if (lit.has_value()) {
3244 AddImplication({e}, lit.value());
3245 ++num_var_op_var_imp_cached;
3246 } else {
3247 AddLinearConstraint({e}, {std::numeric_limits<int64_t>::min(), 0},
3248 {{left.var, 1}, {right.var, -1}});
3249 }
3250 }
3251 } else if (fz_ct.type == "int_ge_reif" || fz_ct.type == "bool_ge_reif") {
3252 VarOrValue left = LookupVarOrValue(fz_ct.arguments[0]);
3253 VarOrValue right = LookupVarOrValue(fz_ct.arguments[1]);
3254 const int e = LookupVar(fz_ct.arguments[2]);
3255 if (left.var == kNoVar) {
3256 if (right.var == kNoVar) {
3257 if (left.value >= right.value) {
3258 AddImplication({}, e);
3259 } else {
3260 AddImplication({}, NegatedRef(e));
3261 }
3262 } else {
3263 AddVarLeValueLiteral(right.var, left.value, e);
3264 }
3265 } else if (right.var == kNoVar) {
3266 AddVarGeValueLiteral(left.var, right.value, e);
3267 } else {
3268 AddVarLeVarLiteral(right.var, left.var, e);
3269 }
3270 } else if (fz_ct.type == "int_lt_reif" || fz_ct.type == "bool_lt_reif") {
3271 VarOrValue left = LookupVarOrValue(fz_ct.arguments[0]);
3272 VarOrValue right = LookupVarOrValue(fz_ct.arguments[1]);
3273 const int e = LookupVar(fz_ct.arguments[2]);
3274 if (left.var == kNoVar) {
3275 if (right.var == kNoVar) {
3276 if (left.value < right.value) {
3277 AddImplication({}, e);
3278 } else {
3279 AddImplication({}, NegatedRef(e));
3280 }
3281 } else {
3282 AddVarGtValueLiteral(right.var, left.value, e);
3283 }
3284 } else if (right.var == kNoVar) {
3285 AddVarLtValueLiteral(left.var, right.value, e);
3286 } else {
3287 AddVarLtVarLiteral(left.var, right.var, e);
3288 }
3289 } else if (fz_ct.type == "int_gt_reif" || fz_ct.type == "bool_gt_reif") {
3290 VarOrValue left = LookupVarOrValue(fz_ct.arguments[0]);
3291 VarOrValue right = LookupVarOrValue(fz_ct.arguments[1]);
3292 const int e = LookupVar(fz_ct.arguments[2]);
3293 if (left.var == kNoVar) {
3294 if (right.var == kNoVar) {
3295 if (left.value < right.value) {
3296 AddImplication({}, e);
3297 } else {
3298 AddImplication({}, NegatedRef(e));
3299 }
3300 } else {
3301 AddVarLtValueLiteral(right.var, left.value, e);
3302 }
3303 } else if (right.var == kNoVar) {
3304 AddVarGtValueLiteral(left.var, right.value, e);
3305 } else {
3306 AddVarLtVarLiteral(right.var, left.var, e);
3307 }
3308 } else if (fz_ct.type == "array_bool_or") {
3309 const std::vector<int> literals = LookupVars(fz_ct.arguments[0]);
3310 const int e = LookupVar(fz_ct.arguments[1]);
3311 switch (literals.size()) {
3312 case 0: {
3313 AddImplication({}, NegatedRef(e));
3314 break;
3315 }
3316 case 1: {
3317 AddLinearConstraint({}, Domain(0), {{literals[0], 1}, {e, -1}});
3318 break;
3319 }
3320 case 2: {
3321 AddVarOrVarLiteral(literals[0], literals[1], e);
3322 break;
3323 }
3324 default: {
3325 ConstraintProto* imply = AddEnforcedConstraint(e);
3326 for (int i = 0; i < literals.size(); ++i) {
3327 imply->mutable_bool_or()->add_literals(literals[i]);
3328 }
3329 ConstraintProto* refute = AddEnforcedConstraint(NegatedRef(e));
3330 for (int i = 0; i < literals.size(); ++i) {
3331 refute->mutable_bool_and()->add_literals(NegatedRef(literals[i]));
3332 }
3333 }
3334 }
3335 } else if (fz_ct.type == "array_bool_and") {
3336 const std::vector<int> literals = LookupVars(fz_ct.arguments[0]);
3337 const int e = LookupVar(fz_ct.arguments[1]);
3338 switch (literals.size()) {
3339 case 0: {
3340 AddImplication({}, e);
3341 break;
3342 }
3343 case 1: {
3344 AddLinearConstraint({}, Domain(0), {{literals[0], 1}, {e, -1}});
3345 break;
3346 }
3347 case 2: {
3348 AddVarAndVarLiteral(literals[0], literals[1], e);
3349 break;
3350 }
3351 default: {
3352 ConstraintProto* imply = AddEnforcedConstraint(e);
3353 for (int i = 0; i < literals.size(); ++i) {
3354 imply->mutable_bool_and()->add_literals(literals[i]);
3355 }
3356 ConstraintProto* refute = AddEnforcedConstraint(NegatedRef(e));
3357 for (int i = 0; i < literals.size(); ++i) {
3358 refute->mutable_bool_or()->add_literals(NegatedRef(literals[i]));
3359 }
3360 }
3361 }
3362 } else {
3363 // Start by adding a non-reified version of the same constraint.
3364 ConstraintProto* ct = proto.add_constraints();
3365 ct->set_name(fz_ct.type);
3366 std::string simplified_type;
3367 if (absl::EndsWith(fz_ct.type, "_reif")) {
3368 // Remove _reif.
3369 simplified_type = fz_ct.type.substr(0, fz_ct.type.size() - 5);
3370 } else if (absl::EndsWith(fz_ct.type, "_imp")) {
3371 // Remove _imp.
3372 simplified_type = fz_ct.type.substr(0, fz_ct.type.size() - 4);
3373 } else {
3374 // Keep name as it is an implicit reified constraint.
3375 simplified_type = fz_ct.type;
3376 }
3377
3378 // We need a copy to be able to change the type of the constraint.
3379 fz::Constraint copy = fz_ct;
3380 copy.type = simplified_type;
3381
3382 // Create the CP-SAT constraint.
3383 FillConstraint(copy, ct);
3384
3385 // In case of reified constraints, the type of the opposite constraint.
3386 std::string negated_type;
3387
3388 // Fill enforcement_literal and set copy.type to the negated constraint.
3389 if (simplified_type == "set_in") {
3390 ct->add_enforcement_literal(LookupVar(fz_ct.arguments[2]));
3391 negated_type = "set_in_negated";
3392 } else if (simplified_type == "bool_eq" || simplified_type == "int_eq") {
3393 ct->add_enforcement_literal(LookupVar(fz_ct.arguments[2]));
3394 negated_type = "int_ne";
3395 } else if (simplified_type == "bool_ne" || simplified_type == "int_ne") {
3396 ct->add_enforcement_literal(LookupVar(fz_ct.arguments[2]));
3397 negated_type = "int_eq";
3398 } else if (simplified_type == "bool_le" || simplified_type == "int_le") {
3399 ct->add_enforcement_literal(LookupVar(fz_ct.arguments[2]));
3400 negated_type = "int_gt";
3401 } else if (simplified_type == "bool_lt" || simplified_type == "int_lt") {
3402 ct->add_enforcement_literal(LookupVar(fz_ct.arguments[2]));
3403 negated_type = "int_ge";
3404 } else if (simplified_type == "bool_ge" || simplified_type == "int_ge") {
3405 ct->add_enforcement_literal(LookupVar(fz_ct.arguments[2]));
3406 negated_type = "int_lt";
3407 } else if (simplified_type == "bool_gt" || simplified_type == "int_gt") {
3408 ct->add_enforcement_literal(LookupVar(fz_ct.arguments[2]));
3409 negated_type = "int_le";
3410 } else if (simplified_type == "int_lin_eq") {
3411 ct->add_enforcement_literal(LookupVar(fz_ct.arguments[3]));
3412 negated_type = "int_lin_ne";
3413 } else if (simplified_type == "int_lin_ne") {
3414 ct->add_enforcement_literal(LookupVar(fz_ct.arguments[3]));
3415 negated_type = "int_lin_eq";
3416 } else if (simplified_type == "int_lin_le") {
3417 ct->add_enforcement_literal(LookupVar(fz_ct.arguments[3]));
3418 negated_type = "int_lin_gt";
3419 } else if (simplified_type == "int_lin_ge") {
3420 ct->add_enforcement_literal(LookupVar(fz_ct.arguments[3]));
3421 negated_type = "int_lin_lt";
3422 } else if (simplified_type == "int_lin_lt") {
3423 ct->add_enforcement_literal(LookupVar(fz_ct.arguments[3]));
3424 negated_type = "int_lin_ge";
3425 } else if (simplified_type == "int_lin_gt") {
3426 ct->add_enforcement_literal(LookupVar(fz_ct.arguments[3]));
3427 negated_type = "int_lin_le";
3428 } else {
3429 LOG(FATAL) << "Unsupported " << simplified_type;
3430 }
3431
3432 // One way implication. We can stop here.
3433 if (absl::EndsWith(fz_ct.type, "_imp")) return;
3434
3435 // Add the other side of the reification because CpModelProto only support
3436 // half reification.
3437 ConstraintProto* negated_ct =
3438 AddEnforcedConstraint(NegatedRef(ct->enforcement_literal(0)));
3439 negated_ct->set_name(fz_ct.type + " (negated)");
3440 copy.type = negated_type;
3441 FillConstraint(copy, negated_ct);
3442 }
3443}
3444
3445void CpModelProtoWithMapping::TranslateSearchAnnotations(
3446 absl::Span<const fz::Annotation> search_annotations, SolverLogger* logger) {
3447 std::vector<fz::Annotation> flat_annotations;
3448 for (const fz::Annotation& annotation : search_annotations) {
3449 fz::FlattenAnnotations(annotation, &flat_annotations);
3450 }
3451
3452 // CP-SAT rejects models containing variables duplicated in hints.
3453 absl::flat_hash_set<int> hinted_vars;
3454
3455 for (const fz::Annotation& annotation : flat_annotations) {
3456 if (annotation.IsFunctionCallWithIdentifier("warm_start")) {
3457 CHECK_EQ(2, annotation.annotations.size());
3458 const fz::Annotation& vars = annotation.annotations[0];
3459 const fz::Annotation& values = annotation.annotations[1];
3460 if (vars.type != fz::Annotation::VAR_REF_ARRAY ||
3461 values.type != fz::Annotation::INT_LIST) {
3462 continue;
3463 }
3464 for (int i = 0; i < vars.variables.size(); ++i) {
3465 fz::Variable* fz_var = vars.variables[i];
3466 const int var = fz_var_to_index.at(fz_var);
3467 const int64_t value = values.values[i];
3468 if (hinted_vars.insert(var).second) {
3469 proto.mutable_solution_hint()->add_vars(var);
3470 proto.mutable_solution_hint()->add_values(value);
3471 }
3472 }
3473 } else if (annotation.IsFunctionCallWithIdentifier("int_search") ||
3474 annotation.IsFunctionCallWithIdentifier("bool_search")) {
3475 const std::vector<fz::Annotation>& args = annotation.annotations;
3476 std::vector<fz::Variable*> vars;
3477 args[0].AppendAllVariables(&vars);
3478
3479 DecisionStrategyProto* strategy = proto.add_search_strategy();
3480 for (fz::Variable* v : vars) {
3481 strategy->add_variables(fz_var_to_index.at(v));
3482 }
3483
3484 const fz::Annotation& choose = args[1];
3485 if (choose.id == "input_order") {
3486 strategy->set_variable_selection_strategy(
3487 DecisionStrategyProto::CHOOSE_FIRST);
3488 } else if (choose.id == "first_fail") {
3489 strategy->set_variable_selection_strategy(
3490 DecisionStrategyProto::CHOOSE_MIN_DOMAIN_SIZE);
3491 } else if (choose.id == "anti_first_fail") {
3492 strategy->set_variable_selection_strategy(
3493 DecisionStrategyProto::CHOOSE_MAX_DOMAIN_SIZE);
3494 } else if (choose.id == "smallest") {
3495 strategy->set_variable_selection_strategy(
3496 DecisionStrategyProto::CHOOSE_LOWEST_MIN);
3497 } else if (choose.id == "largest") {
3498 strategy->set_variable_selection_strategy(
3499 DecisionStrategyProto::CHOOSE_HIGHEST_MAX);
3500 } else {
3501 SOLVER_LOG(logger, "Unsupported variable selection strategy '",
3502 choose.id, "', falling back to 'smallest'");
3503 strategy->set_variable_selection_strategy(
3504 DecisionStrategyProto::CHOOSE_LOWEST_MIN);
3505 }
3506
3507 const fz::Annotation& select = args[2];
3508 if (select.id == "indomain_min" || select.id == "indomain") {
3509 strategy->set_domain_reduction_strategy(
3510 DecisionStrategyProto::SELECT_MIN_VALUE);
3511 } else if (select.id == "indomain_max") {
3512 strategy->set_domain_reduction_strategy(
3513 DecisionStrategyProto::SELECT_MAX_VALUE);
3514 } else if (select.id == "indomain_split") {
3515 strategy->set_domain_reduction_strategy(
3516 DecisionStrategyProto::SELECT_LOWER_HALF);
3517 } else if (select.id == "indomain_reverse_split") {
3518 strategy->set_domain_reduction_strategy(
3519 DecisionStrategyProto::SELECT_UPPER_HALF);
3520 } else if (select.id == "indomain_median") {
3521 strategy->set_domain_reduction_strategy(
3522 DecisionStrategyProto::SELECT_MEDIAN_VALUE);
3523 } else {
3524 SOLVER_LOG(logger, "Unsupported value selection strategy '", select.id,
3525 "', falling back to 'indomain_min'");
3526 strategy->set_domain_reduction_strategy(
3527 DecisionStrategyProto::SELECT_MIN_VALUE);
3528 }
3529 } else if (annotation.IsFunctionCallWithIdentifier("set_search")) {
3530 const std::vector<fz::Annotation>& args = annotation.annotations;
3531 std::vector<fz::Variable*> vars;
3532 args[0].AppendAllVariables(&vars);
3533
3534 DecisionStrategyProto* strategy = proto.add_search_strategy();
3535 for (fz::Variable* v : vars) {
3536 std::shared_ptr<SetVariable> set_var = set_variables.at(v);
3537 strategy->mutable_variables()->Add(set_var->var_indices.begin(),
3538 set_var->var_indices.end());
3539 }
3540
3541 const fz::Annotation& choose = args[1];
3542 if (choose.id == "input_order") {
3543 strategy->set_variable_selection_strategy(
3544 DecisionStrategyProto::CHOOSE_FIRST);
3545 } else {
3546 SOLVER_LOG(logger, "Unsupported set variable selection strategy '",
3547 choose.id, "', falling back to 'input_order'");
3548 strategy->set_variable_selection_strategy(
3549 DecisionStrategyProto::CHOOSE_FIRST);
3550 }
3551
3552 const fz::Annotation& select = args[2];
3553 if (select.id == "indomain_min" || select.id == "indomain") {
3554 strategy->set_domain_reduction_strategy(
3555 DecisionStrategyProto::SELECT_MIN_VALUE);
3556 } else if (select.id == "indomain_max") {
3557 strategy->set_domain_reduction_strategy(
3558 DecisionStrategyProto::SELECT_MAX_VALUE);
3559 } else {
3560 SOLVER_LOG(logger, "Unsupported value selection strategy '", select.id,
3561 "', falling back to 'indomain_min'");
3562 strategy->set_domain_reduction_strategy(
3563 DecisionStrategyProto::SELECT_MIN_VALUE);
3564 }
3565 }
3566 }
3567}
3568
3569// The format is fixed in the flatzinc specification.
3570std::string SolutionString(
3571 const fz::SolutionOutputSpecs& output,
3572 const std::function<int64_t(fz::Variable*)>& value_func,
3573 const std::function<std::vector<int64_t>(fz::Variable*)>& set_evaluator,
3574 double objective_value) {
3575 if (output.variable != nullptr && !output.variable->domain.is_float &&
3576 !output.variable->domain.is_a_set) {
3577 const int64_t value = value_func(output.variable);
3578 if (output.display_as_boolean) {
3579 return absl::StrCat(output.name, " = ", value == 1 ? "true" : "false",
3580 ";");
3581 } else {
3582 return absl::StrCat(output.name, " = ", value, ";");
3583 }
3584 } else if (output.variable != nullptr && output.variable->domain.is_a_set) {
3585 const std::vector<int64_t> values = set_evaluator(output.variable);
3586 return absl::StrCat(output.name, " = {", absl::StrJoin(values, ","), "};");
3587 } else if (output.variable != nullptr && output.variable->domain.is_float) {
3588 return absl::StrCat(output.name, " = ", objective_value, ";");
3589 } else {
3590 const int bound_size = output.bounds.size();
3591 std::string result =
3592 absl::StrCat(output.name, " = array", bound_size, "d(");
3593 for (int i = 0; i < bound_size; ++i) {
3594 if (output.bounds[i].max_value >= output.bounds[i].min_value) {
3595 absl::StrAppend(&result, output.bounds[i].min_value, "..",
3596 output.bounds[i].max_value, ", ");
3597 } else {
3598 result.append("{},");
3599 }
3600 }
3601 result.append("[");
3602 for (int i = 0; i < output.flat_variables.size(); ++i) {
3603 if (output.flat_variables[i]->domain.is_a_set) {
3604 const std::vector<int64_t> values =
3605 set_evaluator(output.flat_variables[i]);
3606 absl::StrAppend(&result, "{", absl::StrJoin(values, ","), "}");
3607 } else {
3608 const int64_t value = value_func(output.flat_variables[i]);
3609 if (output.display_as_boolean) {
3610 result.append(value ? "true" : "false");
3611 } else {
3612 absl::StrAppend(&result, value);
3613 }
3614 }
3615 if (i != output.flat_variables.size() - 1) {
3616 result.append(", ");
3617 }
3618 }
3619 result.append("]);");
3620 return result;
3621 }
3622 return "";
3623}
3624
3625std::string SolutionString(
3626 const fz::Model& model,
3627 const std::function<int64_t(fz::Variable*)>& value_func,
3628 const std::function<std::vector<int64_t>(fz::Variable*)>& set_evaluator,
3629 double objective_value) {
3630 std::string solution_string;
3631 for (const auto& output_spec : model.output()) {
3632 solution_string.append(SolutionString(output_spec, value_func,
3633 set_evaluator, objective_value));
3634 solution_string.append("\n");
3635 }
3636 return solution_string;
3637}
3638
3639void OutputFlatzincStats(const CpSolverResponse& response,
3640 SolverLogger* solution_logger) {
3641 SOLVER_LOG(solution_logger,
3642 "%%%mzn-stat: objective=", response.objective_value());
3643 SOLVER_LOG(solution_logger,
3644 "%%%mzn-stat: objectiveBound=", response.best_objective_bound());
3645 SOLVER_LOG(solution_logger,
3646 "%%%mzn-stat: boolVariables=", response.num_booleans());
3647 SOLVER_LOG(solution_logger,
3648 "%%%mzn-stat: failures=", response.num_conflicts());
3649 SOLVER_LOG(
3650 solution_logger, "%%%mzn-stat: propagations=",
3651 response.num_binary_propagations() + response.num_integer_propagations());
3652 SOLVER_LOG(solution_logger, "%%%mzn-stat: solveTime=", response.wall_time());
3653}
3654
3655} // namespace
3656
3658 // Scan the model, rename int2float to int_eq, change type of the floating
3659 // point variables to integer.
3660 for (fz::Constraint* ct : fz_model->constraints()) {
3661 if (!ct->active) continue;
3662 if (ct->type == "int2float") {
3663 ct->type = "int_eq";
3664 fz::Domain& float_domain = ct->arguments[1].variables[0]->domain;
3665 float_domain.is_float = false;
3666 for (const double float_value : float_domain.float_values) {
3667 float_domain.values.push_back(static_cast<int64_t>(float_value));
3668 }
3669 float_domain.float_values.clear();
3670 }
3671 }
3672
3673 // Scan the model to find the float objective variable and the float objective
3674 // constraint if defined.
3675 fz::Variable* float_objective_var = nullptr;
3676 for (fz::Variable* var : fz_model->variables()) {
3677 if (!var->active) continue;
3678 if (var->domain.is_float) {
3679 CHECK(float_objective_var == nullptr);
3680 float_objective_var = var;
3681 }
3682 }
3683
3684 fz::Constraint* float_objective_ct = nullptr;
3685 if (float_objective_var != nullptr) {
3686 for (fz::Constraint* ct : fz_model->constraints()) {
3687 if (!ct->active) continue;
3688 if (ct->type == "float_lin_eq") {
3689 CHECK(float_objective_ct == nullptr);
3690 float_objective_ct = ct;
3691 break;
3692 }
3693 }
3694 }
3695
3696 if (float_objective_ct != nullptr || float_objective_var != nullptr) {
3697 CHECK(float_objective_ct != nullptr);
3698 CHECK(float_objective_var != nullptr);
3699 const int arity = float_objective_ct->arguments[0].Size();
3700 CHECK_EQ(float_objective_ct->arguments[1].variables[arity - 1],
3701 float_objective_var);
3702 CHECK_EQ(float_objective_ct->arguments[0].floats[arity - 1], -1.0);
3703 for (int i = 0; i + 1 < arity; ++i) {
3705 float_objective_ct->arguments[1].variables[i],
3706 float_objective_ct->arguments[0].floats[i]);
3707 }
3709 -float_objective_ct->arguments[2].floats[0]);
3710 fz_model->ClearObjective();
3711 float_objective_var->active = false;
3712 float_objective_ct->active = false;
3713 }
3714}
3715
3718 const SatParameters& sat_params,
3719 Model* sat_model,
3720 SolverLogger* solution_logger) {
3721 SolverLogger* logger = sat_model->GetOrCreate<SolverLogger>();
3722 const bool enumerate_all_solutions_of_a_sat_problem =
3723 p.search_all_solutions && fz_model.objective() == nullptr;
3724 CpModelProtoWithMapping m(enumerate_all_solutions_of_a_sat_problem);
3725 m.proto.set_name(fz_model.name());
3726
3727 // The translation is easy, we create one variable
3728 // per flatzinc variable, plus eventually a bunch
3729 // of constant variables that will be created
3730 // lazily.
3731 for (fz::Variable* fz_var : fz_model.variables()) {
3732 if (!fz_var->active) continue;
3733 CHECK(!fz_var->domain.is_float) << "CP-SAT does not support float "
3734 "variables";
3735
3736 if (fz_var->domain.is_a_set) {
3737 std::shared_ptr<SetVariable> set_var = std::make_shared<SetVariable>();
3738
3739 // Fill values.
3740 if (fz_var->domain.is_interval) {
3741 set_var->sorted_values.reserve(fz_var->domain.values[1] -
3742 fz_var->domain.values[0] + 1);
3743 for (int64_t value = fz_var->domain.values[0];
3744 value <= fz_var->domain.values[1]; ++value) {
3745 set_var->sorted_values.push_back(value);
3746 }
3747 } else {
3748 set_var->sorted_values.reserve(fz_var->domain.values.size());
3749 for (int64_t value : fz_var->domain.values) {
3750 set_var->sorted_values.push_back(value);
3751 }
3752 }
3753
3754 // Add Boolean variables.
3755 for (int i = 0; i < set_var->sorted_values.size(); ++i) {
3756 if (fz_var->domain.is_fixed_set) {
3757 set_var->var_indices.push_back(m.LookupConstant(1));
3758 } else {
3759 set_var->var_indices.push_back(m.NewBoolVar());
3760 }
3761 }
3762 m.set_variables[fz_var] = set_var;
3763 } else {
3764 m.fz_var_to_index[fz_var] = m.proto.variables_size();
3765 IntegerVariableProto* var = m.proto.add_variables();
3766 var->set_name(fz_var->name);
3767 if (fz_var->domain.is_interval) {
3768 if (fz_var->domain.values.empty()) {
3769 // The CP-SAT solver checks that
3770 // constraints cannot overflow during
3771 // their propagation. Because of that, we
3772 // trim undefined variable domains (i.e.
3773 // int in minizinc) to something hopefully
3774 // large enough.
3775 LOG_FIRST_N(WARNING, 1) << "Using flag --fz_int_max for "
3776 "unbounded integer variables.";
3777 LOG_FIRST_N(WARNING, 1)
3778 << " actual domain is [" << -absl::GetFlag(FLAGS_fz_int_max)
3779 << ".." << absl::GetFlag(FLAGS_fz_int_max) << "]";
3780 FillDomainInProto(-absl::GetFlag(FLAGS_fz_int_max),
3781 absl::GetFlag(FLAGS_fz_int_max), var);
3782 } else {
3783 FillDomainInProto(fz_var->domain.values[0], fz_var->domain.values[1],
3784 var);
3785 }
3786 } else {
3787 FillDomainInProto(Domain::FromValues(fz_var->domain.values), var);
3788 }
3789 }
3790 }
3791
3792 // Translate the constraints.
3793
3794 // First pass: extract useful information.
3795 for (fz::Constraint* fz_ct : fz_model.constraints()) {
3796 m.FirstPass(*fz_ct);
3797 }
3798
3799 // Second pass: translate the constraints.
3800 for (fz::Constraint* fz_ct : fz_model.constraints()) {
3801 if (fz_ct == nullptr || !fz_ct->active) continue;
3802 if (m.ConstraintContainsSetVariables(*fz_ct)) {
3803 m.ExtractSetConstraint(*fz_ct);
3804 } else {
3805 if (absl::EndsWith(fz_ct->type, "_reif") ||
3806 absl::EndsWith(fz_ct->type, "_imp") ||
3807 fz_ct->type == "array_bool_or" || fz_ct->type == "array_bool_and") {
3808 m.FillReifiedOrImpliedConstraint(*fz_ct);
3809 } else {
3810 ConstraintProto* ct = m.proto.add_constraints();
3811 ct->set_name(fz_ct->type);
3812 m.FillConstraint(*fz_ct, ct);
3813 }
3814 }
3815 }
3816
3817 // Third pass: Implement the encoding constraints.
3818 m.AddAllEncodingConstraints();
3819
3820 // Fill the objective.
3821 if (fz_model.objective() != nullptr) {
3822 CpObjectiveProto* objective = m.proto.mutable_objective();
3823 if (fz_model.maximize()) {
3824 objective->set_scaling_factor(-1);
3825 objective->add_coeffs(-1);
3826 objective->add_vars(m.fz_var_to_index[fz_model.objective()]);
3827 } else {
3828 objective->add_coeffs(1);
3829 objective->add_vars(m.fz_var_to_index[fz_model.objective()]);
3830 }
3831 } else if (!fz_model.float_objective_variables().empty()) {
3832 FloatObjectiveProto* objective = m.proto.mutable_floating_point_objective();
3833 for (int i = 0; i < fz_model.float_objective_variables().size(); ++i) {
3834 objective->add_vars(
3835 m.fz_var_to_index[fz_model.float_objective_variables()[i]]);
3836 objective->add_coeffs(fz_model.float_objective_coefficients()[i]);
3837 }
3838 objective->set_offset(fz_model.float_objective_offset());
3839 objective->set_maximize(fz_model.maximize());
3840 }
3841
3842 // Fill the search order.
3843 m.TranslateSearchAnnotations(fz_model.search_annotations(), logger);
3844
3845 // Extra statistics.
3846 SOLVER_LOG(logger,
3847 " - #var_op_value_reif cached: ", m.num_var_op_value_reif_cached);
3848 SOLVER_LOG(logger,
3849 " - #var_op_var_reif cached: ", m.num_var_op_var_reif_cached);
3850 SOLVER_LOG(logger,
3851 " - #var_op_var_imp cached: ", m.num_var_op_var_reif_cached);
3852 SOLVER_LOG(logger, " - #full domain encodings: ", m.num_full_encodings);
3853 if (absl::GetFlag(FLAGS_fz_use_light_encoding)) {
3854 SOLVER_LOG(logger,
3855 " - #partial domain encodings: ", m.num_partial_encodings);
3856 SOLVER_LOG(logger,
3857 " - #light constraint encodings: ", m.num_light_encodings);
3858 }
3859
3860 if (p.search_all_solutions && !m.proto.has_objective()) {
3861 // Enumerate all sat solutions.
3862 m.parameters.set_enumerate_all_solutions(true);
3863 }
3864
3865 m.parameters.set_log_search_progress(p.log_search_progress);
3866 m.parameters.set_log_to_stdout(!p.ortools_mode);
3867
3868 // Helps with challenge unit tests.
3869 m.parameters.set_max_domain_size_when_encoding_eq_neq_constraints(32);
3870
3871 // Computes the number of workers.
3872 int num_workers = 1;
3873 if (p.search_all_solutions && fz_model.objective() == nullptr) {
3874 if (p.number_of_threads > 1) {
3875 // We don't support enumerating all solution in parallel for a SAT
3876 // problem. But note that we do support it for an optimization problem
3877 // since the meaning of p.all_solutions is not the same in this case.
3878 SOLVER_LOG(logger,
3879 "Search for all solutions of a SAT problem in parallel is not "
3880 "supported. Switching back to sequential search.");
3881 }
3882 } else if (p.number_of_threads <= 0) {
3883 // TODO(user): Supports setting the number of workers to 0, which will
3884 // then query the number of cores available. This is complex now as we
3885 // need to still support the expected behabior (no flags -> 1 thread
3886 // fixed search, -f -> 1 thread free search).
3887 SOLVER_LOG(logger,
3888 "The number of search workers, is not specified. For better "
3889 "performances, please set the number of workers to 8, 16, or "
3890 "more depending on the number of cores of your computer.");
3891 } else {
3892 num_workers = p.number_of_threads;
3893 }
3894
3895 // Specifies single thread specific search modes.
3896 if (num_workers == 1 && !p.use_free_search) { // Fixed search.
3897 m.parameters.set_search_branching(SatParameters::FIXED_SEARCH);
3898 m.parameters.set_keep_all_feasible_solutions_in_presolve(true);
3899 } else if (num_workers == 1 && p.use_free_search) { // Free search.
3900 m.parameters.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
3901 if (!p.search_all_solutions &&
3902 (absl::GetFlag(FLAGS_force_interleave_search) || p.ortools_mode)) {
3903 m.parameters.set_interleave_search(true);
3904 m.parameters.set_use_rins_lns(false);
3905 m.parameters.add_subsolvers("default_lp");
3906 m.parameters.add_subsolvers("max_lp");
3907 m.parameters.add_subsolvers("quick_restart");
3908 m.parameters.add_subsolvers("core_or_no_lp"); // no_lp if no objective.
3909 m.parameters.set_num_violation_ls(1); // Off if no objective.
3910 if (!m.proto.search_strategy().empty()) {
3911 m.parameters.add_subsolvers("fixed");
3912 }
3913 }
3914 } else if (num_workers > 1 && num_workers < 8 && p.ortools_mode) {
3915 SOLVER_LOG(logger, "Bumping number of workers from ", num_workers, " to 8");
3916 num_workers = 8;
3917 }
3918 m.parameters.set_num_workers(num_workers);
3919
3920 // Time limit.
3921 if (p.max_time_in_seconds > 0) {
3922 m.parameters.set_max_time_in_seconds(p.max_time_in_seconds);
3923 }
3924
3925 // The order is important, we want the flag parameters to overwrite anything
3926 // set in m.parameters.
3927 m.parameters.MergeFrom(sat_params);
3928
3929 // We only need an observer if 'p.display_all_solutions' or
3930 // 'p.search_all_solutions' are true.
3931 std::function<void(const CpSolverResponse&)> solution_observer = nullptr;
3933 solution_observer = [&fz_model, &m, &p,
3934 solution_logger](const CpSolverResponse& r) {
3935 const std::string solution_string = SolutionString(
3936 fz_model,
3937 [&m, &r](fz::Variable* v) {
3938 return r.solution(m.fz_var_to_index.at(v));
3939 },
3940 [&m, &r](fz::Variable* v) {
3941 std::vector<int64_t> values;
3942 const std::shared_ptr<SetVariable> set_var = m.set_variables.at(v);
3943 for (int i = 0; i < set_var->sorted_values.size(); ++i) {
3944 if (r.solution(set_var->var_indices[i]) == 1) {
3945 values.push_back(set_var->sorted_values[i]);
3946 }
3947 }
3948 return values;
3949 },
3950 r.objective_value());
3951 SOLVER_LOG(solution_logger, solution_string);
3952 if (p.check_all_solutions) {
3953 CHECK(CheckSolution(
3954 fz_model,
3955 [&r, &m](fz::Variable* v) {
3956 return r.solution(m.fz_var_to_index.at(v));
3957 },
3958 [&r, &m](fz::Variable* v) {
3959 std::vector<int64_t> values;
3960 const std::shared_ptr<SetVariable> set_var =
3961 m.set_variables.at(v);
3962 for (int i = 0; i < set_var->sorted_values.size(); ++i) {
3963 if (r.solution(set_var->var_indices[i]) == 1) {
3964 values.push_back(set_var->sorted_values[i]);
3965 }
3966 }
3967 return values;
3968 },
3969 solution_logger));
3970 }
3971 if (p.display_statistics) {
3972 OutputFlatzincStats(r, solution_logger);
3973 }
3974 SOLVER_LOG(solution_logger, "----------");
3975 };
3976 }
3977
3978 sat_model->Add(NewSatParameters(m.parameters));
3979 if (solution_observer != nullptr) {
3980 sat_model->Add(NewFeasibleSolutionObserver(solution_observer));
3981 }
3982
3983 const CpSolverResponse response = SolveCpModel(m.proto, sat_model);
3984
3985 // Check the returned solution with the fz model checker.
3986 if (response.status() == CpSolverStatus::FEASIBLE ||
3987 response.status() == CpSolverStatus::OPTIMAL) {
3988 CHECK(CheckSolution(
3989 fz_model,
3990 [&response, &m](fz::Variable* v) {
3991 return response.solution(m.fz_var_to_index.at(v));
3992 },
3993 [&response, &m](fz::Variable* v) {
3994 std::vector<int64_t> values;
3995 const std::shared_ptr<SetVariable> set_var = m.set_variables.at(v);
3996 for (int i = 0; i < set_var->sorted_values.size(); ++i) {
3997 if (response.solution(set_var->var_indices[i]) == 1) {
3998 values.push_back(set_var->sorted_values[i]);
3999 }
4000 }
4001 return values;
4002 },
4003 logger));
4004 }
4005
4006 // Output the solution in the flatzinc official format.
4007 if (p.ortools_mode) {
4008 if (response.status() == CpSolverStatus::FEASIBLE ||
4009 response.status() == CpSolverStatus::OPTIMAL) {
4010 // Display the solution if it is not already displayed.
4012 // Already printed otherwise.
4013 const std::string solution_string = SolutionString(
4014 fz_model,
4015 [&response, &m](fz::Variable* v) {
4016 return response.solution(m.fz_var_to_index.at(v));
4017 },
4018 [&response, &m](fz::Variable* v) {
4019 std::vector<int64_t> values;
4020 const std::shared_ptr<SetVariable> set_var =
4021 m.set_variables.at(v);
4022 for (int i = 0; i < set_var->sorted_values.size(); ++i) {
4023 if (response.solution(set_var->var_indices[i]) == 1) {
4024 values.push_back(set_var->sorted_values[i]);
4025 }
4026 }
4027 return values;
4028 },
4029 response.objective_value());
4030 SOLVER_LOG(solution_logger, solution_string);
4031 SOLVER_LOG(solution_logger, "----------");
4032 }
4033 const bool should_print_optimal =
4034 response.status() == CpSolverStatus::OPTIMAL &&
4035 (fz_model.objective() != nullptr || p.search_all_solutions);
4036
4037 if (should_print_optimal) {
4038 SOLVER_LOG(solution_logger, "==========");
4039 }
4040 } else if (response.status() == CpSolverStatus::INFEASIBLE) {
4041 SOLVER_LOG(solution_logger, "=====UNSATISFIABLE=====");
4042 } else if (response.status() == CpSolverStatus::MODEL_INVALID) {
4043 const std::string error_message = ValidateCpModel(m.proto);
4044 VLOG(1) << "%% Error message = '" << error_message << "'";
4045 if (absl::StrContains(error_message, "overflow")) {
4046 SOLVER_LOG(solution_logger, "=====OVERFLOW=====");
4047 } else {
4048 SOLVER_LOG(solution_logger, "=====MODEL INVALID=====");
4049 }
4050 } else {
4051 SOLVER_LOG(solution_logger, "%% TIMEOUT");
4052 }
4053 if (p.display_statistics) {
4054 OutputFlatzincStats(response, solution_logger);
4055 }
4056 }
4057 return response;
4058}
4059
4060} // namespace sat
4061} // namespace operations_research
static Domain FromValues(std::vector< int64_t > values)
Domain AdditionWith(const Domain &domain) const
const std::vector< double > & float_objective_coefficients() const
Definition model.h:387
void AddFloatingPointObjectiveTerm(Variable *var, double coeff)
Definition model.h:393
const std::vector< Variable * > & float_objective_variables() const
Definition model.h:384
const std::vector< Constraint * > & constraints() const
Definition model.h:377
void SetFloatingPointObjectiveOffset(double offset)
Definition model.h:397
const std::vector< Variable * > & variables() const
Definition model.h:376
double float_objective_offset() const
Definition model.h:390
const std::string & name() const
Definition model.h:404
const std::vector< Annotation > & search_annotations() const
Definition model.h:378
Variable * objective() const
Definition model.h:383
::operations_research::sat::BoolArgumentProto *PROTOBUF_NONNULL mutable_bool_and()
::operations_research::sat::BoolArgumentProto *PROTOBUF_NONNULL mutable_exactly_one()
::operations_research::sat::BoolArgumentProto *PROTOBUF_NONNULL mutable_bool_or()
void set_name(Arg_ &&arg, Args_... args)
::operations_research::sat::BoolArgumentProto *PROTOBUF_NONNULL mutable_at_most_one()
const ::operations_research::sat::IntegerVariableProto & variables(int index) const
::operations_research::sat::IntegerVariableProto *PROTOBUF_NONNULL add_variables()
::operations_research::sat::PartialVariableAssignment *PROTOBUF_NONNULL mutable_solution_hint()
::operations_research::sat::ConstraintProto *PROTOBUF_NONNULL add_constraints()
::operations_research::sat::IntegerVariableProto *PROTOBUF_NONNULL mutable_variables(int index)
::operations_research::sat::DecisionStrategyProto *PROTOBUF_NONNULL add_search_strategy()
::operations_research::sat::CpSolverStatus status() const
void set_name(Arg_ &&arg, Args_... args)
T Add(std::function< T(Model *)> f)
Definition model.h:104
static constexpr SearchBranching AUTOMATIC_SEARCH
static constexpr SearchBranching FIXED_SEARCH
ABSL_FLAG(int64_t, fz_int_max, int64_t{1}<< 40, "Default max value for unbounded integer variables.")
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:55
ReverseView< Container > reversed_view(const Container &c)
std::string ValidateCpModel(const CpModelProto &model, bool after_presolve)
std::function< SatParameters(Model *)> NewSatParameters(absl::string_view params)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
CpSolverResponse SolveFzWithCpModelProto(const fz::Model &fz_model, const fz::FlatzincSatParameters &p, const SatParameters &sat_params, Model *sat_model, SolverLogger *solution_logger)
CpSolverResponse SolveCpModel(const CpModelProto &model_proto, Model *model)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
std::function< void(Model *)> NewFeasibleSolutionObserver(const std::function< void(const CpSolverResponse &response)> &callback)
void ProcessFloatingPointOVariablesAndObjective(fz::Model *fz_model)
OR-Tools root namespace.
ClosedInterval::Iterator end(ClosedInterval interval)
int64_t Max() const
Definition model.cc:346
int64_t Min() const
Definition model.cc:340
bool Contains(int64_t value) const
Definition model.cc:363
std::vector< Argument > arguments
Definition model.h:241
std::vector< int64_t > values
Definition model.h:106
std::vector< double > float_values
Definition model.h:114
#define SOLVER_LOG(logger,...)
Definition logging.h:114