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