Google OR-Tools v9.9
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 <utility>
22#include <vector>
23
24#include "absl/algorithm/container.h"
25#include "absl/container/flat_hash_set.h"
26#include "absl/log/check.h"
27#include "absl/types/span.h"
31#include "ortools/sat/cp_model.pb.h"
33#include "ortools/sat/util.h"
36
37namespace operations_research {
38namespace sat {
39
40int64_t ExprValue(const LinearExpressionProto& expr,
41 absl::Span<const int64_t> solution) {
42 int64_t result = expr.offset();
43 for (int i = 0; i < expr.vars_size(); ++i) {
44 result += solution[expr.vars(i)] * expr.coeffs(i);
45 }
46 return result;
47}
48
49int64_t ExprMin(const LinearExpressionProto& expr, const CpModelProto& model) {
50 int64_t result = expr.offset();
51 for (int i = 0; i < expr.vars_size(); ++i) {
52 const IntegerVariableProto& var_proto = model.variables(expr.vars(i));
53 if (expr.coeffs(i) > 0) {
54 result += expr.coeffs(i) * var_proto.domain(0);
55 } else {
56 result += expr.coeffs(i) * var_proto.domain(var_proto.domain_size() - 1);
57 }
58 }
59 return result;
60}
61
62int64_t ExprMax(const LinearExpressionProto& expr, const CpModelProto& model) {
63 int64_t result = expr.offset();
64 for (int i = 0; i < expr.vars_size(); ++i) {
65 const IntegerVariableProto& var_proto = model.variables(expr.vars(i));
66 if (expr.coeffs(i) > 0) {
67 result += expr.coeffs(i) * var_proto.domain(var_proto.domain_size() - 1);
68 } else {
69 result += expr.coeffs(i) * var_proto.domain(0);
70 }
71 }
72 return result;
73}
74
75bool LiteralValue(int lit, absl::Span<const int64_t> solution) {
76 if (RefIsPositive(lit)) {
77 return solution[lit] != 0;
78 } else {
79 return solution[PositiveRef(lit)] == 0;
80 }
81}
82
83// ---- LinearIncrementalEvaluator -----
84
86 DCHECK(creation_phase_);
87 domains_.push_back(domain);
88 offsets_.push_back(0);
89 activities_.push_back(0);
90 num_false_enforcement_.push_back(0);
91 distances_.push_back(0);
92 is_violated_.push_back(false);
93 return num_constraints_++;
94}
95
97 DCHECK(creation_phase_);
98 const int var = PositiveRef(lit);
99 if (literal_entries_.size() <= var) {
100 literal_entries_.resize(var + 1);
101 }
102 literal_entries_[var].push_back(
103 {.ct_index = ct_index, .positive = RefIsPositive(lit)});
104}
105
107 int64_t coeff) {
108 DCHECK(creation_phase_);
109 if (RefIsPositive(lit)) {
110 AddTerm(ct_index, lit, coeff, 0);
111 } else {
112 AddTerm(ct_index, PositiveRef(lit), -coeff, coeff);
113 }
114}
115
117 int64_t offset) {
118 DCHECK(creation_phase_);
119 DCHECK_GE(var, 0);
120 if (coeff == 0) return;
121
122 if (var_entries_.size() <= var) {
123 var_entries_.resize(var + 1);
124 }
125 if (!var_entries_[var].empty() &&
126 var_entries_[var].back().ct_index == ct_index) {
127 var_entries_[var].back().coefficient += coeff;
128 if (var_entries_[var].back().coefficient == 0) {
129 var_entries_[var].pop_back();
130 }
131 } else {
132 var_entries_[var].push_back({.ct_index = ct_index, .coefficient = coeff});
133 }
134 AddOffset(ct_index, offset);
135 DCHECK(VarIsConsistent(var));
136}
137
139 DCHECK(creation_phase_);
140 offsets_[ct_index] += offset;
141}
142
144 int ct_index, const LinearExpressionProto& expr, int64_t multiplier) {
145 DCHECK(creation_phase_);
146 AddOffset(ct_index, expr.offset() * multiplier);
147 for (int i = 0; i < expr.vars_size(); ++i) {
148 if (expr.coeffs(i) * multiplier == 0) continue;
149 AddTerm(ct_index, expr.vars(i), expr.coeffs(i) * multiplier);
150 }
151}
152
154 if (var_entries_.size() <= var) return true;
155
156 absl::flat_hash_set<int> visited;
157 for (const Entry& entry : var_entries_[var]) {
158 if (!visited.insert(entry.ct_index).second) return false;
159 }
160 return true;
161}
162
164 absl::Span<const int64_t> solution) {
165 DCHECK(!creation_phase_);
166
167 // Resets the activity as the offset and the number of false enforcement to 0.
168 activities_ = offsets_;
169 in_last_affected_variables_.resize(columns_.size(), false);
170 num_false_enforcement_.assign(num_constraints_, 0);
171
172 // Update these numbers for all columns.
173 for (int var = 0; var < columns_.size(); ++var) {
174 const SpanData& data = columns_[var];
175 const int64_t value = solution[var];
176
177 int i = data.start;
178 for (int k = 0; k < data.num_pos_literal; ++k, ++i) {
179 const int c = ct_buffer_[i];
180 if (value == 0) num_false_enforcement_[c]++;
181 }
182 for (int k = 0; k < data.num_neg_literal; ++k, ++i) {
183 const int c = ct_buffer_[i];
184 if (value == 1) num_false_enforcement_[c]++;
185 }
186
187 if (value == 0) continue;
188 int j = data.linear_start;
189 for (int k = 0; k < data.num_linear_entries; ++k, ++i, ++j) {
190 const int c = ct_buffer_[i];
191 const int64_t coeff = coeff_buffer_[j];
192 activities_[c] += coeff * value;
193 }
194 }
195
196 // Cache violations (not counting enforcement).
197 for (int c = 0; c < num_constraints_; ++c) {
198 distances_[c] = domains_[c].Distance(activities_[c]);
199 is_violated_[c] = Violation(c) > 0;
200 }
201}
202
203// Note that the code assumes that a column has no duplicates ct indices.
205 int var, int64_t delta,
206 std::vector<std::pair<int, int64_t>>* violation_deltas) {
207 DCHECK(!creation_phase_);
208 DCHECK_NE(delta, 0);
209 if (var >= columns_.size()) return;
210
211 const SpanData& data = columns_[var];
212 int i = data.start;
213 for (int k = 0; k < data.num_pos_literal; ++k, ++i) {
214 const int c = ct_buffer_[i];
215 const int64_t v0 = Violation(c);
216 if (delta == 1) {
217 num_false_enforcement_[c]--;
218 DCHECK_GE(num_false_enforcement_[c], 0);
219 } else {
220 num_false_enforcement_[c]++;
221 }
222 const int64_t v1 = Violation(c);
223 is_violated_[c] = v1 > 0;
224 if (violation_deltas != nullptr) {
225 violation_deltas->push_back({c, v1 - v0});
226 }
227 }
228 for (int k = 0; k < data.num_neg_literal; ++k, ++i) {
229 const int c = ct_buffer_[i];
230 const int64_t v0 = Violation(c);
231 if (delta == -1) {
232 num_false_enforcement_[c]--;
233 DCHECK_GE(num_false_enforcement_[c], 0);
234 } else {
235 num_false_enforcement_[c]++;
236 }
237 const int64_t v1 = Violation(c);
238 is_violated_[c] = v1 > 0;
239 if (violation_deltas != nullptr) {
240 violation_deltas->push_back({c, v1 - v0});
241 }
242 }
243 int j = data.linear_start;
244 for (int k = 0; k < data.num_linear_entries; ++k, ++i, ++j) {
245 const int c = ct_buffer_[i];
246 const int64_t v0 = Violation(c);
247 const int64_t coeff = coeff_buffer_[j];
248 activities_[c] += coeff * delta;
249 distances_[c] = domains_[c].Distance(activities_[c]);
250 const int64_t v1 = Violation(c);
251 is_violated_[c] = v1 > 0;
252 if (violation_deltas != nullptr) {
253 violation_deltas->push_back({c, v1 - v0});
254 }
255 }
256}
257
259 in_last_affected_variables_.resize(columns_.size(), false);
260 for (const int var : last_affected_variables_) {
261 in_last_affected_variables_[var] = false;
262 }
263 last_affected_variables_.clear();
264 DCHECK(std::all_of(in_last_affected_variables_.begin(),
265 in_last_affected_variables_.end(),
266 [](bool b) { return !b; }));
267}
268
269// Tricky: Here we re-use in_last_affected_variables_ to resest
270// var_to_score_change. And in particular we need to list all variable whose
271// score changed here. Not just the one for which we have a decrease.
273 int c, absl::Span<const int64_t> jump_deltas,
274 absl::Span<double> var_to_score_change) {
275 if (c >= rows_.size()) return;
276
277 DCHECK_EQ(num_false_enforcement_[c], 0);
278 const SpanData& data = rows_[c];
279
280 // Update enforcement part. Because we only update weight of currently
281 // infeasible constraint, all change are 0 -> 1 transition and change by the
282 // same amount, which is the current distance.
283 const double enforcement_change = static_cast<double>(-distances_[c]);
284 if (enforcement_change != 0.0) {
285 int i = data.start;
286 const int end = data.num_pos_literal + data.num_neg_literal;
287 dtime_ += end;
288 for (int k = 0; k < end; ++k, ++i) {
289 const int var = row_var_buffer_[i];
290 if (!in_last_affected_variables_[var]) {
291 var_to_score_change[var] = enforcement_change;
292 in_last_affected_variables_[var] = true;
293 last_affected_variables_.push_back(var);
294 } else {
295 var_to_score_change[var] += enforcement_change;
296 }
297 }
298 }
299
300 // Update linear part.
301 int i = data.start + data.num_pos_literal + data.num_neg_literal;
302 int j = data.linear_start;
303 dtime_ += 2 * data.num_linear_entries;
304 const int64_t old_distance = distances_[c];
305 for (int k = 0; k < data.num_linear_entries; ++k, ++i, ++j) {
306 const int var = row_var_buffer_[i];
307 const int64_t coeff = row_coeff_buffer_[j];
308 const int64_t new_distance =
309 domains_[c].Distance(activities_[c] + coeff * jump_deltas[var]);
310 if (!in_last_affected_variables_[var]) {
311 var_to_score_change[var] =
312 static_cast<double>(new_distance - old_distance);
313 in_last_affected_variables_[var] = true;
314 last_affected_variables_.push_back(var);
315 } else {
316 var_to_score_change[var] +=
317 static_cast<double>(new_distance - old_distance);
318 }
319 }
320}
321
322void LinearIncrementalEvaluator::UpdateScoreOnNewlyEnforced(
323 int c, double weight, absl::Span<const int64_t> jump_deltas,
324 absl::Span<double> jump_scores) {
325 const SpanData& data = rows_[c];
326
327 // Everyone else had a zero cost transition that now become enforced ->
328 // unenforced. So they all have better score.
329 const double weight_time_violation =
330 weight * static_cast<double>(distances_[c]);
331 if (weight_time_violation > 0.0) {
332 int i = data.start;
333 const int end = data.num_pos_literal + data.num_neg_literal;
334 dtime_ += end;
335 for (int k = 0; k < end; ++k, ++i) {
336 const int var = row_var_buffer_[i];
337 jump_scores[var] -= weight_time_violation;
338 if (!in_last_affected_variables_[var]) {
339 in_last_affected_variables_[var] = true;
340 last_affected_variables_.push_back(var);
341 }
342 }
343 }
344
345 // Update linear part! It was zero and is now a diff.
346 {
347 int i = data.start + data.num_pos_literal + data.num_neg_literal;
348 int j = data.linear_start;
349 dtime_ += 2 * data.num_linear_entries;
350 const int64_t old_distance = distances_[c];
351 for (int k = 0; k < data.num_linear_entries; ++k, ++i, ++j) {
352 const int var = row_var_buffer_[i];
353 const int64_t coeff = row_coeff_buffer_[j];
354 const int64_t new_distance =
355 domains_[c].Distance(activities_[c] + coeff * jump_deltas[var]);
356 jump_scores[var] +=
357 weight * static_cast<double>(new_distance - old_distance);
358 if (!in_last_affected_variables_[var]) {
359 in_last_affected_variables_[var] = true;
360 last_affected_variables_.push_back(var);
361 }
362 }
363 }
364}
365
366void LinearIncrementalEvaluator::UpdateScoreOnNewlyUnenforced(
367 int c, double weight, absl::Span<const int64_t> jump_deltas,
368 absl::Span<double> jump_scores) {
369 const SpanData& data = rows_[c];
370
371 // Everyone else had a enforced -> unenforced transition that now become zero.
372 // So they all have worst score, and we don't need to update
373 // last_affected_variables_.
374 const double weight_time_violation =
375 weight * static_cast<double>(distances_[c]);
376 if (weight_time_violation > 0.0) {
377 int i = data.start;
378 const int end = data.num_pos_literal + data.num_neg_literal;
379 dtime_ += end;
380 for (int k = 0; k < end; ++k, ++i) {
381 const int var = row_var_buffer_[i];
382 jump_scores[var] += weight_time_violation;
383 }
384 }
385
386 // Update linear part! It had a diff and is now zero.
387 {
388 int i = data.start + data.num_pos_literal + data.num_neg_literal;
389 int j = data.linear_start;
390 dtime_ += 2 * data.num_linear_entries;
391 const int64_t old_distance = distances_[c];
392 for (int k = 0; k < data.num_linear_entries; ++k, ++i, ++j) {
393 const int var = row_var_buffer_[i];
394 const int64_t coeff = row_coeff_buffer_[j];
395 const int64_t new_distance =
396 domains_[c].Distance(activities_[c] + coeff * jump_deltas[var]);
397 jump_scores[var] -=
398 weight * static_cast<double>(new_distance - old_distance);
399 if (!in_last_affected_variables_[var]) {
400 in_last_affected_variables_[var] = true;
401 last_affected_variables_.push_back(var);
402 }
403 }
404 }
405}
406
407// We just need to modify the old/new transition that decrease the number of
408// enforcement literal at false.
409void LinearIncrementalEvaluator::UpdateScoreOfEnforcementIncrease(
410 int c, double score_change, absl::Span<const int64_t> jump_deltas,
411 absl::Span<double> jump_scores) {
412 if (score_change == 0.0) return;
413
414 const SpanData& data = rows_[c];
415 int i = data.start;
416 dtime_ += data.num_pos_literal;
417 for (int k = 0; k < data.num_pos_literal; ++k, ++i) {
418 const int var = row_var_buffer_[i];
419 if (jump_deltas[var] == 1) {
420 jump_scores[var] += score_change;
421 if (score_change < 0.0 && !in_last_affected_variables_[var]) {
422 in_last_affected_variables_[var] = true;
423 last_affected_variables_.push_back(var);
424 }
425 }
426 }
427 dtime_ += data.num_neg_literal;
428 for (int k = 0; k < data.num_neg_literal; ++k, ++i) {
429 const int var = row_var_buffer_[i];
430 if (jump_deltas[var] == -1) {
431 jump_scores[var] += score_change;
432 if (score_change < 0.0 && !in_last_affected_variables_[var]) {
433 in_last_affected_variables_[var] = true;
434 last_affected_variables_.push_back(var);
435 }
436 }
437 }
438}
439
440void LinearIncrementalEvaluator::UpdateScoreOnActivityChange(
441 int c, double weight, int64_t activity_delta,
442 absl::Span<const int64_t> jump_deltas, absl::Span<double> jump_scores) {
443 if (activity_delta == 0) return;
444 const SpanData& data = rows_[c];
445
446 // In some cases, we can know that the score of all the involved variable
447 // will not change. This is the case if whatever 1 variable change the
448 // violation delta before/after is the same.
449 //
450 // TODO(user): Maintain more precise bounds.
451 // - We could easily compute on each ComputeInitialActivities() the
452 // maximum increase/decrease per variable, and take the max as each
453 // variable changes?
454 // - Know if a constraint is only <= or >= !
455 const int64_t old_activity = activities_[c];
456 const int64_t new_activity = old_activity + activity_delta;
457 int64_t min_range;
458 int64_t max_range;
459 if (new_activity > old_activity) {
460 min_range = old_activity - row_max_variations_[c];
461 max_range = new_activity + row_max_variations_[c];
462 } else {
463 min_range = new_activity - row_max_variations_[c];
464 max_range = old_activity + row_max_variations_[c];
465 }
466
467 // If the violation delta was zero and will still always be zero, we can skip.
468 if (Domain(min_range, max_range).IsIncludedIn(domains_[c])) return;
469
470 // Enforcement is always enforced -> un-enforced.
471 // So it was -weight_time_distance and is now -weight_time_new_distance.
472 const double delta =
473 -weight *
474 static_cast<double>(domains_[c].Distance(new_activity) - distances_[c]);
475 if (delta != 0.0) {
476 int i = data.start;
477 const int end = data.num_pos_literal + data.num_neg_literal;
478 dtime_ += end;
479 for (int k = 0; k < end; ++k, ++i) {
480 const int var = row_var_buffer_[i];
481 jump_scores[var] += delta;
482 if (delta < 0.0 && !in_last_affected_variables_[var]) {
483 in_last_affected_variables_[var] = true;
484 last_affected_variables_.push_back(var);
485 }
486 }
487 }
488
489 // If we are infeasible and no move can correct it, both old_b - old_a and
490 // new_b - new_a will have the same value. We only needed to update the
491 // violation of the enforced literal.
492 if (min_range >= domains_[c].Max() || max_range <= domains_[c].Min()) return;
493
494 // Update linear part.
495 {
496 int i = data.start + data.num_pos_literal + data.num_neg_literal;
497 int j = data.linear_start;
498 dtime_ += 2 * data.num_linear_entries;
499 const Domain& rhs = domains_[c];
500 const int64_t old_a_minus_new_a =
501 distances_[c] - rhs.Distance(new_activity);
502 for (int k = 0; k < data.num_linear_entries; ++k, ++i, ++j) {
503 const int var = row_var_buffer_[i];
504 const int64_t impact = row_coeff_buffer_[j] * jump_deltas[var];
505 const int64_t old_b = rhs.Distance(old_activity + impact);
506 const int64_t new_b = rhs.Distance(new_activity + impact);
507
508 // The old score was:
509 // weight * static_cast<double>(old_b - old_a);
510 // the new score is
511 // weight * static_cast<double>(new_b - new_a); so the diff is:
512 //
513 // TODO(user): If a variable is at its lower (resp. upper) bound, then
514 // we know that the score will always move in the same direction, so we
515 // might skip the last_affected_variables_ update.
516 jump_scores[var] +=
517 weight * static_cast<double>(old_a_minus_new_a + new_b - old_b);
518 if (!in_last_affected_variables_[var]) {
519 in_last_affected_variables_[var] = true;
520 last_affected_variables_.push_back(var);
521 }
522 }
523 }
524}
525
527 int var, int64_t delta, absl::Span<const double> weights,
528 absl::Span<const int64_t> jump_deltas, absl::Span<double> jump_scores,
529 std::vector<std::pair<int, int64_t>>* violation_deltas) {
530 DCHECK(!creation_phase_);
531 DCHECK_NE(delta, 0);
532 if (var >= columns_.size()) return;
533
534 const SpanData& data = columns_[var];
535 int i = data.start;
536 for (int k = 0; k < data.num_pos_literal; ++k, ++i) {
537 const int c = ct_buffer_[i];
538 const int64_t v0 = Violation(c);
539 if (delta == 1) {
540 num_false_enforcement_[c]--;
541 DCHECK_GE(num_false_enforcement_[c], 0);
542 if (num_false_enforcement_[c] == 0) {
543 UpdateScoreOnNewlyEnforced(c, weights[c], jump_deltas, jump_scores);
544 } else if (num_false_enforcement_[c] == 1) {
545 const double enforcement_change =
546 weights[c] * static_cast<double>(distances_[c]);
547 UpdateScoreOfEnforcementIncrease(c, enforcement_change, jump_deltas,
548 jump_scores);
549 }
550 } else {
551 num_false_enforcement_[c]++;
552 if (num_false_enforcement_[c] == 1) {
553 UpdateScoreOnNewlyUnenforced(c, weights[c], jump_deltas, jump_scores);
554 } else if (num_false_enforcement_[c] == 2) {
555 const double enforcement_change =
556 weights[c] * static_cast<double>(distances_[c]);
557 UpdateScoreOfEnforcementIncrease(c, -enforcement_change, jump_deltas,
558 jump_scores);
559 }
560 }
561 const int64_t v1 = Violation(c);
562 is_violated_[c] = v1 > 0;
563 if (violation_deltas != nullptr) {
564 violation_deltas->push_back(std::make_pair(c, v1 - v0));
565 }
566 }
567 for (int k = 0; k < data.num_neg_literal; ++k, ++i) {
568 const int c = ct_buffer_[i];
569 const int64_t v0 = Violation(c);
570 if (delta == -1) {
571 num_false_enforcement_[c]--;
572 DCHECK_GE(num_false_enforcement_[c], 0);
573 if (num_false_enforcement_[c] == 0) {
574 UpdateScoreOnNewlyEnforced(c, weights[c], jump_deltas, jump_scores);
575 } else if (num_false_enforcement_[c] == 1) {
576 const double enforcement_change =
577 weights[c] * static_cast<double>(distances_[c]);
578 UpdateScoreOfEnforcementIncrease(c, enforcement_change, jump_deltas,
579 jump_scores);
580 }
581 } else {
582 num_false_enforcement_[c]++;
583 if (num_false_enforcement_[c] == 1) {
584 UpdateScoreOnNewlyUnenforced(c, weights[c], jump_deltas, jump_scores);
585 } else if (num_false_enforcement_[c] == 2) {
586 const double enforcement_change =
587 weights[c] * static_cast<double>(distances_[c]);
588 UpdateScoreOfEnforcementIncrease(c, -enforcement_change, jump_deltas,
589 jump_scores);
590 }
591 }
592 const int64_t v1 = Violation(c);
593 is_violated_[c] = v1 > 0;
594 if (violation_deltas != nullptr) {
595 violation_deltas->push_back(std::make_pair(c, v1 - v0));
596 }
597 }
598 int j = data.linear_start;
599 for (int k = 0; k < data.num_linear_entries; ++k, ++i, ++j) {
600 const int c = ct_buffer_[i];
601 const int64_t v0 = Violation(c);
602 const int64_t coeff = coeff_buffer_[j];
603
604 if (num_false_enforcement_[c] == 1) {
605 // Only the 1 -> 0 are impacted.
606 // This is the same as the 1->2 transition, but the old 1->0 needs to
607 // be changed from - weight * distance to - weight * new_distance.
608 const int64_t new_distance =
609 domains_[c].Distance(activities_[c] + coeff * delta);
610 if (new_distance != distances_[c]) {
611 UpdateScoreOfEnforcementIncrease(
612 c, -weights[c] * static_cast<double>(distances_[c] - new_distance),
613 jump_deltas, jump_scores);
614 }
615 } else if (num_false_enforcement_[c] == 0) {
616 UpdateScoreOnActivityChange(c, weights[c], coeff * delta, jump_deltas,
617 jump_scores);
618 }
619
620 activities_[c] += coeff * delta;
621 distances_[c] = domains_[c].Distance(activities_[c]);
622 const int64_t v1 = Violation(c);
623 is_violated_[c] = v1 > 0;
624 if (violation_deltas != nullptr) {
625 violation_deltas->push_back(std::make_pair(c, v1 - v0));
626 }
627 }
628}
629
631 return activities_[c];
632}
633
635 return num_false_enforcement_[c] > 0 ? 0 : distances_[c];
636}
637
639 DCHECK_EQ(is_violated_[c], Violation(c) > 0);
640 return is_violated_[c];
641}
642
643bool LinearIncrementalEvaluator::ReduceBounds(int c, int64_t lb, int64_t ub) {
644 if (domains_[c].Min() >= lb && domains_[c].Max() <= ub) return false;
645 domains_[c] = domains_[c].IntersectionWith(Domain(lb, ub));
646 distances_[c] = domains_[c].Distance(activities_[c]);
647 return true;
648}
649
651 absl::Span<const double> weights) const {
652 double result = 0.0;
653 DCHECK_GE(weights.size(), num_constraints_);
654 for (int c = 0; c < num_constraints_; ++c) {
655 if (num_false_enforcement_[c] > 0) continue;
656 result += weights[c] * static_cast<double>(distances_[c]);
657 }
658 return result;
659}
660
661// Most of the time is spent in this function.
662//
663// TODO(user): We can safely abort early if we know that delta will be >= 0.
664// TODO(user): Maybe we can compute an absolute value instead of removing
665// old_distance.
667 absl::Span<const double> weights, int var, int64_t delta) const {
668 DCHECK_NE(delta, 0);
669 if (var >= columns_.size()) return 0.0;
670 const SpanData& data = columns_[var];
671
672 int i = data.start;
673 double result = 0.0;
674 dtime_ += data.num_pos_literal;
675 for (int k = 0; k < data.num_pos_literal; ++k, ++i) {
676 const int c = ct_buffer_[i];
677 if (num_false_enforcement_[c] == 0) {
678 // Since delta != 0, we are sure this is an enforced -> unenforced change.
679 DCHECK_EQ(delta, -1);
680 result -= weights[c] * static_cast<double>(distances_[c]);
681 } else {
682 if (delta == 1 && num_false_enforcement_[c] == 1) {
683 result += weights[c] * static_cast<double>(distances_[c]);
684 }
685 }
686 }
687
688 dtime_ += data.num_neg_literal;
689 for (int k = 0; k < data.num_neg_literal; ++k, ++i) {
690 const int c = ct_buffer_[i];
691 if (num_false_enforcement_[c] == 0) {
692 // Since delta != 0, we are sure this is an enforced -> unenforced change.
693 DCHECK_EQ(delta, 1);
694 result -= weights[c] * static_cast<double>(distances_[c]);
695 } else {
696 if (delta == -1 && num_false_enforcement_[c] == 1) {
697 result += weights[c] * static_cast<double>(distances_[c]);
698 }
699 }
700 }
701
702 int j = data.linear_start;
703 dtime_ += 2 * data.num_linear_entries;
704 for (int k = 0; k < data.num_linear_entries; ++k, ++i, ++j) {
705 const int c = ct_buffer_[i];
706 if (num_false_enforcement_[c] > 0) continue;
707 const int64_t coeff = coeff_buffer_[j];
708 const int64_t old_distance = distances_[c];
709 const int64_t new_distance =
710 domains_[c].Distance(activities_[c] + coeff * delta);
711 result += weights[c] * static_cast<double>(new_distance - old_distance);
712 }
713
714 return result;
715}
716
718 if (var >= columns_.size()) return false;
719 for (const int c : VarToConstraints(var)) {
720 if (Violation(c) > 0) return true;
721 }
722 return false;
723}
724
726 int var, int64_t current_value, const Domain& var_domain) const {
727 std::vector<int64_t> result = var_domain.FlattenedIntervals();
728 if (var_domain.Size() <= 2 || var >= columns_.size()) return result;
729
730 const SpanData& data = columns_[var];
731 int i = data.start + data.num_pos_literal + data.num_neg_literal;
732 int j = data.linear_start;
733 for (int k = 0; k < data.num_linear_entries; ++k, ++i, ++j) {
734 const int c = ct_buffer_[i];
735 if (num_false_enforcement_[c] > 0) continue;
736
737 // We only consider min / max.
738 // There is a change when we cross the slack.
739 // TODO(user): Deal with holes?
740 const int64_t coeff = coeff_buffer_[j];
741 const int64_t activity = activities_[c] - current_value * coeff;
742
743 const int64_t slack_min = CapSub(domains_[c].Min(), activity);
744 const int64_t slack_max = CapSub(domains_[c].Max(), activity);
745 if (slack_min != std::numeric_limits<int64_t>::min()) {
746 const int64_t ceil_bp = CeilOfRatio(slack_min, coeff);
747 if (ceil_bp != result.back() && var_domain.Contains(ceil_bp)) {
748 result.push_back(ceil_bp);
749 }
750 const int64_t floor_bp = FloorOfRatio(slack_min, coeff);
751 if (floor_bp != result.back() && var_domain.Contains(floor_bp)) {
752 result.push_back(floor_bp);
753 }
754 }
755 if (slack_min != slack_max &&
756 slack_max != std::numeric_limits<int64_t>::min()) {
757 const int64_t ceil_bp = CeilOfRatio(slack_max, coeff);
758 if (ceil_bp != result.back() && var_domain.Contains(ceil_bp)) {
759 result.push_back(ceil_bp);
760 }
761 const int64_t floor_bp = FloorOfRatio(slack_max, coeff);
762 if (floor_bp != result.back() && var_domain.Contains(floor_bp)) {
763 result.push_back(floor_bp);
764 }
765 }
766 }
767
769 return result;
770}
771
773 absl::Span<const int64_t> var_max_variation) {
774 creation_phase_ = false;
775 if (num_constraints_ == 0) return;
776
777 // Compute the total size.
778 // Note that at this point the constraint indices are not "encoded" yet.
779 int total_size = 0;
780 int total_linear_size = 0;
781 tmp_row_sizes_.assign(num_constraints_, 0);
782 tmp_row_num_positive_literals_.assign(num_constraints_, 0);
783 tmp_row_num_negative_literals_.assign(num_constraints_, 0);
784 tmp_row_num_linear_entries_.assign(num_constraints_, 0);
785 for (const auto& column : literal_entries_) {
786 total_size += column.size();
787 for (const auto [c, is_positive] : column) {
788 tmp_row_sizes_[c]++;
789 if (is_positive) {
790 tmp_row_num_positive_literals_[c]++;
791 } else {
792 tmp_row_num_negative_literals_[c]++;
793 }
794 }
795 }
796
797 row_max_variations_.assign(num_constraints_, 0);
798 for (int var = 0; var < var_entries_.size(); ++var) {
799 const int64_t range = var_max_variation[var];
800 const auto& column = var_entries_[var];
801 total_size += column.size();
802 total_linear_size += column.size();
803 for (const auto [c, coeff] : column) {
804 tmp_row_sizes_[c]++;
805 tmp_row_num_linear_entries_[c]++;
806 row_max_variations_[c] =
807 std::max(row_max_variations_[c], range * std::abs(coeff));
808 }
809 }
810
811 // Compactify for faster WeightedViolationDelta().
812 ct_buffer_.reserve(total_size);
813 coeff_buffer_.reserve(total_linear_size);
814 columns_.resize(std::max(literal_entries_.size(), var_entries_.size()));
815 for (int var = 0; var < columns_.size(); ++var) {
816 columns_[var].start = static_cast<int>(ct_buffer_.size());
817 columns_[var].linear_start = static_cast<int>(coeff_buffer_.size());
818 if (var < literal_entries_.size()) {
819 for (const auto [c, is_positive] : literal_entries_[var]) {
820 if (is_positive) {
821 columns_[var].num_pos_literal++;
822 ct_buffer_.push_back(c);
823 }
824 }
825 for (const auto [c, is_positive] : literal_entries_[var]) {
826 if (!is_positive) {
827 columns_[var].num_neg_literal++;
828 ct_buffer_.push_back(c);
829 }
830 }
831 }
832 if (var < var_entries_.size()) {
833 for (const auto [c, coeff] : var_entries_[var]) {
834 columns_[var].num_linear_entries++;
835 ct_buffer_.push_back(c);
836 coeff_buffer_.push_back(coeff);
837 }
838 }
839 }
840
841 // We do not need var_entries_ or literal_entries_ anymore.
842 //
843 // TODO(user): We could delete them before. But at the time of this
844 // optimization, I didn't want to change the behavior of the algorithm at all.
845 gtl::STLClearObject(&var_entries_);
846 gtl::STLClearObject(&literal_entries_);
847
848 // Initialize the SpanData.
849 // Transform tmp_row_sizes_ to starts in the row_var_buffer_.
850 // Transform tmp_row_num_linear_entries_ to starts in the row_coeff_buffer_.
851 int offset = 0;
852 int linear_offset = 0;
853 rows_.resize(num_constraints_);
854 for (int c = 0; c < num_constraints_; ++c) {
855 rows_[c].num_pos_literal = tmp_row_num_positive_literals_[c];
856 rows_[c].num_neg_literal = tmp_row_num_negative_literals_[c];
857 rows_[c].num_linear_entries = tmp_row_num_linear_entries_[c];
858
859 rows_[c].start = offset;
860 offset += tmp_row_sizes_[c];
861 tmp_row_sizes_[c] = rows_[c].start;
862
863 rows_[c].linear_start = linear_offset;
864 linear_offset += tmp_row_num_linear_entries_[c];
865 tmp_row_num_linear_entries_[c] = rows_[c].linear_start;
866 }
867 DCHECK_EQ(offset, total_size);
868 DCHECK_EQ(linear_offset, total_linear_size);
869
870 // Copy data.
871 row_var_buffer_.resize(total_size);
872 row_coeff_buffer_.resize(total_linear_size);
873 for (int var = 0; var < columns_.size(); ++var) {
874 const SpanData& data = columns_[var];
875 int i = data.start;
876 for (int k = 0; k < data.num_pos_literal; ++i, ++k) {
877 const int c = ct_buffer_[i];
878 row_var_buffer_[tmp_row_sizes_[c]++] = var;
879 }
880 }
881 for (int var = 0; var < columns_.size(); ++var) {
882 const SpanData& data = columns_[var];
883 int i = data.start + data.num_pos_literal;
884 for (int k = 0; k < data.num_neg_literal; ++i, ++k) {
885 const int c = ct_buffer_[i];
886 row_var_buffer_[tmp_row_sizes_[c]++] = var;
887 }
888 }
889 for (int var = 0; var < columns_.size(); ++var) {
890 const SpanData& data = columns_[var];
891 int i = data.start + data.num_pos_literal + data.num_neg_literal;
892 int j = data.linear_start;
893 for (int k = 0; k < data.num_linear_entries; ++i, ++j, ++k) {
894 const int c = ct_buffer_[i];
895 row_var_buffer_[tmp_row_sizes_[c]++] = var;
896 row_coeff_buffer_[tmp_row_num_linear_entries_[c]++] = coeff_buffer_[j];
897 }
898 }
899
900 cached_deltas_.assign(columns_.size(), 0);
901 cached_scores_.assign(columns_.size(), 0);
902}
903
905 for (const int c : VarToConstraints(var)) {
906 if (domains_[c].NumIntervals() > 2) return false;
907 }
908 return true;
909}
910
911// ----- CompiledConstraint -----
912
913CompiledConstraint::CompiledConstraint(const ConstraintProto& ct_proto)
914 : ct_proto_(ct_proto) {}
915
917 absl::Span<const int64_t> solution) {
918 violation_ = ComputeViolation(solution);
919}
920
922 int var, int64_t old_value,
923 absl::Span<const int64_t> solution_with_new_value) {
924 violation_ += ViolationDelta(var, old_value, solution_with_new_value);
925}
926
927int64_t CompiledConstraint::ViolationDelta(int /*var*/, int64_t /*old_value*/,
928 absl::Span<const int64_t> solution) {
929 return ComputeViolation(solution) - violation_;
930}
931
932// ----- CompiledBoolXorConstraint -----
933
935 const ConstraintProto& ct_proto)
936 : CompiledConstraint(ct_proto) {}
937
939 absl::Span<const int64_t> solution) {
940 int64_t sum_of_literals = 0;
941 for (const int lit : ct_proto().bool_xor().literals()) {
942 sum_of_literals += LiteralValue(lit, solution);
943 }
944 return 1 - (sum_of_literals % 2);
945}
946
948 int /*var*/, int64_t /*old_value*/,
949 absl::Span<const int64_t> /*solution_with_new_value*/) {
950 return violation() == 0 ? 1 : -1;
951}
952
953// ----- CompiledLinMaxConstraint -----
954
956 const ConstraintProto& ct_proto)
957 : CompiledConstraint(ct_proto) {}
958
960 absl::Span<const int64_t> solution) {
961 const int64_t target_value =
962 ExprValue(ct_proto().lin_max().target(), solution);
963 int64_t max_of_expressions = std::numeric_limits<int64_t>::min();
964 for (const LinearExpressionProto& expr : ct_proto().lin_max().exprs()) {
965 const int64_t expr_value = ExprValue(expr, solution);
966 max_of_expressions = std::max(max_of_expressions, expr_value);
967 }
968 return std::max(target_value - max_of_expressions, int64_t{0});
969}
970
971// ----- CompiledIntProdConstraint -----
972
974 const ConstraintProto& ct_proto)
975 : CompiledConstraint(ct_proto) {}
976
978 absl::Span<const int64_t> solution) {
979 const int64_t target_value =
980 ExprValue(ct_proto().int_prod().target(), solution);
981 int64_t prod_value = 1;
982 for (const LinearExpressionProto& expr : ct_proto().int_prod().exprs()) {
983 prod_value *= ExprValue(expr, solution);
984 }
985 return std::abs(target_value - prod_value);
986}
987
988// ----- CompiledIntDivConstraint -----
989
991 const ConstraintProto& ct_proto)
992 : CompiledConstraint(ct_proto) {}
993
995 absl::Span<const int64_t> solution) {
996 const int64_t target_value =
997 ExprValue(ct_proto().int_div().target(), solution);
998 DCHECK_EQ(ct_proto().int_div().exprs_size(), 2);
999 const int64_t div_value = ExprValue(ct_proto().int_div().exprs(0), solution) /
1000 ExprValue(ct_proto().int_div().exprs(1), solution);
1001 return std::abs(target_value - div_value);
1002}
1003
1004// ----- CompiledAllDiffConstraint -----
1005
1007 const ConstraintProto& ct_proto)
1008 : CompiledConstraint(ct_proto) {}
1009
1011 absl::Span<const int64_t> solution) {
1012 values_.clear();
1013 for (const LinearExpressionProto& expr : ct_proto().all_diff().exprs()) {
1014 values_.push_back(ExprValue(expr, solution));
1015 }
1016 std::sort(values_.begin(), values_.end());
1017
1018 int64_t value = values_[0];
1019 int counter = 1;
1020 int64_t violation = 0;
1021 for (int i = 1; i < values_.size(); ++i) {
1022 const int64_t new_value = values_[i];
1023 if (new_value == value) {
1024 counter++;
1025 } else {
1026 violation += counter * (counter - 1) / 2;
1027 counter = 1;
1028 value = new_value;
1029 }
1030 }
1031 violation += counter * (counter - 1) / 2;
1032 return violation;
1033}
1034
1035// ----- CompiledNoOverlapConstraint -----
1036
1037namespace {
1038int64_t ComputeOverloadArea(
1039 absl::Span<const int> intervals,
1040 absl::Span<const LinearExpressionProto* const> demands,
1041 const CpModelProto& cp_model, const absl::Span<const int64_t> solution,
1042 int64_t max_capacity, std::vector<std::pair<int64_t, int64_t>>& events) {
1043 events.clear();
1044 for (int i = 0; i < intervals.size(); ++i) {
1045 const int i_var = intervals[i];
1046 const ConstraintProto& interval_proto = cp_model.constraints(i_var);
1047 if (!interval_proto.enforcement_literal().empty() &&
1048 !LiteralValue(interval_proto.enforcement_literal(0), solution)) {
1049 continue;
1050 }
1051
1052 const int64_t demand =
1053 demands.empty() ? 1 : ExprValue(*demands[i], solution);
1054 if (demand == 0) continue;
1055
1056 const int64_t start =
1057 ExprValue(interval_proto.interval().start(), solution);
1058 const int64_t size = ExprValue(interval_proto.interval().size(), solution);
1059 const int64_t end = ExprValue(interval_proto.interval().end(), solution);
1060 const int64_t max_end = std::max(start + size, end);
1061 if (start >= max_end) continue;
1062
1063 events.emplace_back(start, demand);
1064 events.emplace_back(max_end, -demand);
1065 }
1066
1067 if (events.empty()) return 0;
1068 std::sort(events.begin(), events.end(),
1069 [](const std::pair<int64_t, int64_t>& e1,
1070 const std::pair<int64_t, int64_t>& e2) {
1071 return e1.first < e2.first;
1072 });
1073
1074 int64_t overload = 0;
1075 int64_t current_load = 0;
1076 int64_t previous_time = events.front().first;
1077 for (int i = 0; i < events.size();) {
1078 // At this point, current_load is the load at previous_time.
1079 const int64_t time = events[i].first;
1080 if (current_load > max_capacity) {
1081 overload = CapAdd(
1082 overload, CapProd(current_load - max_capacity, time - previous_time));
1083 }
1084 while (i < events.size() && events[i].first == time) {
1085 current_load += events[i].second;
1086 i++;
1087 }
1088 DCHECK_GE(current_load, 0);
1089 previous_time = time;
1090 }
1091 DCHECK_EQ(current_load, 0);
1092 return overload;
1093}
1094
1095int64_t ComputeOverlap(const ConstraintProto& interval1,
1096 const ConstraintProto& interval2,
1097 absl::Span<const int64_t> solution) {
1098 for (const int lit : interval1.enforcement_literal()) {
1099 if (!LiteralValue(lit, solution)) return 0;
1100 }
1101 for (const int lit : interval2.enforcement_literal()) {
1102 if (!LiteralValue(lit, solution)) return 0;
1103 }
1104
1105 const int64_t start1 = ExprValue(interval1.interval().start(), solution);
1106 const int64_t size1 = ExprValue(interval1.interval().size(), solution);
1107 const int64_t end1 =
1108 std::min(start1 + size1, ExprValue(interval1.interval().end(), solution));
1109
1110 const int64_t start2 = ExprValue(interval2.interval().start(), solution);
1111 const int64_t size2 = ExprValue(interval2.interval().size(), solution);
1112 const int64_t end2 =
1113 std::min(start2 + size2, ExprValue(interval2.interval().end(), solution));
1114
1115 if (start1 >= end2 || start2 >= end1) return 0; // Disjoint.
1116
1117 // We force a min cost of 1 to cover the case where a interval of size 0 is in
1118 // the middle of another interval.
1119 return std::max(std::min(std::min(end2 - start2, end1 - start1),
1120 std::min(end2 - start1, end1 - start2)),
1121 int64_t{1});
1122}
1123
1124} // namespace
1125
1127 const ConstraintProto& ct_proto, const CpModelProto& cp_model)
1128 : CompiledConstraint(ct_proto), cp_model_(cp_model) {}
1129
1131 absl::Span<const int64_t> solution) {
1132 DCHECK_GE(ct_proto().no_overlap().intervals_size(), 2);
1133 if (ct_proto().no_overlap().intervals_size() == 2) {
1134 return ComputeOverlap(
1135 cp_model_.constraints(ct_proto().no_overlap().intervals(0)),
1136 cp_model_.constraints(ct_proto().no_overlap().intervals(1)), solution);
1137 }
1138 return ComputeOverloadArea(ct_proto().no_overlap().intervals(), {}, cp_model_,
1139 solution, 1, events_);
1140}
1141
1142// ----- CompiledCumulativeConstraint -----
1143
1145 const ConstraintProto& ct_proto, const CpModelProto& cp_model)
1146 : CompiledConstraint(ct_proto), cp_model_(cp_model) {}
1147
1149 absl::Span<const int64_t> solution) {
1150 return ComputeOverloadArea(
1151 ct_proto().cumulative().intervals(), ct_proto().cumulative().demands(),
1152 cp_model_, solution,
1153 ExprValue(ct_proto().cumulative().capacity(), solution), events_);
1154}
1155
1156// ----- CompiledCumulativeConstraint -----
1157
1159 const ConstraintProto& ct_proto, const CpModelProto& cp_model)
1160 : CompiledConstraint(ct_proto), cp_model_(cp_model) {}
1161
1162int64_t CompiledNoOverlap2dConstraint::ComputeOverlapArea(
1163 absl::Span<const int64_t> solution, int i, int j) const {
1164 const int64_t x_overlap = ComputeOverlap(
1165 cp_model_.constraints(ct_proto().no_overlap_2d().x_intervals(i)),
1166 cp_model_.constraints(ct_proto().no_overlap_2d().x_intervals(j)),
1167 solution);
1168 if (x_overlap > 0) {
1169 return x_overlap *
1170 ComputeOverlap(
1171 cp_model_.constraints(ct_proto().no_overlap_2d().y_intervals(i)),
1172 cp_model_.constraints(ct_proto().no_overlap_2d().y_intervals(j)),
1173 solution);
1174 } else {
1175 return 0;
1176 }
1177}
1178
1180 absl::Span<const int64_t> solution) {
1181 DCHECK_GE(ct_proto().no_overlap_2d().x_intervals_size(), 2);
1182 const int size = ct_proto().no_overlap_2d().x_intervals_size();
1183 int64_t violation = 0;
1184 for (int i = 0; i + 1 < size; ++i) {
1185 for (int j = i + 1; j < size; ++j) {
1186 violation += ComputeOverlapArea(solution, i, j);
1187 }
1188 }
1189 return violation;
1190}
1191
1192// ----- CompiledCircuitConstraint -----
1193
1194// The violation of a circuit has three parts:
1195// 1. Flow imbalance, maintained by the linear part.
1196// 2. The number of non-skipped SCCs in the graph minus 1.
1197// 3. The number of non-skipped SCCs that cannot be reached from any other
1198// component minus 1.
1199//
1200// #3 is not necessary for correctness, but makes the function much smoother.
1201//
1202// The only difference between single and multi circuit is flow balance at the
1203// depot, so we use the same compiled constraint for both.
1205 public:
1206 explicit CompiledCircuitConstraint(const ConstraintProto& ct_proto);
1207 ~CompiledCircuitConstraint() override = default;
1208
1209 int64_t ComputeViolation(absl::Span<const int64_t> solution) override;
1210
1211 private:
1212 struct SccOutput {
1213 void emplace_back(const int* start, const int* end);
1214 void reset(int num_nodes);
1215
1216 int num_components = 0;
1217 std::vector<bool> skipped;
1218 std::vector<int> root;
1219 };
1220 void UpdateGraph(absl::Span<const int64_t> solution);
1221 absl::Span<const int> literals_;
1222 absl::Span<const int> tails_;
1223 absl::Span<const int> heads_;
1224 // Stores the currently active arcs per tail node.
1225 std::vector<std::vector<int>> graph_;
1226 SccOutput sccs_;
1227 std::vector<bool> has_in_arc_;
1229 SccOutput>
1230 scc_finder_;
1231};
1232
1233void CompiledCircuitConstraint::SccOutput::emplace_back(int const* start,
1234 int const* end) {
1235 const int root_node = *start;
1236 const int size = end - start;
1237 if (size == 1) {
1238 skipped[root_node] = true;
1239 } else {
1240 ++num_components;
1241 }
1242 for (; start != end; ++start) {
1243 root[*start] = root_node;
1244 }
1245}
1246void CompiledCircuitConstraint::SccOutput::reset(int num_nodes) {
1247 num_components = 0;
1248 root.clear();
1249 root.resize(num_nodes);
1250 skipped.clear();
1251 skipped.resize(num_nodes);
1252}
1253
1255 const ConstraintProto& ct_proto)
1257 const bool routes = ct_proto.has_routes();
1258 tails_ = routes ? ct_proto.routes().tails() : ct_proto.circuit().tails();
1259 heads_ = absl::MakeConstSpan(routes ? ct_proto.routes().heads()
1260 : ct_proto.circuit().heads());
1261 literals_ = absl::MakeConstSpan(routes ? ct_proto.routes().literals()
1262 : ct_proto.circuit().literals());
1263 graph_.resize(*absl::c_max_element(tails_) + 1);
1264}
1265
1266void CompiledCircuitConstraint::UpdateGraph(
1267 absl::Span<const int64_t> solution) {
1268 for (std::vector<int>& edges : graph_) {
1269 edges.clear();
1270 }
1271 for (int i = 0; i < tails_.size(); ++i) {
1272 if (!LiteralValue(literals_[i], solution)) continue;
1273 graph_[tails_[i]].push_back(heads_[i]);
1274 }
1275}
1277 absl::Span<const int64_t> solution) {
1278 const int num_nodes = graph_.size();
1279 sccs_.reset(num_nodes);
1280 UpdateGraph(solution);
1281 scc_finder_.FindStronglyConnectedComponents(num_nodes, graph_, &sccs_);
1282 // Skipping all nodes causes off-by-one errors below, so it's simpler to
1283 // handle explicitly.
1284 if (sccs_.num_components == 0) return 0;
1285 // Count the number of SCCs that have inbound cross-component arcs
1286 // as a smoother measure of progress towards strong connectivity.
1287 int num_half_connected_components = 0;
1288 has_in_arc_.clear();
1289 has_in_arc_.resize(num_nodes, false);
1290 for (int tail = 0; tail < graph_.size(); ++tail) {
1291 if (sccs_.skipped[tail]) continue;
1292 for (const int head : graph_[tail]) {
1293 const int head_root = sccs_.root[head];
1294 if (sccs_.root[tail] == head_root) continue;
1295 if (has_in_arc_[head_root]) continue;
1296 if (sccs_.skipped[head_root]) continue;
1297 has_in_arc_[head_root] = true;
1298 ++num_half_connected_components;
1299 }
1300 }
1301 const int64_t violation = sccs_.num_components - 1 + sccs_.num_components -
1302 num_half_connected_components - 1 +
1303 (ct_proto().has_routes() ? sccs_.skipped[0] : 0);
1304 VLOG(2) << "#SCCs=" << sccs_.num_components << " #nodes=" << num_nodes
1305 << " #half_connected_components=" << num_half_connected_components
1306 << " violation=" << violation;
1307 return violation;
1308}
1309
1311 const ConstraintProto& ct_proto) {
1312 const bool routes = ct_proto.has_routes();
1313 auto heads = routes ? ct_proto.routes().heads() : ct_proto.circuit().heads();
1314 auto tails = routes ? ct_proto.routes().tails() : ct_proto.circuit().tails();
1315 auto literals =
1316 routes ? ct_proto.routes().literals() : ct_proto.circuit().literals();
1317
1318 std::vector<std::vector<int>> inflow_lits;
1319 std::vector<std::vector<int>> outflow_lits;
1320 for (int i = 0; i < heads.size(); ++i) {
1321 if (heads[i] >= inflow_lits.size()) {
1322 inflow_lits.resize(heads[i] + 1);
1323 }
1324 inflow_lits[heads[i]].push_back(literals[i]);
1325 if (tails[i] >= outflow_lits.size()) {
1326 outflow_lits.resize(tails[i] + 1);
1327 }
1328 outflow_lits[tails[i]].push_back(literals[i]);
1329 }
1330 if (routes) {
1331 const int depot_net_flow = linear_evaluator.NewConstraint({0, 0});
1332 for (const int lit : inflow_lits[0]) {
1333 linear_evaluator.AddLiteral(depot_net_flow, lit, 1);
1334 }
1335 for (const int lit : outflow_lits[0]) {
1336 linear_evaluator.AddLiteral(depot_net_flow, lit, -1);
1337 }
1338 }
1339 for (int i = routes ? 1 : 0; i < inflow_lits.size(); ++i) {
1340 const int inflow_ct = linear_evaluator.NewConstraint({1, 1});
1341 for (const int lit : inflow_lits[i]) {
1342 linear_evaluator.AddLiteral(inflow_ct, lit);
1343 }
1344 }
1345 for (int i = routes ? 1 : 0; i < outflow_lits.size(); ++i) {
1346 const int outflow_ct = linear_evaluator.NewConstraint({1, 1});
1347 for (const int lit : outflow_lits[i]) {
1348 linear_evaluator.AddLiteral(outflow_ct, lit);
1349 }
1350 }
1351}
1352
1353// ----- LsEvaluator -----
1354
1355LsEvaluator::LsEvaluator(const CpModelProto& cp_model,
1356 const SatParameters& params)
1357 : cp_model_(cp_model), params_(params) {
1358 var_to_constraints_.resize(cp_model_.variables_size());
1359 jump_value_optimal_.resize(cp_model_.variables_size(), true);
1360 num_violated_constraint_per_var_.assign(cp_model_.variables_size(), 0);
1361
1362 std::vector<bool> ignored_constraints(cp_model_.constraints_size(), false);
1363 std::vector<ConstraintProto> additional_constraints;
1364 CompileConstraintsAndObjective(ignored_constraints, additional_constraints);
1365 BuildVarConstraintGraph();
1366 pos_in_violated_constraints_.assign(NumEvaluatorConstraints(), -1);
1367}
1368
1370 const CpModelProto& cp_model, const SatParameters& params,
1371 const std::vector<bool>& ignored_constraints,
1372 const std::vector<ConstraintProto>& additional_constraints)
1373 : cp_model_(cp_model), params_(params) {
1374 var_to_constraints_.resize(cp_model_.variables_size());
1375 jump_value_optimal_.resize(cp_model_.variables_size(), true);
1376 num_violated_constraint_per_var_.assign(cp_model_.variables_size(), 0);
1377 CompileConstraintsAndObjective(ignored_constraints, additional_constraints);
1378 BuildVarConstraintGraph();
1379 pos_in_violated_constraints_.assign(NumEvaluatorConstraints(), -1);
1380}
1381
1382void LsEvaluator::BuildVarConstraintGraph() {
1383 // Clear the var <-> constraint graph.
1384 for (std::vector<int>& ct_indices : var_to_constraints_) ct_indices.clear();
1385 constraint_to_vars_.resize(constraints_.size());
1386
1387 // Build the var <-> constraint graph.
1388 for (int ct_index = 0; ct_index < constraints_.size(); ++ct_index) {
1389 for (const int var : UsedVariables(constraints_[ct_index]->ct_proto())) {
1390 var_to_constraints_[var].push_back(ct_index);
1391 constraint_to_vars_[ct_index].push_back(var);
1392 }
1393 for (const int i_var : UsedIntervals(constraints_[ct_index]->ct_proto())) {
1394 const ConstraintProto& interval_proto = cp_model_.constraints(i_var);
1395 for (const int var : UsedVariables(interval_proto)) {
1396 var_to_constraints_[var].push_back(ct_index);
1397 constraint_to_vars_[ct_index].push_back(var);
1398 }
1399 }
1400 }
1401
1402 // Remove duplicates.
1403 for (std::vector<int>& constraints : var_to_constraints_) {
1404 gtl::STLSortAndRemoveDuplicates(&constraints);
1405 }
1406 for (std::vector<int>& vars : constraint_to_vars_) {
1408 }
1409
1410 // Scan the model to decide if a variable is linked to a convex evaluation.
1411 jump_value_optimal_.resize(cp_model_.variables_size());
1412 for (int i = 0; i < cp_model_.variables_size(); ++i) {
1413 if (!var_to_constraints_[i].empty()) {
1414 jump_value_optimal_[i] = false;
1415 continue;
1416 }
1417
1418 const IntegerVariableProto& var_proto = cp_model_.variables(i);
1419 if (var_proto.domain_size() == 2 && var_proto.domain(0) == 0 &&
1420 var_proto.domain(1) == 1) {
1421 // Boolean variables violation change is always convex.
1422 jump_value_optimal_[i] = true;
1423 continue;
1424 }
1425
1426 jump_value_optimal_[i] = linear_evaluator_.ViolationChangeIsConvex(i);
1427 }
1428}
1429
1430void LsEvaluator::CompileOneConstraint(const ConstraintProto& ct) {
1431 switch (ct.constraint_case()) {
1432 case ConstraintProto::ConstraintCase::kBoolOr: {
1433 // Encoding using enforcement literal is slightly more efficient.
1434 const int ct_index = linear_evaluator_.NewConstraint(Domain(1, 1));
1435 for (const int lit : ct.enforcement_literal()) {
1436 linear_evaluator_.AddEnforcementLiteral(ct_index, lit);
1437 }
1438 for (const int lit : ct.bool_or().literals()) {
1439 linear_evaluator_.AddEnforcementLiteral(ct_index, NegatedRef(lit));
1440 }
1441 break;
1442 }
1443 case ConstraintProto::ConstraintCase::kBoolAnd: {
1444 const int num_literals = ct.bool_and().literals_size();
1445 const int ct_index =
1446 linear_evaluator_.NewConstraint(Domain(num_literals));
1447 for (const int lit : ct.enforcement_literal()) {
1448 linear_evaluator_.AddEnforcementLiteral(ct_index, lit);
1449 }
1450 for (const int lit : ct.bool_and().literals()) {
1451 linear_evaluator_.AddLiteral(ct_index, lit);
1452 }
1453 break;
1454 }
1455 case ConstraintProto::ConstraintCase::kAtMostOne: {
1456 DCHECK(ct.enforcement_literal().empty());
1457 const int ct_index = linear_evaluator_.NewConstraint({0, 1});
1458 for (const int lit : ct.at_most_one().literals()) {
1459 linear_evaluator_.AddLiteral(ct_index, lit);
1460 }
1461 break;
1462 }
1463 case ConstraintProto::ConstraintCase::kExactlyOne: {
1464 DCHECK(ct.enforcement_literal().empty());
1465 const int ct_index = linear_evaluator_.NewConstraint({1, 1});
1466 for (const int lit : ct.exactly_one().literals()) {
1467 linear_evaluator_.AddLiteral(ct_index, lit);
1468 }
1469 break;
1470 }
1471 case ConstraintProto::ConstraintCase::kBoolXor: {
1472 constraints_.emplace_back(new CompiledBoolXorConstraint(ct));
1473 break;
1474 }
1475 case ConstraintProto::ConstraintCase::kAllDiff: {
1476 constraints_.emplace_back(new CompiledAllDiffConstraint(ct));
1477 break;
1478 }
1479 case ConstraintProto::ConstraintCase::kLinMax: {
1480 // This constraint is split into linear precedences and its max
1481 // maintenance.
1482 const LinearExpressionProto& target = ct.lin_max().target();
1483 for (const LinearExpressionProto& expr : ct.lin_max().exprs()) {
1484 const int64_t max_value =
1485 ExprMax(target, cp_model_) - ExprMin(expr, cp_model_);
1486 const int precedence_index =
1487 linear_evaluator_.NewConstraint({0, max_value});
1488 linear_evaluator_.AddLinearExpression(precedence_index, target, 1);
1489 linear_evaluator_.AddLinearExpression(precedence_index, expr, -1);
1490 }
1491
1492 // Penalty when target > all expressions.
1493 constraints_.emplace_back(new CompiledLinMaxConstraint(ct));
1494 break;
1495 }
1496 case ConstraintProto::ConstraintCase::kIntProd: {
1497 constraints_.emplace_back(new CompiledIntProdConstraint(ct));
1498 break;
1499 }
1500 case ConstraintProto::ConstraintCase::kIntDiv: {
1501 constraints_.emplace_back(new CompiledIntDivConstraint(ct));
1502 break;
1503 }
1504 case ConstraintProto::ConstraintCase::kLinear: {
1505 const Domain domain = ReadDomainFromProto(ct.linear());
1506 const int ct_index = linear_evaluator_.NewConstraint(domain);
1507 for (const int lit : ct.enforcement_literal()) {
1508 linear_evaluator_.AddEnforcementLiteral(ct_index, lit);
1509 }
1510 for (int i = 0; i < ct.linear().vars_size(); ++i) {
1511 const int var = ct.linear().vars(i);
1512 const int64_t coeff = ct.linear().coeffs(i);
1513 linear_evaluator_.AddTerm(ct_index, var, coeff);
1514 }
1515 break;
1516 }
1517 case ConstraintProto::ConstraintCase::kNoOverlap: {
1518 if (ct.no_overlap().intervals_size() <= 1) break;
1519 if (ct.no_overlap().intervals_size() >
1520 params_.feasibility_jump_max_expanded_constraint_size()) {
1521 CompiledNoOverlapConstraint* no_overlap =
1522 new CompiledNoOverlapConstraint(ct, cp_model_);
1523 constraints_.emplace_back(no_overlap);
1524 } else {
1525 // We expand the no_overlap constraints into a quadratic number of
1526 // disjunctions.
1527 for (int i = 0; i + 1 < ct.no_overlap().intervals_size(); ++i) {
1528 const IntervalConstraintProto& interval_i =
1529 cp_model_.constraints(ct.no_overlap().intervals(i)).interval();
1530 const int64_t min_start_i = ExprMin(interval_i.start(), cp_model_);
1531 const int64_t max_end_i = ExprMax(interval_i.end(), cp_model_);
1532 for (int j = i + 1; j < ct.no_overlap().intervals_size(); ++j) {
1533 const IntervalConstraintProto& interval_j =
1534 cp_model_.constraints(ct.no_overlap().intervals(j)).interval();
1535 const int64_t min_start_j = ExprMin(interval_j.start(), cp_model_);
1536 const int64_t max_end_j = ExprMax(interval_j.end(), cp_model_);
1537 if (min_start_i >= max_end_j || min_start_j >= max_end_i) continue;
1538 ConstraintProto* disj = expanded_constraints_.add_constraints();
1539 disj->mutable_no_overlap()->add_intervals(
1540 ct.no_overlap().intervals(i));
1541 disj->mutable_no_overlap()->add_intervals(
1542 ct.no_overlap().intervals(j));
1543 CompiledNoOverlapConstraint* no_overlap =
1544 new CompiledNoOverlapConstraint(*disj, cp_model_);
1545 constraints_.emplace_back(no_overlap);
1546 }
1547 }
1548 }
1549 break;
1550 }
1551 case ConstraintProto::ConstraintCase::kCumulative: {
1552 constraints_.emplace_back(
1553 new CompiledCumulativeConstraint(ct, cp_model_));
1554 break;
1555 }
1556 case ConstraintProto::ConstraintCase::kNoOverlap2D: {
1557 const auto& x_intervals = ct.no_overlap_2d().x_intervals();
1558 const auto& y_intervals = ct.no_overlap_2d().y_intervals();
1559 if (x_intervals.size() <= 1) break;
1560 if (x_intervals.size() >
1561 params_.feasibility_jump_max_expanded_constraint_size()) {
1562 CompiledNoOverlap2dConstraint* no_overlap_2d =
1563 new CompiledNoOverlap2dConstraint(ct, cp_model_);
1564 constraints_.emplace_back(no_overlap_2d);
1565 break;
1566 }
1567
1568 for (int i = 0; i + 1 < x_intervals.size(); ++i) {
1569 const IntervalConstraintProto& x_interval_i =
1570 cp_model_.constraints(x_intervals[i]).interval();
1571 const int64_t x_min_start_i = ExprMin(x_interval_i.start(), cp_model_);
1572 const int64_t x_max_end_i = ExprMax(x_interval_i.end(), cp_model_);
1573 const IntervalConstraintProto& y_interval_i =
1574 cp_model_.constraints(y_intervals[i]).interval();
1575 const int64_t y_min_start_i = ExprMin(y_interval_i.start(), cp_model_);
1576 const int64_t y_max_end_i = ExprMax(y_interval_i.end(), cp_model_);
1577 for (int j = i + 1; j < x_intervals.size(); ++j) {
1578 const IntervalConstraintProto& x_interval_j =
1579 cp_model_.constraints(x_intervals[j]).interval();
1580 const int64_t x_min_start_j =
1581 ExprMin(x_interval_j.start(), cp_model_);
1582 const int64_t x_max_end_j = ExprMax(x_interval_j.end(), cp_model_);
1583 const IntervalConstraintProto& y_interval_j =
1584 cp_model_.constraints(y_intervals[j]).interval();
1585 const int64_t y_min_start_j =
1586 ExprMin(y_interval_j.start(), cp_model_);
1587 const int64_t y_max_end_j = ExprMax(y_interval_j.end(), cp_model_);
1588 if (x_min_start_i >= x_max_end_j || x_min_start_j >= x_max_end_i ||
1589 y_min_start_i >= y_max_end_j || y_min_start_j >= y_max_end_i) {
1590 continue;
1591 }
1592 ConstraintProto* diffn = expanded_constraints_.add_constraints();
1593 diffn->mutable_no_overlap_2d()->add_x_intervals(x_intervals[i]);
1594 diffn->mutable_no_overlap_2d()->add_x_intervals(x_intervals[j]);
1595 diffn->mutable_no_overlap_2d()->add_y_intervals(y_intervals[i]);
1596 diffn->mutable_no_overlap_2d()->add_y_intervals(y_intervals[j]);
1597 CompiledNoOverlap2dConstraint* no_overlap_2d =
1598 new CompiledNoOverlap2dConstraint(*diffn, cp_model_);
1599 constraints_.emplace_back(no_overlap_2d);
1600 }
1601 }
1602 break;
1603 }
1604 case ConstraintProto::ConstraintCase::kCircuit:
1605 case ConstraintProto::ConstraintCase::kRoutes:
1606 constraints_.emplace_back(new CompiledCircuitConstraint(ct));
1607 AddCircuitFlowConstraints(linear_evaluator_, ct);
1608 break;
1609 default:
1610 VLOG(1) << "Not implemented: " << ct.constraint_case();
1611 break;
1612 }
1613}
1614
1615void LsEvaluator::CompileConstraintsAndObjective(
1616 const std::vector<bool>& ignored_constraints,
1617 const std::vector<ConstraintProto>& additional_constraints) {
1618 constraints_.clear();
1619
1620 // The first compiled constraint is always the objective if present.
1621 if (cp_model_.has_objective()) {
1622 const int ct_index = linear_evaluator_.NewConstraint(
1623 cp_model_.objective().domain().empty()
1625 : ReadDomainFromProto(cp_model_.objective()));
1626 DCHECK_EQ(0, ct_index);
1627 for (int i = 0; i < cp_model_.objective().vars_size(); ++i) {
1628 const int var = cp_model_.objective().vars(i);
1629 const int64_t coeff = cp_model_.objective().coeffs(i);
1630 linear_evaluator_.AddTerm(ct_index, var, coeff);
1631 }
1632 }
1633
1634 for (int c = 0; c < cp_model_.constraints_size(); ++c) {
1635 if (ignored_constraints[c]) continue;
1636 CompileOneConstraint(cp_model_.constraints(c));
1637 }
1638
1639 for (const ConstraintProto& ct : additional_constraints) {
1640 CompileOneConstraint(ct);
1641 }
1642
1643 // Make sure we have access to the data in an efficient way.
1644 std::vector<int64_t> var_max_variations(cp_model_.variables().size());
1645 for (int var = 0; var < cp_model_.variables().size(); ++var) {
1646 const auto& domain = cp_model_.variables(var).domain();
1647 var_max_variations[var] = domain[domain.size() - 1] - domain[0];
1648 }
1649 linear_evaluator_.PrecomputeCompactView(var_max_variations);
1650}
1651
1652bool LsEvaluator::ReduceObjectiveBounds(int64_t lb, int64_t ub) {
1653 if (!cp_model_.has_objective()) return false;
1654 if (linear_evaluator_.ReduceBounds(/*c=*/0, lb, ub)) {
1655 UpdateViolatedList(0);
1656 return true;
1657 }
1658 return false;
1659}
1660
1661void LsEvaluator::OverwriteCurrentSolution(absl::Span<const int64_t> solution) {
1662 current_solution_.assign(solution.begin(), solution.end());
1663}
1664
1666 // Linear constraints.
1667 linear_evaluator_.ComputeInitialActivities(current_solution_);
1668
1669 // Generic constraints.
1670 for (const auto& ct : constraints_) {
1671 ct->InitializeViolation(current_solution_);
1672 }
1673
1674 RecomputeViolatedList(/*linear_only=*/false);
1675}
1676
1678 // Generic constraints.
1679 for (const auto& ct : constraints_) {
1680 ct->InitializeViolation(current_solution_);
1681 }
1682}
1683
1684void LsEvaluator::UpdateNonLinearViolations(int var, int64_t new_value) {
1685 const int64_t old_value = current_solution_[var];
1686 if (old_value == new_value) return;
1687
1688 current_solution_[var] = new_value;
1689 for (const int general_ct_index : var_to_constraints_[var]) {
1690 const int c = general_ct_index + linear_evaluator_.num_constraints();
1691 const int64_t v0 = constraints_[general_ct_index]->violation();
1692 constraints_[general_ct_index]->PerformMove(var, old_value,
1693 current_solution_);
1694 const int64_t violation_delta =
1695 constraints_[general_ct_index]->violation() - v0;
1696 last_update_violation_changes_.push_back({c, violation_delta});
1697 }
1698 current_solution_[var] = old_value;
1699}
1700
1702 absl::Span<const double> weights,
1703 absl::Span<const int64_t> jump_deltas,
1704 absl::Span<double> jump_scores) {
1705 DCHECK(RefIsPositive(var));
1706 const int64_t old_value = current_solution_[var];
1707 if (old_value == value) return;
1708 last_update_violation_changes_.clear();
1709 linear_evaluator_.ClearAffectedVariables();
1710 linear_evaluator_.UpdateVariableAndScores(var, value - old_value, weights,
1711 jump_deltas, jump_scores,
1712 &last_update_violation_changes_);
1713}
1714
1715void LsEvaluator::UpdateVariableValue(int var, int64_t new_value) {
1716 current_solution_[var] = new_value;
1717
1718 // Maintain the list of violated constraints.
1719 for (const auto [c, delta] : last_update_violation_changes_) {
1720 UpdateViolatedList(c);
1721 }
1722}
1723
1725 int64_t evaluation = 0;
1726
1727 // Process the linear part.
1728 for (int i = 0; i < linear_evaluator_.num_constraints(); ++i) {
1729 evaluation += linear_evaluator_.Violation(i);
1730 DCHECK_GE(linear_evaluator_.Violation(i), 0);
1731 }
1732
1733 // Process the generic constraint part.
1734 for (const auto& ct : constraints_) {
1735 evaluation += ct->violation();
1736 DCHECK_GE(ct->violation(), 0);
1737 }
1738 return evaluation;
1739}
1740
1742 DCHECK(cp_model_.has_objective());
1743 return linear_evaluator_.Activity(/*c=*/0);
1744}
1745
1747 return linear_evaluator_.num_constraints();
1748}
1749
1751 return static_cast<int>(constraints_.size());
1752}
1753
1755 return linear_evaluator_.num_constraints() +
1756 static_cast<int>(constraints_.size());
1757}
1758
1760 int result = 0;
1761 for (int c = 0; c < linear_evaluator_.num_constraints(); ++c) {
1762 if (linear_evaluator_.Violation(c) > 0) {
1763 ++result;
1764 }
1765 }
1766 for (const auto& constraint : constraints_) {
1767 if (constraint->violation() > 0) {
1768 ++result;
1769 }
1770 }
1771 return result;
1772}
1773
1774int64_t LsEvaluator::Violation(int c) const {
1775 if (c < linear_evaluator_.num_constraints()) {
1776 return linear_evaluator_.Violation(c);
1777 } else {
1778 return constraints_[c - linear_evaluator_.num_constraints()]->violation();
1779 }
1780}
1781
1782bool LsEvaluator::IsViolated(int c) const {
1783 if (c < linear_evaluator_.num_constraints()) {
1784 return linear_evaluator_.IsViolated(c);
1785 } else {
1786 return constraints_[c - linear_evaluator_.num_constraints()]->violation() >
1787 0;
1788 }
1789}
1790
1791double LsEvaluator::WeightedViolation(absl::Span<const double> weights) const {
1792 DCHECK_EQ(weights.size(), NumEvaluatorConstraints());
1793 double violations = linear_evaluator_.WeightedViolation(weights);
1794
1795 const int num_linear_constraints = linear_evaluator_.num_constraints();
1796 for (int c = 0; c < constraints_.size(); ++c) {
1797 violations += static_cast<double>(constraints_[c]->violation()) *
1798 weights[num_linear_constraints + c];
1799 }
1800 return violations;
1801}
1802
1804 absl::Span<const double> weights, int var, int64_t delta) const {
1805 const int64_t old_value = current_solution_[var];
1806 double violation_delta = 0;
1807 // We change the mutable solution here, are restore it after the evaluation.
1808 current_solution_[var] += delta;
1809 const int num_linear_constraints = linear_evaluator_.num_constraints();
1810 for (const int ct_index : var_to_constraints_[var]) {
1811 DCHECK_LT(ct_index, constraints_.size());
1812 const int64_t delta = constraints_[ct_index]->ViolationDelta(
1813 var, old_value, current_solution_);
1814 violation_delta +=
1815 static_cast<double>(delta) * weights[ct_index + num_linear_constraints];
1816 }
1817 // Restore.
1818 current_solution_[var] -= delta;
1819 return violation_delta;
1820}
1821
1822double LsEvaluator::WeightedViolationDelta(absl::Span<const double> weights,
1823 int var, int64_t delta) const {
1824 DCHECK_LT(var, current_solution_.size());
1825 return linear_evaluator_.WeightedViolationDelta(weights, var, delta) +
1827}
1828
1830 int var) const {
1831 return jump_value_optimal_[var];
1832}
1833
1835 num_violated_constraint_per_var_.assign(cp_model_.variables_size(), 0);
1836 violated_constraints_.clear();
1837 pos_in_violated_constraints_.assign(NumEvaluatorConstraints(), -1);
1838
1839 const int num_constraints =
1841 for (int c = 0; c < num_constraints; ++c) {
1842 UpdateViolatedList(c);
1843 }
1844}
1845
1846void LsEvaluator::UpdateViolatedList(const int c) {
1847 const int pos = pos_in_violated_constraints_[c];
1848
1849 if (Violation(c) > 0) {
1850 // The constraint is violated. Add if needed.
1851 if (pos != -1) return;
1852 pos_in_violated_constraints_[c] = violated_constraints_.size();
1853 violated_constraints_.push_back(c);
1854 for (const int v : ConstraintToVars(c)) {
1855 num_violated_constraint_per_var_[v] += 1;
1856 }
1857 return;
1858 }
1859
1860 // The constraint is not violated. Remove if needed.
1861 if (pos == -1) return;
1862 const int last = violated_constraints_.back();
1863 pos_in_violated_constraints_[last] = pos;
1864 violated_constraints_[pos] = last;
1865 pos_in_violated_constraints_[c] = -1;
1866 violated_constraints_.pop_back();
1867 for (const int v : ConstraintToVars(c)) {
1868 num_violated_constraint_per_var_[v] -= 1;
1869 }
1870}
1871
1872} // namespace sat
1873} // namespace operations_research
void FindStronglyConnectedComponents(const NodeIndex num_nodes, const Graph &graph, SccOutput *components)
std::vector< int64_t > FlattenedIntervals() const
bool Contains(int64_t value) const
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
int64_t ComputeViolation(absl::Span< const int64_t > solution) override
void PerformMove(int var, int64_t old_value, absl::Span< const int64_t > solution_with_new_value)
Update 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.
CompiledConstraint(const ConstraintProto &ct_proto)
--— CompiledConstraint --—
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].
const ConstraintProto & ct_proto() const
Getters.
int64_t ComputeViolation(absl::Span< const int64_t > solution) override
CompiledCumulativeConstraint(const ConstraintProto &ct_proto, const CpModelProto &cp_model)
--— CompiledCumulativeConstraint --—
CompiledIntDivConstraint(const ConstraintProto &ct_proto)
--— CompiledIntDivConstraint --—
int64_t ComputeViolation(absl::Span< const int64_t > solution) override
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)
--— CompiledCumulativeConstraint --—
int64_t ComputeViolation(absl::Span< const int64_t > solution) override
CompiledNoOverlapConstraint(const ConstraintProto &ct_proto, const CpModelProto &cp_model)
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 Update(int var, int64_t delta, std::vector< std::pair< int, int64_t > > *violation_deltas=nullptr)
void ComputeInitialActivities(absl::Span< const int64_t > solution)
Compute activities and update them.
void AddLiteral(int ct_index, int lit, int64_t coeff=1)
int NewConstraint(Domain domain)
Returns the index of the new constraint.
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< std::pair< int, int64_t > > *violation_deltas=nullptr)
double WeightedViolation(absl::Span< const double > weights) const
bool VarIsConsistent(int var) const
Used to DCHECK the state of the evaluator.
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 UpdateAllNonLinearViolations()
Recompute the violations of non linear constraints.
double WeightedNonLinearViolationDelta(absl::Span< const double > weights, int var, int64_t delta) const
Ignores the violations of the linear constraints.
void OverwriteCurrentSolution(absl::Span< const int64_t > solution)
Overwrites the current solution.
bool VariableOnlyInLinearConstraintWithConvexViolationChange(int var) const
Indicates if the computed jump value is always the best choice.
double WeightedViolationDelta(absl::Span< const double > weights, int var, int64_t delta) const
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.
void UpdateVariableValue(int var, int64_t new_value)
void UpdateNonLinearViolations(int var, int64_t new_value)
Recomputes the violations of all impacted non linear constraints.
absl::Span< const int > ConstraintToVars(int c) const
LsEvaluator(const CpModelProto &cp_model, const SatParameters &params)
The cp_model must outlive this class.
void ComputeAllViolations()
Computes the violations of all constraints.
void UpdateLinearScores(int var, int64_t 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.
const Constraint * ct
int64_t value
IntVar * var
GRBmodel * model
int lit
int ct_index
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 ExprMax(const LinearExpressionProto &expr, const CpModelProto &model)
bool LiteralValue(int lit, absl::Span< const int64_t > solution)
IntType CeilOfRatio(IntType numerator, IntType denominator)
Definition util.h:692
IntType FloorOfRatio(IntType numerator, IntType denominator)
Definition util.h:697
int64_t ExprValue(const LinearExpressionProto &expr, absl::Span< const int64_t > solution)
std::vector< int > UsedVariables(const ConstraintProto &ct)
std::vector< int > UsedIntervals(const ConstraintProto &ct)
Returns the sorted list of interval used by a constraint.
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.
int64_t ExprMin(const LinearExpressionProto &expr, const CpModelProto &model)
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