Skip to content

Commit 8733308

Browse files
committed
[CP-SAT] regroup all hint preservation code in a SolutionCrunch class; more work on no_overlap_2d propagator; add exception processing if an exceptions is raised in a python callback (solution, log, best_bound)
1 parent eee21a5 commit 8733308

31 files changed

+1207
-536
lines changed
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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/2d_mandatory_overlap_propagator.h"
15+
16+
#include <cstdint>
17+
#include <optional>
18+
#include <string>
19+
#include <utility>
20+
#include <vector>
21+
22+
#include "absl/types/span.h"
23+
#include "ortools/base/logging.h"
24+
#include "ortools/sat/diffn_util.h"
25+
#include "ortools/sat/integer.h"
26+
#include "ortools/sat/model.h"
27+
#include "ortools/sat/no_overlap_2d_helper.h"
28+
#include "ortools/sat/scheduling_helpers.h"
29+
30+
namespace operations_research {
31+
namespace sat {
32+
33+
int MandatoryOverlapPropagator::RegisterWith(GenericLiteralWatcher* watcher) {
34+
const int id = watcher->Register(this);
35+
helper_.WatchAllBoxes(id);
36+
return id;
37+
}
38+
39+
MandatoryOverlapPropagator::~MandatoryOverlapPropagator() {
40+
if (!VLOG_IS_ON(1)) return;
41+
std::vector<std::pair<std::string, int64_t>> stats;
42+
stats.push_back({"MandatoryOverlapPropagator/called_with_zero_area",
43+
num_calls_zero_area_});
44+
stats.push_back({"MandatoryOverlapPropagator/called_without_zero_area",
45+
num_calls_nonzero_area_});
46+
stats.push_back({"MandatoryOverlapPropagator/conflicts", num_conflicts_});
47+
48+
shared_stats_->AddStats(stats);
49+
}
50+
51+
bool MandatoryOverlapPropagator::Propagate() {
52+
if (!helper_.SynchronizeAndSetDirection(true, true, false)) return false;
53+
54+
mandatory_regions_.clear();
55+
mandatory_regions_index_.clear();
56+
bool has_zero_area_boxes = false;
57+
absl::Span<const TaskTime> tasks =
58+
helper_.x_helper().TaskByIncreasingNegatedStartMax();
59+
for (int i = tasks.size() - 1; i >= 0; --i) {
60+
const int b = tasks[i].task_index;
61+
if (!helper_.IsPresent(b)) continue;
62+
const ItemWithVariableSize item = helper_.GetItemWithVariableSize(b);
63+
if (item.x.start_max > item.x.end_min ||
64+
item.y.start_max > item.y.end_min) {
65+
continue;
66+
}
67+
mandatory_regions_.push_back({.x_min = item.x.start_max,
68+
.x_max = item.x.end_min,
69+
.y_min = item.y.start_max,
70+
.y_max = item.y.end_min});
71+
mandatory_regions_index_.push_back(b);
72+
73+
if (mandatory_regions_.back().SizeX() == 0 ||
74+
mandatory_regions_.back().SizeY() == 0) {
75+
has_zero_area_boxes = true;
76+
}
77+
}
78+
std::optional<std::pair<int, int>> conflict;
79+
if (has_zero_area_boxes) {
80+
num_calls_zero_area_++;
81+
conflict = FindOneIntersectionIfPresentWithZeroArea(mandatory_regions_);
82+
} else {
83+
num_calls_nonzero_area_++;
84+
conflict = FindOneIntersectionIfPresent(mandatory_regions_);
85+
}
86+
87+
if (conflict.has_value()) {
88+
num_conflicts_++;
89+
return helper_.ReportConflictFromTwoBoxes(
90+
mandatory_regions_index_[conflict->first],
91+
mandatory_regions_index_[conflict->second]);
92+
}
93+
return true;
94+
}
95+
96+
void CreateAndRegisterMandatoryOverlapPropagator(
97+
NoOverlap2DConstraintHelper* helper, Model* model,
98+
GenericLiteralWatcher* watcher, int priority) {
99+
MandatoryOverlapPropagator* propagator =
100+
new MandatoryOverlapPropagator(helper, model);
101+
watcher->SetPropagatorPriority(propagator->RegisterWith(watcher), priority);
102+
model->TakeOwnership(propagator);
103+
}
104+
105+
} // namespace sat
106+
} // namespace operations_research
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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+
#ifndef OR_TOOLS_SAT_2D_MANDATORY_OVERLAP_PROPAGATOR_H_
15+
#define OR_TOOLS_SAT_2D_MANDATORY_OVERLAP_PROPAGATOR_H_
16+
17+
#include <cstdint>
18+
#include <vector>
19+
20+
#include "ortools/sat/diffn_util.h"
21+
#include "ortools/sat/integer.h"
22+
#include "ortools/sat/model.h"
23+
#include "ortools/sat/no_overlap_2d_helper.h"
24+
#include "ortools/sat/synchronization.h"
25+
26+
namespace operations_research {
27+
namespace sat {
28+
29+
// Propagator that checks that no mandatory area of two boxes overlap in
30+
// O(N * log N) time.
31+
void CreateAndRegisterMandatoryOverlapPropagator(
32+
NoOverlap2DConstraintHelper* helper, Model* model,
33+
GenericLiteralWatcher* watcher, int priority);
34+
35+
// Exposed for testing.
36+
class MandatoryOverlapPropagator : public PropagatorInterface {
37+
public:
38+
MandatoryOverlapPropagator(NoOverlap2DConstraintHelper* helper, Model* model)
39+
: helper_(*helper),
40+
shared_stats_(model->GetOrCreate<SharedStatistics>()) {}
41+
42+
~MandatoryOverlapPropagator() override;
43+
44+
bool Propagate() final;
45+
int RegisterWith(GenericLiteralWatcher* watcher);
46+
47+
private:
48+
NoOverlap2DConstraintHelper& helper_;
49+
SharedStatistics* shared_stats_;
50+
std::vector<Rectangle> mandatory_regions_;
51+
std::vector<int> mandatory_regions_index_;
52+
53+
int64_t num_conflicts_ = 0;
54+
int64_t num_calls_zero_area_ = 0;
55+
int64_t num_calls_nonzero_area_ = 0;
56+
57+
MandatoryOverlapPropagator(const MandatoryOverlapPropagator&) = delete;
58+
MandatoryOverlapPropagator& operator=(const MandatoryOverlapPropagator&) =
59+
delete;
60+
};
61+
62+
} // namespace sat
63+
} // namespace operations_research
64+
65+
#endif // OR_TOOLS_SAT_2D_MANDATORY_OVERLAP_PROPAGATOR_H_

