Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
diffn.cc
Go to the documentation of this file.
1// Copyright 2010-2025 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14#include "ortools/sat/diffn.h"
15
16#include <stddef.h>
17
18#include <algorithm>
19#include <cstddef>
20#include <cstdint>
21#include <iterator>
22#include <limits>
23#include <optional>
24#include <string>
25#include <utility>
26#include <vector>
27
28#include "absl/container/flat_hash_set.h"
29#include "absl/container/inlined_vector.h"
30#include "absl/log/check.h"
31#include "absl/log/log.h"
32#include "absl/log/vlog_is_on.h"
33#include "absl/numeric/bits.h"
34#include "absl/types/span.h"
39#include "ortools/sat/clause.h"
43#include "ortools/sat/integer.h"
47#include "ortools/sat/model.h"
58
59namespace operations_research {
60namespace sat {
61
62namespace {
63
64IntegerVariable CreateVariableWithTightDomain(
65 absl::Span<const AffineExpression> exprs, Model* model) {
66 IntegerValue min = kMaxIntegerValue;
67 IntegerValue max = kMinIntegerValue;
68 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
69 for (const AffineExpression& e : exprs) {
70 min = std::min(min, integer_trail->LevelZeroLowerBound(e));
71 max = std::max(max, integer_trail->LevelZeroUpperBound(e));
72 }
73 return integer_trail->AddIntegerVariable(min, max);
74}
75
76IntegerVariable CreateVariableAtOrAboveMinOf(
77 absl::Span<const AffineExpression> exprs, Model* model) {
78 const IntegerVariable var = CreateVariableWithTightDomain(exprs, model);
79 auto* constraint = new MinPropagator({exprs.begin(), exprs.end()}, var,
80 model->GetOrCreate<IntegerTrail>());
81 constraint->RegisterWith(model->GetOrCreate<GenericLiteralWatcher>());
82 model->TakeOwnership(constraint);
83
84 return var;
85}
86
87IntegerVariable CreateVariableAtOrBelowMaxOf(
88 absl::Span<const AffineExpression> exprs, Model* model) {
89 std::vector<AffineExpression> negated_exprs(exprs.begin(), exprs.end());
90 for (AffineExpression& affine : negated_exprs) affine = affine.Negated();
91
92 const IntegerVariable var =
93 CreateVariableWithTightDomain(negated_exprs, model);
94 auto* constraint = new MinPropagator(std::move(negated_exprs), var,
95 model->GetOrCreate<IntegerTrail>());
96 constraint->RegisterWith(model->GetOrCreate<GenericLiteralWatcher>());
97 model->TakeOwnership(constraint);
98
99 return NegationOf(var);
100}
101
102// Add a cumulative relaxation. That is, on one dimension, it does not enforce
103// the rectangle aspect, allowing vertical slices to move freely.
104void AddDiffnCumulativeRelationOnX(
105 absl::Span<const Literal> enforcement_literals,
107 Model* model) {
108 // Note that we only need one side!
109 // We want something <= max_end - min_start
110 //
111 // TODO(user): Use conditional affine min/max !!
112 bool all_optional = true;
113 for (int i = 0; i < y->NumTasks(); ++i) {
114 if (!y->IsOptional(i)) {
115 all_optional = false;
116 break;
117 }
118 }
119
120 AffineExpression capacity;
121 if (all_optional) {
122 IntegerValue min_start = kMaxIntegerValue;
123 IntegerValue max_end = kMinIntegerValue;
124 for (int i = 0; i < y->NumTasks(); ++i) {
125 min_start = std::min(min_start, y->LevelZeroStartMin(i));
126 max_end = std::max(max_end, y->LevelZeroEndMax(i));
127 }
128 if (max_end < min_start) {
129 return;
130 }
131 capacity = AffineExpression(CapSubI(max_end, min_start).value());
132 } else {
133 // This might not work if all task are optional, since the min could be
134 // greater than the max.
135 const IntegerVariable min_start_var =
136 CreateVariableAtOrAboveMinOf(y->Starts(), model);
137 const IntegerVariable max_end_var =
138 CreateVariableAtOrBelowMaxOf(y->Ends(), model);
139
140 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
141 if (integer_trail->UpperBound(max_end_var) <
142 integer_trail->LowerBound(min_start_var)) {
143 // Trivial infeasible case, will be handled by the linear constraint
144 // from the interval.
145 return;
146 }
147 // (max_end - min_start) >= capacity.
148 capacity = model->Add(NewIntegerVariable(
149 0, CapSub(integer_trail->UpperBound(max_end_var).value(),
150 integer_trail->LowerBound(min_start_var).value())));
151 const std::vector<int64_t> coeffs = {-capacity.coeff.value(), -1, 1};
152 // TODO(user): is the conditional really needed?
154 enforcement_literals, {capacity.var, min_start_var, max_end_var},
155 coeffs, capacity.constant.value()));
156 }
157
158 SchedulingDemandHelper* demands =
159 model->GetOrCreate<IntervalsRepository>()->GetOrCreateDemandHelper(
160 x, y->Sizes());
161
162 // Propagator responsible for applying Timetabling filtering rule. It
163 // increases the minimum of the start variables, decrease the maximum of the
164 // end variables, and increase the minimum of the capacity variable.
165 const SatParameters& params = *model->GetOrCreate<SatParameters>();
166 if (params.use_timetabling_in_no_overlap_2d()) {
167 TimeTablingPerTask* time_tabling =
168 new TimeTablingPerTask(capacity, x, demands, model);
169 time_tabling->RegisterWith(model->GetOrCreate<GenericLiteralWatcher>());
170 model->TakeOwnership(time_tabling);
171 }
172
173 // Propagator responsible for applying the Overload Checking filtering rule.
174 // It increases the minimum of the capacity variable.
175 if (params.use_energetic_reasoning_in_no_overlap_2d()) {
176 AddCumulativeOverloadChecker(capacity, x, demands, model);
177 }
178}
179
180bool AddAtMostOne(absl::Span<const Literal> enforcement_literals,
181 absl::Span<const Literal> literals, Model* model) {
182 if (enforcement_literals.empty()) {
183 return model->GetOrCreate<BinaryImplicationGraph>()->AddAtMostOne(literals);
184 }
185 std::vector<Literal> enforcement(enforcement_literals.begin(),
186 enforcement_literals.end());
187 std::vector<LiteralWithCoeff> cst;
188 cst.reserve(literals.size());
189 for (const Literal l : literals) {
190 cst.emplace_back(l, Coefficient(1));
191 }
192 return model->GetOrCreate<SatSolver>()->AddLinearConstraint(
193 /*use_lower_bound=*/false, Coefficient(0),
194 /*use_upper_bound=*/true, Coefficient(1), &enforcement, &cst);
195}
196
197} // namespace
198
200 const std::vector<Literal>& enforcement_literals,
201 const std::vector<IntervalVariable>& x,
202 const std::vector<IntervalVariable>& y, Model* model) {
204 NoOverlap2DConstraintHelper* helper_2d =
205 repository->GetOrCreate2DHelper(enforcement_literals, x, y);
206
207 GenericLiteralWatcher* const watcher =
209
210 CreateAndRegisterMandatoryOverlapPropagator(helper_2d, model, watcher, 3);
211 if (model->GetOrCreate<SatParameters>()
213 Precedences2DPropagator* propagator =
214 new Precedences2DPropagator(helper_2d, model);
215 watcher->SetPropagatorPriority(propagator->RegisterWith(watcher), 4);
216 model->TakeOwnership(propagator);
217 }
218
221 constraint->Register(/*fast_priority=*/3, /*slow_priority=*/4);
222 model->TakeOwnership(constraint);
223
224 RectanglePairwisePropagator* pairwise_propagator =
225 new RectanglePairwisePropagator(helper_2d, model);
226 watcher->SetPropagatorPriority(pairwise_propagator->RegisterWith(watcher), 4);
227 model->TakeOwnership(pairwise_propagator);
228
229 const SatParameters& params = *model->GetOrCreate<SatParameters>();
230 const bool add_cumulative_relaxation =
233
234 if (add_cumulative_relaxation) {
236 repository->GetOrCreateHelper(enforcement_literals, x);
238 repository->GetOrCreateHelper(enforcement_literals, y);
239
240 // We must first check if the cumulative relaxation is possible.
241 bool some_boxes_are_only_optional_on_x = false;
242 bool some_boxes_are_only_optional_on_y = false;
243 for (int i = 0; i < x.size(); ++i) {
244 if (x_helper->IsOptional(i) && y_helper->IsOptional(i) &&
245 x_helper->PresenceLiteral(i) != y_helper->PresenceLiteral(i)) {
246 // Abort as the task would be conditioned by two literals.
247 return;
248 }
249 if (x_helper->IsOptional(i) && !y_helper->IsOptional(i)) {
250 // We cannot use x_size as the demand of the cumulative based on
251 // the y_intervals.
252 some_boxes_are_only_optional_on_x = true;
253 }
254 if (y_helper->IsOptional(i) && !x_helper->IsOptional(i)) {
255 // We cannot use y_size as the demand of the cumulative based on
256 // the y_intervals.
257 some_boxes_are_only_optional_on_y = true;
258 }
259 }
260 if (!some_boxes_are_only_optional_on_y) {
261 AddDiffnCumulativeRelationOnX(enforcement_literals, x_helper, y_helper,
262 model);
263 }
264 if (!some_boxes_are_only_optional_on_x) {
265 AddDiffnCumulativeRelationOnX(enforcement_literals, y_helper, x_helper,
266 model);
267 }
268 }
269
272 new NonOverlappingRectanglesEnergyPropagator(helper_2d, model);
273 GenericLiteralWatcher* const watcher =
275 watcher->SetPropagatorPriority(energy_constraint->RegisterWith(watcher), 5);
276 model->TakeOwnership(energy_constraint);
277 }
278
280 CreateAndRegisterTryEdgePropagator(helper_2d, model, watcher, 5);
281 }
282
283 // Create all 2D "precedence" Booleans.
284 //
285 // The code will be sub-optimal for optional box with non-fixed sizes as we
286 // miss some constraint in that case.
287 // TODO(user): For now we do not deal with optional boxes with
288 // variable sizes.
289 //
290 // TODO(user): Like we do for 1D, one way to scale this is to only
291 // create such Boolean dynamically as we need to take a decision, and
292 // the previously created one are all assigned. It is a bit trickier
293 // though because of the extra constraints between these Booleans. Maybe
294 // one easy step is to create all 4 Booleans for a given pair of boxes
295 // at once.
296 //
297 // TODO(user): Tricky, it would be better to use the helper_2d instead
298 // of the general repository, however, as we add constraints, this
299 // propagates which might swap/change the underlying x_helper and
300 // y_helper...
301 const int num_boxes = x.size();
302 if (num_boxes < params.no_overlap_2d_boolean_relations_limit()) {
303 auto* sat_solver = model->GetOrCreate<SatSolver>();
304 auto* integer_trail = model->GetOrCreate<IntegerTrail>();
305 DCHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
306
307 for (int i = 0; i < num_boxes; ++i) {
308 if (repository->IsAbsent(x[i])) continue;
309 if (repository->IsAbsent(y[i])) continue;
310 if ((repository->IsOptional(x[i]) || repository->IsOptional(y[i])) &&
311 (!integer_trail->IsFixed(repository->Size(x[i])) ||
312 !integer_trail->IsFixed(repository->Size(y[i])))) {
313 continue;
314 }
315 for (int j = i + 1; j < num_boxes; ++j) {
316 if (repository->IsAbsent(x[j])) continue;
317 if (repository->IsAbsent(y[j])) continue;
318 if ((repository->IsOptional(x[j]) || repository->IsOptional(y[j])) &&
319 (!integer_trail->IsFixed(repository->Size(x[j])) ||
320 !integer_trail->IsFixed(repository->Size(y[j])))) {
321 continue;
322 }
323
324 // At most one of these two x options is true if the sizes are fixed or
325 // if the boxes are not optional.
326 const Literal x_ij = repository->GetOrCreatePrecedenceLiteral(
327 repository->End(x[i]), repository->Start(x[j]));
328 const Literal x_ji = repository->GetOrCreatePrecedenceLiteral(
329 repository->End(x[j]), repository->Start(x[i]));
330 if ((integer_trail->LowerBound(repository->Size(x[i])) > 0 ||
331 integer_trail->LowerBound(repository->Size(x[j])) > 0) &&
332 !AddAtMostOne(enforcement_literals, {x_ij, x_ji}, model)) {
333 sat_solver->NotifyThatModelIsUnsat();
334 return;
335 }
336
337 // At most one of these two y options is true if the sizes are fixed or
338 // if the boxes are not optional.
339 const Literal y_ij = repository->GetOrCreatePrecedenceLiteral(
340 repository->End(y[i]), repository->Start(y[j]));
341 const Literal y_ji = repository->GetOrCreatePrecedenceLiteral(
342 repository->End(y[j]), repository->Start(y[i]));
343 if ((integer_trail->LowerBound(repository->Size(y[i])) > 0 ||
344 integer_trail->LowerBound(repository->Size(y[j])) > 0) &&
345 !AddAtMostOne(enforcement_literals, {y_ij, y_ji}, model)) {
346 sat_solver->NotifyThatModelIsUnsat();
347 return;
348 }
349
350 // At least one of the 4 options is true if all boxes are present.
351 std::vector<Literal> clause = {x_ij, x_ji, y_ij, y_ji};
352 if (repository->IsOptional(x[i])) {
353 clause.push_back(repository->PresenceLiteral(x[i]).Negated());
354 }
355 if (repository->IsOptional(y[i])) {
356 clause.push_back(repository->PresenceLiteral(y[i]).Negated());
357 }
358 if (repository->IsOptional(x[j])) {
359 clause.push_back(repository->PresenceLiteral(x[j]).Negated());
360 }
361 if (repository->IsOptional(y[j])) {
362 clause.push_back(repository->PresenceLiteral(y[j]).Negated());
363 }
364 model->Add(EnforcedClause(enforcement_literals, clause));
365 if (sat_solver->ModelIsUnsat()) return;
366 }
367 }
368 }
369}
370
371#define RETURN_IF_FALSE(f) \
372 if (!(f)) return false;
373
376 if (!VLOG_IS_ON(1)) return;
377 std::vector<std::pair<std::string, int64_t>> stats;
378 stats.push_back(
379 {"NonOverlappingRectanglesEnergyPropagator/called", num_calls_});
380 stats.push_back(
381 {"NonOverlappingRectanglesEnergyPropagator/conflicts", num_conflicts_});
382 stats.push_back(
383 {"NonOverlappingRectanglesEnergyPropagator/conflicts_two_boxes",
384 num_conflicts_two_boxes_});
385 stats.push_back({"NonOverlappingRectanglesEnergyPropagator/refined",
386 num_refined_conflicts_});
387 stats.push_back(
388 {"NonOverlappingRectanglesEnergyPropagator/conflicts_with_slack",
389 num_conflicts_with_slack_});
390
391 shared_stats_->AddStats(stats);
392}
393
395 // TODO(user): double-check/revisit the algo for box of variable sizes.
396 if (!helper_.IsEnforced()) return true;
397 if (!helper_.SynchronizeAndSetDirection()) return false;
398
399 Rectangle bounding_box = {.x_min = std::numeric_limits<IntegerValue>::max(),
400 .x_max = std::numeric_limits<IntegerValue>::min(),
401 .y_min = std::numeric_limits<IntegerValue>::max(),
402 .y_max = std::numeric_limits<IntegerValue>::min()};
403 std::vector<RectangleInRange> active_box_ranges;
404 const int num_boxes = helper_.NumBoxes();
405 active_box_ranges.reserve(num_boxes);
406 for (int box = 0; box < num_boxes; ++box) {
407 if (!helper_.IsPresent(box)) continue;
408 RectangleInRange rec = helper_.GetItemRangeForSizeMin(box);
409 if (rec.x_size == 0 || rec.y_size == 0) continue;
410 bounding_box.GrowToInclude(rec.bounding_area);
411 active_box_ranges.push_back(std::move(rec));
412 }
413
414 if (active_box_ranges.size() < 2) {
415 return true;
416 }
417
418 // Our algo is quadratic, so we don't want to run it on really large problems.
419 if (active_box_ranges.size() > 1000) {
420 return true;
421 }
422
424 CapProdI(CapProdI(bounding_box.SizeX(), bounding_box.SizeY()),
425 active_box_ranges.size()))) {
426 // Avoid integer overflows if the area of the boxes get comparable with
427 // INT64_MAX.
428 return true;
429 }
430
431 num_calls_++;
432
433 std::optional<Conflict> best_conflict =
434 FindConflict(std::move(active_box_ranges));
435 if (!best_conflict.has_value()) {
436 return true;
437 }
438 num_conflicts_++;
439
440 // We found a conflict, so we can afford to run the propagator again to
441 // search for a best explanation. This is specially the case since we only
442 // want to re-run it over the items that participate in the conflict, so it is
443 // a much smaller problem.
444 IntegerValue best_explanation_size =
445 best_conflict->opp_result.GetItemsParticipatingOnConflict().size();
446 bool refined = false;
447 while (true) {
448 std::vector<RectangleInRange> items_participating_in_conflict;
449 items_participating_in_conflict.reserve(
450 best_conflict->items_for_opp.size());
451 for (const auto& item :
452 best_conflict->opp_result.GetItemsParticipatingOnConflict()) {
453 items_participating_in_conflict.push_back(
454 best_conflict->items_for_opp[item.index]);
455 }
456 std::optional<Conflict> conflict =
457 FindConflict(items_participating_in_conflict);
458 if (!conflict.has_value()) break;
459 // We prefer an explanation with the least number of boxes.
460 if (conflict->opp_result.GetItemsParticipatingOnConflict().size() >=
461 best_explanation_size) {
462 // The new explanation isn't better than the old one. Stop trying.
463 break;
464 }
465 best_explanation_size =
466 conflict->opp_result.GetItemsParticipatingOnConflict().size();
467 best_conflict = std::move(conflict);
468 refined = true;
469 }
470
471 num_refined_conflicts_ += refined;
472 const std::vector<RectangleInRange> generalized_explanation =
473 GeneralizeExplanation(*best_conflict);
474 if (best_explanation_size == 2) {
475 num_conflicts_two_boxes_++;
476 }
477 helper_.ReportConflictFromInfeasibleBoxRanges(generalized_explanation);
478 return false;
479}
480
481std::optional<NonOverlappingRectanglesEnergyPropagator::Conflict>
482NonOverlappingRectanglesEnergyPropagator::FindConflict(
483 std::vector<RectangleInRange> active_box_ranges) {
484 const auto rectangles_with_too_much_energy =
485 FindRectanglesWithEnergyConflictMC(active_box_ranges, *random_, 1.0, 0.8);
486
487 if (rectangles_with_too_much_energy.conflicts.empty() &&
488 rectangles_with_too_much_energy.candidates.empty()) {
489 return std::nullopt;
490 }
491
492 Conflict best_conflict;
493
494 // Sample 10 rectangles (at least five among the ones for which we already
495 // detected an energy overflow), extract an orthogonal packing subproblem for
496 // each and look for conflict. Sampling avoids making this heuristic too
497 // costly.
498 constexpr int kSampleSize = 10;
499 absl::InlinedVector<Rectangle, kSampleSize> sampled_rectangles;
500 std::sample(rectangles_with_too_much_energy.conflicts.begin(),
501 rectangles_with_too_much_energy.conflicts.end(),
502 std::back_inserter(sampled_rectangles), 5, *random_);
503 std::sample(rectangles_with_too_much_energy.candidates.begin(),
504 rectangles_with_too_much_energy.candidates.end(),
505 std::back_inserter(sampled_rectangles),
506 kSampleSize - sampled_rectangles.size(), *random_);
507 std::sort(sampled_rectangles.begin(), sampled_rectangles.end(),
508 [](const Rectangle& a, const Rectangle& b) {
509 const bool larger = std::make_pair(a.SizeX(), a.SizeY()) >
510 std::make_pair(b.SizeX(), b.SizeY());
511 // Double-check the invariant from
512 // FindRectanglesWithEnergyConflictMC() that given two returned
513 // rectangles there is one that is fully inside the other.
514 if (larger) {
515 // Rectangle b is fully contained inside a
516 DCHECK(a.x_min <= b.x_min && a.x_max >= b.x_max &&
517 a.y_min <= b.y_min && a.y_max >= b.y_max);
518 } else {
519 // Rectangle a is fully contained inside b
520 DCHECK(a.x_min >= b.x_min && a.x_max <= b.x_max &&
521 a.y_min >= b.y_min && a.y_max <= b.y_max);
522 }
523 return larger;
524 });
525 std::vector<IntegerValue> sizes_x, sizes_y;
526 sizes_x.reserve(active_box_ranges.size());
527 sizes_y.reserve(active_box_ranges.size());
528 std::vector<RectangleInRange> filtered_items;
529 filtered_items.reserve(active_box_ranges.size());
530 for (const Rectangle& r : sampled_rectangles) {
531 sizes_x.clear();
532 sizes_y.clear();
533 filtered_items.clear();
534 for (int i = 0; i < active_box_ranges.size(); ++i) {
535 const RectangleInRange& box = active_box_ranges[i];
536 const Rectangle intersection = box.GetMinimumIntersection(r);
537 if (intersection.SizeX() > 0 && intersection.SizeY() > 0) {
538 sizes_x.push_back(intersection.SizeX());
539 sizes_y.push_back(intersection.SizeY());
540 filtered_items.push_back(box);
541 }
542 }
543 // This check the feasibility of a related orthogonal packing problem where
544 // our rectangle is the bounding box, and we need to fit inside it a set of
545 // items corresponding to the minimum intersection of the original items
546 // with this bounding box.
547 const auto opp_result = orthogonal_packing_checker_.TestFeasibility(
548 sizes_x, sizes_y, {r.SizeX(), r.SizeY()},
550 .use_pairwise = true,
551 .use_dff_f0 = true,
552 .use_dff_f2 = true,
553 .brute_force_threshold = 7,
554 .dff2_max_number_of_parameters_to_check = 100});
555 if (opp_result.GetResult() == OrthogonalPackingResult::Status::INFEASIBLE &&
556 (best_conflict.opp_result.GetResult() !=
558 best_conflict.opp_result.GetItemsParticipatingOnConflict().size() >
559 opp_result.GetItemsParticipatingOnConflict().size())) {
560 best_conflict.items_for_opp = filtered_items;
561 best_conflict.opp_result = opp_result;
562 best_conflict.rectangle_with_too_much_energy = r;
563 }
564 // Use the fact that our rectangles are ordered in shrinking order to remove
565 // all items that will never contribute again.
566 filtered_items.swap(active_box_ranges);
567 }
568 if (best_conflict.opp_result.GetResult() ==
570 return best_conflict;
571 } else {
572 return std::nullopt;
573 }
574}
575
576std::vector<RectangleInRange>
577NonOverlappingRectanglesEnergyPropagator::GeneralizeExplanation(
578 const Conflict& conflict) {
579 const Rectangle& rectangle = conflict.rectangle_with_too_much_energy;
580 OrthogonalPackingResult relaxed_result = conflict.opp_result;
581
582 // Use the potential slack to have a stronger reason.
583 int used_slack_count = 0;
584 const auto& items = relaxed_result.GetItemsParticipatingOnConflict();
585 for (int i = 0; i < items.size(); ++i) {
586 if (!relaxed_result.HasSlack()) {
587 break;
588 }
589 const RectangleInRange& range = conflict.items_for_opp[items[i].index];
590 const RectangleInRange item_in_zero_level_range = {
591 .bounding_area =
592 {.x_min = helper_.x_helper().LevelZeroStartMin(range.box_index),
593 .x_max = helper_.x_helper().LevelZeroStartMax(range.box_index) +
594 range.x_size,
595 .y_min = helper_.y_helper().LevelZeroStartMin(range.box_index),
596 .y_max = helper_.y_helper().LevelZeroStartMax(range.box_index) +
597 range.y_size},
598 .x_size = range.x_size,
599 .y_size = range.y_size};
600 // There is no point trying to intersect less the item with the rectangle
601 // than it would on zero-level. So do not waste the slack with it.
602 const Rectangle max_overlap =
603 item_in_zero_level_range.GetMinimumIntersection(rectangle);
604 used_slack_count += relaxed_result.TryUseSlackToReduceItemSize(
605 i, OrthogonalPackingResult::Coord::kCoordX, max_overlap.SizeX());
606 used_slack_count += relaxed_result.TryUseSlackToReduceItemSize(
607 i, OrthogonalPackingResult::Coord::kCoordY, max_overlap.SizeY());
608 }
609 num_conflicts_with_slack_ += (used_slack_count > 0);
610 VLOG_EVERY_N_SEC(2, 3)
611 << "Found a conflict on the OPP sub-problem of rectangle: " << rectangle
612 << " using "
613 << conflict.opp_result.GetItemsParticipatingOnConflict().size() << "/"
614 << conflict.items_for_opp.size() << " items.";
615
616 std::vector<RectangleInRange> ranges_for_explanation;
617 ranges_for_explanation.reserve(conflict.items_for_opp.size());
618 std::vector<OrthogonalPackingResult::Item> sorted_items =
619 relaxed_result.GetItemsParticipatingOnConflict();
620 // TODO(user) understand why sorting high-impact items first improves the
621 // benchmarks
622 std::sort(sorted_items.begin(), sorted_items.end(),
623 [](const OrthogonalPackingResult::Item& a,
624 const OrthogonalPackingResult::Item& b) {
625 return a.size_x * a.size_y > b.size_x * b.size_y;
626 });
627 for (const auto& item : sorted_items) {
628 const RectangleInRange& range = conflict.items_for_opp[item.index];
629 ranges_for_explanation.push_back(
630 RectangleInRange::BiggestWithMinIntersection(rectangle, range,
631 item.size_x, item.size_y));
632 }
633 return ranges_for_explanation;
634}
635
637 GenericLiteralWatcher* watcher) {
638 const int id = watcher->Register(this);
639 helper_.WatchAllBoxes(id);
640 return id;
641}
642
643namespace {
644
645// We want for different propagation to reuse as much as possible the same
646// line. The idea behind this is to compute the 'canonical' line to use
647// when explaining that boxes overlap on the 'y_dim' dimension. We compute
648// the multiple of the biggest power of two that is common to all boxes.
649IntegerValue FindCanonicalValue(IntegerValue lb, IntegerValue ub) {
650 if (lb == ub) return lb;
651 if (lb <= 0 && ub > 0) return IntegerValue(0);
652 if (lb < 0 && ub <= 0) {
653 return -FindCanonicalValue(-ub, -lb);
654 }
655
656 int64_t mask = 0;
657 IntegerValue candidate = ub;
658 for (int o = 0; o < 62; ++o) {
659 mask = 2 * mask + 1;
660 const IntegerValue masked_ub(ub.value() & ~mask);
661 if (masked_ub >= lb) {
662 candidate = masked_ub;
663 } else {
664 break;
665 }
666 }
667 return candidate;
668}
669
670void SplitDisjointBoxes(const SchedulingConstraintHelper& x,
671 absl::Span<const int> boxes,
672 std::vector<absl::Span<const int>>* result) {
673 result->clear();
674 DCHECK(std::is_sorted(boxes.begin(), boxes.end(), [&x](int a, int b) {
675 return x.ShiftedStartMin(a) < x.ShiftedStartMin(b);
676 }));
677 int current_start = 0;
678 std::size_t current_length = 1;
679 IntegerValue current_max_end = x.EndMax(boxes[0]);
680
681 for (int b = 1; b < boxes.size(); ++b) {
682 const int box = boxes[b];
683 if (x.ShiftedStartMin(box) < current_max_end) {
684 // Merge.
685 current_length++;
686 current_max_end = std::max(current_max_end, x.EndMax(box));
687 } else {
688 if (current_length > 1) { // Ignore lists of size 1.
689 result->emplace_back(&boxes[current_start], current_length);
690 }
691 current_start = b;
692 current_length = 1;
693 current_max_end = x.EndMax(box);
694 }
695 }
696
697 // Push last span.
698 if (current_length > 1) {
699 result->emplace_back(&boxes[current_start], current_length);
700 }
701}
702
703} // namespace
704
705// Note that x_ and y_ must be initialized with enough intervals when passed
706// to the disjunctive propagators.
707NonOverlappingRectanglesDisjunctivePropagator::
708 NonOverlappingRectanglesDisjunctivePropagator(
709 NoOverlap2DConstraintHelper* helper, Model* model)
710 : helper_(helper),
711 x_(helper->NumBoxes(), model),
712 watcher_(model->GetOrCreate<GenericLiteralWatcher>()),
713 time_limit_(model->GetOrCreate<TimeLimit>()),
714 overload_checker_(&x_),
715 forward_detectable_precedences_(true, &x_),
716 backward_detectable_precedences_(false, &x_),
717 forward_not_last_(true, &x_),
718 backward_not_last_(false, &x_),
719 forward_edge_finding_(true, &x_),
720 backward_edge_finding_(false, &x_),
721 disjunctive_with_two_items_(&x_) {}
722
725
727 int fast_priority, int slow_priority) {
728 fast_id_ = watcher_->Register(this);
729 watcher_->SetPropagatorPriority(fast_id_, fast_priority);
730 helper_->WatchAllBoxes(fast_id_);
731
732 // This propagator is the one making sure our propagation is complete, so
733 // we do need to make sure it is called again if it modified some bounds.
734 watcher_->NotifyThatPropagatorMayNotReachFixedPointInOnePass(fast_id_);
735
736 const int slow_id = watcher_->Register(this);
737 watcher_->SetPropagatorPriority(slow_id, slow_priority);
738 helper_->WatchAllBoxes(slow_id);
739}
740
741bool NonOverlappingRectanglesDisjunctivePropagator::
742 FindBoxesThatMustOverlapAHorizontalLineAndPropagate(
743 bool fast_propagation, absl::Span<const int> requested_boxes) {
744 // When they are many fixed box that we know do not overlap, we compute
745 // the bounding box of the others, and we can exclude all boxes outside this
746 // region. This can help, especially for some LNS neighborhood.
747 int num_fixed = 0;
748 int num_others = 0;
749 Rectangle other_bounding_box;
750
751 // push_back() can be slow as it might not be inlined, so we manage directly
752 // our "boxes" in boxes_data[0 .. num_boxes], with a memory that is always big
753 // enough.
754 indexed_boxes_.resize(helper_->NumBoxes());
755 int num_boxes = 0;
756 IndexedInterval* boxes_data = indexed_boxes_.data();
757
758 SchedulingConstraintHelper* x = &helper_->x_helper();
759 SchedulingConstraintHelper* y = &helper_->y_helper();
760
761 // Optimization: we only initialize the set if we don't have all task here.
762 absl::flat_hash_set<int> requested_boxes_set;
763 const bool not_all_boxes = requested_boxes.size() != helper_->NumBoxes();
764 if (not_all_boxes) {
765 requested_boxes_set = {requested_boxes.begin(), requested_boxes.end()};
766 }
767
768 // Compute relevant boxes, the one with a mandatory part on y. Because we will
769 // need to sort it this way, we consider them by increasing start max.
770 const auto temp = y->TaskByIncreasingNegatedStartMax();
771 auto fixed_boxes = already_checked_fixed_boxes_.view();
772 for (int i = temp.size(); --i >= 0;) {
773 const int box = temp[i].task_index;
774 if (not_all_boxes && !requested_boxes_set.contains(box)) continue;
775
776 // By definition, fixed boxes are always present.
777 // Doing this check optimize a bit the case where we have many fixed boxes.
778 if (!fixed_boxes[box]) {
779 // Ignore absent boxes.
780 if (x->IsAbsent(box) || y->IsAbsent(box)) continue;
781
782 // Ignore boxes where the relevant presence literal is only on the y
783 // dimension, or if both intervals are optionals with different literals.
784 if (x->IsPresent(box) && !y->IsPresent(box)) continue;
785 if (!x->IsPresent(box) && !y->IsPresent(box) &&
786 x->PresenceLiteral(box) != y->PresenceLiteral(box)) {
787 continue;
788 }
789 }
790
791 // Only consider box with a mandatory part on y.
792 const IntegerValue start_max = -temp[i].time;
793 const IntegerValue end_min = y->EndMin(box);
794 if (start_max < end_min) {
795 boxes_data[num_boxes++] = {box, start_max, end_min};
796
797 // Optim: If many rectangle are fixed and known not to overlap, we might
798 // filter them out.
799 if (fixed_boxes[box]) {
800 ++num_fixed;
801 } else {
802 if (helper_->IsFixed(box)) {
803 // We will "check it" below, so it will be checked next time.
804 fixed_boxes.Set(box);
805 }
806
807 const Rectangle r = {x->StartMin(box), x->EndMax(box), start_max,
808 end_min};
809 if (num_others == 0) {
810 other_bounding_box = r;
811 } else {
812 other_bounding_box.GrowToInclude(r);
813 }
814 ++num_others;
815 }
816 }
817 }
818
819 // We remove from boxes_data all the fixed and checked box outside the
820 // other_bounding_box.
821 //
822 // TODO(user): We could be smarter here, if we have just a few non-fixed
823 // boxes, likely their mandatory y-part do not span the whole horizon, so
824 // we could remove any fixed boxes outside these "stripes".
825 if (num_others == 0) return true;
826 if (num_fixed > 0) {
827 int new_size = 0;
828 for (int i = 0; i < num_boxes; ++i) {
829 const IndexedInterval& interval = boxes_data[i];
830 const int box = interval.index;
831 const Rectangle r = {x->StartMin(box), x->EndMax(box), interval.start,
832 interval.end};
833 if (other_bounding_box.IsDisjoint(r)) continue;
834 boxes_data[new_size++] = interval;
835 }
836 num_boxes = new_size;
837 }
838
839 // Less than 2 boxes, no propagation.
840 const auto boxes = absl::MakeSpan(boxes_data, num_boxes);
841 if (boxes.size() < 2) return true;
842
843 // Optim: Abort if all rectangle can be fixed to their mandatory y +
844 // minimum x position without any overlap.
845 //
846 // This is guaranteed to be O(N log N) whereas the algo below is O(N ^ 2).
847 //
848 // TODO(user): We might still propagate the x end in this setting, but the
849 // current code will just abort below in SplitDisjointBoxes() anyway.
850 {
851 rectangles_.clear();
852 rectangles_.reserve(boxes.size());
853 for (const auto [box, y_mandatory_start, y_mandatory_end] : boxes) {
854 // Note that we invert the x/y position here in order to be already
855 // sorted for FindOneIntersectionIfPresent()
856 rectangles_.push_back(
857 {/*x_min=*/y_mandatory_start, /*x_max=*/y_mandatory_end,
858 /*y_min=*/x->StartMin(box), /*y_max=*/x->EndMin(box)});
859 }
860 const auto opt_pair = FindOneIntersectionIfPresent(rectangles_);
861 {
862 const size_t n = rectangles_.size();
863 time_limit_->AdvanceDeterministicTime(
864 static_cast<double>(n) * static_cast<double>(absl::bit_width(n)) *
865 1e-8);
866 }
867 if (opt_pair == std::nullopt) {
868 return true;
869 } else {
870 // TODO(user): Test if we have a conflict here.
871 }
872 }
873
874 order_.assign(x->NumTasks(), 0);
875 {
876 int i = 0;
877 for (const auto [t, _lit, _time] : x->TaskByIncreasingShiftedStartMin()) {
878 order_[t] = i++;
879 }
880 }
881 ConstructOverlappingSets(boxes, &events_overlapping_boxes_, order_);
882
883 // Split lists of boxes into disjoint set of boxes (w.r.t. overlap).
884 boxes_to_propagate_.clear();
885 reduced_overlapping_boxes_.clear();
886 int work_done = boxes.size();
887 for (int i = 0; i < events_overlapping_boxes_.size(); ++i) {
888 work_done += events_overlapping_boxes_[i].size();
889 SplitDisjointBoxes(*x, events_overlapping_boxes_[i], &disjoint_boxes_);
890 for (const absl::Span<const int> sub_boxes : disjoint_boxes_) {
891 // Boxes are sorted in a stable manner in the Split method.
892 // Note that we do not use reduced_overlapping_boxes_ directly so that
893 // the order of iteration is deterministic.
894 const auto& insertion = reduced_overlapping_boxes_.insert(sub_boxes);
895 if (insertion.second) boxes_to_propagate_.push_back(sub_boxes);
896 }
897 }
898
899 // TODO(user): This is a poor dtime, but we want it not to be zero here.
900 time_limit_->AdvanceDeterministicTime(static_cast<double>(work_done) * 1e-8);
901
902 // And finally propagate.
903 //
904 // TODO(user): Sorting of boxes seems influential on the performance.
905 // Test.
906 for (const absl::Span<const int> boxes : boxes_to_propagate_) {
907 // The case of two boxes should be taken care of during "fast"
908 // propagation, so we can skip it here.
909 if (!fast_propagation && boxes.size() <= 2) continue;
910
911 x_.SetExtraExplanationForItemCallback(nullptr);
912 if (!x_.ResetFromSubset(*x, boxes)) return false;
913
914 // Collect the common overlapping coordinates of all boxes.
915 IntegerValue lb(std::numeric_limits<int64_t>::min());
916 IntegerValue ub(std::numeric_limits<int64_t>::max());
917 for (const int b : boxes) {
918 lb = std::max(lb, y->StartMax(b));
919 ub = std::min(ub, y->EndMin(b) - 1);
920 }
921 CHECK_LE(lb, ub);
922
923 // We want for different propagation to reuse as much as possible the same
924 // line. The idea behind this is to compute the 'canonical' line to use
925 // when explaining that boxes overlap on the 'y_dim' dimension. We compute
926 // the multiple of the biggest power of two that is common to all boxes.
927 //
928 // TODO(user): We should scan the integer trail to find the oldest
929 // non-empty common interval. Then we can pick the canonical value within
930 // it.
931 const IntegerValue line_to_use_for_reason = FindCanonicalValue(lb, ub);
932
933 // Make sure we always add the reason for the line when propagating x.
934 x_.SetExtraExplanationForItemCallback(
935 [&boxes, y, line_to_use_for_reason](
936 absl::Span<const int> items, std::vector<Literal>* literal_reason,
937 std::vector<IntegerLiteral>* integer_reason) {
938 y->ResetReason();
939 if (items.size() > 5) {
940 // Build an explanation for all the boxes intersecting the line.
941 for (const int t : items) {
942 y->AddStartMaxReason(boxes[t], line_to_use_for_reason);
943 y->AddEndMinReason(boxes[t], line_to_use_for_reason + 1);
944 }
945 } else {
946 // For small problems we can build a stronger explanation that only
947 // requires that each box must overlap on y with all the others.
948 for (int i = 0; i < items.size(); ++i) {
949 for (int j = i + 1; j < items.size(); ++j) {
950 const int t = items[i];
951 const int u = items[j];
952 y->AddReasonForBeingBeforeAssumingNoOverlap(boxes[t], boxes[u]);
953 y->AddReasonForBeingBeforeAssumingNoOverlap(boxes[u], boxes[t]);
954 }
955 }
956 }
957 y->AppendAndResetReason(integer_reason, literal_reason);
958 });
959
960 if (fast_propagation) {
961 if (x_.NumTasks() == 2) {
962 // In that case, we can use simpler algorithms.
963 // Note that this case happens frequently (~30% of all calls to this
964 // method according to our tests).
965 RETURN_IF_FALSE(disjunctive_with_two_items_.Propagate());
966 } else {
967 RETURN_IF_FALSE(overload_checker_.Propagate());
968 RETURN_IF_FALSE(forward_detectable_precedences_.Propagate());
969 RETURN_IF_FALSE(backward_detectable_precedences_.Propagate());
970 }
971 } else {
972 DCHECK_GT(x_.NumTasks(), 2);
973 RETURN_IF_FALSE(forward_not_last_.Propagate());
974 RETURN_IF_FALSE(backward_not_last_.Propagate());
975 RETURN_IF_FALSE(backward_edge_finding_.Propagate());
976 RETURN_IF_FALSE(forward_edge_finding_.Propagate());
977 }
978 }
979
980 return true;
981}
982
983// Note that we optimized this function for two main use cases:
984// - smallish problem where we don't have more than 100 boxes.
985// - large problem with many 1000s boxes, but with only a small subset that is
986// not fixed (mainly coming from LNS).
988 if (!helper_->IsEnforced()) return true;
989 if (!helper_->SynchronizeAndSetDirection(true, true, false)) return false;
990
991 // If we are "diving" we maintain the set of fixed boxes for which we know
992 // that they are not overlapping.
993 const bool backtrack_since_last_call = !rev_is_in_dive_;
994 watcher_->SetUntilNextBacktrack(&rev_is_in_dive_);
995 if (backtrack_since_last_call ||
996 last_helper_inprocessing_count_ != helper_->InProcessingCount()) {
997 last_helper_inprocessing_count_ = helper_->InProcessingCount();
998 const int num_tasks = helper_->NumBoxes();
999 already_checked_fixed_boxes_.ClearAndResize(num_tasks);
1000 }
1001
1002 // Note that the code assumes that this was registered twice in fast and slow
1003 // mode. So we will not redo some propagation in slow mode that was already
1004 // done by the fast mode.
1005 const bool fast_propagation = watcher_->GetCurrentId() == fast_id_;
1006 for (const auto subset : helper_->connected_components().AsVectorOfSpan()) {
1007 if (!FindBoxesThatMustOverlapAHorizontalLineAndPropagate(fast_propagation,
1008 subset)) {
1009 return false;
1010 }
1011 }
1012 // We can actually swap dimensions to propagate vertically.
1013 if (!helper_->SynchronizeAndSetDirection(true, true, true)) return false;
1014
1015 for (const auto subset : helper_->connected_components().AsVectorOfSpan()) {
1016 if (!FindBoxesThatMustOverlapAHorizontalLineAndPropagate(fast_propagation,
1017 subset)) {
1018 return false;
1019 }
1020 }
1021
1022 return true;
1023}
1024
1026 const int id = watcher->Register(this);
1027 helper_->WatchAllBoxes(id);
1029 return id;
1030}
1031
1033 if (!VLOG_IS_ON(1)) return;
1034 std::vector<std::pair<std::string, int64_t>> stats;
1035 stats.push_back({"RectanglePairwisePropagator/called", num_calls_});
1036 stats.push_back({"RectanglePairwisePropagator/pairwise_conflicts",
1037 num_pairwise_conflicts_});
1038 stats.push_back({"RectanglePairwisePropagator/pairwise_propagations",
1039 num_pairwise_propagations_});
1040
1041 shared_stats_->AddStats(stats);
1042}
1043
1045 if (!helper_->IsEnforced()) return true;
1046 if (!helper_->SynchronizeAndSetDirection()) return false;
1047
1048 num_calls_++;
1049 std::vector<PairwiseRestriction> restrictions;
1050
1051 for (int component_index = 0;
1052 component_index < helper_->connected_components().size();
1053 ++component_index) {
1054 horizontal_zero_area_boxes_.clear();
1055 vertical_zero_area_boxes_.clear();
1056 point_zero_area_boxes_.clear();
1057 fixed_non_zero_area_boxes_.clear();
1058 non_fixed_non_zero_area_boxes_.clear();
1059 for (int b : helper_->connected_components()[component_index]) {
1060 if (!helper_->IsPresent(b)) continue;
1061 const auto [x_size_max, y_size_max] = helper_->GetBoxSizesMax(b);
1062 ItemWithVariableSize box = helper_->GetItemWithVariableSize(b);
1063 if (x_size_max == 0) {
1064 if (y_size_max == 0) {
1065 point_zero_area_boxes_.push_back(std::move(box));
1066 } else {
1067 vertical_zero_area_boxes_.push_back(std::move(box));
1068 }
1069 } else if (y_size_max == 0) {
1070 horizontal_zero_area_boxes_.push_back(std::move(box));
1071 } else {
1072 // TODO(user): if the number of fixed boxes is large, we keep
1073 // reconstructing them each time this is called for no reason.
1074 if (box.IsFixed()) {
1075 fixed_non_zero_area_boxes_.push_back(std::move(box));
1076 } else {
1077 non_fixed_non_zero_area_boxes_.push_back(std::move(box));
1078 }
1079 }
1080 }
1081
1082 // We ignore pairs of two fixed boxes. The only thing to propagate between
1083 // two fixed boxes is a conflict and it should already have been taken care
1084 // of by the MandatoryOverlapPropagator propagator.
1085 RETURN_IF_FALSE(FindRestrictionsAndPropagateConflict(
1086 non_fixed_non_zero_area_boxes_, fixed_non_zero_area_boxes_,
1087 &restrictions));
1088
1089 // Check zero area boxes against non-zero area boxes.
1090 for (auto& non_zero_area_boxes :
1091 {fixed_non_zero_area_boxes_, non_fixed_non_zero_area_boxes_}) {
1092 RETURN_IF_FALSE(FindRestrictionsAndPropagateConflict(
1093 non_zero_area_boxes, horizontal_zero_area_boxes_, &restrictions));
1094 RETURN_IF_FALSE(FindRestrictionsAndPropagateConflict(
1095 non_zero_area_boxes, vertical_zero_area_boxes_, &restrictions));
1096 RETURN_IF_FALSE(FindRestrictionsAndPropagateConflict(
1097 non_zero_area_boxes, point_zero_area_boxes_, &restrictions));
1098 }
1099
1100 // Check vertical zero area boxes against horizontal zero area boxes.
1101 RETURN_IF_FALSE(FindRestrictionsAndPropagateConflict(
1102 vertical_zero_area_boxes_, horizontal_zero_area_boxes_, &restrictions));
1103 }
1104 for (const PairwiseRestriction& restriction : restrictions) {
1105 RETURN_IF_FALSE(PropagateTwoBoxes(restriction));
1106 }
1107 return true;
1108}
1109
1110bool RectanglePairwisePropagator::FindRestrictionsAndPropagateConflict(
1111 absl::Span<const ItemWithVariableSize> items1,
1112 absl::Span<const ItemWithVariableSize> items2,
1113 std::vector<PairwiseRestriction>* restrictions) {
1114 const int max_pairs =
1116 if (items1.size() * items2.size() > max_pairs) {
1117 return true;
1118 }
1119 AppendPairwiseRestrictions(items1, items2, restrictions);
1120 for (const PairwiseRestriction& restriction : *restrictions) {
1121 if (restriction.type ==
1123 RETURN_IF_FALSE(PropagateTwoBoxes(restriction));
1124 }
1125 }
1126 return true;
1127}
1128
1129bool RectanglePairwisePropagator::PropagateTwoBoxes(
1130 const PairwiseRestriction& restriction) {
1131 if (restriction.type ==
1133 num_pairwise_conflicts_++;
1134 } else {
1135 num_pairwise_propagations_++;
1136 }
1137 return helper_->PropagateRelativePosition(
1138 restriction.first_index, restriction.second_index, restriction.type);
1139}
1140
1141#undef RETURN_IF_FALSE
1142} // namespace sat
1143} // namespace operations_research
void SetPropagatorPriority(int id, int priority)
Definition integer.cc:2700
int Register(PropagatorInterface *propagator)
Definition integer.cc:2674
IntegerVariable AddIntegerVariable(IntegerValue lower_bound, IntegerValue upper_bound)
Definition integer.cc:856
SchedulingConstraintHelper * GetOrCreateHelper(std::vector< Literal > enforcement_literals, const std::vector< IntervalVariable > &variables, bool register_as_disjunctive_helper=false)
Definition intervals.cc:357
AffineExpression Start(IntervalVariable i) const
Definition intervals.h:94
AffineExpression Size(IntervalVariable i) const
Definition intervals.h:93
AffineExpression End(IntervalVariable i) const
Definition intervals.h:95
Literal GetOrCreatePrecedenceLiteral(AffineExpression x, AffineExpression y)
Definition intervals.cc:341
bool IsAbsent(IntervalVariable i) const
Definition intervals.h:81
Literal PresenceLiteral(IntervalVariable i) const
Definition intervals.h:74
bool IsOptional(IntervalVariable i) const
Definition intervals.h:71
NoOverlap2DConstraintHelper * GetOrCreate2DHelper(std::vector< Literal > enforcement_literals, const std::vector< IntervalVariable > &x_variables, const std::vector< IntervalVariable > &y_variables)
Definition intervals.cc:399
void RegisterWith(GenericLiteralWatcher *watcher)
T Add(std::function< T(Model *)> f)
Definition model.h:104
void Register(int fast_priority, int slow_priority)
Definition diffn.cc:726
int RegisterWith(GenericLiteralWatcher *watcher)
Definition diffn.cc:1025
::int32_t max_pairs_pairwise_reasoning_in_no_overlap_2d() const
void AddStartMaxReason(int t, IntegerValue upper_bound)
void AppendAndResetReason(std::vector< IntegerLiteral > *integer_reason, std::vector< Literal > *literal_reason)
void AddEndMinReason(int t, IntegerValue lower_bound)
std::tuple< int64_t, int64_t, const double > Coefficient
constexpr IntegerValue kMaxIntegerValue(std::numeric_limits< IntegerValue::ValueType >::max() - 1)
std::function< void(Model *)> EnforcedClause(absl::Span< const Literal > enforcement_literals, absl::Span< const Literal > clause)
void CreateAndRegisterTryEdgePropagator(NoOverlap2DConstraintHelper *helper, Model *model, GenericLiteralWatcher *watcher, int priority)
std::vector< IntegerVariable > NegationOf(absl::Span< const IntegerVariable > vars)
Definition integer.cc:52
std::optional< std::pair< int, int > > FindOneIntersectionIfPresent(absl::Span< const Rectangle > rectangles)
constexpr IntegerValue kMinIntegerValue(-kMaxIntegerValue.value())
void AddNonOverlappingRectangles(const std::vector< Literal > &enforcement_literals, const std::vector< IntervalVariable > &x, const std::vector< IntervalVariable > &y, Model *model)
Definition diffn.cc:199
void AppendPairwiseRestrictions(absl::Span< const ItemWithVariableSize > items, std::vector< PairwiseRestriction > *result)
void ConstructOverlappingSets(absl::Span< IndexedInterval > intervals, CompactVectorVector< int > *result, absl::Span< const int > order)
void CreateAndRegisterMandatoryOverlapPropagator(NoOverlap2DConstraintHelper *helper, Model *model, GenericLiteralWatcher *watcher, int priority)
std::function< void(Model *)> ConditionalWeightedSumGreaterOrEqual(absl::Span< const Literal > enforcement_literals, absl::Span< const IntegerVariable > vars, absl::Span< const int64_t > coefficients, int64_t upper_bound)
FindRectanglesResult FindRectanglesWithEnergyConflictMC(const std::vector< RectangleInRange > &intervals, absl::BitGenRef random, double temperature, double candidate_energy_usage_factor)
std::function< IntegerVariable(Model *)> NewIntegerVariable(int64_t lb, int64_t ub)
Definition integer.h:1771
IntegerValue CapSubI(IntegerValue a, IntegerValue b)
bool AtMinOrMaxInt64I(IntegerValue t)
void AddCumulativeOverloadChecker(AffineExpression capacity, SchedulingConstraintHelper *helper, SchedulingDemandHelper *demands, Model *model)
IntegerValue CapProdI(IntegerValue a, IntegerValue b)
OR-Tools root namespace.
int64_t CapSub(int64_t x, int64_t y)
#define RETURN_IF_FALSE(f)
Definition diffn.cc:371
void GrowToInclude(const Rectangle &other)
Definition diffn_util.h:49
bool IsDisjoint(const Rectangle &other) const
Definition diffn_util.cc:58