From bd5c282a518fa1572e09bebcc600ceb04bb1c087 Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Mon, 16 Dec 2019 12:34:17 +0100 Subject: [PATCH] improve time placement when nodes have disjoint time windows --- ortools/constraint_solver/routing.cc | 47 +++++++++++++++-- ortools/constraint_solver/routing.h | 18 +++++-- ortools/constraint_solver/routing_sat.cc | 9 +++- ortools/constraint_solver/routing_search.cc | 57 ++++++++++----------- ortools/constraint_solver/search.cc | 7 +-- 5 files changed, 97 insertions(+), 41 deletions(-) diff --git a/ortools/constraint_solver/routing.cc b/ortools/constraint_solver/routing.cc index 209cf3bcdd..e9b198fffd 100644 --- a/ortools/constraint_solver/routing.cc +++ b/ortools/constraint_solver/routing.cc @@ -177,8 +177,11 @@ class SetCumulsFromLocalDimensionCosts : public DecisionBuilder { SetCumulsFromLocalDimensionCosts( const std::vector>* local_optimizers, + const std::vector>* + local_mp_optimizers, SearchMonitor* monitor, bool optimize_and_pack = false) : local_optimizers_(*local_optimizers), + local_mp_optimizers_(*local_mp_optimizers), monitor_(monitor), optimize_and_pack_(optimize_and_pack) {} Decision* Next(Solver* const solver) override { @@ -186,7 +189,10 @@ class SetCumulsFromLocalDimensionCosts : public DecisionBuilder { // order to postpone the Fail() call until after the internal for loop, so // there are no memory leaks related to the cumul_values vector. bool should_fail = false; - for (const auto& optimizer : local_optimizers_) { + for (int i = 0; i < local_optimizers_.size(); ++i) { + const auto& optimizer = local_mp_optimizers_[i] != nullptr + ? local_mp_optimizers_[i] + : local_optimizers_[i]; const RoutingDimension* const dimension = optimizer->dimension(); RoutingModel* const model = dimension->model(); const auto next = [model](int64 i) { return model->NextVar(i)->Value(); }; @@ -242,6 +248,8 @@ class SetCumulsFromLocalDimensionCosts : public DecisionBuilder { private: const std::vector>& local_optimizers_; + const std::vector>& + local_mp_optimizers_; SearchMonitor* const monitor_; const bool optimize_and_pack_; }; @@ -309,6 +317,7 @@ const Assignment* RoutingModel::PackCumulsOfOptimizerDimensionsFromAssignment( if (time_limit_ms <= 0 || original_assignment == nullptr || (global_dimension_optimizers_.empty() && local_dimension_optimizers_.empty())) { + DCHECK(local_dimension_mp_optimizers_.empty()); return original_assignment; } @@ -327,7 +336,7 @@ const Assignment* RoutingModel::PackCumulsOfOptimizerDimensionsFromAssignment( solver_->MakeRestoreAssignment(packed_assignment)); decision_builders.push_back( solver_->RevAlloc(new SetCumulsFromLocalDimensionCosts( - &local_dimension_optimizers_, + &local_dimension_optimizers_, &local_dimension_mp_optimizers_, GetOrCreateLargeNeighborhoodSearchLimit(), /*optimize_and_pack=*/true))); decision_builders.push_back( @@ -1164,6 +1173,18 @@ LocalDimensionCumulOptimizer* RoutingModel::GetMutableLocalCumulOptimizer( return local_dimension_optimizers_[optimizer_index].get(); } +LocalDimensionCumulOptimizer* RoutingModel::GetMutableLocalCumulMPOptimizer( + const RoutingDimension& dimension) const { + const DimensionIndex dim_index = GetDimensionIndex(dimension.name()); + if (dim_index < 0 || dim_index >= local_optimizer_index_.size() || + local_optimizer_index_[dim_index] < 0) { + return nullptr; + } + const int optimizer_index = local_optimizer_index_[dim_index]; + DCHECK_LT(optimizer_index, local_dimension_mp_optimizers_.size()); + return local_dimension_mp_optimizers_[optimizer_index].get(); +} + bool RoutingModel::HasDimension(const std::string& dimension_name) const { return gtl::ContainsKey(dimension_name_to_index_, dimension_name); } @@ -4511,9 +4532,28 @@ void RoutingModel::StoreDimensionCumulOptimizers( local_dimension_optimizers_.push_back( absl::make_unique( dimension, parameters.continuous_scheduling_solver())); + bool has_intervals = false; + for (const SortedDisjointIntervalList& intervals : + dimension->forbidden_intervals()) { + // TODO(user): Change the following test to check intervals within + // the domain of the corresponding variables. + if (intervals.NumIntervals() > 0) { + has_intervals = true; + break; + } + } + if (has_intervals) { + local_dimension_mp_optimizers_.push_back( + absl::make_unique( + dimension, parameters.mixed_integer_scheduling_solver())); + } else { + local_dimension_mp_optimizers_.push_back(nullptr); + } packed_dimensions_collector_assignment->Add(dimension->cumuls()); } } + DCHECK_EQ(local_dimension_mp_optimizers_.size(), + local_dimension_optimizers_.size()); } // NOTE(b/129252839): We also add all other extra variables to the @@ -4589,7 +4629,8 @@ DecisionBuilder* RoutingModel::CreateSolutionFinalizer(SearchLimit* lns_limit) { if (!local_dimension_optimizers_.empty()) { decision_builders.push_back( solver_->RevAlloc(new SetCumulsFromLocalDimensionCosts( - &local_dimension_optimizers_, lns_limit))); + &local_dimension_optimizers_, &local_dimension_mp_optimizers_, + lns_limit))); } if (!global_dimension_optimizers_.empty()) { decision_builders.push_back( diff --git a/ortools/constraint_solver/routing.h b/ortools/constraint_solver/routing.h index 7866100cf7..bfa8e9c71a 100644 --- a/ortools/constraint_solver/routing.h +++ b/ortools/constraint_solver/routing.h @@ -531,6 +531,10 @@ class RoutingModel { GetLocalDimensionCumulOptimizers() const { return local_dimension_optimizers_; } + const std::vector >& + GetLocalDimensionCumulMPOptimizers() const { + return local_dimension_mp_optimizers_; + } // clang-format on /// Returns the global/local dimension cumul optimizer for a given dimension, @@ -539,6 +543,8 @@ class RoutingModel { const RoutingDimension& dimension) const; LocalDimensionCumulOptimizer* GetMutableLocalCumulOptimizer( const RoutingDimension& dimension) const; + LocalDimensionCumulOptimizer* GetMutableLocalCumulMPOptimizer( + const RoutingDimension& dimension) const; /// Returns true if a dimension exists for a given dimension name. bool HasDimension(const std::string& dimension_name) const; @@ -1550,6 +1556,8 @@ class RoutingModel { gtl::ITIVector global_optimizer_index_; std::vector > local_dimension_optimizers_; + std::vector > + local_dimension_mp_optimizers_; // clang-format off gtl::ITIVector local_optimizer_index_; std::string primary_constrained_dimension_; @@ -2841,7 +2849,7 @@ class GlobalCheapestInsertionFilteredHeuristic /// newly modified route arcs: after the node insertion position and after the /// node position. void InsertNodesOnRoutes(const std::vector& nodes, - const std::vector& vehicles); + const absl::flat_hash_set& vehicles); /// Inserts non-inserted individual nodes on routes by constructing routes /// sequentially. @@ -2854,8 +2862,8 @@ class GlobalCheapestInsertionFilteredHeuristic /// (i.e. Value(start) != end) or not. /// Updates the three passed vectors accordingly. void DetectUsedVehicles(std::vector* is_vehicle_used, - std::vector* used_vehicles, - std::vector* unused_vehicles); + std::vector* unused_vehicles, + absl::flat_hash_set* used_vehicles); /// Inserts the (farthest_seeds_ratio_ * model()->vehicles()) nodes farthest /// from the start/ends of the available vehicle routes as seeds on their @@ -2927,14 +2935,14 @@ class GlobalCheapestInsertionFilteredHeuristic void InitializePositions(const std::vector& nodes, AdjustablePriorityQueue* priority_queue, std::vector* position_to_node_entries, - const std::vector& vehicles); + const absl::flat_hash_set& vehicles); /// Adds insertion entries performing 'node', and updates 'priority_queue' and /// position_to_node_entries accordingly. /// Based on gci_params_.use_neighbors_ratio_for_initialization, either all /// contained nodes are considered as insertion positions, or only the /// closest neighbors of 'node'. void InitializeInsertionEntriesPerformingNode( - int64 node, int64 penalty, const std::vector& vehicles, + int64 node, int64 penalty, const absl::flat_hash_set& vehicles, AdjustablePriorityQueue* priority_queue, std::vector* position_to_node_entries); /// Updates all node entries inserting a node after node "insert_after" and diff --git a/ortools/constraint_solver/routing_sat.cc b/ortools/constraint_solver/routing_sat.cc index d1727fb3ec..065f11f957 100644 --- a/ortools/constraint_solver/routing_sat.cc +++ b/ortools/constraint_solver/routing_sat.cc @@ -339,7 +339,14 @@ void AddSolutionAsHintToModel(const Assignment* solution, const int head = solution->Value(model.NextVar(tail)); const int head_index = model.IsEnd(head) ? depot : head; if (tail_index == depot && head_index == depot) continue; - hint->add_vars(gtl::FindOrDie(arc_vars, {tail_index, head_index})); + const int* const var_index = + gtl::FindOrNull(arc_vars, {tail_index, head_index}); + // Arcs with a cost of kint64max are not added to the model (considered as + // infeasible). In some rare cases CP solutions might contain such arcs in + // which case they are skipped here and a partial solution is used as a + // hint. + if (var_index == nullptr) continue; + hint->add_vars(*var_index); hint->add_values(1); } } diff --git a/ortools/constraint_solver/routing_search.cc b/ortools/constraint_solver/routing_search.cc index b2d693c90d..734d27eb89 100644 --- a/ortools/constraint_solver/routing_search.cc +++ b/ortools/constraint_solver/routing_search.cc @@ -3128,36 +3128,38 @@ bool GlobalCheapestInsertionFilteredHeuristic::CheckVehicleIndices() const { bool GlobalCheapestInsertionFilteredHeuristic::BuildSolutionInternal() { // Insert partially inserted pairs. - std::vector pair_nodes; + absl::flat_hash_map> vehicle_to_pair_nodes; for (const RoutingModel::IndexPair& index_pair : model()->GetPickupAndDeliveryPairs()) { - bool has_inserted_pickup = false; + int pickup_vehicle = -1; for (int64 pickup : index_pair.first) { if (Contains(pickup)) { - has_inserted_pickup = true; + pickup_vehicle = node_index_to_vehicle_[pickup]; break; } } - bool has_inserted_delivery = false; + int delivery_vehicle = -1; for (int64 delivery : index_pair.second) { if (Contains(delivery)) { - has_inserted_delivery = true; + delivery_vehicle = node_index_to_vehicle_[delivery]; break; } } - if (has_inserted_pickup && !has_inserted_delivery) { + if (pickup_vehicle >= 0 && delivery_vehicle < 0) { + std::vector& pair_nodes = vehicle_to_pair_nodes[pickup_vehicle]; for (int64 delivery : index_pair.second) { pair_nodes.push_back(delivery); } } - if (!has_inserted_pickup && has_inserted_delivery) { + if (pickup_vehicle < 0 && delivery_vehicle >= 0) { + std::vector& pair_nodes = vehicle_to_pair_nodes[delivery_vehicle]; for (int64 pickup : index_pair.first) { pair_nodes.push_back(pickup); } } } - if (!pair_nodes.empty()) { - InsertNodesOnRoutes(pair_nodes, {}); + for (const auto& vehicle_and_nodes : vehicle_to_pair_nodes) { + InsertNodesOnRoutes(vehicle_and_nodes.second, {vehicle_and_nodes.first}); } // TODO(user): Adapt the pair insertions to also support seed and // sequential insertion. @@ -3247,7 +3249,7 @@ void GlobalCheapestInsertionFilteredHeuristic::InsertPairs() { } void GlobalCheapestInsertionFilteredHeuristic::InsertNodesOnRoutes( - const std::vector& nodes, const std::vector& vehicles) { + const std::vector& nodes, const absl::flat_hash_set& vehicles) { AdjustablePriorityQueue priority_queue; std::vector position_to_node_entries; InitializePositions(nodes, &priority_queue, &position_to_node_entries, @@ -3316,10 +3318,10 @@ void GlobalCheapestInsertionFilteredHeuristic::InsertNodesOnRoutes( void GlobalCheapestInsertionFilteredHeuristic::SequentialInsertNodes( const std::vector& nodes) { std::vector is_vehicle_used; - std::vector used_vehicles; + absl::flat_hash_set used_vehicles; std::vector unused_vehicles; - DetectUsedVehicles(&is_vehicle_used, &used_vehicles, &unused_vehicles); + DetectUsedVehicles(&is_vehicle_used, &unused_vehicles, &used_vehicles); if (!used_vehicles.empty()) { InsertNodesOnRoutes(nodes, used_vehicles); } @@ -3341,8 +3343,8 @@ void GlobalCheapestInsertionFilteredHeuristic::SequentialInsertNodes( } void GlobalCheapestInsertionFilteredHeuristic::DetectUsedVehicles( - std::vector* is_vehicle_used, std::vector* used_vehicles, - std::vector* unused_vehicles) { + std::vector* is_vehicle_used, std::vector* unused_vehicles, + absl::flat_hash_set* used_vehicles) { is_vehicle_used->clear(); is_vehicle_used->resize(model()->vehicles()); @@ -3355,7 +3357,7 @@ void GlobalCheapestInsertionFilteredHeuristic::DetectUsedVehicles( for (int vehicle = 0; vehicle < model()->vehicles(); vehicle++) { if (Value(model()->Start(vehicle)) != model()->End(vehicle)) { (*is_vehicle_used)[vehicle] = true; - used_vehicles->push_back(vehicle); + used_vehicles->insert(vehicle); } else { (*is_vehicle_used)[vehicle] = false; unused_vehicles->push_back(vehicle); @@ -3370,9 +3372,9 @@ void GlobalCheapestInsertionFilteredHeuristic::InsertFarthestNodesAsSeeds() { std::ceil(gci_params_.farthest_seeds_ratio * model()->vehicles())); std::vector is_vehicle_used; - std::vector used_vehicles; + absl::flat_hash_set used_vehicles; std::vector unused_vehicles; - DetectUsedVehicles(&is_vehicle_used, &used_vehicles, &unused_vehicles); + DetectUsedVehicles(&is_vehicle_used, &unused_vehicles, &used_vehicles); std::vector> start_end_distances_per_node = ComputeStartEndDistanceForVehicles(unused_vehicles); @@ -3837,7 +3839,7 @@ void GlobalCheapestInsertionFilteredHeuristic::InitializePositions( GlobalCheapestInsertionFilteredHeuristic::NodeEntry>* priority_queue, std::vector* position_to_node_entries, - const std::vector& vehicles) { + const absl::flat_hash_set& vehicles) { priority_queue->Clear(); position_to_node_entries->clear(); position_to_node_entries->resize(model()->Size()); @@ -3875,7 +3877,7 @@ void GlobalCheapestInsertionFilteredHeuristic::InitializePositions( void GlobalCheapestInsertionFilteredHeuristic:: InitializeInsertionEntriesPerformingNode( - int64 node, int64 penalty, const std::vector& vehicles, + int64 node, int64 penalty, const absl::flat_hash_set& vehicles, AdjustablePriorityQueue< GlobalCheapestInsertionFilteredHeuristic::NodeEntry>* priority_queue, @@ -3884,8 +3886,9 @@ void GlobalCheapestInsertionFilteredHeuristic:: const int num_vehicles = vehicles.empty() ? model()->vehicles() : vehicles.size(); if (!gci_params_.use_neighbors_ratio_for_initialization) { + auto vehicles_it = vehicles.begin(); for (int v = 0; v < num_vehicles; v++) { - const int vehicle = vehicles.empty() ? v : vehicles[v]; + const int vehicle = vehicles.empty() ? v : *vehicles_it++; std::vector valued_positions; const int64 start = model()->Start(vehicle); @@ -3906,15 +3909,11 @@ void GlobalCheapestInsertionFilteredHeuristic:: // the node. absl::flat_hash_set vehicles_to_consider; const bool all_vehicles = (num_vehicles == model()->vehicles()); - if (!all_vehicles) { - vehicles_to_consider = - absl::flat_hash_set(vehicles.begin(), vehicles.end()); - } - const auto insert_on_vehicle_for_cost_class = - [this, &vehicles_to_consider, all_vehicles](int v, int cost_class) { - return (model()->GetCostClassIndexOfVehicle(v).value() == cost_class) && - (all_vehicles || vehicles_to_consider.contains(v)); - }; + const auto insert_on_vehicle_for_cost_class = [this, &vehicles, all_vehicles]( + int v, int cost_class) { + return (model()->GetCostClassIndexOfVehicle(v).value() == cost_class) && + (all_vehicles || vehicles.contains(v)); + }; for (int cost_class = 0; cost_class < model()->GetCostClassesCount(); cost_class++) { for (const std::vector* const neighbors : diff --git a/ortools/constraint_solver/search.cc b/ortools/constraint_solver/search.cc index 0bc13a4c03..eb19b92610 100644 --- a/ortools/constraint_solver/search.cc +++ b/ortools/constraint_solver/search.cc @@ -2514,11 +2514,12 @@ bool BestValueSolutionCollector::AtSolution() { if (prototype_ != nullptr) { const IntVar* objective = prototype_->Objective(); if (objective != nullptr) { - if (maximize_ && objective->Max() > best_) { + if (maximize_ && (solution_count() == 0 || objective->Max() > best_)) { PopSolution(); PushSolution(); best_ = objective->Max(); - } else if (!maximize_ && objective->Min() < best_) { + } else if (!maximize_ && + (solution_count() == 0 || objective->Min() < best_)) { PopSolution(); PushSolution(); best_ = objective->Min(); @@ -2606,7 +2607,7 @@ bool NBestValueSolutionCollector::AtSolution() { const IntVar* objective = prototype_->Objective(); if (objective != nullptr) { const int64 objective_value = - maximize_ ? -objective->Max() : objective->Min(); + maximize_ ? CapSub(0, objective->Max()) : objective->Min(); if (solutions_pq_.size() < solution_count_) { solutions_pq_.push( {objective_value, BuildSolutionDataForCurrentState()});