ortools/sat/2d_try_edge_propagator.cc

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,9 @@ std::vector<int> TryEdgeRectanglePropagator::GetMinimumProblemWithPropagation(
301301
}
302302

303303
// Now gather the data per box to make easier to use the set cover solver API.
304+
// TODO(user): skip the boxes that are fixed at level zero. They do not
305+
// contribute to the size of the explanation (so we shouldn't minimize their
306+
// number) and make the SetCover problem harder to solve.
304307
std::vector<std::vector<int>> conflicting_position_per_box(
305308
active_box_ranges_.size(), std::vector<int>());
306309
for (int i = 0; i < conflicts_per_x_and_y_.size(); ++i) {
@@ -403,28 +406,30 @@ bool TryEdgeRectanglePropagator::ExplainAndPropagate(
403406

404407
void CreateAndRegisterTryEdgePropagator(NoOverlap2DConstraintHelper* helper,
405408
Model* model,
406-
GenericLiteralWatcher* watcher) {
409+
GenericLiteralWatcher* watcher,
410+
int priority) {
407411
TryEdgeRectanglePropagator* try_edge_propagator =
408412
new TryEdgeRectanglePropagator(true, true, false, helper, model);
409-
watcher->SetPropagatorPriority(try_edge_propagator->RegisterWith(watcher), 5);
413+
watcher->SetPropagatorPriority(try_edge_propagator->RegisterWith(watcher),
414+
priority);
410415
model->TakeOwnership(try_edge_propagator);
411416

412417
TryEdgeRectanglePropagator* try_edge_propagator_mirrored =
413418
new TryEdgeRectanglePropagator(false, true, false, helper, model);
414419
watcher->SetPropagatorPriority(
415-
try_edge_propagator_mirrored->RegisterWith(watcher), 5);
420+
try_edge_propagator_mirrored->RegisterWith(watcher), priority);
416421
model->TakeOwnership(try_edge_propagator_mirrored);
417422

418423
TryEdgeRectanglePropagator* try_edge_propagator_swap =
419424
new TryEdgeRectanglePropagator(true, true, true, helper, model);
420425
watcher->SetPropagatorPriority(
421-
try_edge_propagator_swap->RegisterWith(watcher), 5);
426+
try_edge_propagator_swap->RegisterWith(watcher), priority);
422427
model->TakeOwnership(try_edge_propagator_swap);
423428

424429
TryEdgeRectanglePropagator* try_edge_propagator_swap_mirrored =
425430
new TryEdgeRectanglePropagator(false, true, true, helper, model);
426431
watcher->SetPropagatorPriority(
427-
try_edge_propagator_swap_mirrored->RegisterWith(watcher), 5);
432+
try_edge_propagator_swap_mirrored->RegisterWith(watcher), priority);
428433
model->TakeOwnership(try_edge_propagator_swap_mirrored);
429434
}
430435

