Skip to content

Commit 5c1f352

Browse files
committed
[CP-SAT] experimental routing cuts; new cumulative cuts; improve no_overlap_2d code all around
1 parent 16687e6 commit 5c1f352

29 files changed

+1233
-253
lines changed

ortools/sat/BUILD.bazel

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1790,7 +1790,6 @@ cc_library(
17901790
"//ortools/util:sort",
17911791
"//ortools/util:strong_integers",
17921792
"@com_google_absl//absl/base:core_headers",
1793-
"@com_google_absl//absl/container:flat_hash_map",
17941793
"@com_google_absl//absl/log:check",
17951794
"@com_google_absl//absl/meta:type_traits",
17961795
"@com_google_absl//absl/strings",
@@ -2368,12 +2367,14 @@ cc_library(
23682367
":cp_model_utils",
23692368
":cuts",
23702369
":diffn_cuts",
2370+
":diffn_util",
23712371
":implied_bounds",
23722372
":integer",
23732373
":integer_base",
23742374
":integer_expr",
23752375
":intervals",
23762376
":linear_constraint",
2377+
":linear_constraint_manager",
23772378
":model",
23782379
":no_overlap_2d_helper",
23792380
":precedences",
@@ -2653,16 +2654,19 @@ cc_library(
26532654
":linear_constraint",
26542655
":linear_constraint_manager",
26552656
":model",
2657+
":precedences",
26562658
":sat_base",
26572659
"//ortools/base",
26582660
"//ortools/base:mathutil",
26592661
"//ortools/base:strong_vector",
2660-
"//ortools/graph",
26612662
"//ortools/graph:max_flow",
26622663
"//ortools/util:strong_integers",
2664+
"@com_google_absl//absl/algorithm:container",
26632665
"@com_google_absl//absl/cleanup",
2666+
"@com_google_absl//absl/container:flat_hash_map",
26642667
"@com_google_absl//absl/container:flat_hash_set",
26652668
"@com_google_absl//absl/log:check",
2669+
"@com_google_absl//absl/random:distributions",
26662670
"@com_google_absl//absl/strings",
26672671
"@com_google_absl//absl/types:span",
26682672
],
@@ -2678,12 +2682,14 @@ cc_test(
26782682
":linear_constraint",
26792683
":linear_constraint_manager",
26802684
":model",
2685+
":precedences",
26812686
":routing_cuts",
26822687
":sat_base",
26832688
"//ortools/base:gmock_main",
26842689
"//ortools/base:strong_vector",
26852690
"//ortools/graph:max_flow",
26862691
"//ortools/util:strong_integers",
2692+
"@com_google_absl//absl/container:flat_hash_map",
26872693
"@com_google_absl//absl/log",
26882694
"@com_google_absl//absl/random",
26892695
"@com_google_absl//absl/random:distributions",
@@ -2704,6 +2710,7 @@ cc_library(
27042710
":linear_constraint_manager",
27052711
":model",
27062712
":sat_base",
2713+
":scheduling_helpers",
27072714
":util",
27082715
"//ortools/base",
27092716
"//ortools/base:stl_util",
@@ -3094,6 +3101,7 @@ cc_library(
30943101
"//ortools/graph:strongly_connected_components",
30953102
"//ortools/util:fixed_shape_binary_tree",
30963103
"//ortools/util:integer_pq",
3104+
"//ortools/util:saturated_arithmetic",
30973105
"//ortools/util:strong_integers",
30983106
"@com_google_absl//absl/algorithm:container",
30993107
"@com_google_absl//absl/container:btree",

ortools/sat/cp_model_expand.cc

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -892,7 +892,9 @@ void LinkLiteralsAndValues(const std::vector<int>& literals,
892892
for (const auto& [encoding_lit, support] : encoding_lit_to_support) {
893893
CHECK(!support.empty());
894894
if (support.size() == 1) {
895-
context->StoreBooleanEqualityRelation(encoding_lit, support[0]);
895+
if (!context->StoreBooleanEqualityRelation(encoding_lit, support[0])) {
896+
return;
897+
}
896898
} else {
897899
BoolArgumentProto* bool_or =
898900
context->working_model->add_constraints()->mutable_bool_or();
@@ -979,6 +981,7 @@ void ExpandAutomaton(ConstraintProto* ct, PresolveContext* context) {
979981
std::vector<int64_t> in_states;
980982
std::vector<int64_t> labels;
981983
std::vector<int64_t> out_states;
984+
absl::flat_hash_set<int64_t> still_reachable_after_domain_change;
982985
for (int i = 0; i < proto.transition_label_size(); ++i) {
983986
const int64_t tail = proto.transition_tail(i);
984987
const int64_t label = proto.transition_label(i);
@@ -988,6 +991,7 @@ void ExpandAutomaton(ConstraintProto* ct, PresolveContext* context) {
988991
if (!reachable_states[time + 1].contains(head)) continue;
989992
if (!context->DomainContains(proto.exprs(time), label)) continue;
990993

994+
still_reachable_after_domain_change.insert(head);
991995
// TODO(user): if this transition correspond to just one in-state or
992996
// one-out state or one variable value, we could reuse the corresponding
993997
// Boolean variable instead of creating a new one!
@@ -999,6 +1003,8 @@ void ExpandAutomaton(ConstraintProto* ct, PresolveContext* context) {
9991003
out_states.push_back(time + 1 == n ? 0 : head);
10001004
}
10011005

1006+
reachable_states[time + 1] = still_reachable_after_domain_change;
1007+
10021008
// Deal with single tuple.
10031009
const int num_tuples = in_states.size();
10041010
if (num_tuples == 1) {
@@ -1013,10 +1019,9 @@ void ExpandAutomaton(ConstraintProto* ct, PresolveContext* context) {
10131019
// at false.
10141020
std::vector<int> at_false;
10151021
for (const auto [value, literal] : in_encoding) {
1016-
if (value != in_states[0]) at_false.push_back(literal);
1017-
}
1018-
for (const int literal : at_false) {
1019-
if (!context->SetLiteralToFalse(literal)) return;
1022+
if (value != in_states[0]) {
1023+
if (!context->SetLiteralToFalse(literal)) return;
1024+
}
10201025
}
10211026

10221027
in_encoding.clear();

ortools/sat/cp_model_lns.cc

Lines changed: 68 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -951,22 +951,23 @@ NeighborhoodGeneratorHelper::GetSchedulingPrecedences(
951951
}
952952

953953
std::vector<std::vector<int>>
954-
NeighborhoodGeneratorHelper::GetRoutingPathLiterals(
954+
NeighborhoodGeneratorHelper::GetRoutingPathBooleanVariables(
955955
const CpSolverResponse& initial_solution) const {
956-
struct HeadAndArcLiteral {
956+
struct HeadAndArcBooleanVariable {
957957
int head;
958-
int literal;
958+
int bool_var;
959959
};
960960

961961
std::vector<std::vector<int>> result;
962-
absl::flat_hash_map<int, HeadAndArcLiteral> tail_to_head_and_arc_literal;
962+
absl::flat_hash_map<int, HeadAndArcBooleanVariable>
963+
tail_to_head_and_arc_bool_var;
963964

964965
for (const int i : TypeToConstraints(ConstraintProto::kCircuit)) {
965966
const CircuitConstraintProto& ct = ModelProto().constraints(i).circuit();
966967

967968
// Collect arcs.
968969
int min_node = std::numeric_limits<int>::max();
969-
tail_to_head_and_arc_literal.clear();
970+
tail_to_head_and_arc_bool_var.clear();
970971
for (int i = 0; i < ct.literals_size(); ++i) {
971972
const int literal = ct.literals(i);
972973
const int head = ct.heads(i);
@@ -977,27 +978,27 @@ NeighborhoodGeneratorHelper::GetRoutingPathLiterals(
977978
if (RefIsPositive(literal) == (value == 0)) continue;
978979
// Ignore self loops.
979980
if (head == tail) continue;
980-
tail_to_head_and_arc_literal[tail] = {head, bool_var};
981+
tail_to_head_and_arc_bool_var[tail] = {head, bool_var};
981982
min_node = std::min(tail, min_node);
982983
}
983-
if (tail_to_head_and_arc_literal.empty()) continue;
984+
if (tail_to_head_and_arc_bool_var.empty()) continue;
984985

985986
// Unroll the path.
986987
int current_node = min_node;
987988
std::vector<int> path;
988989
do {
989-
auto it = tail_to_head_and_arc_literal.find(current_node);
990-
CHECK(it != tail_to_head_and_arc_literal.end());
990+
auto it = tail_to_head_and_arc_bool_var.find(current_node);
991+
CHECK(it != tail_to_head_and_arc_bool_var.end());
991992
current_node = it->second.head;
992-
path.push_back(it->second.literal);
993+
path.push_back(it->second.bool_var);
993994
} while (current_node != min_node);
994995
result.push_back(std::move(path));
995996
}
996997

997-
std::vector<HeadAndArcLiteral> route_starts;
998+
std::vector<HeadAndArcBooleanVariable> route_starts;
998999
for (const int i : TypeToConstraints(ConstraintProto::kRoutes)) {
9991000
const RoutesConstraintProto& ct = ModelProto().constraints(i).routes();
1000-
tail_to_head_and_arc_literal.clear();
1001+
tail_to_head_and_arc_bool_var.clear();
10011002
route_starts.clear();
10021003

10031004
// Collect route starts and arcs.
@@ -1014,20 +1015,20 @@ NeighborhoodGeneratorHelper::GetRoutingPathLiterals(
10141015
if (tail == 0) {
10151016
route_starts.push_back({head, bool_var});
10161017
} else {
1017-
tail_to_head_and_arc_literal[tail] = {head, bool_var};
1018+
tail_to_head_and_arc_bool_var[tail] = {head, bool_var};
10181019
}
10191020
}
10201021

10211022
// Unroll all routes.
1022-
for (const HeadAndArcLiteral& head_var : route_starts) {
1023+
for (const HeadAndArcBooleanVariable& head_var : route_starts) {
10231024
std::vector<int> path;
10241025
int current_node = head_var.head;
1025-
path.push_back(head_var.literal);
1026+
path.push_back(head_var.bool_var);
10261027
do {
1027-
auto it = tail_to_head_and_arc_literal.find(current_node);
1028-
CHECK(it != tail_to_head_and_arc_literal.end());
1028+
auto it = tail_to_head_and_arc_bool_var.find(current_node);
1029+
CHECK(it != tail_to_head_and_arc_bool_var.end());
10291030
current_node = it->second.head;
1030-
path.push_back(it->second.literal);
1031+
path.push_back(it->second.bool_var);
10311032
} while (current_node != 0);
10321033
result.push_back(std::move(path));
10331034
}
@@ -2598,39 +2599,50 @@ Neighborhood RoutingRandomNeighborhoodGenerator::Generate(
25982599
const CpSolverResponse& initial_solution, SolveData& data,
25992600
absl::BitGenRef random) {
26002601
const std::vector<std::vector<int>> all_paths =
2601-
helper_.GetRoutingPathLiterals(initial_solution);
2602+
helper_.GetRoutingPathBooleanVariables(initial_solution);
26022603

26032604
// Collect all unique variables.
2604-
absl::flat_hash_set<int> all_path_variables;
2605-
for (auto& path : all_paths) {
2606-
all_path_variables.insert(path.begin(), path.end());
2605+
std::vector<int> variables_to_fix;
2606+
for (const auto& path : all_paths) {
2607+
variables_to_fix.insert(variables_to_fix.end(), path.begin(), path.end());
26072608
}
2608-
std::vector<int> fixed_variables(all_path_variables.begin(),
2609-
all_path_variables.end());
2610-
std::sort(fixed_variables.begin(), fixed_variables.end());
2611-
GetRandomSubset(1.0 - data.difficulty, &fixed_variables, random);
2609+
gtl::STLSortAndRemoveDuplicates(&variables_to_fix);
2610+
GetRandomSubset(1.0 - data.difficulty, &variables_to_fix, random);
26122611

26132612
Bitset64<int> to_fix(helper_.NumVariables());
2614-
for (const int var : fixed_variables) to_fix.Set(var);
2613+
for (const int var : variables_to_fix) to_fix.Set(var);
26152614
return helper_.FixGivenVariables(initial_solution, to_fix);
26162615
}
26172616

26182617
Neighborhood RoutingPathNeighborhoodGenerator::Generate(
26192618
const CpSolverResponse& initial_solution, SolveData& data,
26202619
absl::BitGenRef random) {
26212620
std::vector<std::vector<int>> all_paths =
2622-
helper_.GetRoutingPathLiterals(initial_solution);
2621+
helper_.GetRoutingPathBooleanVariables(initial_solution);
2622+
2623+
// Remove a corner case where all paths are empty.
2624+
if (all_paths.empty()) {
2625+
return helper_.NoNeighborhood();
2626+
}
26232627

26242628
// Collect all unique variables.
2625-
absl::flat_hash_set<int> all_path_variables;
2629+
std::vector<int> all_path_variables;
2630+
int sum_of_path_sizes = 0;
2631+
for (const auto& path : all_paths) {
2632+
sum_of_path_sizes += path.size();
2633+
}
2634+
all_path_variables.reserve(sum_of_path_sizes);
26262635
for (const auto& path : all_paths) {
2627-
all_path_variables.insert(path.begin(), path.end());
2636+
all_path_variables.insert(all_path_variables.end(), path.begin(),
2637+
path.end());
26282638
}
2639+
gtl::STLSortAndRemoveDuplicates(&all_path_variables);
26292640

2630-
// Select variables to relax.
2641+
// Select target number of variables to relax.
26312642
const int num_variables_to_relax =
26322643
static_cast<int>(all_path_variables.size() * data.difficulty);
26332644
absl::flat_hash_set<int> relaxed_variables;
2645+
26342646
while (relaxed_variables.size() < num_variables_to_relax) {
26352647
DCHECK(!all_paths.empty());
26362648
const int path_index = absl::Uniform<int>(random, 0, all_paths.size());
@@ -2665,57 +2677,54 @@ Neighborhood RoutingFullPathNeighborhoodGenerator::Generate(
26652677
const CpSolverResponse& initial_solution, SolveData& data,
26662678
absl::BitGenRef random) {
26672679
std::vector<std::vector<int>> all_paths =
2668-
helper_.GetRoutingPathLiterals(initial_solution);
2680+
helper_.GetRoutingPathBooleanVariables(initial_solution);
2681+
26692682
// Remove a corner case where all paths are empty.
26702683
if (all_paths.empty()) {
26712684
return helper_.NoNeighborhood();
26722685
}
26732686

26742687
// Collect all unique variables.
2675-
absl::flat_hash_set<int> all_path_variables;
2688+
std::vector<int> all_path_variables;
2689+
int sum_of_path_sizes = 0;
26762690
for (const auto& path : all_paths) {
2677-
all_path_variables.insert(path.begin(), path.end());
2691+
sum_of_path_sizes += path.size();
26782692
}
2693+
all_path_variables.reserve(sum_of_path_sizes);
2694+
for (const auto& path : all_paths) {
2695+
all_path_variables.insert(all_path_variables.end(), path.begin(),
2696+
path.end());
2697+
}
2698+
gtl::STLSortAndRemoveDuplicates(&all_path_variables);
26792699

2680-
// Select variables to relax.
2700+
// Select target number of variables to relax.
26812701
const int num_variables_to_relax =
26822702
static_cast<int>(all_path_variables.size() * data.difficulty);
26832703
absl::flat_hash_set<int> relaxed_variables;
26842704

26852705
// Relax the start and end of each path to ease relocation.
2706+
// TODO(user): Restrict this if the difficulty is very low.
26862707
for (const auto& path : all_paths) {
26872708
relaxed_variables.insert(path.front());
26882709
relaxed_variables.insert(path.back());
26892710
}
26902711

2691-
// Randomize paths.
2692-
for (auto& path : all_paths) {
2693-
std::shuffle(path.begin(), path.end(), random);
2694-
}
2695-
2696-
// Relax all variables (if possible) in one random path.
2697-
const int path_to_clean = absl::Uniform<int>(random, 0, all_paths.size());
2712+
// Relax all variables, if possible, of one random path.
2713+
const int path_index = absl::Uniform<int>(random, 0, all_paths.size());
2714+
std::shuffle(all_paths[path_index].begin(), all_paths[path_index].end(),
2715+
random);
26982716
while (relaxed_variables.size() < num_variables_to_relax &&
2699-
!all_paths[path_to_clean].empty()) {
2700-
relaxed_variables.insert(all_paths[path_to_clean].back());
2701-
all_paths[path_to_clean].pop_back();
2702-
}
2703-
if (all_paths[path_to_clean].empty()) {
2704-
std::swap(all_paths[path_to_clean], all_paths.back());
2705-
all_paths.pop_back();
2717+
!all_paths[path_index].empty()) {
2718+
relaxed_variables.insert(all_paths[path_index].back());
2719+
all_paths[path_index].pop_back();
27062720
}
27072721

27082722
// Relax more variables until the target is reached.
2709-
while (relaxed_variables.size() < num_variables_to_relax) {
2710-
DCHECK(!all_paths.empty());
2711-
const int path_index = absl::Uniform<int>(random, 0, all_paths.size());
2712-
relaxed_variables.insert(all_paths[path_index].back());
2713-
2714-
// Remove variable and clean up empty paths.
2715-
all_paths[path_index].pop_back();
2716-
if (all_paths[path_index].empty()) {
2717-
std::swap(all_paths[path_index], all_paths.back());
2718-
all_paths.pop_back();
2723+
if (relaxed_variables.size() < num_variables_to_relax) {
2724+
std::shuffle(all_path_variables.begin(), all_path_variables.end(), random);
2725+
while (relaxed_variables.size() < num_variables_to_relax) {
2726+
relaxed_variables.insert(all_path_variables.back());
2727+
all_path_variables.pop_back();
27192728
}
27202729
}
27212730

ortools/sat/cp_model_lns.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -247,12 +247,13 @@ class NeighborhoodGeneratorHelper : public SubSolver {
247247
// cumulative, or as a dimension of a no_overlap_2d constraint.
248248
std::vector<std::vector<int>> GetUniqueIntervalSets() const;
249249

250-
// Returns one sub-vector per circuit or per single vehicle circuit in a
250+
// Returns one sub-vector per circuit or per individual vehicle circuit in a
251251
// routes constraints. Each circuit is non empty, and does not contain any
252252
// self-looping arcs. Path are sorted, starting from the arc with the lowest
253253
// tail index, and going in sequence up to the last arc before the circuit is
254-
// closed. Each entry correspond to the arc literal on the circuit.
255-
std::vector<std::vector<int>> GetRoutingPathLiterals(
254+
// closed. Each entry correspond to the Boolean variable of the arc literal on
255+
// the circuit.
256+
std::vector<std::vector<int>> GetRoutingPathBooleanVariables(
256257
const CpSolverResponse& initial_solution) const;
257258

258259
// Returns all precedences extracted from the scheduling constraint and the
@@ -818,7 +819,7 @@ class RoutingPathNeighborhoodGenerator : public NeighborhoodGenerator {
818819
SolveData& data, absl::BitGenRef random) final;
819820
};
820821

821-
// This routing based LNS generator aims are relaxing one full path, and make
822+
// This routing based LNS generator aims at relaxing one full path, and make
822823
// some room on the other paths to absorb the nodes of the relaxed path.
823824
//
824825
// In order to do so, it will relax the first and the last arc of each path in

0 commit comments

Comments
 (0)