[Routing] improve filters

This commit is contained in:
Laurent Perron
2025-03-02 15:14:16 +01:00
parent eb1207f0cb
commit 1f32572d5b
4 changed files with 401 additions and 251 deletions

View File

@@ -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<const int> 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<const int> 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<const Interval> 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<Interval> 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<const int64_t> 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<const int64_t> 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<int64_t> 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<const int64_t> 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<int64_t> 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<const Interval> 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<Interval> 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<const VehicleBreak> 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<VehicleBreak>& 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<const int> ChangedPaths() const {
return absl::MakeConstSpan(changed_paths_.PositionsSetAtLeastOnce());
absl::Span<const size_t> 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> range_of_path_;
std::vector<Range> committed_range_of_path_;
CommittableVector<Range> range_of_path_;
// Associates span to each path.
std::vector<Interval> span_;
std::vector<Interval> committed_span_;
CommittableVector<Interval> span_;
// Associates vehicle breaks with each path.
// TODO(user): turns this into a committable vector.
std::vector<std::vector<VehicleBreak>> vehicle_breaks_;
std::vector<std::vector<VehicleBreak>> committed_vehicle_breaks_;
// Stores whether each path has been changed since last committed state.
SparseBitset<int> 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<size_t> 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<const int64_t> 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<const int64_t> 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<int64_t> 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<const int64_t> 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<const int64_t> 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<int64_t> 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<const size_t> 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<int64_t> pre_visit_;
std::vector<int64_t> post_visit_;
// Temporary vectors used in Commit() during compaction.
std::vector<int64_t> temp_pre_visit_;
std::vector<int64_t> 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> 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<size_t> num_elements_;
};
} // namespace operations_research::routing

View File

@@ -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);
}

View File

@@ -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<int64_t>
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<int64_t>
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<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
pickup_entries,
std::vector<GlobalCheapestInsertionFilteredHeuristic::PairEntries>*
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<int64_t> 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<int64_t> 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<int64_t> 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<int64_t> 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<int64_t> 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);

View File

@@ -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<int64_t> 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<int64_t> 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<PairEntry>* priority_queue,
std::vector<PairEntries>* pickup_entries,
std::vector<PairEntries>* delivery_entries) const;
std::vector<PairEntries>* delivery_entries);
/// Updates the pair entry's value and rearranges the priority queue
/// accordingly.
void UpdatePairEntry(
PairEntry* pair_entry,
AdjustablePriorityQueue<PairEntry>* 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<PairEntry>* 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);