ortools/sat/2d_try_edge_propagator.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ namespace sat {
3737
// it is different from the current x_min, it will propagate the new x_min.
3838
void CreateAndRegisterTryEdgePropagator(NoOverlap2DConstraintHelper* helper,
3939
Model* model,
40-
GenericLiteralWatcher* watcher);
40+
GenericLiteralWatcher* watcher,
41+
int priority);
4142

4243
// Exposed for testing.
4344
class TryEdgeRectanglePropagator : public PropagatorInterface {

ortools/sat/BUILD.bazel

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,22 @@ proto_library(
9494
srcs = ["cp_model.proto"],
9595
)
9696

97+
cc_library(
98+
name = "2d_mandatory_overlap_propagator",
99+
srcs = ["2d_mandatory_overlap_propagator.cc"],
100+
hdrs = ["2d_mandatory_overlap_propagator.h"],
101+
deps = [
102+
":diffn_util",
103+
":integer",
104+
":model",
105+
":no_overlap_2d_helper",
106+
":scheduling_helpers",
107+
":synchronization",
108+
"@com_google_absl//absl/log",
109+
"@com_google_absl//absl/types:span",
110+
],
111+
)
112+
97113
cc_proto_library(
98114
name = "cp_model_cc_proto",
99115
deps = [":cp_model_proto"],
@@ -765,6 +781,7 @@ cc_library(
765781
":presolve_util",
766782
":sat_parameters_cc_proto",
767783
":sat_solver",
784+
":solution_crush",
768785
":util",
769786
"//ortools/algorithms:sparse_permutation",
770787
"//ortools/base",
@@ -798,16 +815,34 @@ cc_test(
798815
":cp_model_utils",
799816
":model",
800817
":presolve_context",
818+
":solution_crush",
801819
"//ortools/base:gmock_main",
802820
"//ortools/base:parse_test_proto",
803-
"//ortools/base:types",
804821
"//ortools/util:affine_relation",
805822
"//ortools/util:sorted_interval_list",
806823
"@com_google_absl//absl/container:flat_hash_set",
807824
"@com_google_absl//absl/types:span",
808825
],
809826
)
810827

828+
cc_library(
829+
name = "solution_crush",
830+
srcs = [
831+
"solution_crush.cc",
832+
],
833+
hdrs = ["solution_crush.h"],
834+
deps = [
835+
":cp_model_cc_proto",
836+
":cp_model_utils",
837+
":sat_parameters_cc_proto",
838+
"//ortools/algorithms:sparse_permutation",
839+
"//ortools/util:sorted_interval_list",
840+
"@com_google_absl//absl/container:flat_hash_map",
841+
"@com_google_absl//absl/log:check",
842+
"@com_google_absl//absl/types:span",
843+
],
844+
)
845+
811846
cc_library(
812847
name = "cp_model_table",
813848
srcs = ["cp_model_table.cc"],
@@ -873,6 +908,7 @@ cc_library(
873908
":sat_parameters_cc_proto",
874909
":sat_solver",
875910
":simplification",
911+
":solution_crush",
876912
":util",
877913
":var_domination",
878914
"//ortools/base",
@@ -975,6 +1011,7 @@ cc_library(
9751011
":cp_model_utils",
9761012
":presolve_context",
9771013
":sat_parameters_cc_proto",
1014+
":solution_crush",
9781015
":util",
9791016
"//ortools/base",
9801017
"//ortools/base:stl_util",
@@ -1436,6 +1473,7 @@ cc_library(
14361473
":integer_base",
14371474
":presolve_context",
14381475
":presolve_util",
1476+
":solution_crush",
14391477
":util",
14401478
"//ortools/algorithms:dynamic_partition",
14411479
"//ortools/base",
@@ -1723,7 +1761,9 @@ cc_library(
17231761
":sat_base",
17241762
":scheduling_helpers",
17251763
":util",
1764+
"@com_google_absl//absl/base:log_severity",
17261765
"@com_google_absl//absl/log",
1766+
"@com_google_absl//absl/log:check",
17271767
"@com_google_absl//absl/types:span",
17281768
],
17291769
)
@@ -2283,13 +2323,15 @@ cc_library(
22832323
":intervals",
22842324
":linear_constraint",
22852325
":model",
2326+
":no_overlap_2d_helper",
22862327
":precedences",
22872328
":presolve_util",
22882329
":routing_cuts",
22892330
":sat_base",
22902331
":sat_parameters_cc_proto",
22912332
":sat_solver",
22922333
":scheduling_cuts",
2334+
":scheduling_helpers",
22932335
":util",
22942336
"//ortools/base",
22952337
"//ortools/base:mathutil",
@@ -2665,6 +2707,7 @@ cc_library(
26652707
":linear_constraint",
26662708
":linear_constraint_manager",
26672709
":model",
2710+
":no_overlap_2d_helper",
26682711
":sat_base",
26692712
":scheduling_helpers",
26702713
":util",
@@ -3204,6 +3247,7 @@ cc_library(
32043247
srcs = ["diffn.cc"],
32053248
hdrs = ["diffn.h"],
32063249
deps = [
3250+
":2d_mandatory_overlap_propagator",
32073251
":2d_orthogonal_packing",
32083252
":2d_try_edge_propagator",
32093253
":cumulative_energy",
@@ -3632,6 +3676,7 @@ cc_library(
36323676
":sat_base",
36333677
":sat_parameters_cc_proto",
36343678
":sat_solver",
3679+
":solution_crush",
36353680
":symmetry_util",
36363681
":util",
36373682
"//ortools/algorithms:binary_search",

ortools/sat/cp_model_checker.cc

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1442,18 +1442,15 @@ class ConstraintChecker {
14421442
}
14431443

14441444
std::optional<std::pair<int, int>> one_intersection;
1445-
if (!has_zero_sizes) {
1446-
absl::c_stable_sort(enforced_rectangles,
1447-
[](const Rectangle& a, const Rectangle& b) {
1448-
return a.x_min < b.x_min;
1449-
});
1450-
one_intersection = FindOneIntersectionIfPresent(enforced_rectangles);
1445+
absl::c_stable_sort(enforced_rectangles,
1446+
[](const Rectangle& a, const Rectangle& b) {
1447+
return a.x_min < b.x_min;
1448+
});
1449+
if (has_zero_sizes) {
1450+
one_intersection =
1451+
FindOneIntersectionIfPresentWithZeroArea(enforced_rectangles);
14511452
} else {
1452-
const std::vector<std::pair<int, int>> intersections =
1453-
FindPartialRectangleIntersections(enforced_rectangles);
1454-
if (!intersections.empty()) {
1455-
one_intersection = intersections[0];
1456-
}
1453+
one_intersection = FindOneIntersectionIfPresent(enforced_rectangles);
14571454
}
14581455

14591456
if (one_intersection != std::nullopt) {

0 commit comments

Comments
 (0)