Google OR-Tools v9.12
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
cp_model_postsolve.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <cstdint>
18#include <limits>
19#include <vector>
20
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"
30
31namespace operations_research {
32namespace sat {
33
34// This postsolve is "special". If the clause is not satisfied, we fix the
35// first literal in the clause to true (even if it was fixed to false). This
36// allows to handle more complex presolve operations used by the SAT presolver.
37//
38// Also, any "free" Boolean should be fixed to some value for the subsequent
39// postsolve steps.
40void PostsolveClause(const ConstraintProto& ct, std::vector<Domain>* domains) {
41 const int size = ct.bool_or().literals_size();
42 CHECK_NE(size, 0);
43 bool satisfied = false;
44 for (int i = 0; i < size; ++i) {
45 const int ref = ct.bool_or().literals(i);
46 const int var = PositiveRef(ref);
47 if ((*domains)[var].IsFixed()) {
48 if ((*domains)[var].FixedValue() == (RefIsPositive(ref) ? 1 : 0)) {
49 satisfied = true;
50 }
51 } else {
52 // We still need to assign free variable. Any value should work.
53 (*domains)[PositiveRef(ref)] = Domain(0);
54 }
55 }
56 if (satisfied) return;
57
58 // Change the value of the first variable (which was chosen at presolve).
59 const int first_ref = ct.bool_or().literals(0);
60 (*domains)[PositiveRef(first_ref)] = Domain(RefIsPositive(first_ref) ? 1 : 0);
61}
62
63void PostsolveExactlyOne(const ConstraintProto& ct,
64 std::vector<Domain>* domains) {
65 bool satisfied = false;
66 std::vector<int> free_variables;
67 for (const int ref : ct.exactly_one().literals()) {
68 const int var = PositiveRef(ref);
69 if ((*domains)[var].IsFixed()) {
70 if ((*domains)[var].FixedValue() == (RefIsPositive(ref) ? 1 : 0)) {
71 CHECK(!satisfied) << "Two variables at one in exactly one.";
72 satisfied = true;
73 }
74 } else {
75 free_variables.push_back(ref);
76 }
77 }
78 if (!satisfied) {
79 // Fix one at true.
80 CHECK(!free_variables.empty()) << "All zero in exactly one";
81 const int ref = free_variables.back();
82 (*domains)[PositiveRef(ref)] = Domain(RefIsPositive(ref) ? 1 : 0);
83 free_variables.pop_back();
84 }
85
86 // Fix any free variable left at false.
87 for (const int ref : free_variables) {
88 (*domains)[PositiveRef(ref)] = Domain(RefIsPositive(ref) ? 0 : 1);
89 }
90}
91
92// For now we set the first unset enforcement literal to false.
93// There must be one.
94void SetEnforcementLiteralToFalse(const ConstraintProto& ct,
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()) {
99 if ((*domains)[PositiveRef(enf)].IsFixed()) continue;
100 has_free_enforcement_literal = true;
101 if (RefIsPositive(enf)) {
102 (*domains)[enf] = Domain(0);
103 } else {
104 (*domains)[PositiveRef(enf)] = Domain(1);
105 }
106 break;
107 }
108 if (!has_free_enforcement_literal) {
109 LOG(FATAL)
110 << "Unsatisfied linear constraint with no free enforcement literal: "
112 }
113}
114
115// Here we simply assign all non-fixed variable to a feasible value. Which
116// should always exists by construction.
117void PostsolveLinear(const ConstraintProto& ct, std::vector<Domain>* domains) {
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;
129 } else {
130 free_vars.push_back(var);
131 free_coeffs.push_back(coeff);
132 }
133 }
134 if (free_vars.empty()) {
135 const Domain rhs = ReadDomainFromProto(ct.linear());
136 if (!rhs.Contains(fixed_activity)) {
137 SetEnforcementLiteralToFalse(ct, domains);
138 } else {
139 // The constraint is satisfied, if there is any enforcement that are
140 // not fixed yet, we need to fix them.
141 //
142 // Tricky: We sometime push two constraints for postsolve:
143 // 1/ l => A
144 // 2/ not(l) => B
145 // if B is true, it is better to fix `l` so that the constraint 2/ is
146 // enforced. This way we should have no problem when processing 1/
147 //
148 // TODO(user): This is a bit hacky, if we need to postsolve both
149 // constraints at once, it might be cleaner to do that in a single
150 // postsolve operation. However this allows us to reuse normal constraints
151 // for the postsolve specification, which is nice.
152 for (const int enf : ct.enforcement_literal()) {
153 Domain& d = (*domains)[PositiveRef(enf)];
154 if (!d.IsFixed()) {
155 d = Domain(RefIsPositive(enf) ? 1 : 0);
156 }
157 }
158 }
159 return;
160 }
161
162 // Fast track for the most common case.
163 const Domain initial_rhs = ReadDomainFromProto(ct.linear());
164 if (free_vars.size() == 1) {
165 const int var = free_vars[0];
166 const Domain domain = initial_rhs.AdditionWith(Domain(-fixed_activity))
167 .InverseMultiplicationBy(free_coeffs[0])
168 .IntersectionWith((*domains)[var]);
169 if (domain.IsEmpty()) {
170 SetEnforcementLiteralToFalse(ct, domains);
171 return;
172 }
173 (*domains)[var] = Domain(domain.SmallestValue());
174 return;
175 }
176
177 // The postsolve code is a bit involved if there is more than one free
178 // variable, we have to postsolve them one by one.
179 //
180 // Here we recompute the same domains as during the presolve. Everything is
181 // like if we where substiting the variable one by one:
182 // terms[i] + fixed_activity \in rhs_domains[i]
183 // In the reverse order.
184 std::vector<Domain> rhs_domains;
185 rhs_domains.push_back(initial_rhs);
186 for (int i = 0; i + 1 < free_vars.size(); ++i) {
187 // Note that these should be exactly the same computation as the one done
188 // during presolve and should be exact. However, we have some tests that do
189 // not comply, so we don't check exactness here. Also, as long as we don't
190 // get empty domain below, and the complexity of the domain do not explode
191 // here, we should be fine.
192 Domain term = (*domains)[free_vars[i]].MultiplicationBy(-free_coeffs[i]);
193 rhs_domains.push_back(term.AdditionWith(rhs_domains.back()));
194 }
195 std::vector<int64_t> values(free_vars.size());
196 for (int i = free_vars.size() - 1; i >= 0; --i) {
197 // Choose a value for free_vars[i] that fall into rhs_domains[i] -
198 // fixed_activity. This will crash if the intersection is empty, but it
199 // shouldn't be.
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]);
206
207 // TODO(user): I am not 100% that the algo here might cover all the presolve
208 // case, so if this fail, it might indicate an issue here and not in the
209 // presolve/solver code.
210 if (domain.IsEmpty()) {
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];
215 }
216 LOG(FATAL) << "Couldn't postsolve the constraint: "
218 }
219
220 CHECK(!domain.IsEmpty()) << ProtobufShortDebugString(ct);
221 const int64_t value = domain.SmallestValue();
222 values[i] = value;
223 fixed_activity += coeff * value;
224 }
225
226 // We assign that afterwards for better debugging if we run into the domains
227 // empty above.
228 for (int i = 0; i < free_vars.size(); ++i) {
229 (*domains)[free_vars[i]] = Domain(values[i]);
230 }
231
232 DCHECK(initial_rhs.Contains(fixed_activity));
233}
234
235namespace {
236
237// Note that if a domain is not fixed, we just take its Min() value.
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 =
244 domains[PositiveRef(expr.vars(i))].Min() * expr.coeffs(i);
245 value += RefIsPositive(ref) ? increment : -increment;
246 }
247 return value;
248}
249
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;
254 }
255 return true;
256}
257
258} // namespace
259
260// Compute the max of each expression, and assign it to the target expr. We only
261// support post-solving the case where whatever the value of all expression,
262// there will be a valid target.
263void PostsolveLinMax(const ConstraintProto& ct, std::vector<Domain>* domains) {
264 int64_t max_value = std::numeric_limits<int64_t>::min();
265 for (const LinearExpressionProto& expr : ct.lin_max().exprs()) {
266 // In most case all expression are fixed, except in the corner case where
267 // one of the expression refer to the target itself !
268 max_value = std::max(max_value, EvaluateLinearExpression(expr, *domains));
269 }
270
271 const LinearExpressionProto& target = ct.lin_max().target();
272 CHECK_EQ(target.vars().size(), 1);
273 CHECK(RefIsPositive(target.vars(0)));
274
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);
279}
280
281// We only support 2 cases: either the index was removed, of the target, not
282// both.
283void PostsolveElement(const ConstraintProto& ct, std::vector<Domain>* domains) {
284 const LinearExpressionProto& index = ct.element().linear_index();
285 const LinearExpressionProto& target = ct.element().linear_target();
286
287 DCHECK(LinearExpressionIsFixed(index, *domains) ||
288 LinearExpressionIsFixed(target, *domains));
289
290 // Deal with fixed index.
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());
298 } else {
299 (*domains)[target.vars(0)] = Domain(GetInnerVarValue(target, expr_value));
300 }
301 return;
302 }
303
304 // Deal with fixed target (and constant vars).
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());
311
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;
316 break;
317 }
318 }
319
320 CHECK_NE(selected_index_value, -1);
321 (*domains)[index.vars(0)] =
322 Domain(GetInnerVarValue(index, selected_index_value));
323}
324
325// We only support assigning to an affine target.
326void PostsolveIntMod(const ConstraintProto& ct, std::vector<Domain>* domains) {
327 const int64_t exp = EvaluateLinearExpression(ct.int_mod().exprs(0), *domains);
328 const int64_t mod = EvaluateLinearExpression(ct.int_mod().exprs(1), *domains);
329 CHECK_NE(mod, 0);
330 const int64_t target_value = exp % mod;
331
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);
339}
340
341// We only support assigning to an affine target.
342void PostsolveIntProd(const ConstraintProto& ct, std::vector<Domain>* domains) {
343 int64_t target_value = 1;
344 for (const LinearExpressionProto& expr : ct.int_prod().exprs()) {
345 target_value *= EvaluateLinearExpression(expr, *domains);
346 }
347
348 const LinearExpressionProto& target = ct.int_prod().target();
349 CHECK_EQ(target.vars().size(), 1);
350 CHECK(RefIsPositive(target.vars(0)));
351
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);
356}
357
358namespace {
359void CheckPostsolveFixedVariables(const ConstraintProto& ct,
360 absl::Span<const Domain> domains) {
361 if (DEBUG_MODE) {
362 for (const int var : UsedVariables(ct)) {
363 DCHECK(domains[PositiveRef(var)].IsFixed())
364 << "Variable " << PositiveRef(var)
365 << " is not fixed after postsolve for constraint: \""
367 << "\" with domain: " << domains[PositiveRef(var)] << ".";
368 }
369 }
370}
371
372void ArbitrarilyFixVariables(const ConstraintProto& ct,
373 absl::Span<Domain> domains) {
374 for (const int var : UsedVariables(ct)) {
375 if (!domains[PositiveRef(var)].IsFixed()) {
376 domains[PositiveRef(var)] = Domain(domains[PositiveRef(var)].Min());
377 }
378 }
379}
380} // namespace
381
382void PostsolveResponse(const int64_t num_variables_in_original_model,
383 const CpModelProto& mapping_proto,
384 absl::Span<const int> postsolve_mapping,
385 std::vector<int64_t>* solution) {
386 CHECK_EQ(solution->size(), postsolve_mapping.size());
387
388 // Read the initial variable domains, either from the fixed solution of the
389 // presolved problems or from the mapping model.
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());
393 domains[postsolve_mapping[i]] = Domain((*solution)[i]);
394 }
395 for (int i = 0; i < domains.size(); ++i) {
396 if (domains[i].IsEmpty()) {
397 domains[i] = ReadDomainFromProto(mapping_proto.variables(i));
398 }
399 CHECK(!domains[i].IsEmpty());
400 }
401
402 // Process the constraints in reverse order.
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);
406
407 // We ignore constraint with an enforcement literal set to false. If the
408 // enforcement is still unclear, we still process this constraint.
409 bool constraint_can_be_ignored = false;
410 for (const int enf : ct.enforcement_literal()) {
411 const int var = PositiveRef(enf);
412 const bool is_false =
413 domains[var].IsFixed() &&
414 RefIsPositive(enf) == (domains[var].FixedValue() == 0);
415 if (is_false) {
416 constraint_can_be_ignored = true;
417 break;
418 }
419 }
420 if (constraint_can_be_ignored) {
421 ArbitrarilyFixVariables(ct, absl::MakeSpan(domains));
422 CheckPostsolveFixedVariables(ct, domains);
423 continue;
424 }
425
426 switch (ct.constraint_case()) {
427 case ConstraintProto::kBoolOr:
428 PostsolveClause(ct, &domains);
429 break;
430 case ConstraintProto::kExactlyOne:
431 PostsolveExactlyOne(ct, &domains);
432 break;
433 case ConstraintProto::kLinear:
434 PostsolveLinear(ct, &domains);
435 break;
436 case ConstraintProto::kLinMax:
437 PostsolveLinMax(ct, &domains);
438 break;
439 case ConstraintProto::kElement:
440 PostsolveElement(ct, &domains);
441 break;
442 case ConstraintProto::kIntMod:
443 PostsolveIntMod(ct, &domains);
444 break;
445 case ConstraintProto::kIntProd:
446 PostsolveIntProd(ct, &domains);
447 break;
448 default:
449 // This should never happen as we control what kind of constraint we
450 // add to the mapping_proto;
451 LOG(FATAL) << "Unsupported constraint: "
453 }
454 CheckPostsolveFixedVariables(ct, domains);
455 }
456
457 // Fill the response.
458 // Maybe fix some still unfixed variable.
459 solution->clear();
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());
463 }
464}
465
466void FillTightenedDomainInResponse(const CpModelProto& original_model,
467 const CpModelProto& mapping_proto,
468 absl::Span<const int> postsolve_mapping,
469 absl::Span<const Domain> search_domains,
470 CpSolverResponse* response,
471 SolverLogger* logger) {
472 // The [0, num_vars) part will contain the tightened domains.
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);
477
478 // Start with the domain from the mapping proto. Note that by construction
479 // this should be tighter than the original variable domains.
480 for (int i = 0; i < num_expanded_vars; ++i) {
481 domains[i] = ReadDomainFromProto(mapping_proto.variables(i));
482 if (i < num_original_vars) {
483 CHECK(domains[i].IsIncludedIn(
484 ReadDomainFromProto(original_model.variables(i))));
485 }
486 }
487
488 // The first test is for the corner case of presolve closing the problem,
489 // in which case there is no more info to process.
490 int num_common_vars = 0;
491 int num_affine_reductions = 0;
492 if (!search_domains.empty()) {
493 if (postsolve_mapping.empty()) {
494 // Currently no mapping should mean all variables are in common. This
495 // happen when presolve is disabled, but we might still have more
496 // variables due to expansion for instance.
497 //
498 // There is also the corner case of presolve closing the problem,
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]);
503 }
504 } else {
505 // This is the normal presolve case.
506 // Intersect the domain of the variables in common.
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) {
511 ++num_common_vars;
512 }
513 domains[i_in_mapping_model] =
514 domains[i_in_mapping_model].IntersectionWith(
515 search_domains[search_i]);
516 }
517
518 // Look for affine relation, and do more intersection.
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) {
530 std::swap(v1, v2);
531 std::swap(c1, c2);
532 }
533 if (v1 < num_original_vars && v2 >= num_original_vars) {
534 // We can reduce the domain of v1 by using the affine relation
535 // and the domain of v2.
536 // We have c1 * v2 + c2 * v2 = offset;
537 const int64_t offset = lin.domain(0);
538 const Domain restriction =
539 Domain(offset)
540 .AdditionWith(domains[v2].ContinuousMultiplicationBy(-c2))
542 if (!domains[v1].IsIncludedIn(restriction)) {
543 ++num_affine_reductions;
544 domains[v1] = domains[v1].IntersectionWith(restriction);
545 }
546 }
547 }
548 }
549 }
550
551 // Copy the names and replace domains.
552 *response->mutable_tightened_variables() = original_model.variables();
553 int num_tigher_domains = 0;
554 int num_empty = 0;
555 int num_fixed = 0;
556 for (int i = 0; i < num_original_vars; ++i) {
557 FillDomainInProto(domains[i], response->mutable_tightened_variables(i));
558 if (domains[i].IsEmpty()) {
559 ++num_empty;
560 continue;
561 }
562
563 if (domains[i].IsFixed()) num_fixed++;
564 const Domain original = ReadDomainFromProto(original_model.variables(i));
565 if (domains[i] != original) {
566 DCHECK(domains[i].IsIncludedIn(original));
567 ++num_tigher_domains;
568 }
569 }
570
571 // Some stats.
572 if (num_empty > 0) {
573 SOLVER_LOG(logger, num_empty,
574 " tightened domains are empty. This should not happen except if "
575 "we proven infeasibility or optimality.");
576 }
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);
581 SOLVER_LOG(logger,
582 "[TighteningInfo] original_num_variables:", num_original_vars,
583 " during_presolve:", num_expanded_vars,
584 " after:", search_domains.size(), " in_common:", num_common_vars);
585 SOLVER_LOG(logger, "");
586}
587
588} // namespace sat
589} // namespace operations_research
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
Domain InverseMultiplicationBy(int64_t coeff) const
const bool DEBUG_MODE
Definition macros.h:26
void PostsolveElement(const ConstraintProto &ct, std::vector< Domain > *domains)
void PostsolveLinear(const ConstraintProto &ct, std::vector< Domain > *domains)
std::function< bool(const Model &)> IsFixed(IntegerVariable v)
Definition integer.h:1551
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)
Definition proto_utils.h:41
#define SOLVER_LOG(logger,...)
Definition logging.h:109