4242#include "absl/status/statusor.h"
4343#include "absl/strings/str_cat.h"
4444#include "absl/types/span.h"
45+ #include "google/protobuf/arena.h"
4546#include "google/protobuf/repeated_field.h"
47+ #include "google/protobuf/repeated_ptr_field.h"
4648#include "google/protobuf/text_format.h"
4749#include "ortools/base/logging.h"
4850#include "ortools/base/mathutil.h"
@@ -5836,9 +5838,8 @@ bool CpModelPresolver::PresolveNoOverlap2D(int /*c*/, ConstraintProto* ct) {
58365838
58375839 // Filter absent boxes.
58385840 int new_size = 0;
5839- std::vector<Rectangle> bounding_boxes, fixed_boxes;
5841+ std::vector<Rectangle> bounding_boxes, fixed_boxes, non_fixed_bounding_boxes ;
58405842 std::vector<RectangleInRange> non_fixed_boxes;
5841- std::vector<int> active_boxes;
58425843 absl::flat_hash_set<int> fixed_item_indexes;
58435844 for (int i = 0; i < proto.x_intervals_size(); ++i) {
58445845 const int x_interval_index = proto.x_intervals(i);
@@ -5877,14 +5878,14 @@ bool CpModelPresolver::PresolveNoOverlap2D(int /*c*/, ConstraintProto* ct) {
58775878 IntegerValue(context_->EndMax(x_interval_index)),
58785879 IntegerValue(context_->StartMin(y_interval_index)),
58795880 IntegerValue(context_->EndMax(y_interval_index))});
5880- active_boxes.push_back(new_size);
58815881 if (context_->IntervalIsConstant(x_interval_index) &&
58825882 context_->IntervalIsConstant(y_interval_index) &&
58835883 context_->SizeMax(x_interval_index) > 0 &&
58845884 context_->SizeMax(y_interval_index) > 0) {
58855885 fixed_boxes.push_back(bounding_boxes.back());
58865886 fixed_item_indexes.insert(new_size);
58875887 } else {
5888+ non_fixed_bounding_boxes.push_back(bounding_boxes.back());
58885889 non_fixed_boxes.push_back(
58895890 {.box_index = new_size,
58905891 .bounding_area = bounding_boxes.back(),
@@ -5909,15 +5910,25 @@ bool CpModelPresolver::PresolveNoOverlap2D(int /*c*/, ConstraintProto* ct) {
59095910 }
59105911 }
59115912
5912- const CompactVectorVector<int> components = GetOverlappingRectangleComponents(
5913- bounding_boxes, absl::MakeSpan(active_boxes));
5914- // The result of GetOverlappingRectangleComponents() omit singleton components
5915- // thus to check whether a graph is fully connected we must check also the
5916- // size of the unique component.
5917- const bool is_fully_connected =
5918- (components.size() == 1 && components[0].size() == active_boxes.size()) ||
5919- (active_boxes.size() <= 1);
5920- if (!is_fully_connected) {
5913+ if (new_size < initial_num_boxes) {
5914+ context_->UpdateRuleStats("no_overlap_2d: removed inactive boxes");
5915+ ct->mutable_no_overlap_2d()->mutable_x_intervals()->Truncate(new_size);
5916+ ct->mutable_no_overlap_2d()->mutable_y_intervals()->Truncate(new_size);
5917+ }
5918+
5919+ if (new_size == 0) {
5920+ context_->UpdateRuleStats("no_overlap_2d: no boxes");
5921+ return RemoveConstraint(ct);
5922+ }
5923+
5924+ if (new_size == 1) {
5925+ context_->UpdateRuleStats("no_overlap_2d: only one box");
5926+ return RemoveConstraint(ct);
5927+ }
5928+
5929+ const CompactVectorVector<int> components =
5930+ GetOverlappingRectangleComponents(bounding_boxes);
5931+ if (components.size() > 1) {
59215932 for (int i = 0; i < components.size(); ++i) {
59225933 absl::Span<const int> boxes = components[i];
59235934 if (boxes.size() <= 1) continue;
@@ -5962,30 +5973,14 @@ bool CpModelPresolver::PresolveNoOverlap2D(int /*c*/, ConstraintProto* ct) {
59625973 return RemoveConstraint(ct);
59635974 }
59645975
5965- if (new_size < initial_num_boxes) {
5966- context_->UpdateRuleStats("no_overlap_2d: removed inactive boxes");
5967- ct->mutable_no_overlap_2d()->mutable_x_intervals()->Truncate(new_size);
5968- ct->mutable_no_overlap_2d()->mutable_y_intervals()->Truncate(new_size);
5969- }
5970-
5971- if (new_size == 0) {
5972- context_->UpdateRuleStats("no_overlap_2d: no boxes");
5973- return RemoveConstraint(ct);
5974- }
5975-
5976- if (new_size == 1) {
5977- context_->UpdateRuleStats("no_overlap_2d: only one box");
5978- return RemoveConstraint(ct);
5979- }
5980-
59815976 // We check if the fixed boxes are not overlapping so downstream code can
59825977 // assume it to be true.
59835978 if (!FindPartialRectangleIntersections(fixed_boxes).empty()) {
59845979 return context_->NotifyThatModelIsUnsat(
59855980 "Two fixed boxes in no_overlap_2d overlap");
59865981 }
59875982
5988- if (fixed_boxes.size() == active_boxes.size ()) {
5983+ if (non_fixed_bounding_boxes.empty ()) {
59895984 context_->UpdateRuleStats("no_overlap_2d: all boxes are fixed");
59905985 return RemoveConstraint(ct);
59915986 }
@@ -6035,6 +6030,36 @@ bool CpModelPresolver::PresolveNoOverlap2D(int /*c*/, ConstraintProto* ct) {
60356030 return RemoveConstraint(ct);
60366031 }
60376032 }
6033+ // If the non-fixed boxes are disjoint but connected by fixed boxes, we can
6034+ // split the constraint and duplicate the fixed boxes. To avoid duplicating
6035+ // too many fixed boxes, we do this after we we applied the presolve reducing
6036+ // their number to as few as possible.
6037+ const CompactVectorVector<int> non_fixed_components =
6038+ GetOverlappingRectangleComponents(non_fixed_bounding_boxes);
6039+ if (non_fixed_components.size() > 1) {
6040+ for (int i = 0; i < non_fixed_components.size(); ++i) {
6041+ // Note: we care about components of size 1 because they might be
6042+ // overlapping with the fixed boxes.
6043+ absl::Span<const int> indexes = non_fixed_components[i];
6044+
6045+ NoOverlap2DConstraintProto* new_no_overlap_2d =
6046+ context_->working_model->add_constraints()->mutable_no_overlap_2d();
6047+ for (const int idx : indexes) {
6048+ const int b = non_fixed_boxes[idx].box_index;
6049+ new_no_overlap_2d->add_x_intervals(proto.x_intervals(b));
6050+ new_no_overlap_2d->add_y_intervals(proto.y_intervals(b));
6051+ }
6052+ for (const int b : fixed_item_indexes) {
6053+ new_no_overlap_2d->add_x_intervals(proto.x_intervals(b));
6054+ new_no_overlap_2d->add_y_intervals(proto.y_intervals(b));
6055+ }
6056+ }
6057+ context_->UpdateNewConstraintsVariableUsage();
6058+ context_->UpdateRuleStats(
6059+ "no_overlap_2d: split into disjoint components duplicating fixed "
6060+ "boxes");
6061+ return RemoveConstraint(ct);
6062+ }
60386063 RunPropagatorsForConstraint(*ct);
60396064 return new_size < initial_num_boxes;
60406065}
@@ -9295,8 +9320,9 @@ void CpModelPresolver::DetectDuplicateColumns() {
92959320 if (rep_to_dups[var].empty()) continue;
92969321
92979322 // Since columns are the same, we can introduce a new variable = sum all
9298- // columns. Note that we shouldn't have any overflow here by the
9299- // precondition on our variable domains.
9323+ // columns. Note that the linear expression will not overflow, but the
9324+ // overflow check also requires that max_sum < int_max/2, which might
9325+ // happen.
93009326 //
93019327 // In the corner case where there is a lot of holes in the domain, and the
93029328 // sum domain is too complex, we skip. Hopefully this should be rare.
@@ -9318,7 +9344,10 @@ void CpModelPresolver::DetectDuplicateColumns() {
93189344 }
93199345 const int new_var = context_->NewIntVarWithDefinition(
93209346 domain, definition, /*append_constraint_to_mapping_model=*/true);
9321- CHECK_NE(new_var, -1);
9347+ if (new_var == -1) {
9348+ context_->UpdateRuleStats("TODO duplicate: possible overflow");
9349+ continue;
9350+ }
93229351
93239352 var_to_remove.push_back(var);
93249353 CHECK_EQ(var_to_rep[var], -1);
@@ -14357,19 +14386,25 @@ void ApplyVariableMapping(absl::Span<int> mapping,
1435714386 }
1435814387
1435914388 // Move the variable definitions.
14360- std::vector<IntegerVariableProto> new_variables;
14389+ google::protobuf::RepeatedPtrField<IntegerVariableProto>
14390+ new_variables_storage;
14391+ google::protobuf::RepeatedPtrField<IntegerVariableProto>* new_variables;
14392+ if (proto->GetArena() == nullptr) {
14393+ new_variables = &new_variables_storage;
14394+ } else {
14395+ new_variables = google::protobuf::Arena::Create<
14396+ google::protobuf::RepeatedPtrField<IntegerVariableProto>>(
14397+ proto->GetArena());
14398+ }
1436114399 for (int i = 0; i < mapping.size(); ++i) {
1436214400 const int image = mapping[i];
1436314401 if (image < 0) continue;
14364- if (image >= new_variables. size()) {
14365- new_variables.resize(image + 1, IntegerVariableProto() );
14402+ while (image >= new_variables-> size()) {
14403+ new_variables->Add( );
1436614404 }
14367- new_variables[image].Swap(proto->mutable_variables(i));
14368- }
14369- proto->clear_variables();
14370- for (IntegerVariableProto& proto_ref : new_variables) {
14371- proto->add_variables()->Swap(&proto_ref);
14405+ (*new_variables)[image].Swap(proto->mutable_variables(i));
1437214406 }
14407+ proto->mutable_variables()->Swap(new_variables);
1437314408
1437414409 // Check that all variables have a non-empty domain.
1437514410 for (const IntegerVariableProto& v : proto->variables()) {
0 commit comments