diff --git a/ortools/constraint_solver/routing.cc b/ortools/constraint_solver/routing.cc index 80e69cbfaee..a5266b8ea49 100644 --- a/ortools/constraint_solver/routing.cc +++ b/ortools/constraint_solver/routing.cc @@ -65,6 +65,7 @@ #include "ortools/constraint_solver/routing_parameters.pb.h" #include "ortools/constraint_solver/routing_search.h" #include "ortools/constraint_solver/routing_types.h" +#include "ortools/constraint_solver/routing_utils.h" #include "ortools/constraint_solver/solver_parameters.pb.h" #include "ortools/graph/connected_components.h" #include "ortools/graph/ebert_graph.h" @@ -3999,7 +4000,8 @@ void RoutingModel::CreateNeighborhoodOperators( parameters.local_cheapest_insertion_pickup_delivery_strategy(), GetOrCreateLocalSearchFilterManager( parameters, - {/*filter_objective=*/false, /*filter_with_cp_solver=*/false})); + {/*filter_objective=*/false, /*filter_with_cp_solver=*/false}), + bin_capacities_.get()); }; local_search_operators_[GLOBAL_CHEAPEST_INSERTION_CLOSE_NODES_LNS] = solver_->RevAlloc(new FilteredHeuristicCloseNodesLNSOperator( @@ -4409,6 +4411,60 @@ LocalSearchFilterManager* RoutingModel::GetOrCreateLocalSearchFilterManager( return local_search_filter_manager; } +std::unique_ptr MakeBinCapacities( + const std::vector& dimensions, + const PathsMetadata& paths_metadata) { + const int num_vehicles = paths_metadata.NumPaths(); + auto bin_capacities = std::make_unique(num_vehicles); + std::vector load_limits; + for (const RoutingDimension* dimension : dimensions) { + // If the dimension is not unary, skip. + if (dimension->GetUnaryTransitEvaluator(0) == nullptr) continue; + // If the dimension has no constant-signed transit evaluator, skip. + if (dimension->AllTransitEvaluatorSignsAreUnknown()) continue; + // For each vehicle, if the sign of its evaluator is constant, + // set a transit evaluator to pass to BinCapacities. + load_limits.assign(num_vehicles, {.max_load = kint64max, + .soft_max_load = 0, + .cost_above_soft_max_load = 0}); + for (int vehicle = 0; vehicle < num_vehicles; ++vehicle) { + const RoutingModel::TransitEvaluatorSign sign = + dimension->GetTransitEvaluatorSign(vehicle); + if (sign == RoutingModel::kTransitEvaluatorSignUnknown) continue; + // Vehicle load changes monotonically along the route. + // If transit signs are >= 0, the min load is at start, the max at end. + // If transit signs are <= 0, the max load is at start, the min at end. + // The encoding into BinCapacities associates a bin dimension with this + // routing dimension, with bin capacity = vehicle capacity - min load, + // and bin item size = abs(transit(node)). + int64_t min_node = paths_metadata.Starts()[vehicle]; + int64_t max_node = paths_metadata.Ends()[vehicle]; + if (sign == RoutingModel::kTransitEvaluatorSignNegativeOrZero) { + std::swap(min_node, max_node); + } + const int64_t load_min = + std::max(0, dimension->CumulVar(min_node)->Min()); + const int64_t load_max = + std::min(dimension->vehicle_capacities()[vehicle], + dimension->CumulVar(max_node)->Max()); + load_limits[vehicle].max_load = CapSub(load_max, load_min); + if (dimension->HasCumulVarSoftUpperBound(max_node)) { + load_limits[vehicle].soft_max_load = + CapSub(dimension->GetCumulVarSoftUpperBound(max_node), load_min); + load_limits[vehicle].cost_above_soft_max_load = + dimension->GetCumulVarSoftUpperBoundCoefficient(max_node); + } + } + bin_capacities->AddDimension( + [dimension](int node, int vehicle) { + return CapAbs(dimension->GetUnaryTransitEvaluator(vehicle)(node)); + }, + load_limits); + } + if (bin_capacities->NumDimensions() == 0) bin_capacities.reset(nullptr); + return bin_capacities; +} + namespace { bool AllTransitsPositive(const RoutingDimension& dimension) { for (int vehicle = 0; vehicle < dimension.model()->vehicles(); vehicle++) { @@ -4846,7 +4902,8 @@ void RoutingModel::CreateFirstSolutionDecisionBuilders( lci_pair_strategy, GetOrCreateLocalSearchFilterManager( search_parameters, {/*filter_objective=*/false, - /*filter_with_cp_solver=*/false})); + /*filter_with_cp_solver=*/false}), + bin_capacities_.get()); IntVarFilteredDecisionBuilder* const strong_lci = CreateIntVarFilteredDecisionBuilder< LocalCheapestInsertionFilteredHeuristic>( @@ -4854,9 +4911,10 @@ void RoutingModel::CreateFirstSolutionDecisionBuilders( return GetArcCostForVehicle(i, j, vehicle); }, lci_pair_strategy, - GetOrCreateLocalSearchFilterManager( - search_parameters, {/*filter_objective=*/false, - /*filter_with_cp_solver=*/true})); + GetOrCreateLocalSearchFilterManager(search_parameters, + {/*filter_objective=*/false, + /*filter_with_cp_solver=*/true}), + bin_capacities_.get()); first_solution_decision_builders_ [FirstSolutionStrategy::LOCAL_CHEAPEST_INSERTION] = solver_->Try( first_solution_filtered_decision_builders_ @@ -4876,14 +4934,16 @@ void RoutingModel::CreateFirstSolutionDecisionBuilders( /*evaluator=*/nullptr, lcci_pair_strategy, GetOrCreateLocalSearchFilterManager( search_parameters, {/*filter_objective=*/true, - /*filter_with_cp_solver=*/false})); + /*filter_with_cp_solver=*/false}), + bin_capacities_.get()); IntVarFilteredDecisionBuilder* const strong_lcci = CreateIntVarFilteredDecisionBuilder< LocalCheapestInsertionFilteredHeuristic>( /*evaluator=*/nullptr, lcci_pair_strategy, - GetOrCreateLocalSearchFilterManager( - search_parameters, {/*filter_objective=*/true, - /*filter_with_cp_solver=*/true})); + GetOrCreateLocalSearchFilterManager(search_parameters, + {/*filter_objective=*/true, + /*filter_with_cp_solver=*/true}), + bin_capacities_.get()); first_solution_decision_builders_ [FirstSolutionStrategy::LOCAL_CHEAPEST_COST_INSERTION] = solver_->Try( first_solution_filtered_decision_builders_ @@ -5897,6 +5957,16 @@ int64_t RoutingDimension::GetTransitValue(int64_t from_index, int64_t to_index, return transit_evaluator(vehicle)(from_index, to_index); } +bool RoutingDimension::AllTransitEvaluatorSignsAreUnknown() const { + for (const int evaluator_index : class_evaluators_) { + if (model()->transit_evaluator_sign_[evaluator_index] != + RoutingModel::kTransitEvaluatorSignUnknown) { + return false; + } + } + return true; +} + SortedDisjointIntervalList RoutingDimension::GetAllowedIntervalsInRange( int64_t index, int64_t min_value, int64_t max_value) const { SortedDisjointIntervalList allowed; diff --git a/ortools/constraint_solver/routing.h b/ortools/constraint_solver/routing.h index 4e9ed0ed447..e5f185f91b8 100644 --- a/ortools/constraint_solver/routing.h +++ b/ortools/constraint_solver/routing.h @@ -185,6 +185,7 @@ #include "ortools/constraint_solver/routing_index_manager.h" #include "ortools/constraint_solver/routing_parameters.pb.h" #include "ortools/constraint_solver/routing_types.h" +#include "ortools/constraint_solver/routing_utils.h" #include "ortools/graph/graph.h" #include "ortools/sat/theta_tree.h" #include "ortools/util/bitset.h" @@ -236,6 +237,8 @@ class PathsMetadata { int GetPath(int64_t start_or_end_node) const { return path_of_node_[start_or_end_node]; } + int NumPaths() const { return start_of_path_.size(); } + const std::vector& Paths() const { return path_of_node_; } const std::vector& Starts() const { return start_of_path_; } const std::vector& Ends() const { return end_of_path_; } @@ -1750,6 +1753,11 @@ class RoutingModel { DecisionBuilder* MakeSelfDependentDimensionFinalizer( const RoutingDimension* dimension); + const PathsMetadata& GetPathsMetadata() const { return paths_metadata_; } +#ifndef SWIG + BinCapacities* GetBinCapacities() { return bin_capacities_.get(); } +#endif // SWIG + private: /// Local search move operator usable in routing. enum RoutingLocalSearchOperator { @@ -2410,6 +2418,9 @@ class RoutingModel { std::vector> state_dependent_transit_evaluators_cache_; + // Returns global BinCapacities state, may be nullptr. + std::unique_ptr bin_capacities_; + friend class RoutingDimension; friend class RoutingModelInspector; friend class ResourceGroup::Resource; @@ -2963,6 +2974,7 @@ class RoutingDimension { return model()->transit_evaluator_sign_[evaluator_index] == RoutingModel::kTransitEvaluatorSignPositiveOrZero; } + bool AllTransitEvaluatorSignsAreUnknown() const; RoutingModel::TransitEvaluatorSign GetTransitEvaluatorSign( int vehicle) const { const int evaluator_index = class_evaluators_[vehicle_to_class_[vehicle]]; diff --git a/ortools/constraint_solver/routing_parameters.cc b/ortools/constraint_solver/routing_parameters.cc index 3cb663496b9..1a4fb1ac5e3 100644 --- a/ortools/constraint_solver/routing_parameters.cc +++ b/ortools/constraint_solver/routing_parameters.cc @@ -24,12 +24,12 @@ #include "google/protobuf/descriptor.h" #include "google/protobuf/duration.pb.h" #include "google/protobuf/message.h" -#include "google/protobuf/text_format.h" #include "ortools/base/logging.h" #include "ortools/base/protoutil.h" #include "ortools/base/types.h" #include "ortools/constraint_solver/constraint_solver.h" #include "ortools/constraint_solver/routing_enums.pb.h" +#include "ortools/constraint_solver/routing_parameters.pb.h" #include "ortools/constraint_solver/solver_parameters.pb.h" #include "ortools/sat/sat_parameters.pb.h" #include "ortools/util/optional_boolean.pb.h" diff --git a/ortools/constraint_solver/routing_sat.cc b/ortools/constraint_solver/routing_sat.cc index d55aff28297..f5cc21de718 100644 --- a/ortools/constraint_solver/routing_sat.cc +++ b/ortools/constraint_solver/routing_sat.cc @@ -23,7 +23,6 @@ #include "absl/container/flat_hash_map.h" #include "absl/time/time.h" -#include "ortools/base/logging.h" #include "ortools/base/map_util.h" #include "ortools/constraint_solver/constraint_solver.h" #include "ortools/constraint_solver/routing.h" @@ -34,6 +33,7 @@ #include "ortools/sat/integer.h" #include "ortools/sat/model.h" #include "ortools/sat/sat_parameters.pb.h" +#include "ortools/util/bitset.h" #include "ortools/util/optional_boolean.pb.h" #include "ortools/util/saturated_arithmetic.h" @@ -758,13 +758,17 @@ ArcVarMap PopulateGeneralizedRouteModelFromRoutingModel( // Set literals for vehicle performing node. for (int cp_node = 1; cp_node < num_cp_nodes; cp_node++) { + const int routing_index = cp_node - 1; // For starts and ends nodes vehicle_performs_node variables already set. - if (model.IsStart(cp_node - 1) || model.IsEnd(cp_node - 1)) continue; + if (model.IsStart(routing_index) || model.IsEnd(routing_index)) continue; // Each node should be performed by 1 vehicle, or be unperformed. // SUM(vehicle)(vehicle_performs_node[vehicle][cp_node]) + loop(cp_node) = 1 std::vector> var_coeffs; for (int vehicle = 0; vehicle < model.vehicles(); vehicle++) { - vehicle_performs_node[vehicle][cp_node] = AddVariable(cp_model, 0, 1); + vehicle_performs_node[vehicle][cp_node] = + model.VehicleVar(routing_index)->Contains(vehicle) + ? AddVariable(cp_model, 0, 1) + : AddVariable(cp_model, 0, 0); var_coeffs.push_back({vehicle_performs_node[vehicle][cp_node], 1}); } var_coeffs.push_back({is_unperformed[cp_node], 1}); diff --git a/ortools/constraint_solver/routing_search.cc b/ortools/constraint_solver/routing_search.cc index 0c536660447..06c6a80d746 100644 --- a/ortools/constraint_solver/routing_search.cc +++ b/ortools/constraint_solver/routing_search.cc @@ -300,7 +300,7 @@ void IntVarFilteredHeuristic::ResetSolution() { SynchronizeFilters(); } -Assignment* const IntVarFilteredHeuristic::BuildSolution() { +Assignment* IntVarFilteredHeuristic::BuildSolution() { // Initialize must be called before the state of the heuristic is changed, in // particular before InitializeSolution() and BuildSolutionInternal(). Initialize(); diff --git a/ortools/constraint_solver/routing_search.h b/ortools/constraint_solver/routing_search.h index 2b4d1c8859c..1d7fe810233 100644 --- a/ortools/constraint_solver/routing_search.h +++ b/ortools/constraint_solver/routing_search.h @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -36,16 +35,11 @@ #include "absl/container/flat_hash_set.h" #include "absl/log/check.h" #include "ortools/base/adjustable_priority_queue.h" -#include "ortools/base/logging.h" #include "ortools/base/macros.h" -#include "ortools/base/mathutil.h" -#include "ortools/base/types.h" #include "ortools/constraint_solver/constraint_solver.h" #include "ortools/constraint_solver/constraint_solveri.h" #include "ortools/constraint_solver/routing.h" -#include "ortools/constraint_solver/routing_index_manager.h" #include "ortools/constraint_solver/routing_utils.h" -#include "ortools/util/bitset.h" namespace operations_research { @@ -182,7 +176,7 @@ class IntVarFilteredHeuristic { /// Builds a solution. Returns the resulting assignment if a solution was /// found, and nullptr otherwise. - Assignment* const BuildSolution(); + Assignment* BuildSolution(); /// Returns statistics on search, number of decisions sent to filters, number /// of decisions rejected by filters. diff --git a/ortools/constraint_solver/routing_utils.h b/ortools/constraint_solver/routing_utils.h index 99d890108e4..0452d0b17f8 100644 --- a/ortools/constraint_solver/routing_utils.h +++ b/ortools/constraint_solver/routing_utils.h @@ -47,6 +47,7 @@ class BinCapacities { void AddDimension( std::function load_demand_of_item_for_bin, std::vector load_limit_per_bin); + int NumDimensions() const { return load_demands_per_dimension_.size(); } // Checks whether adding item(s) is feasible w.r.t. dimensions. bool CheckAdditionFeasibility(int item, int bin) const;