Skip to content

Commit 932f686

Browse files
committed
[CP-SAT] polish new cumulative cuts code; more experimental code on routing cuts; cleaning, fixes
1 parent 281a9f8 commit 932f686

18 files changed

+1131
-474
lines changed

ortools/sat/BUILD.bazel

+3
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ cc_library(
430430
"//ortools/base",
431431
"//ortools/base:stl_util",
432432
"//ortools/util:strong_integers",
433+
"@com_google_absl//absl/algorithm:container",
433434
"@com_google_absl//absl/container:flat_hash_map",
434435
"@com_google_absl//absl/container:flat_hash_set",
435436
"@com_google_absl//absl/log:check",
@@ -1849,6 +1850,7 @@ cc_library(
18491850
":synchronization",
18501851
":util",
18511852
"//ortools/base",
1853+
"//ortools/base:mathutil",
18521854
"//ortools/base:stl_util",
18531855
"//ortools/base:strong_vector",
18541856
"//ortools/graph",
@@ -2610,6 +2612,7 @@ cc_library(
26102612
"@com_google_absl//absl/container:btree",
26112613
"@com_google_absl//absl/container:flat_hash_map",
26122614
"@com_google_absl//absl/container:flat_hash_set",
2615+
"@com_google_absl//absl/functional:any_invocable",
26132616
"@com_google_absl//absl/log",
26142617
"@com_google_absl//absl/log:check",
26152618
"@com_google_absl//absl/meta:type_traits",

ortools/sat/cp_model_search.cc

+38-11
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <utility>
2323
#include <vector>
2424

25+
#include "absl/algorithm/container.h"
2526
#include "absl/container/flat_hash_map.h"
2627
#include "absl/container/flat_hash_set.h"
2728
#include "absl/log/check.h"
@@ -741,20 +742,46 @@ absl::flat_hash_map<std::string, SatParameters> GetNamedParameters(
741742

742743
// Base parameters for LNS worker.
743744
{
744-
SatParameters new_params = base_params;
745-
new_params.set_stop_after_first_solution(false);
746-
new_params.set_cp_model_presolve(true);
745+
SatParameters lns_params = base_params;
746+
lns_params.set_stop_after_first_solution(false);
747+
lns_params.set_cp_model_presolve(true);
747748

748749
// We disable costly presolve/inprocessing.
749-
new_params.set_use_sat_inprocessing(false);
750-
new_params.set_cp_model_probing_level(0);
751-
new_params.set_symmetry_level(0);
752-
new_params.set_find_big_linear_overlap(false);
750+
lns_params.set_use_sat_inprocessing(false);
751+
lns_params.set_cp_model_probing_level(0);
752+
lns_params.set_symmetry_level(0);
753+
lns_params.set_find_big_linear_overlap(false);
754+
755+
lns_params.set_log_search_progress(false);
756+
lns_params.set_debug_crash_on_bad_hint(false); // Can happen in lns.
757+
lns_params.set_solution_pool_size(1); // Keep the best solution found.
758+
strategies["lns"] = lns_params;
759+
760+
// Note that we only do this for the derived parameters. The strategy "lns"
761+
// will be handled along with the other ones.
762+
auto it = absl::c_find_if(
763+
base_params.subsolver_params(),
764+
[](const SatParameters& params) { return params.name() == "lns"; });
765+
if (it != base_params.subsolver_params().end()) {
766+
lns_params.MergeFrom(*it);
767+
}
753768

754-
new_params.set_log_search_progress(false);
755-
new_params.set_debug_crash_on_bad_hint(false); // Can happen in lns.
756-
new_params.set_solution_pool_size(1); // Keep the best solution found.
757-
strategies["lns"] = new_params;
769+
SatParameters lns_params_base = lns_params;
770+
lns_params_base.set_linearization_level(0);
771+
lns_params_base.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
772+
strategies["lns_base"] = lns_params_base;
773+
774+
SatParameters lns_params_stalling = lns_params;
775+
lns_params_stalling.set_search_branching(SatParameters::PORTFOLIO_SEARCH);
776+
lns_params_stalling.set_search_random_variable_pool_size(5);
777+
strategies["lns_stalling"] = lns_params_stalling;
778+
779+
// For routing, the LP relaxation seems pretty important, so we prefer an
780+
// high linearization level to solve LNS subproblems.
781+
SatParameters lns_params_routing = lns_params;
782+
lns_params_routing.set_linearization_level(2);
783+
lns_params_routing.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
784+
strategies["lns_routing"] = lns_params_routing;
758785
}
759786

760787
// Add user defined ones.

ortools/sat/cp_model_solver.cc

+41-44
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <optional>
2626
#include <random>
2727
#include <string>
28+
#include <string_view>
2829
#include <thread>
2930
#include <utility>
3031
#include <vector>
@@ -46,6 +47,7 @@
4647
#include "absl/strings/str_format.h"
4748
#include "absl/strings/str_join.h"
4849
#include "absl/strings/string_view.h"
50+
#include "absl/strings/strip.h"
4951
#include "absl/synchronization/mutex.h"
5052
#include "absl/types/span.h"
5153
#include "google/protobuf/arena.h"
@@ -1293,14 +1295,14 @@ class FeasibilityPumpSolver : public SubSolver {
12931295
class LnsSolver : public SubSolver {
12941296
public:
12951297
LnsSolver(std::unique_ptr<NeighborhoodGenerator> generator,
1296-
const SatParameters& lns_parameters,
1297-
NeighborhoodGeneratorHelper* helper, SharedClasses* shared,
1298-
int preferred_linearization_level = 0)
1298+
const SatParameters& lns_parameters_base,
1299+
const SatParameters& lns_parameters_stalling,
1300+
NeighborhoodGeneratorHelper* helper, SharedClasses* shared)
12991301
: SubSolver(generator->name(), INCOMPLETE),
1300-
preferred_linearization_level_(preferred_linearization_level),
13011302
generator_(std::move(generator)),
13021303
helper_(helper),
1303-
lns_parameters_(lns_parameters),
1304+
lns_parameters_base_(lns_parameters_base),
1305+
lns_parameters_stalling_(lns_parameters_stalling),
13041306
shared_(shared) {}
13051307

13061308
~LnsSolver() override {
@@ -1328,7 +1330,7 @@ class LnsSolver : public SubSolver {
13281330
// change the LNS behavior.
13291331
const int32_t low = static_cast<int32_t>(task_id);
13301332
const int32_t high = static_cast<int32_t>(task_id >> 32);
1331-
std::seed_seq seed{low, high, lns_parameters_.random_seed()};
1333+
std::seed_seq seed{low, high, lns_parameters_base_.random_seed()};
13321334
random_engine_t random(seed);
13331335

13341336
NeighborhoodGenerator::SolveData data;
@@ -1372,26 +1374,22 @@ class LnsSolver : public SubSolver {
13721374

13731375
if (!neighborhood.is_generated) return;
13741376

1375-
SatParameters local_params(lns_parameters_);
1376-
local_params.set_max_deterministic_time(data.deterministic_limit);
1377+
SatParameters local_params;
13771378

1378-
// TODO(user): Tune these.
13791379
// TODO(user): This could be a good candidate for bandits.
13801380
const int64_t stall = generator_->num_consecutive_non_improving_calls();
1381-
std::string search_info;
13821381
const int search_index = stall < 10 ? 0 : task_id % 2;
13831382
switch (search_index) {
13841383
case 0:
1385-
local_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
1386-
local_params.set_linearization_level(preferred_linearization_level_);
1387-
search_info = absl::StrCat("auto_l", preferred_linearization_level_);
1384+
local_params = lns_parameters_base_;
13881385
break;
13891386
default:
1390-
local_params.set_search_branching(SatParameters::PORTFOLIO_SEARCH);
1391-
local_params.set_search_random_variable_pool_size(5);
1392-
search_info = "folio_rnd";
1387+
local_params = lns_parameters_stalling_;
13931388
break;
13941389
}
1390+
const std::string_view search_info =
1391+
absl::StripPrefix(std::string_view(local_params.name()), "lns_");
1392+
local_params.set_max_deterministic_time(data.deterministic_limit);
13951393

13961394
std::string source_info =
13971395
neighborhood.source_info.empty() ? name() : neighborhood.source_info;
@@ -1486,7 +1484,7 @@ class LnsSolver : public SubSolver {
14861484
}
14871485
}
14881486
bool hint_feasible_before_presolve = false;
1489-
if (lns_parameters_.debug_crash_if_presolve_breaks_hint()) {
1487+
if (lns_parameters_base_.debug_crash_if_presolve_breaks_hint()) {
14901488
hint_feasible_before_presolve =
14911489
SolutionHintIsCompleteAndFeasible(lns_fragment, /*logger=*/nullptr);
14921490
}
@@ -1525,7 +1523,7 @@ class LnsSolver : public SubSolver {
15251523
context.reset(nullptr);
15261524
neighborhood.delta.Clear();
15271525

1528-
if (lns_parameters_.debug_crash_if_presolve_breaks_hint() &&
1526+
if (lns_parameters_base_.debug_crash_if_presolve_breaks_hint() &&
15291527
hint_feasible_before_presolve &&
15301528
!SolutionHintIsCompleteAndFeasible(lns_fragment,
15311529
/*logger=*/nullptr)) {
@@ -1720,10 +1718,10 @@ class LnsSolver : public SubSolver {
17201718
}
17211719

17221720
private:
1723-
int preferred_linearization_level_ = 0;
17241721
std::unique_ptr<NeighborhoodGenerator> generator_;
17251722
NeighborhoodGeneratorHelper* helper_;
1726-
const SatParameters lns_parameters_;
1723+
const SatParameters lns_parameters_base_;
1724+
const SatParameters lns_parameters_stalling_;
17271725
SharedClasses* shared_;
17281726
// This is a optimization to allocate the arena for the LNS fragment already
17291727
// at roughly the right size. We will update it with the last size of the
@@ -1780,7 +1778,9 @@ void SolveCpModelParallel(SharedClasses* shared, Model* global_model) {
17801778
}));
17811779

17821780
const auto name_to_params = GetNamedParameters(params);
1783-
const SatParameters& lns_params = name_to_params.at("lns");
1781+
const SatParameters& lns_params_base = name_to_params.at("lns_base");
1782+
const SatParameters& lns_params_stalling = name_to_params.at("lns_stalling");
1783+
const SatParameters& lns_params_routing = name_to_params.at("lns_routing");
17841784

17851785
// Add the NeighborhoodGeneratorHelper as a special subsolver so that its
17861786
// Synchronize() is called before any LNS neighborhood solvers.
@@ -1854,7 +1854,7 @@ void SolveCpModelParallel(SharedClasses* shared, Model* global_model) {
18541854
std::make_unique<RelaxationInducedNeighborhoodGenerator>(
18551855
helper, shared->response, shared->lp_solutions.get(),
18561856
shared->incomplete_solutions.get(), name_filter.LastName()),
1857-
lns_params, helper, shared));
1857+
lns_params_base, lns_params_stalling, helper, shared));
18581858
}
18591859

18601860
// Add incomplete subsolvers that require an objective.
@@ -1870,45 +1870,45 @@ void SolveCpModelParallel(SharedClasses* shared, Model* global_model) {
18701870
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
18711871
std::make_unique<RelaxRandomVariablesGenerator>(
18721872
helper, name_filter.LastName()),
1873-
lns_params, helper, shared));
1873+
lns_params_base, lns_params_stalling, helper, shared));
18741874
}
18751875
if (name_filter.Keep("rnd_cst_lns")) {
18761876
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
18771877
std::make_unique<RelaxRandomConstraintsGenerator>(
18781878
helper, name_filter.LastName()),
1879-
lns_params, helper, shared));
1879+
lns_params_base, lns_params_stalling, helper, shared));
18801880
}
18811881
if (name_filter.Keep("graph_var_lns")) {
18821882
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
18831883
std::make_unique<VariableGraphNeighborhoodGenerator>(
18841884
helper, name_filter.LastName()),
1885-
lns_params, helper, shared));
1885+
lns_params_base, lns_params_stalling, helper, shared));
18861886
}
18871887
if (name_filter.Keep("graph_arc_lns")) {
18881888
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
18891889
std::make_unique<ArcGraphNeighborhoodGenerator>(
18901890
helper, name_filter.LastName()),
1891-
lns_params, helper, shared));
1891+
lns_params_base, lns_params_stalling, helper, shared));
18921892
}
18931893
if (name_filter.Keep("graph_cst_lns")) {
18941894
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
18951895
std::make_unique<ConstraintGraphNeighborhoodGenerator>(
18961896
helper, name_filter.LastName()),
1897-
lns_params, helper, shared));
1897+
lns_params_base, lns_params_stalling, helper, shared));
18981898
}
18991899
if (name_filter.Keep("graph_dec_lns")) {
19001900
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
19011901
std::make_unique<DecompositionGraphNeighborhoodGenerator>(
19021902
helper, name_filter.LastName()),
1903-
lns_params, helper, shared));
1903+
lns_params_base, lns_params_stalling, helper, shared));
19041904
}
19051905
if (params.use_lb_relax_lns() &&
19061906
params.num_workers() >= params.lb_relax_num_workers_threshold() &&
19071907
name_filter.Keep("lb_relax_lns")) {
19081908
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
19091909
std::make_unique<LocalBranchingLpBasedNeighborhoodGenerator>(
19101910
helper, name_filter.LastName(), shared->time_limit, shared),
1911-
lns_params, helper, shared));
1911+
lns_params_base, lns_params_stalling, helper, shared));
19121912
}
19131913

