21#include "absl/base/log_severity.h"
22#include "absl/log/check.h"
23#include "absl/types/span.h"
26#include "ortools/sat/cp_model.pb.h"
41 const int size = ct.bool_or().literals_size();
43 bool satisfied =
false;
44 for (
int i = 0;
i < size; ++
i) {
45 const int ref = ct.bool_or().literals(
i);
47 if ((*domains)[var].
IsFixed()) {
48 if ((*domains)[var].FixedValue() == (
RefIsPositive(ref) ? 1 : 0)) {
56 if (satisfied)
return;
59 const int first_ref = ct.bool_or().literals(0);
64 std::vector<Domain>* domains) {
65 bool satisfied =
false;
66 std::vector<int> free_variables;
67 for (
const int ref : ct.exactly_one().literals()) {
69 if ((*domains)[var].
IsFixed()) {
70 if ((*domains)[var].FixedValue() == (
RefIsPositive(ref) ? 1 : 0)) {
71 CHECK(!satisfied) <<
"Two variables at one in exactly one.";
75 free_variables.push_back(ref);
80 CHECK(!free_variables.empty()) <<
"All zero in exactly one";
81 const int ref = free_variables.back();
83 free_variables.pop_back();
87 for (
const int ref : free_variables) {
95 std::vector<Domain>* domains) {
96 CHECK(!ct.enforcement_literal().empty());
97 bool has_free_enforcement_literal =
false;
98 for (
const int enf : ct.enforcement_literal()) {
100 has_free_enforcement_literal =
true;
102 (*domains)[enf] =
Domain(0);
108 if (!has_free_enforcement_literal) {
110 <<
"Unsatisfied linear constraint with no free enforcement literal: "
118 int64_t fixed_activity = 0;
119 const int size = ct.linear().vars().size();
120 std::vector<int> free_vars;
121 std::vector<int64_t> free_coeffs;
122 for (
int i = 0;
i < size; ++
i) {
123 const int var = ct.linear().vars(
i);
124 const int64_t coeff = ct.linear().coeffs(
i);
125 CHECK_LT(var, domains->size());
126 if (coeff == 0)
continue;
127 if ((*domains)[var].
IsFixed()) {
128 fixed_activity += (*domains)[var].FixedValue() * coeff;
130 free_vars.push_back(var);
131 free_coeffs.push_back(coeff);
134 if (free_vars.empty()) {
136 if (!rhs.
Contains(fixed_activity)) {
152 for (
const int enf : ct.enforcement_literal()) {
164 if (free_vars.size() == 1) {
165 const int var = free_vars[0];
184 std::vector<Domain> rhs_domains;
185 rhs_domains.push_back(initial_rhs);
186 for (
int i = 0;
i + 1 < free_vars.size(); ++
i) {
193 rhs_domains.push_back(term.
AdditionWith(rhs_domains.back()));
195 std::vector<int64_t> values(free_vars.size());
196 for (
int i = free_vars.size() - 1;
i >= 0; --
i) {
200 const int var = free_vars[
i];
201 const int64_t coeff = free_coeffs[
i];
202 const Domain domain = rhs_domains[
i]
203 .AdditionWith(
Domain(-fixed_activity))
204 .InverseMultiplicationBy(coeff)
205 .IntersectionWith((*domains)[var]);
211 LOG(INFO) <<
"Empty domain while trying to assign " << var;
212 for (
int i = 0;
i < size; ++
i) {
213 const int var = ct.linear().vars(
i);
214 LOG(INFO) << var <<
" " << (*domains)[var];
216 LOG(FATAL) <<
"Couldn't postsolve the constraint: "
223 fixed_activity += coeff * value;
228 for (
int i = 0;
i < free_vars.size(); ++
i) {
229 (*domains)[free_vars[
i]] =
Domain(values[
i]);
232 DCHECK(initial_rhs.
Contains(fixed_activity));
238int64_t EvaluateLinearExpression(
const LinearExpressionProto& expr,
239 absl::Span<const Domain> domains) {
240 int64_t value = expr.offset();
241 for (
int i = 0;
i < expr.vars_size(); ++
i) {
242 const int ref = expr.vars(
i);
243 const int64_t increment =
250bool LinearExpressionIsFixed(
const LinearExpressionProto& expr,
251 absl::Span<const Domain> domains) {
252 for (
const int var : expr.vars()) {
253 if (!domains[var].
IsFixed())
return false;
264 int64_t max_value = std::numeric_limits<int64_t>::min();
265 for (
const LinearExpressionProto& expr : ct.lin_max().exprs()) {
268 max_value = std::max(max_value, EvaluateLinearExpression(expr, *domains));
271 const LinearExpressionProto& target = ct.lin_max().target();
272 CHECK_EQ(target.vars().size(), 1);
275 max_value -= target.offset();
276 CHECK_EQ(max_value % target.coeffs(0), 0);
277 max_value /= target.coeffs(0);
278 (*domains)[target.vars(0)] =
Domain(max_value);
284 const LinearExpressionProto& index = ct.element().linear_index();
285 const LinearExpressionProto& target = ct.element().linear_target();
287 DCHECK(LinearExpressionIsFixed(index, *domains) ||
288 LinearExpressionIsFixed(target, *domains));
291 if (LinearExpressionIsFixed(index, *domains)) {
292 const int64_t index_value = EvaluateLinearExpression(index, *domains);
293 const LinearExpressionProto& expr = ct.element().exprs(index_value);
294 DCHECK(LinearExpressionIsFixed(expr, *domains));
295 const int64_t expr_value = EvaluateLinearExpression(expr, *domains);
296 if (target.vars().empty()) {
297 DCHECK_EQ(expr_value, target.offset());
305 const int64_t target_value = EvaluateLinearExpression(target, *domains);
306 int selected_index_value = -1;
307 for (
const int64_t v : (*domains)[index.vars(0)].Values()) {
308 const int64_t index_value = index.offset() + v * index.coeffs(0);
309 DCHECK_GE(index_value, 0);
310 DCHECK_LT(index_value, ct.element().exprs_size());
312 const LinearExpressionProto& expr = ct.element().exprs(index_value);
313 const int64_t value = EvaluateLinearExpression(expr, *domains);
314 if (value == target_value) {
315 selected_index_value = index_value;
320 CHECK_NE(selected_index_value, -1);
321 (*domains)[index.vars(0)] =
327 const int64_t exp = EvaluateLinearExpression(ct.int_mod().exprs(0), *domains);
328 const int64_t mod = EvaluateLinearExpression(ct.int_mod().exprs(1), *domains);
330 const int64_t target_value = exp % mod;
332 const LinearExpressionProto& target = ct.int_mod().target();
333 CHECK_EQ(target.vars().size(), 1);
334 const int64_t term_value = target_value - target.offset();
335 CHECK_EQ(term_value % target.coeffs(0), 0);
336 const int64_t value = term_value / target.coeffs(0);
337 CHECK((*domains)[target.vars(0)].Contains(value));
338 (*domains)[target.vars(0)] =
Domain(value);
343 int64_t target_value = 1;
344 for (
const LinearExpressionProto& expr : ct.int_prod().exprs()) {
345 target_value *= EvaluateLinearExpression(expr, *domains);
348 const LinearExpressionProto& target = ct.int_prod().target();
349 CHECK_EQ(target.vars().size(), 1);
352 target_value -= target.offset();
353 CHECK_EQ(target_value % target.coeffs(0), 0);
354 target_value /= target.coeffs(0);
355 (*domains)[target.vars(0)] =
Domain(target_value);
359void CheckPostsolveFixedVariables(
const ConstraintProto& ct,
360 absl::Span<const Domain> domains) {
365 <<
" is not fixed after postsolve for constraint: \""
367 <<
"\" with domain: " << domains[
PositiveRef(var)] <<
".";
372void ArbitrarilyFixVariables(
const ConstraintProto& ct,
373 absl::Span<Domain> domains) {
383 const CpModelProto& mapping_proto,
384 absl::Span<const int> postsolve_mapping,
386 CHECK_EQ(
solution->size(), postsolve_mapping.size());
390 std::vector<Domain> domains(mapping_proto.variables_size());
391 for (
int i = 0;
i < postsolve_mapping.size(); ++
i) {
392 CHECK_LE(postsolve_mapping[
i], domains.size());
395 for (
int i = 0;
i < domains.size(); ++
i) {
396 if (domains[
i].IsEmpty()) {
399 CHECK(!domains[
i].IsEmpty());
403 const int num_constraints = mapping_proto.constraints_size();
404 for (
int i = num_constraints - 1;
i >= 0;
i--) {
405 const ConstraintProto& ct = mapping_proto.constraints(
i);
409 bool constraint_can_be_ignored =
false;
410 for (
const int enf : ct.enforcement_literal()) {
412 const bool is_false =
413 domains[var].IsFixed() &&
416 constraint_can_be_ignored =
true;
420 if (constraint_can_be_ignored) {
421 ArbitrarilyFixVariables(ct, absl::MakeSpan(domains));
422 CheckPostsolveFixedVariables(ct, domains);
426 switch (ct.constraint_case()) {
427 case ConstraintProto::kBoolOr:
430 case ConstraintProto::kExactlyOne:
433 case ConstraintProto::kLinear:
436 case ConstraintProto::kLinMax:
439 case ConstraintProto::kElement:
442 case ConstraintProto::kIntMod:
445 case ConstraintProto::kIntProd:
451 LOG(FATAL) <<
"Unsupported constraint: "
454 CheckPostsolveFixedVariables(ct, domains);
460 CHECK_LE(num_variables_in_original_model, domains.size());
461 for (
int i = 0;
i < num_variables_in_original_model; ++
i) {
462 solution->push_back(domains[
i].SmallestValue());
467 const CpModelProto& mapping_proto,
468 absl::Span<const int> postsolve_mapping,
469 absl::Span<const Domain> search_domains,
470 CpSolverResponse* response,
473 const int num_original_vars = original_model.variables().size();
474 const int num_expanded_vars = mapping_proto.variables().size();
475 CHECK_LE(num_original_vars, num_expanded_vars);
476 std::vector<Domain> domains(num_expanded_vars);
480 for (
int i = 0;
i < num_expanded_vars; ++
i) {
482 if (
i < num_original_vars) {
483 CHECK(domains[
i].IsIncludedIn(
490 int num_common_vars = 0;
491 int num_affine_reductions = 0;
492 if (!search_domains.empty()) {
493 if (postsolve_mapping.empty()) {
499 CHECK_GE(search_domains.size(), num_original_vars);
500 num_common_vars = num_original_vars;
501 for (
int i = 0;
i < num_original_vars; ++
i) {
502 domains[
i] = domains[
i].IntersectionWith(search_domains[
i]);
507 CHECK_EQ(postsolve_mapping.size(), search_domains.size());
508 for (
int search_i = 0; search_i < postsolve_mapping.size(); ++search_i) {
509 const int i_in_mapping_model = postsolve_mapping[search_i];
510 if (i_in_mapping_model < num_original_vars) {
513 domains[i_in_mapping_model] =
514 domains[i_in_mapping_model].IntersectionWith(
515 search_domains[search_i]);
519 for (
const ConstraintProto& ct : mapping_proto.constraints()) {
520 if (ct.constraint_case() != ConstraintProto::kLinear)
continue;
521 const LinearConstraintProto& lin = ct.linear();
522 if (lin.vars().size() != 2)
continue;
523 if (lin.domain().size() != 2)
continue;
524 if (lin.domain(0) != lin.domain(1))
continue;
525 int v1 = lin.vars(0);
526 int v2 = lin.vars(1);
527 int c1 = lin.coeffs(0);
528 int c2 = lin.coeffs(1);
529 if (v2 < num_original_vars && v1 >= num_original_vars) {
533 if (v1 < num_original_vars && v2 >= num_original_vars) {
537 const int64_t offset = lin.domain(0);
538 const Domain restriction =
540 .
AdditionWith(domains[v2].ContinuousMultiplicationBy(-c2))
542 if (!domains[v1].IsIncludedIn(restriction)) {
543 ++num_affine_reductions;
544 domains[v1] = domains[v1].IntersectionWith(restriction);
552 *response->mutable_tightened_variables() = original_model.variables();
553 int num_tigher_domains = 0;
556 for (
int i = 0;
i < num_original_vars; ++
i) {
558 if (domains[
i].IsEmpty()) {
563 if (domains[
i].
IsFixed()) num_fixed++;
565 if (domains[
i] != original) {
566 DCHECK(domains[
i].IsIncludedIn(original));
567 ++num_tigher_domains;
574 " tightened domains are empty. This should not happen except if "
575 "we proven infeasibility or optimality.");
577 SOLVER_LOG(logger,
"Filled tightened domains in the response.");
578 SOLVER_LOG(logger,
"[TighteningInfo] num_tighter:", num_tigher_domains,
579 " num_fixed:", num_fixed,
580 " num_affine_reductions:", num_affine_reductions);
582 "[TighteningInfo] original_num_variables:", num_original_vars,
583 " during_presolve:", num_expanded_vars,
584 " after:", search_domains.size(),
" in_common:", num_common_vars);
Domain MultiplicationBy(int64_t coeff, bool *exact=nullptr) const
Domain IntersectionWith(const Domain &domain) const
bool Contains(int64_t value) const
Domain AdditionWith(const Domain &domain) const
int64_t SmallestValue() const
Domain InverseMultiplicationBy(int64_t coeff) const
void PostsolveElement(const ConstraintProto &ct, std::vector< Domain > *domains)
void PostsolveLinear(const ConstraintProto &ct, std::vector< Domain > *domains)
bool RefIsPositive(int ref)
std::function< bool(const Model &)> IsFixed(IntegerVariable v)
int64_t GetInnerVarValue(const LinearExpressionProto &expr, int64_t value)
void PostsolveExactlyOne(const ConstraintProto &ct, std::vector< Domain > *domains)
void SetEnforcementLiteralToFalse(const ConstraintProto &ct, std::vector< Domain > *domains)
void PostsolveIntProd(const ConstraintProto &ct, std::vector< Domain > *domains)
We only support assigning to an affine target.
std::vector< int > UsedVariables(const ConstraintProto &ct)
void PostsolveLinMax(const ConstraintProto &ct, std::vector< Domain > *domains)
void FillDomainInProto(const Domain &domain, ProtoWithDomain *proto)
Serializes a Domain into the domain field of a proto.
void PostsolveIntMod(const ConstraintProto &ct, std::vector< Domain > *domains)
We only support assigning to an affine target.
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
Reads a Domain from the domain field of a proto.
void PostsolveClause(const ConstraintProto &ct, std::vector< Domain > *domains)
void PostsolveResponse(const int64_t num_variables_in_original_model, const CpModelProto &mapping_proto, absl::Span< const int > postsolve_mapping, std::vector< int64_t > *solution)
void FillTightenedDomainInResponse(const CpModelProto &original_model, const CpModelProto &mapping_proto, absl::Span< const int > postsolve_mapping, absl::Span< const Domain > search_domains, CpSolverResponse *response, SolverLogger *logger)
In SWIG mode, we don't want anything besides these top-level includes.
Select next search node to expand Select next item_i to add this new search node to the search Generate a new search node where item_i is not in the knapsack Check validity of this new partial solution(using propagators) - If valid
std::string ProtobufShortDebugString(const P &message)
#define SOLVER_LOG(logger,...)