Skip to content

Commit

Permalink
[CP-SAT] polish new cumulative cuts code; more experimental code on r…
Browse files Browse the repository at this point in the history
…outing cuts; cleaning, fixes
  • Loading branch information
lperron committed Feb 7, 2025
1 parent 05c362a commit 8b4a24a
Show file tree
Hide file tree
Showing 18 changed files with 1,131 additions and 474 deletions.
3 changes: 3 additions & 0 deletions ortools/sat/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,7 @@ cc_library(
"//ortools/base",
"//ortools/base:stl_util",
"//ortools/util:strong_integers",
"@com_google_absl//absl/algorithm:container",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/log:check",
Expand Down Expand Up @@ -1849,6 +1850,7 @@ cc_library(
":synchronization",
":util",
"//ortools/base",
"//ortools/base:mathutil",
"//ortools/base:stl_util",
"//ortools/base:strong_vector",
"//ortools/graph",
Expand Down Expand Up @@ -2609,6 +2611,7 @@ cc_library(
"@com_google_absl//absl/container:btree",
"@com_google_absl//absl/container:flat_hash_map",
"@com_google_absl//absl/container:flat_hash_set",
"@com_google_absl//absl/functional:any_invocable",
"@com_google_absl//absl/log",
"@com_google_absl//absl/log:check",
"@com_google_absl//absl/meta:type_traits",
Expand Down
49 changes: 38 additions & 11 deletions ortools/sat/cp_model_search.cc
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <utility>
#include <vector>

#include "absl/algorithm/container.h"
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/log/check.h"
Expand Down Expand Up @@ -741,20 +742,46 @@ absl::flat_hash_map<std::string, SatParameters> GetNamedParameters(

// Base parameters for LNS worker.
{
SatParameters new_params = base_params;
new_params.set_stop_after_first_solution(false);
new_params.set_cp_model_presolve(true);
SatParameters lns_params = base_params;
lns_params.set_stop_after_first_solution(false);
lns_params.set_cp_model_presolve(true);

// We disable costly presolve/inprocessing.
new_params.set_use_sat_inprocessing(false);
new_params.set_cp_model_probing_level(0);
new_params.set_symmetry_level(0);
new_params.set_find_big_linear_overlap(false);
lns_params.set_use_sat_inprocessing(false);
lns_params.set_cp_model_probing_level(0);
lns_params.set_symmetry_level(0);
lns_params.set_find_big_linear_overlap(false);

lns_params.set_log_search_progress(false);
lns_params.set_debug_crash_on_bad_hint(false); // Can happen in lns.
lns_params.set_solution_pool_size(1); // Keep the best solution found.
strategies["lns"] = lns_params;

// Note that we only do this for the derived parameters. The strategy "lns"
// will be handled along with the other ones.
auto it = absl::c_find_if(
base_params.subsolver_params(),
[](const SatParameters& params) { return params.name() == "lns"; });
if (it != base_params.subsolver_params().end()) {
lns_params.MergeFrom(*it);
}

new_params.set_log_search_progress(false);
new_params.set_debug_crash_on_bad_hint(false); // Can happen in lns.
new_params.set_solution_pool_size(1); // Keep the best solution found.
strategies["lns"] = new_params;
SatParameters lns_params_base = lns_params;
lns_params_base.set_linearization_level(0);
lns_params_base.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
strategies["lns_base"] = lns_params_base;

SatParameters lns_params_stalling = lns_params;
lns_params_stalling.set_search_branching(SatParameters::PORTFOLIO_SEARCH);
lns_params_stalling.set_search_random_variable_pool_size(5);
strategies["lns_stalling"] = lns_params_stalling;

// For routing, the LP relaxation seems pretty important, so we prefer an
// high linearization level to solve LNS subproblems.
SatParameters lns_params_routing = lns_params;
lns_params_routing.set_linearization_level(2);
lns_params_routing.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
strategies["lns_routing"] = lns_params_routing;
}

// Add user defined ones.
Expand Down
85 changes: 41 additions & 44 deletions ortools/sat/cp_model_solver.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <optional>
#include <random>
#include <string>
#include <string_view>
#include <thread>
#include <utility>
#include <vector>
Expand All @@ -46,6 +47,7 @@
#include "absl/strings/str_format.h"
#include "absl/strings/str_join.h"
#include "absl/strings/string_view.h"
#include "absl/strings/strip.h"
#include "absl/synchronization/mutex.h"
#include "absl/types/span.h"
#include "google/protobuf/arena.h"
Expand Down Expand Up @@ -1293,14 +1295,14 @@ class FeasibilityPumpSolver : public SubSolver {
class LnsSolver : public SubSolver {
public:
LnsSolver(std::unique_ptr<NeighborhoodGenerator> generator,
const SatParameters& lns_parameters,
NeighborhoodGeneratorHelper* helper, SharedClasses* shared,
int preferred_linearization_level = 0)
const SatParameters& lns_parameters_base,
const SatParameters& lns_parameters_stalling,
NeighborhoodGeneratorHelper* helper, SharedClasses* shared)
: SubSolver(generator->name(), INCOMPLETE),
preferred_linearization_level_(preferred_linearization_level),
generator_(std::move(generator)),
helper_(helper),
lns_parameters_(lns_parameters),
lns_parameters_base_(lns_parameters_base),
lns_parameters_stalling_(lns_parameters_stalling),
shared_(shared) {}

~LnsSolver() override {
Expand Down Expand Up @@ -1328,7 +1330,7 @@ class LnsSolver : public SubSolver {
// change the LNS behavior.
const int32_t low = static_cast<int32_t>(task_id);
const int32_t high = static_cast<int32_t>(task_id >> 32);
std::seed_seq seed{low, high, lns_parameters_.random_seed()};
std::seed_seq seed{low, high, lns_parameters_base_.random_seed()};
random_engine_t random(seed);

NeighborhoodGenerator::SolveData data;
Expand Down Expand Up @@ -1372,26 +1374,22 @@ class LnsSolver : public SubSolver {

if (!neighborhood.is_generated) return;

SatParameters local_params(lns_parameters_);
local_params.set_max_deterministic_time(data.deterministic_limit);
SatParameters local_params;

// TODO(user): Tune these.
// TODO(user): This could be a good candidate for bandits.
const int64_t stall = generator_->num_consecutive_non_improving_calls();
std::string search_info;
const int search_index = stall < 10 ? 0 : task_id % 2;
switch (search_index) {
case 0:
local_params.set_search_branching(SatParameters::AUTOMATIC_SEARCH);
local_params.set_linearization_level(preferred_linearization_level_);
search_info = absl::StrCat("auto_l", preferred_linearization_level_);
local_params = lns_parameters_base_;
break;
default:
local_params.set_search_branching(SatParameters::PORTFOLIO_SEARCH);
local_params.set_search_random_variable_pool_size(5);
search_info = "folio_rnd";
local_params = lns_parameters_stalling_;
break;
}
const std::string_view search_info =
absl::StripPrefix(std::string_view(local_params.name()), "lns_");
local_params.set_max_deterministic_time(data.deterministic_limit);

std::string source_info =
neighborhood.source_info.empty() ? name() : neighborhood.source_info;
Expand Down Expand Up @@ -1486,7 +1484,7 @@ class LnsSolver : public SubSolver {
}
}
bool hint_feasible_before_presolve = false;
if (lns_parameters_.debug_crash_if_presolve_breaks_hint()) {
if (lns_parameters_base_.debug_crash_if_presolve_breaks_hint()) {
hint_feasible_before_presolve =
SolutionHintIsCompleteAndFeasible(lns_fragment, /*logger=*/nullptr);
}
Expand Down Expand Up @@ -1525,7 +1523,7 @@ class LnsSolver : public SubSolver {
context.reset(nullptr);
neighborhood.delta.Clear();

if (lns_parameters_.debug_crash_if_presolve_breaks_hint() &&
if (lns_parameters_base_.debug_crash_if_presolve_breaks_hint() &&
hint_feasible_before_presolve &&
!SolutionHintIsCompleteAndFeasible(lns_fragment,
/*logger=*/nullptr)) {
Expand Down Expand Up @@ -1720,10 +1718,10 @@ class LnsSolver : public SubSolver {
}

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

const auto name_to_params = GetNamedParameters(params);
const SatParameters& lns_params = name_to_params.at("lns");
const SatParameters& lns_params_base = name_to_params.at("lns_base");
const SatParameters& lns_params_stalling = name_to_params.at("lns_stalling");
const SatParameters& lns_params_routing = name_to_params.at("lns_routing");

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

// Add incomplete subsolvers that require an objective.
Expand All @@ -1870,45 +1870,45 @@ void SolveCpModelParallel(SharedClasses* shared, Model* global_model) {
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
std::make_unique<RelaxRandomVariablesGenerator>(
helper, name_filter.LastName()),
lns_params, helper, shared));
lns_params_base, lns_params_stalling, helper, shared));
}
if (name_filter.Keep("rnd_cst_lns")) {
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
std::make_unique<RelaxRandomConstraintsGenerator>(
helper, name_filter.LastName()),
lns_params, helper, shared));
lns_params_base, lns_params_stalling, helper, shared));
}
if (name_filter.Keep("graph_var_lns")) {
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
std::make_unique<VariableGraphNeighborhoodGenerator>(
helper, name_filter.LastName()),
lns_params, helper, shared));
lns_params_base, lns_params_stalling, helper, shared));
}
if (name_filter.Keep("graph_arc_lns")) {
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
std::make_unique<ArcGraphNeighborhoodGenerator>(
helper, name_filter.LastName()),
lns_params, helper, shared));
lns_params_base, lns_params_stalling, helper, shared));
}
if (name_filter.Keep("graph_cst_lns")) {
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
std::make_unique<ConstraintGraphNeighborhoodGenerator>(
helper, name_filter.LastName()),
lns_params, helper, shared));
lns_params_base, lns_params_stalling, helper, shared));
}
if (name_filter.Keep("graph_dec_lns")) {
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
std::make_unique<DecompositionGraphNeighborhoodGenerator>(
helper, name_filter.LastName()),
lns_params, helper, shared));
lns_params_base, lns_params_stalling, helper, shared));
}
if (params.use_lb_relax_lns() &&
params.num_workers() >= params.lb_relax_num_workers_threshold() &&
name_filter.Keep("lb_relax_lns")) {
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
std::make_unique<LocalBranchingLpBasedNeighborhoodGenerator>(
helper, name_filter.LastName(), shared->time_limit, shared),
lns_params, helper, shared));
lns_params_base, lns_params_stalling, helper, shared));
}

