Skip to content

Commit

Permalink
[CP-SAT] regroup all hint preservation code in a SolutionCrunch class…
Browse files Browse the repository at this point in the history
…; more work on no_overlap_2d propagator; add exception processing if an exceptions is raised in a python callback (solution, log, best_bound)
  • Loading branch information
lperron committed Jan 20, 2025
1 parent eee21a5 commit 8733308
Show file tree
Hide file tree
Showing 31 changed files with 1,207 additions and 536 deletions.
106 changes: 106 additions & 0 deletions ortools/sat/2d_mandatory_overlap_propagator.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "ortools/sat/2d_mandatory_overlap_propagator.h"

#include <cstdint>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "absl/types/span.h"
#include "ortools/base/logging.h"
#include "ortools/sat/diffn_util.h"
#include "ortools/sat/integer.h"
#include "ortools/sat/model.h"
#include "ortools/sat/no_overlap_2d_helper.h"
#include "ortools/sat/scheduling_helpers.h"

namespace operations_research {
namespace sat {

int MandatoryOverlapPropagator::RegisterWith(GenericLiteralWatcher* watcher) {
const int id = watcher->Register(this);
helper_.WatchAllBoxes(id);
return id;
}

MandatoryOverlapPropagator::~MandatoryOverlapPropagator() {
if (!VLOG_IS_ON(1)) return;
std::vector<std::pair<std::string, int64_t>> stats;
stats.push_back({"MandatoryOverlapPropagator/called_with_zero_area",
num_calls_zero_area_});
stats.push_back({"MandatoryOverlapPropagator/called_without_zero_area",
num_calls_nonzero_area_});
stats.push_back({"MandatoryOverlapPropagator/conflicts", num_conflicts_});

shared_stats_->AddStats(stats);
}

bool MandatoryOverlapPropagator::Propagate() {
if (!helper_.SynchronizeAndSetDirection(true, true, false)) return false;

mandatory_regions_.clear();
mandatory_regions_index_.clear();
bool has_zero_area_boxes = false;
absl::Span<const TaskTime> tasks =
helper_.x_helper().TaskByIncreasingNegatedStartMax();
for (int i = tasks.size() - 1; i >= 0; --i) {
const int b = tasks[i].task_index;
if (!helper_.IsPresent(b)) continue;
const ItemWithVariableSize item = helper_.GetItemWithVariableSize(b);
if (item.x.start_max > item.x.end_min ||
item.y.start_max > item.y.end_min) {
continue;
}
mandatory_regions_.push_back({.x_min = item.x.start_max,
.x_max = item.x.end_min,
.y_min = item.y.start_max,
.y_max = item.y.end_min});
mandatory_regions_index_.push_back(b);

if (mandatory_regions_.back().SizeX() == 0 ||
mandatory_regions_.back().SizeY() == 0) {
has_zero_area_boxes = true;
}
}
std::optional<std::pair<int, int>> conflict;
if (has_zero_area_boxes) {
num_calls_zero_area_++;
conflict = FindOneIntersectionIfPresentWithZeroArea(mandatory_regions_);
} else {
num_calls_nonzero_area_++;
conflict = FindOneIntersectionIfPresent(mandatory_regions_);
}

if (conflict.has_value()) {
num_conflicts_++;
return helper_.ReportConflictFromTwoBoxes(
mandatory_regions_index_[conflict->first],
mandatory_regions_index_[conflict->second]);
}
return true;
}

void CreateAndRegisterMandatoryOverlapPropagator(
NoOverlap2DConstraintHelper* helper, Model* model,
GenericLiteralWatcher* watcher, int priority) {
MandatoryOverlapPropagator* propagator =
new MandatoryOverlapPropagator(helper, model);
watcher->SetPropagatorPriority(propagator->RegisterWith(watcher), priority);
model->TakeOwnership(propagator);
}

} // namespace sat
} // namespace operations_research
65 changes: 65 additions & 0 deletions ortools/sat/2d_mandatory_overlap_propagator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#ifndef OR_TOOLS_SAT_2D_MANDATORY_OVERLAP_PROPAGATOR_H_
#define OR_TOOLS_SAT_2D_MANDATORY_OVERLAP_PROPAGATOR_H_

#include <cstdint>
#include <vector>

#include "ortools/sat/diffn_util.h"
#include "ortools/sat/integer.h"
#include "ortools/sat/model.h"
#include "ortools/sat/no_overlap_2d_helper.h"
#include "ortools/sat/synchronization.h"