19141914
const bool has_no_overlap_or_cumulative =
@@ -1921,13 +1921,13 @@ void SolveCpModelParallel(SharedClasses* shared, Model* global_model) {
19211921
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
19221922
std::make_unique<RandomIntervalSchedulingNeighborhoodGenerator>(
19231923
helper, name_filter.LastName()),
1924-
lns_params, helper, shared));
1924+
lns_params_base, lns_params_stalling, helper, shared));
19251925
}
19261926
if (name_filter.Keep("scheduling_time_window_lns")) {
19271927
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
19281928
std::make_unique<SchedulingTimeWindowNeighborhoodGenerator>(
19291929
helper, name_filter.LastName()),
1930-
lns_params, helper, shared));
1930+
lns_params_base, lns_params_stalling, helper, shared));
19311931
}
19321932
const std::vector<std::vector<int>> intervals_in_constraints =
19331933
helper->GetUniqueIntervalSets();
@@ -1936,7 +1936,7 @@ void SolveCpModelParallel(SharedClasses* shared, Model* global_model) {
19361936
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
19371937
std::make_unique<SchedulingResourceWindowsNeighborhoodGenerator>(
19381938
helper, intervals_in_constraints, name_filter.LastName()),
1939-
lns_params, helper, shared));
1939+
lns_params_base, lns_params_stalling, helper, shared));
19401940
}
19411941
}
19421942