const bool has_no_overlap_or_cumulative =
Expand All @@ -1921,13 +1921,13 @@ void SolveCpModelParallel(SharedClasses* shared, Model* global_model) {
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
std::make_unique<RandomIntervalSchedulingNeighborhoodGenerator>(
helper, name_filter.LastName()),
lns_params, helper, shared));
lns_params_base, lns_params_stalling, helper, shared));
}
if (name_filter.Keep("scheduling_time_window_lns")) {
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
std::make_unique<SchedulingTimeWindowNeighborhoodGenerator>(
helper, name_filter.LastName()),
lns_params, helper, shared));
lns_params_base, lns_params_stalling, helper, shared));
}
const std::vector<std::vector<int>> intervals_in_constraints =
helper->GetUniqueIntervalSets();
Expand All @@ -1936,7 +1936,7 @@ void SolveCpModelParallel(SharedClasses* shared, Model* global_model) {
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
std::make_unique<SchedulingResourceWindowsNeighborhoodGenerator>(
helper, intervals_in_constraints, name_filter.LastName()),
lns_params, helper, shared));
lns_params_base, lns_params_stalling, helper, shared));
}
}

Expand All @@ -1948,31 +1948,31 @@ void SolveCpModelParallel(SharedClasses* shared, Model* global_model) {
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
std::make_unique<RandomRectanglesPackingNeighborhoodGenerator>(
helper, name_filter.LastName()),
lns_params, helper, shared));
lns_params_base, lns_params_stalling, helper, shared));
}
if (name_filter.Keep("packing_square_lns")) {
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
std::make_unique<RectanglesPackingRelaxOneNeighborhoodGenerator>(
helper, name_filter.LastName()),
lns_params, helper, shared));
lns_params_base, lns_params_stalling, helper, shared));
}
if (name_filter.Keep("packing_swap_lns")) {
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
std::make_unique<RectanglesPackingRelaxTwoNeighborhoodsGenerator>(
helper, name_filter.LastName()),
lns_params, helper, shared));
lns_params_base, lns_params_stalling, helper, shared));
}
if (name_filter.Keep("packing_precedences_lns")) {
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
std::make_unique<RandomPrecedencesPackingNeighborhoodGenerator>(
helper, name_filter.LastName()),
lns_params, helper, shared));
lns_params_base, lns_params_stalling, helper, shared));
}
if (name_filter.Keep("packing_slice_lns")) {
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
std::make_unique<SlicePackingNeighborhoodGenerator>(
helper, name_filter.LastName()),
lns_params, helper, shared));
lns_params_base, lns_params_stalling, helper, shared));
}
}

