Skip to content

Commit 05fccb4

Browse files
committed
[CP-SAT] more work on hints; no_overlap_2d optimization
1 parent d4c1c95 commit 05fccb4

24 files changed

+237
-166
lines changed

ortools/sat/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ cc_library(
125125
"//ortools/base:file",
126126
"//ortools/base:hash",
127127
"//ortools/base:stl_util",
128+
"//ortools/util:bitset",
128129
"//ortools/util:saturated_arithmetic",
129130
"//ortools/util:sorted_interval_list",
130131
"@com_google_absl//absl/container:flat_hash_map",

ortools/sat/circuit.cc

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -358,10 +358,9 @@ bool CircuitPropagator::Propagate() {
358358
return true;
359359
}
360360

361-
NoCyclePropagator::NoCyclePropagator(int num_nodes,
362-
const std::vector<int>& tails,
363-
const std::vector<int>& heads,
364-
const std::vector<Literal>& literals,
361+
NoCyclePropagator::NoCyclePropagator(int num_nodes, absl::Span<const int> tails,
362+
absl::Span<const int> heads,
363+
absl::Span<const Literal> literals,
365364
Model* model)
366365
: num_nodes_(num_nodes),
367366
trail_(model->GetOrCreate<Trail>()),

ortools/sat/circuit.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,9 +119,9 @@ class CircuitPropagator : PropagatorInterface, ReversibleInterface {
119119
// Enforce the fact that there is no cycle in the given directed graph.
120120
class NoCyclePropagator : PropagatorInterface, ReversibleInterface {
121121
public:
122-
NoCyclePropagator(int num_nodes, const std::vector<int>& tails,
123-
const std::vector<int>& heads,
124-
const std::vector<Literal>& literals, Model* model);
122+
NoCyclePropagator(int num_nodes, absl::Span<const int> tails,
123+
absl::Span<const int> heads,
124+
absl::Span<const Literal> literals, Model* model);
125125

126126
void SetLevel(int level) final;
127127
bool Propagate() final;

ortools/sat/clause.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1446,7 +1446,17 @@ bool BinaryImplicationGraph::DetectEquivalences(bool log_info) {
14461446
// that this might result in more implications when we expand small at most
14471447
// one.
14481448
at_most_ones_.clear();
1449+
int saved_trail_index = propagation_trail_index_;
14491450
CleanUpAndAddAtMostOnes(/*base_index=*/0);
1451+
// This might have run the propagation on a few variables without taking
1452+
// into account the AMOs. Propagate again.
1453+
//
1454+
// TODO(user): Maybe a better alternative is to not propagate when we fix
1455+
// variables inside CleanUpAndAddAtMostOnes().
1456+
if (propagation_trail_index_ != saved_trail_index) {
1457+
propagation_trail_index_ = saved_trail_index;
1458+
Propagate(trail_);
1459+
}
14501460

14511461
num_implications_ = 0;
14521462
for (LiteralIndex i(0); i < size; ++i) {

ortools/sat/clause_test.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,16 @@ TEST(BinaryImplicationGraphTest, BasicUnsatSccTest) {
108108
EXPECT_FALSE(graph->DetectEquivalences());
109109
}
110110

111+
TEST(BinaryImplicationGraphTest, IssueFoundOnMipLibTest) {
112+
Model model;
113+
auto* graph = model.GetOrCreate<BinaryImplicationGraph>();
114+
model.GetOrCreate<SatSolver>()->SetNumVariables(10);
115+
EXPECT_TRUE(graph->AddAtMostOne(Literals({+1, +2, -3, -4})));
116+
EXPECT_TRUE(graph->AddAtMostOne(Literals({+1, +2, +3, +4, +5})));
117+
EXPECT_TRUE(graph->AddAtMostOne(Literals({+1, -2, -3, +4, +5})));
118+
EXPECT_TRUE(graph->DetectEquivalences());
119+
}
120+
111121
TEST(BinaryImplicationGraphTest, DetectEquivalences) {
112122
// We take a bunch of random permutations, equivalence classes will be cycles.
113123
// We make sure the representative of x and not(x) are always negation of

ortools/sat/cp_model_lns.cc

Lines changed: 74 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -558,7 +558,7 @@ struct TimePartition {
558558
// Selects all intervals in a random time window to meet the difficulty
559559
// requirement.
560560
TimePartition PartitionIndicesAroundRandomTimeWindow(
561-
const std::vector<int>& intervals, const CpModelProto& model_proto,
561+
absl::Span<const int> intervals, const CpModelProto& model_proto,
562562
const CpSolverResponse& initial_solution, double difficulty,
563563
absl::BitGenRef random) {
564564
std::vector<StartEndIndex> start_end_indices;
@@ -1036,15 +1036,10 @@ std::vector<std::vector<int>> NeighborhoodGeneratorHelper::GetRoutingPaths(
10361036

10371037
Neighborhood NeighborhoodGeneratorHelper::FixGivenVariables(
10381038
const CpSolverResponse& base_solution,
1039-
const absl::flat_hash_set<int>& variables_to_fix) const {
1040-
int initial_num_variables = 0;
1041-
{
1042-
absl::ReaderMutexLock domain_lock(&domain_mutex_);
1043-
1044-
initial_num_variables =
1045-
model_proto_with_only_variables_->variables().size();
1046-
}
1047-
Neighborhood neighborhood(initial_num_variables);
1039+
const Bitset64<int>& variables_to_fix) const {
1040+
const int num_variables = variables_to_fix.size();
1041+
Neighborhood neighborhood(num_variables);
1042+
neighborhood.delta.mutable_variables()->Reserve(num_variables);
10481043

10491044
// TODO(user): Maybe relax all variables in the objective when the number
10501045
// is small or negligible compared to the number of variables.
@@ -1054,12 +1049,9 @@ Neighborhood NeighborhoodGeneratorHelper::FixGivenVariables(
10541049
: -1;
10551050

10561051
// Fill in neighborhood.delta all variable domains.
1052+
int num_fixed = 0;
10571053
{
10581054
absl::ReaderMutexLock domain_lock(&domain_mutex_);
1059-
1060-
const int num_variables =
1061-
model_proto_with_only_variables_->variables().size();
1062-
neighborhood.delta.mutable_variables()->Reserve(num_variables);
10631055
for (int i = 0; i < num_variables; ++i) {
10641056
const IntegerVariableProto& current_var =
10651057
model_proto_with_only_variables_->variables(i);
@@ -1068,17 +1060,20 @@ Neighborhood NeighborhoodGeneratorHelper::FixGivenVariables(
10681060
// We only copy the name in debug mode.
10691061
if (DEBUG_MODE) new_var->set_name(current_var.name());
10701062

1071-
const Domain domain = ReadDomainFromProto(current_var);
1072-
const int64_t base_value = base_solution.solution(i);
1063+
if (variables_to_fix[i] && i != unique_objective_variable) {
1064+
++num_fixed;
10731065

1074-
if (variables_to_fix.contains(i) && i != unique_objective_variable) {
1075-
if (domain.Contains(base_value)) {
1066+
// Note the use of DomainInProtoContains() instead of
1067+
// ReadDomainFromProto() as the later is slower and allocate memory.
1068+
const int64_t base_value = base_solution.solution(i);
1069+
if (DomainInProtoContains(current_var, base_value)) {
10761070
new_var->add_domain(base_value);
10771071
new_var->add_domain(base_value);
10781072
} else {
10791073
// If under the updated domain, the base solution is no longer valid,
10801074
// We should probably regenerate this neighborhood. But for now we
10811075
// just do a best effort and take the closest value.
1076+
const Domain domain = ReadDomainFromProto(current_var);
10821077
int64_t closest_value = domain.Min();
10831078
int64_t closest_dist = std::abs(closest_value - base_value);
10841079
for (const ClosedInterval interval : domain) {
@@ -1093,7 +1088,7 @@ Neighborhood NeighborhoodGeneratorHelper::FixGivenVariables(
10931088
FillDomainInProto(Domain(closest_value, closest_value), new_var);
10941089
}
10951090
} else {
1096-
FillDomainInProto(domain, new_var);
1091+
*new_var->mutable_domain() = current_var.domain();
10971092
}
10981093
}
10991094
}
@@ -1138,10 +1133,15 @@ Neighborhood NeighborhoodGeneratorHelper::FixGivenVariables(
11381133
neighborhood.variables_that_can_be_fixed_to_local_optimum.clear();
11391134
}
11401135

1136+
const int num_relaxed = num_variables - num_fixed;
1137+
neighborhood.delta.mutable_solution_hint()->mutable_vars()->Reserve(
1138+
num_relaxed);
1139+
neighborhood.delta.mutable_solution_hint()->mutable_values()->Reserve(
1140+
num_relaxed);
11411141
AddSolutionHinting(base_solution, &neighborhood.delta);
11421142

11431143
neighborhood.is_generated = true;
1144-
neighborhood.is_reduced = !variables_to_fix.empty();
1144+
neighborhood.is_reduced = num_fixed > 0;
11451145
neighborhood.is_simple = true;
11461146

11471147
// TODO(user): force better objective? Note that this is already done when the
@@ -1172,25 +1172,26 @@ void NeighborhoodGeneratorHelper::AddSolutionHinting(
11721172
Neighborhood NeighborhoodGeneratorHelper::RelaxGivenVariables(
11731173
const CpSolverResponse& initial_solution,
11741174
absl::Span<const int> relaxed_variables) const {
1175-
std::vector<bool> relaxed_variables_set(model_proto_.variables_size(), false);
1176-
for (const int var : relaxed_variables) relaxed_variables_set[var] = true;
1177-
absl::flat_hash_set<int> fixed_variables;
1175+
Bitset64<int> fixed_variables(NumVariables());
11781176
{
11791177
absl::ReaderMutexLock graph_lock(&graph_mutex_);
11801178
for (const int i : active_variables_) {
1181-
if (!relaxed_variables_set[i]) {
1182-
fixed_variables.insert(i);
1183-
}
1179+
fixed_variables.Set(i);
11841180
}
11851181
}
1182+
for (const int var : relaxed_variables) fixed_variables.Clear(var);
11861183
return FixGivenVariables(initial_solution, fixed_variables);
11871184
}
11881185

11891186
Neighborhood NeighborhoodGeneratorHelper::FixAllVariables(
11901187
const CpSolverResponse& initial_solution) const {
1191-
const std::vector<int>& all_variables = ActiveVariables();
1192-
const absl::flat_hash_set<int> fixed_variables(all_variables.begin(),
1193-
all_variables.end());
1188+
Bitset64<int> fixed_variables(NumVariables());
1189+
{
1190+
absl::ReaderMutexLock graph_lock(&graph_mutex_);
1191+
for (const int i : active_variables_) {
1192+
fixed_variables.Set(i);
1193+
}
1194+
}
11941195
return FixGivenVariables(initial_solution, fixed_variables);
11951196
}
11961197

@@ -1319,8 +1320,10 @@ Neighborhood RelaxRandomVariablesGenerator::Generate(
13191320
absl::BitGenRef random) {
13201321
std::vector<int> fixed_variables = helper_.ActiveVariables();
13211322
GetRandomSubset(1.0 - data.difficulty, &fixed_variables, random);
1322-
return helper_.FixGivenVariables(
1323-
initial_solution, {fixed_variables.begin(), fixed_variables.end()});
1323+
1324+
Bitset64<int> to_fix(helper_.NumVariables());
1325+
for (const int var : fixed_variables) to_fix.Set(var);
1326+
return helper_.FixGivenVariables(initial_solution, to_fix);
13241327
}
13251328

13261329
Neighborhood RelaxRandomConstraintsGenerator::Generate(
@@ -2302,14 +2305,13 @@ Neighborhood RandomRectanglesPackingNeighborhoodGenerator::Generate(
23022305
helper_.GetActiveRectangles(initial_solution);
23032306
GetRandomSubset(1.0 - data.difficulty, &rectangles_to_freeze, random);
23042307

2305-
absl::flat_hash_set<int> variables_to_freeze;
2308+
Bitset64<int> variables_to_freeze(helper_.NumVariables());
23062309
for (const ActiveRectangle& rectangle : rectangles_to_freeze) {
2307-
InsertVariablesFromConstraint(helper_.ModelProto(), rectangle.x_interval,
2308-
variables_to_freeze);
2309-
InsertVariablesFromConstraint(helper_.ModelProto(), rectangle.y_interval,
2310-
variables_to_freeze);
2310+
InsertVariablesFromInterval(helper_.ModelProto(), rectangle.x_interval,
2311+
variables_to_freeze);
2312+
InsertVariablesFromInterval(helper_.ModelProto(), rectangle.y_interval,
2313+
variables_to_freeze);
23112314
}
2312-
23132315
return helper_.FixGivenVariables(initial_solution, variables_to_freeze);
23142316
}
23152317

@@ -2351,15 +2353,15 @@ Neighborhood RectanglesPackingRelaxOneNeighborhoodGenerator::Generate(
23512353
//
23522354
// Note that we only consider two rectangles as potential neighbors if they
23532355
// are part of the same no_overlap_2d constraint.
2354-
absl::flat_hash_set<int> variables_to_freeze;
2356+
Bitset64<int> variables_to_freeze(helper_.NumVariables());
23552357
std::vector<std::pair<int, double>> distances;
23562358
distances.reserve(all_active_rectangles.size());
23572359
for (int i = 0; i < all_active_rectangles.size(); ++i) {
23582360
const ActiveRectangle& rectangle = all_active_rectangles[i];
2359-
InsertVariablesFromConstraint(helper_.ModelProto(), rectangle.x_interval,
2360-
variables_to_freeze);
2361-
InsertVariablesFromConstraint(helper_.ModelProto(), rectangle.y_interval,
2362-
variables_to_freeze);
2361+
InsertVariablesFromInterval(helper_.ModelProto(), rectangle.x_interval,
2362+
variables_to_freeze);
2363+
InsertVariablesFromInterval(helper_.ModelProto(), rectangle.y_interval,
2364+
variables_to_freeze);
23632365

23642366
const Rectangle rect = get_rectangle(rectangle);
23652367
const bool same_no_overlap_as_center_rect = absl::c_any_of(
@@ -2398,14 +2400,10 @@ Neighborhood RectanglesPackingRelaxOneNeighborhoodGenerator::Generate(
23982400

23992401
for (const int b : boxes_to_relax) {
24002402
const ActiveRectangle& rectangle = all_active_rectangles[b];
2401-
absl::flat_hash_set<int> variables_to_relax;
2402-
InsertVariablesFromConstraint(helper_.ModelProto(), rectangle.x_interval,
2403-
variables_to_relax);
2404-
InsertVariablesFromConstraint(helper_.ModelProto(), rectangle.y_interval,
2405-
variables_to_relax);
2406-
for (const int v : variables_to_relax) {
2407-
variables_to_freeze.erase(v);
2408-
}
2403+
RemoveVariablesFromInterval(helper_.ModelProto(), rectangle.x_interval,
2404+
variables_to_freeze);
2405+
RemoveVariablesFromInterval(helper_.ModelProto(), rectangle.y_interval,
2406+
variables_to_freeze);
24092407
}
24102408
Neighborhood neighborhood =
24112409
helper_.FixGivenVariables(initial_solution, variables_to_freeze);
@@ -2489,17 +2487,17 @@ Neighborhood RectanglesPackingRelaxTwoNeighborhoodsGenerator::Generate(
24892487
// TODO(user): This computes the distance between the center of the
24902488
// rectangles. We could use the real distance between the closest points, but
24912489
// not sure it is worth the extra complexity.
2492-
absl::flat_hash_set<int> variables_to_freeze;
2490+
Bitset64<int> variables_to_freeze(helper_.NumVariables());
24932491
std::vector<std::pair<int, double>> distances1;
24942492
std::vector<std::pair<int, double>> distances2;
24952493
distances1.reserve(all_active_rectangles.size());
24962494
distances2.reserve(all_active_rectangles.size());
24972495
for (int i = 0; i < all_active_rectangles.size(); ++i) {
24982496
const ActiveRectangle& rectangle = all_active_rectangles[i];
2499-
InsertVariablesFromConstraint(helper_.ModelProto(), rectangle.x_interval,
2500-
variables_to_freeze);
2501-
InsertVariablesFromConstraint(helper_.ModelProto(), rectangle.y_interval,
2502-
variables_to_freeze);
2497+
InsertVariablesFromInterval(helper_.ModelProto(), rectangle.x_interval,
2498+
variables_to_freeze);
2499+
InsertVariablesFromInterval(helper_.ModelProto(), rectangle.y_interval,
2500+
variables_to_freeze);
25032501

25042502
const Rectangle rect = get_rectangle(rectangle);
25052503
const bool same_no_overlap_as_rect1 =
@@ -2525,22 +2523,18 @@ Neighborhood RectanglesPackingRelaxTwoNeighborhoodsGenerator::Generate(
25252523
[](const auto& a, const auto& b) { return a.second < b.second; });
25262524
std::sort(distances2.begin(), distances2.end(),
25272525
[](const auto& a, const auto& b) { return a.second < b.second; });
2528-
absl::flat_hash_set<int> variables_to_relax;
25292526
for (auto& samples : {distances1, distances2}) {
25302527
const int num_potential_samples = samples.size();
25312528
for (int i = 0; i < std::min(num_potential_samples, num_to_sample_each);
25322529
++i) {
25332530
const int rectangle_idx = samples[i].first;
25342531
const ActiveRectangle& rectangle = all_active_rectangles[rectangle_idx];
2535-
InsertVariablesFromConstraint(helper_.ModelProto(), rectangle.x_interval,
2536-
variables_to_relax);
2537-
InsertVariablesFromConstraint(helper_.ModelProto(), rectangle.y_interval,
2538-
variables_to_relax);
2532+
RemoveVariablesFromInterval(helper_.ModelProto(), rectangle.x_interval,
2533+
variables_to_freeze);
2534+
RemoveVariablesFromInterval(helper_.ModelProto(), rectangle.y_interval,
2535+
variables_to_freeze);
25392536
}
25402537
}
2541-
for (const int v : variables_to_relax) {
2542-
variables_to_freeze.erase(v);
2543-
}
25442538

25452539
return helper_.FixGivenVariables(initial_solution, variables_to_freeze);
25462540
}
@@ -2583,15 +2577,15 @@ Neighborhood SlicePackingNeighborhoodGenerator::Generate(
25832577
indices_to_fix[index] = false;
25842578
}
25852579

2586-
absl::flat_hash_set<int> variables_to_freeze;
2580+
Bitset64<int> variables_to_freeze(helper_.NumVariables());
25872581
for (int index = 0; index < active_rectangles.size(); ++index) {
25882582
if (indices_to_fix[index]) {
2589-
InsertVariablesFromConstraint(helper_.ModelProto(),
2590-
active_rectangles[index].x_interval,
2591-
variables_to_freeze);
2592-
InsertVariablesFromConstraint(helper_.ModelProto(),
2593-
active_rectangles[index].y_interval,
2594-
variables_to_freeze);
2583+
InsertVariablesFromInterval(helper_.ModelProto(),
2584+
active_rectangles[index].x_interval,
2585+
variables_to_freeze);
2586+
InsertVariablesFromInterval(helper_.ModelProto(),
2587+
active_rectangles[index].y_interval,
2588+
variables_to_freeze);
25952589
}
25962590
}
25972591

@@ -2613,8 +2607,10 @@ Neighborhood RoutingRandomNeighborhoodGenerator::Generate(
26132607
all_path_variables.end());
26142608
std::sort(fixed_variables.begin(), fixed_variables.end());
26152609
GetRandomSubset(1.0 - data.difficulty, &fixed_variables, random);
2616-
return helper_.FixGivenVariables(
2617-
initial_solution, {fixed_variables.begin(), fixed_variables.end()});
2610+
2611+
Bitset64<int> to_fix(helper_.NumVariables());
2612+
for (const int var : fixed_variables) to_fix.Set(var);
2613+
return helper_.FixGivenVariables(initial_solution, to_fix);
26182614
}
26192615

26202616
Neighborhood RoutingPathNeighborhoodGenerator::Generate(
@@ -2656,11 +2652,11 @@ Neighborhood RoutingPathNeighborhoodGenerator::Generate(
26562652
}
26572653

26582654
// Compute the set of variables to fix.
2659-
absl::flat_hash_set<int> fixed_variables;
2655+
Bitset64<int> to_fix(helper_.NumVariables());
26602656
for (const int var : all_path_variables) {
2661-
if (!relaxed_variables.contains(var)) fixed_variables.insert(var);
2657+
if (!relaxed_variables.contains(var)) to_fix.Set(var);
26622658
}
2663-
return helper_.FixGivenVariables(initial_solution, fixed_variables);
2659+
return helper_.FixGivenVariables(initial_solution, to_fix);
26642660
}
26652661

26662662
Neighborhood RoutingFullPathNeighborhoodGenerator::Generate(
@@ -2722,11 +2718,11 @@ Neighborhood RoutingFullPathNeighborhoodGenerator::Generate(
27222718
}
27232719

27242720
// Compute the set of variables to fix.
2725-
absl::flat_hash_set<int> fixed_variables;
2721+
Bitset64<int> to_fix(helper_.NumVariables());
27262722
for (const int var : all_path_variables) {
2727-
if (!relaxed_variables.contains(var)) fixed_variables.insert(var);
2723+
if (!relaxed_variables.contains(var)) to_fix.Set(var);
27282724
}
2729-
return helper_.FixGivenVariables(initial_solution, fixed_variables);
2725+
return helper_.FixGivenVariables(initial_solution, to_fix);
27302726
}
27312727

27322728
bool RelaxationInducedNeighborhoodGenerator::ReadyToGenerate() const {

0 commit comments

Comments
 (0)