diff --git a/ortools/routing/filter_committables.h b/ortools/routing/filter_committables.h index 05be5b842d..61909de6c6 100644 --- a/ortools/routing/filter_committables.h +++ b/ortools/routing/filter_committables.h @@ -72,6 +72,14 @@ class CommittableVector { return elements_[index].current; } + // Returns a reference to the value stored at index in the current state, + // and sets the index as modified. + T& GetMutable(size_t index) { + DCHECK_LT(index, elements_.size()); + changed_.Set(index); + return elements_[index].current; + } + // Set the value stored at index in the current state to given value. void Set(size_t index, const T& value) { DCHECK_GE(index, 0); @@ -85,7 +93,7 @@ class CommittableVector { for (const size_t index : changed_.PositionsSetAtLeastOnce()) { elements_[index].current = elements_[index].committed; } - changed_.ClearAll(); + changed_.SparseClearAll(); } // Makes the current state committed, clearing all changes. @@ -93,13 +101,12 @@ class CommittableVector { for (const size_t index : changed_.PositionsSetAtLeastOnce()) { elements_[index].committed = elements_[index].current; } - changed_.ClearAll(); + changed_.SparseClearAll(); } // Sets all elements of this vector to given value, and commits to this state. - // Supposes that there are no changes since the last Commit() or Revert(). void SetAllAndCommit(const T& value) { - DCHECK_EQ(0, changed_.NumberOfSetCallsWithDifferentArguments()); + changed_.SparseClearAll(); elements_.assign(elements_.size(), {value, value}); } @@ -174,13 +181,11 @@ class DimensionValues { public: DimensionValues(int num_paths, int num_nodes) : range_of_path_(num_paths, {.begin = 0, .end = 0}), - committed_range_of_path_(num_paths, {.begin = 0, .end = 0}), span_(num_paths, Interval::AllIntegers()), - committed_span_(num_paths, Interval::AllIntegers()), vehicle_breaks_(num_paths), committed_vehicle_breaks_(num_paths), - changed_paths_(num_paths), - max_num_committed_elements_(16 * num_nodes) { + max_num_committed_elements_(16 * num_nodes), + num_elements_(0) { nodes_.reserve(max_num_committed_elements_); transit_.reserve(max_num_committed_elements_); travel_.reserve(max_num_committed_elements_); @@ -260,75 +265,68 @@ class DimensionValues { // Turns new nodes into a new path, allocating dimension values for it. void MakePathFromNewNodes(int path) { DCHECK_GE(path, 0); - DCHECK_LT(path, range_of_path_.size()); - DCHECK(!changed_paths_[path]); - range_of_path_[path] = {.begin = num_current_elements_, - .end = nodes_.size()}; - changed_paths_.Set(path); + DCHECK_LT(path, range_of_path_.Size()); + DCHECK(!range_of_path_.HasChanged(path)); + range_of_path_.Set(path, + {.begin = num_elements_.Get(), .end = nodes_.size()}); // Allocate dimension values. We allocate n cells for all dimension values, // even transits, so they can all be indexed by the same range_of_path. transit_.resize(nodes_.size(), Interval::AllIntegers()); travel_.resize(nodes_.size(), 0); travel_sum_.resize(nodes_.size(), 0); cumul_.resize(nodes_.size(), Interval::AllIntegers()); - num_current_elements_ = nodes_.size(); - span_[path] = Interval::AllIntegers(); + num_elements_.Set(nodes_.size()); + span_.Set(path, Interval::AllIntegers()); } // Resets all path to empty, in both committed and current state. void Reset() { - const int num_paths = range_of_path_.size(); - range_of_path_.assign(num_paths, {.begin = 0, .end = 0}); - committed_range_of_path_.assign(num_paths, {.begin = 0, .end = 0}); - changed_paths_.SparseClearAll(); - num_current_elements_ = 0; - num_committed_elements_ = 0; + range_of_path_.SetAllAndCommit({.begin = 0, .end = 0}); + num_elements_.SetAndCommit(0); nodes_.clear(); transit_.clear(); travel_.clear(); travel_sum_.clear(); cumul_.clear(); - committed_span_.assign(num_paths, Interval::AllIntegers()); + span_.SetAllAndCommit(Interval::AllIntegers()); } // Clears the changed state, make it point to the committed state. void Revert() { - for (const int path : changed_paths_.PositionsSetAtLeastOnce()) { - range_of_path_[path] = committed_range_of_path_[path]; - } - changed_paths_.SparseClearAll(); - num_current_elements_ = num_committed_elements_; - nodes_.resize(num_current_elements_); - transit_.resize(num_current_elements_); - travel_.resize(num_current_elements_); - travel_sum_.resize(num_current_elements_); - cumul_.resize(num_current_elements_); + range_of_path_.Revert(); + num_elements_.Revert(); + nodes_.resize(num_elements_.Get()); + transit_.resize(num_elements_.Get()); + travel_.resize(num_elements_.Get()); + travel_sum_.resize(num_elements_.Get()); + cumul_.resize(num_elements_.Get()); + span_.Revert(); } // Makes the committed state point to the current state. // If the state representation is too large, reclaims memory by compacting // the committed state. void Commit() { - for (const int path : changed_paths_.PositionsSetAtLeastOnce()) { - committed_range_of_path_[path] = range_of_path_[path]; - committed_span_[path] = span_[path]; + for (const int path : range_of_path_.ChangedIndices()) { committed_vehicle_breaks_[path] = vehicle_breaks_[path]; } - changed_paths_.SparseClearAll(); - num_committed_elements_ = num_current_elements_; + range_of_path_.Commit(); + num_elements_.Commit(); + span_.Commit(); // If the committed data would take too much space, compact the data: // copy committed data to the end of vectors, erase old data, refresh // indexing (range_of_path_). - if (num_current_elements_ <= max_num_committed_elements_) return; + if (num_elements_.Get() <= max_num_committed_elements_) return; temp_nodes_.clear(); temp_transit_.clear(); temp_travel_.clear(); temp_travel_sum_.clear(); temp_cumul_.clear(); - for (int path = 0; path < range_of_path_.size(); ++path) { - if (committed_range_of_path_[path].Size() == 0) continue; + const int num_paths = range_of_path_.Size(); + for (int path = 0; path < num_paths; ++path) { + if (range_of_path_.GetCommitted(path).Size() == 0) continue; const size_t new_begin = temp_nodes_.size(); - const auto [begin, end] = committed_range_of_path_[path]; + const auto [begin, end] = range_of_path_.GetCommitted(path); temp_nodes_.insert(temp_nodes_.end(), nodes_.begin() + begin, nodes_.begin() + end); temp_transit_.insert(temp_transit_.end(), transit_.begin() + begin, @@ -340,34 +338,32 @@ class DimensionValues { travel_sum_.begin() + end); temp_cumul_.insert(temp_cumul_.end(), cumul_.begin() + begin, cumul_.begin() + end); - committed_range_of_path_[path] = {.begin = new_begin, - .end = temp_nodes_.size()}; + range_of_path_.Set(path, {.begin = new_begin, .end = temp_nodes_.size()}); } std::swap(nodes_, temp_nodes_); std::swap(transit_, temp_transit_); std::swap(travel_, temp_travel_); std::swap(travel_sum_, temp_travel_sum_); std::swap(cumul_, temp_cumul_); - range_of_path_ = committed_range_of_path_; - num_committed_elements_ = nodes_.size(); - num_current_elements_ = nodes_.size(); + range_of_path_.Commit(); + num_elements_.SetAndCommit(nodes_.size()); } // Returns a const view of the nodes of the path, in the committed state. absl::Span CommittedNodes(int path) const { - const auto [begin, end] = committed_range_of_path_[path]; + const auto [begin, end] = range_of_path_.GetCommitted(path); return absl::MakeConstSpan(nodes_.data() + begin, nodes_.data() + end); } // Returns a const view of the nodes of the path, in the current state. absl::Span Nodes(int path) const { - const auto [begin, end] = range_of_path_[path]; + const auto [begin, end] = range_of_path_.Get(path); return absl::MakeConstSpan(nodes_.data() + begin, nodes_.data() + end); } // Returns a const view of the transits of the path, in the current state. absl::Span Transits(int path) const { - auto [begin, end] = range_of_path_[path]; + auto [begin, end] = range_of_path_.Get(path); // When the path is not empty, #transits = #nodes - 1. // When the path is empty, begin = end, return empty span. if (begin < end) --end; @@ -376,7 +372,7 @@ class DimensionValues { // Returns a mutable view of the transits of the path, in the current state. absl::Span MutableTransits(int path) { - auto [begin, end] = range_of_path_[path]; + auto [begin, end] = range_of_path_.Get(path); // When the path is not empty, #transits = #nodes - 1. // When the path is empty, begin = end, return empty span. if (begin < end) --end; @@ -385,65 +381,64 @@ class DimensionValues { // Returns a const view of the travels of the path, in the committed state. absl::Span CommittedTravels(int path) const { - auto [begin, end] = committed_range_of_path_[path]; + auto [begin, end] = range_of_path_.GetCommitted(path); if (begin < end) --end; return absl::MakeConstSpan(travel_.data() + begin, travel_.data() + end); } // Returns a const view of the travels of the path, in the current state. absl::Span Travels(int path) const { - auto [begin, end] = range_of_path_[path]; + auto [begin, end] = range_of_path_.Get(path); if (begin < end) --end; return absl::MakeConstSpan(travel_.data() + begin, travel_.data() + end); } // Returns a mutable view of the travels of the path, in the current state. absl::Span MutableTravels(int path) { - auto [begin, end] = range_of_path_[path]; + auto [begin, end] = range_of_path_.Get(path); if (begin < end) --end; return absl::MakeSpan(travel_.data() + begin, travel_.data() + end); } // Returns a const view of the travel sums of the path, in the current state. absl::Span TravelSums(int path) const { - const auto [begin, end] = range_of_path_[path]; + const auto [begin, end] = range_of_path_.Get(path); return absl::MakeConstSpan(travel_sum_.data() + begin, travel_sum_.data() + end); } // Returns a mutable view of the travel sums of the path in the current state. absl::Span MutableTravelSums(int path) { - const auto [begin, end] = range_of_path_[path]; + const auto [begin, end] = range_of_path_.Get(path); return absl::MakeSpan(travel_sum_.data() + begin, travel_sum_.data() + end); } // Returns a const view of the cumul mins of the path, in the current state. absl::Span Cumuls(int path) const { - const auto [begin, end] = range_of_path_[path]; + const auto [begin, end] = range_of_path_.Get(path); return absl::MakeConstSpan(cumul_.data() + begin, cumul_.data() + end); } // Returns a mutable view of the cumul mins of the path, in the current state. absl::Span MutableCumuls(int path) { - const auto [begin, end] = range_of_path_[path]; + const auto [begin, end] = range_of_path_.Get(path); return absl::MakeSpan(cumul_.data() + begin, cumul_.data() + end); } // Returns the span interval of the path, in the current state. - Interval Span(int path) const { - return changed_paths_[path] ? span_[path] : committed_span_[path]; - } + Interval Span(int path) const { return span_.Get(path); } + // Returns a mutable view of the span of the path, in the current state. // The path must have been changed since the last commit. Interval& MutableSpan(int path) { - DCHECK(changed_paths_[path]); - return span_[path]; + DCHECK(range_of_path_.HasChanged(path)); + return span_.GetMutable(path); } // Returns a const view of the vehicle breaks of the path, in the current // state. absl::Span VehicleBreaks(int path) const { - return absl::MakeConstSpan(changed_paths_[path] + return absl::MakeConstSpan(range_of_path_.HasChanged(path) ? vehicle_breaks_[path] : committed_vehicle_breaks_[path]); } @@ -451,18 +446,20 @@ class DimensionValues { // Returns a mutable vector of the vehicle breaks of the path, in the current // state. The path must have been changed since the last commit. std::vector& MutableVehicleBreaks(int path) { - DCHECK(changed_paths_[path]); + DCHECK(range_of_path_.HasChanged(path)); return vehicle_breaks_[path]; } // Returns the number of nodes of the path, in the current state. - int NumNodes(int path) const { return range_of_path_[path].Size(); } + int NumNodes(int path) const { return range_of_path_.Get(path).Size(); } // Returns a const view of the set of paths changed, in the current state. - absl::Span ChangedPaths() const { - return absl::MakeConstSpan(changed_paths_.PositionsSetAtLeastOnce()); + absl::Span ChangedPaths() const { + return absl::MakeConstSpan(range_of_path_.ChangedIndices()); } // Returns whether the given path was changed, in the current state. - bool PathHasChanged(int path) const { return changed_paths_[path]; } + bool PathHasChanged(int path) const { + return range_of_path_.HasChanged(path); + } private: // These vectors hold the data of both committed and current states. @@ -496,23 +493,168 @@ class DimensionValues { size_t end = 0; int Size() const { return end - begin; } }; - std::vector range_of_path_; - std::vector committed_range_of_path_; + CommittableVector range_of_path_; // Associates span to each path. - std::vector span_; - std::vector committed_span_; + CommittableVector span_; // Associates vehicle breaks with each path. + // TODO(user): turns this into a committable vector. std::vector> vehicle_breaks_; std::vector> committed_vehicle_breaks_; - // Stores whether each path has been changed since last committed state. - SparseBitset changed_paths_; // Threshold for the size of the committed vector. This is purely heuristic: // it should be more than the number of nodes so compactions do not occur at // each submit, but ranges should not be too far apart to avoid cache misses. const size_t max_num_committed_elements_; // This locates the start of new nodes. - size_t num_current_elements_ = 0; - size_t num_committed_elements_ = 0; + CommittableValue num_elements_; +}; + +class PrePostVisitValues { + public: + PrePostVisitValues(int num_paths, int num_nodes) + : range_of_path_(num_paths, {.begin = 0, .end = 0}), + max_num_committed_elements_(16 * num_nodes), + num_elements_(0) { + pre_visit_.reserve(max_num_committed_elements_); + post_visit_.reserve(max_num_committed_elements_); + } + + using Interval = DimensionValues::Interval; + + // Turns new nodes into a new path, allocating pre-post visit values for it. + void ChangePathSize(int path, int new_num_nodes) { + DCHECK_GE(path, 0); + DCHECK_LT(path, range_of_path_.Size()); + DCHECK(!range_of_path_.HasChanged(path)); + size_t num_current_elements = num_elements_.Get(); + range_of_path_.Set(path, {.begin = num_current_elements, + .end = num_current_elements + new_num_nodes}); + // Allocate dimension values. We allocate n cells for all dimension values, + // even transits, so they can all be indexed by the same range_of_path. + num_current_elements += new_num_nodes; + pre_visit_.resize(num_current_elements, 0); + post_visit_.resize(num_current_elements, 0); + num_elements_.Set(num_current_elements); + } + + // Resets all path to empty, in both committed and current state. + void Reset() { + range_of_path_.SetAllAndCommit({.begin = 0, .end = 0}); + num_elements_.SetAndCommit(0); + pre_visit_.clear(); + post_visit_.clear(); + } + + // Clears the changed state, makes it point to the committed state. + void Revert() { + range_of_path_.Revert(); + num_elements_.Revert(); + pre_visit_.resize(num_elements_.Get()); + post_visit_.resize(num_elements_.Get()); + } + + // Makes the committed state point to the current state. + // If the state representation is too large, reclaims memory by compacting + // the committed state. + void Commit() { + range_of_path_.Commit(); + num_elements_.Commit(); + // If the committed data would take too much space, compact the data: + // copy committed data to the end of vectors, erase old data, refresh + // indexing (range_of_path_). + if (num_elements_.Get() <= max_num_committed_elements_) return; + temp_pre_visit_.clear(); + temp_post_visit_.clear(); + for (int path = 0; path < range_of_path_.Size(); ++path) { + if (range_of_path_.GetCommitted(path).Size() == 0) continue; + const size_t new_begin = temp_pre_visit_.size(); + const auto [begin, end] = range_of_path_.GetCommitted(path); + temp_pre_visit_.insert(temp_pre_visit_.end(), pre_visit_.begin() + begin, + pre_visit_.begin() + end); + temp_post_visit_.insert(temp_post_visit_.end(), + post_visit_.begin() + begin, + post_visit_.begin() + end); + range_of_path_.Set(path, + {.begin = new_begin, .end = temp_pre_visit_.size()}); + } + std::swap(pre_visit_, temp_pre_visit_); + std::swap(post_visit_, temp_post_visit_); + range_of_path_.Commit(); + num_elements_.SetAndCommit(pre_visit_.size()); + } + + // Returns a const view of the pre-visits of the path, in the committed state. + absl::Span CommittedPreVisits(int path) const { + auto [begin, end] = range_of_path_.GetCommitted(path); + return absl::MakeConstSpan(pre_visit_.data() + begin, + pre_visit_.data() + end); + } + + // Returns a const view of the pre-visits of the path, in the current state. + absl::Span PreVisits(int path) const { + auto [begin, end] = range_of_path_.Get(path); + return absl::MakeConstSpan(pre_visit_.data() + begin, + pre_visit_.data() + end); + } + + // Returns a mutable view of the pre-visits of the path, in the current state. + absl::Span MutablePreVisits(int path) { + auto [begin, end] = range_of_path_.Get(path); + return absl::MakeSpan(pre_visit_.data() + begin, pre_visit_.data() + end); + } + + // Returns a const view of the post-visits of the path, in the committed + // state. + absl::Span CommittedPostVisits(int path) const { + auto [begin, end] = range_of_path_.GetCommitted(path); + return absl::MakeConstSpan(post_visit_.data() + begin, + post_visit_.data() + end); + } + + // Returns a const view of the post-visits of the path, in the current state. + absl::Span PostVisits(int path) const { + const auto [begin, end] = range_of_path_.Get(path); + return absl::MakeConstSpan(post_visit_.data() + begin, + post_visit_.data() + end); + } + + // Returns a mutable view of the post-visits of the path in the current state. + absl::Span MutablePostVisits(int path) { + const auto [begin, end] = range_of_path_.Get(path); + return absl::MakeSpan(post_visit_.data() + begin, post_visit_.data() + end); + } + + // Returns the number of nodes of the path, in the current state. + int NumNodes(int path) const { return range_of_path_.Get(path).Size(); } + // Returns a const view of the set of paths changed, in the current state. + absl::Span ChangedPaths() const { + return absl::MakeConstSpan(range_of_path_.ChangedIndices()); + } + // Returns whether the given path was changed, in the current state. + bool PathHasChanged(int path) const { + return range_of_path_.HasChanged(path); + } + + private: + // These vectors hold the data. + std::vector pre_visit_; + std::vector post_visit_; + // Temporary vectors used in Commit() during compaction. + std::vector temp_pre_visit_; + std::vector temp_post_visit_; + // A path has a range of indices in the committed state and another one in the + // current state. + struct Range { + size_t begin = 0; + size_t end = 0; + int Size() const { return end - begin; } + }; + CommittableVector range_of_path_; + // Threshold for the size of the committed vector. This is purely heuristic: + // it should be more than the number of nodes so compactions do not occur at + // each submit, but ranges should not be too far apart to avoid cache misses. + const size_t max_num_committed_elements_; + // This locates the start of new nodes. + CommittableValue num_elements_; }; } // namespace operations_research::routing diff --git a/ortools/routing/filters.cc b/ortools/routing/filters.cc index 5a0dd60c42..5c434ce64b 100644 --- a/ortools/routing/filters.cc +++ b/ortools/routing/filters.cc @@ -2120,9 +2120,7 @@ void PathCumulFilter::OnBeforeSynchronizePaths(bool synchronizing_all_paths) { // OnSynchronizePathFromStart() calls recompute everything. Otherwise we let // the InitializeAcceptPath() call below revert the data structures. dimension_values_.Reset(); - cost_of_path_.Revert(); cost_of_path_.SetAllAndCommit(0); - location_of_node_.Revert(); location_of_node_.SetAllAndCommit({-1, -1}); global_span_cost_.SetAndCommit(0); synchronized_objective_value_ = 0; // Accept() relies on this value. @@ -2492,7 +2490,6 @@ PickupDeliveryFilter::PickupDeliveryFilter( } void PickupDeliveryFilter::Reset() { - assigned_status_of_pair_.Revert(); assigned_status_of_pair_.SetAllAndCommit({}); num_assigned_pairs_.SetAndCommit(0); } diff --git a/ortools/routing/search.cc b/ortools/routing/search.cc index 9aa07a81ec..a1c047ea7d 100644 --- a/ortools/routing/search.cc +++ b/ortools/routing/search.cc @@ -876,7 +876,8 @@ void CheapestInsertionFilteredHeuristic::InsertBetween(int64_t node, } } -int64_t CheapestInsertionFilteredHeuristic::GetInsertionCostForNodeAtPosition( +int64_t +CheapestInsertionFilteredHeuristic::GetEvaluatorInsertionCostForNodeAtPosition( int64_t node_to_insert, int64_t insert_after, int64_t insert_before, int vehicle) const { DCHECK(evaluator_ != nullptr); @@ -891,6 +892,45 @@ int64_t CheapestInsertionFilteredHeuristic::GetInsertionCostForNodeAtPosition( cache_entry.value); } +std::optional +CheapestInsertionFilteredHeuristic::GetInsertionCostForNodeAtPosition( + int64_t node_to_insert, int64_t insert_after, int64_t insert_before, + int vehicle, int hint_weight) { + if (evaluator_ != nullptr) { + return GetEvaluatorInsertionCostForNodeAtPosition( + node_to_insert, insert_after, insert_before, vehicle); + } + InsertBetween(node_to_insert, insert_after, insert_before, vehicle); + return Evaluate(/*commit=*/false, /*ignore_upper_bound=*/hint_weight > 0, + /*update_upper_bound=*/hint_weight >= 0); +} + +std::optional +CheapestInsertionFilteredHeuristic::GetInsertionCostForPairAtPositions( + int64_t pickup, int64_t pickup_insert_after, int64_t delivery, + int64_t delivery_insert_after, int vehicle, int hint_weight) { + DCHECK_GE(pickup_insert_after, 0); + const int64_t pickup_insert_before = Value(pickup_insert_after); + DCHECK_GE(delivery_insert_after, 0); + const int64_t delivery_insert_before = (delivery_insert_after == pickup) + ? pickup_insert_before + : Value(delivery_insert_after); + if (evaluator_ != nullptr) { + const int64_t pickup_cost = GetEvaluatorInsertionCostForNodeAtPosition( + pickup, pickup_insert_after, pickup_insert_before, vehicle); + const int64_t delivery_cost = GetEvaluatorInsertionCostForNodeAtPosition( + delivery, delivery_insert_after, delivery_insert_before, vehicle); + return CapAdd(pickup_cost, delivery_cost); + } + + InsertBetween(pickup, pickup_insert_after, pickup_insert_before, vehicle); + InsertBetween(delivery, delivery_insert_after, delivery_insert_before, + vehicle); + return Evaluate(/*commit=*/false, + /*ignore_upper_bound=*/hint_weight > 0, + /*update_upper_bound=*/hint_weight >= 0); +} + int64_t CheapestInsertionFilteredHeuristic::GetUnperformedValue( int64_t node_to_insert) const { if (penalty_evaluator_ != nullptr) { @@ -1273,7 +1313,10 @@ bool GlobalCheapestInsertionFilteredHeuristic:: model()->Start(next_fixed_cost_empty_vehicle)); pickup_to_entries->at(pair_entry->pickup_insert_after()).insert(pair_entry); DCHECK_EQ(pair_entry->delivery_insert_after(), pickup); - UpdatePairEntry(pair_entry, priority_queue); + if (!UpdatePairEntry(pair_entry, priority_queue)) { + DeletePairEntry(pair_entry, priority_queue, pickup_to_entries, + delivery_to_entries); + } } else { DeletePairEntry(pair_entry, priority_queue, pickup_to_entries, delivery_to_entries); @@ -1484,6 +1527,7 @@ bool GlobalCheapestInsertionFilteredHeuristic:: const NodeEntryQueue::Entry* node_entry = queue->Top(); const int entry_vehicle = node_entry->vehicle; DCHECK(UseEmptyVehicleTypeCuratorForVehicle(entry_vehicle, all_vehicles)); + DCHECK(evaluator_ != nullptr); // Trying to insert on an empty vehicle, and all vehicles are being // considered simultaneously. @@ -1567,7 +1611,7 @@ bool GlobalCheapestInsertionFilteredHeuristic:: // previous entry_vehicle. queue->Pop(); const int64_t insert_after = model()->Start(next_fixed_cost_empty_vehicle); - const int64_t insertion_cost = GetInsertionCostForNodeAtPosition( + const int64_t insertion_cost = GetEvaluatorInsertionCostForNodeAtPosition( node_to_insert, insert_after, Value(insert_after), next_fixed_cost_empty_vehicle); const int64_t penalty_shift = @@ -1639,19 +1683,21 @@ void GlobalCheapestInsertionFilteredHeuristic::DetectUsedVehicles( bool GlobalCheapestInsertionFilteredHeuristic::IsCheapestClassRepresentative( int vehicle) const { - if (VehicleIsEmpty(vehicle)) { - // We only consider the least expensive empty vehicle of each type for - // entries of the same vehicle class. - const int curator_vehicle = - empty_vehicle_type_curator_->GetLowestFixedCostVehicleOfType( - empty_vehicle_type_curator_->Type(vehicle)); - if (curator_vehicle != vehicle && - model()->GetVehicleClassIndexOfVehicle(curator_vehicle).value() == - model()->GetVehicleClassIndexOfVehicle(vehicle).value()) { - return false; - } + if (evaluator_ == nullptr) { + // When no evaluator_ is given, filters are used to evaluate the cost and + // feasibility of inserting on each vehicle, so we don't use only one + // class representative for each class. + return true; } - return true; + if (!VehicleIsEmpty(vehicle)) return true; + // We only consider the least expensive empty vehicle of each type for + // entries of the same vehicle class. + const int curator_vehicle = + empty_vehicle_type_curator_->GetLowestFixedCostVehicleOfType( + empty_vehicle_type_curator_->Type(vehicle)); + return curator_vehicle == vehicle || + model()->GetVehicleClassIndexOfVehicle(curator_vehicle).value() != + model()->GetVehicleClassIndexOfVehicle(vehicle).value(); } void GlobalCheapestInsertionFilteredHeuristic::InsertFarthestNodesAsSeeds() { @@ -1801,8 +1847,6 @@ void GlobalCheapestInsertionFilteredHeuristic:: while (!model()->IsEnd(pickup_insert_after)) { int64_t delivery_insert_after = pickup; while (!model()->IsEnd(delivery_insert_after)) { - // TODO(user): Add a check to filter feasibility of the insertion - // when the evaluator_ is null. AddPairEntry(pickup, pickup_insert_after, delivery, delivery_insert_after, vehicle, priority_queue, pickup_to_entries, delivery_to_entries); @@ -1966,7 +2010,9 @@ bool GlobalCheapestInsertionFilteredHeuristic::UpdateExistingPairEntriesOnChain( .contains(pair_entry)); DCHECK(delivery_to_entries->at(pair_entry->delivery_insert_after()) .contains(pair_entry)); - UpdatePairEntry(pair_entry, priority_queue); + if (!UpdatePairEntry(pair_entry, priority_queue)) { + to_remove.push_back(pair_entry); + } } } } @@ -2098,7 +2144,7 @@ void GlobalCheapestInsertionFilteredHeuristic::AddPairEntry( std::vector* pickup_entries, std::vector* - delivery_entries) const { + delivery_entries) { const IntVar* pickup_vehicle_var = model()->VehicleVar(pickup); const IntVar* delivery_vehicle_var = model()->VehicleVar(delivery); if (!pickup_vehicle_var->Contains(vehicle) || @@ -2119,25 +2165,31 @@ void GlobalCheapestInsertionFilteredHeuristic::AddPairEntry( } const int num_allowed_vehicles = std::min(pickup_vehicle_var->Size(), delivery_vehicle_var->Size()); + const int64_t pair_penalty = + CapAdd(GetUnperformedValue(pickup), GetUnperformedValue(delivery)); + const int64_t penalty_shift = + absl::GetFlag(FLAGS_routing_shift_insertion_cost_by_penalty) + ? pair_penalty + : 0; if (pickup_insert_after == -1) { DCHECK_EQ(delivery_insert_after, -1); DCHECK_EQ(vehicle, -1); PairEntry* pair_entry = pair_entry_allocator_.NewEntry( pickup, -1, delivery, -1, -1, num_allowed_vehicles); - pair_entry->set_value( - absl::GetFlag(FLAGS_routing_shift_insertion_cost_by_penalty) - ? 0 - : CapAdd(GetUnperformedValue(pickup), - GetUnperformedValue(delivery))); + pair_entry->set_value(CapSub(pair_penalty, penalty_shift)); priority_queue->Add(pair_entry); return; } + const std::optional insertion_cost = + GetInsertionCostForPairAtPositions(pickup, pickup_insert_after, delivery, + delivery_insert_after, vehicle); + + if (!insertion_cost.has_value()) return; PairEntry* const pair_entry = pair_entry_allocator_.NewEntry( pickup, pickup_insert_after, delivery, delivery_insert_after, vehicle, num_allowed_vehicles); - pair_entry->set_value(GetInsertionValueForPairAtPositions( - pickup, pickup_insert_after, delivery, delivery_insert_after, vehicle)); + pair_entry->set_value(CapSub(*insertion_cost, penalty_shift)); // Add entry to priority_queue and pickup_/delivery_entries. DCHECK(!priority_queue->Contains(pair_entry)); @@ -2146,42 +2198,32 @@ void GlobalCheapestInsertionFilteredHeuristic::AddPairEntry( priority_queue->Add(pair_entry); } -void GlobalCheapestInsertionFilteredHeuristic::UpdatePairEntry( +bool GlobalCheapestInsertionFilteredHeuristic::UpdatePairEntry( GlobalCheapestInsertionFilteredHeuristic::PairEntry* const pair_entry, AdjustablePriorityQueue< - GlobalCheapestInsertionFilteredHeuristic::PairEntry>* priority_queue) - const { - pair_entry->set_value(GetInsertionValueForPairAtPositions( - pair_entry->pickup_to_insert(), pair_entry->pickup_insert_after(), - pair_entry->delivery_to_insert(), pair_entry->delivery_insert_after(), - pair_entry->vehicle())); + GlobalCheapestInsertionFilteredHeuristic::PairEntry>* priority_queue) { + const int64_t pickup = pair_entry->pickup_to_insert(); + const int64_t delivery = pair_entry->delivery_to_insert(); + const std::optional insertion_cost = + GetInsertionCostForPairAtPositions( + pickup, pair_entry->pickup_insert_after(), delivery, + pair_entry->delivery_insert_after(), pair_entry->vehicle()); + const int64_t penalty_shift = + absl::GetFlag(FLAGS_routing_shift_insertion_cost_by_penalty) && + insertion_cost.has_value() + ? CapAdd(GetUnperformedValue(pickup), GetUnperformedValue(delivery)) + : 0; + + if (!insertion_cost.has_value()) { + return false; + } + + pair_entry->set_value(CapSub(*insertion_cost, penalty_shift)); // Update the priority_queue. DCHECK(priority_queue->Contains(pair_entry)); priority_queue->NoteChangedPriority(pair_entry); -} - -int64_t -GlobalCheapestInsertionFilteredHeuristic::GetInsertionValueForPairAtPositions( - int64_t pickup, int64_t pickup_insert_after, int64_t delivery, - int64_t delivery_insert_after, int vehicle) const { - DCHECK_GE(pickup_insert_after, 0); - const int64_t pickup_insert_before = Value(pickup_insert_after); - const int64_t pickup_value = GetInsertionCostForNodeAtPosition( - pickup, pickup_insert_after, pickup_insert_before, vehicle); - - DCHECK_GE(delivery_insert_after, 0); - const int64_t delivery_insert_before = (delivery_insert_after == pickup) - ? pickup_insert_before - : Value(delivery_insert_after); - const int64_t delivery_value = GetInsertionCostForNodeAtPosition( - delivery, delivery_insert_after, delivery_insert_before, vehicle); - - const int64_t penalty_shift = - absl::GetFlag(FLAGS_routing_shift_insertion_cost_by_penalty) - ? CapAdd(GetUnperformedValue(pickup), GetUnperformedValue(delivery)) - : 0; - return CapSub(CapAdd(pickup_value, delivery_value), penalty_shift); + return true; } bool GlobalCheapestInsertionFilteredHeuristic::InitializePositions( @@ -2230,8 +2272,6 @@ void GlobalCheapestInsertionFilteredHeuristic:: int64_t insert_after = model()->Start(vehicle); while (insert_after != model()->End(vehicle)) { - // TODO(user): Add a check to filter feasibility of the insertion - // when the evaluator_ is null. AddNodeEntry(node, insert_after, vehicle, all_vehicles, queue); insert_after = Value(insert_after); } @@ -2335,15 +2375,10 @@ bool GlobalCheapestInsertionFilteredHeuristic::AddNodeEntriesAfter( void GlobalCheapestInsertionFilteredHeuristic::AddNodeEntry( int64_t node, int64_t insert_after, int vehicle, bool all_vehicles, - NodeEntryQueue* queue) const { - const int64_t node_penalty = GetUnperformedValue(node); - const int64_t penalty_shift = - absl::GetFlag(FLAGS_routing_shift_insertion_cost_by_penalty) - ? node_penalty - : 0; + NodeEntryQueue* queue) { const IntVar* const vehicle_var = model()->VehicleVar(node); if (!vehicle_var->Contains(vehicle)) { - if (vehicle == -1 || !VehicleIsEmpty(vehicle)) return; + if (!UseEmptyVehicleTypeCuratorForVehicle(vehicle, all_vehicles)) return; // We need to check there is not an equivalent empty vehicle the node // could fit on. const auto vehicle_is_compatible = [vehicle_var](int vehicle) { @@ -2356,6 +2391,11 @@ void GlobalCheapestInsertionFilteredHeuristic::AddNodeEntry( } } const int num_allowed_vehicles = vehicle_var->Size(); + const int64_t node_penalty = GetUnperformedValue(node); + const int64_t penalty_shift = + absl::GetFlag(FLAGS_routing_shift_insertion_cost_by_penalty) + ? node_penalty + : 0; if (vehicle == -1) { DCHECK_EQ(node, insert_after); if (!all_vehicles) { @@ -2369,17 +2409,18 @@ void GlobalCheapestInsertionFilteredHeuristic::AddNodeEntry( return; } - const int64_t insertion_cost = GetInsertionCostForNodeAtPosition( - node, insert_after, Value(insert_after), vehicle); - if (!all_vehicles && insertion_cost > node_penalty) { + const std::optional insertion_cost = + GetInsertionCostForNodeAtPosition(node, insert_after, Value(insert_after), + vehicle); + if (!insertion_cost.has_value()) return; + if (!all_vehicles && *insertion_cost > node_penalty) { // NOTE: When all vehicles aren't considered for insertion, we don't // add entries making nodes unperformed, so we don't add insertions // which cost more than the node penalty either. return; } - queue->PushInsertion(node, insert_after, vehicle, num_allowed_vehicles, - CapSub(insertion_cost, penalty_shift)); + CapSub(*insertion_cost, penalty_shift)); } void InsertionSequenceGenerator::AppendPickupDeliveryMultitourInsertions( @@ -3117,46 +3158,9 @@ void LocalCheapestInsertionFilteredHeuristic::InsertBestPairMultitour( }; // Fills value field of all insertions, kint64max if unevaluable. - auto price_insertion_sequences_evaluator = - [this](BinCapacities* bin_capacities) { - for (InsertionSequence sequence : insertion_container_) { - int64_t sequence_cost = 0; - int previous_node = -1; - int previous_succ = -1; - int hint_weight = 0; - for (const Insertion& insertion : sequence) { - const int succ = previous_node == insertion.pred - ? previous_succ - : Value(insertion.pred); - hint_weight += IsHint(insertion.pred, insertion.node); - hint_weight += IsHint(insertion.node, succ); - const int64_t cost = GetInsertionCostForNodeAtPosition( - insertion.node, insertion.pred, succ, sequence.Vehicle()); - CapAddTo(cost, &sequence_cost); - previous_node = insertion.node; - previous_succ = succ; - } - sequence.Cost() = sequence_cost; - sequence.SetHintWeight(hint_weight); - } - if (bin_capacities == nullptr) return; - for (InsertionSequence sequence : insertion_container_) { - const int64_t old_cost = bin_capacities->TotalCost(); - for (const Insertion& insertion : sequence) { - bin_capacities->AddItemToBin(insertion.node, sequence.Vehicle()); - } - const int64_t new_cost = bin_capacities->TotalCost(); - const int64_t delta_cost = CapSub(new_cost, old_cost); - CapAddTo(delta_cost, &sequence.Cost()); - for (const Insertion& insertion : sequence) { - bin_capacities->RemoveItemFromBin(insertion.node, - sequence.Vehicle()); - } - } - }; - - auto price_insertion_sequences_no_evaluator = [this]() { + auto price_insertion_sequences = [this]() { for (InsertionSequence sequence : insertion_container_) { + int64_t sequence_cost = 0; int previous_node = -1; int previous_succ = -1; int hint_weight = 0; @@ -3166,15 +3170,38 @@ void LocalCheapestInsertionFilteredHeuristic::InsertBestPairMultitour( : Value(insertion.pred); hint_weight += IsHint(insertion.pred, insertion.node); hint_weight += IsHint(insertion.node, succ); - InsertBetween(insertion.node, insertion.pred, succ, sequence.Vehicle()); + if (evaluator_ == nullptr) { + InsertBetween(insertion.node, insertion.pred, succ, + sequence.Vehicle()); + } else { + const auto cost = GetEvaluatorInsertionCostForNodeAtPosition( + insertion.node, insertion.pred, succ, sequence.Vehicle()); + CapAddTo(cost, &sequence_cost); + } previous_node = insertion.node; previous_succ = succ; } - sequence.Cost() = - Evaluate(/*commit=*/false, /*ignore_upper_bound=*/hint_weight > 0) - .value_or(kint64max); + if (evaluator_ == nullptr) { + sequence_cost = + Evaluate(/*commit=*/false, /*ignore_upper_bound=*/hint_weight > 0) + .value_or(kint64max); + } + sequence.Cost() = sequence_cost; sequence.SetHintWeight(hint_weight); } + if (bin_capacities_ == nullptr || evaluator_ == nullptr) return; + for (InsertionSequence sequence : insertion_container_) { + const int64_t old_cost = bin_capacities_->TotalCost(); + for (const Insertion& insertion : sequence) { + bin_capacities_->AddItemToBin(insertion.node, sequence.Vehicle()); + } + const int64_t new_cost = bin_capacities_->TotalCost(); + const int64_t delta_cost = CapSub(new_cost, old_cost); + CapAddTo(delta_cost, &sequence.Cost()); + for (const Insertion& insertion : sequence) { + bin_capacities_->RemoveItemFromBin(insertion.node, sequence.Vehicle()); + } + } }; for (int pickup : pair.pickup_alternatives) { @@ -3199,11 +3226,7 @@ void LocalCheapestInsertionFilteredHeuristic::InsertBestPairMultitour( path_node_is_delivery, insertion_container_); } if (StopSearch()) return; - if (evaluator_ == nullptr) { - price_insertion_sequences_no_evaluator(); - } else { - price_insertion_sequences_evaluator(bin_capacities_); - } + price_insertion_sequences(); if (StopSearch()) return; insertion_container_.RemoveIf( [](const InsertionSequence& sequence) -> bool { @@ -3356,20 +3379,11 @@ void LocalCheapestInsertionFilteredHeuristic::AppendInsertionPositionsAfter( const int hint_weight = IsHint(insert_after, node_to_insert) + IsHint(node_to_insert, insert_before) - IsHint(insert_after, insert_before); - if (evaluator_ == nullptr) { - InsertBetween(node_to_insert, insert_after, insert_before, vehicle); - std::optional insertion_cost = - Evaluate(/*commit=*/false, /*ignore_upper_bound=*/hint_weight > 0, - /*update_upper_bound=*/hint_weight >= 0); - if (insertion_cost.has_value()) { - node_insertions->push_back( - {insert_after, vehicle, -hint_weight, *insertion_cost}); - } - } else { + const auto insertion_cost = GetInsertionCostForNodeAtPosition( + node_to_insert, insert_after, insert_before, vehicle, hint_weight); + if (insertion_cost.has_value()) { node_insertions->push_back( - {insert_after, vehicle, -hint_weight, - GetInsertionCostForNodeAtPosition(node_to_insert, insert_after, - insert_before, vehicle)}); + {insert_after, vehicle, -hint_weight, *insertion_cost}); } insert_after = insert_before; } @@ -3459,25 +3473,12 @@ LocalCheapestInsertionFilteredHeuristic::ComputeEvaluatorSortedPairPositions( IsHint(insert_delivery_after, delivery) + IsHint(pickup, insert_pickup_before) + IsHint(delivery, insert_delivery_before); - if (evaluator_ == nullptr) { - InsertBetween(pickup, insert_pickup_after, insert_pickup_before, - vehicle); - InsertBetween(delivery, insert_delivery_after, insert_delivery_before, - vehicle); - std::optional insertion_cost = Evaluate( - /*commit=*/false, /*ignore_upper_bound=*/hint_weight > 0); - if (insertion_cost.has_value()) { - sorted_pickup_delivery_insertions.push_back( - {insert_pickup_after, insert_delivery_after, -hint_weight, - *insertion_cost, vehicle}); - } - } else { - const int64_t pickup_cost = GetInsertionCostForNodeAtPosition( - pickup, insert_pickup_after, insert_pickup_before, vehicle); - const int64_t delivery_cost = GetInsertionCostForNodeAtPosition( - delivery, insert_delivery_after, insert_delivery_before, vehicle); - int64_t total_cost = CapAdd(pickup_cost, delivery_cost); - if (MustUpdateBinCapacities()) { + const auto insertion_cost = GetInsertionCostForPairAtPositions( + pickup, insert_pickup_after, delivery, insert_delivery_after, + vehicle, hint_weight); + if (insertion_cost.has_value()) { + int64_t total_cost = *insertion_cost; + if (evaluator_ && MustUpdateBinCapacities()) { const int64_t old_cost = bin_capacities_->TotalCost(); bin_capacities_->AddItemToBin(pickup, vehicle); bin_capacities_->AddItemToBin(delivery, vehicle); diff --git a/ortools/routing/search.h b/ortools/routing/search.h index e671fb966a..02fcf8f2e3 100644 --- a/ortools/routing/search.h +++ b/ortools/routing/search.h @@ -447,15 +447,27 @@ class CheapestInsertionFilteredHeuristic : public RoutingFilteredHeuristic { void InsertBetween(int64_t node, int64_t predecessor, int64_t successor, int vehicle = -1); /// Returns the cost of inserting 'node_to_insert' between 'insert_after' and - /// 'insert_before' on the 'vehicle', i.e. - /// Cost(insert_after-->node) + Cost(node-->insert_before) - /// - Cost (insert_after-->insert_before). + /// 'insert_before' on the 'vehicle' when the evaluator_ is defined, i.e. + /// evaluator_(insert_after-->node) + evaluator_(node-->insert_before) + /// - evaluator_(insert_after-->insert_before). // TODO(user): Replace 'insert_before' and 'insert_after' by 'predecessor' // and 'successor' in the code. - int64_t GetInsertionCostForNodeAtPosition(int64_t node_to_insert, - int64_t insert_after, - int64_t insert_before, - int vehicle) const; + int64_t GetEvaluatorInsertionCostForNodeAtPosition(int64_t node_to_insert, + int64_t insert_after, + int64_t insert_before, + int vehicle) const; + /// Same as above, except that when the evaluator_ is not defined, the cost is + /// determined by Evaluate-ing the insertion of the node through the filter + /// manager, returning std::nullopt when the insertion is not feasible. + std::optional GetInsertionCostForNodeAtPosition( + int64_t node_to_insert, int64_t insert_after, int64_t insert_before, + int vehicle, int hint_weight = 0); + /// Same as above for the insertion of a pickup/delivery pair at the given + /// positions. + std::optional GetInsertionCostForPairAtPositions( + int64_t pickup_to_insert, int64_t pickup_insert_after, + int64_t delivery_to_insert, int64_t delivery_insert_after, int vehicle, + int hint_weight = 0); /// Returns the cost of unperforming node 'node_to_insert'. Returns kint64max /// if penalty callback is null or if the node cannot be unperformed. int64_t GetUnperformedValue(int64_t node_to_insert) const; @@ -642,8 +654,12 @@ class GlobalCheapestInsertionFilteredHeuristic /// nodes/pairs on the given vehicle, i.e. iff the route of the given vehicle /// is empty and 'all_vehicles' is true. bool UseEmptyVehicleTypeCuratorForVehicle(int vehicle, - bool all_vehicles = true) { - return vehicle >= 0 && VehicleIsEmpty(vehicle) && all_vehicles; + bool all_vehicles = true) const { + // NOTE: When the evaluator_ is null, filters are used to evaluate the cost + // and feasibility of inserting on each vehicle, so all vehicles are + // considered for insertion instead of just one per class. + return vehicle >= 0 && VehicleIsEmpty(vehicle) && all_vehicles && + evaluator_ != nullptr; } /// Tries to insert the pickup/delivery pair on a vehicle of the same type and @@ -804,20 +820,14 @@ class GlobalCheapestInsertionFilteredHeuristic int vehicle, AdjustablePriorityQueue* priority_queue, std::vector* pickup_entries, - std::vector* delivery_entries) const; + std::vector* delivery_entries); /// Updates the pair entry's value and rearranges the priority queue /// accordingly. - void UpdatePairEntry( - PairEntry* pair_entry, - AdjustablePriorityQueue* priority_queue) const; - /// Computes and returns the insertion value of inserting 'pickup' and - /// 'delivery' respectively after 'pickup_insert_after' and - /// 'delivery_insert_after' on 'vehicle'. - int64_t GetInsertionValueForPairAtPositions(int64_t pickup, - int64_t pickup_insert_after, - int64_t delivery, - int64_t delivery_insert_after, - int vehicle) const; + /// Returns true iff the pair entry was correctly updated, otherwise returns + /// false which indicates the pair entry should be removed from the priority + /// queue. + bool UpdatePairEntry(PairEntry* pair_entry, + AdjustablePriorityQueue* priority_queue); /// Initializes the priority queue and the node entries with the current state /// of the solution on the given vehicle routes. @@ -855,7 +865,7 @@ class GlobalCheapestInsertionFilteredHeuristic /// 'insert_after' on 'vehicle' and adds it to the 'queue' and /// 'node_entries'. void AddNodeEntry(int64_t node, int64_t insert_after, int vehicle, - bool all_vehicles, NodeEntryQueue* queue) const; + bool all_vehicles, NodeEntryQueue* queue); void ResetVehicleIndices() override { node_index_to_vehicle_.assign(node_index_to_vehicle_.size(), -1);