Expand All @@ -1982,13 +1982,10 @@ void SolveCpModelParallel(SharedClasses* shared, Model* global_model) {
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
std::make_unique<RandomPrecedenceSchedulingNeighborhoodGenerator>(
helper, name_filter.LastName()),
lns_params, helper, shared));
lns_params_base, lns_params_stalling, helper, shared));
}
}

// For routing, the LP relaxation seems pretty important, so we prefer an
// high linearization level to solve LNS subproblems.
const int routing_lin_level = 2;
const int num_circuit = static_cast<int>(
helper->TypeToConstraints(ConstraintProto::kCircuit).size());
const int num_routes = static_cast<int>(
Expand All @@ -1998,21 +1995,21 @@ void SolveCpModelParallel(SharedClasses* shared, Model* global_model) {
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
std::make_unique<RoutingRandomNeighborhoodGenerator>(
helper, name_filter.LastName()),
lns_params, helper, shared, routing_lin_level));
lns_params_routing, lns_params_stalling, helper, shared));
}
if (name_filter.Keep("routing_path_lns")) {
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
std::make_unique<RoutingPathNeighborhoodGenerator>(
helper, name_filter.LastName()),
lns_params, helper, shared, routing_lin_level));
lns_params_routing, lns_params_stalling, helper, shared));
}
}
if (num_routes > 0 || num_circuit > 1) {
if (name_filter.Keep("routing_full_path_lns")) {
reentrant_interleaved_subsolvers.push_back(std::make_unique<LnsSolver>(
std::make_unique<RoutingFullPathNeighborhoodGenerator>(
helper, name_filter.LastName()),
lns_params, helper, shared, routing_lin_level));
lns_params_routing, lns_params_stalling, helper, shared));
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions ortools/sat/cuts.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@
#include "absl/container/btree_set.h"
#include "absl/container/flat_hash_map.h"
#include "absl/container/flat_hash_set.h"
#include "absl/functional/any_invocable.h"
#include "absl/numeric/int128.h"
#include "absl/strings/str_cat.h"
#include "absl/types/span.h"
#include "ortools/base/strong_vector.h"
#include "ortools/lp_data/lp_types.h"
#include "ortools/sat/implied_bounds.h"
#include "ortools/sat/integer.h"
#include "ortools/sat/integer_base.h"
Expand All @@ -56,7 +56,7 @@ namespace sat {
struct CutGenerator {
bool only_run_at_level_zero = false;
std::vector<IntegerVariable> vars;
std::function<bool(LinearConstraintManager* manager)> generate_cuts;
absl::AnyInvocable<bool(LinearConstraintManager* manager)> generate_cuts;
};

// To simplify cut generation code, we use a more complex data structure than
Expand Down
Loading

0 comments on commit 8b4a24a

Please sign in to comment.