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