@@ -1948,31 +1948,31 @@ void SolveCpModelParallel(SharedClasses* shared, Model* global_model) {
19481948
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
19491949
std::make_unique<RandomRectanglesPackingNeighborhoodGenerator>(
19501950
helper, name_filter.LastName()),
1951-
lns_params, helper, shared));
1951+
lns_params_base, lns_params_stalling, helper, shared));
19521952
}
19531953
if (name_filter.Keep("packing_square_lns")) {
19541954
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
19551955
std::make_unique<RectanglesPackingRelaxOneNeighborhoodGenerator>(
19561956
helper, name_filter.LastName()),
1957-
lns_params, helper, shared));
1957+
lns_params_base, lns_params_stalling, helper, shared));
19581958
}
19591959
if (name_filter.Keep("packing_swap_lns")) {
19601960
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
19611961
std::make_unique<RectanglesPackingRelaxTwoNeighborhoodsGenerator>(
19621962
helper, name_filter.LastName()),
1963-
lns_params, helper, shared));
1963+
lns_params_base, lns_params_stalling, helper, shared));
19641964
}
19651965
if (name_filter.Keep("packing_precedences_lns")) {
19661966
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
19671967
std::make_unique<RandomPrecedencesPackingNeighborhoodGenerator>(
19681968
helper, name_filter.LastName()),
1969-
lns_params, helper, shared));
1969+
lns_params_base, lns_params_stalling, helper, shared));
19701970
}
19711971
if (name_filter.Keep("packing_slice_lns")) {
19721972
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
19731973
std::make_unique<SlicePackingNeighborhoodGenerator>(
19741974
helper, name_filter.LastName()),
1975-
lns_params, helper, shared));
1975+
lns_params_base, lns_params_stalling, helper, shared));
19761976
}
19771977
}
19781978

