diff --git a/ortools/constraint_solver/routing.cc b/ortools/constraint_solver/routing.cc index f714f7708f..613e3aaee7 100644 --- a/ortools/constraint_solver/routing.cc +++ b/ortools/constraint_solver/routing.cc @@ -4509,11 +4509,15 @@ void RoutingModel::CreateNeighborhoodOperators( [this, ¶meters]() { using Heuristic = GlobalCheapestInsertionFilteredHeuristic; Heuristic::GlobalCheapestInsertionParameters ls_gci_parameters = { - /* is_sequential */ false, - /* farthest_seeds_ratio */ 0.0, - parameters.cheapest_insertion_ls_operator_neighbors_ratio(), - /* use_neighbors_ratio_for_initialization */ true, - parameters.cheapest_insertion_add_unperformed_entries()}; + .is_sequential = false, + .farthest_seeds_ratio = 0.0, + .neighbors_ratio = + parameters.cheapest_insertion_ls_operator_neighbors_ratio(), + .min_neighbors = + parameters.cheapest_insertion_ls_operator_min_neighbors(), + .use_neighbors_ratio_for_initialization = true, + .add_unperformed_entries = + parameters.cheapest_insertion_add_unperformed_entries()}; return absl::make_unique( this, absl::bind_front(&RoutingModel::GetArcCostForVehicle, this), absl::bind_front(&RoutingModel::UnperformedPenaltyOrValue, this, 0), @@ -5157,11 +5161,18 @@ void RoutingModel::CreateFirstSolutionDecisionBuilders( // Parallel/Sequential Global cheapest insertion GlobalCheapestInsertionFilteredHeuristic::GlobalCheapestInsertionParameters gci_parameters = { - /* is_sequential */ false, - search_parameters.cheapest_insertion_farthest_seeds_ratio(), - search_parameters.cheapest_insertion_first_solution_neighbors_ratio(), - /* use_neighbors_ratio_for_initialization */ false, - search_parameters.cheapest_insertion_add_unperformed_entries()}; + .is_sequential = false, + .farthest_seeds_ratio = + search_parameters.cheapest_insertion_farthest_seeds_ratio(), + .neighbors_ratio = + search_parameters + .cheapest_insertion_first_solution_neighbors_ratio(), + .min_neighbors = + search_parameters + .cheapest_insertion_first_solution_min_neighbors(), + .use_neighbors_ratio_for_initialization = false, + .add_unperformed_entries = + search_parameters.cheapest_insertion_add_unperformed_entries()}; for (bool is_sequential : {false, true}) { FirstSolutionStrategy::Value first_solution_strategy = is_sequential ? FirstSolutionStrategy::SEQUENTIAL_CHEAPEST_INSERTION diff --git a/ortools/constraint_solver/routing.h b/ortools/constraint_solver/routing.h index 4087adbb4e..0f66e01205 100644 --- a/ortools/constraint_solver/routing.h +++ b/ortools/constraint_solver/routing.h @@ -177,6 +177,7 @@ #include "ortools/base/hash.h" #include "ortools/base/logging.h" #include "ortools/base/macros.h" +#include "ortools/base/mathutil.h" #include "ortools/constraint_solver/constraint_solver.h" #include "ortools/constraint_solver/constraint_solveri.h" #include "ortools/constraint_solver/routing_enums.pb.h" @@ -3191,11 +3192,15 @@ class GlobalCheapestInsertionFilteredHeuristic /// starting the cheapest insertion. double farthest_seeds_ratio; /// If neighbors_ratio < 1 then for each node only this ratio of its - /// neighbors leading to the smallest arc costs are considered. + /// neighbors leading to the smallest arc costs are considered for + /// insertions, with a minimum of 'min_neighbors': + /// num_closest_neighbors = max(min_neighbors, neighbors_ratio*N), + /// where N is the number of non-start/end nodes in the model. double neighbors_ratio; - /// If true, only closest neighbors (see neighbors_ratio) are considered - /// as insertion positions during initialization. Otherwise, all possible - /// insertion positions are considered. + int64 min_neighbors; + /// If true, only closest neighbors (see neighbors_ratio and min_neighbors) + /// are considered as insertion positions during initialization. Otherwise, + /// all possible insertion positions are considered. bool use_neighbors_ratio_for_initialization; /// If true, entries are created for making the nodes/pairs unperformed, and /// when the cost of making a node unperformed is lower than all insertions, @@ -3450,6 +3455,12 @@ class GlobalCheapestInsertionFilteredHeuristic return model()->Size() - model()->vehicles(); } + int64 NumNeighbors() const { + return std::max(gci_params_.min_neighbors, + MathUtil::FastInt64Round(gci_params_.neighbors_ratio * + NumNonStartEndNodes())); + } + void ResetVehicleIndices() override { node_index_to_vehicle_.assign(node_index_to_vehicle_.size(), -1); } diff --git a/ortools/constraint_solver/routing_flags.cc b/ortools/constraint_solver/routing_flags.cc index afda2c2b55..2bd5f61cd6 100644 --- a/ortools/constraint_solver/routing_flags.cc +++ b/ortools/constraint_solver/routing_flags.cc @@ -173,6 +173,7 @@ void SetFirstSolutionStrategyFromFlags(RoutingSearchParameters* parameters) { absl::GetFlag(FLAGS_cheapest_insertion_farthest_seeds_ratio)); parameters->set_cheapest_insertion_first_solution_neighbors_ratio( absl::GetFlag(FLAGS_cheapest_insertion_first_solution_neighbors_ratio)); + parameters->set_cheapest_insertion_first_solution_min_neighbors(1); } void SetLocalSearchMetaheuristicFromFlags(RoutingSearchParameters* parameters) { @@ -202,6 +203,7 @@ void AddLocalSearchNeighborhoodOperatorsFromFlags( RoutingSearchParameters* parameters) { CHECK(parameters != nullptr); parameters->set_cheapest_insertion_ls_operator_neighbors_ratio(1.0); + parameters->set_cheapest_insertion_ls_operator_min_neighbors(1); RoutingSearchParameters::LocalSearchNeighborhoodOperators* const local_search_operators = parameters->mutable_local_search_operators(); diff --git a/ortools/constraint_solver/routing_parameters.cc b/ortools/constraint_solver/routing_parameters.cc index 0376274824..7ae145ed08 100644 --- a/ortools/constraint_solver/routing_parameters.cc +++ b/ortools/constraint_solver/routing_parameters.cc @@ -52,7 +52,9 @@ RoutingSearchParameters DefaultRoutingSearchParameters() { "savings_parallel_routes: false " "cheapest_insertion_farthest_seeds_ratio: 0 " "cheapest_insertion_first_solution_neighbors_ratio: 1 " + "cheapest_insertion_first_solution_min_neighbors: 1 " "cheapest_insertion_ls_operator_neighbors_ratio: 1 " + "cheapest_insertion_ls_operator_min_neighbors: 1 " "cheapest_insertion_add_unperformed_entries: false " "local_search_operators {" " use_relocate: BOOL_TRUE" @@ -199,6 +201,14 @@ std::string FindErrorInRoutingSearchParameters( "Invalid cheapest_insertion_first_solution_neighbors_ratio: ", ratio); } } + { + const int32 min_neighbors = + search_parameters.cheapest_insertion_first_solution_min_neighbors(); + if (min_neighbors < 1) { + return StrCat("Invalid cheapest_insertion_first_solution_min_neighbors: ", + min_neighbors, ". Must be greater or equal to 1."); + } + } { const double ratio = search_parameters.cheapest_insertion_ls_operator_neighbors_ratio(); @@ -207,6 +217,14 @@ std::string FindErrorInRoutingSearchParameters( ratio); } } + { + const int32 min_neighbors = + search_parameters.cheapest_insertion_ls_operator_min_neighbors(); + if (min_neighbors < 1) { + return StrCat("Invalid cheapest_insertion_ls_operator_min_neighbors: ", + min_neighbors, ". Must be greater or equal to 1."); + } + } { const int32 num_arcs = search_parameters.relocate_expensive_chain_num_arcs_to_consider(); diff --git a/ortools/constraint_solver/routing_parameters.proto b/ortools/constraint_solver/routing_parameters.proto index db4c33c6e2..08a310eff7 100644 --- a/ortools/constraint_solver/routing_parameters.proto +++ b/ortools/constraint_solver/routing_parameters.proto @@ -34,7 +34,7 @@ package operations_research; // then the routing library will pick its preferred value for that parameter // automatically: this should be the case for most parameters. // To see those "default" parameters, call GetDefaultRoutingSearchParameters(). -// Next ID: 44 +// Next ID: 46 message RoutingSearchParameters { // First solution strategies, used as starting point of local search. FirstSolutionStrategy.Value first_solution_strategy = 1; @@ -77,13 +77,24 @@ message RoutingSearchParameters { // cheapest insertion heuristic. // If not overridden, its default value is 1, meaning all neighbors will be // considered. + // The neighborhood ratio is coupled with the corresponding min_neighbors + // integer, indicating the minimum number of neighbors to consider for each + // node: + // num_closest_neighbors = + // max(min_neighbors, neighbors_ratio * NUM_NON_START_END_NODES) + // This minimum number of neighbors must be greater or equal to 1, its + // default value. // - // Neighbors ratio for the first solution heuristic. + // Neighbors ratio and minimum number of neighbors for the first solution + // heuristic. double cheapest_insertion_first_solution_neighbors_ratio = 21; - // Neighbors ratio for the heuristic when used in a local search operator (see + int32 cheapest_insertion_first_solution_min_neighbors = 44; + // Neighbors ratio and minimum number of neighbors for the heuristic when used + // in a local search operator (see // local_search_operators.use_global_cheapest_insertion_path_lns and // local_search_operators.use_global_cheapest_insertion_chain_lns below). double cheapest_insertion_ls_operator_neighbors_ratio = 31; + int32 cheapest_insertion_ls_operator_min_neighbors = 45; // Whether or not to consider entries making the nodes/pairs unperformed in // the GlobalCheapestInsertion heuristic. diff --git a/ortools/constraint_solver/routing_sat.cc b/ortools/constraint_solver/routing_sat.cc index 35cb70a325..53371a36d3 100644 --- a/ortools/constraint_solver/routing_sat.cc +++ b/ortools/constraint_solver/routing_sat.cc @@ -251,7 +251,7 @@ ArcVarMap PopulateMultiRouteModelFromRoutingModel(const RoutingModel& model, } // The following flow constraints seem to be necessary with the Route - // constraint, greatly improving preformance due to stronger LP relaxation + // constraint, greatly improving performance due to stronger LP relaxation // (supposedly). // TODO(user): Remove these constraints when the Route constraint handles // LP relaxations properly. @@ -261,9 +261,13 @@ ArcVarMap PopulateMultiRouteModelFromRoutingModel(const RoutingModel& model, ct->add_domain(0); for (int node = 0; node < num_nodes; ++node) { if (model.IsStart(node) || model.IsEnd(node)) continue; - ct->add_vars(gtl::FindOrDie(arc_vars, {depot, node})); + int* const depot_node_var = gtl::FindOrNull(arc_vars, {depot, node}); + if (depot_node_var == nullptr) continue; + ct->add_vars(*depot_node_var); ct->add_coeffs(1); - ct->add_vars(gtl::FindOrDie(arc_vars, {node, depot})); + int* const node_depot_var = gtl::FindOrNull(arc_vars, {node, depot}); + if (node_depot_var == nullptr) continue; + ct->add_vars(*node_depot_var); ct->add_coeffs(-1); } } @@ -276,7 +280,9 @@ ArcVarMap PopulateMultiRouteModelFromRoutingModel(const RoutingModel& model, std::min(model.vehicles(), model.GetMaximumNumberOfActiveVehicles())); for (int node = 0; node < num_nodes; ++node) { if (model.IsStart(node) || model.IsEnd(node)) continue; - ct->add_vars(gtl::FindOrDie(arc_vars, {depot, node})); + int* const var = gtl::FindOrNull(arc_vars, {depot, node}); + if (var == nullptr) continue; + ct->add_vars(*var); ct->add_coeffs(1); } } @@ -297,7 +303,9 @@ ArcVarMap PopulateMultiRouteModelFromRoutingModel(const RoutingModel& model, head = depot; depot_added = true; } - ct->add_vars(gtl::FindOrDie(arc_vars, {tail, head})); + int* const var = gtl::FindOrNull(arc_vars, {tail, head}); + if (var == nullptr) continue; + ct->add_vars(*var); ct->add_coeffs(1); } } @@ -311,7 +319,9 @@ ArcVarMap PopulateMultiRouteModelFromRoutingModel(const RoutingModel& model, if (model.IsEnd(head)) continue; if (tail == head) continue; if (model.IsStart(tail) && tail != depot) continue; - ct->add_vars(gtl::FindOrDie(arc_vars, {tail, head})); + int* const var = gtl::FindOrNull(arc_vars, {tail, head}); + if (var == nullptr) continue; + ct->add_vars(*var); ct->add_coeffs(1); } } diff --git a/ortools/constraint_solver/routing_search.cc b/ortools/constraint_solver/routing_search.cc index 41912c7b3a..5b2df19b5a 100644 --- a/ortools/constraint_solver/routing_search.cc +++ b/ortools/constraint_solver/routing_search.cc @@ -3352,12 +3352,9 @@ GlobalCheapestInsertionFilteredHeuristic:: empty_vehicle_type_curator_(nullptr) { CHECK_GT(gci_params_.neighbors_ratio, 0); CHECK_LE(gci_params_.neighbors_ratio, 1); + CHECK_GE(gci_params_.min_neighbors, 1); - const int64 num_non_start_end_nodes = NumNonStartEndNodes(); - const int64 num_neighbors = - std::max(1.0, gci_params_.neighbors_ratio * num_non_start_end_nodes); - - if (num_neighbors >= num_non_start_end_nodes - 1) { + if (NumNeighbors() >= NumNonStartEndNodes() - 1) { // All nodes are neighbors, so we set the neighbors_ratio to 1 to avoid // unnecessary computations in the code. gci_params_.neighbors_ratio = 1; @@ -3378,12 +3375,10 @@ void GlobalCheapestInsertionFilteredHeuristic::ComputeNeighborhoods() { } // TODO(user): Refactor the neighborhood computations in RoutingModel. - const int64 num_non_start_end_nodes = NumNonStartEndNodes(); - const int64 num_neighbors = - std::max(1.0, gci_params_.neighbors_ratio * num_non_start_end_nodes); + const int64 num_neighbors = NumNeighbors(); // If num_neighbors was greater or equal num_non_start_end_nodes - 1, // gci_params_.neighbors_ratio should have been set to 1. - DCHECK_LT(num_neighbors, num_non_start_end_nodes - 1); + DCHECK_LT(num_neighbors, NumNonStartEndNodes() - 1); const RoutingModel& routing_model = *model(); const int64 size = routing_model.Size(); diff --git a/ortools/constraint_solver/samples/VrpCapacity.java b/ortools/constraint_solver/samples/VrpCapacity.java index ab31e64e1e..3dc4e27b7d 100644 --- a/ortools/constraint_solver/samples/VrpCapacity.java +++ b/ortools/constraint_solver/samples/VrpCapacity.java @@ -26,8 +26,8 @@ import com.google.protobuf.Duration; import java.util.logging.Logger; // [END import] -/** Minimal VRP.*/ -public class VrpCapacity { +/** Minimal VRP. */ +public final class VrpCapacity { private static final Logger logger = Logger.getLogger(VrpCapacity.class.getName()); // [START data_model] @@ -160,5 +160,7 @@ public class VrpCapacity { printSolution(data, routing, manager, solution); // [END print_solution] } + + private VrpCapacity() {} } // [END program]