namespace operations_research {
namespace sat {

// Propagator that checks that no mandatory area of two boxes overlap in
// O(N * log N) time.
void CreateAndRegisterMandatoryOverlapPropagator(
NoOverlap2DConstraintHelper* helper, Model* model,
GenericLiteralWatcher* watcher, int priority);

// Exposed for testing.
class MandatoryOverlapPropagator : public PropagatorInterface {
public:
MandatoryOverlapPropagator(NoOverlap2DConstraintHelper* helper, Model* model)
: helper_(*helper),
shared_stats_(model->GetOrCreate<SharedStatistics>()) {}

~MandatoryOverlapPropagator() override;

bool Propagate() final;
int RegisterWith(GenericLiteralWatcher* watcher);

private:
NoOverlap2DConstraintHelper& helper_;
SharedStatistics* shared_stats_;
std::vector<Rectangle> mandatory_regions_;
std::vector<int> mandatory_regions_index_;

int64_t num_conflicts_ = 0;
int64_t num_calls_zero_area_ = 0;
int64_t num_calls_nonzero_area_ = 0;

MandatoryOverlapPropagator(const MandatoryOverlapPropagator&) = delete;
MandatoryOverlapPropagator& operator=(const MandatoryOverlapPropagator&) =
delete;
};

} // namespace sat
} // namespace operations_research

#endif // OR_TOOLS_SAT_2D_MANDATORY_OVERLAP_PROPAGATOR_H_
15 changes: 10 additions & 5 deletions ortools/sat/2d_try_edge_propagator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,9 @@ std::vector<int> TryEdgeRectanglePropagator::GetMinimumProblemWithPropagation(
}

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

void CreateAndRegisterTryEdgePropagator(NoOverlap2DConstraintHelper* helper,
Model* model,
GenericLiteralWatcher* watcher) {
GenericLiteralWatcher* watcher,
int priority) {
TryEdgeRectanglePropagator* try_edge_propagator =
new TryEdgeRectanglePropagator(true, true, false, helper, model);
watcher->SetPropagatorPriority(try_edge_propagator->RegisterWith(watcher), 5);
watcher->SetPropagatorPriority(try_edge_propagator->RegisterWith(watcher),
priority);
model->TakeOwnership(try_edge_propagator);

TryEdgeRectanglePropagator* try_edge_propagator_mirrored =
new TryEdgeRectanglePropagator(false, true, false, helper, model);
watcher->SetPropagatorPriority(
try_edge_propagator_mirrored->RegisterWith(watcher), 5);
try_edge_propagator_mirrored->RegisterWith(watcher), priority);
model->TakeOwnership(try_edge_propagator_mirrored);

TryEdgeRectanglePropagator* try_edge_propagator_swap =
new TryEdgeRectanglePropagator(true, true, true, helper, model);
watcher->SetPropagatorPriority(
try_edge_propagator_swap->RegisterWith(watcher), 5);
try_edge_propagator_swap->RegisterWith(watcher), priority);
model->TakeOwnership(try_edge_propagator_swap);

TryEdgeRectanglePropagator* try_edge_propagator_swap_mirrored =
new TryEdgeRectanglePropagator(false, true, true, helper, model);
watcher->SetPropagatorPriority(
try_edge_propagator_swap_mirrored->RegisterWith(watcher), 5);
try_edge_propagator_swap_mirrored->RegisterWith(watcher), priority);
model->TakeOwnership(try_edge_propagator_swap_mirrored);
}

Expand Down
3 changes: 2 additions & 1 deletion ortools/sat/2d_try_edge_propagator.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ namespace sat {
// it is different from the current x_min, it will propagate the new x_min.
void CreateAndRegisterTryEdgePropagator(NoOverlap2DConstraintHelper* helper,
Model* model,
GenericLiteralWatcher* watcher);
GenericLiteralWatcher* watcher,
int priority);