@@ -1982,13 +1982,10 @@ void SolveCpModelParallel(SharedClasses* shared, Model* global_model) {
19821982
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
19831983
std::make_unique<RandomPrecedenceSchedulingNeighborhoodGenerator>(
19841984
helper, name_filter.LastName()),
1985-
lns_params, helper, shared));
1985+
lns_params_base, lns_params_stalling, helper, shared));
19861986
}
19871987
}
19881988

1989-
// For routing, the LP relaxation seems pretty important, so we prefer an
1990-
// high linearization level to solve LNS subproblems.
1991-
const int routing_lin_level = 2;
19921989
const int num_circuit = static_cast<int>(
19931990
helper->TypeToConstraints(ConstraintProto::kCircuit).size());
19941991
const int num_routes = static_cast<int>(
@@ -1998,21 +1995,21 @@ void SolveCpModelParallel(SharedClasses* shared, Model* global_model) {
19981995
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
19991996
std::make_unique<RoutingRandomNeighborhoodGenerator>(
20001997
helper, name_filter.LastName()),
2001-
lns_params, helper, shared, routing_lin_level));
1998+
lns_params_routing, lns_params_stalling, helper, shared));
20021999
}
20032000
if (name_filter.Keep("routing_path_lns")) {
20042001
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
20052002
std::make_unique<RoutingPathNeighborhoodGenerator>(
20062003
helper, name_filter.LastName()),
2007-
lns_params, helper, shared, routing_lin_level));
2004+
lns_params_routing, lns_params_stalling, helper, shared));
20082005
}
20092006
}
20102007
if (num_routes > 0 || num_circuit > 1) {
20112008
if (name_filter.Keep("routing_full_path_lns")) {
20122009
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
20132010
std::make_unique<RoutingFullPathNeighborhoodGenerator>(
20142011
helper, name_filter.LastName()),
2015-
lns_params, helper, shared, routing_lin_level));
2012+
lns_params_routing, lns_params_stalling, helper, shared));
20162013
}
20172014
}
20182015
}

ortools/sat/cuts.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@
2929
#include "absl/container/btree_set.h"
3030
#include "absl/container/flat_hash_map.h"
3131
#include "absl/container/flat_hash_set.h"
32+
#include "absl/functional/any_invocable.h"
3233
#include "absl/numeric/int128.h"
3334
#include "absl/strings/str_cat.h"
3435
#include "absl/types/span.h"
3536
#include "ortools/base/strong_vector.h"
36-
#include "ortools/lp_data/lp_types.h"
3737
#include "ortools/sat/implied_bounds.h"
3838
#include "ortools/sat/integer.h"
3939
#include "ortools/sat/integer_base.h"
@@ -56,7 +56,7 @@ namespace sat {
5656
struct CutGenerator {
5757
bool only_run_at_level_zero = false;
5858
std::vector<IntegerVariable> vars;
59-
std::function<bool(LinearConstraintManager* manager)> generate_cuts;
59+
absl::AnyInvocable<bool(LinearConstraintManager* manager)> generate_cuts;
6060
};
6161

6262
// To simplify cut generation code, we use a more complex data structure than

0 commit comments

Comments
 (0)