Files
ortools-clone/ortools/constraint_solver/routing_search.h
Corentin Le Molgat b4b226801b update include guards
2025-11-05 11:54:02 +01:00

1541 lines
67 KiB
C++

// Copyright 2010-2025 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef ORTOOLS_CONSTRAINT_SOLVER_ROUTING_SEARCH_H_
#define ORTOOLS_CONSTRAINT_SOLVER_ROUTING_SEARCH_H_
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <deque>
#include <functional>
#include <initializer_list>
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <queue>
#include <random>
#include <set>
#include <string>
#include <tuple>
#include <type_traits>
#include <utility>
#include <vector>
#include "absl/container/flat_hash_set.h"
#include "absl/container/inlined_vector.h"
#include "absl/log/check.h"
#include "absl/types/span.h"
#include "ortools/base/adjustable_priority_queue.h"
#include "ortools/constraint_solver/constraint_solver.h"
#include "ortools/constraint_solver/constraint_solveri.h"
#include "ortools/constraint_solver/routing.h"
#include "ortools/constraint_solver/routing_enums.pb.h"
#include "ortools/constraint_solver/routing_heuristic_parameters.pb.h"
#include "ortools/constraint_solver/routing_parameters.pb.h"
#include "ortools/constraint_solver/routing_types.h"
#include "ortools/constraint_solver/routing_utils.h"
#include "ortools/util/bitset.h"
namespace operations_research {
// Solves a routing model using alternative models. This assumes that the models
// are equivalent in the sense that a solution to one model is also a solution
// to the other models. This is true for models that differ only by their arc
// costs or objective for instance.
// The primary model is the main model, to which the returned solution will
// correspond.
// The method solves the primary model and alternative models alternatively.
// It works as follows (all solves use 'parameters'):
// 1) solve the primary model with a greedy descent,
// 2) let 'alt' be the first alternative model,
// 3) solve 'alt' starting from the solution to the primary model with a greedy
// descent,
// 4) solve the primary model from the solution to 'alt' with a greedy descent,
// 5) if the new solution improves the best solution found so far, update it,
// otherwise increase the iteration counter,
// 6) if the iteration counter is less than 'max_non_improving_iterations', let
// 'alt' be the next "round-robin" alternative model, and go to step 3,
// 7) if 'parameters' specified a metaheuristic, solve the primary model using
// that metaheuristic starting from the best solution found so far,
// 8) return the best solution found.
// Note that if the time limit is reached at any stage, the search is
// interrupted and the best solution found will be returned immediately.
// TODO(user): Add a version taking search parameters for alternative models.
const Assignment* SolveWithAlternativeSolvers(
RoutingModel* primary_model,
const std::vector<RoutingModel*>& alternative_models,
const RoutingSearchParameters& parameters,
int max_non_improving_iterations);
// Same as above, but taking an initial solution.
const Assignment* SolveFromAssignmentWithAlternativeSolvers(
const Assignment* assignment, RoutingModel* primary_model,
const std::vector<RoutingModel*>& alternative_models,
const RoutingSearchParameters& parameters,
int max_non_improving_iterations);
// Same as above but taking alternative parameters for each alternative model.
const Assignment* SolveFromAssignmentWithAlternativeSolversAndParameters(
const Assignment* assignment, RoutingModel* primary_model,
const RoutingSearchParameters& parameters,
const std::vector<RoutingModel*>& alternative_models,
const std::vector<RoutingSearchParameters>& alternative_parameters,
int max_non_improving_iterations);
class IntVarFilteredHeuristic;
#ifndef SWIG
/// Helper class that manages vehicles. This class is based on the
/// RoutingModel::VehicleTypeContainer that sorts and stores vehicles based on
/// their "type".
class VehicleTypeCurator {
public:
explicit VehicleTypeCurator(
const RoutingModel::VehicleTypeContainer& vehicle_type_container)
: vehicle_type_container_(&vehicle_type_container) {}
int NumTypes() const { return vehicle_type_container_->NumTypes(); }
int Type(int vehicle) const { return vehicle_type_container_->Type(vehicle); }
/// Resets the vehicles stored, storing only vehicles from the
/// vehicle_type_container_ for which store_vehicle() returns true.
void Reset(const std::function<bool(int)>& store_vehicle);
/// Goes through all the currently stored vehicles and removes vehicles for
/// which remove_vehicle() returns true.
void Update(const std::function<bool(int)>& remove_vehicle);
int GetLowestFixedCostVehicleOfType(int type) const {
DCHECK_LT(type, NumTypes());
const std::set<VehicleClassEntry>& vehicle_classes =
sorted_vehicle_classes_per_type_[type];
if (vehicle_classes.empty()) {
return -1;
}
const int vehicle_class = (vehicle_classes.begin())->vehicle_class;
DCHECK(!vehicles_per_vehicle_class_[vehicle_class].empty());
return vehicles_per_vehicle_class_[vehicle_class][0];
}
void ReinjectVehicleOfClass(int vehicle, int vehicle_class,
int64_t fixed_cost) {
std::vector<int>& vehicles = vehicles_per_vehicle_class_[vehicle_class];
if (vehicles.empty()) {
/// Add the vehicle class entry to the set (it was removed when
/// vehicles_per_vehicle_class_[vehicle_class] got empty).
std::set<VehicleClassEntry>& vehicle_classes =
sorted_vehicle_classes_per_type_[Type(vehicle)];
const auto& insertion =
vehicle_classes.insert({vehicle_class, fixed_cost});
DCHECK(insertion.second);
}
vehicles.push_back(vehicle);
}
/// Searches a compatible vehicle of the given type; returns false if none was
/// found.
bool HasCompatibleVehicleOfType(
int type, const std::function<bool(int)>& vehicle_is_compatible) const;
/// Searches for the best compatible vehicle of the given type, i.e. the first
/// vehicle v of type 'type' for which vehicle_is_compatible(v) returns true.
/// If a compatible *vehicle* is found, its index is removed from
/// vehicles_per_vehicle_class_ and the function returns {vehicle, -1}.
/// If for some *vehicle* 'stop_and_return_vehicle' returns true before a
/// compatible vehicle is found, the function simply returns {-1, vehicle}.
/// Returns {-1, -1} if no compatible vehicle is found and the stopping
/// condition is never met.
std::pair<int, int> GetCompatibleVehicleOfType(
int type, const std::function<bool(int)>& vehicle_is_compatible,
const std::function<bool(int)>& stop_and_return_vehicle);
private:
using VehicleClassEntry =
RoutingModel::VehicleTypeContainer::VehicleClassEntry;
const RoutingModel::VehicleTypeContainer* const vehicle_type_container_;
// clang-format off
std::vector<std::set<VehicleClassEntry> > sorted_vehicle_classes_per_type_;
std::vector<std::vector<int> > vehicles_per_vehicle_class_;
// clang-format on
};
/// Returns the best value for the automatic first solution strategy, based on
/// the given model parameters.
operations_research::FirstSolutionStrategy::Value
AutomaticFirstSolutionStrategy(bool has_pickup_deliveries,
bool has_node_precedences,
bool has_single_vehicle_node);
/// Computes and returns the first node in the end chain of each vehicle in the
/// model, based on the current bound NextVar values.
std::vector<int64_t> ComputeVehicleEndChainStarts(const RoutingModel& model);
/// Decision builder building a solution using heuristics with local search
/// filters to evaluate its feasibility. This is very fast but can eventually
/// fail when the solution is restored if filters did not detect all
/// infeasiblities.
/// More details:
/// Using local search filters to build a solution. The approach is pretty
/// straight-forward: have a general assignment storing the current solution,
/// build delta assignment representing possible extensions to the current
/// solution and validate them with filters.
/// The tricky bit comes from using the assignment and filter APIs in a way
/// which avoids the lazy creation of internal hash_maps between variables
/// and indices.
/// Generic filter-based decision builder using an IntVarFilteredHeuristic.
// TODO(user): Eventually move this to the core CP solver library
/// when the code is mature enough.
class IntVarFilteredDecisionBuilder : public DecisionBuilder {
public:
explicit IntVarFilteredDecisionBuilder(
std::unique_ptr<IntVarFilteredHeuristic> heuristic);
~IntVarFilteredDecisionBuilder() override = default;
Decision* Next(Solver* solver) override;
std::string DebugString() const override;
/// Returns statistics from its underlying heuristic.
int64_t number_of_decisions() const;
int64_t number_of_rejects() const;
private:
const std::unique_ptr<IntVarFilteredHeuristic> heuristic_;
};
/// Generic filter-based heuristic applied to IntVars.
class IntVarFilteredHeuristic {
public:
IntVarFilteredHeuristic(Solver* solver, const std::vector<IntVar*>& vars,
const std::vector<IntVar*>& secondary_vars,
LocalSearchFilterManager* filter_manager);
virtual ~IntVarFilteredHeuristic() = default;
/// Builds a solution. Returns the resulting assignment if a solution was
/// found, and nullptr otherwise.
Assignment* BuildSolution();
/// Returns statistics on search, number of decisions sent to filters, number
/// of decisions rejected by filters.
int64_t number_of_decisions() const { return number_of_decisions_; }
int64_t number_of_rejects() const { return number_of_rejects_; }
virtual std::string DebugString() const { return "IntVarFilteredHeuristic"; }
protected:
/// Resets the data members for a new solution.
void ResetSolution();
/// Initialize the heuristic; called before starting to build a new solution.
virtual void Initialize() {}
/// Virtual method to initialize the solution.
virtual bool InitializeSolution() { return true; }
/// Virtual method to redefine how to build a solution.
virtual bool BuildSolutionInternal() = 0;
/// Evaluates the modifications to the current solution. If these
/// modifications are "filter-feasible" returns their corresponding cost
/// computed by filters.
/// If 'commit' is true, the modifications are committed to the current
/// solution.
/// In any case all modifications to the internal delta are cleared before
/// returning.
std::optional<int64_t> Evaluate(bool commit, bool ignore_upper_bound = false,
bool update_upper_bound = true);
/// Returns true if the search must be stopped.
virtual bool StopSearch() { return false; }
/// Modifies the current solution by setting the variable of index 'index' to
/// value 'value'.
void SetValue(int64_t index, int64_t value) {
DCHECK_LT(index, is_in_delta_.size());
if (!is_in_delta_[index]) {
delta_->FastAdd(vars_[index])->SetValue(value);
delta_indices_.push_back(index);
is_in_delta_[index] = true;
} else {
delta_->SetValue(vars_[index], value);
}
}
/// Returns the indices of the nodes currently in the insertion delta.
const std::vector<int>& delta_indices() const { return delta_indices_; }
/// Returns the value of the variable of index 'index' in the last committed
/// solution.
int64_t Value(int64_t index) const {
return assignment_->IntVarContainer().Element(index).Value();
}
/// Returns true if the variable of index 'index' is in the current solution.
bool Contains(int64_t index) const {
return assignment_->IntVarContainer().Element(index).Var() != nullptr;
}
/// Returns the variable of index 'index'.
IntVar* Var(int64_t index) const { return vars_[index]; }
/// Returns the index of a secondary var.
int64_t SecondaryVarIndex(int64_t index) const {
DCHECK(HasSecondaryVars());
return index + base_vars_size_;
}
/// Returns true if there are secondary variables.
bool HasSecondaryVars() const { return base_vars_size_ != vars_.size(); }
/// Returns true if 'index' is a secondary variable index.
bool IsSecondaryVar(int64_t index) const { return index >= base_vars_size_; }
/// Synchronizes filters with an assignment (the current solution).
void SynchronizeFilters();
Assignment* const assignment_;
private:
/// Checks if filters accept a given modification to the current solution
/// (represented by delta).
bool FilterAccept(bool ignore_upper_bound);
Solver* solver_;
std::vector<IntVar*> vars_;
const int base_vars_size_;
Assignment* const delta_;
std::vector<int> delta_indices_;
std::vector<bool> is_in_delta_;
Assignment* const empty_;
LocalSearchFilterManager* filter_manager_;
int64_t objective_upper_bound_;
/// Stats on search
int64_t number_of_decisions_;
int64_t number_of_rejects_;
};
/// Filter-based heuristic dedicated to routing.
class RoutingFilteredHeuristic : public IntVarFilteredHeuristic {
public:
RoutingFilteredHeuristic(RoutingModel* model,
std::function<bool()> stop_search,
LocalSearchFilterManager* filter_manager);
~RoutingFilteredHeuristic() override = default;
/// Builds a solution starting from the routes formed by the next accessor.
Assignment* BuildSolutionFromRoutes(
const std::function<int64_t(int64_t)>& next_accessor);
RoutingModel* model() const { return model_; }
/// Returns the end of the start chain of vehicle,
int GetStartChainEnd(int vehicle) const { return start_chain_ends_[vehicle]; }
/// Returns the start of the end chain of vehicle,
int GetEndChainStart(int vehicle) const { return end_chain_starts_[vehicle]; }
/// Make nodes in the same disjunction as 'node' unperformed. 'node' is a
/// variable index corresponding to a node.
void MakeDisjunctionNodesUnperformed(int64_t node);
/// Adds all unassigned nodes to empty vehicles.
void AddUnassignedNodesToEmptyVehicles();
/// Make all unassigned nodes unperformed, always returns true.
bool MakeUnassignedNodesUnperformed();
/// Make all partially performed pickup and delivery pairs unperformed. A
/// pair is partially unperformed if one element of the pair has one of its
/// alternatives performed in the solution and the other has no alternatives
/// in the solution or none performed.
void MakePartiallyPerformedPairsUnperformed();
protected:
bool StopSearch() override { return stop_search_(); }
virtual void SetVehicleIndex(int64_t /*node*/, int /*vehicle*/) {}
virtual void ResetVehicleIndices() {}
bool VehicleIsEmpty(int vehicle) const {
return Value(model()->Start(vehicle)) == model()->End(vehicle);
}
void SetNext(int64_t node, int64_t next, int vehicle) {
SetValue(node, next);
if (HasSecondaryVars()) SetValue(SecondaryVarIndex(node), vehicle);
}
private:
/// Initializes the current solution with empty or partial vehicle routes.
bool InitializeSolution() override;
RoutingModel* const model_;
std::function<bool()> stop_search_;
std::vector<int64_t> start_chain_ends_;
std::vector<int64_t> end_chain_starts_;
};
class CheapestInsertionFilteredHeuristic : public RoutingFilteredHeuristic {
public:
/// Takes ownership of evaluator.
CheapestInsertionFilteredHeuristic(
RoutingModel* model, std::function<bool()> stop_search,
std::function<int64_t(int64_t, int64_t, int64_t)> evaluator,
std::function<int64_t(int64_t)> penalty_evaluator,
LocalSearchFilterManager* filter_manager);
~CheapestInsertionFilteredHeuristic() override = default;
protected:
struct StartEndValue {
int64_t distance;
int vehicle;
bool operator<(const StartEndValue& other) const {
return std::tie(distance, vehicle) <
std::tie(other.distance, other.vehicle);
}
};
struct EvaluatorCache {
int64_t value = 0;
int64_t node = -1;
int vehicle = -1;
};
struct Seed {
absl::InlinedVector<int64_t, 8> properties;
int vehicle;
/// Indicates whether this Seed corresponds to a pair or a single node.
/// If false, the 'index' is the pair_index, otherwise it's the node index.
bool is_node_index = true;
int index;
bool operator>(const Seed& other) const {
for (size_t i = 0; i < properties.size(); ++i) {
if (properties[i] == other.properties[i]) continue;
return properties[i] > other.properties[i];
}
return std::tie(vehicle, is_node_index, index) >
std::tie(other.vehicle, other.is_node_index, other.index);
}
};
// clang-format off
struct SeedQueue {
explicit SeedQueue(bool prioritize_farthest_nodes) :
prioritize_farthest_nodes(prioritize_farthest_nodes) {}
/// By default, the priority is given (hierarchically) to nodes with lower
/// number of allowed vehicles, higher penalty and lower start/end distance.
std::priority_queue<Seed, std::vector<Seed>, std::greater<Seed> >
priority_queue;
/// When 'prioritize_farthest_nodes' is true, the start/end distance is
/// inverted so higher priority is given to farther nodes.
const bool prioritize_farthest_nodes;
};
/// Computes and returns the distance of each uninserted node to every vehicle
/// in 'vehicles' as a std::vector<std::vector<StartEndValue>>,
/// start_end_distances_per_node.
/// For each node, start_end_distances_per_node[node] is sorted in decreasing
/// order.
std::vector<std::vector<StartEndValue> >
ComputeStartEndDistanceForVehicles(absl::Span<const int> vehicles);
/// Initializes sq->priority_queue by inserting the best entry corresponding
/// to each node, i.e. the last element of start_end_distances_per_node[node],
/// which is supposed to be sorted in decreasing order.
void InitializeSeedQueue(
std::vector<std::vector<StartEndValue> >* start_end_distances_per_node,
SeedQueue* sq);
// clang-format on
/// Adds a Seed corresponding to the given 'node' to sq.priority_queue, based
/// on the last entry in its 'start_end_distances' (from which it's deleted).
void AddSeedNodeToQueue(int node,
std::vector<StartEndValue>* start_end_distances,
SeedQueue* sq);
/// Inserts 'node' just after 'predecessor', and just before 'successor' on
/// the route of 'vehicle', resulting in the following subsequence:
/// predecessor -> node -> successor.
/// If 'node' is part of a disjunction, other nodes of the disjunction are
/// made unperformed.
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' 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 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;
bool HasHintedNext(int node) const {
CHECK_LT(node, hint_next_values_.size());
return hint_next_values_[node] != -1;
}
bool HasHintedPrev(int node) const {
CHECK_LT(node, hint_prev_values_.size());
return hint_prev_values_[node] != -1;
}
bool IsHint(int node, int64_t next) const {
return node < hint_next_values_.size() && hint_next_values_[node] == next;
}
std::function<int64_t(int64_t, int64_t, int64_t)> evaluator_;
// TODO(user): Remove mutable if possible.
mutable std::vector<EvaluatorCache> evaluator_cache_;
std::function<int64_t(int64_t)> penalty_evaluator_;
std::vector<int> hint_next_values_;
std::vector<int> hint_prev_values_;
};
/// Filter-based decision builder which builds a solution by inserting
/// nodes at their cheapest position on any route; potentially several routes
/// can be built in parallel. The cost of a position is computed from an
/// arc-based cost callback. The node selected for insertion is the one which
/// minimizes insertion cost. If a non null penalty evaluator is passed, making
/// nodes unperformed is also taken into account with the corresponding penalty
/// cost.
class GlobalCheapestInsertionFilteredHeuristic
: public CheapestInsertionFilteredHeuristic {
public:
struct GlobalCheapestInsertionParameters {
/// Whether the routes are constructed sequentially or in parallel.
bool is_sequential;
/// The ratio of routes on which to insert farthest nodes as seeds before
/// starting the cheapest insertion.
double farthest_seeds_ratio;
/// If neighbors_ratio < 1 then for each node only this ratio of its
/// neighbors leading to the smallest arc costs are considered for
/// insertions, with a minimum of 'min_neighbors':
/// num_closest_neighbors = max(min_neighbors, neighbors_ratio*N),
/// where N is the number of non-start/end nodes in the model.
double neighbors_ratio;
int64_t min_neighbors;
/// If true, only closest neighbors (see neighbors_ratio and min_neighbors)
/// are considered as insertion positions during initialization. Otherwise,
/// all possible insertion positions are considered.
bool use_neighbors_ratio_for_initialization;
/// If true, entries are created for making the nodes/pairs unperformed, and
/// when the cost of making a node unperformed is lower than all insertions,
/// the node/pair will be made unperformed. If false, only entries making
/// a node/pair performed are considered.
bool add_unperformed_entries;
};
/// Takes ownership of evaluators.
GlobalCheapestInsertionFilteredHeuristic(
RoutingModel* model, std::function<bool()> stop_search,
std::function<int64_t(int64_t, int64_t, int64_t)> evaluator,
std::function<int64_t(int64_t)> penalty_evaluator,
LocalSearchFilterManager* filter_manager,
GlobalCheapestInsertionParameters parameters);
~GlobalCheapestInsertionFilteredHeuristic() override = default;
bool BuildSolutionInternal() override;
std::string DebugString() const override {
return "GlobalCheapestInsertionFilteredHeuristic";
}
private:
/// Priority queue entries used by global cheapest insertion heuristic.
class NodeEntryQueue;
/// Entry in priority queue containing the insertion positions of a node pair.
class PairEntry {
public:
PairEntry(int pickup_to_insert, int pickup_insert_after,
int delivery_to_insert, int delivery_insert_after, int vehicle,
int64_t bucket)
: value_(std::numeric_limits<int64_t>::max()),
heap_index_(-1),
pickup_to_insert_(pickup_to_insert),
pickup_insert_after_(pickup_insert_after),
delivery_to_insert_(delivery_to_insert),
delivery_insert_after_(delivery_insert_after),
vehicle_(vehicle),
bucket_(bucket) {}
// Note: for compatibility reasons, comparator follows tie-breaking rules
// used in the first version of GlobalCheapestInsertion.
bool operator<(const PairEntry& other) const {
// We give higher priority to insertions from lower buckets.
if (bucket_ != other.bucket_) {
return bucket_ > other.bucket_;
}
// We then compare by value, then we favor insertions (vehicle != -1).
// The rest of the tie-breaking is done with std::tie.
if (value_ != other.value_) {
return value_ > other.value_;
}
if ((vehicle_ == -1) ^ (other.vehicle_ == -1)) {
return vehicle_ == -1;
}
return std::tie(pickup_insert_after_, pickup_to_insert_,
delivery_insert_after_, delivery_to_insert_, vehicle_) >
std::tie(other.pickup_insert_after_, other.pickup_to_insert_,
other.delivery_insert_after_, other.delivery_to_insert_,
other.vehicle_);
}
void SetHeapIndex(int h) { heap_index_ = h; }
int GetHeapIndex() const { return heap_index_; }
void set_value(int64_t value) { value_ = value; }
int pickup_to_insert() const { return pickup_to_insert_; }
int pickup_insert_after() const { return pickup_insert_after_; }
void set_pickup_insert_after(int pickup_insert_after) {
pickup_insert_after_ = pickup_insert_after;
}
int delivery_to_insert() const { return delivery_to_insert_; }
int delivery_insert_after() const { return delivery_insert_after_; }
int vehicle() const { return vehicle_; }
void set_vehicle(int vehicle) { vehicle_ = vehicle; }
private:
int64_t value_;
int heap_index_;
int pickup_to_insert_;
int pickup_insert_after_;
int delivery_to_insert_;
int delivery_insert_after_;
int vehicle_;
int64_t bucket_;
};
typedef absl::flat_hash_set<PairEntry*> PairEntries;
/// Priority queue entry allocator.
template <typename T>
class EntryAllocator {
public:
EntryAllocator() = default;
void Clear() {
entries_.clear();
free_entries_.clear();
}
template <typename... Args>
T* NewEntry(const Args&... args) {
if (!free_entries_.empty()) {
auto* entry = free_entries_.back();
free_entries_.pop_back();
*entry = T(args...);
return entry;
} else {
entries_.emplace_back(args...);
return &entries_.back();
}
}
void FreeEntry(T* entry) { free_entries_.push_back(entry); }
private:
/// std::deque references to elements are stable when extended.
std::deque<T> entries_;
std::vector<T*> free_entries_;
};
/// Inserts non-inserted single nodes or pickup/delivery pairs which have a
/// visit type in the type requirement graph, i.e. required for or requiring
/// another type for insertions.
/// These nodes are inserted iff the requirement graph is acyclic, in which
/// case nodes are inserted based on the topological order of their type,
/// given by the routing model's GetTopologicallySortedVisitTypes() method.
bool InsertPairsAndNodesByRequirementTopologicalOrder();
/// Inserts non-inserted single nodes or pickup/delivery pairs which are in
/// precedence constraints.
/// These nodes are inserted iff the precedence graph is acyclic, in which
/// case nodes are inserted based on the topological order of the precedence
/// graph, given by the routing model's
/// GetTopologicallySortedNodePrecedences() method.
bool InsertPairsAndNodesByPrecedenceTopologicalOrder();
/// Inserts non-inserted pickup and delivery pairs. Maintains a priority
/// queue of possible pair insertions, which is incrementally updated when a
/// pair insertion is committed. Incrementality is obtained by updating pair
/// insertion positions on the four newly modified route arcs: after the
/// pickup insertion position, after the pickup position, after the delivery
/// insertion position and after the delivery position.
bool InsertPairs(
const std::map<int64_t, std::vector<int>>& pair_indices_by_bucket);
/// Returns true iff the empty_vehicle_type_curator_ should be used to insert
/// 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) 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
/// same fixed cost as the pair_entry.vehicle() using the
/// empty_vehicle_type_curator_, and updates the pair_entry accordingly if the
/// insertion was not possible.
/// NOTE: Assumes (DCHECKS) that
/// UseEmptyVehicleTypeCuratorForVehicle(pair_entry.vehicle()) is true.
bool InsertPairEntryUsingEmptyVehicleTypeCurator(
const absl::flat_hash_set<int>& pair_indices, PairEntry* pair_entry,
AdjustablePriorityQueue<PairEntry>* priority_queue,
std::vector<PairEntries>* pickup_to_entries,
std::vector<PairEntries>* delivery_to_entries);
/// Inserts non-inserted individual nodes on the given routes (or all routes
/// if "vehicles" is an empty vector), by constructing routes in parallel.
/// Maintains a priority queue of possible insertions, which is incrementally
/// updated when an insertion is committed.
/// Incrementality is obtained by updating insertion positions on the two
/// newly modified route arcs: after the node insertion position and after the
/// node position.
bool InsertNodesOnRoutes(
const std::map<int64_t, std::vector<int>>& nodes_by_bucket,
const absl::flat_hash_set<int>& vehicles);
/// Tries to insert the node_entry.node_to_insert on a vehicle of the same
/// type and same fixed cost as the node_entry.vehicle() using the
/// empty_vehicle_type_curator_, and updates the node_entry accordingly if the
/// insertion was not possible.
/// NOTE: Assumes (DCHECKS) that
/// UseEmptyVehicleTypeCuratorForVehicle(node_entry.vehicle(), all_vehicles)
/// is true.
bool InsertNodeEntryUsingEmptyVehicleTypeCurator(
const SparseBitset<int>& nodes, bool all_vehicles, NodeEntryQueue* queue);
/// Inserts non-inserted individual nodes on routes by constructing routes
/// sequentially.
/// For each new route, the vehicle to use and the first node to insert on it
/// are given by calling InsertSeedNode(). The route is then completed with
/// other nodes by calling InsertNodesOnRoutes({vehicle}).
bool SequentialInsertNodes(
const std::map<int64_t, std::vector<int>>& nodes_by_bucket);
/// Goes through all vehicles in the model to check if they are already used
/// (i.e. Value(start) != end) or not.
/// Updates the three passed vectors accordingly.
void DetectUsedVehicles(std::vector<bool>* is_vehicle_used,
std::vector<int>* unused_vehicles,
absl::flat_hash_set<int>* used_vehicles);
/// Returns true of the vehicle's route is not empty or if the vehicle is the
/// representative of its class and type.
bool IsCheapestClassRepresentative(int vehicle) const;
/// Inserts the (farthest_seeds_ratio_ * model()->vehicles()) nodes farthest
/// from the start/ends of the available vehicle routes as seeds on their
/// closest route.
void InsertFarthestNodesAsSeeds();
/// Inserts a "seed node" based on the given priority_queue of Seeds.
/// A "seed" is the node used in order to start a new route.
/// If the Seed at the top of the priority queue cannot be inserted,
/// (node already inserted in the model, corresponding vehicle already used,
/// or unsuccessful Commit()), start_end_distances_per_node is updated and
/// used to insert a new entry for that node if necessary (next best vehicle).
/// If a seed node is successfully inserted, updates is_vehicle_used and
/// returns the vehice of the corresponding route. Returns -1 otherwise.
int InsertSeedNode(
std::vector<std::vector<StartEndValue>>* start_end_distances_per_node,
SeedQueue* sq, std::vector<bool>* is_vehicle_used);
// clang-format on
/// Initializes the priority queue and the pair entries for the given pair
/// indices with the current state of the solution.
bool InitializePairPositions(
const absl::flat_hash_set<int>& pair_indices,
AdjustablePriorityQueue<PairEntry>* priority_queue,
std::vector<PairEntries>* pickup_to_entries,
std::vector<PairEntries>* delivery_to_entries);
/// Adds insertion entries performing the 'pickup' and 'delivery', and updates
/// 'priority_queue', pickup_to_entries and delivery_to_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 'pickup' and/or 'delivery'.
void InitializeInsertionEntriesPerformingPair(
int64_t pickup, int64_t delivery,
AdjustablePriorityQueue<PairEntry>* priority_queue,
std::vector<PairEntries>* pickup_to_entries,
std::vector<PairEntries>* delivery_to_entries);
/// Performs all the necessary updates after a pickup/delivery pair was
/// successfully inserted on the 'vehicle', respectively after
/// 'pickup_position' and 'delivery_position'.
bool UpdateAfterPairInsertion(
const absl::flat_hash_set<int>& pair_indices, int vehicle, int64_t pickup,
int64_t pickup_position, int64_t delivery, int64_t delivery_position,
AdjustablePriorityQueue<PairEntry>* priority_queue,
std::vector<PairEntries>* pickup_to_entries,
std::vector<PairEntries>* delivery_to_entries);
/// Updates all existing pair entries inserting a node after 'insert_after'
/// and updates the priority queue accordingly.
bool UpdateExistingPairEntriesAfter(
int64_t insert_after, AdjustablePriorityQueue<PairEntry>* priority_queue,
std::vector<PairEntries>* pickup_to_entries,
std::vector<PairEntries>* delivery_to_entries);
/// Adds pair entries inserting either a pickup or a delivery after
/// "insert_after". When inserting pickups after "insert_after", will skip
/// entries inserting their delivery after
/// "skip_entries_inserting_delivery_after". This can be used to avoid the
/// insertion of redundant entries.
bool AddPairEntriesAfter(const absl::flat_hash_set<int>& pair_indices,
int vehicle, int64_t insert_after,
int64_t skip_entries_inserting_delivery_after,
AdjustablePriorityQueue<PairEntry>* priority_queue,
std::vector<PairEntries>* pickup_to_entries,
std::vector<PairEntries>* delivery_to_entries) {
return AddPairEntriesWithDeliveryAfter(pair_indices, vehicle, insert_after,
priority_queue, pickup_to_entries,
delivery_to_entries) &&
AddPairEntriesWithPickupAfter(pair_indices, vehicle, insert_after,
skip_entries_inserting_delivery_after,
priority_queue, pickup_to_entries,
delivery_to_entries);
}
/// Adds pair entries inserting a pickup after node "insert_after" and a
/// delivery in a position after the pickup, and updates the priority queue
/// accordingly.
/// Will skip entries inserting their delivery after
/// "skip_entries_inserting_delivery_after". This can be used to avoid the
/// insertion of redundant entries.
bool AddPairEntriesWithPickupAfter(
const absl::flat_hash_set<int>& pair_indices, int vehicle,
int64_t insert_after, int64_t skip_entries_inserting_delivery_after,
AdjustablePriorityQueue<PairEntry>* priority_queue,
std::vector<PairEntries>* pickup_to_entries,
std::vector<PairEntries>* delivery_to_entries);
/// Adds pair entries inserting a delivery after node "insert_after" and a
/// pickup in a position before "insert_after", and updates the priority queue
/// accordingly.
bool AddPairEntriesWithDeliveryAfter(
const absl::flat_hash_set<int>& pair_indices, int vehicle,
int64_t insert_after, AdjustablePriorityQueue<PairEntry>* priority_queue,
std::vector<PairEntries>* pickup_to_entries,
std::vector<PairEntries>* delivery_to_entries);
/// Deletes an entry, removing it from the priority queue and the appropriate
/// pickup and delivery entry sets.
void DeletePairEntry(PairEntry* entry,
AdjustablePriorityQueue<PairEntry>* priority_queue,
std::vector<PairEntries>* pickup_to_entries,
std::vector<PairEntries>* delivery_to_entries);
/// Creates a PairEntry corresponding to the insertion of 'pickup' and
/// 'delivery' respectively after 'pickup_insert_after' and
/// 'delivery_insert_after', and adds it to the 'priority_queue',
/// 'pickup_entries' and 'delivery_entries'.
void AddPairEntry(int64_t pickup, int64_t pickup_insert_after,
int64_t delivery, int64_t delivery_insert_after,
int vehicle,
AdjustablePriorityQueue<PairEntry>* priority_queue,
std::vector<PairEntries>* pickup_entries,
std::vector<PairEntries>* delivery_entries);
/// Updates the pair entry's value and rearranges the priority queue
/// accordingly.
/// 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.
bool InitializePositions(const SparseBitset<int>& nodes,
const absl::flat_hash_set<int>& vehicles,
NodeEntryQueue* queue);
/// Adds insertion entries performing 'node', and updates '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_t node, const absl::flat_hash_set<int>& vehicles,
NodeEntryQueue* queue);
/// Performs all the necessary updates after 'node' was successfully inserted
/// on the 'vehicle' after 'insert_after'.
bool UpdateAfterNodeInsertion(const SparseBitset<int>& nodes, int vehicle,
int64_t node, int64_t insert_after,
bool all_vehicles, NodeEntryQueue* queue);
/// Adds node entries inserting a node after "insert_after" and updates the
/// priority queue accordingly.
bool AddNodeEntriesAfter(const SparseBitset<int>& nodes, int vehicle,
int64_t insert_after, bool all_vehicles,
NodeEntryQueue* queue);
/// Creates a NodeEntry corresponding to the insertion of 'node' after
/// '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);
void ResetVehicleIndices() override {
node_index_to_vehicle_.assign(node_index_to_vehicle_.size(), -1);
}
void SetVehicleIndex(int64_t node, int vehicle) override {
DCHECK_LT(node, node_index_to_vehicle_.size());
node_index_to_vehicle_[node] = vehicle;
}
/// Function that verifies node_index_to_vehicle_ is correctly filled for all
/// nodes given the current routes.
bool CheckVehicleIndices() const;
/// Returns the bucket of a node.
int64_t GetBucketOfNode(int node) const {
return model()->VehicleVar(node)->Size();
}
/// Returns the bucket of a pair of pickup and delivery alternates.
int64_t GetBucketOfPair(const PickupDeliveryPair& pair) const {
int64_t max_pickup_bucket = 0;
for (int64_t pickup : pair.pickup_alternatives) {
max_pickup_bucket = std::max(max_pickup_bucket, GetBucketOfNode(pickup));
}
int64_t max_delivery_bucket = 0;
for (int64_t delivery : pair.delivery_alternatives) {
max_delivery_bucket =
std::max(max_delivery_bucket, GetBucketOfNode(delivery));
}
return std::min(max_pickup_bucket, max_delivery_bucket);
}
/// Checks if the search should be stopped (time limit reached), and cleans up
/// the priority queue if it's the case.
template <typename T>
bool StopSearchAndCleanup(AdjustablePriorityQueue<T>* priority_queue) {
if (!StopSearch()) return false;
if constexpr (std::is_same_v<T, PairEntry>) {
pair_entry_allocator_.Clear();
}
priority_queue->Clear();
return true;
}
GlobalCheapestInsertionParameters gci_params_;
/// Stores the vehicle index of each node in the current assignment.
std::vector<int> node_index_to_vehicle_;
const RoutingModel::NodeNeighborsByCostClass*
node_index_to_neighbors_by_cost_class_;
std::unique_ptr<VehicleTypeCurator> empty_vehicle_type_curator_;
// Temporary member used to keep track of node insertions wherever needed.
SparseBitset<int> temp_inserted_nodes_;
mutable EntryAllocator<PairEntry> pair_entry_allocator_;
};
// Holds sequences of insertions.
// A sequence of insertions must be in the same path, each insertion must
// take place either after the previously inserted node or further down the
// path, never before.
class InsertionSequenceContainer {
private:
// InsertionSequenceContainer holds all insertion sequences in the same vector
// contiguously, each insertion sequence is defined by a pair of bounds.
// Using Insertion* directly to delimit bounds would cause out-of-memory reads
// when the underlying vector<Insertion> is extended and reallocated,
// so this stores integer bounds in InsertionBounds to delimit sequences,
// and InsertionSequenceIterator translates those bounds to
// Insertion*-based ranges (InsertionSequence) on-the-fly when iterating over
// all sequences.
struct InsertionBounds {
size_t begin;
size_t end;
int vehicle;
int neg_hint_weight;
int64_t cost;
bool operator<(const InsertionBounds& other) const {
return std::tie(neg_hint_weight, cost, vehicle, begin) <
std::tie(other.neg_hint_weight, other.cost, other.vehicle,
other.begin);
}
size_t Size() const { return end - begin; }
};
public:
struct Insertion {
int pred;
int node;
bool operator==(const Insertion& other) const {
return pred == other.pred && node == other.node;
}
};
// Represents an insertion sequence as passed to AddInsertionSequence.
// This only allows to modify the cost, as a means to reorder sequences.
class InsertionSequence {
public:
InsertionSequence(Insertion* data, InsertionBounds* bounds)
: data_(data), bounds_(bounds) {}
bool operator!=(const InsertionSequence& other) const {
DCHECK_NE(data_, other.data_);
return bounds_ != other.bounds_;
}
const Insertion* begin() const { return data_ + bounds_->begin; }
const Insertion* end() const { return data_ + bounds_->end; }
size_t Size() const { return bounds_->Size(); }
int Vehicle() const { return bounds_->vehicle; }
int64_t Cost() const { return bounds_->cost; }
int64_t& Cost() { return bounds_->cost; }
void SetHintWeight(int hint_weight) {
bounds_->neg_hint_weight = -hint_weight;
}
int NegHintWeight() const { return bounds_->neg_hint_weight; }
private:
const Insertion* const data_;
InsertionBounds* const bounds_;
};
class InsertionSequenceIterator {
public:
InsertionSequenceIterator(Insertion* data, InsertionBounds* bounds)
: data_(data), bounds_(bounds) {}
bool operator!=(const InsertionSequenceIterator& other) const {
DCHECK_EQ(data_, other.data_);
return bounds_ != other.bounds_;
}
InsertionSequenceIterator& operator++() {
++bounds_;
return *this;
}
InsertionSequence operator*() const { return {data_, bounds_}; }
private:
Insertion* data_;
InsertionBounds* bounds_;
};
// InsertionSequenceContainer is a range over insertion sequences.
InsertionSequenceIterator begin() {
return {insertions_.data(), insertion_bounds_.data()};
}
InsertionSequenceIterator end() {
return {insertions_.data(),
insertion_bounds_.data() + insertion_bounds_.size()};
}
// Returns the number of sequences of this container.
size_t Size() const { return insertion_bounds_.size(); }
// Adds an insertion sequence to the container.
// Passing an initializer_list allows deeper optimizations by the compiler
// for cases where the sequence has a compile-time fixed size.
void AddInsertionSequence(
int vehicle, std::initializer_list<Insertion> insertion_sequence) {
insertion_bounds_.push_back(
{.begin = insertions_.size(),
.end = insertions_.size() + insertion_sequence.size(),
.vehicle = vehicle,
.cost = 0});
insertions_.insert(insertions_.end(), insertion_sequence.begin(),
insertion_sequence.end());
}
// Adds an insertion sequence to the container.
void AddInsertionSequence(int vehicle,
absl::Span<const Insertion> insertion_sequence) {
insertion_bounds_.push_back(
{.begin = insertions_.size(),
.end = insertions_.size() + insertion_sequence.size(),
.vehicle = vehicle,
.cost = 0});
insertions_.insert(insertions_.end(), insertion_sequence.begin(),
insertion_sequence.end());
}
// Similar to std::remove_if(), removes all sequences that match a predicate.
// This keeps original order, and removes selected sequences.
void RemoveIf(const std::function<bool(const InsertionSequence&)>& p) {
size_t from = 0;
size_t to = 0;
for (const InsertionSequence& sequence : *this) {
// TODO(user): Benchmark this against std::swap().
if (!p(sequence)) insertion_bounds_[to++] = insertion_bounds_[from];
++from;
}
insertion_bounds_.resize(to);
}
// Sorts sequences according to (cost, vehicle).
// TODO(user): benchmark this against other ways to get insertion
// sequence in order, for instance sorting by index, separating {cost, index},
// making a heap.
void Sort() { std::sort(insertion_bounds_.begin(), insertion_bounds_.end()); }
// Removes all sequences.
void Clear() {
insertions_.clear();
insertion_bounds_.clear();
}
private:
std::vector<Insertion> insertions_;
std::vector<InsertionBounds> insertion_bounds_;
};
// Generates insertion positions respecting structural constraints.
class InsertionSequenceGenerator {
public:
InsertionSequenceGenerator() = default;
/// Generates insertions for a pickup and delivery pair in a multitour path:
/// - a series of pickups may only start if all the deliveries of previous
/// pickups have been performed.
/// - given a maximal pickup*delivery* subsequence, either the pickups or the
/// deliveries are symmetric, meaning their order does not matter.
/// Under these specifications, this method generates all
/// unique insertions of the given pair that keep the multitour property.
/// Note that there are several ways one can extend pure pickup and delivery
/// problems to mixed paired and unpaired visits.
/// Two extreme views:
/// - multitour heuristics should consider that only consecutive pickups or
/// consecutive deliveries have a symmetry. Heuristics should only break
/// those symmetries, in the mixed case inserting a pair in a path of length
/// p has O(p²) possibilities.
/// - multitour heuristics should not consider unpaired visits. Insertions
/// are made on the subpath of paired nodes, all extensions to the original
/// path that conserve order are equivalent.
void AppendPickupDeliveryMultitourInsertions(
int pickup, int delivery, int vehicle, absl::Span<const int> path,
const std::vector<bool>& path_node_is_pickup,
const std::vector<bool>& path_node_is_delivery,
InsertionSequenceContainer& insertions);
private:
// Information[i] describes the insertion between path[i] and path[i+1].
std::vector<int> next_decrease_; // next position after a delivery.
std::vector<int> next_increase_; // next position after a pickup.
std::vector<int> prev_decrease_; // previous position after delivery.
std::vector<int> prev_increase_; // previous position after pickup.
};
struct PickupDeliveryInsertion {
int64_t insert_pickup_after;
int64_t insert_delivery_after;
int neg_hint_weight;
int64_t value;
int vehicle;
bool operator<(const PickupDeliveryInsertion& other) const {
return std::tie(neg_hint_weight, value, insert_pickup_after,
insert_delivery_after, vehicle) <
std::tie(other.neg_hint_weight, other.value,
other.insert_pickup_after, other.insert_delivery_after,
other.vehicle);
}
};
/// Filter-based decision builder which builds a solution by inserting
/// nodes at their cheapest position. The cost of a position is computed
/// an arc-based cost callback. Node selected for insertion are considered in
/// decreasing order of distance to the start/ends of the routes, i.e. farthest
/// nodes are inserted first.
class LocalCheapestInsertionFilteredHeuristic
: public CheapestInsertionFilteredHeuristic {
public:
/// Takes ownership of evaluator.
LocalCheapestInsertionFilteredHeuristic(
RoutingModel* model, std::function<bool()> stop_search,
std::function<int64_t(int64_t, int64_t, int64_t)> evaluator,
LocalCheapestInsertionParameters lci_params,
LocalSearchFilterManager* filter_manager, bool use_first_solution_hint,
BinCapacities* bin_capacities = nullptr,
std::function<bool(const std::vector<RoutingModel::VariableValuePair>&,
std::vector<RoutingModel::VariableValuePair>*)>
optimize_on_insertion = nullptr);
~LocalCheapestInsertionFilteredHeuristic() override = default;
bool BuildSolutionInternal() override;
std::string DebugString() const override {
return "LocalCheapestInsertionFilteredHeuristic";
}
protected:
void Initialize() override;
private:
struct NodeInsertion {
int64_t insert_after;
int vehicle;
int neg_hint_weight;
int64_t value;
bool operator<(const NodeInsertion& other) const {
return std::tie(neg_hint_weight, value, insert_after, vehicle) <
std::tie(other.neg_hint_weight, other.value, other.insert_after,
other.vehicle);
}
};
/// Computes the order of insertion of the node/pairs in the model based on
/// the "Seed" values (number of allowed vehicles, penalty, distance from
/// vehicle start/ends), and stores them in insertion_order_.
void ComputeInsertionOrder();
/// Helper method to the ComputeEvaluatorSortedPositions* methods. Finds all
/// possible insertion positions of node 'node_to_insert' in the partial route
/// starting at node 'start' and adds them to 'node_insertions' (no sorting is
/// done).
void AppendInsertionPositionsAfter(
int64_t node_to_insert, int64_t start, int64_t next_after_start,
int vehicle, std::vector<NodeInsertion>* node_insertions);
/// Computes the possible insertion positions of 'node' and sorts them
/// according to the current cost evaluator.
/// 'node' is a variable index corresponding to a node.
std::vector<NodeInsertion> ComputeEvaluatorSortedPositions(int64_t node);
/// Like ComputeEvaluatorSortedPositions, subject to the additional
/// restrictions that the node may only be inserted after node 'start' on the
/// route. For convenience, this method also needs the node that is right
/// after 'start' on the route.
std::vector<NodeInsertion> ComputeEvaluatorSortedPositionsOnRouteAfter(
int64_t node, int64_t start, int64_t next_after_start, int vehicle);
/// Computes the possible simultaneous insertion positions of the pair
/// 'pickup' and 'delivery'. Sorts them according to the current cost
/// evaluator.
std::vector<PickupDeliveryInsertion> ComputeEvaluatorSortedPairPositions(
int pickup, int delivery);
// Tries to insert any alternative of the given pair,
// ordered by cost of pickup insertion, then by cost of delivery insertion.
void InsertBestPickupThenDelivery(const PickupDeliveryPair& pair);
// Tries to insert any alternative of the given pair,
// ordered by the sum of pickup and delivery insertion.
void InsertBestPair(const PickupDeliveryPair& pair);
// Tries to insert any alternative of the given pair,
// at a position that preserves the multitour property,
// ordered by the sum of pickup and delivery insertion.
void InsertBestPairMultitour(const PickupDeliveryPair& pair);
// Tries to insert a pair at the given location. Returns true iff inserted.
bool InsertPair(int64_t pickup, int64_t insert_pickup_after, int64_t delivery,
int64_t insert_delivery_after, int vehicle);
// Runs an internal optimization. Returns true if the solution was changed.
bool OptimizeOnInsertion(std::vector<int> delta_indices);
// Returns true if bin capacities should be updated.
// TODO(user): Allow updating bin capacities when we do internal
// optimizations.
bool MustUpdateBinCapacities() const {
return bin_capacities_ != nullptr && optimize_on_insertion_ == nullptr;
}
std::vector<Seed> insertion_order_;
const LocalCheapestInsertionParameters::PairInsertionStrategy
pair_insertion_strategy_;
std::vector<LocalCheapestInsertionParameters::InsertionSortingProperty>
insertion_sorting_properties_;
InsertionSequenceContainer insertion_container_;
InsertionSequenceGenerator insertion_generator_;
const bool use_first_solution_hint_;
BinCapacities* const bin_capacities_;
std::function<bool(const std::vector<RoutingModel::VariableValuePair>&,
std::vector<RoutingModel::VariableValuePair>*)>
optimize_on_insertion_;
bool synchronize_insertion_optimizer_ = true;
const bool use_random_insertion_order_;
std::mt19937 rnd_;
};
/// Filtered-base decision builder based on the addition heuristic, extending
/// a path from its start node with the cheapest arc.
class CheapestAdditionFilteredHeuristic : public RoutingFilteredHeuristic {
public:
CheapestAdditionFilteredHeuristic(RoutingModel* model,
std::function<bool()> stop_search,
LocalSearchFilterManager* filter_manager);
~CheapestAdditionFilteredHeuristic() override = default;
bool BuildSolutionInternal() override;
private:
class PartialRoutesAndLargeVehicleIndicesFirst {
public:
explicit PartialRoutesAndLargeVehicleIndicesFirst(
const CheapestAdditionFilteredHeuristic& builder)
: builder_(builder) {}
bool operator()(int vehicle1, int vehicle2) const;
private:
const CheapestAdditionFilteredHeuristic& builder_;
};
/// Returns a vector of possible next indices of node from an iterator.
template <typename Iterator>
std::vector<int64_t> GetPossibleNextsFromIterator(int64_t node,
Iterator start,
Iterator end) const {
const int size = model()->Size();
std::vector<int64_t> nexts;
for (Iterator it = start; it != end; ++it) {
const int64_t next = *it;
if (next != node && (next >= size || !Contains(next))) {
nexts.push_back(next);
}
}
return nexts;
}
/// Sorts a vector of successors of node.
virtual void SortSuccessors(int64_t node,
std::vector<int64_t>* successors) = 0;
virtual int64_t FindTopSuccessor(int64_t node,
const std::vector<int64_t>& successors) = 0;
};
/// A CheapestAdditionFilteredHeuristic where the notion of 'cheapest arc'
/// comes from an arc evaluator.
class EvaluatorCheapestAdditionFilteredHeuristic
: public CheapestAdditionFilteredHeuristic {
public:
/// Takes ownership of evaluator.
EvaluatorCheapestAdditionFilteredHeuristic(
RoutingModel* model, std::function<bool()> stop_search,
std::function<int64_t(int64_t, int64_t)> evaluator,
LocalSearchFilterManager* filter_manager);
~EvaluatorCheapestAdditionFilteredHeuristic() override = default;
std::string DebugString() const override {
return "EvaluatorCheapestAdditionFilteredHeuristic";
}
private:
/// Next nodes are sorted according to the current evaluator.
void SortSuccessors(int64_t node, std::vector<int64_t>* successors) override;
int64_t FindTopSuccessor(int64_t node,
const std::vector<int64_t>& successors) override;
std::function<int64_t(int64_t, int64_t)> evaluator_;
};
/// A CheapestAdditionFilteredHeuristic where the notion of 'cheapest arc'
/// comes from an arc comparator.
class ComparatorCheapestAdditionFilteredHeuristic
: public CheapestAdditionFilteredHeuristic {
public:
/// Takes ownership of evaluator.
ComparatorCheapestAdditionFilteredHeuristic(
RoutingModel* model, std::function<bool()> stop_search,
Solver::VariableValueComparator comparator,
LocalSearchFilterManager* filter_manager);
~ComparatorCheapestAdditionFilteredHeuristic() override = default;
std::string DebugString() const override {
return "ComparatorCheapestAdditionFilteredHeuristic";
}
private:
/// Next nodes are sorted according to the current comparator.
void SortSuccessors(int64_t node, std::vector<int64_t>* successors) override;
int64_t FindTopSuccessor(int64_t node,
const std::vector<int64_t>& successors) override;
Solver::VariableValueComparator comparator_;
};
/// Filter-based decision builder which builds a solution by using
/// Clarke & Wright's Savings heuristic. For each pair of nodes, the savings
/// value is the difference between the cost of two routes visiting one node
/// each and one route visiting both nodes. Routes are built sequentially, each
/// route being initialized from the pair with the best available savings value
/// then extended by selecting the nodes with best savings on both ends of the
/// partial route. Cost is based on the arc cost function of the routing model
/// and cost classes are taken into account.
class SavingsFilteredHeuristic : public RoutingFilteredHeuristic {
public:
SavingsFilteredHeuristic(RoutingModel* model,
std::function<bool()> stop_search,
SavingsParameters parameters,
LocalSearchFilterManager* filter_manager);
~SavingsFilteredHeuristic() override;
bool BuildSolutionInternal() override;
protected:
struct Saving {
int64_t saving;
unsigned int vehicle_type : 20;
unsigned int before_node : 22;
unsigned int after_node : 22;
bool operator<(const Saving& other) const {
return std::tie(saving, vehicle_type, before_node, after_node) <
std::tie(other.saving, other.vehicle_type, other.before_node,
other.after_node);
}
};
template <typename S>
class SavingsContainer;
virtual double ExtraSavingsMemoryMultiplicativeFactor() const = 0;
virtual void BuildRoutesFromSavings() = 0;
/// Finds the best available vehicle of type "type" to start a new route to
/// serve the arc before_node-->after_node.
/// Since there are different vehicle classes for each vehicle type, each
/// vehicle class having its own capacity constraints, we go through all
/// vehicle types (in each case only studying the first available vehicle) to
/// make sure this Saving is inserted if possible.
/// If possible, the arc is committed to the best vehicle, and the vehicle
/// index is returned. If this arc can't be served by any vehicle of this
/// type, the function returns -1.
int StartNewRouteWithBestVehicleOfType(int type, int64_t before_node,
int64_t after_node);
// clang-format off
std::unique_ptr<SavingsContainer<Saving> > savings_container_;
// clang-format on
std::unique_ptr<VehicleTypeCurator> vehicle_type_curator_;
private:
/// Used when add_reverse_arcs_ is true.
/// Given the vector of adjacency lists of a graph, adds symmetric arcs not
/// already in the graph to the adjacencies (i.e. if n1-->n2 is present and
/// not n2-->n1, then n1 is added to adjacency_matrix[n2].
// clang-format off
void AddSymmetricArcsToAdjacencyLists(
std::vector<std::vector<int64_t> >* adjacency_lists);
// clang-format on
/// Computes saving values for node pairs (see MaxNumNeighborsPerNode()) and
/// all vehicle types (see ComputeVehicleTypes()).
/// The saving index attached to each saving value is an index used to
/// store and recover the node pair to which the value is linked (cf. the
/// index conversion methods below).
/// The computed savings are stored and sorted using the savings_container_.
/// Returns false if the computation could not be done within the model's
/// time limit.
bool ComputeSavings();
/// Builds a saving from a saving value, a vehicle type and two nodes.
Saving BuildSaving(int64_t saving, int vehicle_type, int before_node,
int after_node) const {
return {saving, static_cast<unsigned int>(vehicle_type),
static_cast<unsigned int>(before_node),
static_cast<unsigned int>(after_node)};
}
/// Computes and returns the maximum number of (closest) neighbors to consider
/// for each node when computing Savings, based on the neighbors ratio and max
/// memory usage specified by the savings_params_.
int64_t MaxNumNeighborsPerNode(int num_vehicle_types) const;
const SavingsParameters savings_params_;
friend class SavingsFilteredHeuristicTestPeer;
};
class SequentialSavingsFilteredHeuristic : public SavingsFilteredHeuristic {
public:
SequentialSavingsFilteredHeuristic(RoutingModel* model,
std::function<bool()> stop_search,
SavingsParameters parameters,
LocalSearchFilterManager* filter_manager)
: SavingsFilteredHeuristic(model, std::move(stop_search), parameters,
filter_manager) {}
~SequentialSavingsFilteredHeuristic() override = default;
std::string DebugString() const override {
return "SequentialSavingsFilteredHeuristic";
}
private:
/// Builds routes sequentially.
/// Once a Saving is used to start a new route, we extend this route as much
/// as possible from both ends by gradually inserting the best Saving at
/// either end of the route.
void BuildRoutesFromSavings() override;
double ExtraSavingsMemoryMultiplicativeFactor() const override { return 1.0; }
};
class ParallelSavingsFilteredHeuristic : public SavingsFilteredHeuristic {
public:
ParallelSavingsFilteredHeuristic(RoutingModel* model,
std::function<bool()> stop_search,
SavingsParameters parameters,
LocalSearchFilterManager* filter_manager)
: SavingsFilteredHeuristic(model, std::move(stop_search), parameters,
filter_manager) {}
~ParallelSavingsFilteredHeuristic() override = default;
std::string DebugString() const override {
return "ParallelSavingsFilteredHeuristic";
}
private:
/// Goes through the ordered computed Savings to build routes in parallel.
/// Given a Saving for a before-->after arc :
/// -- If both before and after are uncontained, we start a new route.
/// -- If only before is served and is the last node on its route, we try
/// adding after at the end of the route.
/// -- If only after is served and is first on its route, we try adding before
/// as first node on this route.
/// -- If both nodes are contained and are respectively the last and first
/// nodes on their (different) routes, we merge the routes of the two nodes
/// into one if possible.
void BuildRoutesFromSavings() override;
double ExtraSavingsMemoryMultiplicativeFactor() const override { return 2.0; }
/// Merges the routes of first_vehicle and second_vehicle onto the vehicle
/// with lower fixed cost. The routes respectively end at before_node and
/// start at after_node, and are merged into one by adding the arc
/// before_node-->after_node.
void MergeRoutes(int first_vehicle, int second_vehicle, int64_t before_node,
int64_t after_node);
/// First and last non start/end nodes served by each vehicle.
std::vector<int64_t> first_node_on_route_;
std::vector<int64_t> last_node_on_route_;
/// For each first/last node served by a vehicle (besides start/end nodes of
/// vehicle), this vector contains the index of the vehicle serving them.
/// For other (intermediary) nodes, contains -1.
std::vector<int> vehicle_of_first_or_last_node_;
};
/// Christofides addition heuristic. Initially created to solve TSPs, extended
/// to support any model by extending routes as much as possible following the
/// path found by the heuristic, before starting a new route.
class ChristofidesFilteredHeuristic : public RoutingFilteredHeuristic {
public:
ChristofidesFilteredHeuristic(RoutingModel* model,
std::function<bool()> stop_search,
LocalSearchFilterManager* filter_manager,
bool use_minimum_matching);
~ChristofidesFilteredHeuristic() override = default;
bool BuildSolutionInternal() override;
std::string DebugString() const override {
return "ChristofidesFilteredHeuristic";
}
private:
const bool use_minimum_matching_;
};
/// Class to arrange indices by their distance and their angle from the depot.
/// Used in the Sweep first solution heuristic.
class SweepArranger {
public:
explicit SweepArranger(absl::Span<const std::pair<int64_t, int64_t>> points);
// This type is neither copyable nor movable.
SweepArranger(const SweepArranger&) = delete;
SweepArranger& operator=(const SweepArranger&) = delete;
virtual ~SweepArranger() = default;
void ArrangeIndices(std::vector<int64_t>* indices);
void SetSectors(int sectors) { sectors_ = sectors; }
private:
std::vector<int> coordinates_;
int sectors_;
};
#endif // SWIG
// Returns a DecisionBuilder building a first solution based on the Sweep
// heuristic. Mostly suitable when cost is proportional to distance.
DecisionBuilder* MakeSweepDecisionBuilder(RoutingModel* model,
bool check_assignment);
// Returns a DecisionBuilder making all nodes unperformed.
DecisionBuilder* MakeAllUnperformed(RoutingModel* model);
} // namespace operations_research
#endif // ORTOOLS_CONSTRAINT_SOLVER_ROUTING_SEARCH_H_