Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
constraint_violation.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 <cstdlib>
19#include <limits>
20#include <memory>
21#include <optional>
22#include <utility>
23#include <vector>
24
25#include "absl/algorithm/container.h"
26#include "absl/container/flat_hash_map.h"
27#include "absl/container/flat_hash_set.h"
28#include "absl/log/check.h"
29#include "absl/log/log.h"
30#include "absl/types/span.h"
36#include "ortools/sat/util.h"
41
42namespace operations_research {
43namespace sat {
44
45namespace {
46
47int64_t ExprValue(const LinearExpressionProto& expr,
48 absl::Span<const int64_t> solution) {
49 int64_t result = expr.offset();
50 for (int i = 0; i < expr.vars_size(); ++i) {
51 result += solution[expr.vars(i)] * expr.coeffs(i);
52 }
53 return result;
54}
55
56int64_t AffineValue(const ViewOfAffineLinearExpressionProto& affine,
57 absl::Span<const int64_t> solution) {
58 if (affine.coeff == 0) return affine.offset;
59 return affine.coeff * solution[affine.var] + affine.offset;
60}
61
65 result.set_offset(a.offset() + b.offset());
66 result.mutable_vars()->Reserve(a.vars().size() + b.vars().size());
67 result.mutable_coeffs()->Reserve(a.vars().size() + b.vars().size());
68 for (const LinearExpressionProto& p : {a, b}) {
69 for (int i = 0; i < p.vars().size(); ++i) {
70 result.add_vars(p.vars(i));
71 result.add_coeffs(p.coeffs(i));
72 }
73 }
74 return result;
75}
76
77LinearExpressionProto NegatedLinearExpression(LinearExpressionProto a) {
78 LinearExpressionProto result = a;
79 result.set_offset(-a.offset());
80 for (int64_t& coeff : *result.mutable_coeffs()) {
81 coeff = -coeff;
82 }
83 return result;
84}
85
86int64_t ExprMin(const LinearExpressionProto& expr, const CpModelProto& model) {
87 int64_t result = expr.offset();
88 for (int i = 0; i < expr.vars_size(); ++i) {
89 const IntegerVariableProto& var_proto = model.variables(expr.vars(i));
90 if (expr.coeffs(i) > 0) {
91 result += expr.coeffs(i) * var_proto.domain(0);
92 } else {
93 result += expr.coeffs(i) * var_proto.domain(var_proto.domain_size() - 1);
94 }
95 }
96 return result;
97}
98
99int64_t ExprMax(const LinearExpressionProto& expr, const CpModelProto& model) {
100 int64_t result = expr.offset();
101 for (int i = 0; i < expr.vars_size(); ++i) {
102 const IntegerVariableProto& var_proto = model.variables(expr.vars(i));
103 if (expr.coeffs(i) > 0) {
104 result += expr.coeffs(i) * var_proto.domain(var_proto.domain_size() - 1);
105 } else {
106 result += expr.coeffs(i) * var_proto.domain(0);
107 }
108 }
109 return result;
110}
111
112bool LiteralValue(int lit, absl::Span<const int64_t> solution) {
113 if (RefIsPositive(lit)) {
114 return solution[lit] != 0;
115 } else {
116 return solution[PositiveRef(lit)] == 0;
117 }
118}
119
120} // namespace
121
122// ---- LinearIncrementalEvaluator -----
123
125 DCHECK(creation_phase_);
126 domains_.push_back(domain);
127 offsets_.push_back(0);
128 activities_.push_back(0);
129 num_false_enforcement_.push_back(0);
130 distances_.push_back(0);
131 is_violated_.push_back(false);
132 return num_constraints_++;
133}
134
136 DCHECK(creation_phase_);
137 const int var = PositiveRef(lit);
138 if (literal_entries_.size() <= var) {
139 literal_entries_.resize(var + 1);
140 }
141 literal_entries_[var].push_back(
142 {.ct_index = ct_index, .positive = RefIsPositive(lit)});
143}
144
145void LinearIncrementalEvaluator::AddLiteral(int ct_index, int lit,
146 int64_t coeff) {
147 DCHECK(creation_phase_);
148 if (RefIsPositive(lit)) {
149 AddTerm(ct_index, lit, coeff, 0);
150 } else {
151 AddTerm(ct_index, PositiveRef(lit), -coeff, coeff);
152 }
153}
154
155void LinearIncrementalEvaluator::AddTerm(int ct_index, int var, int64_t coeff,
156 int64_t offset) {
157 DCHECK(creation_phase_);
158 DCHECK_GE(var, 0);
159 if (coeff == 0) return;
160
161 if (var_entries_.size() <= var) {
162 var_entries_.resize(var + 1);
163 }
164 if (!var_entries_[var].empty() &&
165 var_entries_[var].back().ct_index == ct_index) {
166 var_entries_[var].back().coefficient += coeff;
167 if (var_entries_[var].back().coefficient == 0) {
168 var_entries_[var].pop_back();
169 }
170 } else {
171 var_entries_[var].push_back({.ct_index = ct_index, .coefficient = coeff});
172 }
173 AddOffset(ct_index, offset);
174 DCHECK(VarIsConsistent(var));
175}
176
177void LinearIncrementalEvaluator::AddOffset(int ct_index, int64_t offset) {
178 DCHECK(creation_phase_);
179 offsets_[ct_index] += offset;
180}
181
183 int ct_index, const LinearExpressionProto& expr, int64_t multiplier) {
184 DCHECK(creation_phase_);
185 AddOffset(ct_index, expr.offset() * multiplier);
186 for (int i = 0; i < expr.vars_size(); ++i) {
187 if (expr.coeffs(i) * multiplier == 0) continue;
188 AddTerm(ct_index, expr.vars(i), expr.coeffs(i) * multiplier);
189 }
190}
191
193 if (var_entries_.size() <= var) return true;
194
195 absl::flat_hash_set<int> visited;
196 for (const Entry& entry : var_entries_[var]) {
197 if (!visited.insert(entry.ct_index).second) return false;
198 }
199 return true;
200}
201
203 absl::Span<const int64_t> solution) {
204 DCHECK(!creation_phase_);
205
206 // Resets the activity as the offset and the number of false enforcement to 0.
207 activities_ = offsets_;
208 last_affected_variables_.ClearAndResize(columns_.size());
209 num_false_enforcement_.assign(num_constraints_, 0);
210
211 // Update these numbers for all columns.
212 const int num_vars = columns_.size();
213 for (int var = 0; var < num_vars; ++var) {
214 const SpanData& data = columns_[var];
215 const int64_t value = solution[var];
216
217 if (value == 0 && data.num_pos_literal > 0) {
218 const int* ct_indices = &ct_buffer_[data.start];
219 for (int k = 0; k < data.num_pos_literal; ++k) {
220 num_false_enforcement_[ct_indices[k]]++;
221 }
222 }
223
224 if (value == 1 && data.num_neg_literal > 0) {
225 const int* ct_indices = &ct_buffer_[data.start + data.num_pos_literal];
226 for (int k = 0; k < data.num_neg_literal; ++k) {
227 num_false_enforcement_[ct_indices[k]]++;
228 }
229 }
230
231 if (value != 0 && data.num_linear_entries > 0) {
232 const int* ct_indices =
233 &ct_buffer_[data.start + data.num_pos_literal + data.num_neg_literal];
234 const int64_t* coeffs = &coeff_buffer_[data.linear_start];
235 for (int k = 0; k < data.num_linear_entries; ++k) {
236 activities_[ct_indices[k]] += coeffs[k] * value;
237 }
238 }
239 }
240
241 // Cache violations (not counting enforcement).
242 for (int c = 0; c < num_constraints_; ++c) {
243 distances_[c] = domains_[c].Distance(activities_[c]);
244 is_violated_[c] = Violation(c) > 0;
245 }
246}
247
249 last_affected_variables_.ClearAndResize(columns_.size());
250}
251
252// Tricky: Here we reuse last_affected_variables_ to reset
253// var_to_score_change. And in particular we need to list all variable whose
254// score changed here. Not just the one for which we have a decrease.
256 int c, absl::Span<const int64_t> jump_deltas,
257 absl::Span<double> var_to_score_change) {
258 if (c >= rows_.size()) return;
259
260 DCHECK_EQ(num_false_enforcement_[c], 0);
261 const SpanData& data = rows_[c];
262
263 // Update enforcement part. Because we only update weight of currently
264 // infeasible constraint, all change are 0 -> 1 transition and change by the
265 // same amount, which is the current distance.
266 const double enforcement_change = static_cast<double>(-distances_[c]);
267 if (enforcement_change != 0.0) {
268 int i = data.start;
269 const int end = data.num_pos_literal + data.num_neg_literal;
270 num_ops_ += end;
271 for (int k = 0; k < end; ++k, ++i) {
272 const int var = row_var_buffer_[i];
273 if (!last_affected_variables_[var]) {
274 var_to_score_change[var] = enforcement_change;
275 last_affected_variables_.Set(var);
276 } else {
277 var_to_score_change[var] += enforcement_change;
278 }
279 }
280 }
281
282 // Update linear part.
283 if (data.num_linear_entries > 0) {
284 const int* row_vars = &row_var_buffer_[data.start + data.num_pos_literal +
285 data.num_neg_literal];
286 const int64_t* row_coeffs = &row_coeff_buffer_[data.linear_start];
287 num_ops_ += 2 * data.num_linear_entries;
288
289 // Computing general Domain distance is slow.
290 // TODO(user): optimize even more for one sided constraints.
291 // Note(user): I tried to factor the two usage of this, but it is slower.
292 const Domain& rhs = domains_[c];
293 const int64_t rhs_min = rhs.Min();
294 const int64_t rhs_max = rhs.Max();
295 const bool is_simple = rhs.NumIntervals() == 2;
296 const auto violation = [&rhs, rhs_min, rhs_max, is_simple](int64_t v) {
297 if (v >= rhs_max) {
298 return v - rhs_max;
299 } else if (v <= rhs_min) {
300 return rhs_min - v;
301 } else {
302 return is_simple ? int64_t{0} : rhs.Distance(v);
303 }
304 };
305
306 const int64_t old_distance = distances_[c];
307 const int64_t activity = activities_[c];
308 for (int k = 0; k < data.num_linear_entries; ++k) {
309 const int var = row_vars[k];
310 const int64_t coeff = row_coeffs[k];
311 const int64_t diff =
312 violation(activity + coeff * jump_deltas[var]) - old_distance;
313 if (!last_affected_variables_[var]) {
314 var_to_score_change[var] = static_cast<double>(diff);
315 last_affected_variables_.Set(var);
316 } else {
317 var_to_score_change[var] += static_cast<double>(diff);
318 }
319 }
320 }
321}
322
323void LinearIncrementalEvaluator::UpdateScoreOnNewlyEnforced(
324 int c, double weight, absl::Span<const int64_t> jump_deltas,
325 absl::Span<double> jump_scores) {
326 const SpanData& data = rows_[c];
327
328 // Everyone else had a zero cost transition that now become enforced ->
329 // unenforced. So they all have better score.
330 const double weight_time_violation =
331 weight * static_cast<double>(distances_[c]);
332 if (weight_time_violation > 0.0) {
333 int i = data.start;
334 const int end = data.num_pos_literal + data.num_neg_literal;
335 num_ops_ += end;
336 for (int k = 0; k < end; ++k, ++i) {
337 const int var = row_var_buffer_[i];
338 jump_scores[var] -= weight_time_violation;
339 last_affected_variables_.Set(var);
340 }
341 }
342
343 // Update linear part! It was zero and is now a diff.
344 {
345 int i = data.start + data.num_pos_literal + data.num_neg_literal;
346 int j = data.linear_start;
347 num_ops_ += 2 * data.num_linear_entries;
348 const int64_t old_distance = distances_[c];
349 for (int k = 0; k < data.num_linear_entries; ++k, ++i, ++j) {
350 const int var = row_var_buffer_[i];
351 const int64_t coeff = row_coeff_buffer_[j];
352 const int64_t new_distance =
353 domains_[c].Distance(activities_[c] + coeff * jump_deltas[var]);
354 jump_scores[var] +=
355 weight * static_cast<double>(new_distance - old_distance);
356 last_affected_variables_.Set(var);
357 }
358 }
359}
360
361void LinearIncrementalEvaluator::UpdateScoreOnNewlyUnenforced(
362 int c, double weight, absl::Span<const int64_t> jump_deltas,
363 absl::Span<double> jump_scores) {
364 const SpanData& data = rows_[c];
365
366 // Everyone else had a enforced -> unenforced transition that now become zero.
367 // So they all have worst score, and we don't need to update
368 // last_affected_variables_.
369 const double weight_time_violation =
370 weight * static_cast<double>(distances_[c]);
371 if (weight_time_violation > 0.0) {
372 int i = data.start;
373 const int end = data.num_pos_literal + data.num_neg_literal;
374 num_ops_ += end;
375 for (int k = 0; k < end; ++k, ++i) {
376 const int var = row_var_buffer_[i];
377 jump_scores[var] += weight_time_violation;
378 }
379 }
380
381 // Update linear part! It had a diff and is now zero.
382 {
383 int i = data.start + data.num_pos_literal + data.num_neg_literal;
384 int j = data.linear_start;
385 num_ops_ += 2 * data.num_linear_entries;
386 const int64_t old_distance = distances_[c];
387 for (int k = 0; k < data.num_linear_entries; ++k, ++i, ++j) {
388 const int var = row_var_buffer_[i];
389 const int64_t coeff = row_coeff_buffer_[j];
390 const int64_t new_distance =
391 domains_[c].Distance(activities_[c] + coeff * jump_deltas[var]);
392 jump_scores[var] -=
393 weight * static_cast<double>(new_distance - old_distance);
394 last_affected_variables_.Set(var);
395 }
396 }
397}
398
399// We just need to modify the old/new transition that decrease the number of
400// enforcement literal at false.
401void LinearIncrementalEvaluator::UpdateScoreOfEnforcementIncrease(
402 int c, double score_change, absl::Span<const int64_t> jump_deltas,
403 absl::Span<double> jump_scores) {
404 if (score_change == 0.0) return;
405
406 const SpanData& data = rows_[c];
407 int i = data.start;
408 num_ops_ += data.num_pos_literal;
409 for (int k = 0; k < data.num_pos_literal; ++k, ++i) {
410 const int var = row_var_buffer_[i];
411 if (jump_deltas[var] == 1) {
412 jump_scores[var] += score_change;
413 if (score_change < 0.0) {
414 last_affected_variables_.Set(var);
415 }
416 }
417 }
418 num_ops_ += data.num_neg_literal;
419 for (int k = 0; k < data.num_neg_literal; ++k, ++i) {
420 const int var = row_var_buffer_[i];
421 if (jump_deltas[var] == -1) {
422 jump_scores[var] += score_change;
423 if (score_change < 0.0) {
424 last_affected_variables_.Set(var);
425 }
426 }
427 }
428}
429
430void LinearIncrementalEvaluator::UpdateScoreOnActivityChange(
431 int c, double weight, int64_t activity_delta,
432 absl::Span<const int64_t> jump_deltas, absl::Span<double> jump_scores) {
433 if (activity_delta == 0) return;
434 const SpanData& data = rows_[c];
435
436 // In some cases, we can know that the score of all the involved variable
437 // will not change. This is the case if whatever 1 variable change the
438 // violation delta before/after is the same.
439 //
440 // TODO(user): Maintain more precise bounds.
441 // - We could easily compute on each ComputeInitialActivities() the
442 // maximum increase/decrease per variable, and take the max as each
443 // variable changes?
444 // - Know if a constraint is only <= or >= !
445 const int64_t old_activity = activities_[c];
446 const int64_t new_activity = old_activity + activity_delta;
447 int64_t min_range;
448 int64_t max_range;
449 if (new_activity > old_activity) {
450 min_range = old_activity - row_max_variations_[c];
451 max_range = new_activity + row_max_variations_[c];
452 } else {
453 min_range = new_activity - row_max_variations_[c];
454 max_range = old_activity + row_max_variations_[c];
455 }
456
457 // If the violation delta was zero and will still always be zero, we can skip.
458 if (Domain(min_range, max_range).IsIncludedIn(domains_[c])) return;
459
460 // Enforcement is always enforced -> un-enforced.
461 // So it was -weight_time_distance and is now -weight_time_new_distance.
462 const double delta =
463 -weight *
464 static_cast<double>(domains_[c].Distance(new_activity) - distances_[c]);
465 if (delta != 0.0) {
466 int i = data.start;
467 const int end = data.num_pos_literal + data.num_neg_literal;
468 num_ops_ += end;
469 for (int k = 0; k < end; ++k, ++i) {
470 const int var = row_var_buffer_[i];
471 jump_scores[var] += delta;
472 if (delta < 0.0) {
473 last_affected_variables_.Set(var);
474 }
475 }
476 }
477
478 // If we are infeasible and no move can correct it, both old_b - old_a and
479 // new_b - new_a will have the same value. We only needed to update the
480 // violation of the enforced literal.
481 if (min_range >= domains_[c].Max() || max_range <= domains_[c].Min()) return;
482
483 // Update linear part.
484 if (data.num_linear_entries > 0) {
485 const int* row_vars = &row_var_buffer_[data.start + data.num_pos_literal +
486 data.num_neg_literal];
487 const int64_t* row_coeffs = &row_coeff_buffer_[data.linear_start];
488 num_ops_ += 2 * data.num_linear_entries;
489
490 // Computing general Domain distance is slow.
491 // TODO(user): optimize even more for one sided constraints.
492 // Note(user): I tried to factor the two usage of this, but it is slower.
493 const Domain& rhs = domains_[c];
494 const int64_t rhs_min = rhs.Min();
495 const int64_t rhs_max = rhs.Max();
496 const bool is_simple = rhs.NumIntervals() == 2;
497 const auto violation = [&rhs, rhs_min, rhs_max, is_simple](int64_t v) {
498 if (v >= rhs_max) {
499 return v - rhs_max;
500 } else if (v <= rhs_min) {
501 return rhs_min - v;
502 } else {
503 return is_simple ? int64_t{0} : rhs.Distance(v);
504 }
505 };
506
507 const int64_t old_a_minus_new_a =
508 distances_[c] - domains_[c].Distance(new_activity);
509 for (int k = 0; k < data.num_linear_entries; ++k) {
510 const int var = row_vars[k];
511 const int64_t impact = row_coeffs[k] * jump_deltas[var];
512 const int64_t old_b = violation(old_activity + impact);
513 const int64_t new_b = violation(new_activity + impact);
514
515 // The old score was:
516 // weight * static_cast<double>(old_b - old_a);
517 // the new score is
518 // weight * static_cast<double>(new_b - new_a); so the diff is:
519 // weight * static_cast<double>(new_b - new_a - old_b + old_a)
520 const int64_t diff = old_a_minus_new_a + new_b - old_b;
521
522 // TODO(user): If a variable is at its lower (resp. upper) bound, then
523 // we know that the score will always move in the same direction, so we
524 // might skip the last_affected_variables_ update.
525 jump_scores[var] += weight * static_cast<double>(diff);
526 last_affected_variables_.Set(var);
527 }
528 }
529}
530
531// Note that the code assumes that a column has no duplicates ct indices.
533 int var, int64_t delta, absl::Span<const double> weights,
534 absl::Span<const int64_t> jump_deltas, absl::Span<double> jump_scores,
535 std::vector<int>* constraints_with_changed_violation) {
536 DCHECK(!creation_phase_);
537 DCHECK_NE(delta, 0);
538 if (var >= columns_.size()) return;
539
540 const SpanData& data = columns_[var];
541 int i = data.start;
542 num_ops_ += data.num_pos_literal;
543 for (int k = 0; k < data.num_pos_literal; ++k, ++i) {
544 const int c = ct_buffer_[i];
545 const int64_t v0 = Violation(c);
546 if (delta == 1) {
547 num_false_enforcement_[c]--;
548 DCHECK_GE(num_false_enforcement_[c], 0);
549 if (num_false_enforcement_[c] == 0) {
550 UpdateScoreOnNewlyEnforced(c, weights[c], jump_deltas, jump_scores);
551 } else if (num_false_enforcement_[c] == 1) {
552 const double enforcement_change =
553 weights[c] * static_cast<double>(distances_[c]);
554 UpdateScoreOfEnforcementIncrease(c, enforcement_change, jump_deltas,
555 jump_scores);
556 }
557 } else {
558 num_false_enforcement_[c]++;
559 if (num_false_enforcement_[c] == 1) {
560 UpdateScoreOnNewlyUnenforced(c, weights[c], jump_deltas, jump_scores);
561 } else if (num_false_enforcement_[c] == 2) {
562 const double enforcement_change =
563 weights[c] * static_cast<double>(distances_[c]);
564 UpdateScoreOfEnforcementIncrease(c, -enforcement_change, jump_deltas,
565 jump_scores);
566 }
567 }
568 const int64_t v1 = Violation(c);
569 is_violated_[c] = v1 > 0;
570 if (v1 != v0) {
571 constraints_with_changed_violation->push_back(c);
572 }
573 }
574 num_ops_ += data.num_neg_literal;
575 for (int k = 0; k < data.num_neg_literal; ++k, ++i) {
576 const int c = ct_buffer_[i];
577 const int64_t v0 = Violation(c);
578 if (delta == -1) {
579 num_false_enforcement_[c]--;
580 DCHECK_GE(num_false_enforcement_[c], 0);
581 if (num_false_enforcement_[c] == 0) {
582 UpdateScoreOnNewlyEnforced(c, weights[c], jump_deltas, jump_scores);
583 } else if (num_false_enforcement_[c] == 1) {
584 const double enforcement_change =
585 weights[c] * static_cast<double>(distances_[c]);
586 UpdateScoreOfEnforcementIncrease(c, enforcement_change, jump_deltas,
587 jump_scores);
588 }
589 } else {
590 num_false_enforcement_[c]++;
591 if (num_false_enforcement_[c] == 1) {
592 UpdateScoreOnNewlyUnenforced(c, weights[c], jump_deltas, jump_scores);
593 } else if (num_false_enforcement_[c] == 2) {
594 const double enforcement_change =
595 weights[c] * static_cast<double>(distances_[c]);
596 UpdateScoreOfEnforcementIncrease(c, -enforcement_change, jump_deltas,
597 jump_scores);
598 }
599 }
600 const int64_t v1 = Violation(c);
601 is_violated_[c] = v1 > 0;
602 if (v1 != v0) {
603 constraints_with_changed_violation->push_back(c);
604 }
605 }
606 int j = data.linear_start;
607 num_ops_ += 2 * data.num_linear_entries;
608 for (int k = 0; k < data.num_linear_entries; ++k, ++i, ++j) {
609 const int c = ct_buffer_[i];
610 const int64_t v0 = Violation(c);
611 const int64_t coeff = coeff_buffer_[j];
612
613 if (num_false_enforcement_[c] == 1) {
614 // Only the 1 -> 0 are impacted.
615 // This is the same as the 1->2 transition, but the old 1->0 needs to
616 // be changed from - weight * distance to - weight * new_distance.
617 const int64_t new_distance =
618 domains_[c].Distance(activities_[c] + coeff * delta);
619 if (new_distance != distances_[c]) {
620 UpdateScoreOfEnforcementIncrease(
621 c, -weights[c] * static_cast<double>(distances_[c] - new_distance),
622 jump_deltas, jump_scores);
623 }
624 } else if (num_false_enforcement_[c] == 0) {
625 UpdateScoreOnActivityChange(c, weights[c], coeff * delta, jump_deltas,
626 jump_scores);
627 }
628
629 activities_[c] += coeff * delta;
630 distances_[c] = domains_[c].Distance(activities_[c]);
631 const int64_t v1 = Violation(c);
632 is_violated_[c] = v1 > 0;
633 if (v1 != v0) {
634 constraints_with_changed_violation->push_back(c);
635 }
636 }
637}
638
640 return activities_[c];
641}
642
644 return num_false_enforcement_[c] > 0 ? 0 : distances_[c];
645}
646
648 DCHECK_EQ(is_violated_[c], Violation(c) > 0);
649 return is_violated_[c];
650}
651
652bool LinearIncrementalEvaluator::ReduceBounds(int c, int64_t lb, int64_t ub) {
653 if (domains_[c].Min() >= lb && domains_[c].Max() <= ub) return false;
654 domains_[c] = domains_[c].IntersectionWith(Domain(lb, ub));
655 distances_[c] = domains_[c].Distance(activities_[c]);
656 return true;
657}
658
660 absl::Span<const double> weights) const {
661 double result = 0.0;
662 DCHECK_GE(weights.size(), num_constraints_);
663 for (int c = 0; c < num_constraints_; ++c) {
664 if (num_false_enforcement_[c] > 0) continue;
665 result += weights[c] * static_cast<double>(distances_[c]);
666 }
667 return result;
668}
669
670// Most of the time is spent in this function.
671//
672// TODO(user): We can safely abort early if we know that delta will be >= 0.
673// TODO(user): Maybe we can compute an absolute value instead of removing
674// old_distance.
676 absl::Span<const double> weights, int var, int64_t delta) const {
677 DCHECK_NE(delta, 0);
678 if (var >= columns_.size()) return 0.0;
679 const SpanData& data = columns_[var];
680
681 int i = data.start;
682 double result = 0.0;
683 num_ops_ += data.num_pos_literal;
684 for (int k = 0; k < data.num_pos_literal; ++k, ++i) {
685 const int c = ct_buffer_[i];
686 if (num_false_enforcement_[c] == 0) {
687 // Since delta != 0, we are sure this is an enforced -> unenforced change.
688 DCHECK_EQ(delta, -1);
689 result -= weights[c] * static_cast<double>(distances_[c]);
690 } else {
691 if (delta == 1 && num_false_enforcement_[c] == 1) {
692 result += weights[c] * static_cast<double>(distances_[c]);
693 }
694 }
695 }
696
697 num_ops_ += data.num_neg_literal;
698 for (int k = 0; k < data.num_neg_literal; ++k, ++i) {
699 const int c = ct_buffer_[i];
700 if (num_false_enforcement_[c] == 0) {
701 // Since delta != 0, we are sure this is an enforced -> unenforced change.
702 DCHECK_EQ(delta, 1);
703 result -= weights[c] * static_cast<double>(distances_[c]);
704 } else {
705 if (delta == -1 && num_false_enforcement_[c] == 1) {
706 result += weights[c] * static_cast<double>(distances_[c]);
707 }
708 }
709 }
710
711 int j = data.linear_start;
712 num_ops_ += 2 * data.num_linear_entries;
713 for (int k = 0; k < data.num_linear_entries; ++k, ++i, ++j) {
714 const int c = ct_buffer_[i];
715 if (num_false_enforcement_[c] > 0) continue;
716 const int64_t coeff = coeff_buffer_[j];
717 const int64_t old_distance = distances_[c];
718 const int64_t new_distance =
719 domains_[c].Distance(activities_[c] + coeff * delta);
720 result += weights[c] * static_cast<double>(new_distance - old_distance);
721 }
722
723 return result;
724}
725
727 if (var >= columns_.size()) return false;
728 for (const int c : VarToConstraints(var)) {
729 if (Violation(c) > 0) return true;
730 }
731 return false;
732}
733
735 int var, int64_t current_value, const Domain& var_domain) const {
736 std::vector<int64_t> result = var_domain.FlattenedIntervals();
737 if (var_domain.Size() <= 2 || var >= columns_.size()) return result;
738
739 const SpanData& data = columns_[var];
740 int i = data.start + data.num_pos_literal + data.num_neg_literal;
741 int j = data.linear_start;
742 for (int k = 0; k < data.num_linear_entries; ++k, ++i, ++j) {
743 const int c = ct_buffer_[i];
744 if (num_false_enforcement_[c] > 0) continue;
745
746 // We only consider min / max.
747 // There is a change when we cross the slack.
748 // TODO(user): Deal with holes?
749 const int64_t coeff = coeff_buffer_[j];
750 const int64_t activity = activities_[c] - current_value * coeff;
751
752 const int64_t slack_min = CapSub(domains_[c].Min(), activity);
753 const int64_t slack_max = CapSub(domains_[c].Max(), activity);
754 if (slack_min != std::numeric_limits<int64_t>::min()) {
755 const int64_t ceil_bp = MathUtil::CeilOfRatio(slack_min, coeff);
756 if (ceil_bp != result.back() && var_domain.Contains(ceil_bp)) {
757 result.push_back(ceil_bp);
758 }
759 const int64_t floor_bp = MathUtil::FloorOfRatio(slack_min, coeff);
760 if (floor_bp != result.back() && var_domain.Contains(floor_bp)) {
761 result.push_back(floor_bp);
762 }
763 }
764 if (slack_min != slack_max &&
765 slack_max != std::numeric_limits<int64_t>::min()) {
766 const int64_t ceil_bp = MathUtil::CeilOfRatio(slack_max, coeff);
767 if (ceil_bp != result.back() && var_domain.Contains(ceil_bp)) {
768 result.push_back(ceil_bp);
769 }
770 const int64_t floor_bp = MathUtil::FloorOfRatio(slack_max, coeff);
771 if (floor_bp != result.back() && var_domain.Contains(floor_bp)) {
772 result.push_back(floor_bp);
773 }
774 }
775 }
776
778 return result;
779}
780
782 absl::Span<const int64_t> var_max_variation) {
783 creation_phase_ = false;
784 if (num_constraints_ == 0) return;
785
786 // Compute the total size.
787 // Note that at this point the constraint indices are not "encoded" yet.
788 int total_size = 0;
789 int total_linear_size = 0;
790 tmp_row_sizes_.assign(num_constraints_, 0);
791 tmp_row_num_positive_literals_.assign(num_constraints_, 0);
792 tmp_row_num_negative_literals_.assign(num_constraints_, 0);
793 tmp_row_num_linear_entries_.assign(num_constraints_, 0);
794 for (const auto& column : literal_entries_) {
795 total_size += column.size();
796 for (const auto [c, is_positive] : column) {
797 tmp_row_sizes_[c]++;
798 if (is_positive) {
799 tmp_row_num_positive_literals_[c]++;
800 } else {
801 tmp_row_num_negative_literals_[c]++;
802 }
803 }
804 }
805
806 row_max_variations_.assign(num_constraints_, 0);
807 for (int var = 0; var < var_entries_.size(); ++var) {
808 const int64_t range = var_max_variation[var];
809 const auto& column = var_entries_[var];
810 total_size += column.size();
811 total_linear_size += column.size();
812 for (const auto [c, coeff] : column) {
813 tmp_row_sizes_[c]++;
814 tmp_row_num_linear_entries_[c]++;
815 row_max_variations_[c] =
816 std::max(row_max_variations_[c], range * std::abs(coeff));
817 }
818 }
819
820 // Compactify for faster WeightedViolationDelta().
821 ct_buffer_.reserve(total_size);
822 coeff_buffer_.reserve(total_linear_size);
823 columns_.resize(std::max(literal_entries_.size(), var_entries_.size()));
824 for (int var = 0; var < columns_.size(); ++var) {
825 columns_[var].start = static_cast<int>(ct_buffer_.size());
826 columns_[var].linear_start = static_cast<int>(coeff_buffer_.size());
827 if (var < literal_entries_.size()) {
828 for (const auto [c, is_positive] : literal_entries_[var]) {
829 if (is_positive) {
830 columns_[var].num_pos_literal++;
831 ct_buffer_.push_back(c);
832 }
833 }
834 for (const auto [c, is_positive] : literal_entries_[var]) {
835 if (!is_positive) {
836 columns_[var].num_neg_literal++;
837 ct_buffer_.push_back(c);
838 }
839 }
840 }
841 if (var < var_entries_.size()) {
842 for (const auto [c, coeff] : var_entries_[var]) {
843 columns_[var].num_linear_entries++;
844 ct_buffer_.push_back(c);
845 coeff_buffer_.push_back(coeff);
846 }
847 }
848 }
849
850 // We do not need var_entries_ or literal_entries_ anymore.
851 //
852 // TODO(user): We could delete them before. But at the time of this
853 // optimization, I didn't want to change the behavior of the algorithm at all.
854 gtl::STLClearObject(&var_entries_);
855 gtl::STLClearObject(&literal_entries_);
856
857 // Initialize the SpanData.
858 // Transform tmp_row_sizes_ to starts in the row_var_buffer_.
859 // Transform tmp_row_num_linear_entries_ to starts in the row_coeff_buffer_.
860 int offset = 0;
861 int linear_offset = 0;
862 rows_.resize(num_constraints_);
863 for (int c = 0; c < num_constraints_; ++c) {
864 rows_[c].num_pos_literal = tmp_row_num_positive_literals_[c];
865 rows_[c].num_neg_literal = tmp_row_num_negative_literals_[c];
866 rows_[c].num_linear_entries = tmp_row_num_linear_entries_[c];
867
868 rows_[c].start = offset;
869 offset += tmp_row_sizes_[c];
870 tmp_row_sizes_[c] = rows_[c].start;
871
872 rows_[c].linear_start = linear_offset;
873 linear_offset += tmp_row_num_linear_entries_[c];
874 tmp_row_num_linear_entries_[c] = rows_[c].linear_start;
875 }
876 DCHECK_EQ(offset, total_size);
877 DCHECK_EQ(linear_offset, total_linear_size);
878
879 // Copy data.
880 row_var_buffer_.resize(total_size);
881 row_coeff_buffer_.resize(total_linear_size);
882 for (int var = 0; var < columns_.size(); ++var) {
883 const SpanData& data = columns_[var];
884 int i = data.start;
885 for (int k = 0; k < data.num_pos_literal; ++i, ++k) {
886 const int c = ct_buffer_[i];
887 row_var_buffer_[tmp_row_sizes_[c]++] = var;
888 }
889 }
890 for (int var = 0; var < columns_.size(); ++var) {
891 const SpanData& data = columns_[var];
892 int i = data.start + data.num_pos_literal;
893 for (int k = 0; k < data.num_neg_literal; ++i, ++k) {
894 const int c = ct_buffer_[i];
895 row_var_buffer_[tmp_row_sizes_[c]++] = var;
896 }
897 }
898 for (int var = 0; var < columns_.size(); ++var) {
899 const SpanData& data = columns_[var];
900 int i = data.start + data.num_pos_literal + data.num_neg_literal;
901 int j = data.linear_start;
902 for (int k = 0; k < data.num_linear_entries; ++i, ++j, ++k) {
903 const int c = ct_buffer_[i];
904 row_var_buffer_[tmp_row_sizes_[c]++] = var;
905 row_coeff_buffer_[tmp_row_num_linear_entries_[c]++] = coeff_buffer_[j];
906 }
907 }
908
909 cached_deltas_.assign(columns_.size(), 0);
910 cached_scores_.assign(columns_.size(), 0);
911 last_affected_variables_.ClearAndResize(columns_.size());
912}
913
915 for (const int c : VarToConstraints(var)) {
916 if (domains_[c].NumIntervals() > 2) return false;
917 }
918 return true;
919}
920
921// ----- CompiledConstraint -----
922
924 absl::Span<const int64_t> solution) {
926}
927
929 int var, int64_t old_value,
930 absl::Span<const int64_t> solution_with_new_value) {
931 violation_ += ViolationDelta(var, old_value, solution_with_new_value);
932}
933
935 absl::Span<const int64_t> solution) {
937}
938
939// ----- CompiledConstraintWithProto -----
940
944
946 absl::Span<const int64_t> solution) {
947 for (const int lit : ct_proto_.enforcement_literal()) {
948 if (!LiteralValue(lit, solution)) return 0;
949 }
951}
952
954 int var, int64_t old_value,
955 absl::Span<const int64_t> solution_with_new_value) {
956 bool becomes_enforced = false;
957 bool becomes_unenforced = false;
958 for (const int lit : ct_proto().enforcement_literal()) {
959 if (var == PositiveRef(lit)) {
960 if (LiteralValue(lit, solution_with_new_value) == 1) {
961 becomes_enforced = true;
962 } else {
963 becomes_unenforced = true;
964 }
965 } else if (!LiteralValue(lit, solution_with_new_value)) {
966 // If an enforcement literal stays false, the violation stays 0.
967 return 0;
968 }
969 }
970 if (becomes_enforced) {
971 // New violation (ComputeViolationWhenEnforced()) minus old violation (0).
972 return ComputeViolationWhenEnforced(solution_with_new_value);
973 }
974 if (becomes_unenforced) {
975 // New violation (0) minus old violation (violation()).
976 return -violation();
977 }
978 return ViolationDeltaWhenEnforced(var, old_value, solution_with_new_value);
979}
980
982 const CpModelProto& model_proto) const {
983 std::vector<int> result = sat::UsedVariables(ct_proto_);
984 for (const int i_var : UsedIntervals(ct_proto_)) {
985 const ConstraintProto& interval_proto = model_proto.constraints(i_var);
986 for (const int var : sat::UsedVariables(interval_proto)) {
987 result.push_back(var);
988 }
989 }
991 result.shrink_to_fit();
992 return result;
993}
994
996 int /*var*/, int64_t /*old_value*/,
997 absl::Span<const int64_t> solution_with_new_value) {
998 return ComputeViolationWhenEnforced(solution_with_new_value) - violation();
999}
1000
1001// ----- CompiledBoolXorConstraint -----
1002
1006
1008 absl::Span<const int64_t> solution) {
1009 int64_t sum_of_literals = 0;
1010 for (const int lit : ct_proto().bool_xor().literals()) {
1011 sum_of_literals += LiteralValue(lit, solution);
1012 }
1013 return 1 - (sum_of_literals % 2);
1014}
1015
1017 int /*var*/, int64_t /*old_value*/,
1018 absl::Span<const int64_t> /*solution_with_new_value*/) {
1019 return violation() == 0 ? 1 : -1;
1020}
1021
1022// ----- CompiledLinMaxConstraint -----
1023
1027
1029 absl::Span<const int64_t> solution) {
1030 const int64_t target_value =
1031 ExprValue(ct_proto().lin_max().target(), solution);
1032 int64_t max_of_expressions = std::numeric_limits<int64_t>::min();
1033 for (const LinearExpressionProto& expr : ct_proto().lin_max().exprs()) {
1034 const int64_t expr_value = ExprValue(expr, solution);
1035 max_of_expressions = std::max(max_of_expressions, expr_value);
1036 }
1037 return std::max(target_value - max_of_expressions, int64_t{0});
1038}
1039
1040// ----- CompiledIntProdConstraint -----
1041
1045
1047 absl::Span<const int64_t> solution) {
1048 const int64_t target_value =
1049 ExprValue(ct_proto().int_prod().target(), solution);
1050 int64_t prod_value = 1;
1051 for (const LinearExpressionProto& expr : ct_proto().int_prod().exprs()) {
1052 prod_value *= ExprValue(expr, solution);
1053 }
1054 return std::abs(target_value - prod_value);
1055}
1056
1057// ----- CompiledIntDivConstraint -----
1058
1062
1064 absl::Span<const int64_t> solution) {
1065 const int64_t target_value =
1066 ExprValue(ct_proto().int_div().target(), solution);
1067 DCHECK_EQ(ct_proto().int_div().exprs_size(), 2);
1068 const int64_t div_value = ExprValue(ct_proto().int_div().exprs(0), solution) /
1069 ExprValue(ct_proto().int_div().exprs(1), solution);
1070 return std::abs(target_value - div_value);
1071}
1072
1073// ----- CompiledIntModConstraint -----
1074
1078
1080 absl::Span<const int64_t> solution) {
1081 const int64_t target_value =
1082 ExprValue(ct_proto().int_mod().target(), solution);
1083 DCHECK_EQ(ct_proto().int_mod().exprs_size(), 2);
1084 // Note: The violation computation assumes the modulo is constant.
1085 const int64_t expr_value = ExprValue(ct_proto().int_mod().exprs(0), solution);
1086 const int64_t mod_value = ExprValue(ct_proto().int_mod().exprs(1), solution);
1087 const int64_t rhs = expr_value % mod_value;
1088 if ((expr_value >= 0 && target_value >= 0) ||
1089 (expr_value <= 0 && target_value <= 0)) {
1090 // Easy case.
1091 return std::min({std::abs(target_value - rhs),
1092 std::abs(target_value) + std::abs(mod_value - rhs),
1093 std::abs(rhs) + std::abs(mod_value - target_value)});
1094 } else {
1095 // Different signs.
1096 // We use the sum of the absolute value to have a better gradient.
1097 // We could also use the min of target_move and the expr_move.
1098 return std::abs(target_value) + std::abs(expr_value);
1099 }
1100}
1101
1102// ----- CompiledAllDiffConstraint -----
1103
1107
1109 absl::Span<const int64_t> solution) {
1110 values_.clear();
1111 for (const LinearExpressionProto& expr : ct_proto().all_diff().exprs()) {
1112 values_.push_back(ExprValue(expr, solution));
1113 }
1114 std::sort(values_.begin(), values_.end());
1115
1116 int64_t value = values_[0];
1117 int counter = 1;
1118 int64_t violation = 0;
1119 for (int i = 1; i < values_.size(); ++i) {
1120 const int64_t new_value = values_[i];
1121 if (new_value == value) {
1122 counter++;
1123 } else {
1124 violation += counter * (counter - 1) / 2;
1125 counter = 1;
1126 value = new_value;
1127 }
1128 }
1129 violation += counter * (counter - 1) / 2;
1130 return violation;
1131}
1132
1133// ----- CompiledNoOverlapWithTwoIntervals -----
1134
1135template <bool has_enforcement>
1137 int /*var*/, int64_t /*old_value*/, absl::Span<const int64_t> solution) {
1138 if (has_enforcement) {
1139 for (const int lit : enforcements_) {
1140 if (!LiteralValue(lit, solution)) return -violation_;
1141 }
1142 }
1143
1144 const int64_t s1 = AffineValue(interval1_.start, solution);
1145 const int64_t e1 = AffineValue(interval1_.end, solution);
1146 const int64_t s2 = AffineValue(interval2_.start, solution);
1147 const int64_t e2 = AffineValue(interval2_.end, solution);
1148 const int64_t repair = std::min(e2 - s1, e1 - s2);
1149 if (repair <= 0) return -violation_; // disjoint
1150 return repair - violation_;
1151}
1152
1153template <bool has_enforcement>
1154std::vector<int>
1156 const CpModelProto& /*model_proto*/) const {
1157 std::vector<int> result;
1158 if (has_enforcement) {
1159 for (const int ref : enforcements_) result.push_back(PositiveRef(ref));
1160 }
1161 interval1_.start.AppendVarTo(result);
1162 interval1_.end.AppendVarTo(result);
1163 interval2_.start.AppendVarTo(result);
1164 interval2_.end.AppendVarTo(result);
1166 result.shrink_to_fit();
1167 return result;
1168}
1169
1170// ----- CompiledNoOverlap2dConstraint -----
1171
1173 const ConstraintProto& interval2,
1174 absl::Span<const int64_t> solution) {
1175 for (const int lit : interval1.enforcement_literal()) {
1176 if (!LiteralValue(lit, solution)) return 0;
1177 }
1178 for (const int lit : interval2.enforcement_literal()) {
1179 if (!LiteralValue(lit, solution)) return 0;
1180 }
1181
1182 const int64_t start1 = ExprValue(interval1.interval().start(), solution);
1183 const int64_t end1 = ExprValue(interval1.interval().end(), solution);
1184
1185 const int64_t start2 = ExprValue(interval2.interval().start(), solution);
1186 const int64_t end2 = ExprValue(interval2.interval().end(), solution);
1187
1188 if (start1 >= end2 || start2 >= end1) return 0; // Disjoint.
1189
1190 // We force a min cost of 1 to cover the case where a interval of size 0 is in
1191 // the middle of another interval.
1192 return std::max(std::min(std::min(end2 - start2, end1 - start1),
1193 std::min(end2 - start1, end1 - start2)),
1194 int64_t{1});
1195}
1196
1198 const ConstraintProto& interval2,
1199 absl::Span<const int64_t> solution) {
1200 for (const int lit : interval1.enforcement_literal()) {
1201 if (!LiteralValue(lit, solution)) return 0;
1202 }
1203 for (const int lit : interval2.enforcement_literal()) {
1204 if (!LiteralValue(lit, solution)) return 0;
1205 }
1206
1207 const int64_t start1 = ExprValue(interval1.interval().start(), solution);
1208 const int64_t end1 = ExprValue(interval1.interval().end(), solution);
1209
1210 const int64_t start2 = ExprValue(interval2.interval().start(), solution);
1211 const int64_t end2 = ExprValue(interval2.interval().end(), solution);
1212
1213 return std::max(std::min(end2 - start1, end1 - start2), int64_t{0});
1214}
1215
1219
1221 absl::Span<const int64_t> solution) {
1222 DCHECK_GE(ct_proto().no_overlap_2d().x_intervals_size(), 2);
1223 const int size = ct_proto().no_overlap_2d().x_intervals_size();
1224
1225 int64_t violation = 0;
1226 for (int i = 0; i + 1 < size; ++i) {
1227 const ConstraintProto& x_i =
1228 cp_model_.constraints(ct_proto().no_overlap_2d().x_intervals(i));
1229 const ConstraintProto& y_i =
1230 cp_model_.constraints(ct_proto().no_overlap_2d().y_intervals(i));
1231 for (int j = i + 1; j < size; ++j) {
1232 const ConstraintProto& x_j =
1233 cp_model_.constraints(ct_proto().no_overlap_2d().x_intervals(j));
1234 const ConstraintProto& y_j =
1235 cp_model_.constraints(ct_proto().no_overlap_2d().y_intervals(j));
1236
1237 // TODO(user): Experiment with
1238 // violation +=
1239 // std::max(std::min(NoOverlapMinRepairDistance(x_i, x_j, solution),
1240 // NoOverlapMinRepairDistance(y_i, y_j, solution)),
1241 // int64_t{0});
1242 // Currently, the effect is unclear on 2d packing problems.
1243 violation +=
1244 std::max(std::min(NoOverlapMinRepairDistance(x_i, x_j, solution) *
1247 OverlapOfTwoIntervals(x_i, x_j, solution)),
1248 int64_t{0});
1249 }
1250 }
1251 return violation;
1252}
1253
1254template <bool has_enforcement>
1256 int /*var*/, int64_t /*old_value*/, absl::Span<const int64_t> solution) {
1257 if (has_enforcement) {
1258 for (const int lit : enforcements_) {
1259 if (!LiteralValue(lit, solution)) return -violation_;
1260 }
1261 }
1262
1263 const int64_t x1 = AffineValue(box1_.x_min, solution);
1264 const int64_t X1 = AffineValue(box1_.x_max, solution);
1265 const int64_t x2 = AffineValue(box2_.x_min, solution);
1266 const int64_t X2 = AffineValue(box2_.x_max, solution);
1267 const int64_t repair_x = std::min(X2 - x1, X1 - x2);
1268 if (repair_x <= 0) return -violation_; // disjoint
1269
1270 const int64_t y1 = AffineValue(box1_.y_min, solution);
1271 const int64_t Y1 = AffineValue(box1_.y_max, solution);
1272 const int64_t y2 = AffineValue(box2_.y_min, solution);
1273 const int64_t Y2 = AffineValue(box2_.y_max, solution);
1274 const int64_t repair_y = std::min(Y2 - y1, Y1 - y2);
1275 if (repair_y <= 0) return -violation_; // disjoint
1276
1277 const int64_t overlap_x =
1278 std::min(std::max(std::min(X2 - x2, X1 - x1), int64_t{1}), repair_x);
1279 const int64_t overlap_y =
1280 std::min(std::max(std::min(Y2 - y2, Y1 - y1), int64_t{1}), repair_y);
1281 return std::min(repair_x * overlap_y, repair_y * overlap_x) - violation_;
1282}
1283
1284template <bool has_enforcement>
1285std::vector<int>
1287 const CpModelProto& /*model_proto*/) const {
1288 std::vector<int> result;
1289 if (has_enforcement) {
1290 for (const int ref : enforcements_) result.push_back(PositiveRef(ref));
1291 }
1292 box1_.x_min.AppendVarTo(result);
1293 box1_.x_max.AppendVarTo(result);
1294 box1_.y_min.AppendVarTo(result);
1295 box1_.y_max.AppendVarTo(result);
1296 box2_.x_min.AppendVarTo(result);
1297 box2_.x_max.AppendVarTo(result);
1298 box2_.y_min.AppendVarTo(result);
1299 box2_.y_max.AppendVarTo(result);
1301 result.shrink_to_fit();
1302 return result;
1303}
1304
1305// ----- CompiledCircuitConstraint -----
1306
1307// The violation of a circuit has three parts:
1308// 1. Flow imbalance, maintained by the linear part.
1309// 2. The number of non-skipped SCCs in the graph minus 1.
1310// 3. The number of non-skipped SCCs that cannot be reached from any other
1311// component minus 1.
1312//
1313// #3 is not necessary for correctness, but makes the function much smoother.
1314//
1315// The only difference between single and multi circuit is flow balance at the
1316// depot, so we use the same compiled constraint for both.
1318 public:
1320 ~CompiledCircuitConstraint() override = default;
1321
1323 absl::Span<const int64_t> solution) override;
1324 void PerformMove(int var, int64_t old_value,
1325 absl::Span<const int64_t> new_solution) override;
1327 int var, int64_t old_value,
1328 absl::Span<const int64_t> solution_with_new_value) override;
1329
1330 private:
1331 struct SccOutput {
1332 void emplace_back(const int* start, const int* end);
1333 void reset(int num_nodes);
1334
1335 int num_components = 0;
1336 std::vector<bool> skipped;
1337 std::vector<int> root;
1338 };
1339 void InitGraph(absl::Span<const int64_t> solution);
1340 bool UpdateGraph(int var, int64_t value);
1341 int64_t ViolationForCurrentGraph();
1342
1343 absl::flat_hash_map<int, std::vector<int>> arcs_by_lit_;
1344 absl::Span<const int> literals_;
1345 absl::Span<const int> tails_;
1346 absl::Span<const int> heads_;
1347 // Stores the currently active arcs per tail node.
1348 std::vector<DenseSet<int>> graph_;
1349 SccOutput sccs_;
1350 SccOutput committed_sccs_;
1351 std::vector<bool> has_in_arc_;
1353 scc_finder_;
1354};
1355
1356void CompiledCircuitConstraint::SccOutput::emplace_back(int const* start,
1357 int const* end) {
1358 const int root_node = *start;
1359 const int size = end - start;
1360 if (size > 1) {
1361 ++num_components;
1362 }
1363 for (; start != end; ++start) {
1364 root[*start] = root_node;
1365 skipped[*start] = (size == 1);
1366 }
1367}
1368void CompiledCircuitConstraint::SccOutput::reset(int num_nodes) {
1369 num_components = 0;
1370 root.clear();
1371 root.resize(num_nodes);
1372 skipped.clear();
1373 skipped.resize(num_nodes);
1374}
1375
1379 const bool routes = ct_proto.has_routes();
1380 tails_ = routes ? ct_proto.routes().tails() : ct_proto.circuit().tails();
1381 heads_ = absl::MakeConstSpan(routes ? ct_proto.routes().heads()
1382 : ct_proto.circuit().heads());
1383 literals_ = absl::MakeConstSpan(routes ? ct_proto.routes().literals()
1384 : ct_proto.circuit().literals());
1385 graph_.resize(*absl::c_max_element(tails_) + 1);
1386 for (int i = 0; i < literals_.size(); ++i) {
1387 arcs_by_lit_[literals_[i]].push_back(i);
1388 }
1389}
1390
1391void CompiledCircuitConstraint::InitGraph(absl::Span<const int64_t> solution) {
1392 for (DenseSet<int>& edges : graph_) {
1393 edges.clear();
1394 }
1395 for (int i = 0; i < tails_.size(); ++i) {
1396 if (!LiteralValue(literals_[i], solution)) continue;
1397 graph_[tails_[i]].insert(heads_[i]);
1398 }
1399}
1400
1401bool CompiledCircuitConstraint::UpdateGraph(int var, int64_t value) {
1402 bool needs_update = false;
1403 const int enabled_lit =
1404 value != 0 ? PositiveRef(var) : NegatedRef(PositiveRef(var));
1405 const int disabled_lit = NegatedRef(enabled_lit);
1406 for (const int arc : arcs_by_lit_[disabled_lit]) {
1407 const int tail = tails_[arc];
1408 const int head = heads_[arc];
1409 // Removing a self arc cannot change violation.
1410 needs_update = needs_update || tail != head;
1411 graph_[tails_[arc]].erase(heads_[arc]);
1412 }
1413 for (const int arc : arcs_by_lit_[enabled_lit]) {
1414 const int tail = tails_[arc];
1415 const int head = heads_[arc];
1416 // Adding an arc can only change violation if it connects new SCCs.
1417 needs_update = needs_update ||
1418 committed_sccs_.root[tail] != committed_sccs_.root[head];
1419 graph_[tails_[arc]].insert(heads_[arc]);
1420 }
1421 return needs_update;
1422}
1423
1425 int var, int64_t, absl::Span<const int64_t> new_solution) {
1426 UpdateGraph(var, new_solution[var]);
1427 violation_ = ViolationForCurrentGraph();
1428 std::swap(committed_sccs_, sccs_);
1429}
1430
1432 absl::Span<const int64_t> solution) {
1433 InitGraph(solution);
1434 int64_t result = ViolationForCurrentGraph();
1435 std::swap(committed_sccs_, sccs_);
1436 return result;
1437}
1438
1440 int var, int64_t old_value,
1441 absl::Span<const int64_t> solution_with_new_value) {
1442 int64_t result = 0;
1443 if (UpdateGraph(var, solution_with_new_value[var])) {
1444 result = ViolationForCurrentGraph() - violation_;
1445 }
1446 UpdateGraph(var, old_value);
1447 return result;
1448}
1449
1450int64_t CompiledCircuitConstraint::ViolationForCurrentGraph() {
1451 const int num_nodes = graph_.size();
1452 sccs_.reset(num_nodes);
1453 scc_finder_.FindStronglyConnectedComponents(num_nodes, graph_, &sccs_);
1454 // Skipping all nodes causes off-by-one errors below, so it's simpler to
1455 // handle explicitly.
1456 if (sccs_.num_components == 0) return 0;
1457 // Count the number of SCCs that have inbound cross-component arcs
1458 // as a smoother measure of progress towards strong connectivity.
1459 int num_half_connected_components = 0;
1460 has_in_arc_.clear();
1461 has_in_arc_.resize(num_nodes, false);
1462 for (int tail = 0; tail < graph_.size(); ++tail) {
1463 if (sccs_.skipped[tail]) continue;
1464 for (const int head : graph_[tail]) {
1465 const int head_root = sccs_.root[head];
1466 if (sccs_.root[tail] == head_root) continue;
1467 if (has_in_arc_[head_root]) continue;
1468 if (sccs_.skipped[head_root]) continue;
1469 has_in_arc_[head_root] = true;
1470 ++num_half_connected_components;
1471 }
1472 }
1473 const int64_t violation = sccs_.num_components - 1 + sccs_.num_components -
1474 num_half_connected_components - 1 +
1475 (ct_proto().has_routes() ? sccs_.skipped[0] : 0);
1476 VLOG(2) << "#SCCs=" << sccs_.num_components << " #nodes=" << num_nodes
1477 << " #half_connected_components=" << num_half_connected_components
1478 << " violation=" << violation;
1479 return violation;
1480}
1481
1483 const ConstraintProto& ct_proto) {
1484 const bool routes = ct_proto.has_routes();
1485 auto heads = routes ? ct_proto.routes().heads() : ct_proto.circuit().heads();
1486 auto tails = routes ? ct_proto.routes().tails() : ct_proto.circuit().tails();
1487 auto literals =
1488 routes ? ct_proto.routes().literals() : ct_proto.circuit().literals();
1489
1490 std::vector<std::vector<int>> inflow_lits;
1491 std::vector<std::vector<int>> outflow_lits;
1492 for (int i = 0; i < heads.size(); ++i) {
1493 if (heads[i] >= inflow_lits.size()) {
1494 inflow_lits.resize(heads[i] + 1);
1495 }
1496 inflow_lits[heads[i]].push_back(literals[i]);
1497 if (tails[i] >= outflow_lits.size()) {
1498 outflow_lits.resize(tails[i] + 1);
1499 }
1500 outflow_lits[tails[i]].push_back(literals[i]);
1501 }
1502 if (routes) {
1503 const int depot_net_flow = linear_evaluator.NewConstraint({0, 0});
1504 for (const int lit : inflow_lits[0]) {
1505 linear_evaluator.AddLiteral(depot_net_flow, lit, 1);
1506 }
1507 for (const int lit : outflow_lits[0]) {
1508 linear_evaluator.AddLiteral(depot_net_flow, lit, -1);
1509 }
1510 }
1511 for (int i = routes ? 1 : 0; i < inflow_lits.size(); ++i) {
1512 const int inflow_ct = linear_evaluator.NewConstraint({1, 1});
1513 for (const int lit : inflow_lits[i]) {
1514 linear_evaluator.AddLiteral(inflow_ct, lit);
1515 }
1516 }
1517 for (int i = routes ? 1 : 0; i < outflow_lits.size(); ++i) {
1518 const int outflow_ct = linear_evaluator.NewConstraint({1, 1});
1519 for (const int lit : outflow_lits[i]) {
1520 linear_evaluator.AddLiteral(outflow_ct, lit);
1521 }
1522 }
1523}
1524
1525// ----- LsEvaluator -----
1526
1528 const SatParameters& params, TimeLimit* time_limit)
1529 : cp_model_(cp_model), params_(params), time_limit_(time_limit) {
1530 var_to_constraints_.resize(cp_model_.variables_size());
1531 var_to_dtime_estimate_.resize(cp_model_.variables_size());
1532 jump_value_optimal_.resize(cp_model_.variables_size(), true);
1533 num_violated_constraint_per_var_ignoring_objective_.assign(
1534 cp_model_.variables_size(), 0);
1535
1536 std::vector<bool> ignored_constraints(cp_model_.constraints_size(), false);
1537 std::vector<ConstraintProto> additional_constraints;
1538 CompileConstraintsAndObjective(ignored_constraints, additional_constraints);
1539 BuildVarConstraintGraph();
1540 violated_constraints_.reserve(NumEvaluatorConstraints());
1541}
1542
1544 const CpModelProto& cp_model, const SatParameters& params,
1545 const std::vector<bool>& ignored_constraints,
1546 absl::Span<const ConstraintProto> additional_constraints,
1547 TimeLimit* time_limit)
1548 : cp_model_(cp_model), params_(params), time_limit_(time_limit) {
1549 var_to_constraints_.resize(cp_model_.variables_size());
1550 var_to_dtime_estimate_.resize(cp_model_.variables_size());
1551 jump_value_optimal_.resize(cp_model_.variables_size(), true);
1552 num_violated_constraint_per_var_ignoring_objective_.assign(
1553 cp_model_.variables_size(), 0);
1554 CompileConstraintsAndObjective(ignored_constraints, additional_constraints);
1555 BuildVarConstraintGraph();
1556 violated_constraints_.reserve(NumEvaluatorConstraints());
1557}
1558
1559void LsEvaluator::BuildVarConstraintGraph() {
1560 // Clear the var <-> constraint graph.
1561 for (std::vector<int>& ct_indices : var_to_constraints_) ct_indices.clear();
1562 constraint_to_vars_.resize(constraints_.size());
1563
1564 // Build the var <-> constraint graph.
1565 for (int ct_index = 0; ct_index < constraints_.size(); ++ct_index) {
1566 constraint_to_vars_[ct_index] =
1567 constraints_[ct_index]->UsedVariables(cp_model_);
1568
1569 const double dtime = 1e-8 * constraint_to_vars_[ct_index].size();
1570 for (const int var : constraint_to_vars_[ct_index]) {
1571 var_to_constraints_[var].push_back(ct_index);
1572 var_to_dtime_estimate_[var] += dtime;
1573 }
1574 }
1575
1576 // Remove duplicates.
1577 for (std::vector<int>& constraints : var_to_constraints_) {
1578 gtl::STLSortAndRemoveDuplicates(&constraints);
1579 }
1580 for (std::vector<int>& vars : constraint_to_vars_) {
1582 }
1583
1584 // Scan the model to decide if a variable is linked to a convex evaluation.
1585 jump_value_optimal_.resize(cp_model_.variables_size());
1586 for (int i = 0; i < cp_model_.variables_size(); ++i) {
1587 if (!var_to_constraints_[i].empty()) {
1588 jump_value_optimal_[i] = false;
1589 continue;
1590 }
1591
1592 const IntegerVariableProto& var_proto = cp_model_.variables(i);
1593 if (var_proto.domain_size() == 2 && var_proto.domain(0) == 0 &&
1594 var_proto.domain(1) == 1) {
1595 // Boolean variables violation change is always convex.
1596 jump_value_optimal_[i] = true;
1597 continue;
1598 }
1599
1600 jump_value_optimal_[i] = linear_evaluator_.ViolationChangeIsConvex(i);
1601 }
1602}
1603
1604void LsEvaluator::CompileOneConstraint(const ConstraintProto& ct) {
1605 switch (ct.constraint_case()) {
1607 // Encoding using enforcement literal is slightly more efficient.
1608 const int ct_index = linear_evaluator_.NewConstraint(Domain(1, 1));
1609 for (const int lit : ct.enforcement_literal()) {
1610 linear_evaluator_.AddEnforcementLiteral(ct_index, lit);
1611 }
1612 for (const int lit : ct.bool_or().literals()) {
1613 linear_evaluator_.AddEnforcementLiteral(ct_index, NegatedRef(lit));
1614 }
1615 break;
1616 }
1618 const int num_literals = ct.bool_and().literals_size();
1619 const int ct_index =
1620 linear_evaluator_.NewConstraint(Domain(num_literals));
1621 for (const int lit : ct.enforcement_literal()) {
1622 linear_evaluator_.AddEnforcementLiteral(ct_index, lit);
1623 }
1624 for (const int lit : ct.bool_and().literals()) {
1625 linear_evaluator_.AddLiteral(ct_index, lit);
1626 }
1627 break;
1628 }
1630 DCHECK(ct.enforcement_literal().empty());
1631 const int ct_index = linear_evaluator_.NewConstraint({0, 1});
1632 for (const int lit : ct.at_most_one().literals()) {
1633 linear_evaluator_.AddLiteral(ct_index, lit);
1634 }
1635 break;
1636 }
1638 DCHECK(ct.enforcement_literal().empty());
1639 const int ct_index = linear_evaluator_.NewConstraint({1, 1});
1640 for (const int lit : ct.exactly_one().literals()) {
1641 linear_evaluator_.AddLiteral(ct_index, lit);
1642 }
1643 break;
1644 }
1646 constraints_.emplace_back(new CompiledBoolXorConstraint(ct));
1647 break;
1648 }
1650 constraints_.emplace_back(new CompiledAllDiffConstraint(ct));
1651 break;
1652 }
1654 // This constraint is split into linear precedences and its max
1655 // maintenance.
1656 const LinearExpressionProto& target = ct.lin_max().target();
1657 for (const LinearExpressionProto& expr : ct.lin_max().exprs()) {
1658 const int64_t max_value =
1659 ExprMax(target, cp_model_) - ExprMin(expr, cp_model_);
1660 const int precedence_index =
1661 linear_evaluator_.NewConstraint({0, max_value});
1662 linear_evaluator_.AddLinearExpression(precedence_index, target, 1);
1663 linear_evaluator_.AddLinearExpression(precedence_index, expr, -1);
1664 }
1665
1666 // Penalty when target > all expressions.
1667 constraints_.emplace_back(new CompiledLinMaxConstraint(ct));
1668 break;
1669 }
1671 constraints_.emplace_back(new CompiledIntProdConstraint(ct));
1672 break;
1673 }
1675 constraints_.emplace_back(new CompiledIntDivConstraint(ct));
1676 break;
1677 }
1679 DCHECK_EQ(ExprMin(ct.int_mod().exprs(1), cp_model_),
1680 ExprMax(ct.int_mod().exprs(1), cp_model_));
1681 constraints_.emplace_back(new CompiledIntModConstraint(ct));
1682 break;
1683 }
1685 const Domain domain = ReadDomainFromProto(ct.linear());
1686 const int ct_index = linear_evaluator_.NewConstraint(domain);
1687 for (const int lit : ct.enforcement_literal()) {
1688 linear_evaluator_.AddEnforcementLiteral(ct_index, lit);
1689 }
1690 for (int i = 0; i < ct.linear().vars_size(); ++i) {
1691 const int var = ct.linear().vars(i);
1692 const int64_t coeff = ct.linear().coeffs(i);
1693 linear_evaluator_.AddTerm(ct_index, var, coeff);
1694 }
1695 break;
1696 }
1698 const int size = ct.no_overlap().intervals_size();
1699 if (size <= 1) break;
1700 if (size > params_.feasibility_jump_max_expanded_constraint_size()) {
1701 // Similar code to the kCumulative constraint.
1702 // The violation will be the area above the capacity.
1703 LinearExpressionProto one;
1704 one.set_offset(1);
1705 std::vector<int> enforcement_literals;
1706 for (const int lit : ct.enforcement_literal()) {
1707 enforcement_literals.push_back(lit);
1708 }
1709 std::vector<std::optional<int>> is_active;
1710 std::vector<LinearExpressionProto> times;
1711 std::vector<LinearExpressionProto> demands;
1712 const int num_intervals = ct.no_overlap().intervals().size();
1713 for (int i = 0; i < num_intervals; ++i) {
1714 const ConstraintProto& interval_ct =
1715 cp_model_.constraints(ct.no_overlap().intervals(i));
1716 if (interval_ct.enforcement_literal().empty()) {
1717 is_active.push_back(std::nullopt);
1718 is_active.push_back(std::nullopt);
1719 } else {
1720 CHECK_EQ(interval_ct.enforcement_literal().size(), 1);
1721 is_active.push_back(interval_ct.enforcement_literal(0));
1722 is_active.push_back(interval_ct.enforcement_literal(0));
1723 }
1724
1725 times.push_back(interval_ct.interval().start());
1726 times.push_back(LinearExprSum(interval_ct.interval().start(),
1727 interval_ct.interval().size()));
1728 demands.push_back(one);
1729 demands.push_back(NegatedLinearExpression(one));
1730 }
1731 constraints_.emplace_back(new CompiledReservoirConstraint(
1732 std::move(enforcement_literals), std::move(one),
1733 std::move(is_active), std::move(times), std::move(demands)));
1734 } else {
1735 // We expand the no_overlap constraints into a quadratic number of
1736 // disjunctions.
1737 for (int i = 0; i + 1 < size; ++i) {
1738 const ConstraintProto& proto_i =
1739 cp_model_.constraints(ct.no_overlap().intervals(i));
1740 const IntervalConstraintProto& interval_i = proto_i.interval();
1741 const int64_t min_start_i = ExprMin(interval_i.start(), cp_model_);
1742 const int64_t max_end_i = ExprMax(interval_i.end(), cp_model_);
1743 for (int j = i + 1; j < size; ++j) {
1744 const ConstraintProto& proto_j =
1745 cp_model_.constraints(ct.no_overlap().intervals(j));
1746 const IntervalConstraintProto& interval_j = proto_j.interval();
1747 const int64_t min_start_j = ExprMin(interval_j.start(), cp_model_);
1748 const int64_t max_end_j = ExprMax(interval_j.end(), cp_model_);
1749 if (min_start_i >= max_end_j || min_start_j >= max_end_i) continue;
1750
1751 const bool has_enforcement =
1752 !ct.enforcement_literal().empty() ||
1753 !proto_i.enforcement_literal().empty() ||
1754 !proto_j.enforcement_literal().empty();
1755 if (has_enforcement) {
1756 constraints_.emplace_back(
1757 new CompiledNoOverlapWithTwoIntervals<true>(
1758 ct.enforcement_literal(), proto_i, proto_j));
1759 } else {
1760 constraints_.emplace_back(
1761 new CompiledNoOverlapWithTwoIntervals<false>(
1762 /*enforcement_literals=*/{}, proto_i, proto_j));
1763 }
1764 }
1765 }
1766 }
1767 break;
1768 }
1770 LinearExpressionProto capacity = ct.cumulative().capacity();
1771 std::vector<int> enforcement_literals;
1772 for (const int lit : ct.enforcement_literal()) {
1773 enforcement_literals.push_back(lit);
1774 }
1775 std::vector<std::optional<int>> is_active;
1776 std::vector<LinearExpressionProto> times;
1777 std::vector<LinearExpressionProto> demands;
1778 const int num_intervals = ct.cumulative().intervals().size();
1779 for (int i = 0; i < num_intervals; ++i) {
1780 const ConstraintProto& interval_ct =
1781 cp_model_.constraints(ct.cumulative().intervals(i));
1782 if (interval_ct.enforcement_literal().empty()) {
1783 is_active.push_back(std::nullopt);
1784 is_active.push_back(std::nullopt);
1785 } else {
1786 CHECK_EQ(interval_ct.enforcement_literal().size(), 1);
1787 is_active.push_back(interval_ct.enforcement_literal(0));
1788 is_active.push_back(interval_ct.enforcement_literal(0));
1789 }
1790
1791 // Start.
1792 times.push_back(interval_ct.interval().start());
1793 demands.push_back(ct.cumulative().demands(i));
1794
1795 // End.
1796 // I tried 3 alternatives: end, max(end, start+size) and just start +
1797 // size. The most performing one was "start + size" on the multi-mode
1798 // RCPSP.
1799 //
1800 // Note that for fixed size, this do not matter. It is easy enough to
1801 // try any expression by creating a small wrapper class to use instead
1802 // of a LinearExpressionProto for time.
1803 times.push_back(LinearExprSum(interval_ct.interval().start(),
1804 interval_ct.interval().size()));
1805 demands.push_back(NegatedLinearExpression(ct.cumulative().demands(i)));
1806 }
1807
1808 constraints_.emplace_back(new CompiledReservoirConstraint(
1809 std::move(enforcement_literals), std::move(capacity),
1810 std::move(is_active), std::move(times), std::move(demands)));
1811 break;
1812 }
1814 const auto& x_intervals = ct.no_overlap_2d().x_intervals();
1815 const auto& y_intervals = ct.no_overlap_2d().y_intervals();
1816 const int size = x_intervals.size();
1817 if (size <= 1) break;
1818 if (size == 2 ||
1819 size > params_.feasibility_jump_max_expanded_constraint_size()) {
1820 CompiledNoOverlap2dConstraint* no_overlap_2d =
1821 new CompiledNoOverlap2dConstraint(ct, cp_model_);
1822 constraints_.emplace_back(no_overlap_2d);
1823 break;
1824 }
1825
1826 for (int i = 0; i + 1 < size; ++i) {
1827 const ConstraintProto& x_proto_i =
1828 cp_model_.constraints(x_intervals[i]);
1829 const IntervalConstraintProto& x_interval_i = x_proto_i.interval();
1830 const int64_t x_min_start_i = ExprMin(x_interval_i.start(), cp_model_);
1831 const int64_t x_max_end_i = ExprMax(x_interval_i.end(), cp_model_);
1832 const ConstraintProto& y_proto_i =
1833 cp_model_.constraints(y_intervals[i]);
1834 const IntervalConstraintProto& y_interval_i = y_proto_i.interval();
1835 const int64_t y_min_start_i = ExprMin(y_interval_i.start(), cp_model_);
1836 const int64_t y_max_end_i = ExprMax(y_interval_i.end(), cp_model_);
1837 for (int j = i + 1; j < size; ++j) {
1838 const ConstraintProto& x_proto_j =
1839 cp_model_.constraints(x_intervals[j]);
1840 const IntervalConstraintProto& x_interval_j = x_proto_j.interval();
1841 const int64_t x_min_start_j =
1842 ExprMin(x_interval_j.start(), cp_model_);
1843 const int64_t x_max_end_j = ExprMax(x_interval_j.end(), cp_model_);
1844 const ConstraintProto& y_proto_j =
1845 cp_model_.constraints(y_intervals[j]);
1846 const IntervalConstraintProto& y_interval_j = y_proto_j.interval();
1847 const int64_t y_min_start_j =
1848 ExprMin(y_interval_j.start(), cp_model_);
1849 const int64_t y_max_end_j = ExprMax(y_interval_j.end(), cp_model_);
1850 if (x_min_start_i >= x_max_end_j || x_min_start_j >= x_max_end_i ||
1851 y_min_start_i >= y_max_end_j || y_min_start_j >= y_max_end_i) {
1852 continue;
1853 }
1854
1855 const bool has_enforcement =
1856 !ct.enforcement_literal().empty() ||
1857 !x_proto_i.enforcement_literal().empty() ||
1858 !x_proto_j.enforcement_literal().empty() ||
1859 !y_proto_i.enforcement_literal().empty() ||
1860 !y_proto_j.enforcement_literal().empty();
1861 if (has_enforcement) {
1862 constraints_.emplace_back(new CompiledNoOverlap2dWithTwoBoxes<true>(
1863 ct.enforcement_literal(), x_proto_i, y_proto_i, x_proto_j,
1864 y_proto_j));
1865 } else {
1866 constraints_.emplace_back(
1867 new CompiledNoOverlap2dWithTwoBoxes<false>(
1868 /*enforcement_literals=*/{}, x_proto_i, y_proto_i,
1869 x_proto_j, y_proto_j));
1870 }
1871 }
1872 }
1873 break;
1874 }
1877 constraints_.emplace_back(new CompiledCircuitConstraint(ct));
1878 AddCircuitFlowConstraints(linear_evaluator_, ct);
1879 break;
1880 default:
1881 VLOG(1) << "Not implemented: " << ct.constraint_case();
1882 break;
1883 }
1884}
1885
1886void LsEvaluator::CompileConstraintsAndObjective(
1887 const std::vector<bool>& ignored_constraints,
1888 absl::Span<const ConstraintProto> additional_constraints) {
1889 constraints_.clear();
1890
1891 // The first compiled constraint is always the objective if present.
1892 if (cp_model_.has_objective()) {
1893 const int ct_index = linear_evaluator_.NewConstraint(
1894 cp_model_.objective().domain().empty()
1896 : ReadDomainFromProto(cp_model_.objective()));
1897 DCHECK_EQ(0, ct_index);
1898 for (int i = 0; i < cp_model_.objective().vars_size(); ++i) {
1899 const int var = cp_model_.objective().vars(i);
1900 const int64_t coeff = cp_model_.objective().coeffs(i);
1901 linear_evaluator_.AddTerm(ct_index, var, coeff);
1902 }
1903 }
1904
1905 TimeLimitCheckEveryNCalls checker(1000, time_limit_);
1906 for (int c = 0; c < cp_model_.constraints_size(); ++c) {
1907 if (ignored_constraints[c]) continue;
1908 CompileOneConstraint(cp_model_.constraints(c));
1909 if (checker.LimitReached()) break;
1910 }
1911
1912 for (const ConstraintProto& ct : additional_constraints) {
1913 CompileOneConstraint(ct);
1914 }
1915
1916 // Make sure we have access to the data in an efficient way.
1917 std::vector<int64_t> var_max_variations(cp_model_.variables().size());
1918 for (int var = 0; var < cp_model_.variables().size(); ++var) {
1919 const auto& domain = cp_model_.variables(var).domain();
1920 var_max_variations[var] = domain[domain.size() - 1] - domain[0];
1921 }
1922 linear_evaluator_.PrecomputeCompactView(var_max_variations);
1923}
1924
1925bool LsEvaluator::ReduceObjectiveBounds(int64_t lb, int64_t ub) {
1926 if (!cp_model_.has_objective()) return false;
1927 if (linear_evaluator_.ReduceBounds(/*c=*/0, lb, ub)) {
1929 return true;
1930 }
1931 return false;
1932}
1933
1934void LsEvaluator::ComputeAllViolations(absl::Span<const int64_t> solution) {
1935 // Linear constraints.
1936 linear_evaluator_.ComputeInitialActivities(solution);
1937
1938 // Generic constraints.
1939 for (const auto& ct : constraints_) {
1940 ct->InitializeViolation(solution);
1941 }
1942
1943 RecomputeViolatedList(/*linear_only=*/false);
1944}
1945
1947 absl::Span<const int64_t> solution) {
1948 // Generic constraints.
1949 for (const auto& ct : constraints_) {
1950 ct->InitializeViolation(solution);
1951 }
1952}
1953
1955 int var, int64_t old_value, absl::Span<const int64_t> new_solution) {
1956 for (const int general_ct_index : var_to_constraints_[var]) {
1957 const int c = general_ct_index + linear_evaluator_.num_constraints();
1958 const int64_t v0 = constraints_[general_ct_index]->violation();
1959 constraints_[general_ct_index]->PerformMove(var, old_value, new_solution);
1960 const int64_t violation_delta =
1961 constraints_[general_ct_index]->violation() - v0;
1962 if (violation_delta != 0) {
1963 last_update_violation_changes_.push_back(c);
1964 }
1965 }
1966}
1967
1968void LsEvaluator::UpdateLinearScores(int var, int64_t old_value,
1969 int64_t new_value,
1970 absl::Span<const double> weights,
1971 absl::Span<const int64_t> jump_deltas,
1972 absl::Span<double> jump_scores) {
1973 DCHECK(RefIsPositive(var));
1974 if (old_value == new_value) return;
1975 last_update_violation_changes_.clear();
1976 linear_evaluator_.ClearAffectedVariables();
1977 linear_evaluator_.UpdateVariableAndScores(var, new_value - old_value, weights,
1978 jump_deltas, jump_scores,
1979 &last_update_violation_changes_);
1980}
1981
1983 // Maintain the list of violated constraints.
1984 dtime_ += 1e-8 * last_update_violation_changes_.size();
1985 for (const int c : last_update_violation_changes_) {
1987 }
1988}
1989
1991 int64_t evaluation = 0;
1992
1993 // Process the linear part.
1994 for (int i = 0; i < linear_evaluator_.num_constraints(); ++i) {
1995 evaluation += linear_evaluator_.Violation(i);
1996 DCHECK_GE(linear_evaluator_.Violation(i), 0);
1997 }
1998
1999 // Process the generic constraint part.
2000 for (const auto& ct : constraints_) {
2001 evaluation += ct->violation();
2002 DCHECK_GE(ct->violation(), 0);
2003 }
2004 return evaluation;
2005}
2006
2008 DCHECK(cp_model_.has_objective());
2009 return linear_evaluator_.Activity(/*c=*/0);
2010}
2011
2013 return linear_evaluator_.num_constraints();
2014}
2015
2017 return static_cast<int>(constraints_.size());
2018}
2019
2021 return linear_evaluator_.num_constraints() +
2022 static_cast<int>(constraints_.size());
2023}
2024
2026 int result = 0;
2027 for (int c = 0; c < linear_evaluator_.num_constraints(); ++c) {
2028 if (linear_evaluator_.Violation(c) > 0) {
2029 ++result;
2030 }
2031 }
2032 for (const auto& constraint : constraints_) {
2033 if (constraint->violation() > 0) {
2034 ++result;
2035 }
2036 }
2037 return result;
2038}
2039
2040int64_t LsEvaluator::Violation(int c) const {
2041 if (c < linear_evaluator_.num_constraints()) {
2042 return linear_evaluator_.Violation(c);
2043 } else {
2044 return constraints_[c - linear_evaluator_.num_constraints()]->violation();
2045 }
2046}
2047
2048bool LsEvaluator::IsViolated(int c) const {
2049 if (c < linear_evaluator_.num_constraints()) {
2050 return linear_evaluator_.IsViolated(c);
2051 } else {
2052 return constraints_[c - linear_evaluator_.num_constraints()]->violation() >
2053 0;
2054 }
2055}
2056
2057double LsEvaluator::WeightedViolation(absl::Span<const double> weights) const {
2058 DCHECK_EQ(weights.size(), NumEvaluatorConstraints());
2059 double result = linear_evaluator_.WeightedViolation(weights);
2060
2061 const int num_linear_constraints = linear_evaluator_.num_constraints();
2062 for (int c = 0; c < constraints_.size(); ++c) {
2063 result += static_cast<double>(constraints_[c]->violation()) *
2064 weights[num_linear_constraints + c];
2065 }
2066 return result;
2067}
2068
2070 bool linear_only, absl::Span<const double> weights, int var, int64_t delta,
2071 absl::Span<int64_t> mutable_solution) const {
2072 double result = linear_evaluator_.WeightedViolationDelta(weights, var, delta);
2073 if (linear_only) return result;
2074
2075 // We change the mutable solution here, and restore it after the evaluation.
2076 const int64_t old_value = mutable_solution[var];
2077 mutable_solution[var] += delta;
2078
2079 // We assume linear time delta computation in number of variables.
2080 // TODO(user): refine on a per constraint basis.
2081 dtime_ += var_to_dtime_estimate_[var];
2082
2083 const int num_linear_constraints = linear_evaluator_.num_constraints();
2084 const std::unique_ptr<CompiledConstraint>* data = constraints_.data();
2085 const auto non_linear_weights = weights.subspan(num_linear_constraints);
2086 for (const int ct_index : var_to_constraints_[var]) {
2087 DCHECK_LT(ct_index, constraints_.size());
2088 const int64_t ct_delta =
2089 data[ct_index]->ViolationDelta(var, old_value, mutable_solution);
2090 result += static_cast<double>(ct_delta) * non_linear_weights[ct_index];
2091 }
2092
2093 // Restore.
2094 mutable_solution[var] = old_value;
2095 return result;
2096}
2097
2099 int var) const {
2100 return jump_value_optimal_[var];
2101}
2102
2104 num_violated_constraint_per_var_ignoring_objective_.assign(
2105 cp_model_.variables_size(), 0);
2106 violated_constraints_.clear();
2107 const int num_constraints =
2109 for (int c = 0; c < num_constraints; ++c) {
2111 }
2112}
2113
2114void LsEvaluator::UpdateViolatedList(const int c) {
2115 if (Violation(c) > 0) {
2116 auto [it, inserted] = violated_constraints_.insert(c);
2117 // The constraint is violated. Add if needed.
2118 if (!inserted) return;
2119 if (IsObjectiveConstraint(c)) return;
2120 dtime_ += 1e-8 * ConstraintToVars(c).size();
2121 for (const int v : ConstraintToVars(c)) {
2122 num_violated_constraint_per_var_ignoring_objective_[v] += 1;
2123 }
2124 return;
2125 }
2126 if (violated_constraints_.erase(c) == 1) {
2127 if (IsObjectiveConstraint(c)) return;
2128 dtime_ += 1e-8 * ConstraintToVars(c).size();
2129 for (const int v : ConstraintToVars(c)) {
2130 num_violated_constraint_per_var_ignoring_objective_[v] -= 1;
2131 }
2132 }
2133}
2134
2135// Note that since we have our own ViolationDelta() implementation this is
2136// only used for initialization and our PerformMove(). It is why we set
2137// violations_ here.
2139 absl::Span<const int64_t> solution) {
2140 for (const int lit : enforcement_literals_) {
2141 if (!LiteralValue(lit, solution)) {
2142 violation_ = 0;
2143 return 0;
2144 }
2145 }
2146 violation_ = BuildProfileAndReturnViolation(solution);
2147 return violation_;
2148}
2149
2150int64_t CompiledReservoirConstraint::BuildProfileAndReturnViolation(
2151 absl::Span<const int64_t> solution) {
2152 // Starts by filling the cache and profile_.
2153 capacity_value_ = ExprValue(capacity_, solution);
2154 const int num_events = time_values_.size();
2155 profile_.clear();
2156 for (int i = 0; i < num_events; ++i) {
2157 time_values_[i] = ExprValue(times_[i], solution);
2158 if (is_active_[i] != std::nullopt &&
2159 LiteralValue(*is_active_[i], solution) == 0) {
2160 demand_values_[i] = 0;
2161 } else {
2162 demand_values_[i] = ExprValue(demands_[i], solution);
2163 if (demand_values_[i] != 0) {
2164 profile_.push_back({time_values_[i], demand_values_[i]});
2165 }
2166 }
2167 }
2168
2169 if (profile_.empty()) return 0;
2170 absl::c_sort(profile_);
2171
2172 // Compress the profile for faster incremental evaluation.
2173 {
2174 int p = 0;
2175 for (int i = 1; i < profile_.size(); ++i) {
2176 if (profile_[i].time == profile_[p].time) {
2177 profile_[p].demand += profile_[i].demand;
2178 } else {
2179 profile_[++p] = profile_[i];
2180 }
2181 }
2182 profile_.resize(p + 1);
2183 }
2184
2185 int64_t overload = 0;
2186 int64_t current_load = 0;
2187 int64_t previous_time = std::numeric_limits<int64_t>::min();
2188 for (int i = 0; i < profile_.size(); ++i) {
2189 // At this point, current_load is the load at previous_time.
2190 const int64_t time = profile_[i].time;
2191 if (current_load > capacity_value_) {
2192 overload = CapAdd(overload, CapProd(current_load - capacity_value_,
2193 time - previous_time));
2194 }
2195
2196 current_load += profile_[i].demand;
2197 previous_time = time;
2198 }
2199 return overload;
2200}
2201
2202int64_t CompiledReservoirConstraint::IncrementalViolation(
2203 int var, absl::Span<const int64_t> solution) {
2204 for (const int lit : enforcement_literals_) {
2205 if (!LiteralValue(lit, solution)) {
2206 return 0;
2207 }
2208 }
2209 const int64_t capacity = ExprValue(capacity_, solution);
2210 profile_delta_.clear();
2211 CHECK(RefIsPositive(var));
2212 for (const int i : dense_index_to_events_[var_to_dense_index_.at(var)]) {
2213 const int64_t time = ExprValue(times_[i], solution);
2214 int64_t demand = 0;
2215 if (is_active_[i] == std::nullopt ||
2216 LiteralValue(*is_active_[i], solution) == 1) {
2217 demand = ExprValue(demands_[i], solution);
2218 }
2219
2220 if (time == time_values_[i]) {
2221 if (demand != demand_values_[i]) {
2222 // Update the demand at time.
2223 profile_delta_.push_back({time, demand - demand_values_[i]});
2224 }
2225 } else {
2226 // Remove previous.
2227 if (demand_values_[i] != 0) {
2228 profile_delta_.push_back({time_values_[i], -demand_values_[i]});
2229 }
2230 // Add new.
2231 if (demand != 0) {
2232 profile_delta_.push_back({time, demand});
2233 }
2234 }
2235 }
2236
2237 // Abort early if there is no change.
2238 // This might happen because we use max(start + size, end) for the time and
2239 // even if some variable changed there, the time might not have.
2240 if (capacity == capacity_value_ && profile_delta_.empty()) {
2241 return violation_;
2242 }
2243 absl::c_sort(profile_delta_);
2244
2245 // Similar algo, but we scan the two vectors at once.
2246 int64_t overload = 0;
2247 int64_t current_load = 0;
2248 int64_t previous_time = std::numeric_limits<int64_t>::min();
2249
2250 // TODO(user): This code is the hotspot for our local search on cumulative.
2251 // It can probably be slightly improved. We might also be able to abort early
2252 // if we know that capacity is high enough compared to the highest point of
2253 // the profile.
2254 int i = 0;
2255 int j = 0;
2256 const absl::Span<const Event> i_profile(profile_);
2257 const absl::Span<const Event> j_profile(profile_delta_);
2258 while (true) {
2259 int64_t time;
2260 if (i < i_profile.size() && j < j_profile.size()) {
2261 time = std::min(i_profile[i].time, j_profile[j].time);
2262 } else if (i < i_profile.size()) {
2263 time = i_profile[i].time;
2264 } else if (j < j_profile.size()) {
2265 time = j_profile[j].time;
2266 } else {
2267 // End of loop.
2268 break;
2269 }
2270
2271 // Update overload if needed.
2272 // At this point, current_load is the load at previous_time.
2273 if (current_load > capacity) {
2274 overload = CapAdd(overload,
2275 CapProd(current_load - capacity, time - previous_time));
2276 }
2277
2278 // Update i and current load.
2279 while (i < i_profile.size() && i_profile[i].time == time) {
2280 current_load += i_profile[i].demand;
2281 i++;
2282 }
2283
2284 // Update j and current load.
2285 while (j < j_profile.size() && j_profile[j].time == time) {
2286 current_load += j_profile[j].demand;
2287 j++;
2288 }
2289
2290 previous_time = time;
2291 }
2292 return overload;
2293}
2294
2295void CompiledReservoirConstraint::AppendVariablesForEvent(
2296 int i, std::vector<int>* result) const {
2297 if (is_active_[i] != std::nullopt) {
2298 result->push_back(PositiveRef(*is_active_[i]));
2299 }
2300 for (const int var : times_[i].vars()) {
2301 result->push_back(PositiveRef(var));
2302 }
2303 for (const int var : demands_[i].vars()) {
2304 result->push_back(PositiveRef(var));
2305 }
2306}
2307
2308void CompiledReservoirConstraint::InitializeDenseIndexToEvents() {
2309 // We scan the constraint a few times, but this is called once, so we don't
2310 // care too much.
2311 CpModelProto unused;
2312 int num_dense_indices = 0;
2313 for (const int var : UsedVariables(unused)) {
2314 var_to_dense_index_[var] = num_dense_indices++;
2315 }
2316
2317 CompactVectorVector<int, int> event_to_dense_indices;
2318 event_to_dense_indices.reserve(times_.size());
2319 const int num_events = times_.size();
2320 std::vector<int> result;
2321 for (int i = 0; i < num_events; ++i) {
2322 result.clear();
2323 AppendVariablesForEvent(i, &result);
2324
2325 // Remap and add.
2326 for (int& var : result) {
2327 var = var_to_dense_index_.at(var);
2328 }
2330 event_to_dense_indices.Add(result);
2331 }
2332
2333 // Note that because of the capacity (which might be variable) it is important
2334 // to resize this to num_dense_indices.
2335 dense_index_to_events_.ResetFromTranspose(event_to_dense_indices,
2336 num_dense_indices);
2337}
2338
2340 const CpModelProto& /*model_proto*/) const {
2341 std::vector<int> result;
2342 const int num_events = times_.size();
2343 for (int i = 0; i < num_events; ++i) {
2344 AppendVariablesForEvent(i, &result);
2345 }
2346 for (const int var : capacity_.vars()) {
2347 result.push_back(PositiveRef(var));
2348 }
2350 result.shrink_to_fit();
2351 return result;
2352}
2353
2354} // namespace sat
2355} // namespace operations_research
std::pair< iterator, bool > insert(T value)
Definition dense_set.h:56
std::vector< int64_t > FlattenedIntervals() const
bool Contains(int64_t value) const
int64_t Distance(int64_t value) const
static IntegralType CeilOfRatio(IntegralType numerator, IntegralType denominator)
Definition mathutil.h:39
static IntegralType FloorOfRatio(IntegralType numerator, IntegralType denominator)
Definition mathutil.h:53
void Set(IntegerType index)
Definition bitset.h:878
int64_t ComputeViolationWhenEnforced(absl::Span< const int64_t > solution) override
int64_t ViolationDeltaWhenEnforced(int var, int64_t old_value, absl::Span< const int64_t > solution_with_new_value) override
int64_t ComputeViolationWhenEnforced(absl::Span< const int64_t > solution) override
int64_t ComputeViolationWhenEnforced(absl::Span< const int64_t > solution) override
void PerformMove(int var, int64_t old_value, absl::Span< const int64_t > new_solution) override
int64_t ViolationDeltaWhenEnforced(int var, int64_t old_value, absl::Span< const int64_t > solution_with_new_value) override
int64_t ComputeViolation(absl::Span< const int64_t > solution) final
int64_t ViolationDelta(int var, int64_t old_value, absl::Span< const int64_t > solution_with_new_value) final
std::vector< int > UsedVariables(const CpModelProto &model_proto) const final
virtual int64_t ViolationDeltaWhenEnforced(int var, int64_t old_value, absl::Span< const int64_t > solution_with_new_value)
virtual int64_t ComputeViolationWhenEnforced(absl::Span< const int64_t > solution)=0
virtual void PerformMove(int var, int64_t old_value, absl::Span< const int64_t > solution_with_new_value)
virtual int64_t ComputeViolation(absl::Span< const int64_t > solution)=0
void InitializeViolation(absl::Span< const int64_t > solution)
virtual int64_t ViolationDelta(int var, int64_t old_value, absl::Span< const int64_t > solution_with_new_value)
int64_t ComputeViolationWhenEnforced(absl::Span< const int64_t > solution) override
int64_t ComputeViolationWhenEnforced(absl::Span< const int64_t > solution) override
int64_t ComputeViolationWhenEnforced(absl::Span< const int64_t > solution) override
int64_t ComputeViolationWhenEnforced(absl::Span< const int64_t > solution) override
int64_t ComputeViolationWhenEnforced(absl::Span< const int64_t > solution) override
CompiledNoOverlap2dConstraint(const ConstraintProto &ct_proto, const CpModelProto &cp_model)
int64_t ViolationDelta(int, int64_t, absl::Span< const int64_t > solution_with_new_value) final
std::vector< int > UsedVariables(const CpModelProto &model_proto) const final
int64_t ViolationDelta(int, int64_t, absl::Span< const int64_t > solution_with_new_value) final
std::vector< int > UsedVariables(const CpModelProto &model_proto) const final
int64_t ComputeViolation(absl::Span< const int64_t > solution) final
std::vector< int > UsedVariables(const CpModelProto &model_proto) const final
::int32_t enforcement_literal(int index) const
const ::operations_research::sat::IntervalConstraintProto & interval() const
const ::operations_research::sat::RoutesConstraintProto & routes() const
const ::operations_research::sat::CircuitConstraintProto & circuit() const
const ::operations_research::sat::NoOverlap2DConstraintProto & no_overlap_2d() const
const ::operations_research::sat::ConstraintProto & constraints(int index) const
const ::operations_research::sat::LinearExpressionProto & end() const
const ::operations_research::sat::LinearExpressionProto & start() const
void UpdateScoreOnWeightUpdate(int c, absl::Span< const int64_t > jump_deltas, absl::Span< double > var_to_score_change)
double WeightedViolationDelta(absl::Span< const double > weights, int var, int64_t delta) const
void ComputeInitialActivities(absl::Span< const int64_t > solution)
void AddLiteral(int ct_index, int lit, int64_t coeff=1)
double WeightedViolation(absl::Span< const double > weights) const
void UpdateVariableAndScores(int var, int64_t delta, absl::Span< const double > weights, absl::Span< const int64_t > jump_deltas, absl::Span< double > jump_scores, std::vector< int > *constraints_with_changed_violations)
void AddLinearExpression(int ct_index, const LinearExpressionProto &expr, int64_t multiplier)
void PrecomputeCompactView(absl::Span< const int64_t > var_max_variation)
std::vector< int64_t > SlopeBreakpoints(int var, int64_t current_value, const Domain &var_domain) const
void AddTerm(int ct_index, int var, int64_t coeff, int64_t offset=0)
double WeightedViolation(absl::Span< const double > weights) const
void ComputeAllNonLinearViolations(absl::Span< const int64_t > solution)
void UpdateLinearScores(int var, int64_t old_value, int64_t new_value, absl::Span< const double > weights, absl::Span< const int64_t > jump_deltas, absl::Span< double > jump_scores)
bool VariableOnlyInLinearConstraintWithConvexViolationChange(int var) const
void ComputeAllViolations(absl::Span< const int64_t > solution)
void UpdateNonLinearViolations(int var, int64_t old_value, absl::Span< const int64_t > new_solution)
bool ReduceObjectiveBounds(int64_t lb, int64_t ub)
LsEvaluator(const CpModelProto &cp_model, const SatParameters &params, TimeLimit *time_limit)
absl::Span< const int > ConstraintToVars(int c) const
double WeightedViolationDelta(bool linear_only, absl::Span< const double > weights, int var, int64_t delta, absl::Span< int64_t > mutable_solution) const
void STLSortAndRemoveDuplicates(T *v, const LessFunc &less_func)
Definition stl_util.h:55
void STLClearObject(T *obj)
Definition stl_util.h:120
void AddCircuitFlowConstraints(LinearIncrementalEvaluator &linear_evaluator, const ConstraintProto &ct_proto)
int64_t OverlapOfTwoIntervals(const ConstraintProto &interval1, const ConstraintProto &interval2, absl::Span< const int64_t > solution)
std::vector< int > UsedVariables(const ConstraintProto &ct)
std::vector< int > UsedIntervals(const ConstraintProto &ct)
int64_t NoOverlapMinRepairDistance(const ConstraintProto &interval1, const ConstraintProto &interval2, absl::Span< const int64_t > solution)
Domain ReadDomainFromProto(const ProtoWithDomain &proto)
OR-Tools root namespace.
int64_t CapAdd(int64_t x, int64_t y)
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
int64_t CapSub(int64_t x, int64_t y)
ClosedInterval::Iterator end(ClosedInterval interval)
int64_t CapProd(int64_t x, int64_t y)
int64_t Max() const
Definition model.cc:346
int64_t Min() const
Definition model.cc:340