Files
ortools-clone/ortools/flatzinc/cp_model_fz_solver.cc
2025-10-16 12:57:23 +02:00

4049 lines
159 KiB
C++

// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "ortools/flatzinc/cp_model_fz_solver.h"
#include <algorithm>
#include <cstdint>
#include <cstdlib>
#include <functional>
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>
#include "absl/algorithm/container.h"
#include "absl/base/no_destructor.h"
#include "absl/container/btree_map.h"
#include "absl/container/btree_set.h"
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/flags/flag.h"
#include "absl/log/check.h"
#include "absl/log/log.h"
#include "absl/strings/match.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_join.h"
#include "absl/types/span.h"
#include "google/protobuf/arena.h"
#include "google/protobuf/text_format.h"
#include "ortools/base/iterator_adaptors.h"
#include "ortools/base/stl_util.h"
#include "ortools/flatzinc/checker.h"
#include "ortools/flatzinc/model.h"
#include "ortools/sat/cp_model.pb.h"
#include "ortools/sat/cp_model_checker.h"
#include "ortools/sat/cp_model_solver.h"
#include "ortools/sat/cp_model_utils.h"
#include "ortools/sat/model.h"
#include "ortools/sat/sat_parameters.pb.h"
#include "ortools/util/logging.h"
#include "ortools/util/sorted_interval_list.h"
ABSL_FLAG(int64_t, fz_int_max, int64_t{1} << 40,
"Default max value for unbounded integer variables.");
ABSL_FLAG(bool, force_interleave_search, false,
"If true, enable interleaved workers when num_workers is 1.");
ABSL_FLAG(bool, fz_use_light_encoding, false,
"Use lighter encodings for the model");
// TODO(user): SetIn forces card to be > 0.
namespace operations_research {
namespace sat {
namespace {
static const int kNoVar = std::numeric_limits<int>::min();
struct VarOrValue {
int var = kNoVar;
int64_t value = 0;
};
struct SetVariable {
std::vector<int> var_indices;
std::vector<int64_t> sorted_values;
int card_var_index = kNoVar;
std::optional<int> fixed_card = std::nullopt;
int find_value_index(int64_t value) const {
const auto it =
std::lower_bound(sorted_values.begin(), sorted_values.end(), value);
if (it == sorted_values.end() || *it != value) return -1;
return it - sorted_values.begin();
}
};
// Helper class to convert a flatzinc model to a CpModelProto.
struct CpModelProtoWithMapping {
CpModelProtoWithMapping(bool sat_enumeration)
: arena(std::make_unique<google::protobuf::Arena>()),
proto(*google::protobuf::Arena::Create<CpModelProto>(arena.get())),
sat_enumeration_(sat_enumeration) {}
// Returns the index of a new boolean variable.
int NewBoolVar();
// Returns the index of a new integer variable.
int NewIntVar(int64_t min_value, int64_t max_value);
// Returns a constant CpModelProto variable created on-demand.
int LookupConstant(int64_t value);
void AddImplication(absl::Span<const int> e, int l) {
ConstraintProto* ct = proto.add_constraints();
for (int e_lit : e) {
ct->add_enforcement_literal(e_lit);
}
ct->mutable_bool_and()->add_literals(l);
}
// Convert a flatzinc argument to a variable or a list of variable.
// Note that we always encode a constant argument with a constant variable.
int LookupVar(const fz::Argument& argument);
LinearExpressionProto LookupExpr(const fz::Argument& argument,
bool negate = false);
LinearExpressionProto LookupExprAt(const fz::Argument& argument, int pos,
bool negate = false);
std::vector<int> LookupVars(const fz::Argument& argument);
VarOrValue LookupVarOrValue(const fz::Argument& argument);
std::vector<VarOrValue> LookupVarsOrValues(const fz::Argument& argument);
// Checks is the domain of the variable is included in [0, 1].
bool VariableIsBoolean(int var) const;
// Set variables.
void ExtractSetConstraint(const fz::Constraint& fz_ct);
bool ConstraintContainsSetVariables(const fz::Constraint& constraint) const;
std::shared_ptr<SetVariable> LookupSetVar(const fz::Argument& argument);
std::shared_ptr<SetVariable> LookupSetVarAt(const fz::Argument& argument,
int pos);
// literal <=> (var op value).
int GetOrCreateEncodingLiteral(int var, int64_t value);
std::optional<int> GetEncodingLiteral(int var, int64_t value);
void AddVarEqValueLiteral(int var, int64_t value, int literal);
void AddVarGeValueLiteral(int var, int64_t value, int literal);
void AddVarGtValueLiteral(int var, int64_t value, int literal);
void AddVarLeValueLiteral(int var, int64_t value, int literal);
void AddVarLtValueLiteral(int var, int64_t value, int literal);
// literal <=> (var1 op var2).
int GetOrCreateLiteralForVarEqVar(int var1, int var2);
void AddVarEqVarLiteral(int var1, int var2, int literal);
std::optional<int> GetVarEqVarLiteral(int var1, int var2);
int GetOrCreateLiteralForVarNeVar(int var1, int var2);
std::optional<int> GetVarNeVarLiteral(int var1, int var2);
void AddVarNeVarLiteral(int var1, int var2, int literal);
int GetOrCreateLiteralForVarLeVar(int var1, int var2);
std::optional<int> GetVarLeVarLiteral(int var1, int var2);
void AddVarLeVarLiteral(int var1, int var2, int literal);
int GetOrCreateLiteralForVarLtVar(int var1, int var2);
std::optional<int> GetVarLtVarLiteral(int var1, int var2);
void AddVarLtVarLiteral(int var1, int var2, int literal);
void AddVarOrVarLiteral(int var1, int var2, int literal);
void AddVarAndVarLiteral(int var1, int var2, int literal);
// Returns the list of literals corresponding to the domain of the variable.
absl::btree_map<int64_t, int> GetFullEncoding(int var);
// Create and return the indices of the IntervalConstraint corresponding
// to the flatzinc "interval" specified by a start var and a size var.
// This method will cache intervals with the key <start, size>.
std::vector<int> CreateIntervals(absl::Span<const VarOrValue> starts,
absl::Span<const VarOrValue> sizes);
// Create and return the indices of the IntervalConstraint corresponding
// to the flatzinc "interval" specified by a start var and a size var.
// This method will cache intervals with the key <start, size>.
// This interval will be optional if the size can be 0.
// It also adds a constraint linking the enforcement literal with the size,
// stating that the interval will be performed if and only if the size is
// greater than 0.
std::vector<int> CreateNonZeroOrOptionalIntervals(
absl::Span<const VarOrValue> starts, absl::Span<const VarOrValue> sizes);
// Create and return the index of the optional IntervalConstraint
// corresponding to the flatzinc "interval" specified by a start var, the
// size_var, and the Boolean opt_var. This method will cache intervals with
// the key <start, size, opt_var>. If opt_var == kNoVar, the interval will not
// be optional.
int GetOrCreateOptionalInterval(VarOrValue start_var, VarOrValue size,
int opt_var);
// Get or Create a literal that implies the variable is > 0.
// Its negation implies the variable is == 0.
int NonZeroLiteralFrom(VarOrValue size);
// Adds a constraint to the model, add the enforcement literal if it is
// different from kNoVar, and returns a ptr to the ConstraintProto.
ConstraintProto* AddEnforcedConstraint(int literal);
// Adds a enforced linear constraint to the model with the given domain.
LinearConstraintProto* AddLinearConstraint(
absl::Span<const int> enforcement_literals, const Domain& domain,
absl::Span<const std::pair<int, int64_t>> terms = {});
// Adds a lex_less{eq} constraint to the model.
void AddLexOrdering(absl::Span<const int> x, absl::Span<const int> y,
int enforcement_literal, bool accepts_equals);
// Enforces that x_array != y_array.
void AddLiteralVectorsNotEqual(absl::Span<const int> x_array,
absl::Span<const int> y_array);
// This one must be called after the domain has been set.
void AddTermToLinearConstraint(int var, int64_t coeff,
LinearConstraintProto* ct);
void FillAMinusBInDomain(const std::vector<int64_t>& domain,
const fz::Constraint& fz_ct, ConstraintProto* ct);
void FillLinearConstraintWithGivenDomain(absl::Span<const int64_t> domain,
const fz::Constraint& fz_ct,
ConstraintProto* ct);
void BuildTableFromDomainIntLinEq(const fz::Constraint& fz_ct,
ConstraintProto* ct);
void FirstPass(const fz::Constraint& fz_ct);
void FillConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void FillReifiedOrImpliedConstraint(const fz::Constraint& fz_ct);
void AddAllEncodingConstraints();
// Methods to handle specific constraints.
void AlwaysFalseConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void BoolClauseConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void BoolXorConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void ArrayBoolXorConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void BoolOrIntLeConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void BoolOrIntGeConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void BoolOrIntLtConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void BoolOrIntGtConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void BoolEqConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void BoolNeConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void IntNeConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void IntLinEqConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void BoolLinEqConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void BoolOrIntLinLeConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct);
void IntLinLtConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void IntLinGeConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void IntLinGtConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void IntLinNeConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void SetCardConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void SetInConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void SetInNegatedConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void IntMinConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void ArrayIntMinConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void IntMaxConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void ArrayIntMaxConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void IntTimesConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void IntAbsConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void IntPlusConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void IntDivConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void IntModConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void ArrayElementConstraint(const fz::Constraint& fz_ct, ConstraintProto* ct);
void OrToolsArrayElementConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct);
void OrToolsTableIntConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct);
void OrToolsRegular(const fz::Constraint& fz_ct, ConstraintProto* ct);
void OrToolsArgMax(const fz::Constraint& fz_ct, ConstraintProto* ct);
void FznAllDifferentInt(const fz::Constraint& fz_ct, ConstraintProto* ct);
void FznValuePrecedeInt(const fz::Constraint& fz_ct, ConstraintProto* ct);
void OrToolsCountEqCst(const fz::Constraint& fz_ct, ConstraintProto* ct);
void OrToolsCountEq(const fz::Constraint& fz_ct, ConstraintProto* ct);
void OrToolsCircuit(const fz::Constraint& fz_ct, ConstraintProto* ct);
void OrToolsInverse(const fz::Constraint& fz_ct, ConstraintProto* ct);
void OrToolsLexOrdering(const fz::Constraint& fz_ct, ConstraintProto* ct);
void OrToolsPrecedeChainInt(const fz::Constraint& fz_ct, ConstraintProto* ct);
void FznDisjunctive(const fz::Constraint& fz_ct, ConstraintProto* ct);
void FznDisjunctiveStrict(const fz::Constraint& fz_ct, ConstraintProto* ct);
void FznCumulative(const fz::Constraint& fz_ct, ConstraintProto* ct);
void OrToolsCumulativeOpt(const fz::Constraint& fz_ct, ConstraintProto* ct);
void OrToolsDisjunctiveStrictOpt(const fz::Constraint& fz_ct,
ConstraintProto* ct);
void FznDiffn(const fz::Constraint& fz_ct, ConstraintProto* ct);
void OrToolsNetworkFlow(const fz::Constraint& fz_ct, ConstraintProto* ct);
void OrToolsBinPacking(const fz::Constraint& fz_ct, ConstraintProto* ct);
void OrToolsBinPackingCapa(const fz::Constraint& fz_ct, ConstraintProto* ct);
void OrToolsBinPackingLoad(const fz::Constraint& fz_ct, ConstraintProto* ct);
void OrToolsNvalue(const fz::Constraint& fz_ct, ConstraintProto* ct);
void OrToolsGlobalCardinality(const fz::Constraint& fz_ct,
ConstraintProto* ct);
void OrToolsGlobalCardinalityLowUp(const fz::Constraint& fz_ct,
ConstraintProto* ct);
// Methods to handle constraints on set variables.
void ArraySetElementConstraint(const fz::Constraint& fz_ct);
void ArrayVarSetElementConstraint(const fz::Constraint& fz_ct);
void FznAllDifferentSetConstraint(const fz::Constraint& fz_ct);
void FznAllDisjointConstraint(const fz::Constraint& fz_ct);
void FznDisjointConstraint(const fz::Constraint& fz_ct);
void FznPartitionSetConstraint(const fz::Constraint& fz_ct);
void SetCardConstraint(const fz::Constraint& fz_ct);
void SetInConstraint(const fz::Constraint& fz_ct);
void SetInReifConstraint(const fz::Constraint& fz_ct);
void SetOpConstraint(const fz::Constraint& fz_ct);
void SetSubSupersetEqConstraint(const fz::Constraint& fz_ct);
void SetNeConstraint(const fz::Constraint& fz_ct);
void SetSubSupersetEqReifConstraint(const fz::Constraint& fz_ct);
void SetLexOrderingConstraint(const fz::Constraint& fz_ct);
// This function takes a list of set variables, normalize them to have all the
// same domain then fill var_booleans[i][j] with the booleans encoding the
// j-th possible value of the i-th set variable.
void PutSetBooleansInCommonDomain(
absl::Span<const std::shared_ptr<SetVariable>> set_vars,
absl::Span<std::vector<int>* const> var_booleans);
// Translates the flatzinc search annotations into the CpModelProto
// search_order field.
void TranslateSearchAnnotations(
absl::Span<const fz::Annotation> search_annotations,
SolverLogger* logger);
// The output proto.
std::unique_ptr<google::protobuf::Arena> arena;
CpModelProto& proto;
SatParameters parameters;
const bool sat_enumeration_;
// Mapping from flatzinc variables to CpModelProto variables.
absl::flat_hash_map<fz::Variable*, int> fz_var_to_index;
absl::flat_hash_map<int64_t, int> constant_value_to_index;
absl::flat_hash_map<std::tuple<int, int64_t, int, int64_t, int>, int>
interval_key_to_index;
absl::flat_hash_map<int, int> var_to_lit_implies_greater_than_zero;
// Var op value encoding literals.
absl::btree_map<int, absl::btree_map<int64_t, int>> encoding_literals;
absl::btree_map<int, absl::btree_map<int64_t, int>> var_le_value_to_literal;
// Var op var encoding literals.
absl::flat_hash_map<std::pair<int, int>, int> var_eq_var_to_literal;
absl::flat_hash_map<std::pair<int, int>, int> var_lt_var_to_literal;
absl::flat_hash_map<std::pair<int, int>, int> var_or_var_to_literal;
absl::flat_hash_map<std::pair<int, int>, int> var_and_var_to_literal;
// Set variables.
absl::flat_hash_map<fz::Variable*, std::shared_ptr<SetVariable>>
set_variables;
// Statistics.
int num_var_op_value_reif_cached = 0;
int num_var_op_var_reif_cached = 0;
int num_var_op_var_imp_cached = 0;
int num_full_encodings = 0;
int num_light_encodings = 0;
int num_partial_encodings = 0;
};
int CpModelProtoWithMapping::NewBoolVar() { return NewIntVar(0, 1); }
int CpModelProtoWithMapping::NewIntVar(int64_t min_value, int64_t max_value) {
const int index = proto.variables_size();
FillDomainInProto(min_value, max_value, proto.add_variables());
return index;
}
int CpModelProtoWithMapping::LookupConstant(int64_t value) {
if (constant_value_to_index.contains(value)) {
return constant_value_to_index[value];
}
// Create the constant on the fly.
const int index = NewIntVar(value, value);
constant_value_to_index[value] = index;
return index;
}
int CpModelProtoWithMapping::LookupVar(const fz::Argument& argument) {
if (argument.HasOneValue()) return LookupConstant(argument.Value());
CHECK_EQ(argument.type, fz::Argument::VAR_REF);
return fz_var_to_index[argument.Var()];
}
LinearExpressionProto CpModelProtoWithMapping::LookupExpr(
const fz::Argument& argument, bool negate) {
LinearExpressionProto expr;
if (argument.HasOneValue()) {
const int64_t value = argument.Value();
expr.set_offset(negate ? -value : value);
} else {
expr.add_vars(LookupVar(argument));
expr.add_coeffs(negate ? -1 : 1);
}
return expr;
}
LinearExpressionProto CpModelProtoWithMapping::LookupExprAt(
const fz::Argument& argument, int pos, bool negate) {
LinearExpressionProto expr;
if (argument.HasOneValueAt(pos)) {
const int64_t value = argument.ValueAt(pos);
expr.set_offset(negate ? -value : value);
} else {
expr.add_vars(fz_var_to_index[argument.VarAt(pos)]);
expr.add_coeffs(negate ? -1 : 1);
}
return expr;
}
std::vector<int> CpModelProtoWithMapping::LookupVars(
const fz::Argument& argument) {
std::vector<int> result;
if (argument.type == fz::Argument::VOID_ARGUMENT) return result;
if (argument.type == fz::Argument::INT_LIST) {
for (int64_t value : argument.values) {
result.push_back(LookupConstant(value));
}
} else if (argument.type == fz::Argument::INT_VALUE) {
result.push_back(LookupConstant(argument.Value()));
} else {
CHECK_EQ(argument.type, fz::Argument::VAR_REF_ARRAY);
for (fz::Variable* var : argument.variables) {
CHECK(var != nullptr);
result.push_back(fz_var_to_index[var]);
}
}
return result;
}
VarOrValue CpModelProtoWithMapping::LookupVarOrValue(
const fz::Argument& argument) {
if (argument.type == fz::Argument::INT_VALUE) {
return {kNoVar, argument.Value()};
} else {
CHECK_EQ(argument.type, fz::Argument::VAR_REF);
fz::Variable* var = argument.Var();
CHECK(var != nullptr);
if (var->domain.HasOneValue()) {
return {kNoVar, var->domain.Value()};
} else {
return {fz_var_to_index[var], 0};
}
}
}
std::vector<VarOrValue> CpModelProtoWithMapping::LookupVarsOrValues(
const fz::Argument& argument) {
std::vector<VarOrValue> result;
const int no_var = kNoVar;
if (argument.type == fz::Argument::VOID_ARGUMENT) return result;
if (argument.type == fz::Argument::INT_LIST) {
for (int64_t value : argument.values) {
result.push_back({no_var, value});
}
} else if (argument.type == fz::Argument::INT_VALUE) {
result.push_back({no_var, argument.Value()});
} else {
CHECK_EQ(argument.type, fz::Argument::VAR_REF_ARRAY);
for (fz::Variable* var : argument.variables) {
CHECK(var != nullptr);
if (var->domain.HasOneValue()) {
result.push_back({no_var, var->domain.Value()});
} else {
result.push_back({fz_var_to_index[var], 0});
}
}
}
return result;
}
bool CpModelProtoWithMapping::VariableIsBoolean(int var) const {
if (var < 0) var = NegatedRef(var);
const IntegerVariableProto& var_proto = proto.variables(var);
return var_proto.domain_size() == 2 && var_proto.domain(0) >= 0 &&
var_proto.domain(1) <= 1;
}
bool CpModelProtoWithMapping::ConstraintContainsSetVariables(
const fz::Constraint& constraint) const {
for (const fz::Argument& argument : constraint.arguments) {
for (fz::Variable* var : argument.variables) {
if (var != nullptr && var->domain.is_a_set) return true;
}
}
return false;
}
std::shared_ptr<SetVariable> CpModelProtoWithMapping::LookupSetVar(
const fz::Argument& argument) {
if (argument.type == fz::Argument::VAR_REF) {
CHECK(argument.Var()->domain.is_a_set);
return set_variables[argument.Var()];
} else {
std::shared_ptr<SetVariable> result = std::make_shared<SetVariable>();
const int true_literal = LookupConstant(1);
if (argument.type == fz::Argument::INT_INTERVAL) {
for (int64_t value = argument.values[0]; value <= argument.values[1];
++value) {
result->sorted_values.push_back(value);
result->var_indices.push_back(true_literal);
}
} else if (argument.type == fz::Argument::INT_LIST) {
for (int64_t value : argument.values) {
result->sorted_values.push_back(value);
result->var_indices.push_back(true_literal);
}
} else {
LOG(FATAL) << "LookupSetVar:Unsupported argument type '" << argument.type
<< "'";
}
return result;
}
}
std::shared_ptr<SetVariable> CpModelProtoWithMapping::LookupSetVarAt(
const fz::Argument& argument, int pos) {
if (argument.type == fz::Argument::VAR_REF_ARRAY) {
CHECK(argument.variables[pos]->domain.is_a_set);
return set_variables[argument.variables[pos]];
} else {
LOG(FATAL) << "LookupSetVarAt:Unsupported argument type '" << argument.type
<< "'";
}
}
ConstraintProto* CpModelProtoWithMapping::AddEnforcedConstraint(int literal) {
ConstraintProto* result = proto.add_constraints();
if (literal != kNoVar) {
result->add_enforcement_literal(literal);
}
return result;
}
LinearConstraintProto* CpModelProtoWithMapping::AddLinearConstraint(
absl::Span<const int> enforcement_literals, const Domain& domain,
absl::Span<const std::pair<int, int64_t>> terms) {
ConstraintProto* result = proto.add_constraints();
for (const int literal : enforcement_literals) {
result->add_enforcement_literal(literal);
}
FillDomainInProto(domain, result->mutable_linear());
for (const auto& [var, coeff] : terms) {
AddTermToLinearConstraint(var, coeff, result->mutable_linear());
}
return result->mutable_linear();
}
void CpModelProtoWithMapping::AddTermToLinearConstraint(
int var, int64_t coeff, LinearConstraintProto* ct) {
CHECK(!ct->domain().empty());
if (coeff == 0) return;
if (var >= 0) {
ct->add_vars(var);
ct->add_coeffs(coeff);
} else {
ct->add_vars(NegatedRef(var));
ct->add_coeffs(-coeff);
// We need to update the domain of the constraint.
for (int i = 0; i < ct->domain_size(); ++i) {
const int64_t b = ct->domain(i);
if (b == std::numeric_limits<int64_t>::min() ||
b == std::numeric_limits<int64_t>::max()) {
continue;
}
ct->set_domain(i, b - coeff);
}
}
}
void CpModelProtoWithMapping::AddLexOrdering(absl::Span<const int> x,
absl::Span<const int> y,
int enforcement_literal,
bool accepts_equals) {
const int min_size = std::min(x.size(), y.size());
std::vector<int> hold(min_size + 1); // Variables indices.
hold[0] = enforcement_literal;
for (int i = 1; i < min_size; ++i) hold[i] = NewBoolVar();
const bool hold_sentinel_is_true =
x.size() < y.size() || (x.size() == y.size() && accepts_equals);
hold[min_size] = LookupConstant(hold_sentinel_is_true ? 1 : 0);
if (!sat_enumeration_ && absl::GetFlag(FLAGS_fz_use_light_encoding)) {
// Faster version, but can produce duplicate solutions.
const Domain le_domain(std::numeric_limits<int64_t>::min(), 0);
const Domain lt_domain(std::numeric_limits<int64_t>::min(), -1);
for (int i = 0; i < min_size; ++i) {
// hold[i] => x[i] <= y[i].
AddLinearConstraint({hold[i]}, le_domain, {{x[i], 1}, {y[i], -1}});
// hold[i] => x[i] < y[i] || hold[i + 1]
const int is_lt = NewBoolVar();
AddLinearConstraint({is_lt}, lt_domain, {{x[i], 1}, {y[i], -1}});
ConstraintProto* chain = AddEnforcedConstraint(hold[i]);
chain->mutable_bool_or()->add_literals(is_lt);
chain->mutable_bool_or()->add_literals(hold[i + 1]);
}
++num_light_encodings;
} else {
std::vector<int> is_lt(min_size);
std::vector<int> is_le(min_size);
for (int i = 0; i < min_size; ++i) {
is_le[i] = GetOrCreateLiteralForVarLeVar(x[i], y[i]);
is_lt[i] = GetOrCreateLiteralForVarLtVar(x[i], y[i]);
}
for (int i = 0; i < min_size; ++i) {
// hold[i] => x[i] <= y[i].
AddImplication({hold[i]}, is_le[i]);
// hold[i] => x[i] < y[i] || hold[i + 1]
ConstraintProto* chain = AddEnforcedConstraint(hold[i]);
chain->mutable_bool_or()->add_literals(is_lt[i]);
chain->mutable_bool_or()->add_literals(hold[i + 1]);
// Optimization.
if (i + 1 < min_size) {
// is_lt[i] => no need to look at further hold variables.
AddImplication({is_lt[i]}, NegatedRef(hold[i + 1]));
// Propagate the negation of hold[i] to hold[i + 1] to avoid unnecessary
// branching and disable the chain constraints.
AddImplication({NegatedRef(hold[i])}, NegatedRef(hold[i + 1]));
}
}
}
}
void CpModelProtoWithMapping::AddLiteralVectorsNotEqual(
absl::Span<const int> x_array, absl::Span<const int> y_array) {
const int size = x_array.size();
CHECK_EQ(size, y_array.size());
if (sat_enumeration_ || !absl::GetFlag(FLAGS_fz_use_light_encoding)) {
BoolArgumentProto* at_least_one_different =
proto.add_constraints()->mutable_bool_or();
for (int i = 0; i < x_array.size(); ++i) {
at_least_one_different->add_literals(
GetOrCreateLiteralForVarNeVar(x_array[i], y_array[i]));
}
++num_light_encodings;
} else {
// This version is way faster, but can produce duplicates solution when
// enumerating solutions in a SAT model.
std::vector<int> is_ne(size);
for (int i = 0; i < size; ++i) {
// Using a simple implication is faster, but it forbids the optimization
// of the lex ordering.
is_ne[i] = NewBoolVar();
AddLinearConstraint({is_ne[i]}, Domain(1),
{{x_array[i], 1}, {y_array[i], 1}});
}
std::vector<int> hold(size + 1);
hold[0] = LookupConstant(1);
for (int i = 1; i < size; ++i) hold[i] = NewBoolVar();
hold[size] = LookupConstant(0);
for (int i = 0; i < size; ++i) {
// hold[i] => x[i] != y[i] || hold[i + 1]
ConstraintProto* chain = AddEnforcedConstraint(hold[i]);
chain->mutable_bool_or()->add_literals(is_ne[i]);
chain->mutable_bool_or()->add_literals(hold[i + 1]);
}
}
}
// lit <=> var op value
std::optional<int> CpModelProtoWithMapping::GetEncodingLiteral(int var,
int64_t value) {
CHECK_GE(var, 0);
const IntegerVariableProto& var_proto = proto.variables(var);
const Domain domain = ReadDomainFromProto(var_proto);
// We have a few shortcuts where we do not create an encoding.
// Shortcut 1:Rule out values that are not in the domain.
if (!domain.Contains(value)) return LookupConstant(0);
// Shortcut 2: As we have ruled out values that are not in the domain, we can
// return a true literal if the domain has only one value.
if (domain.Size() == 1) return LookupConstant(1);
// Shortcut 3: The encoding of a Boolean variable is itself.
if (domain.Size() == 2 && domain.Min() == 0 && domain.Max() == 1) {
return value == 1 ? var : NegatedRef(var);
}
const auto it = encoding_literals.find(var);
if (it != encoding_literals.end()) {
const auto it2 = it->second.find(value);
if (it2 != it->second.end()) return it2->second;
}
return std::nullopt;
}
int CpModelProtoWithMapping::GetOrCreateEncodingLiteral(int var,
int64_t value) {
CHECK_GE(var, 0);
const std::optional<int> existing_literal = GetEncodingLiteral(var, value);
if (existing_literal.has_value()) {
CHECK_NE(existing_literal.value(), kNoVar);
return existing_literal.value();
}
const IntegerVariableProto& var_proto = proto.variables(var);
const Domain domain = ReadDomainFromProto(var_proto);
CHECK(domain.Contains(value));
CHECK(domain.Size() > 2 ||
(domain.Size() == 2 && (domain.Min() != 0 || domain.Max() != 1)));
const int lit = NewBoolVar();
if (domain.Size() == 2) { // We fully encode the variable.
encoding_literals[var][domain.Min()] = NegatedRef(lit);
encoding_literals[var][domain.Max()] = lit;
return value == domain.Min() ? NegatedRef(lit) : lit;
} else {
encoding_literals[var][value] = lit;
return lit;
}
}
void CpModelProtoWithMapping::AddVarEqValueLiteral(int var, int64_t value,
int lit) {
CHECK_GE(var, 0);
const std::optional<int> existing_literal = GetEncodingLiteral(var, value);
if (existing_literal.has_value()) {
AddLinearConstraint({}, Domain(0),
{{lit, 1}, {existing_literal.value(), -1}});
++num_var_op_value_reif_cached;
} else {
const IntegerVariableProto& var_proto = proto.variables(var);
const Domain domain = ReadDomainFromProto(var_proto);
CHECK(domain.Contains(value));
if (domain.Size() == 2) {
CHECK(domain.Min() != 0 || domain.Max() != 1);
if (value == domain.Min()) {
encoding_literals[var][domain.Min()] = lit;
encoding_literals[var][domain.Max()] = NegatedRef(lit);
} else {
encoding_literals[var][domain.Min()] = NegatedRef(lit);
encoding_literals[var][domain.Max()] = lit;
}
} else {
encoding_literals[var][value] = lit;
}
}
}
void CpModelProtoWithMapping::AddVarGeValueLiteral(int var, int64_t value,
int lit) {
AddVarLeValueLiteral(var, value - 1, NegatedRef(lit));
}
void CpModelProtoWithMapping::AddVarGtValueLiteral(int var, int64_t value,
int lit) {
AddVarLeValueLiteral(var, value, NegatedRef(lit));
}
void CpModelProtoWithMapping::AddVarLtValueLiteral(int var, int64_t value,
int lit) {
AddVarLeValueLiteral(var, value - 1, lit);
}
void CpModelProtoWithMapping::AddVarLeValueLiteral(int var, int64_t value,
int lit) {
const Domain domain = ReadDomainFromProto(proto.variables(var));
const auto it = var_le_value_to_literal.find(var);
if (it != var_le_value_to_literal.end()) {
const auto it2 = it->second.find(value);
if (it2 != it->second.end()) {
AddLinearConstraint({}, Domain(0), {{lit, 1}, {it2->second, -1}});
++num_var_op_value_reif_cached;
return;
}
}
var_le_value_to_literal[var][value] = lit;
}
absl::btree_map<int64_t, int> CpModelProtoWithMapping::GetFullEncoding(
int var) {
absl::btree_map<int64_t, int> result;
const Domain domain = ReadDomainFromProto(proto.variables(var));
for (int64_t value : domain.Values()) {
result[value] = GetOrCreateEncodingLiteral(var, value);
}
return result;
}
void CpModelProtoWithMapping::AddAllEncodingConstraints() {
const bool light_encoding = absl::GetFlag(FLAGS_fz_use_light_encoding);
for (const auto& [var, encoding] : encoding_literals) {
const Domain domain = ReadDomainFromProto(proto.variables(var));
CHECK(domain.Size() > 2 ||
(domain.Size() == 2 && (domain.Min() != 0 || domain.Max() != 1)));
if (domain.Size() == encoding.size()) ++num_full_encodings;
if (domain.Size() == 2) {
CHECK_EQ(encoding.size(), 2);
const int lit = encoding.at(domain.Max());
CHECK_EQ(encoding.at(domain.Min()), NegatedRef(lit));
AddLinearConstraint({}, Domain(domain.Min()),
{{var, 1}, {lit, domain.Min() - domain.Max()}});
continue;
}
if (domain.Size() == encoding.size() && light_encoding) {
BoolArgumentProto* exo = proto.add_constraints()->mutable_exactly_one();
for (const auto& [value, lit] : encoding) {
exo->add_literals(lit);
AddLinearConstraint({lit}, Domain(value), {{var, 1}});
}
} else if (encoding.size() > domain.Size() / 2 && light_encoding) {
BoolArgumentProto* exo = proto.add_constraints()->mutable_exactly_one();
// We iterate on the domain to make sure we visit all values.
for (const int64_t value : domain.Values()) {
const int lit = GetOrCreateEncodingLiteral(var, value);
exo->add_literals(lit);
AddLinearConstraint({lit}, Domain(value), {{var, 1}});
}
++num_partial_encodings;
} else {
for (const auto& [value, lit] : encoding) {
AddLinearConstraint({lit}, Domain(value), {{var, 1}});
AddLinearConstraint({NegatedRef(lit)}, Domain(value).Complement(),
{{var, 1}});
}
}
}
for (const auto& [var, encoding] : var_le_value_to_literal) {
const Domain domain = ReadDomainFromProto(proto.variables(var));
for (const auto& [value, lit] : encoding) {
// If the value is the min of the max of the domain, it is equivalent to
// the encoding literal of the variable bounds. In that case, we can
// link it to the corresponding encoding literal if it exists.
if (value == domain.Min()) {
const std::optional<int> encoding =
GetEncodingLiteral(var, domain.Min());
if (encoding.has_value()) {
AddLinearConstraint({}, Domain(0),
{{lit, 1}, {encoding.value(), -1}});
++num_var_op_value_reif_cached;
}
}
if (value == domain.Max() - 1 && !light_encoding) {
// In the case of light encoding, !encoding_lit does not propagate.
const std::optional<int> encoding_lit =
GetEncodingLiteral(var, domain.Max());
if (encoding_lit.has_value()) {
AddLinearConstraint(
{}, Domain(0),
{{lit, 1}, {NegatedRef(encoding_lit.value()), -1}});
++num_var_op_value_reif_cached;
continue;
}
}
const Domain le_domain(std::numeric_limits<int64_t>::min(), value);
AddLinearConstraint({lit}, le_domain, {{var, 1}});
AddLinearConstraint({NegatedRef(lit)}, le_domain.Complement(),
{{var, 1}});
}
}
}
// literal <=> var1 op var2
int CpModelProtoWithMapping::GetOrCreateLiteralForVarEqVar(int var1, int var2) {
CHECK_NE(var1, kNoVar);
CHECK_NE(var2, kNoVar);
if (var1 > var2) std::swap(var1, var2);
if (var1 == var2) return LookupConstant(1);
const std::pair<int, int> key = {var1, var2};
const auto it = var_eq_var_to_literal.find(key);
if (it != var_eq_var_to_literal.end()) return it->second;
const int lit = NewBoolVar();
AddVarEqVarLiteral(var1, var2, lit);
return lit;
}
int CpModelProtoWithMapping::GetOrCreateLiteralForVarNeVar(int var1, int var2) {
return NegatedRef(GetOrCreateLiteralForVarEqVar(var1, var2));
}
void CpModelProtoWithMapping::AddVarEqVarLiteral(int var1, int var2, int lit) {
CHECK_NE(var1, kNoVar);
CHECK_NE(var2, kNoVar);
if (var1 == var2) {
AddImplication({}, lit);
return;
}
if (var1 > var2) std::swap(var1, var2);
const std::pair<int, int> key = {var1, var2};
const auto it = var_eq_var_to_literal.find(key);
if (it != var_eq_var_to_literal.end()) {
AddLinearConstraint({}, Domain(0), {{lit, 1}, {it->second, -1}});
++num_var_op_var_reif_cached;
} else {
if (VariableIsBoolean(var1) && VariableIsBoolean(var2)) {
AddLinearConstraint({lit}, Domain(0), {{var1, 1}, {var2, -1}});
AddLinearConstraint({NegatedRef(lit)}, Domain(1), {{var1, 1}, {var2, 1}});
} else {
AddLinearConstraint({lit}, Domain(0), {{var1, 1}, {var2, -1}});
AddLinearConstraint({NegatedRef(lit)}, Domain(0).Complement(),
{{var1, 1}, {var2, -1}});
}
var_eq_var_to_literal[key] = lit;
}
}
void CpModelProtoWithMapping::AddVarNeVarLiteral(int var1, int var2, int lit) {
AddVarEqVarLiteral(var1, var2, NegatedRef(lit));
}
std::optional<int> CpModelProtoWithMapping::GetVarEqVarLiteral(int var1,
int var2) {
CHECK_NE(var1, kNoVar);
CHECK_NE(var2, kNoVar);
if (var1 == var2) return LookupConstant(1);
if (var1 > var2) std::swap(var1, var2);
const std::pair<int, int> key = {var1, var2};
const auto it = var_eq_var_to_literal.find(key);
if (it != var_eq_var_to_literal.end()) return it->second;
return std::nullopt;
}
std::optional<int> CpModelProtoWithMapping::GetVarNeVarLiteral(int var1,
int var2) {
const std::optional<int> lit = GetVarEqVarLiteral(var1, var2);
if (lit.has_value()) return NegatedRef(lit.value());
return std::nullopt;
}
int CpModelProtoWithMapping::GetOrCreateLiteralForVarLtVar(int var1, int var2) {
CHECK_NE(var1, kNoVar);
CHECK_NE(var2, kNoVar);
if (var1 == var2) return LookupConstant(0);
const std::pair<int, int> key = {var1, var2};
const auto it = var_lt_var_to_literal.find(key);
if (it != var_lt_var_to_literal.end()) return it->second;
const int lit = NewBoolVar();
AddVarLtVarLiteral(var1, var2, lit);
return lit;
}
void CpModelProtoWithMapping::AddVarLtVarLiteral(int var1, int var2, int lit) {
CHECK_NE(var1, kNoVar);
CHECK_NE(var2, kNoVar);
if (var1 == var2) {
AddImplication({}, NegatedRef(lit));
return;
}
const std::pair<int, int> key = {var1, var2};
const auto it = var_lt_var_to_literal.find(key);
if (it != var_lt_var_to_literal.end()) {
AddLinearConstraint({}, Domain(0), {{lit, 1}, {it->second, -1}});
++num_var_op_var_reif_cached;
} else {
var_lt_var_to_literal[key] = lit;
if (VariableIsBoolean(var1) && VariableIsBoolean(var2)) {
// bool_var => var1 < var2 => !var1 && var2
BoolArgumentProto* is_lt = AddEnforcedConstraint(lit)->mutable_bool_and();
is_lt->add_literals(NegatedRef(var1));
is_lt->add_literals(var2);
// !bool_var => var1 >= var2 => var1 || !var2
BoolArgumentProto* is_ge =
AddEnforcedConstraint(NegatedRef(lit))->mutable_bool_or();
is_ge->add_literals(var1);
is_ge->add_literals(NegatedRef(var2));
} else {
AddLinearConstraint({lit},
Domain(std::numeric_limits<int64_t>::min(), -1),
{{var1, 1}, {var2, -1}});
AddLinearConstraint({NegatedRef(lit)},
Domain(0, std::numeric_limits<int64_t>::max()),
{{var1, 1}, {var2, -1}});
}
}
}
std::optional<int> CpModelProtoWithMapping::GetVarLtVarLiteral(int var1,
int var2) {
CHECK_NE(var1, kNoVar);
CHECK_NE(var2, kNoVar);
if (var1 == var2) return LookupConstant(0);
const std::pair<int, int> key = {var1, var2};
const auto it = var_lt_var_to_literal.find(key);
if (it != var_lt_var_to_literal.end()) return it->second;
return std::nullopt;
}
int CpModelProtoWithMapping::GetOrCreateLiteralForVarLeVar(int var1, int var2) {
CHECK_NE(var1, kNoVar);
CHECK_NE(var2, kNoVar);
if (var1 == var2) return LookupConstant(1);
return NegatedRef(GetOrCreateLiteralForVarLtVar(var2, var1));
}
void CpModelProtoWithMapping::AddVarLeVarLiteral(int var1, int var2, int lit) {
CHECK_NE(var1, kNoVar);
CHECK_NE(var2, kNoVar);
if (var1 == var2) {
AddImplication({}, lit);
return;
}
AddVarLtVarLiteral(var2, var1, NegatedRef(lit));
}
std::optional<int> CpModelProtoWithMapping::GetVarLeVarLiteral(int var1,
int var2) {
CHECK_NE(var1, kNoVar);
CHECK_NE(var2, kNoVar);
if (var1 == var2) return LookupConstant(1);
const std::optional<int> lit = GetVarLtVarLiteral(var2, var1);
if (lit.has_value()) return NegatedRef(lit.value());
return std::nullopt;
}
void CpModelProtoWithMapping::AddVarOrVarLiteral(int var1, int var2, int lit) {
CHECK_NE(var1, kNoVar);
CHECK_NE(var2, kNoVar);
CHECK(VariableIsBoolean(var1));
CHECK(VariableIsBoolean(var2));
if (var1 == var2) {
AddLinearConstraint({}, Domain(0), {{var1, 1}, {lit, -1}});
return;
}
if (var1 > var2) std::swap(var1, var2);
const std::pair<int, int> key = {var1, var2};
const auto it = var_or_var_to_literal.find(key);
if (it != var_or_var_to_literal.end()) {
AddLinearConstraint({}, Domain(0), {{lit, 1}, {it->second, -1}});
++num_var_op_var_reif_cached;
} else {
AddImplication({var1}, lit);
AddImplication({var2}, lit);
AddImplication({NegatedRef(var1), NegatedRef(var2)}, NegatedRef(lit));
var_or_var_to_literal[key] = lit;
}
}
void CpModelProtoWithMapping::AddVarAndVarLiteral(int var1, int var2, int lit) {
CHECK_NE(var1, kNoVar);
CHECK_NE(var2, kNoVar);
CHECK(VariableIsBoolean(var1));
CHECK(VariableIsBoolean(var2));
if (var1 == var2) {
AddLinearConstraint({}, Domain(0), {{var1, 1}, {lit, -1}});
return;
}
if (var1 > var2) std::swap(var1, var2);
const std::pair<int, int> key = {var1, var2};
const auto it = var_and_var_to_literal.find(key);
if (it != var_and_var_to_literal.end()) {
AddLinearConstraint({}, Domain(0), {{lit, 1}, {it->second, -1}});
++num_var_op_var_reif_cached;
} else {
AddImplication({lit}, var1);
AddImplication({lit}, var2);
AddImplication({var1, var2}, lit);
var_and_var_to_literal[key] = lit;
}
}
int CpModelProtoWithMapping::GetOrCreateOptionalInterval(VarOrValue start,
VarOrValue size,
int opt_var) {
const int interval_index = proto.constraints_size();
const std::tuple<int, int64_t, int, int64_t, int> key =
std::make_tuple(start.var, start.value, size.var, size.value, opt_var);
const auto [it, inserted] =
interval_key_to_index.insert({key, interval_index});
if (!inserted) {
return it->second;
}
if (size.var == kNoVar || start.var == kNoVar) { // Size or start fixed.
auto* interval = AddEnforcedConstraint(opt_var)->mutable_interval();
if (start.var != kNoVar) {
interval->mutable_start()->add_vars(start.var);
interval->mutable_start()->add_coeffs(1);
interval->mutable_end()->add_vars(start.var);
interval->mutable_end()->add_coeffs(1);
} else {
interval->mutable_start()->set_offset(start.value);
interval->mutable_end()->set_offset(start.value);
}
if (size.var != kNoVar) {
interval->mutable_size()->add_vars(size.var);
interval->mutable_size()->add_coeffs(1);
interval->mutable_end()->add_vars(size.var);
interval->mutable_end()->add_coeffs(1);
} else {
interval->mutable_size()->set_offset(size.value);
interval->mutable_end()->set_offset(size.value +
interval->end().offset());
}
return interval_index;
} else { // start and size are variable.
const int end_var = proto.variables_size();
FillDomainInProto(
ReadDomainFromProto(proto.variables(start.var))
.AdditionWith(ReadDomainFromProto(proto.variables(size.var))),
proto.add_variables());
// Create the interval.
auto* interval = AddEnforcedConstraint(opt_var)->mutable_interval();
interval->mutable_start()->add_vars(start.var);
interval->mutable_start()->add_coeffs(1);
interval->mutable_size()->add_vars(size.var);
interval->mutable_size()->add_coeffs(1);
interval->mutable_end()->add_vars(end_var);
interval->mutable_end()->add_coeffs(1);
return interval_index;
}
}
std::vector<int> CpModelProtoWithMapping::CreateIntervals(
absl::Span<const VarOrValue> starts, absl::Span<const VarOrValue> sizes) {
std::vector<int> intervals;
intervals.reserve(starts.size());
for (int i = 0; i < starts.size(); ++i) {
intervals.push_back(
GetOrCreateOptionalInterval(starts[i], sizes[i], kNoVar));
}
return intervals;
}
std::vector<int> CpModelProtoWithMapping::CreateNonZeroOrOptionalIntervals(
absl::Span<const VarOrValue> starts, absl::Span<const VarOrValue> sizes) {
std::vector<int> intervals;
for (int i = 0; i < starts.size(); ++i) {
const int opt_var = NonZeroLiteralFrom(sizes[i]);
intervals.push_back(
GetOrCreateOptionalInterval(starts[i], sizes[i], opt_var));
}
return intervals;
}
int CpModelProtoWithMapping::NonZeroLiteralFrom(VarOrValue size) {
if (size.var == kNoVar) {
return LookupConstant(size.value > 0);
}
const auto it = var_to_lit_implies_greater_than_zero.find(size.var);
if (it != var_to_lit_implies_greater_than_zero.end()) {
return it->second;
}
const IntegerVariableProto& var_proto = proto.variables(size.var);
const Domain domain = ReadDomainFromProto(var_proto);
CHECK_GE(domain.Min(), 0);
if (domain.Min() > 0) return LookupConstant(1);
if (domain.Max() == 0) {
return LookupConstant(0);
}
const int var_greater_than_zero_lit = NewBoolVar();
AddLinearConstraint({var_greater_than_zero_lit},
domain.IntersectionWith({1, domain.Max()}),
{{size.var, 1}});
AddLinearConstraint({NegatedRef(var_greater_than_zero_lit)}, Domain(0),
{{size.var, 1}});
var_to_lit_implies_greater_than_zero[size.var] = var_greater_than_zero_lit;
return var_greater_than_zero_lit;
}
void CpModelProtoWithMapping::FillAMinusBInDomain(
const std::vector<int64_t>& domain, const fz::Constraint& fz_ct,
ConstraintProto* ct) {
auto* arg = ct->mutable_linear();
if (fz_ct.arguments[1].type == fz::Argument::INT_VALUE) {
const int64_t value = fz_ct.arguments[1].Value();
const int var_a = LookupVar(fz_ct.arguments[0]);
for (const int64_t domain_bound : domain) {
if (domain_bound == std::numeric_limits<int64_t>::min() ||
domain_bound == std::numeric_limits<int64_t>::max()) {
arg->add_domain(domain_bound);
} else {
arg->add_domain(domain_bound + value);
}
}
AddTermToLinearConstraint(var_a, 1, arg);
} else if (fz_ct.arguments[0].type == fz::Argument::INT_VALUE) {
const int64_t value = fz_ct.arguments[0].Value();
const int var_b = LookupVar(fz_ct.arguments[1]);
for (int64_t domain_bound : gtl::reversed_view(domain)) {
if (domain_bound == std::numeric_limits<int64_t>::min()) {
arg->add_domain(std::numeric_limits<int64_t>::max());
} else if (domain_bound == std::numeric_limits<int64_t>::max()) {
arg->add_domain(std::numeric_limits<int64_t>::min());
} else {
arg->add_domain(value - domain_bound);
}
}
AddTermToLinearConstraint(var_b, 1, arg);
} else {
for (const int64_t domain_bound : domain) arg->add_domain(domain_bound);
AddTermToLinearConstraint(LookupVar(fz_ct.arguments[0]), 1, arg);
AddTermToLinearConstraint(LookupVar(fz_ct.arguments[1]), -1, arg);
}
}
void CpModelProtoWithMapping::FillLinearConstraintWithGivenDomain(
absl::Span<const int64_t> domain, const fz::Constraint& fz_ct,
ConstraintProto* ct) {
auto* arg = ct->mutable_linear();
for (const int64_t domain_bound : domain) arg->add_domain(domain_bound);
std::vector<int> vars = LookupVars(fz_ct.arguments[1]);
for (int i = 0; i < vars.size(); ++i) {
AddTermToLinearConstraint(vars[i], fz_ct.arguments[0].values[i], arg);
}
}
void CpModelProtoWithMapping::BuildTableFromDomainIntLinEq(
const fz::Constraint& fz_ct, ConstraintProto* ct) {
const std::vector<int64_t>& coeffs = fz_ct.arguments[0].values;
const std::vector<int> vars = LookupVars(fz_ct.arguments[1]);
const int rhs = fz_ct.arguments[2].Value();
CHECK_EQ(coeffs.back(), -1);
for (const int var : vars) {
LinearExpressionProto* expr = ct->mutable_table()->add_exprs();
expr->add_vars(var);
expr->add_coeffs(1);
}
switch (vars.size()) {
case 3: {
const Domain domain0 = ReadDomainFromProto(proto.variables(vars[0]));
const Domain domain1 = ReadDomainFromProto(proto.variables(vars[1]));
for (const int64_t v0 : domain0.Values()) {
for (const int64_t v1 : domain1.Values()) {
const int64_t v2 = coeffs[0] * v0 + coeffs[1] * v1 - rhs;
ct->mutable_table()->add_values(v0);
ct->mutable_table()->add_values(v1);
ct->mutable_table()->add_values(v2);
}
}
break;
}
case 4: {
const Domain domain0 = ReadDomainFromProto(proto.variables(vars[0]));
const Domain domain1 = ReadDomainFromProto(proto.variables(vars[1]));
const Domain domain2 = ReadDomainFromProto(proto.variables(vars[2]));
for (const int64_t v0 : domain0.Values()) {
for (const int64_t v1 : domain1.Values()) {
for (const int64_t v2 : domain2.Values()) {
const int64_t v3 =
coeffs[0] * v0 + coeffs[1] * v1 + coeffs[2] * v2 - rhs;
ct->mutable_table()->add_values(v0);
ct->mutable_table()->add_values(v1);
ct->mutable_table()->add_values(v2);
ct->mutable_table()->add_values(v3);
}
}
}
break;
}
default:
LOG(FATAL) << "Unsupported size";
}
}
void CpModelProtoWithMapping::AlwaysFalseConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
// An empty clause is always false.
ct->mutable_bool_or();
}
void CpModelProtoWithMapping::BoolClauseConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
auto* arg = ct->mutable_bool_or();
for (const int var : LookupVars(fz_ct.arguments[0])) {
arg->add_literals(var);
}
for (const int var : LookupVars(fz_ct.arguments[1])) {
arg->add_literals(NegatedRef(var));
}
}
void CpModelProtoWithMapping::BoolXorConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
// This is not the same semantics as the array_bool_xor as this constraint
// is actually a fully reified xor(a, b) <==> x.
const int a = LookupVar(fz_ct.arguments[0]);
const int b = LookupVar(fz_ct.arguments[1]);
const int x = LookupVar(fz_ct.arguments[2]);
// not(x) => a == b
ct->add_enforcement_literal(NegatedRef(x));
auto* const refute = ct->mutable_linear();
FillDomainInProto(0, refute);
AddTermToLinearConstraint(a, 1, refute);
AddTermToLinearConstraint(b, -1, refute);
// x => a + b == 1
AddLinearConstraint({x}, Domain(1), {{a, 1}, {b, 1}});
}
void CpModelProtoWithMapping::ArrayBoolXorConstraint(
const fz::Constraint& fz_ct, ConstraintProto* ct) {
auto* arg = ct->mutable_bool_xor();
for (const int var : LookupVars(fz_ct.arguments[0])) {
arg->add_literals(var);
}
}
void CpModelProtoWithMapping::BoolOrIntLeConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
FillAMinusBInDomain({std::numeric_limits<int64_t>::min(), 0}, fz_ct, ct);
}
void CpModelProtoWithMapping::BoolOrIntGeConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
FillAMinusBInDomain({0, std::numeric_limits<int64_t>::max()}, fz_ct, ct);
}
void CpModelProtoWithMapping::BoolOrIntLtConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
FillAMinusBInDomain({std::numeric_limits<int64_t>::min(), -1}, fz_ct, ct);
}
void CpModelProtoWithMapping::BoolOrIntGtConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
FillAMinusBInDomain({1, std::numeric_limits<int64_t>::max()}, fz_ct, ct);
}
void CpModelProtoWithMapping::BoolEqConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
FillAMinusBInDomain({0, 0}, fz_ct, ct);
}
void CpModelProtoWithMapping::BoolNeConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
auto* arg = ct->mutable_linear();
FillDomainInProto(1, arg);
AddTermToLinearConstraint(LookupVar(fz_ct.arguments[0]), 1, arg);
AddTermToLinearConstraint(LookupVar(fz_ct.arguments[1]), 1, arg);
}
void CpModelProtoWithMapping::IntNeConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
FillAMinusBInDomain({std::numeric_limits<int64_t>::min(), -1, 1,
std::numeric_limits<int64_t>::max()},
fz_ct, ct);
}
void CpModelProtoWithMapping::IntLinEqConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
// Special case for the index of element 2D and element 3D constraints.
if (fz_ct.strong_propagation && fz_ct.arguments[0].Size() >= 3 &&
fz_ct.arguments[0].Size() <= 4 &&
fz_ct.arguments[0].values.back() == -1) {
BuildTableFromDomainIntLinEq(fz_ct, ct);
} else {
const int64_t rhs = fz_ct.arguments[2].values[0];
FillLinearConstraintWithGivenDomain({rhs, rhs}, fz_ct, ct);
}
}
void CpModelProtoWithMapping::BoolLinEqConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
auto* arg = ct->mutable_linear();
const std::vector<int> vars = LookupVars(fz_ct.arguments[1]);
if (fz_ct.arguments[2].IsVariable()) {
FillDomainInProto(0, arg);
AddTermToLinearConstraint(LookupVar(fz_ct.arguments[2]), -1, arg);
} else {
const int64_t v = fz_ct.arguments[2].Value();
FillDomainInProto(v, arg);
}
for (int i = 0; i < vars.size(); ++i) {
AddTermToLinearConstraint(vars[i], fz_ct.arguments[0].values[i], arg);
}
}
void CpModelProtoWithMapping::BoolOrIntLinLeConstraint(
const fz::Constraint& fz_ct, ConstraintProto* ct) {
const int64_t rhs = fz_ct.arguments[2].values[0];
FillLinearConstraintWithGivenDomain(
{std::numeric_limits<int64_t>::min(), rhs}, fz_ct, ct);
}
void CpModelProtoWithMapping::IntLinLtConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
const int64_t rhs = fz_ct.arguments[2].values[0];
FillLinearConstraintWithGivenDomain(
{std::numeric_limits<int64_t>::min(), rhs - 1}, fz_ct, ct);
}
void CpModelProtoWithMapping::IntLinGeConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
const int64_t rhs = fz_ct.arguments[2].values[0];
FillLinearConstraintWithGivenDomain(
{rhs, std::numeric_limits<int64_t>::max()}, fz_ct, ct);
}
void CpModelProtoWithMapping::IntLinGtConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
const int64_t rhs = fz_ct.arguments[2].values[0];
FillLinearConstraintWithGivenDomain(
{rhs + 1, std::numeric_limits<int64_t>::max()}, fz_ct, ct);
}
void CpModelProtoWithMapping::IntLinNeConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
const int64_t rhs = fz_ct.arguments[2].values[0];
FillLinearConstraintWithGivenDomain(
{std::numeric_limits<int64_t>::min(), rhs - 1, rhs + 1,
std::numeric_limits<int64_t>::max()},
fz_ct, ct);
}
void CpModelProtoWithMapping::SetCardConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
int64_t set_size = 0;
if (fz_ct.arguments[0].type == fz::Argument::INT_LIST) {
set_size = fz_ct.arguments[0].values.size();
} else if (fz_ct.arguments[0].type == fz::Argument::INT_INTERVAL) {
set_size = fz_ct.arguments[0].values[1] - fz_ct.arguments[0].values[0] + 1;
} else {
LOG(FATAL) << "Wrong format" << fz_ct.DebugString();
}
auto* arg = ct->mutable_linear();
FillDomainInProto(set_size, arg);
AddTermToLinearConstraint(LookupVar(fz_ct.arguments[1]), 1, arg);
}
void CpModelProtoWithMapping::SetInConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
auto* arg = ct->mutable_linear();
arg->add_vars(LookupVar(fz_ct.arguments[0]));
arg->add_coeffs(1);
if (fz_ct.arguments[1].type == fz::Argument::INT_LIST) {
FillDomainInProto(Domain::FromValues(std::vector<int64_t>{
fz_ct.arguments[1].values.begin(),
fz_ct.arguments[1].values.end()}),
arg);
} else if (fz_ct.arguments[1].type == fz::Argument::INT_INTERVAL) {
FillDomainInProto(fz_ct.arguments[1].values[0],
fz_ct.arguments[1].values[1], arg);
} else {
LOG(FATAL) << "Wrong format";
}
}
void CpModelProtoWithMapping::SetInNegatedConstraint(
const fz::Constraint& fz_ct, ConstraintProto* ct) {
auto* arg = ct->mutable_linear();
if (fz_ct.arguments[1].type == fz::Argument::INT_LIST) {
FillDomainInProto(Domain::FromValues(std::vector<int64_t>{
fz_ct.arguments[1].values.begin(),
fz_ct.arguments[1].values.end()})
.Complement(),
arg);
} else if (fz_ct.arguments[1].type == fz::Argument::INT_INTERVAL) {
FillDomainInProto(
Domain(fz_ct.arguments[1].values[0], fz_ct.arguments[1].values[1])
.Complement(),
arg);
} else {
LOG(FATAL) << "Wrong format";
}
AddTermToLinearConstraint(LookupVar(fz_ct.arguments[0]), 1, arg);
}
void CpModelProtoWithMapping::IntMinConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
auto* arg = ct->mutable_lin_max();
*arg->add_exprs() = LookupExpr(fz_ct.arguments[0], /*negate=*/true);
*arg->add_exprs() = LookupExpr(fz_ct.arguments[1], /*negate=*/true);
*arg->mutable_target() = LookupExpr(fz_ct.arguments[2], /*negate=*/true);
}
void CpModelProtoWithMapping::ArrayIntMinConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
auto* arg = ct->mutable_lin_max();
*arg->mutable_target() = LookupExpr(fz_ct.arguments[0], /*negate=*/true);
for (int i = 0; i < fz_ct.arguments[1].Size(); ++i) {
*arg->add_exprs() = LookupExprAt(fz_ct.arguments[1], i, /*negate=*/true);
}
}
void CpModelProtoWithMapping::IntMaxConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
auto* arg = ct->mutable_lin_max();
*arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
*arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
*arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
}
void CpModelProtoWithMapping::ArrayIntMaxConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
auto* arg = ct->mutable_lin_max();
*arg->mutable_target() = LookupExpr(fz_ct.arguments[0]);
for (int i = 0; i < fz_ct.arguments[1].Size(); ++i) {
*arg->add_exprs() = LookupExprAt(fz_ct.arguments[1], i);
}
}
void CpModelProtoWithMapping::IntTimesConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
auto* arg = ct->mutable_int_prod();
*arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
*arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
*arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
}
void CpModelProtoWithMapping::IntAbsConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
auto* arg = ct->mutable_lin_max();
*arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
*arg->add_exprs() = LookupExpr(fz_ct.arguments[0], /*negate=*/true);
*arg->mutable_target() = LookupExpr(fz_ct.arguments[1]);
}
void CpModelProtoWithMapping::IntPlusConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
auto* arg = ct->mutable_linear();
FillDomainInProto(0, arg);
AddTermToLinearConstraint(LookupVar(fz_ct.arguments[0]), 1, arg);
AddTermToLinearConstraint(LookupVar(fz_ct.arguments[1]), 1, arg);
AddTermToLinearConstraint(LookupVar(fz_ct.arguments[2]), -1, arg);
}
void CpModelProtoWithMapping::IntDivConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
auto* arg = ct->mutable_int_div();
*arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
*arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
*arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
}
void CpModelProtoWithMapping::IntModConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
auto* arg = ct->mutable_int_mod();
*arg->add_exprs() = LookupExpr(fz_ct.arguments[0]);
*arg->add_exprs() = LookupExpr(fz_ct.arguments[1]);
*arg->mutable_target() = LookupExpr(fz_ct.arguments[2]);
}
void CpModelProtoWithMapping::ArrayElementConstraint(
const fz::Constraint& fz_ct, ConstraintProto* ct) {
auto* arg = ct->mutable_element();
*arg->mutable_linear_index() = LookupExpr(fz_ct.arguments[0]);
if (!absl::EndsWith(fz_ct.type, "_nonshifted")) {
arg->mutable_linear_index()->set_offset(arg->linear_index().offset() - 1);
}
*arg->mutable_linear_target() = LookupExpr(fz_ct.arguments[2]);
for (const VarOrValue elem : LookupVarsOrValues(fz_ct.arguments[1])) {
auto* elem_proto = ct->mutable_element()->add_exprs();
if (elem.var != kNoVar) {
elem_proto->add_vars(elem.var);
elem_proto->add_coeffs(1);
} else {
elem_proto->set_offset(elem.value);
}
}
}
void CpModelProtoWithMapping::OrToolsArrayElementConstraint(
const fz::Constraint& fz_ct, ConstraintProto* ct) {
auto* arg = ct->mutable_element();
// Index.
*arg->mutable_linear_index() = LookupExpr(fz_ct.arguments[0]);
const int64_t index_min = fz_ct.arguments[1].values[0];
arg->mutable_linear_index()->set_offset(arg->linear_index().offset() -
index_min);
// Target.
*arg->mutable_linear_target() = LookupExpr(fz_ct.arguments[3]);
// Expressions.
for (const VarOrValue elem : LookupVarsOrValues(fz_ct.arguments[2])) {
auto* elem_proto = ct->mutable_element()->add_exprs();
if (elem.var != kNoVar) {
elem_proto->add_vars(elem.var);
elem_proto->add_coeffs(1);
} else {
elem_proto->set_offset(elem.value);
}
}
}
void CpModelProtoWithMapping::OrToolsTableIntConstraint(
const fz::Constraint& fz_ct, ConstraintProto* ct) {
auto* arg = ct->mutable_table();
for (const VarOrValue v : LookupVarsOrValues(fz_ct.arguments[0])) {
LinearExpressionProto* expr = arg->add_exprs();
if (v.var != kNoVar) {
expr->add_vars(v.var);
expr->add_coeffs(1);
} else {
expr->set_offset(v.value);
}
}
for (const int64_t value : fz_ct.arguments[1].values) arg->add_values(value);
}
void CpModelProtoWithMapping::OrToolsRegular(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
auto* arg = ct->mutable_automaton();
for (const VarOrValue v : LookupVarsOrValues(fz_ct.arguments[0])) {
LinearExpressionProto* expr = arg->add_exprs();
if (v.var != kNoVar) {
expr->add_vars(v.var);
expr->add_coeffs(1);
} else {
expr->set_offset(v.value);
}
}
int count = 0;
const int num_states = fz_ct.arguments[1].Value();
const int num_values = fz_ct.arguments[2].Value();
for (int i = 1; i <= num_states; ++i) {
for (int j = 1; j <= num_values; ++j) {
CHECK_LT(count, fz_ct.arguments[3].values.size());
const int next = fz_ct.arguments[3].values[count++];
if (next == 0) continue; // 0 is a failing state.
arg->add_transition_tail(i);
arg->add_transition_label(j);
arg->add_transition_head(next);
}
}
arg->set_starting_state(fz_ct.arguments[4].Value());
switch (fz_ct.arguments[5].type) {
case fz::Argument::INT_VALUE: {
arg->add_final_states(fz_ct.arguments[5].values[0]);
break;
}
case fz::Argument::INT_INTERVAL: {
for (int v = fz_ct.arguments[5].values[0];
v <= fz_ct.arguments[5].values[1]; ++v) {
arg->add_final_states(v);
}
break;
}
case fz::Argument::INT_LIST: {
for (const int v : fz_ct.arguments[5].values) {
arg->add_final_states(v);
}
break;
}
default: {
LOG(FATAL) << "Wrong constraint " << fz_ct.DebugString();
}
}
}
void CpModelProtoWithMapping::OrToolsArgMax(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
const std::vector<int> x = LookupVars(fz_ct.arguments[0]);
const int num_vars = x.size();
const int z = LookupVar(fz_ct.arguments[1]);
const int64_t min_index = fz_ct.arguments[2].Value();
const int64_t multiplier = fz_ct.arguments[3].Value();
DCHECK_EQ(std::abs(multiplier), 1);
int64_t min_range = std::numeric_limits<int64_t>::max();
int64_t max_range = std::numeric_limits<int64_t>::min();
auto* max_array = ct->mutable_lin_max();
for (int i = 0; i < num_vars; ++i) {
const IntegerVariableProto& var_proto = proto.variables(x[i]);
const int64_t min_var = var_proto.domain(0);
const int64_t max_var = var_proto.domain(var_proto.domain_size() - 1);
const int64_t offset = num_vars - i;
int64_t min_expr_range = min_var * multiplier * num_vars + offset;
int64_t max_expr_range = max_var * multiplier * num_vars + offset;
if (multiplier == -1) std::swap(min_expr_range, max_expr_range);
min_range = std::min(min_range, min_expr_range);
max_range = std::max(max_range, max_expr_range);
auto* exprs = max_array->add_exprs();
exprs->add_vars(x[i]);
exprs->add_coeffs(num_vars * multiplier);
exprs->set_offset(offset);
}
const int max_var = NewIntVar(min_range, max_range);
// (argmax(x) == z) <=> (x[z] == max_i(x[i]))
//
// With tie - breakers :
// (argmax(x) == z) <=>
// (num_vars * x[z] + num_vars - z) = max_i(num_vars * x[i] + num_vars - i)
max_array->mutable_target()->add_vars(max_var);
max_array->mutable_target()->add_coeffs(1);
for (const auto& [value, literal] : GetFullEncoding(z)) {
if (value < min_index || value >= min_index + num_vars) {
AddImplication({}, NegatedRef(literal));
}
const int64_t index = value - min_index;
AddLinearConstraint({literal}, Domain(index - num_vars),
{{x[index], num_vars * multiplier}, {max_var, -1}});
AddLinearConstraint(
{NegatedRef(literal)},
{std::numeric_limits<int64_t>::min(), index - num_vars - 1},
{{x[index], num_vars * multiplier}, {max_var, -1}});
}
}
void CpModelProtoWithMapping::FznAllDifferentInt(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
auto* arg = ct->mutable_all_diff();
for (int i = 0; i < fz_ct.arguments[0].Size(); ++i) {
*arg->add_exprs() = LookupExprAt(fz_ct.arguments[0], i);
}
}
void CpModelProtoWithMapping::FznValuePrecedeInt(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
const int64_t before = fz_ct.arguments[0].Value();
const int64_t after = fz_ct.arguments[1].Value();
const std::vector<int> x = LookupVars(fz_ct.arguments[2]);
if (x.empty()) return;
std::vector<int> hold(x.size());
hold[0] = LookupConstant(0);
for (int i = 1; i < x.size(); ++i) hold[i] = NewBoolVar();
if (absl::GetFlag(FLAGS_fz_use_light_encoding) && !sat_enumeration_) {
for (int i = 0; i < x.size(); ++i) {
const int is_before = GetOrCreateEncodingLiteral(x[i], before);
if (i + 1 < x.size()) {
AddImplication({is_before}, hold[i + 1]);
AddLinearConstraint({NegatedRef(is_before)}, Domain(0),
{{hold[i], 1}, {hold[i + 1], -1}});
}
AddLinearConstraint({NegatedRef(hold[i])}, Domain(after).Complement(),
{{x[i], 1}});
}
++num_light_encodings;
} else {
for (int i = 0; i < x.size(); ++i) {
const int is_before = GetOrCreateEncodingLiteral(x[i], before);
const int is_after = GetOrCreateEncodingLiteral(x[i], after);
if (i + 1 < x.size()) {
AddImplication({is_before}, hold[i + 1]);
AddLinearConstraint({NegatedRef(is_before)}, Domain(0),
{{hold[i], 1}, {hold[i + 1], -1}});
}
AddImplication({NegatedRef(hold[i])}, NegatedRef(is_after));
}
}
}
void CpModelProtoWithMapping::OrToolsCountEqCst(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
const std::vector<VarOrValue> counts = LookupVarsOrValues(fz_ct.arguments[0]);
const int64_t value = fz_ct.arguments[1].Value();
const VarOrValue target = LookupVarOrValue(fz_ct.arguments[2]);
LinearConstraintProto* arg = ct->mutable_linear();
int64_t fixed_contributions = 0;
for (const VarOrValue& count : counts) {
if (count.var == kNoVar) {
fixed_contributions += count.value == value ? 1 : 0;
}
}
if (target.var == kNoVar) {
FillDomainInProto(target.value - fixed_contributions, arg);
} else {
FillDomainInProto(-fixed_contributions, arg);
AddTermToLinearConstraint(target.var, -1, arg);
}
for (const VarOrValue& count : counts) {
if (count.var != kNoVar) {
AddTermToLinearConstraint(GetOrCreateEncodingLiteral(count.var, value), 1,
arg);
}
}
}
void CpModelProtoWithMapping::OrToolsCountEq(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
const std::vector<VarOrValue> counts = LookupVarsOrValues(fz_ct.arguments[0]);
const int var = LookupVar(fz_ct.arguments[1]);
const VarOrValue target = LookupVarOrValue(fz_ct.arguments[2]);
LinearConstraintProto* arg = ct->mutable_linear();
if (target.var == kNoVar) {
FillDomainInProto(target.value, arg);
} else {
FillDomainInProto(0, arg);
AddTermToLinearConstraint(target.var, -1, arg);
}
for (const VarOrValue& count : counts) {
const int bool_var = count.var == kNoVar
? GetOrCreateEncodingLiteral(var, count.value)
: GetOrCreateLiteralForVarEqVar(var, count.var);
AddTermToLinearConstraint(bool_var, 1, arg);
}
}
void CpModelProtoWithMapping::OrToolsCircuit(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
const int64_t min_index = fz_ct.arguments[1].Value();
const int size = std::max(fz_ct.arguments[0].values.size(),
fz_ct.arguments[0].variables.size());
const int64_t max_index = min_index + size - 1;
// The arc-based mutable circuit.
auto* circuit_arg = ct->mutable_circuit();
// We fully encode all variables so we can use the literal based circuit.
// TODO(user): avoid fully encoding more than once?
int64_t index = min_index;
const bool is_circuit = (fz_ct.type == "ortools_circuit");
for (const int var : LookupVars(fz_ct.arguments[0])) {
Domain domain = ReadDomainFromProto(proto.variables(var));
// Restrict the domain of var to [min_index, max_index]
domain = domain.IntersectionWith({min_index, max_index});
if (is_circuit) {
// We simply make sure that the variable cannot take the value index.
domain = domain.IntersectionWith(Domain::FromIntervals(
{{std::numeric_limits<int64_t>::min(), index - 1},
{index + 1, std::numeric_limits<int64_t>::max()}}));
}
FillDomainInProto(domain, proto.mutable_variables(var));
for (const ClosedInterval interval : domain) {
for (int64_t value = interval.start; value <= interval.end; ++value) {
// Create one Boolean variable for this arc.
const int literal = proto.variables_size();
{
auto* new_var = proto.add_variables();
FillDomainInProto(0, 1, new_var);
}
// Add the arc.
circuit_arg->add_tails(index);
circuit_arg->add_heads(value);
circuit_arg->add_literals(literal);
AddLinearConstraint({literal}, Domain(value), {{var, 1}});
AddLinearConstraint({NegatedRef(literal)}, Domain(value).Complement(),
{{var, 1}});
}
}
++index;
}
}
void CpModelProtoWithMapping::OrToolsInverse(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
auto* arg = ct->mutable_inverse();
const auto direct_variables = LookupVars(fz_ct.arguments[0]);
const auto inverse_variables = LookupVars(fz_ct.arguments[1]);
const int base_direct = fz_ct.arguments[2].Value();
const int base_inverse = fz_ct.arguments[3].Value();
CHECK_EQ(direct_variables.size(), inverse_variables.size());
const int num_variables = direct_variables.size();
const int end_direct = base_direct + num_variables;
const int end_inverse = base_inverse + num_variables;
// Any convention that maps the "fixed values" to the one of the inverse and
// back works. We decided to follow this one:
// There are 3 cases:
// (A) base_direct == base_inverse, we fill the arrays
// direct = [0, .., base_direct - 1] U [direct_vars]
// inverse = [0, .., base_direct - 1] U [inverse_vars]
// (B) base_direct == base_inverse + offset (> 0), we fill the arrays
// direct = [0, .., base_inverse - 1] U
// [end_inverse, .., end_inverse + offset - 1] U
// [direct_vars]
// inverse = [0, .., base_inverse - 1] U
// [inverse_vars] U
// [base_inverse, .., base_base_inverse + offset - 1]
// (C): base_inverse == base_direct + offset (> 0), we fill the arrays
// direct = [0, .., base_direct - 1] U
// [direct_vars] U
// [base_direct, .., base_direct + offset - 1]
// inverse [0, .., base_direct - 1] U
// [end_direct, .., end_direct + offset - 1] U
// [inverse_vars]
const int arity = std::max(base_inverse, base_direct) + num_variables;
for (int i = 0; i < arity; ++i) {
// Fill the direct array.
if (i < base_direct) {
if (i < base_inverse) {
arg->add_f_direct(LookupConstant(i));
} else if (i >= base_inverse) {
arg->add_f_direct(LookupConstant(i + num_variables));
}
} else if (i >= base_direct && i < end_direct) {
arg->add_f_direct(direct_variables[i - base_direct]);
} else {
arg->add_f_direct(LookupConstant(i - num_variables));
}
// Fill the inverse array.
if (i < base_inverse) {
if (i < base_direct) {
arg->add_f_inverse(LookupConstant(i));
} else if (i >= base_direct) {
arg->add_f_inverse(LookupConstant(i + num_variables));
}
} else if (i >= base_inverse && i < end_inverse) {
arg->add_f_inverse(inverse_variables[i - base_inverse]);
} else {
arg->add_f_inverse(LookupConstant(i - num_variables));
}
}
}
void CpModelProtoWithMapping::OrToolsLexOrdering(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
const std::vector<int> x = LookupVars(fz_ct.arguments[0]);
const std::vector<int> y = LookupVars(fz_ct.arguments[1]);
const bool accepts_equals = fz_ct.type == "ortools_lex_lesseq_bool" ||
fz_ct.type == "ortools_lex_lesseq_int";
const int false_literal = LookupConstant(0);
AddLexOrdering(x, y, NegatedRef(false_literal), accepts_equals);
}
void CpModelProtoWithMapping::OrToolsPrecedeChainInt(
const fz::Constraint& fz_ct, ConstraintProto* ct) {
std::vector<int64_t> values;
if (fz_ct.arguments[0].type == fz::Argument::INT_INTERVAL) {
values.reserve(fz_ct.arguments[0].values[1] - fz_ct.arguments[0].values[0] +
1);
for (int64_t value = fz_ct.arguments[0].values[0];
value <= fz_ct.arguments[0].values[1]; ++value) {
values.push_back(value);
}
} else if (fz_ct.arguments[0].type == fz::Argument::INT_LIST) {
values = fz_ct.arguments[0].values;
} else {
LOG(FATAL) << "Unsupported argument type: " << fz_ct.arguments[0].type
<< " for " << fz_ct.arguments[0].DebugString();
}
const std::vector<int> x = LookupVars(fz_ct.arguments[1]);
if (x.empty()) return;
if (values.size() <= 1) return;
std::vector<int> row;
for (int r = 0; r + 1 < values.size(); ++r) {
row.clear();
const int64_t before = values[r];
const int64_t after = values[r + 1];
row.resize(x.size());
for (int i = 0; i < x.size(); ++i) {
row[i] = (i <= r ? LookupConstant(0) : NewBoolVar());
}
for (int i = 0; i < x.size(); ++i) {
const int is_before = GetOrCreateEncodingLiteral(x[i], before);
const int is_after = GetOrCreateEncodingLiteral(x[i], after);
if (i + 1 < x.size()) {
AddImplication({is_before}, row[i + 1]);
AddLinearConstraint({NegatedRef(is_before)}, Domain(0),
{{row[i], 1}, {row[i + 1], -1}});
}
AddImplication({NegatedRef(row[i])}, NegatedRef(is_after));
}
}
}
void CpModelProtoWithMapping::FznDisjunctive(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
const std::vector<VarOrValue> starts = LookupVarsOrValues(fz_ct.arguments[0]);
const std::vector<VarOrValue> sizes = LookupVarsOrValues(fz_ct.arguments[1]);
auto* arg = ct->mutable_cumulative();
arg->mutable_capacity()->set_offset(1);
for (int i = 0; i < starts.size(); ++i) {
arg->add_intervals(
GetOrCreateOptionalInterval(starts[i], sizes[i], kNoVar));
arg->add_demands()->set_offset(1);
}
}
void CpModelProtoWithMapping::FznDisjunctiveStrict(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
const std::vector<VarOrValue> starts = LookupVarsOrValues(fz_ct.arguments[0]);
const std::vector<VarOrValue> sizes = LookupVarsOrValues(fz_ct.arguments[1]);
auto* arg = ct->mutable_no_overlap();
for (int i = 0; i < starts.size(); ++i) {
arg->add_intervals(
GetOrCreateOptionalInterval(starts[i], sizes[i], kNoVar));
}
}
void CpModelProtoWithMapping::FznCumulative(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
const std::vector<VarOrValue> starts = LookupVarsOrValues(fz_ct.arguments[0]);
const std::vector<VarOrValue> sizes = LookupVarsOrValues(fz_ct.arguments[1]);
const std::vector<VarOrValue> demands =
LookupVarsOrValues(fz_ct.arguments[2]);
auto* arg = ct->mutable_cumulative();
if (fz_ct.arguments[3].HasOneValue()) {
arg->mutable_capacity()->set_offset(fz_ct.arguments[3].Value());
} else {
arg->mutable_capacity()->add_vars(LookupVar(fz_ct.arguments[3]));
arg->mutable_capacity()->add_coeffs(1);
}
for (int i = 0; i < starts.size(); ++i) {
// Special case for a 0-1 demand, we mark the interval as optional
// instead and fix the demand to 1.
if (demands[i].var != kNoVar &&
proto.variables(demands[i].var).domain().size() == 2 &&
proto.variables(demands[i].var).domain(0) == 0 &&
proto.variables(demands[i].var).domain(1) == 1 &&
fz_ct.arguments[3].HasOneValue() && fz_ct.arguments[3].Value() == 1) {
arg->add_intervals(
GetOrCreateOptionalInterval(starts[i], sizes[i], demands[i].var));
arg->add_demands()->set_offset(1);
} else {
arg->add_intervals(
GetOrCreateOptionalInterval(starts[i], sizes[i], kNoVar));
LinearExpressionProto* demand = arg->add_demands();
if (demands[i].var == kNoVar) {
demand->set_offset(demands[i].value);
} else {
demand->add_vars(demands[i].var);
demand->add_coeffs(1);
}
}
}
}
void CpModelProtoWithMapping::OrToolsCumulativeOpt(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
const std::vector<int> occurs = LookupVars(fz_ct.arguments[0]);
const std::vector<VarOrValue> starts = LookupVarsOrValues(fz_ct.arguments[1]);
const std::vector<VarOrValue> durations =
LookupVarsOrValues(fz_ct.arguments[2]);
const std::vector<VarOrValue> demands =
LookupVarsOrValues(fz_ct.arguments[3]);
auto* arg = ct->mutable_cumulative();
if (fz_ct.arguments[3].HasOneValue()) {
arg->mutable_capacity()->set_offset(fz_ct.arguments[4].Value());
} else {
arg->mutable_capacity()->add_vars(LookupVar(fz_ct.arguments[4]));
arg->mutable_capacity()->add_coeffs(1);
}
for (int i = 0; i < starts.size(); ++i) {
arg->add_intervals(
GetOrCreateOptionalInterval(starts[i], durations[i], occurs[i]));
LinearExpressionProto* demand = arg->add_demands();
if (demands[i].var == kNoVar) {
demand->set_offset(demands[i].value);
} else {
demand->add_vars(demands[i].var);
demand->add_coeffs(1);
}
}
}
void CpModelProtoWithMapping::OrToolsDisjunctiveStrictOpt(
const fz::Constraint& fz_ct, ConstraintProto* ct) {
const std::vector<int> occurs = LookupVars(fz_ct.arguments[0]);
const std::vector<VarOrValue> starts = LookupVarsOrValues(fz_ct.arguments[1]);
const std::vector<VarOrValue> durations =
LookupVarsOrValues(fz_ct.arguments[2]);
auto* arg = ct->mutable_no_overlap();
for (int i = 0; i < starts.size(); ++i) {
arg->add_intervals(
GetOrCreateOptionalInterval(starts[i], durations[i], occurs[i]));
}
}
void CpModelProtoWithMapping::FznDiffn(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
const bool is_strict = fz_ct.type == "fzn_diffn";
const std::vector<VarOrValue> x = LookupVarsOrValues(fz_ct.arguments[0]);
const std::vector<VarOrValue> y = LookupVarsOrValues(fz_ct.arguments[1]);
const std::vector<VarOrValue> dx = LookupVarsOrValues(fz_ct.arguments[2]);
const std::vector<VarOrValue> dy = LookupVarsOrValues(fz_ct.arguments[3]);
const std::vector<int> x_intervals =
is_strict ? CreateIntervals(x, dx)
: CreateNonZeroOrOptionalIntervals(x, dx);
const std::vector<int> y_intervals =
is_strict ? CreateIntervals(y, dy)
: CreateNonZeroOrOptionalIntervals(y, dy);
auto* arg = ct->mutable_no_overlap_2d();
for (int i = 0; i < x.size(); ++i) {
arg->add_x_intervals(x_intervals[i]);
arg->add_y_intervals(y_intervals[i]);
}
}
void CpModelProtoWithMapping::OrToolsNetworkFlow(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
// Note that we leave ct empty here (with just the name set).
// We simply do a linear encoding of this constraint.
const bool has_cost = fz_ct.type == "ortools_network_flow_cost";
const std::vector<int> flow = LookupVars(fz_ct.arguments[3]);
// Flow conservation constraints.
const int num_nodes = fz_ct.arguments[1].values.size();
const int base_node = fz_ct.arguments[2].Value();
std::vector<std::vector<int>> flows_per_node(num_nodes);
std::vector<std::vector<int>> coeffs_per_node(num_nodes);
const int num_arcs = fz_ct.arguments[0].values.size() / 2;
for (int arc = 0; arc < num_arcs; arc++) {
const int tail = fz_ct.arguments[0].values[2 * arc] - base_node;
const int head = fz_ct.arguments[0].values[2 * arc + 1] - base_node;
if (tail == head) continue;
flows_per_node[tail].push_back(flow[arc]);
coeffs_per_node[tail].push_back(1);
flows_per_node[head].push_back(flow[arc]);
coeffs_per_node[head].push_back(-1);
}
for (int node = 0; node < num_nodes; node++) {
auto* arg =
AddLinearConstraint({}, Domain(fz_ct.arguments[1].values[node]));
for (int i = 0; i < flows_per_node[node].size(); ++i) {
AddTermToLinearConstraint(flows_per_node[node][i],
coeffs_per_node[node][i], arg);
}
}
if (has_cost) {
auto* arg = AddLinearConstraint({}, Domain(0));
for (int arc = 0; arc < num_arcs; arc++) {
AddTermToLinearConstraint(flow[arc], fz_ct.arguments[4].values[arc], arg);
}
AddTermToLinearConstraint(LookupVar(fz_ct.arguments[5]), -1, arg);
}
}
void CpModelProtoWithMapping::OrToolsBinPacking(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
const int capacity = fz_ct.arguments[0].Value();
const std::vector<int> positions = LookupVars(fz_ct.arguments[1]);
CHECK_EQ(fz_ct.arguments[2].type, fz::Argument::INT_LIST);
const std::vector<int64_t> weights = fz_ct.arguments[2].values;
std::vector<absl::btree_map<int64_t, int>> bin_encodings;
absl::btree_set<int64_t> all_bin_indices;
bin_encodings.reserve(positions.size());
for (int p = 0; p < positions.size(); ++p) {
absl::btree_map<int64_t, int> encoding = GetFullEncoding(positions[p]);
for (const auto& [value, literal] : encoding) {
all_bin_indices.insert(value);
}
bin_encodings.push_back(std::move(encoding));
}
for (const int b : all_bin_indices) {
LinearConstraintProto* lin = AddLinearConstraint({}, {0, capacity});
for (int p = 0; p < positions.size(); ++p) {
const auto it = bin_encodings[p].find(b);
if (it != bin_encodings[p].end()) {
AddTermToLinearConstraint(it->second, weights[p], lin);
}
}
}
}
void CpModelProtoWithMapping::OrToolsBinPackingCapa(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
const std::vector<int64_t>& capacities = fz_ct.arguments[0].values;
const std::vector<int> bins = LookupVars(fz_ct.arguments[1]);
CHECK_EQ(fz_ct.arguments[2].type, fz::Argument::INT_INTERVAL);
const int first_bin = fz_ct.arguments[2].values[0];
const int last_bin = fz_ct.arguments[2].values[1];
CHECK_EQ(fz_ct.arguments[3].type, fz::Argument::INT_LIST);
const std::vector<int64_t> weights = fz_ct.arguments[3].values;
std::vector<absl::btree_map<int64_t, int>> bin_encodings;
bin_encodings.reserve(bins.size());
for (int bin = 0; bin < bins.size(); ++bin) {
absl::btree_map<int64_t, int> encoding = GetFullEncoding(bins[bin]);
for (const auto& [value, literal] : encoding) {
if (value < first_bin || value > last_bin) {
AddImplication({}, NegatedRef(literal)); // not a valid index.
}
}
bin_encodings.push_back(std::move(encoding));
}
for (int b = first_bin; b <= last_bin; ++b) {
LinearConstraintProto* lin =
AddLinearConstraint({}, {0, capacities[b - first_bin]});
for (int bin = 0; bin < bins.size(); ++bin) {
const auto it = bin_encodings[bin].find(b);
if (it != bin_encodings[bin].end()) {
AddTermToLinearConstraint(it->second, weights[bin], lin);
}
}
}
}
void CpModelProtoWithMapping::OrToolsBinPackingLoad(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
const std::vector<int>& loads = LookupVars(fz_ct.arguments[0]);
const std::vector<int> positions = LookupVars(fz_ct.arguments[1]);
if (fz_ct.arguments[3].values.empty()) { // No items.
for (const int load : loads) {
AddLinearConstraint({}, {0, 0}, {{load, 1}});
}
return;
}
CHECK_EQ(fz_ct.arguments[2].type, fz::Argument::INT_INTERVAL);
const int first_bin = fz_ct.arguments[2].values[0];
const int last_bin = fz_ct.arguments[2].values[1];
CHECK_EQ(fz_ct.arguments[3].type, fz::Argument::INT_LIST);
const std::vector<int64_t> weights = fz_ct.arguments[3].values;
CHECK_EQ(weights.size(), positions.size());
CHECK_EQ(loads.size(), last_bin - first_bin + 1);
std::vector<absl::btree_map<int64_t, int>> bin_encodings;
bin_encodings.reserve(positions.size());
for (int p = 0; p < positions.size(); ++p) {
absl::btree_map<int64_t, int> encoding = GetFullEncoding(positions[p]);
for (const auto& [value, literal] : encoding) {
if (value < first_bin || value > last_bin) {
AddImplication({}, NegatedRef(literal)); // not a valid index.
}
}
bin_encodings.push_back(std::move(encoding));
}
for (int b = first_bin; b <= last_bin; ++b) {
LinearConstraintProto* lin = AddLinearConstraint({}, Domain(0));
AddTermToLinearConstraint(loads[b - first_bin], -1, lin);
for (int p = 0; p < positions.size(); ++p) {
const auto it = bin_encodings[p].find(b);
if (it != bin_encodings[p].end()) {
AddTermToLinearConstraint(it->second, weights[p], lin);
}
}
}
// Redundant constraint.
int64_t total_load = 0;
for (int64_t weight : weights) {
total_load += weight;
}
LinearConstraintProto* lin = AddLinearConstraint({}, Domain(total_load));
for (int i = 0; i < loads.size(); ++i) {
AddTermToLinearConstraint(loads[i], 1, lin);
}
}
void CpModelProtoWithMapping::OrToolsNvalue(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
const int card = LookupVar(fz_ct.arguments[0]);
const std::vector<int>& x = LookupVars(fz_ct.arguments[1]);
LinearConstraintProto* global_cardinality = ct->mutable_linear();
FillDomainInProto(x.size(), global_cardinality);
absl::btree_map<int64_t, std::vector<int>> value_to_literals;
for (int i = 0; i < x.size(); ++i) {
const absl::btree_map<int64_t, int> encoding = GetFullEncoding(x[i]);
for (const auto& [value, literal] : encoding) {
value_to_literals[value].push_back(literal);
AddTermToLinearConstraint(literal, 1, global_cardinality);
}
}
// Constrain the range of the card variable.
const int64_t max_size = std::min(x.size(), value_to_literals.size());
AddLinearConstraint({}, {1, max_size}, {{card, 1}});
LinearConstraintProto* lin = AddLinearConstraint({}, Domain(0), {{card, -1}});
for (const auto& [value, literals] : value_to_literals) {
const int is_present = NewBoolVar();
AddTermToLinearConstraint(is_present, 1, lin);
BoolArgumentProto* is_present_constraint =
AddEnforcedConstraint(is_present)->mutable_bool_or();
for (const int literal : literals) {
AddImplication({literal}, is_present);
is_present_constraint->add_literals(literal);
}
}
}
void CpModelProtoWithMapping::OrToolsGlobalCardinality(
const fz::Constraint& fz_ct, ConstraintProto* ct) {
const std::vector<int> x = LookupVars(fz_ct.arguments[0]);
CHECK_EQ(fz_ct.arguments[1].type, fz::Argument::INT_LIST);
const std::vector<int64_t>& values = fz_ct.arguments[1].values;
const std::vector<int> cards = LookupVars(fz_ct.arguments[2]);
const bool is_closed = fz_ct.arguments[3].Value() != 0;
const absl::flat_hash_set<int64_t> all_values(values.begin(), values.end());
absl::flat_hash_map<int64_t, std::vector<int>> value_to_literals;
bool exact_cover = true;
for (const int x_var : x) {
const absl::btree_map<int64_t, int> encoding = GetFullEncoding(x_var);
for (const auto& [value, literal] : encoding) {
if (all_values.contains(value)) {
value_to_literals[value].push_back(literal);
} else if (is_closed) {
AddImplication({}, NegatedRef(literal));
} else {
exact_cover = false;
}
}
}
for (int i = 0; i < cards.size(); ++i) {
const int64_t value = values[i];
const int card = cards[i];
LinearConstraintProto* lin =
AddLinearConstraint({}, Domain(0), {{card, -1}});
for (const int literal : value_to_literals[value]) {
AddTermToLinearConstraint(literal, 1, lin);
}
}
if (exact_cover) {
LinearConstraintProto* cover = AddLinearConstraint({}, Domain(x.size()));
for (const int card : cards) {
AddTermToLinearConstraint(card, 1, cover);
}
}
}
void CpModelProtoWithMapping::OrToolsGlobalCardinalityLowUp(
const fz::Constraint& fz_ct, ConstraintProto* ct) {
const std::vector<int> x = LookupVars(fz_ct.arguments[0]);
CHECK(fz_ct.arguments[1].type == fz::Argument::INT_LIST ||
fz_ct.arguments[1].type == fz::Argument::VOID_ARGUMENT);
const std::vector<int64_t>& values = fz_ct.arguments[1].values;
absl::Span<const int64_t> lbs = fz_ct.arguments[2].values;
absl::Span<const int64_t> ubs = fz_ct.arguments[3].values;
const bool is_closed = fz_ct.arguments[4].Value() != 0;
const absl::flat_hash_set<int64_t> all_values(values.begin(), values.end());
absl::flat_hash_map<int64_t, std::vector<int>> value_to_literals;
bool exact_cover = true;
for (const int x_var : x) {
const absl::btree_map<int64_t, int> encoding = GetFullEncoding(x_var);
for (const auto& [value, literal] : encoding) {
if (all_values.contains(value)) {
value_to_literals[value].push_back(literal);
} else if (is_closed) {
AddImplication({}, NegatedRef(literal));
} else {
exact_cover = false;
}
}
}
// Optimization: if sum(lbs) == length(x), then we can reduce ubs to lbs,
// and vice versa. the constraint is redundant.
const int64_t sum_lbs = absl::c_accumulate(lbs, 0);
const int64_t sum_ubs = absl::c_accumulate(ubs, 0);
if (exact_cover && sum_lbs == x.size()) {
ubs = lbs;
} else if (exact_cover && sum_ubs == x.size()) {
lbs = ubs;
}
for (int i = 0; i < values.size(); ++i) {
LinearConstraintProto* lin = AddLinearConstraint({}, {lbs[i], ubs[i]});
for (const int literal : value_to_literals[values[i]]) {
AddTermToLinearConstraint(literal, 1, lin);
}
}
}
using ConstraintToMethodMapType =
absl::flat_hash_map<std::string_view,
void (CpModelProtoWithMapping::*)(const fz::Constraint&,
ConstraintProto*)>;
const ConstraintToMethodMapType& GetConstraintMap() {
using MPMap = CpModelProtoWithMapping;
static const absl::NoDestructor<ConstraintToMethodMapType> kConstraintMap({
{"false_constraint", &MPMap::AlwaysFalseConstraint},
{"bool_clause", &MPMap::BoolClauseConstraint},
{"bool_xor", &MPMap::BoolXorConstraint},
{"array_bool_xor", &MPMap::ArrayBoolXorConstraint},
{"bool_le", &MPMap::BoolOrIntLeConstraint},
{"int_le", &MPMap::BoolOrIntLeConstraint},
{"bool_ge", &MPMap::BoolOrIntGeConstraint},
{"int_ge", &MPMap::BoolOrIntGeConstraint},
{"bool_lt", &MPMap::BoolOrIntLtConstraint},
{"int_lt", &MPMap::BoolOrIntLtConstraint},
{"bool_gt", &MPMap::BoolOrIntGtConstraint},
{"int_gt", &MPMap::BoolOrIntGtConstraint},
{"bool_eq", &MPMap::BoolEqConstraint},
{"int_eq", &MPMap::BoolEqConstraint},
{"bool2int", &MPMap::BoolEqConstraint},
{"bool_ne", &MPMap::BoolNeConstraint},
{"bool_not", &MPMap::BoolNeConstraint},
{"int_ne", &MPMap::IntNeConstraint},
{"int_lin_eq", &MPMap::IntLinEqConstraint},
{"bool_lin_eq", &MPMap::BoolLinEqConstraint},
{"int_lin_le", &MPMap::BoolOrIntLinLeConstraint},
{"bool_lin_le", &MPMap::BoolOrIntLinLeConstraint},
{"int_lin_lt", &MPMap::IntLinLtConstraint},
{"int_lin_ge", &MPMap::IntLinGeConstraint},
{"int_lin_gt", &MPMap::IntLinGtConstraint},
{"int_lin_ne", &MPMap::IntLinNeConstraint},
{"set_card", &MPMap::SetCardConstraint},
{"set_in", &MPMap::SetInConstraint},
{"set_in_negated", &MPMap::SetInNegatedConstraint},
{"int_min", &MPMap::IntMinConstraint},
{"array_int_minimum", &MPMap::ArrayIntMinConstraint},
{"minimum_int", &MPMap::ArrayIntMinConstraint},
{"int_max", &MPMap::IntMaxConstraint},
{"array_int_maximum", &MPMap::ArrayIntMaxConstraint},
{"maximum_int", &MPMap::ArrayIntMaxConstraint},
{"int_times", &MPMap::IntTimesConstraint},
{"int_abs", &MPMap::IntAbsConstraint},
{"int_plus", &MPMap::IntPlusConstraint},
{"int_div", &MPMap::IntDivConstraint},
{"int_mod", &MPMap::IntModConstraint},
{"array_int_element", &MPMap::ArrayElementConstraint},
{"array_bool_element", &MPMap::ArrayElementConstraint},
{"array_var_int_element", &MPMap::ArrayElementConstraint},
{"array_var_bool_element", &MPMap::ArrayElementConstraint},
{"array_int_element_nonshifted", &MPMap::ArrayElementConstraint},
{"ortools_array_int_element", &MPMap::OrToolsArrayElementConstraint},
{"ortools_array_bool_element", &MPMap::OrToolsArrayElementConstraint},
{"ortools_array_var_int_element", &MPMap::OrToolsArrayElementConstraint},
{"ortools_array_var_bool_element", &MPMap::OrToolsArrayElementConstraint},
{"ortools_table_bool", &MPMap::OrToolsTableIntConstraint},
{"ortools_table_int", &MPMap::OrToolsTableIntConstraint},
{"ortools_regular", &MPMap::OrToolsRegular},
{"ortools_arg_max_int", &MPMap::OrToolsArgMax},
{"ortools_arg_max_bool", &MPMap::OrToolsArgMax},
{"fzn_all_different_int", &MPMap::FznAllDifferentInt},
{"fzn_value_precede_int", &MPMap::FznValuePrecedeInt},
{"ortools_count_eq_cst", &MPMap::OrToolsCountEqCst},
{"ortools_count_eq", &MPMap::OrToolsCountEq},
{"ortools_circuit", &MPMap::OrToolsCircuit},
{"ortools_subcircuit", &MPMap::OrToolsCircuit},
{"ortools_inverse", &MPMap::OrToolsInverse},
{"ortools_lex_less_int", &MPMap::OrToolsLexOrdering},
{"ortools_lex_less_bool", &MPMap::OrToolsLexOrdering},
{"ortools_lex_lesseq_int", &MPMap::OrToolsLexOrdering},
{"ortools_lex_lesseq_bool", &MPMap::OrToolsLexOrdering},
{"ortools_precede_chain_int", &MPMap::OrToolsPrecedeChainInt},
{"fzn_disjunctive", &MPMap::FznDisjunctive},
{"fzn_disjunctive_strict", &MPMap::FznDisjunctiveStrict},
{"fzn_cumulative", &MPMap::FznCumulative},
{"ortools_cumulative_opt", &MPMap::OrToolsCumulativeOpt},
{"ortools_disjunctive_strict_opt", &MPMap::OrToolsDisjunctiveStrictOpt},
{"fzn_diffn", &MPMap::FznDiffn},
{"fzn_diffn_nonstrict", &MPMap::FznDiffn},
{"ortools_network_flow", &MPMap::OrToolsNetworkFlow},
{"ortools_network_flow_cost", &MPMap::OrToolsNetworkFlow},
{"ortools_bin_packing", &MPMap::OrToolsBinPacking},
{"ortools_bin_packing_capa", &MPMap::OrToolsBinPackingCapa},
{"ortools_bin_packing_load", &MPMap::OrToolsBinPackingLoad},
{"ortools_nvalue", &MPMap::OrToolsNvalue},
{"ortools_global_cardinality", &MPMap::OrToolsGlobalCardinality},
{"ortools_global_cardinality_low_up",
&MPMap::OrToolsGlobalCardinalityLowUp},
});
return *kConstraintMap;
}
void CpModelProtoWithMapping::FillConstraint(const fz::Constraint& fz_ct,
ConstraintProto* ct) {
auto it = GetConstraintMap().find(fz_ct.type);
if (it != GetConstraintMap().end()) {
auto method_ptr = it->second;
(this->*method_ptr)(fz_ct, ct);
} else {
LOG(FATAL) << " Not supported " << fz_ct.type;
}
}
void CpModelProtoWithMapping::PutSetBooleansInCommonDomain(
absl::Span<const std::shared_ptr<SetVariable>> set_vars,
absl::Span<std::vector<int>* const> var_booleans) {
CHECK_EQ(set_vars.size(), var_booleans.size());
std::vector<int64_t> all_values;
for (const std::shared_ptr<SetVariable>& set_var : set_vars) {
all_values.insert(all_values.end(), set_var->sorted_values.begin(),
set_var->sorted_values.end());
}
gtl::STLSortAndRemoveDuplicates(&all_values);
absl::flat_hash_map<int64_t, int> value_to_index;
for (int i = 0; i < all_values.size(); ++i) {
value_to_index[all_values[i]] = i;
}
const int false_literal = LookupConstant(0);
for (int set_var_index = 0; set_var_index < var_booleans.size();
++set_var_index) {
std::vector<int>* literals = var_booleans[set_var_index];
std::shared_ptr<SetVariable> set_var = set_vars[set_var_index];
*literals = std::vector<int>(all_values.size(), false_literal);
for (int i = 0; i < set_var->sorted_values.size(); ++i) {
(*literals)[value_to_index[set_var->sorted_values[i]]] =
set_var->var_indices[i];
}
}
}
void CpModelProtoWithMapping::FirstPass(const fz::Constraint& fz_ct) {
if (fz_ct.type == "set_card") {
if (!fz_ct.arguments[0].IsVariable() ||
!fz_ct.arguments[0].Var()->domain.is_a_set ||
!fz_ct.arguments[1].HasOneValue()) {
return;
}
std::shared_ptr<SetVariable> set_var = LookupSetVar(fz_ct.arguments[0]);
const int64_t fixed_card = fz_ct.arguments[1].Value();
set_var->fixed_card = fixed_card;
}
}
void CpModelProtoWithMapping::ArraySetElementConstraint(
const fz::Constraint& fz_ct) {
const int index = LookupVar(fz_ct.arguments[0]);
const Domain index_domain = ReadDomainFromProto(proto.variables(index));
const std::shared_ptr<SetVariable> target_var =
LookupSetVar(fz_ct.arguments[2]);
const absl::flat_hash_set<int64_t> target_values(
target_var->sorted_values.begin(), target_var->sorted_values.end());
absl::flat_hash_map<int64_t, std::vector<int64_t>> value_to_supports;
const int64_t min_index = index_domain.Min();
for (const int64_t value : index_domain.Values()) {
const int64_t offset = value - min_index;
bool index_is_valid = true;
const fz::Domain& set_domain = fz_ct.arguments[1].domains[offset];
if (set_domain.is_interval) {
for (int64_t v = set_domain.values[0]; v <= set_domain.values[1]; ++v) {
if (!target_values.contains(v)) {
index_is_valid = false;
break;
}
}
if (!index_is_valid) continue;
for (int64_t v = set_domain.values[0]; v <= set_domain.values[1]; ++v) {
value_to_supports[v].push_back(value);
}
} else {
for (const int64_t v : set_domain.values) {
if (!target_values.contains(v)) {
index_is_valid = false;
break;
}
}
if (!index_is_valid) continue;
for (const int64_t v : set_domain.values) {
value_to_supports[v].push_back(value);
}
}
}
for (int i = 0; i < target_var->sorted_values.size(); ++i) {
const int64_t target_value = target_var->sorted_values[i];
const int target_literal = target_var->var_indices[i];
const auto it = value_to_supports.find(target_value);
if (it == value_to_supports.end()) {
proto.add_constraints()->mutable_bool_and()->add_literals(
NegatedRef(target_literal));
} else if (it->second.size() == index_domain.Size()) {
proto.add_constraints()->mutable_bool_and()->add_literals(target_literal);
} else {
const Domain true_indices = Domain::FromValues(it->second);
AddLinearConstraint({target_literal}, true_indices, {{index, 1}});
AddLinearConstraint({NegatedRef(target_literal)},
true_indices.Complement(), {{index, 1}});
}
}
}
void CpModelProtoWithMapping::ArrayVarSetElementConstraint(
const fz::Constraint& fz_ct) {
const int index = LookupVar(fz_ct.arguments[0]);
const Domain index_domain = ReadDomainFromProto(proto.variables(index));
const int64_t min_index = index_domain.Min();
const std::shared_ptr<SetVariable> target_var =
LookupSetVar(fz_ct.arguments[2]);
absl::btree_map<int64_t, int> target_values_to_literals;
for (int i = 0; i < target_var->sorted_values.size(); ++i) {
target_values_to_literals[target_var->sorted_values[i]] =
target_var->var_indices[i];
}
BoolArgumentProto* exactly_one =
proto.add_constraints()->mutable_exactly_one();
for (const int64_t value : index_domain.Values()) {
const int index_literal = GetOrCreateEncodingLiteral(index, value);
exactly_one->add_literals(index_literal);
std::shared_ptr<SetVariable> set_var =
LookupSetVarAt(fz_ct.arguments[1], value - min_index);
absl::btree_map<int64_t, int> set_values_to_literals;
for (int i = 0; i < set_var->sorted_values.size(); ++i) {
set_values_to_literals[set_var->sorted_values[i]] =
set_var->var_indices[i];
}
for (const auto& [value, set_literal] : set_values_to_literals) {
const auto it = target_values_to_literals.find(value);
if (it == target_values_to_literals.end()) {
// index is selected => set_literal[value] is false.
AddImplication({index_literal}, NegatedRef(set_literal));
} else {
// index is selected => set_literal[value] == target_literal[value].
AddImplication({index_literal, set_literal}, it->second);
AddImplication({index_literal, it->second}, set_literal);
}
}
// Properly handle the case where not all target literals are reached.
for (const auto& [value, target_literal] : target_values_to_literals) {
if (!set_values_to_literals.contains(value)) {
AddImplication({index_literal}, NegatedRef(target_literal));
}
}
}
}
void CpModelProtoWithMapping::FznAllDifferentSetConstraint(
const fz::Constraint& fz_ct) {
const int num_vars = fz_ct.arguments[0].Size();
if (num_vars == 0) return;
std::vector<std::shared_ptr<SetVariable>> set_vars;
set_vars.reserve(num_vars);
for (int i = 0; i < num_vars; ++i) {
set_vars.push_back(LookupSetVarAt(fz_ct.arguments[0], i));
}
std::vector<std::vector<int>> var_booleans(num_vars);
std::vector<std::vector<int>*> var_booleans_ptrs(num_vars);
for (int i = 0; i < set_vars.size(); ++i) {
var_booleans_ptrs[i] = &var_booleans[i];
}
PutSetBooleansInCommonDomain(set_vars, var_booleans_ptrs);
for (int i = 0; i + 1 < num_vars; ++i) {
for (int j = i + 1; j < num_vars; ++j) {
AddLiteralVectorsNotEqual(var_booleans[i], var_booleans[j]);
}
}
}
void CpModelProtoWithMapping::FznAllDisjointConstraint(
const fz::Constraint& fz_ct) {
const int num_vars = fz_ct.arguments[0].Size();
if (num_vars == 0) return;
absl::btree_map<int64_t, std::vector<int>> value_to_candidates;
for (int i = 0; i < num_vars; ++i) {
const std::shared_ptr<SetVariable> set_var =
LookupSetVarAt(fz_ct.arguments[0], i);
for (int j = 0; j < set_var->sorted_values.size(); ++j) {
const int64_t value = set_var->sorted_values[j];
const int literal = set_var->var_indices[j];
value_to_candidates[value].push_back(literal);
}
}
for (const auto& [value, candidates] : value_to_candidates) {
BoolArgumentProto* amo = proto.add_constraints()->mutable_at_most_one();
for (const int literal : candidates) {
amo->add_literals(literal);
}
}
}
void CpModelProtoWithMapping::FznDisjointConstraint(
const fz::Constraint& fz_ct) {
std::vector<int> x_literals, y_literals;
PutSetBooleansInCommonDomain(
{LookupSetVar(fz_ct.arguments[0]), LookupSetVar(fz_ct.arguments[1])},
{&x_literals, &y_literals});
for (int i = 0; i < x_literals.size(); ++i) {
BoolArgumentProto* at_least_one_false =
proto.add_constraints()->mutable_bool_or();
at_least_one_false->add_literals(NegatedRef(x_literals[i]));
at_least_one_false->add_literals(NegatedRef(y_literals[i]));
}
}
void CpModelProtoWithMapping::FznPartitionSetConstraint(
const fz::Constraint& fz_ct) {
const int num_vars = fz_ct.arguments[0].Size();
if (num_vars == 0) return;
absl::flat_hash_set<int64_t> universe;
if (fz_ct.arguments[1].type == fz::Argument::INT_INTERVAL) {
for (int64_t v = fz_ct.arguments[1].values[0];
v <= fz_ct.arguments[1].values[1]; ++v) {
universe.insert(v);
}
} else {
CHECK_EQ(fz_ct.arguments[1].type, fz::Argument::INT_LIST);
universe = absl::flat_hash_set<int64_t>(fz_ct.arguments[1].values.begin(),
fz_ct.arguments[1].values.end());
}
absl::btree_map<int64_t, std::vector<int>> value_to_candidates;
for (int i = 0; i < num_vars; ++i) {
const std::shared_ptr<SetVariable> set_var =
LookupSetVarAt(fz_ct.arguments[0], i);
for (int j = 0; j < set_var->sorted_values.size(); ++j) {
const int64_t value = set_var->sorted_values[j];
const int literal = set_var->var_indices[j];
if (!universe.contains(value)) {
AddImplication({}, NegatedRef(literal));
} else {
value_to_candidates[value].push_back(literal);
}
}
}
for (const auto& [value, candidates] : value_to_candidates) {
BoolArgumentProto* ex1 = proto.add_constraints()->mutable_exactly_one();
for (const int literal : candidates) {
ex1->add_literals(literal);
}
}
}
void CpModelProtoWithMapping::SetCardConstraint(const fz::Constraint& fz_ct) {
std::shared_ptr<SetVariable> set_var = LookupSetVar(fz_ct.arguments[0]);
VarOrValue var_or_value = LookupVarOrValue(fz_ct.arguments[1]);
if (set_var->card_var_index == kNoVar) {
ConstraintProto* ct = proto.add_constraints();
if (var_or_value.var == kNoVar) {
set_var->card_var_index = LookupConstant(var_or_value.value);
FillDomainInProto(var_or_value.value, ct->mutable_linear());
} else {
set_var->card_var_index = var_or_value.var;
FillDomainInProto(0, ct->mutable_linear());
AddTermToLinearConstraint(var_or_value.var, -1, ct->mutable_linear());
}
for (const int bool_var : set_var->var_indices) {
AddTermToLinearConstraint(bool_var, 1, ct->mutable_linear());
}
} else {
if (var_or_value.var == kNoVar) {
AddLinearConstraint({}, Domain(var_or_value.value),
{{set_var->card_var_index, 1}});
} else {
AddLinearConstraint(
{}, Domain(0),
{{set_var->card_var_index, 1}, {var_or_value.var, -1}});
}
}
}
void CpModelProtoWithMapping::SetInConstraint(const fz::Constraint& fz_ct) {
const VarOrValue var_or_value = LookupVarOrValue(fz_ct.arguments[0]);
std::shared_ptr<SetVariable> set_var = LookupSetVar(fz_ct.arguments[1]);
if (var_or_value.var == kNoVar) {
const int index = set_var->find_value_index(var_or_value.value);
// TODO(user): Improve behavior and reporting when failing.
CHECK_NE(index, -1);
AddImplication({}, set_var->var_indices[index]);
} else if (set_var->fixed_card.has_value() &&
set_var->fixed_card.value() == 1) {
// We have a one to one mapping between the set_var and the full encoding
// of the variable.
//
// Cache the mapping of set_var values to literals.
absl::flat_hash_map<int64_t, int> set_value_to_literal;
for (int i = 0; i < set_var->sorted_values.size(); ++i) {
set_value_to_literal[set_var->sorted_values[i]] = set_var->var_indices[i];
}
// Reduce the domain of the target variable.
AddLinearConstraint({}, Domain::FromValues(set_var->sorted_values),
{{var_or_value.var, 1}});
absl::flat_hash_set<int> var_values;
const Domain var_domain =
ReadDomainFromProto(proto.variables(var_or_value.var));
for (const int64_t value : var_domain.Values()) {
const auto it = set_value_to_literal.find(value);
// Non-covered values are fixed to 0 by the above domain reduction.
if (it == set_value_to_literal.end()) continue;
AddVarEqValueLiteral(var_or_value.var, value, it->second);
var_values.insert(value);
}
// Zero out set literals not covered by the full encoding.
for (int i = 0; i < set_var->sorted_values.size(); ++i) {
if (!var_values.contains(set_var->sorted_values[i])) {
AddImplication({}, NegatedRef(set_var->var_indices[i]));
}
}
} else {
// We express `v \in set({v_i}, {b_i})` by N+1 constraints:
// v \in {v_i}
// (v == v_i) => b_i
//
// Reduce the domain of the target variable.
AddLinearConstraint({}, Domain::FromValues(set_var->sorted_values),
{{var_or_value.var, 1}});
// Then propagate any remove from the set domain to the variable domain.
for (int i = 0; i < set_var->sorted_values.size(); ++i) {
AddLinearConstraint({NegatedRef(set_var->var_indices[i])},
{Domain(set_var->sorted_values[i]).Complement()},
{{var_or_value.var, 1}});
}
}
}
void CpModelProtoWithMapping::SetInReifConstraint(const fz::Constraint& fz_ct) {
VarOrValue var_or_value = LookupVarOrValue(fz_ct.arguments[0]);
std::shared_ptr<const SetVariable> set_var = LookupSetVar(fz_ct.arguments[1]);
const int enforcement_literal = LookupVar(fz_ct.arguments[2]);
if (var_or_value.var == kNoVar) {
const int index = set_var->find_value_index(var_or_value.value);
if (index == -1) {
AddImplication({}, NegatedRef(enforcement_literal));
} else {
AddLinearConstraint(
{}, Domain(0),
{{set_var->var_indices[index], 1}, {enforcement_literal, -1}});
}
} else {
// Reduce the domain of the target variable.
Domain set_domain = Domain::FromValues(set_var->sorted_values);
AddLinearConstraint({enforcement_literal}, set_domain,
{{var_or_value.var, 1}});
// Then propagate any remove from the set domain to the variable domain.
// Note that the reif part is an equivalence. We do not want false
// implies var in set (which contains the value of the var).
for (int i = 0; i < set_var->sorted_values.size(); ++i) {
const int64_t value = set_var->sorted_values[i];
const int set_value_literal = set_var->var_indices[i];
// Let's create the literal as we are going to reuse it.
const int not_var_value_literal =
NegatedRef(GetOrCreateEncodingLiteral(var_or_value.var, value));
AddImplication({enforcement_literal, NegatedRef(set_value_literal)},
not_var_value_literal);
AddImplication({NegatedRef(enforcement_literal), set_value_literal},
not_var_value_literal);
}
}
}
void CpModelProtoWithMapping::SetOpConstraint(const fz::Constraint& fz_ct) {
std::vector<int> x_literals, y_literals, r_literals;
PutSetBooleansInCommonDomain(
{LookupSetVar(fz_ct.arguments[0]), LookupSetVar(fz_ct.arguments[1]),
LookupSetVar(fz_ct.arguments[2])},
{&x_literals, &y_literals, &r_literals});
if (fz_ct.type == "set_intersect") {
for (int i = 0; i < x_literals.size(); ++i) {
AddVarAndVarLiteral(x_literals[i], y_literals[i], r_literals[i]);
}
} else if (fz_ct.type == "set_union") {
for (int i = 0; i < x_literals.size(); ++i) {
AddVarOrVarLiteral(x_literals[i], y_literals[i], r_literals[i]);
}
} else if (fz_ct.type == "set_symdiff") {
for (int i = 0; i < x_literals.size(); ++i) {
AddVarEqVarLiteral(x_literals[i], y_literals[i],
NegatedRef(r_literals[i]));
}
} else if (fz_ct.type == "set_diff") {
for (int i = 0; i < x_literals.size(); ++i) {
AddVarLtVarLiteral(y_literals[i], x_literals[i], r_literals[i]);
}
}
}
void CpModelProtoWithMapping::SetSubSupersetEqConstraint(
const fz::Constraint& fz_ct) {
std::vector<int> x_literals, y_literals;
PutSetBooleansInCommonDomain(
{LookupSetVar(fz_ct.arguments[0]), LookupSetVar(fz_ct.arguments[1])},
{&x_literals, &y_literals});
// Implement the comparison logic.
for (int i = 0; i < x_literals.size(); ++i) {
const int x_lit = x_literals[i];
const int y_lit = y_literals[i];
if (fz_ct.type == "set_subset") {
AddImplication({x_lit}, y_lit);
} else if (fz_ct.type == "set_superset") {
AddImplication({y_lit}, x_lit);
} else if (fz_ct.type == "set_eq") {
// We use the linear as it is easier for the presolve.
AddLinearConstraint({}, Domain(0), {{x_lit, 1}, {y_lit, -1}});
} else {
LOG(FATAL) << "Unsupported " << fz_ct.type;
}
}
}
void CpModelProtoWithMapping::SetNeConstraint(const fz::Constraint& fz_ct) {
std::vector<int> x_literals, y_literals;
PutSetBooleansInCommonDomain(
{LookupSetVar(fz_ct.arguments[0]), LookupSetVar(fz_ct.arguments[1])},
{&x_literals, &y_literals});
AddLiteralVectorsNotEqual(x_literals, y_literals);
}
void CpModelProtoWithMapping::SetSubSupersetEqReifConstraint(
const fz::Constraint& fz_ct) {
std::vector<int> x_literals, y_literals;
PutSetBooleansInCommonDomain(
{LookupSetVar(fz_ct.arguments[0]), LookupSetVar(fz_ct.arguments[1])},
{&x_literals, &y_literals});
const int enforcement_literal = LookupVar(fz_ct.arguments[2]);
const int num_values = x_literals.size();
int e = enforcement_literal;
if (fz_ct.type == "set_ne_reif") {
e = NegatedRef(enforcement_literal);
}
BoolArgumentProto* all_true = AddEnforcedConstraint(e)->mutable_bool_and();
BoolArgumentProto* one_false =
AddEnforcedConstraint(NegatedRef(e))->mutable_bool_or();
for (int i = 0; i < num_values; ++i) {
int lit;
if (fz_ct.type == "set_eq_reif" || fz_ct.type == "set_ne_reif") {
lit = GetOrCreateLiteralForVarEqVar(x_literals[i], y_literals[i]);
} else if (fz_ct.type == "set_subset_reif") {
lit = GetOrCreateLiteralForVarLeVar(x_literals[i], y_literals[i]);
} else {
DCHECK_EQ(fz_ct.type, "set_superset_reif");
lit = GetOrCreateLiteralForVarLeVar(y_literals[i], x_literals[i]);
}
all_true->add_literals(lit);
one_false->add_literals(NegatedRef(lit));
}
}
void CpModelProtoWithMapping::SetLexOrderingConstraint(
const fz::Constraint& fz_ct) {
// set_le is tricky. Let's see all possible sets of size four in their
// lexicographical order and their bit representation:
// {} 0000
// {1} 1000
// {1,2} 1100
// {1,2,3} 1110
// {1,2,3,4} 1111
// {1,2,4} 1101
// {1,3} 1010
// {1,3,4} 1011
// {1,4} 1001
// {2} 0100
// {2,3} 0110
// {2,3,4} 0111
// {2,4} 0101
// {3} 0010
// {3,4} 0011
// {4} 0001
//
// The example above clearly show that we cannot simply force the bit
// representation to be in lexicographical order, which would be
// relatively easy to do. The underlying reason is that the empty
// (sub-)set compares before other sets. To work-around this, we define a
// larger bit representation where between each two bits we add a new bit
// saying whether all the bits to its right are zero or not. This way the
// empty set is mapped from 0000 to 10101010, since every time the bits to
// the right are zero. For the example above, we get:
// {} 10101010
// {1} 01101010
// {1,2} 01011010
// {1,2,3} 01010110
// {1,2,3,4} 01010101
// {1,2,4} 01010001
// {1,3} 01000110
// {1,3,4} 01000101
// {1,4} 01000001
// {2} 00011010
// {2,3} 00010110
// {2,3,4} 00010101
// {2,4} 00010001
// {3} 00000110
// {3,4} 00000101
// {4} 00000001
//
// After this trick, we can just apply the reverse lexicographic ordering
// on the bit representation.
std::vector<int> orig_x_literals, orig_y_literals;
PutSetBooleansInCommonDomain(
{LookupSetVar(fz_ct.arguments[0]), LookupSetVar(fz_ct.arguments[1])},
{&orig_x_literals, &orig_y_literals});
std::vector<int> x_literals, y_literals;
for (int set_idx = 0; set_idx < 2; ++set_idx) {
const std::vector<int>& orig_literals =
set_idx == 0 ? orig_x_literals : orig_y_literals;
std::vector<int>& literals = set_idx == 0 ? x_literals : y_literals;
literals.reserve(2 * orig_literals.size());
for (int i = 0; i < orig_literals.size(); ++i) {
const int empty_suffix_lit = NewBoolVar();
literals.push_back(empty_suffix_lit);
literals.push_back(orig_literals[i]);
ConstraintProto* empty_suffix_ct = proto.add_constraints();
ConstraintProto* empty_suffix_ct_rev = proto.add_constraints();
empty_suffix_ct->add_enforcement_literal(empty_suffix_lit);
empty_suffix_ct_rev->mutable_bool_and()->add_literals(empty_suffix_lit);
for (int j = i; j < orig_literals.size(); ++j) {
empty_suffix_ct->mutable_bool_and()->add_literals(
NegatedRef(orig_literals[j]));
empty_suffix_ct_rev->add_enforcement_literal(
NegatedRef(orig_literals[j]));
}
}
}
const bool accept_equals =
fz_ct.type == "set_le" || fz_ct.type == "set_le_reif";
const bool is_reif =
fz_ct.type == "set_le_reif" || fz_ct.type == "set_lt_reif";
const int enforcement_literal =
is_reif ? LookupVar(fz_ct.arguments[2]) : LookupConstant(1);
AddLexOrdering(y_literals, x_literals, enforcement_literal, accept_equals);
if (is_reif) {
// set_le_reif:
// - l => x <= y
// - ~l => y < x
// set_lt_reif:
// - l => x < y
// - ~l => y <= x
AddLexOrdering(x_literals, y_literals, NegatedRef(enforcement_literal),
!accept_equals);
}
}
using ConstraintToSetMethodMapType =
absl::flat_hash_map<std::string_view, void (CpModelProtoWithMapping::*)(
const fz::Constraint&)>;
const ConstraintToSetMethodMapType& GetSetConstraintMap() {
using MPMap = CpModelProtoWithMapping;
static const absl::NoDestructor<ConstraintToSetMethodMapType> kConstraintMap({
{"array_set_element", &MPMap::ArraySetElementConstraint},
{"array_var_set_element", &MPMap::ArrayVarSetElementConstraint},
{"fzn_all_different_set", &MPMap::FznAllDifferentSetConstraint},
{"fzn_all_disjoint", &MPMap::FznAllDisjointConstraint},
{"fzn_disjoint", &MPMap::FznDisjointConstraint},
{"fzn_partition_set", &MPMap::FznPartitionSetConstraint},
{"set_card", &MPMap::SetCardConstraint},
{"set_in", &MPMap::SetInConstraint},
{"set_in_reif", &MPMap::SetInReifConstraint},
{"set_intersect", &MPMap::SetOpConstraint},
{"set_union", &MPMap::SetOpConstraint},
{"set_symdiff", &MPMap::SetOpConstraint},
{"set_diff", &MPMap::SetOpConstraint},
{"set_subset", &MPMap::SetSubSupersetEqConstraint},
{"set_superset", &MPMap::SetSubSupersetEqConstraint},
{"set_eq", &MPMap::SetSubSupersetEqConstraint},
{"set_ne", &MPMap::SetNeConstraint},
{"set_eq_reif", &MPMap::SetSubSupersetEqReifConstraint},
{"set_ne_reif", &MPMap::SetSubSupersetEqReifConstraint},
{"set_subset_reif", &MPMap::SetSubSupersetEqReifConstraint},
{"set_superset_reif", &MPMap::SetSubSupersetEqReifConstraint},
{"set_le", &MPMap::SetLexOrderingConstraint},
{"set_lt", &MPMap::SetLexOrderingConstraint},
{"set_le_reif", &MPMap::SetLexOrderingConstraint},
{"set_lt_reif", &MPMap::SetLexOrderingConstraint},
});
return *kConstraintMap;
}
void CpModelProtoWithMapping::ExtractSetConstraint(
const fz::Constraint& fz_ct) {
auto it = GetSetConstraintMap().find(fz_ct.type);
if (it != GetSetConstraintMap().end()) {
auto method_ptr = it->second;
(this->*method_ptr)(fz_ct);
} else {
LOG(FATAL) << "Not supported " << fz_ct.DebugString();
}
} // NOLINT(readability/fn_size)
void CpModelProtoWithMapping::FillReifiedOrImpliedConstraint(
const fz::Constraint& fz_ct) {
// Start by processing the constraints that are cached.
if (fz_ct.type == "int_eq_reif" || fz_ct.type == "bool_eq_reif") {
VarOrValue left = LookupVarOrValue(fz_ct.arguments[0]);
VarOrValue right = LookupVarOrValue(fz_ct.arguments[1]);
if (left.var == kNoVar) std::swap(left, right);
const int e = LookupVar(fz_ct.arguments[2]);
if (left.var == kNoVar) {
CHECK_EQ(right.var, kNoVar);
if (left.value == right.value) {
AddImplication({}, e);
} else {
AddImplication({}, NegatedRef(e));
}
} else if (right.var == kNoVar) {
AddVarEqValueLiteral(left.var, right.value, e);
} else {
AddVarEqVarLiteral(left.var, right.var, e);
}
} else if (fz_ct.type == "int_eq_imp" || fz_ct.type == "bool_eq_imp") {
VarOrValue left = LookupVarOrValue(fz_ct.arguments[0]);
VarOrValue right = LookupVarOrValue(fz_ct.arguments[1]);
if (left.var == kNoVar) std::swap(left, right);
const int e = LookupVar(fz_ct.arguments[2]);
if (left.var == kNoVar) {
CHECK_EQ(right.var, kNoVar);
if (left.value != right.value) {
AddImplication({}, NegatedRef(e));
}
} else if (right.var == kNoVar) {
const std::optional<int> lit = GetEncodingLiteral(left.var, right.value);
if (lit.has_value()) {
AddImplication({e}, lit.value());
++num_var_op_var_imp_cached;
} else {
AddLinearConstraint({e}, Domain(right.value), {{left.var, 1}});
}
} else {
const std::optional<int> lit = GetVarEqVarLiteral(left.var, right.var);
if (lit.has_value()) {
AddImplication({e}, lit.value());
++num_var_op_var_imp_cached;
} else {
AddLinearConstraint({e}, Domain(0), {{left.var, 1}, {right.var, -1}});
}
}
} else if (fz_ct.type == "int_ne_reif" || fz_ct.type == "bool_ne_reif") {
VarOrValue left = LookupVarOrValue(fz_ct.arguments[0]);
VarOrValue right = LookupVarOrValue(fz_ct.arguments[1]);
if (left.var == kNoVar) std::swap(left, right);
const int e = LookupVar(fz_ct.arguments[2]);
if (left.var == kNoVar) {
CHECK_EQ(right.var, kNoVar);
if (left.value != right.value) {
AddImplication({}, e);
} else {
AddImplication({}, NegatedRef(e));
}
} else if (right.var == kNoVar) {
AddVarEqValueLiteral(left.var, right.value, NegatedRef(e));
} else {
AddVarEqVarLiteral(left.var, right.var, NegatedRef(e));
}
} else if (fz_ct.type == "int_ne_imp" || fz_ct.type == "bool_ne_imp") {
VarOrValue left = LookupVarOrValue(fz_ct.arguments[0]);
VarOrValue right = LookupVarOrValue(fz_ct.arguments[1]);
if (left.var == kNoVar) std::swap(left, right);
const int e = LookupVar(fz_ct.arguments[2]);
if (left.var == kNoVar) {
CHECK_EQ(right.var, kNoVar);
if (left.value == right.value) {
AddImplication({}, NegatedRef(e));
}
} else if (right.var == kNoVar) {
const std::optional<int> lit = GetEncodingLiteral(left.var, right.value);
if (lit.has_value()) {
AddImplication({e}, NegatedRef(lit.value()));
++num_var_op_var_imp_cached;
} else {
AddLinearConstraint({e}, Domain(right.value).Complement(),
{{left.var, 1}});
}
} else {
const std::optional<int> lit = GetVarNeVarLiteral(left.var, right.var);
if (lit.has_value()) {
AddImplication({e}, lit.value());
++num_var_op_var_imp_cached;
} else {
AddLinearConstraint({e}, Domain(0).Complement(),
{{left.var, 1}, {right.var, -1}});
}
}
} else if (fz_ct.type == "int_le_reif" || fz_ct.type == "bool_le_reif") {
VarOrValue left = LookupVarOrValue(fz_ct.arguments[0]);
VarOrValue right = LookupVarOrValue(fz_ct.arguments[1]);
const int e = LookupVar(fz_ct.arguments[2]);
if (left.var == kNoVar) {
if (right.var == kNoVar) {
if (left.value <= right.value) {
AddImplication({}, e);
} else {
AddImplication({}, NegatedRef(e));
}
} else {
AddVarGeValueLiteral(right.var, left.value, e);
}
} else if (right.var == kNoVar) {
AddVarLeValueLiteral(left.var, right.value, e);
} else {
AddVarLeVarLiteral(left.var, right.var, e);
}
} else if (fz_ct.type == "int_le_imp" || fz_ct.type == "bool_le_imp") {
VarOrValue left = LookupVarOrValue(fz_ct.arguments[0]);
VarOrValue right = LookupVarOrValue(fz_ct.arguments[1]);
const int e = LookupVar(fz_ct.arguments[2]);
if (left.var == kNoVar) {
if (right.var == kNoVar) {
if (left.value > right.value) {
AddImplication({}, NegatedRef(e));
}
} else {
AddLinearConstraint({e},
{left.value, std::numeric_limits<int64_t>::max()},
{{right.var, 1}});
}
} else if (right.var == kNoVar) {
AddLinearConstraint({e},
{std::numeric_limits<int64_t>::min(), right.value},
{{left.var, 1}});
} else {
const std::optional<int> lit = GetVarLeVarLiteral(left.var, right.var);
if (lit.has_value()) {
AddImplication({e}, lit.value());
++num_var_op_var_imp_cached;
} else {
AddLinearConstraint({e}, {std::numeric_limits<int64_t>::min(), 0},
{{left.var, 1}, {right.var, -1}});
}
}
} else if (fz_ct.type == "int_ge_reif" || fz_ct.type == "bool_ge_reif") {
VarOrValue left = LookupVarOrValue(fz_ct.arguments[0]);
VarOrValue right = LookupVarOrValue(fz_ct.arguments[1]);
const int e = LookupVar(fz_ct.arguments[2]);
if (left.var == kNoVar) {
if (right.var == kNoVar) {
if (left.value >= right.value) {
AddImplication({}, e);
} else {
AddImplication({}, NegatedRef(e));
}
} else {
AddVarLeValueLiteral(right.var, left.value, e);
}
} else if (right.var == kNoVar) {
AddVarGeValueLiteral(left.var, right.value, e);
} else {
AddVarLeVarLiteral(right.var, left.var, e);
}
} else if (fz_ct.type == "int_lt_reif" || fz_ct.type == "bool_lt_reif") {
VarOrValue left = LookupVarOrValue(fz_ct.arguments[0]);
VarOrValue right = LookupVarOrValue(fz_ct.arguments[1]);
const int e = LookupVar(fz_ct.arguments[2]);
if (left.var == kNoVar) {
if (right.var == kNoVar) {
if (left.value < right.value) {
AddImplication({}, e);
} else {
AddImplication({}, NegatedRef(e));
}
} else {
AddVarGtValueLiteral(right.var, left.value, e);
}
} else if (right.var == kNoVar) {
AddVarLtValueLiteral(left.var, right.value, e);
} else {
AddVarLtVarLiteral(left.var, right.var, e);
}
} else if (fz_ct.type == "int_gt_reif" || fz_ct.type == "bool_gt_reif") {
VarOrValue left = LookupVarOrValue(fz_ct.arguments[0]);
VarOrValue right = LookupVarOrValue(fz_ct.arguments[1]);
const int e = LookupVar(fz_ct.arguments[2]);
if (left.var == kNoVar) {
if (right.var == kNoVar) {
if (left.value < right.value) {
AddImplication({}, e);
} else {
AddImplication({}, NegatedRef(e));
}
} else {
AddVarLtValueLiteral(right.var, left.value, e);
}
} else if (right.var == kNoVar) {
AddVarGtValueLiteral(left.var, right.value, e);
} else {
AddVarLtVarLiteral(right.var, left.var, e);
}
} else if (fz_ct.type == "array_bool_or") {
const std::vector<int> literals = LookupVars(fz_ct.arguments[0]);
const int e = LookupVar(fz_ct.arguments[1]);
switch (literals.size()) {
case 0: {
AddImplication({}, NegatedRef(e));
break;
}
case 1: {
AddLinearConstraint({}, Domain(0), {{literals[0], 1}, {e, -1}});
break;
}
case 2: {
AddVarOrVarLiteral(literals[0], literals[1], e);
break;
}
default: {
ConstraintProto* imply = AddEnforcedConstraint(e);
for (int i = 0; i < literals.size(); ++i) {
imply->mutable_bool_or()->add_literals(literals[i]);
}
ConstraintProto* refute = AddEnforcedConstraint(NegatedRef(e));
for (int i = 0; i < literals.size(); ++i) {
refute->mutable_bool_and()->add_literals(NegatedRef(literals[i]));
}
}
}
} else if (fz_ct.type == "array_bool_and") {
const std::vector<int> literals = LookupVars(fz_ct.arguments[0]);
const int e = LookupVar(fz_ct.arguments[1]);
switch (literals.size()) {
case 0: {
AddImplication({}, e);
break;
}
case 1: {
AddLinearConstraint({}, Domain(0), {{literals[0], 1}, {e, -1}});
break;
}
case 2: {
AddVarAndVarLiteral(literals[0], literals[1], e);
break;
}
default: {
ConstraintProto* imply = AddEnforcedConstraint(e);
for (int i = 0; i < literals.size(); ++i) {
imply->mutable_bool_and()->add_literals(literals[i]);
}
ConstraintProto* refute = AddEnforcedConstraint(NegatedRef(e));
for (int i = 0; i < literals.size(); ++i) {
refute->mutable_bool_or()->add_literals(NegatedRef(literals[i]));
}
}
}
} else {
// Start by adding a non-reified version of the same constraint.
ConstraintProto* ct = proto.add_constraints();
ct->set_name(fz_ct.type);
std::string simplified_type;
if (absl::EndsWith(fz_ct.type, "_reif")) {
// Remove _reif.
simplified_type = fz_ct.type.substr(0, fz_ct.type.size() - 5);
} else if (absl::EndsWith(fz_ct.type, "_imp")) {
// Remove _imp.
simplified_type = fz_ct.type.substr(0, fz_ct.type.size() - 4);
} else {
// Keep name as it is an implicit reified constraint.
simplified_type = fz_ct.type;
}
// We need a copy to be able to change the type of the constraint.
fz::Constraint copy = fz_ct;
copy.type = simplified_type;
// Create the CP-SAT constraint.
FillConstraint(copy, ct);
// In case of reified constraints, the type of the opposite constraint.
std::string negated_type;
// Fill enforcement_literal and set copy.type to the negated constraint.
if (simplified_type == "set_in") {
ct->add_enforcement_literal(LookupVar(fz_ct.arguments[2]));
negated_type = "set_in_negated";
} else if (simplified_type == "bool_eq" || simplified_type == "int_eq") {
ct->add_enforcement_literal(LookupVar(fz_ct.arguments[2]));
negated_type = "int_ne";
} else if (simplified_type == "bool_ne" || simplified_type == "int_ne") {
ct->add_enforcement_literal(LookupVar(fz_ct.arguments[2]));
negated_type = "int_eq";
} else if (simplified_type == "bool_le" || simplified_type == "int_le") {
ct->add_enforcement_literal(LookupVar(fz_ct.arguments[2]));
negated_type = "int_gt";
} else if (simplified_type == "bool_lt" || simplified_type == "int_lt") {
ct->add_enforcement_literal(LookupVar(fz_ct.arguments[2]));
negated_type = "int_ge";
} else if (simplified_type == "bool_ge" || simplified_type == "int_ge") {
ct->add_enforcement_literal(LookupVar(fz_ct.arguments[2]));
negated_type = "int_lt";
} else if (simplified_type == "bool_gt" || simplified_type == "int_gt") {
ct->add_enforcement_literal(LookupVar(fz_ct.arguments[2]));
negated_type = "int_le";
} else if (simplified_type == "int_lin_eq") {
ct->add_enforcement_literal(LookupVar(fz_ct.arguments[3]));
negated_type = "int_lin_ne";
} else if (simplified_type == "int_lin_ne") {
ct->add_enforcement_literal(LookupVar(fz_ct.arguments[3]));
negated_type = "int_lin_eq";
} else if (simplified_type == "int_lin_le") {
ct->add_enforcement_literal(LookupVar(fz_ct.arguments[3]));
negated_type = "int_lin_gt";
} else if (simplified_type == "int_lin_ge") {
ct->add_enforcement_literal(LookupVar(fz_ct.arguments[3]));
negated_type = "int_lin_lt";
} else if (simplified_type == "int_lin_lt") {
ct->add_enforcement_literal(LookupVar(fz_ct.arguments[3]));
negated_type = "int_lin_ge";
} else if (simplified_type == "int_lin_gt") {
ct->add_enforcement_literal(LookupVar(fz_ct.arguments[3]));
negated_type = "int_lin_le";
} else {
LOG(FATAL) << "Unsupported " << simplified_type;
}
// One way implication. We can stop here.
if (absl::EndsWith(fz_ct.type, "_imp")) return;
// Add the other side of the reification because CpModelProto only support
// half reification.
ConstraintProto* negated_ct =
AddEnforcedConstraint(NegatedRef(ct->enforcement_literal(0)));
negated_ct->set_name(fz_ct.type + " (negated)");
copy.type = negated_type;
FillConstraint(copy, negated_ct);
}
}
void CpModelProtoWithMapping::TranslateSearchAnnotations(
absl::Span<const fz::Annotation> search_annotations, SolverLogger* logger) {
std::vector<fz::Annotation> flat_annotations;
for (const fz::Annotation& annotation : search_annotations) {
fz::FlattenAnnotations(annotation, &flat_annotations);
}
// CP-SAT rejects models containing variables duplicated in hints.
absl::flat_hash_set<int> hinted_vars;
for (const fz::Annotation& annotation : flat_annotations) {
if (annotation.IsFunctionCallWithIdentifier("warm_start")) {
CHECK_EQ(2, annotation.annotations.size());
const fz::Annotation& vars = annotation.annotations[0];
const fz::Annotation& values = annotation.annotations[1];
if (vars.type != fz::Annotation::VAR_REF_ARRAY ||
values.type != fz::Annotation::INT_LIST) {
continue;
}
for (int i = 0; i < vars.variables.size(); ++i) {
fz::Variable* fz_var = vars.variables[i];
const int var = fz_var_to_index.at(fz_var);
const int64_t value = values.values[i];
if (hinted_vars.insert(var).second) {
proto.mutable_solution_hint()->add_vars(var);
proto.mutable_solution_hint()->add_values(value);
}
}
} else if (annotation.IsFunctionCallWithIdentifier("int_search") ||
annotation.IsFunctionCallWithIdentifier("bool_search")) {
const std::vector<fz::Annotation>& args = annotation.annotations;
std::vector<fz::Variable*> vars;
args[0].AppendAllVariables(&vars);
DecisionStrategyProto* strategy = proto.add_search_strategy();
for (fz::Variable* v : vars) {
strategy->add_variables(fz_var_to_index.at(v));
}
const fz::Annotation& choose = args[1];
if (choose.id == "input_order") {
strategy->set_variable_selection_strategy(
DecisionStrategyProto::CHOOSE_FIRST);
} else if (choose.id == "first_fail") {
strategy->set_variable_selection_strategy(
DecisionStrategyProto::CHOOSE_MIN_DOMAIN_SIZE);
} else if (choose.id == "anti_first_fail") {
strategy->set_variable_selection_strategy(
DecisionStrategyProto::CHOOSE_MAX_DOMAIN_SIZE);
} else if (choose.id == "smallest") {
strategy->set_variable_selection_strategy(
DecisionStrategyProto::CHOOSE_LOWEST_MIN);
} else if (choose.id == "largest") {
strategy->set_variable_selection_strategy(
DecisionStrategyProto::CHOOSE_HIGHEST_MAX);
} else {
SOLVER_LOG(logger, "Unsupported variable selection strategy '",
choose.id, "', falling back to 'smallest'");
strategy->set_variable_selection_strategy(
DecisionStrategyProto::CHOOSE_LOWEST_MIN);
}
const fz::Annotation& select = args[2];
if (select.id == "indomain_min" || select.id == "indomain") {
strategy->set_domain_reduction_strategy(
DecisionStrategyProto::SELECT_MIN_VALUE);
} else if (select.id == "indomain_max") {
strategy->set_domain_reduction_strategy(
DecisionStrategyProto::SELECT_MAX_VALUE);
} else if (select.id == "indomain_split") {
strategy->set_domain_reduction_strategy(
DecisionStrategyProto::SELECT_LOWER_HALF);
} else if (select.id == "indomain_reverse_split") {
strategy->set_domain_reduction_strategy(
DecisionStrategyProto::SELECT_UPPER_HALF);
} else if (select.id == "indomain_median") {
strategy->set_domain_reduction_strategy(
DecisionStrategyProto::SELECT_MEDIAN_VALUE);
} else {
SOLVER_LOG(logger, "Unsupported value selection strategy '", select.id,
"', falling back to 'indomain_min'");
strategy->set_domain_reduction_strategy(
DecisionStrategyProto::SELECT_MIN_VALUE);
}
} else if (annotation.IsFunctionCallWithIdentifier("set_search")) {
const std::vector<fz::Annotation>& args = annotation.annotations;
std::vector<fz::Variable*> vars;
args[0].AppendAllVariables(&vars);
DecisionStrategyProto* strategy = proto.add_search_strategy();
for (fz::Variable* v : vars) {
std::shared_ptr<SetVariable> set_var = set_variables.at(v);
strategy->mutable_variables()->Add(set_var->var_indices.begin(),
set_var->var_indices.end());
}
const fz::Annotation& choose = args[1];
if (choose.id == "input_order") {
strategy->set_variable_selection_strategy(
DecisionStrategyProto::CHOOSE_FIRST);
} else {
SOLVER_LOG(logger, "Unsupported set variable selection strategy '",
choose.id, "', falling back to 'input_order'");
strategy->set_variable_selection_strategy(
DecisionStrategyProto::CHOOSE_FIRST);
}
const fz::Annotation& select = args[2];
if (select.id == "indomain_min" || select.id == "indomain") {
strategy->set_domain_reduction_strategy(
DecisionStrategyProto::SELECT_MIN_VALUE);
} else if (select.id == "indomain_max") {
strategy->set_domain_reduction_strategy(
DecisionStrategyProto::SELECT_MAX_VALUE);
} else {
SOLVER_LOG(logger, "Unsupported value selection strategy '", select.id,
"', falling back to 'indomain_min'");
strategy->set_domain_reduction_strategy(
DecisionStrategyProto::SELECT_MIN_VALUE);
}
}
}
}
// The format is fixed in the flatzinc specification.
std::string SolutionString(
const fz::SolutionOutputSpecs& output,
const std::function<int64_t(fz::Variable*)>& value_func,
const std::function<std::vector<int64_t>(fz::Variable*)>& set_evaluator,
double objective_value) {
if (output.variable != nullptr && !output.variable->domain.is_float &&
!output.variable->domain.is_a_set) {
const int64_t value = value_func(output.variable);
if (output.display_as_boolean) {
return absl::StrCat(output.name, " = ", value == 1 ? "true" : "false",
";");
} else {
return absl::StrCat(output.name, " = ", value, ";");
}
} else if (output.variable != nullptr && output.variable->domain.is_a_set) {
const std::vector<int64_t> values = set_evaluator(output.variable);
return absl::StrCat(output.name, " = {", absl::StrJoin(values, ","), "};");
} else if (output.variable != nullptr && output.variable->domain.is_float) {
return absl::StrCat(output.name, " = ", objective_value, ";");
} else {
const int bound_size = output.bounds.size();
std::string result =
absl::StrCat(output.name, " = array", bound_size, "d(");
for (int i = 0; i < bound_size; ++i) {
if (output.bounds[i].max_value >= output.bounds[i].min_value) {
absl::StrAppend(&result, output.bounds[i].min_value, "..",
output.bounds[i].max_value, ", ");
} else {
result.append("{},");
}
}
result.append("[");
for (int i = 0; i < output.flat_variables.size(); ++i) {
if (output.flat_variables[i]->domain.is_a_set) {
const std::vector<int64_t> values =
set_evaluator(output.flat_variables[i]);
absl::StrAppend(&result, "{", absl::StrJoin(values, ","), "}");
} else {
const int64_t value = value_func(output.flat_variables[i]);
if (output.display_as_boolean) {
result.append(value ? "true" : "false");
} else {
absl::StrAppend(&result, value);
}
}
if (i != output.flat_variables.size() - 1) {
result.append(", ");
}
}
result.append("]);");
return result;
}
return "";
}
std::string SolutionString(
const fz::Model& model,
const std::function<int64_t(fz::Variable*)>& value_func,
const std::function<std::vector<int64_t>(fz::Variable*)>& set_evaluator,
double objective_value) {
std::string solution_string;
for (const auto& output_spec : model.output()) {
solution_string.append(SolutionString(output_spec, value_func,
set_evaluator, objective_value));
solution_string.append("\n");
}
return solution_string;
}
void OutputFlatzincStats(const CpSolverResponse& response,
SolverLogger* solution_logger) {
SOLVER_LOG(solution_logger,
"%%%mzn-stat: objective=", response.objective_value());
SOLVER_LOG(solution_logger,
"%%%mzn-stat: objectiveBound=", response.best_objective_bound());
SOLVER_LOG(solution_logger,
"%%%mzn-stat: boolVariables=", response.num_booleans());
SOLVER_LOG(solution_logger,
"%%%mzn-stat: failures=", response.num_conflicts());
SOLVER_LOG(
solution_logger, "%%%mzn-stat: propagations=",
response.num_binary_propagations() + response.num_integer_propagations());
SOLVER_LOG(solution_logger, "%%%mzn-stat: solveTime=", response.wall_time());
}
} // namespace
void ProcessFloatingPointOVariablesAndObjective(fz::Model* fz_model) {
// Scan the model, rename int2float to int_eq, change type of the floating
// point variables to integer.
for (fz::Constraint* ct : fz_model->constraints()) {
if (!ct->active) continue;
if (ct->type == "int2float") {
ct->type = "int_eq";
fz::Domain& float_domain = ct->arguments[1].variables[0]->domain;
float_domain.is_float = false;
for (const double float_value : float_domain.float_values) {
float_domain.values.push_back(static_cast<int64_t>(float_value));
}
float_domain.float_values.clear();
}
}
// Scan the model to find the float objective variable and the float objective
// constraint if defined.
fz::Variable* float_objective_var = nullptr;
for (fz::Variable* var : fz_model->variables()) {
if (!var->active) continue;
if (var->domain.is_float) {
CHECK(float_objective_var == nullptr);
float_objective_var = var;
}
}
fz::Constraint* float_objective_ct = nullptr;
if (float_objective_var != nullptr) {
for (fz::Constraint* ct : fz_model->constraints()) {
if (!ct->active) continue;
if (ct->type == "float_lin_eq") {
CHECK(float_objective_ct == nullptr);
float_objective_ct = ct;
break;
}
}
}
if (float_objective_ct != nullptr || float_objective_var != nullptr) {
CHECK(float_objective_ct != nullptr);
CHECK(float_objective_var != nullptr);
const int arity = float_objective_ct->arguments[0].Size();
CHECK_EQ(float_objective_ct->arguments[1].variables[arity - 1],
float_objective_var);
CHECK_EQ(float_objective_ct->arguments[0].floats[arity - 1], -1.0);
for (int i = 0; i + 1 < arity; ++i) {
fz_model->AddFloatingPointObjectiveTerm(
float_objective_ct->arguments[1].variables[i],
float_objective_ct->arguments[0].floats[i]);
}
fz_model->SetFloatingPointObjectiveOffset(
-float_objective_ct->arguments[2].floats[0]);
fz_model->ClearObjective();
float_objective_var->active = false;
float_objective_ct->active = false;
}
}
void SolveFzWithCpModelProto(const fz::Model& fz_model,
const fz::FlatzincSatParameters& p,
const std::string& sat_params,
SolverLogger* logger,
SolverLogger* solution_logger) {
const bool enumerate_all_solutions_of_a_sat_problem =
p.search_all_solutions && fz_model.objective() == nullptr;
CpModelProtoWithMapping m(enumerate_all_solutions_of_a_sat_problem);
m.proto.set_name(fz_model.name());
// The translation is easy, we create one variable
// per flatzinc variable, plus eventually a bunch
// of constant variables that will be created
// lazily.
for (fz::Variable* fz_var : fz_model.variables()) {
if (!fz_var->active) continue;
CHECK(!fz_var->domain.is_float) << "CP-SAT does not support float "
"variables";
if (fz_var->domain.is_a_set) {
std::shared_ptr<SetVariable> set_var = std::make_shared<SetVariable>();
// Fill values.
if (fz_var->domain.is_interval) {
set_var->sorted_values.reserve(fz_var->domain.values[1] -
fz_var->domain.values[0] + 1);
for (int64_t value = fz_var->domain.values[0];
value <= fz_var->domain.values[1]; ++value) {
set_var->sorted_values.push_back(value);
}
} else {
set_var->sorted_values.reserve(fz_var->domain.values.size());
for (int64_t value : fz_var->domain.values) {
set_var->sorted_values.push_back(value);
}
}
// Add Boolean variables.
for (int i = 0; i < set_var->sorted_values.size(); ++i) {
if (fz_var->domain.is_fixed_set) {
set_var->var_indices.push_back(m.LookupConstant(1));
} else {
set_var->var_indices.push_back(m.NewBoolVar());
}
}
m.set_variables[fz_var] = set_var;
} else {
m.fz_var_to_index[fz_var] = m.proto.variables_size();
IntegerVariableProto* var = m.proto.add_variables();
var->set_name(fz_var->name);
if (fz_var->domain.is_interval) {
if (fz_var->domain.values.empty()) {
// The CP-SAT solver checks that
// constraints cannot overflow during
// their propagation. Because of that, we
// trim undefined variable domains (i.e.
// int in minizinc) to something hopefully
// large enough.
LOG_FIRST_N(WARNING, 1) << "Using flag --fz_int_max for "
"unbounded integer variables.";
LOG_FIRST_N(WARNING, 1)
<< " actual domain is [" << -absl::GetFlag(FLAGS_fz_int_max)
<< ".." << absl::GetFlag(FLAGS_fz_int_max) << "]";
FillDomainInProto(-absl::GetFlag(FLAGS_fz_int_max),
absl::GetFlag(FLAGS_fz_int_max), var);
} else {
FillDomainInProto(fz_var->domain.values[0], fz_var->domain.values[1],
var);
}
} else {
FillDomainInProto(Domain::FromValues(fz_var->domain.values), var);
}
}
}
// Translate the constraints.
// First pass: extract useful information.
for (fz::Constraint* fz_ct : fz_model.constraints()) {
m.FirstPass(*fz_ct);
}
// Second pass: translate the constraints.
for (fz::Constraint* fz_ct : fz_model.constraints()) {
if (fz_ct == nullptr || !fz_ct->active) continue;
if (m.ConstraintContainsSetVariables(*fz_ct)) {
m.ExtractSetConstraint(*fz_ct);
} else {
if (absl::EndsWith(fz_ct->type, "_reif") ||
absl::EndsWith(fz_ct->type, "_imp") ||
fz_ct->type == "array_bool_or" || fz_ct->type == "array_bool_and") {
m.FillReifiedOrImpliedConstraint(*fz_ct);
} else {
ConstraintProto* ct = m.proto.add_constraints();
ct->set_name(fz_ct->type);
m.FillConstraint(*fz_ct, ct);
}
}
}
// Third pass: Implement the encoding constraints.
m.AddAllEncodingConstraints();
// Fill the objective.
if (fz_model.objective() != nullptr) {
CpObjectiveProto* objective = m.proto.mutable_objective();
if (fz_model.maximize()) {
objective->set_scaling_factor(-1);
objective->add_coeffs(-1);
objective->add_vars(m.fz_var_to_index[fz_model.objective()]);
} else {
objective->add_coeffs(1);
objective->add_vars(m.fz_var_to_index[fz_model.objective()]);
}
} else if (!fz_model.float_objective_variables().empty()) {
FloatObjectiveProto* objective = m.proto.mutable_floating_point_objective();
for (int i = 0; i < fz_model.float_objective_variables().size(); ++i) {
objective->add_vars(
m.fz_var_to_index[fz_model.float_objective_variables()[i]]);
objective->add_coeffs(fz_model.float_objective_coefficients()[i]);
}
objective->set_offset(fz_model.float_objective_offset());
objective->set_maximize(fz_model.maximize());
}
// Fill the search order.
m.TranslateSearchAnnotations(fz_model.search_annotations(), logger);
// Extra statistics.
SOLVER_LOG(logger,
" - #var_op_value_reif cached: ", m.num_var_op_value_reif_cached);
SOLVER_LOG(logger,
" - #var_op_var_reif cached: ", m.num_var_op_var_reif_cached);
SOLVER_LOG(logger,
" - #var_op_var_imp cached: ", m.num_var_op_var_reif_cached);
SOLVER_LOG(logger, " - #full domain encodings: ", m.num_full_encodings);
if (absl::GetFlag(FLAGS_fz_use_light_encoding)) {
SOLVER_LOG(logger,
" - #partial domain encodings: ", m.num_partial_encodings);
SOLVER_LOG(logger,
" - #light constraint encodings: ", m.num_light_encodings);
}
if (p.search_all_solutions && !m.proto.has_objective()) {
// Enumerate all sat solutions.
m.parameters.set_enumerate_all_solutions(true);
}
m.parameters.set_log_search_progress(p.log_search_progress);
m.parameters.set_log_to_stdout(!p.ortools_mode);
// Helps with challenge unit tests.
m.parameters.set_max_domain_size_when_encoding_eq_neq_constraints(32);
// Computes the number of workers.
int num_workers = 1;
if (p.search_all_solutions && fz_model.objective() == nullptr) {
if (p.number_of_threads > 1) {
// We don't support enumerating all solution in parallel for a SAT
// problem. But note that we do support it for an optimization problem
// since the meaning of p.all_solutions is not the same in this case.
SOLVER_LOG(logger,
"Search for all solutions of a SAT problem in parallel is not "
"supported. Switching back to sequential search.");
}
} else if (p.number_of_threads <= 0) {
// TODO(user): Supports setting the number of workers to 0, which will
// then query the number of cores available. This is complex now as we
// need to still support the expected behabior (no flags -> 1 thread
// fixed search, -f -> 1 thread free search).
SOLVER_LOG(logger,
"The number of search workers, is not specified. For better "
"performances, please set the number of workers to 8, 16, or "
"more depending on the number of cores of your computer.");
} else {
num_workers = p.number_of_threads;
}
// Specifies single thread specific search modes.
if (num_workers == 1 && !p.use_free_search) { // Fixed search.
m.parameters.set_search_branching(SatParameters::FIXED_SEARCH);
m.parameters.set_keep_all_feasible_solutions_in_presolve(true);
} else if (num_workers == 1 && p.use_free_search) { // Free search.
m.parameters.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
if (!p.search_all_solutions &&
(absl::GetFlag(FLAGS_force_interleave_search) || p.ortools_mode)) {
m.parameters.set_interleave_search(true);
m.parameters.set_use_rins_lns(false);
m.parameters.add_subsolvers("default_lp");
m.parameters.add_subsolvers("max_lp");
m.parameters.add_subsolvers("quick_restart");
m.parameters.add_subsolvers("core_or_no_lp"); // no_lp if no objective.
m.parameters.set_num_violation_ls(1); // Off if no objective.
if (!m.proto.search_strategy().empty()) {
m.parameters.add_subsolvers("fixed");
}
}
} else if (num_workers > 1 && num_workers < 8 && p.ortools_mode) {
SOLVER_LOG(logger, "Bumping number of workers from ", num_workers, " to 8");
num_workers = 8;
}
m.parameters.set_num_workers(num_workers);
// Time limit.
if (p.max_time_in_seconds > 0) {
m.parameters.set_max_time_in_seconds(p.max_time_in_seconds);
}
// The order is important, we want the flag parameters to overwrite anything
// set in m.parameters.
sat::SatParameters flag_parameters;
CHECK(google::protobuf::TextFormat::ParseFromString(sat_params,
&flag_parameters))
<< sat_params;
m.parameters.MergeFrom(flag_parameters);
// We only need an observer if 'p.display_all_solutions' or
// 'p.search_all_solutions' are true.
std::function<void(const CpSolverResponse&)> solution_observer = nullptr;
if (p.display_all_solutions || p.search_all_solutions) {
solution_observer = [&fz_model, &m, &p,
solution_logger](const CpSolverResponse& r) {
const std::string solution_string = SolutionString(
fz_model,
[&m, &r](fz::Variable* v) {
return r.solution(m.fz_var_to_index.at(v));
},
[&m, &r](fz::Variable* v) {
std::vector<int64_t> values;
const std::shared_ptr<SetVariable> set_var = m.set_variables.at(v);
for (int i = 0; i < set_var->sorted_values.size(); ++i) {
if (r.solution(set_var->var_indices[i]) == 1) {
values.push_back(set_var->sorted_values[i]);
}
}
return values;
},
r.objective_value());
SOLVER_LOG(solution_logger, solution_string);
if (p.check_all_solutions) {
CHECK(CheckSolution(
fz_model,
[&r, &m](fz::Variable* v) {
return r.solution(m.fz_var_to_index.at(v));
},
[&r, &m](fz::Variable* v) {
std::vector<int64_t> values;
const std::shared_ptr<SetVariable> set_var =
m.set_variables.at(v);
for (int i = 0; i < set_var->sorted_values.size(); ++i) {
if (r.solution(set_var->var_indices[i]) == 1) {
values.push_back(set_var->sorted_values[i]);
}
}
return values;
},
solution_logger));
}
if (p.display_statistics) {
OutputFlatzincStats(r, solution_logger);
}
SOLVER_LOG(solution_logger, "----------");
};
}
Model sat_model;
// Setup logging.
// Note that we need to do that before we start calling the sat functions
// below that might create a SolverLogger() themselves.
sat_model.Register<SolverLogger>(logger);
sat_model.Add(NewSatParameters(m.parameters));
if (solution_observer != nullptr) {
sat_model.Add(NewFeasibleSolutionObserver(solution_observer));
}
const CpSolverResponse response = SolveCpModel(m.proto, &sat_model);
// Check the returned solution with the fz model checker.
if (response.status() == CpSolverStatus::FEASIBLE ||
response.status() == CpSolverStatus::OPTIMAL) {
CHECK(CheckSolution(
fz_model,
[&response, &m](fz::Variable* v) {
return response.solution(m.fz_var_to_index.at(v));
},
[&response, &m](fz::Variable* v) {
std::vector<int64_t> values;
const std::shared_ptr<SetVariable> set_var = m.set_variables.at(v);
for (int i = 0; i < set_var->sorted_values.size(); ++i) {
if (response.solution(set_var->var_indices[i]) == 1) {
values.push_back(set_var->sorted_values[i]);
}
}
return values;
},
logger));
}
// Output the solution in the flatzinc official format.
if (p.ortools_mode) {
if (response.status() == CpSolverStatus::FEASIBLE ||
response.status() == CpSolverStatus::OPTIMAL) {
// Display the solution if it is not already displayed.
if (!p.display_all_solutions && !p.search_all_solutions) {
// Already printed otherwise.
const std::string solution_string = SolutionString(
fz_model,
[&response, &m](fz::Variable* v) {
return response.solution(m.fz_var_to_index.at(v));
},
[&response, &m](fz::Variable* v) {
std::vector<int64_t> values;
const std::shared_ptr<SetVariable> set_var =
m.set_variables.at(v);
for (int i = 0; i < set_var->sorted_values.size(); ++i) {
if (response.solution(set_var->var_indices[i]) == 1) {
values.push_back(set_var->sorted_values[i]);
}
}
return values;
},
response.objective_value());
SOLVER_LOG(solution_logger, solution_string);
SOLVER_LOG(solution_logger, "----------");
}
const bool should_print_optimal =
response.status() == CpSolverStatus::OPTIMAL &&
(fz_model.objective() != nullptr || p.search_all_solutions);
if (should_print_optimal) {
SOLVER_LOG(solution_logger, "==========");
}
} else if (response.status() == CpSolverStatus::INFEASIBLE) {
SOLVER_LOG(solution_logger, "=====UNSATISFIABLE=====");
} else if (response.status() == CpSolverStatus::MODEL_INVALID) {
const std::string error_message = ValidateCpModel(m.proto);
VLOG(1) << "%% Error message = '" << error_message << "'";
if (absl::StrContains(error_message, "overflow")) {
SOLVER_LOG(solution_logger, "=====OVERFLOW=====");
} else {
SOLVER_LOG(solution_logger, "=====MODEL INVALID=====");
}
} else {
SOLVER_LOG(solution_logger, "%% TIMEOUT");
}
if (p.display_statistics) {
OutputFlatzincStats(response, solution_logger);
}
}
}
} // namespace sat
} // namespace operations_research