// Exposed for testing.
class TryEdgeRectanglePropagator : public PropagatorInterface {
Expand Down
47 changes: 46 additions & 1 deletion ortools/sat/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,22 @@ proto_library(
srcs = ["cp_model.proto"],
)

cc_library(
name = "2d_mandatory_overlap_propagator",
srcs = ["2d_mandatory_overlap_propagator.cc"],
hdrs = ["2d_mandatory_overlap_propagator.h"],
deps = [
":diffn_util",
":integer",
":model",
":no_overlap_2d_helper",
":scheduling_helpers",
":synchronization",
"@com_google_absl//absl/log",
"@com_google_absl//absl/types:span",
],
)

cc_proto_library(
name = "cp_model_cc_proto",
deps = [":cp_model_proto"],
Expand Down Expand Up @@ -765,6 +781,7 @@ cc_library(
":presolve_util",
":sat_parameters_cc_proto",
":sat_solver",
":solution_crush",
":util",
"//ortools/algorithms:sparse_permutation",
"//ortools/base",
Expand Down Expand Up @@ -798,16 +815,34 @@ cc_test(
":cp_model_utils",
":model",
":presolve_context",
":solution_crush",
"//ortools/base:gmock_main",
"//ortools/base:parse_test_proto",
"//ortools/base:types",
"//ortools/util:affine_relation",
"//ortools/util:sorted_interval_list",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/types:span",
],
)

cc_library(
name = "solution_crush",
srcs = [
"solution_crush.cc",
],
hdrs = ["solution_crush.h"],
deps = [
":cp_model_cc_proto",
":cp_model_utils",
":sat_parameters_cc_proto",
"//ortools/algorithms:sparse_permutation",
"//ortools/util:sorted_interval_list",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/types:span",
],
)

cc_library(
name = "cp_model_table",
srcs = ["cp_model_table.cc"],
Expand Down Expand Up @@ -873,6 +908,7 @@ cc_library(
":sat_parameters_cc_proto",
":sat_solver",
":simplification",
":solution_crush",
":util",
":var_domination",
"//ortools/base",
Expand Down Expand Up @@ -975,6 +1011,7 @@ cc_library(
":cp_model_utils",
":presolve_context",
":sat_parameters_cc_proto",
":solution_crush",
":util",
"//ortools/base",
"//ortools/base:stl_util",
Expand Down Expand Up @@ -1436,6 +1473,7 @@ cc_library(
":integer_base",
":presolve_context",
":presolve_util",
":solution_crush",
":util",
"//ortools/algorithms:dynamic_partition",
"//ortools/base",
Expand Down Expand Up @@ -1723,7 +1761,9 @@ cc_library(
":sat_base",
":scheduling_helpers",
":util",
"@com_google_absl//absl/base:log_severity",
"@com_google_absl//absl/log",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/types:span",
],
)
Expand Down Expand Up @@ -2283,13 +2323,15 @@ cc_library(
":intervals",
":linear_constraint",
":model",
":no_overlap_2d_helper",
":precedences",
":presolve_util",
":routing_cuts",
":sat_base",
":sat_parameters_cc_proto",
":sat_solver",
":scheduling_cuts",
":scheduling_helpers",
":util",
"//ortools/base",
"//ortools/base:mathutil",
Expand Down Expand Up @@ -2665,6 +2707,7 @@ cc_library(
":linear_constraint",
":linear_constraint_manager",
":model",
":no_overlap_2d_helper",
":sat_base",
":scheduling_helpers",
":util",
Expand Down Expand Up @@ -3204,6 +3247,7 @@ cc_library(
srcs = ["diffn.cc"],
hdrs = ["diffn.h"],
deps = [
":2d_mandatory_overlap_propagator",
":2d_orthogonal_packing",
":2d_try_edge_propagator",
":cumulative_energy",
Expand Down Expand Up @@ -3632,6 +3676,7 @@ cc_library(
":sat_base",
":sat_parameters_cc_proto",
":sat_solver",
":solution_crush",
":symmetry_util",
":util",
"//ortools/algorithms:binary_search",
Expand Down
19 changes: 8 additions & 11 deletions ortools/sat/cp_model_checker.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1442,18 +1442,15 @@ class ConstraintChecker {
}

std::optional<std::pair<int, int>> one_intersection;
if (!has_zero_sizes) {
absl::c_stable_sort(enforced_rectangles,
[](const Rectangle& a, const Rectangle& b) {
return a.x_min < b.x_min;
});
one_intersection = FindOneIntersectionIfPresent(enforced_rectangles);
absl::c_stable_sort(enforced_rectangles,
[](const Rectangle& a, const Rectangle& b) {
return a.x_min < b.x_min;
});
if (has_zero_sizes) {
one_intersection =
FindOneIntersectionIfPresentWithZeroArea(enforced_rectangles);
} else {
const std::vector<std::pair<int, int>> intersections =
FindPartialRectangleIntersections(enforced_rectangles);
if (!intersections.empty()) {
one_intersection = intersections[0];
}
one_intersection = FindOneIntersectionIfPresent(enforced_rectangles);
}

if (one_intersection != std::nullopt) {
Expand Down
Loading

0 comments on commit 8733308

Please sign in to comment.