OR-Tools  9.3
cp_model_postsolve.cc
Go to the documentation of this file.
1// Copyright 2010-2021 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
22#include "ortools/sat/cp_model.pb.h"
25
26namespace operations_research {
27namespace sat {
28
29// This postsolve is "special". If the clause is not satisfied, we fix the
30// first literal in the clause to true (even if it was fixed to false). This
31// allows to handle more complex presolve operations used by the SAT presolver.
32//
33// Also, any "free" Boolean should be fixed to some value for the subsequent
34// postsolve steps.
35void PostsolveClause(const ConstraintProto& ct, std::vector<Domain>* domains) {
36 const int size = ct.bool_or().literals_size();
37 CHECK_NE(size, 0);
38 bool satisfied = false;
39 for (int i = 0; i < size; ++i) {
40 const int ref = ct.bool_or().literals(i);
41 const int var = PositiveRef(ref);
42 if ((*domains)[var].IsFixed()) {
43 if ((*domains)[var].FixedValue() == (RefIsPositive(ref) ? 1 : 0)) {
44 satisfied = true;
45 }
46 } else {
47 // We still need to assign free variable. Any value should work.
48 (*domains)[PositiveRef(ref)] = Domain(0);
49 }
50 }
51 if (satisfied) return;
52
53 // Change the value of the first variable (which was chosen at presolve).
54 const int first_ref = ct.bool_or().literals(0);
55 (*domains)[PositiveRef(first_ref)] = Domain(RefIsPositive(first_ref) ? 1 : 0);
56}
57
58void PostsolveExactlyOne(const ConstraintProto& ct,
59 std::vector<Domain>* domains) {
60 bool satisfied = false;
61 std::vector<int> free_variables;
62 for (const int ref : ct.exactly_one().literals()) {
63 const int var = PositiveRef(ref);
64 if ((*domains)[var].IsFixed()) {
65 if ((*domains)[var].FixedValue() == (RefIsPositive(ref) ? 1 : 0)) {
66 CHECK(!satisfied) << "Two variables at one in exactly one.";
67 satisfied = true;
68 }
69 } else {
70 free_variables.push_back(ref);
71 }
72 }
73 if (!satisfied) {
74 // Fix one at true.
75 CHECK(!free_variables.empty()) << "All zero in exactly one";
76 const int ref = free_variables.back();
77 (*domains)[PositiveRef(ref)] = Domain(RefIsPositive(ref) ? 1 : 0);
78 free_variables.pop_back();
79 }
80
81 // Fix any free variable left at false.
82 for (const int ref : free_variables) {
83 (*domains)[PositiveRef(ref)] = Domain(RefIsPositive(ref) ? 0 : 1);
84 }
85}
86
87// For now we set the first unset enforcement literal to false.
88// There must be one.
89void SetEnforcementLiteralToFalse(const ConstraintProto& ct,
90 std::vector<Domain>* domains) {
91 CHECK(!ct.enforcement_literal().empty());
92 bool has_free_enforcement_literal = false;
93 for (const int enf : ct.enforcement_literal()) {
94 if ((*domains)[PositiveRef(enf)].IsFixed()) continue;
95 has_free_enforcement_literal = true;
96 if (RefIsPositive(enf)) {
97 (*domains)[enf] = Domain(0);
98 } else {
99 (*domains)[PositiveRef(enf)] = Domain(1);
100 }
101 break;
102 }
103 if (!has_free_enforcement_literal) {
104 LOG(FATAL)
105 << "Unsatisfied linear constraint with no free enforcement literal: "
106 << ct.ShortDebugString();
107 }
108}
109
110// Here we simply assign all non-fixed variable to a feasible value. Which
111// should always exists by construction.
112void PostsolveLinear(const ConstraintProto& ct, std::vector<Domain>* domains) {
113 int64_t fixed_activity = 0;
114 const int size = ct.linear().vars().size();
115 std::vector<int> free_vars;
116 std::vector<int64_t> free_coeffs;
117 for (int i = 0; i < size; ++i) {
118 const int var = ct.linear().vars(i);
119 const int64_t coeff = ct.linear().coeffs(i);
120 CHECK_LT(var, domains->size());
121 if (coeff == 0) continue;
122 if ((*domains)[var].IsFixed()) {
123 fixed_activity += (*domains)[var].FixedValue() * coeff;
124 } else {
125 free_vars.push_back(var);
126 free_coeffs.push_back(coeff);
127 }
128 }
129 if (free_vars.empty()) {
130 const Domain rhs = ReadDomainFromProto(ct.linear());
131 if (!rhs.Contains(fixed_activity)) {
133 }
134 return;
135 }
136
137 // Fast track for the most common case.
138 const Domain initial_rhs = ReadDomainFromProto(ct.linear());
139 if (free_vars.size() == 1) {
140 const int var = free_vars[0];
141 const Domain domain = initial_rhs.AdditionWith(Domain(-fixed_activity))
142 .InverseMultiplicationBy(free_coeffs[0])
143 .IntersectionWith((*domains)[var]);
144 if (domain.IsEmpty()) {
146 return;
147 }
148 (*domains)[var] = Domain(domain.SmallestValue());
149 return;
150 }
151
152 // The postsolve code is a bit involved if there is more than one free
153 // variable, we have to postsolve them one by one.
154 //
155 // Here we recompute the same domains as during the presolve. Everything is
156 // like if we where substiting the variable one by one:
157 // terms[i] + fixed_activity \in rhs_domains[i]
158 // In the reverse order.
159 std::vector<Domain> rhs_domains;
160 rhs_domains.push_back(initial_rhs);
161 for (int i = 0; i + 1 < free_vars.size(); ++i) {
162 // Note that these should be exactly the same computation as the one done
163 // during presolve and should be exact. However, we have some tests that do
164 // not comply, so we don't check exactness here. Also, as long as we don't
165 // get empty domain below, and the complexity of the domain do not explode
166 // here, we should be fine.
167 Domain term = (*domains)[free_vars[i]].MultiplicationBy(-free_coeffs[i]);
168 rhs_domains.push_back(term.AdditionWith(rhs_domains.back()));
169 }
170 for (int i = free_vars.size() - 1; i >= 0; --i) {
171 // Choose a value for free_vars[i] that fall into rhs_domains[i] -
172 // fixed_activity. This will crash if the intersection is empty, but it
173 // shouldn't be.
174 const int var = free_vars[i];
175 const int64_t coeff = free_coeffs[i];
176 const Domain domain = rhs_domains[i]
177 .AdditionWith(Domain(-fixed_activity))
179 .IntersectionWith((*domains)[var]);
180
181 // TODO(user): I am not 100% that the algo here might cover all the presolve
182 // case, so if this fail, it might indicate an issue here and not in the
183 // presolve/solver code.
184 CHECK(!domain.IsEmpty()) << ct.ShortDebugString();
185 const int64_t value = domain.SmallestValue();
186 (*domains)[var] = Domain(value);
187
188 fixed_activity += coeff * value;
189 }
190 DCHECK(initial_rhs.Contains(fixed_activity));
191}
192
193namespace {
194
195int64_t EvaluateLinearExpression(const LinearExpressionProto& expr,
196 const std::vector<Domain>& domains) {
197 int64_t value = expr.offset();
198 for (int i = 0; i < expr.vars_size(); ++i) {
199 const int ref = expr.vars(i);
200 const int64_t increment =
201 domains[PositiveRef(expr.vars(i))].FixedValue() * expr.coeffs(i);
202 value += RefIsPositive(ref) ? increment : -increment;
203 }
204 return value;
205}
206
207} // namespace
208
209// Compute the max of each expression, and assign it to the target expr (which
210// must be of the form +ref or -ref);
211// We only support post-solving the case were the target is unassigned,
212// but everything else is fixed.
213void PostsolveLinMax(const ConstraintProto& ct, std::vector<Domain>* domains) {
214 int64_t max_value = std::numeric_limits<int64_t>::min();
215 for (const LinearExpressionProto& expr : ct.lin_max().exprs()) {
216 max_value = std::max(max_value, EvaluateLinearExpression(expr, *domains));
217 }
218 const int target_ref = GetSingleRefFromExpression(ct.lin_max().target());
219 const int target_var = PositiveRef(target_ref);
220 (*domains)[target_var] = (*domains)[target_var].IntersectionWith(
221 Domain(RefIsPositive(target_ref) ? max_value : -max_value));
222 CHECK(!(*domains)[target_var].IsEmpty());
223}
224
225// We only support 3 cases in the presolve currently.
226void PostsolveElement(const ConstraintProto& ct, std::vector<Domain>* domains) {
227 const int index_ref = ct.element().index();
228 const int index_var = PositiveRef(index_ref);
229 const int target_ref = ct.element().target();
230 const int target_var = PositiveRef(target_ref);
231
232 // Deal with non-fixed target and non-fixed index. This only happen if
233 // whatever the value of the index and selected variable, we can choose a
234 // valid target, so we just fix the index to its min value in this case.
235 if (!(*domains)[target_var].IsFixed() && !(*domains)[index_var].IsFixed()) {
236 const int64_t index_var_value = (*domains)[index_var].Min();
237 (*domains)[index_var] = Domain(index_var_value);
238
239 // If the selected variable is not fixed, we also need to fix it.
240 const int selected_ref = ct.element().vars(
241 RefIsPositive(index_ref) ? index_var_value : -index_var_value);
242 const int selected_var = PositiveRef(selected_ref);
243 if (!(*domains)[selected_var].IsFixed()) {
244 (*domains)[selected_var] = Domain((*domains)[selected_var].Min());
245 }
246 }
247
248 // Deal with fixed index.
249 if ((*domains)[index_var].IsFixed()) {
250 const int64_t index_var_value = (*domains)[index_var].FixedValue();
251 const int selected_ref = ct.element().vars(
252 RefIsPositive(index_ref) ? index_var_value : -index_var_value);
253 const int selected_var = PositiveRef(selected_ref);
254 if ((*domains)[selected_var].IsFixed()) {
255 const int64_t selected_value = (*domains)[selected_var].FixedValue();
256 (*domains)[target_var] = (*domains)[target_var].IntersectionWith(
257 Domain(RefIsPositive(target_ref) == RefIsPositive(selected_ref)
258 ? selected_value
259 : -selected_value));
260 DCHECK(!(*domains)[target_var].IsEmpty());
261 } else {
262 const bool same_sign =
263 (selected_var == selected_ref) == (target_var == target_ref);
264 const Domain target_domain = (*domains)[target_var];
265 const Domain selected_domain = same_sign
266 ? (*domains)[selected_var]
267 : (*domains)[selected_var].Negation();
268 const Domain final = target_domain.IntersectionWith(selected_domain);
269 const int64_t value = final.SmallestValue();
270 (*domains)[target_var] =
271 (*domains)[target_var].IntersectionWith(Domain(value));
272 (*domains)[selected_var] = (*domains)[selected_var].IntersectionWith(
273 Domain(same_sign ? value : -value));
274 DCHECK(!(*domains)[target_var].IsEmpty());
275 DCHECK(!(*domains)[selected_var].IsEmpty());
276 }
277 return;
278 }
279
280 // Deal with fixed target (and constant vars).
281 const int64_t target_value = (*domains)[target_var].FixedValue();
282 int selected_index_value = -1;
283 for (const int64_t v : (*domains)[index_var].Values()) {
284 const int64_t i = index_var == index_ref ? v : -v;
285 if (i < 0 || i >= ct.element().vars_size()) continue;
286
287 const int ref = ct.element().vars(i);
288 const int var = PositiveRef(ref);
289 const int64_t value = (*domains)[var].FixedValue();
290 if (RefIsPositive(target_ref) == RefIsPositive(ref)) {
291 if (value == target_value) {
292 selected_index_value = i;
293 break;
294 }
295 } else {
296 if (value == -target_value) {
297 selected_index_value = i;
298 break;
299 }
300 }
301 }
302
303 CHECK_NE(selected_index_value, -1);
304 (*domains)[index_var] = (*domains)[index_var].IntersectionWith(Domain(
305 RefIsPositive(index_ref) ? selected_index_value : -selected_index_value));
306 DCHECK(!(*domains)[index_var].IsEmpty());
307}
308
309void PostsolveResponse(const int64_t num_variables_in_original_model,
310 const CpModelProto& mapping_proto,
311 const std::vector<int>& postsolve_mapping,
312 std::vector<int64_t>* solution) {
313 CHECK_EQ(solution->size(), postsolve_mapping.size());
314
315 // Read the initial variable domains, either from the fixed solution of the
316 // presolved problems or from the mapping model.
317 std::vector<Domain> domains(mapping_proto.variables_size());
318 for (int i = 0; i < postsolve_mapping.size(); ++i) {
319 CHECK_LE(postsolve_mapping[i], domains.size());
320 domains[postsolve_mapping[i]] = Domain((*solution)[i]);
321 }
322 for (int i = 0; i < domains.size(); ++i) {
323 if (domains[i].IsEmpty()) {
324 domains[i] = ReadDomainFromProto(mapping_proto.variables(i));
325 }
326 CHECK(!domains[i].IsEmpty());
327 }
328
329 // Process the constraints in reverse order.
330 const int num_constraints = mapping_proto.constraints_size();
331 for (int i = num_constraints - 1; i >= 0; i--) {
332 const ConstraintProto& ct = mapping_proto.constraints(i);
333
334 // We ignore constraint with an enforcement literal set to false. If the
335 // enforcement is still unclear, we still process this constraint.
336 bool constraint_can_be_ignored = false;
337 for (const int enf : ct.enforcement_literal()) {
338 const int var = PositiveRef(enf);
339 const bool is_false =
340 domains[var].IsFixed() &&
341 RefIsPositive(enf) == (domains[var].FixedValue() == 0);
342 if (is_false) {
343 constraint_can_be_ignored = true;
344 break;
345 }
346 }
347 if (constraint_can_be_ignored) continue;
348
349 switch (ct.constraint_case()) {
350 case ConstraintProto::kBoolOr:
351 PostsolveClause(ct, &domains);
352 break;
353 case ConstraintProto::kExactlyOne:
354 PostsolveExactlyOne(ct, &domains);
355 break;
356 case ConstraintProto::kLinear:
357 PostsolveLinear(ct, &domains);
358 break;
359 case ConstraintProto::kLinMax:
360 PostsolveLinMax(ct, &domains);
361 break;
362 case ConstraintProto::kElement:
363 PostsolveElement(ct, &domains);
364 break;
365 default:
366 // This should never happen as we control what kind of constraint we
367 // add to the mapping_proto;
368 LOG(FATAL) << "Unsupported constraint: " << ct.ShortDebugString();
369 }
370 }
371
372 // Fill the response.
373 // Maybe fix some still unfixed variable.
374 solution->clear();
375 CHECK_LE(num_variables_in_original_model, domains.size());
376 for (int i = 0; i < num_variables_in_original_model; ++i) {
377 solution->push_back(domains[i].SmallestValue());
378 }
379}
380
381} // namespace sat
382} // namespace operations_research
int64_t max
Definition: alldiff_cst.cc:140
int64_t min
Definition: alldiff_cst.cc:139
#define CHECK(condition)
Definition: base/logging.h:495
#define CHECK_LT(val1, val2)
Definition: base/logging.h:706
#define CHECK_EQ(val1, val2)
Definition: base/logging.h:703
#define CHECK_NE(val1, val2)
Definition: base/logging.h:704
#define LOG(severity)
Definition: base/logging.h:420
#define DCHECK(condition)
Definition: base/logging.h:890
#define CHECK_LE(val1, val2)
Definition: base/logging.h:705
We call domain any subset of Int64 = [kint64min, kint64max].
Domain InverseMultiplicationBy(const int64_t coeff) const
Returns {x ∈ Int64, ∃ e ∈ D, x * coeff = e}.
Domain Negation() const
Returns {x ∈ Int64, ∃ e ∈ D, x = -e}.
bool Contains(int64_t value) const
Returns true iff value is in Domain.
Domain AdditionWith(const Domain &domain) const
Returns {x ∈ Int64, ∃ a ∈ D, ∃ b ∈ domain, x = a + b}.
Domain MultiplicationBy(int64_t coeff, bool *exact=nullptr) const
Returns {x ∈ Int64, ∃ e ∈ D, x = e * coeff}.
Domain IntersectionWith(const Domain &domain) const
Returns the intersection of D and domain.
bool IsEmpty() const
Returns true if this is the empty set.
int64_t SmallestValue() const
Returns the value closest to zero.
const Constraint * ct
int64_t value
IntVar * var
Definition: expr_array.cc:1874
const int FATAL
Definition: log_severity.h:32
void PostsolveElement(const ConstraintProto &ct, std::vector< Domain > *domains)
void PostsolveLinear(const ConstraintProto &ct, std::vector< Domain > *domains)
bool RefIsPositive(int ref)
void PostsolveResponse(const int64_t num_variables_in_original_model, const CpModelProto &mapping_proto, const std::vector< int > &postsolve_mapping, std::vector< int64_t > *solution)
std::function< bool(const Model &)> IsFixed(IntegerVariable v)
Definition: integer.h:1675
void PostsolveExactlyOne(const ConstraintProto &ct, std::vector< Domain > *domains)
void SetEnforcementLiteralToFalse(const ConstraintProto &ct, std::vector< Domain > *domains)
void PostsolveLinMax(const ConstraintProto &ct, std::vector< Domain > *domains)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
void PostsolveClause(const ConstraintProto &ct, std::vector< Domain > *domains)
int GetSingleRefFromExpression(const LinearExpressionProto &expr)
Collection of objects used to extend the Constraint Solver library.
const double coeff