routing: backport changes from main
This commit is contained in:
@@ -612,7 +612,7 @@ void BasePathFilter::SynchronizeFullAssignment() {
|
||||
new_nexts_[touched] = kUnassigned;
|
||||
}
|
||||
delta_touched_.clear();
|
||||
OnBeforeSynchronizePaths();
|
||||
OnBeforeSynchronizePaths(/*synchronizing_all_paths=*/true);
|
||||
UpdateAllRanks();
|
||||
OnAfterSynchronizePaths();
|
||||
}
|
||||
@@ -650,7 +650,7 @@ void BasePathFilter::OnSynchronize(const Assignment* delta) {
|
||||
new_nexts_[touched] = kUnassigned;
|
||||
}
|
||||
delta_touched_.clear();
|
||||
OnBeforeSynchronizePaths();
|
||||
OnBeforeSynchronizePaths(/*synchronizing_all_paths=*/false);
|
||||
for (const int64_t touched_start : touched_paths_.PositionsSetAtLeastOnce()) {
|
||||
int64_t node = touched_start;
|
||||
while (node < Size()) {
|
||||
@@ -1252,7 +1252,10 @@ class PathCumulFilter : public BasePathFilter {
|
||||
int64_t chain_end) override;
|
||||
bool FinalizeAcceptPath(int64_t objective_min,
|
||||
int64_t objective_max) override;
|
||||
void OnBeforeSynchronizePaths() override;
|
||||
|
||||
void OnBeforeSynchronizePaths(bool synchronizing_all_paths) override;
|
||||
void OnSynchronizePathFromStart(int64_t start) override;
|
||||
void OnAfterSynchronizePaths() override;
|
||||
|
||||
// TODO(user): consider making those methods external functions.
|
||||
// Fills the data of path in dimension_values_: nodes, cumuls, travels,
|
||||
@@ -1842,6 +1845,7 @@ bool PathCumulFilter::AcceptPath(int64_t path_start, int64_t /*chain_start*/,
|
||||
// Filter costs: span (linear/quadratic/piecewise),
|
||||
// soft cumul windows (linear/piecewise).
|
||||
if (!filter_objective_cost_) return true;
|
||||
|
||||
CapSubFrom(cost_of_path_.Get(path), &accepted_objective_value_);
|
||||
if (routing_model_.IsEnd(GetNext(path_start)) &&
|
||||
!routing_model_.IsVehicleUsedWhenEmpty(path)) {
|
||||
@@ -2049,33 +2053,45 @@ bool PathCumulFilter::FinalizeAcceptPath(int64_t /*objective_min*/,
|
||||
return accepted_objective_value_ <= objective_max;
|
||||
}
|
||||
|
||||
void PathCumulFilter::OnBeforeSynchronizePaths() {
|
||||
dimension_values_.Reset();
|
||||
cost_of_path_.Revert();
|
||||
cost_of_path_.SetAllAndCommit(0);
|
||||
global_span_cost_.SetAndCommit(0);
|
||||
accepted_objective_value_ = 0;
|
||||
synchronized_objective_value_ = 0; // Accept() relies on this value.
|
||||
location_of_node_.Revert();
|
||||
location_of_node_.SetAllAndCommit({-1, -1});
|
||||
if (!HasAnySyncedPath()) return;
|
||||
// Use AcceptPath() and FinalizeAcceptPath() to compute the bounds and
|
||||
// pathwise costs of the new solution incrementally, then commit all data
|
||||
// structures to this state.
|
||||
InitializeAcceptPath();
|
||||
const int num_paths = NumPaths();
|
||||
for (int path = 0; path < num_paths; ++path) {
|
||||
if (!IsVarSynced(Start(path))) continue;
|
||||
const bool is_accepted = AcceptPath(Start(path), Start(path), End(path));
|
||||
DCHECK(is_accepted);
|
||||
void PathCumulFilter::OnBeforeSynchronizePaths(bool synchronizing_all_paths) {
|
||||
if (synchronizing_all_paths) {
|
||||
// All paths are being synchronized, so we can reset all the data and let
|
||||
// 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.
|
||||
}
|
||||
// TODO(user): check whether this could go into FinalizeAcceptPath(),
|
||||
// with a Commit() on some data structure.
|
||||
|
||||
accepted_objective_value_ = synchronized_objective_value_;
|
||||
if (HasAnySyncedPath()) {
|
||||
// Revert the data structures that are used to compute the bounds and
|
||||
// pathwise costs of the new solution incrementally.
|
||||
InitializeAcceptPath();
|
||||
}
|
||||
}
|
||||
|
||||
void PathCumulFilter::OnSynchronizePathFromStart(int64_t start) {
|
||||
DCHECK(IsVarSynced(start));
|
||||
// Using AcceptPath() to compute the bounds and pathwise costs of the new
|
||||
// solution incrementally.
|
||||
const int path = GetPath(start);
|
||||
const bool is_accepted = AcceptPath(start, start, End(path));
|
||||
DCHECK(is_accepted);
|
||||
}
|
||||
|
||||
void PathCumulFilter::OnAfterSynchronizePaths() {
|
||||
if (filter_objective_cost_ && FilterGlobalSpanCost()) {
|
||||
// TODO(user): check whether this could go into FinalizeAcceptPath(),
|
||||
// with a Commit() on some data structure.
|
||||
paths_by_decreasing_span_min_.clear();
|
||||
paths_by_decreasing_end_min_.clear();
|
||||
paths_by_increasing_start_max_.clear();
|
||||
for (int path = 0; path < num_paths; ++path) {
|
||||
for (int path = 0; path < NumPaths(); ++path) {
|
||||
const int num_path_nodes = dimension_values_.Nodes(path).size();
|
||||
if (num_path_nodes == 0 ||
|
||||
(num_path_nodes == 2 &&
|
||||
@@ -2093,6 +2109,7 @@ void PathCumulFilter::OnBeforeSynchronizePaths() {
|
||||
absl::c_sort(paths_by_decreasing_end_min_, std::greater<ValuedPath>());
|
||||
absl::c_sort(paths_by_increasing_start_max_);
|
||||
}
|
||||
// Using FinalizeAcceptPath() to commit all data structures to this state.
|
||||
FinalizeAcceptPath(kint64min, kint64max);
|
||||
dimension_values_.Commit();
|
||||
cost_of_path_.Commit();
|
||||
@@ -2829,7 +2846,7 @@ class ResourceGroupAssignmentFilter : public BasePathFilter {
|
||||
bool AcceptPath(int64_t path_start, int64_t, int64_t) override;
|
||||
bool FinalizeAcceptPath(int64_t objective_min,
|
||||
int64_t objective_max) override;
|
||||
void OnBeforeSynchronizePaths() override;
|
||||
void OnBeforeSynchronizePaths(bool synchronizing_all_paths) override;
|
||||
void OnSynchronizePathFromStart(int64_t start) override;
|
||||
void OnAfterSynchronizePaths() override;
|
||||
|
||||
@@ -2993,7 +3010,7 @@ bool ResourceGroupAssignmentFilter::FinalizeAcceptPath(
|
||||
return assignment_cost >= 0 && delta_cost_without_transit_ <= objective_max;
|
||||
}
|
||||
|
||||
void ResourceGroupAssignmentFilter::OnBeforeSynchronizePaths() {
|
||||
void ResourceGroupAssignmentFilter::OnBeforeSynchronizePaths(bool) {
|
||||
if (!HasAnySyncedPath()) {
|
||||
vehicle_to_resource_class_assignment_costs_.assign(model_.vehicles(), {});
|
||||
}
|
||||
|
||||
@@ -1443,7 +1443,7 @@ class BasePathFilter : public IntVarLocalSearchFilter {
|
||||
enum Status { UNKNOWN, ENABLED, DISABLED };
|
||||
|
||||
virtual bool DisableFiltering() const { return false; }
|
||||
virtual void OnBeforeSynchronizePaths() {}
|
||||
virtual void OnBeforeSynchronizePaths(bool) {}
|
||||
virtual void OnAfterSynchronizePaths() {}
|
||||
virtual void OnSynchronizePathFromStart(int64_t) {}
|
||||
virtual bool InitializeAcceptPath() { return true; }
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <numeric>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
@@ -456,9 +456,12 @@ class RoutingCPSatWrapper : public RoutingLinearSolverWrapper {
|
||||
// significantly faster than both full presolve and no presolve.
|
||||
parameters_.set_cp_model_presolve(true);
|
||||
parameters_.set_max_presolve_iterations(1);
|
||||
parameters_.set_cp_model_probing_level(0);
|
||||
parameters_.set_use_sat_inprocessing(false);
|
||||
parameters_.set_symmetry_level(0);
|
||||
parameters_.set_catch_sigint_signal(false);
|
||||
parameters_.set_mip_max_bound(1e8);
|
||||
parameters_.set_search_branching(sat::SatParameters::LP_SEARCH);
|
||||
parameters_.set_search_branching(sat::SatParameters::PORTFOLIO_SEARCH);
|
||||
parameters_.set_linearization_level(2);
|
||||
parameters_.set_cut_level(0);
|
||||
parameters_.set_use_absl_random(false);
|
||||
@@ -588,7 +591,9 @@ class RoutingCPSatWrapper : public RoutingLinearSolverWrapper {
|
||||
model_.mutable_constraints(ct)->add_enforcement_literal(condition);
|
||||
}
|
||||
DimensionSchedulingStatus Solve(absl::Duration duration_limit) override {
|
||||
parameters_.set_max_time_in_seconds(absl::ToDoubleSeconds(duration_limit));
|
||||
const double max_time = absl::ToDoubleSeconds(duration_limit);
|
||||
if (max_time <= 0.0) return DimensionSchedulingStatus::INFEASIBLE;
|
||||
parameters_.set_max_time_in_seconds(max_time);
|
||||
VLOG(2) << ProtobufDebugString(model_);
|
||||
if (hint_.vars_size() == model_.variables_size()) {
|
||||
*model_.mutable_solution_hint() = hint_;
|
||||
@@ -597,6 +602,7 @@ class RoutingCPSatWrapper : public RoutingLinearSolverWrapper {
|
||||
model.Add(sat::NewSatParameters(parameters_));
|
||||
response_ = sat::SolveCpModel(model_, &model);
|
||||
VLOG(2) << response_;
|
||||
DCHECK_NE(response_.status(), sat::CpSolverStatus::MODEL_INVALID);
|
||||
if (response_.status() == sat::CpSolverStatus::OPTIMAL ||
|
||||
(response_.status() == sat::CpSolverStatus::FEASIBLE &&
|
||||
!model_.has_floating_point_objective())) {
|
||||
|
||||
@@ -154,6 +154,8 @@ ShortestPathOnAlternatives::ShortestPathOnAlternatives(
|
||||
}
|
||||
|
||||
bool ShortestPathOnAlternatives::HasAlternatives(int node) const {
|
||||
DCHECK_LT(node, to_alternative_set_.size());
|
||||
DCHECK_LT(to_alternative_set_[node], alternative_sets_.size());
|
||||
return alternative_sets_[to_alternative_set_[node]].size() > 1;
|
||||
}
|
||||
|
||||
@@ -232,8 +234,33 @@ TwoOptWithShortestPathOperator::TwoOptWithShortestPathOperator(
|
||||
bool TwoOptWithShortestPathOperator::MakeNeighbor() {
|
||||
DCHECK_EQ(StartNode(0), StartNode(1));
|
||||
const int64_t before_chain = BaseNode(0);
|
||||
if (IsPathEnd(before_chain)) return false;
|
||||
if (IsPathEnd(before_chain)) {
|
||||
ResetChainStatus();
|
||||
return false;
|
||||
}
|
||||
const int64_t after_chain = BaseNode(1);
|
||||
bool has_alternatives = false;
|
||||
if (before_chain != after_chain) {
|
||||
const int64_t prev_after_chain = Prev(after_chain);
|
||||
if (prev_after_chain != before_chain &&
|
||||
chain_status_.start == before_chain &&
|
||||
chain_status_.end == prev_after_chain) {
|
||||
has_alternatives =
|
||||
chain_status_.has_alternatives ||
|
||||
shortest_path_manager_.HasAlternatives(prev_after_chain);
|
||||
} else {
|
||||
// Non-incremental computation of alternative presence. The chains are
|
||||
// small by definition.
|
||||
for (int64_t node = Next(before_chain); node != after_chain;
|
||||
node = Next(node)) {
|
||||
has_alternatives |= shortest_path_manager_.HasAlternatives(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
chain_status_.start = before_chain;
|
||||
chain_status_.end = after_chain;
|
||||
chain_status_.has_alternatives = has_alternatives;
|
||||
if (!has_alternatives) return false;
|
||||
int64_t chain_last;
|
||||
if (!ReverseChain(before_chain, after_chain, &chain_last)) return false;
|
||||
chain_.clear();
|
||||
@@ -255,14 +282,19 @@ SwapActiveToShortestPathOperator::SwapActiveToShortestPathOperator(
|
||||
std::function<int(int64_t)> start_empty_path_class,
|
||||
std::vector<std::vector<int64_t>> alternative_sets,
|
||||
RoutingTransitCallback2 arc_evaluator)
|
||||
: PathOperator(vars, secondary_vars, 1, true, false,
|
||||
: PathOperator(vars, secondary_vars, /*number_of_base_nodes=*/1,
|
||||
/*skip_locally_optimal_paths=*/true,
|
||||
/*accept_path_end_base=*/false,
|
||||
std::move(start_empty_path_class), nullptr, nullptr),
|
||||
shortest_path_manager_(vars.size(), std::move(alternative_sets),
|
||||
std::move(arc_evaluator)) {}
|
||||
|
||||
bool SwapActiveToShortestPathOperator::MakeNeighbor() {
|
||||
const int64_t before_chain = BaseNode(0);
|
||||
if (shortest_path_manager_.HasAlternatives(before_chain)) return false;
|
||||
if (!IsPathStart(before_chain) &&
|
||||
shortest_path_manager_.HasAlternatives(before_chain)) {
|
||||
return false;
|
||||
}
|
||||
int64_t next = Next(before_chain);
|
||||
chain_.clear();
|
||||
while (!IsPathEnd(next) && shortest_path_manager_.HasAlternatives(next)) {
|
||||
|
||||
@@ -119,7 +119,8 @@ class ShortestPathOnAlternatives {
|
||||
};
|
||||
|
||||
// Reverses a sub-chain of a path and then replaces it with the shortest path on
|
||||
// the DAG formed by the sequence of its node alternatives.
|
||||
// the DAG formed by the sequence of its node alternatives. This operator will
|
||||
// only reverse chains with at least a node with multiple alternatives.
|
||||
class TwoOptWithShortestPathOperator : public PathOperator {
|
||||
public:
|
||||
TwoOptWithShortestPathOperator(
|
||||
@@ -140,8 +141,24 @@ class TwoOptWithShortestPathOperator : public PathOperator {
|
||||
int64_t GetBaseNodeRestartPosition(int base_index) override {
|
||||
return (base_index == 0) ? StartNode(0) : BaseNode(0);
|
||||
}
|
||||
// Necessary to have proper information about alternatives of current path.
|
||||
bool RestartAtPathStartOnSynchronize() override { return true; }
|
||||
|
||||
private:
|
||||
void ResetIncrementalism() override { ResetChainStatus(); }
|
||||
void OnNodeInitialization() override { ResetChainStatus(); }
|
||||
void ResetChainStatus() {
|
||||
chain_status_.start = -1;
|
||||
chain_status_.end = -1;
|
||||
chain_status_.has_alternatives = false;
|
||||
}
|
||||
|
||||
struct ChainStatus {
|
||||
int64_t start = -1;
|
||||
int64_t end = -1;
|
||||
bool has_alternatives = false;
|
||||
};
|
||||
ChainStatus chain_status_;
|
||||
ShortestPathOnAlternatives shortest_path_manager_;
|
||||
std::vector<int64_t> chain_;
|
||||
};
|
||||
|
||||
@@ -145,9 +145,11 @@ message RoutingSearchParameters {
|
||||
// ratio.
|
||||
SORTING_PROPERTY_PENALTY_OVER_ALLOWED_VEHICLES_RATIO = 3;
|
||||
// Selects nodes that are on average the farthest from vehicles.
|
||||
SORTING_PROPERTY_HIGHEST_ARC_COST_TO_VEHICLE_START_ENDS = 4;
|
||||
SORTING_PROPERTY_HIGHEST_AVG_ARC_COST_TO_VEHICLE_START_ENDS = 4;
|
||||
// Selects nodes that are on average the closest to vehicles.
|
||||
SORTING_PROPERTY_LOWEST_ARC_COST_TO_VEHICLE_START_ENDS = 5;
|
||||
SORTING_PROPERTY_LOWEST_AVG_ARC_COST_TO_VEHICLE_START_ENDS = 5;
|
||||
// Select nodes with the smallest distance to the closest vehicle.
|
||||
SORTING_PROPERTY_LOWEST_MIN_ARC_COST_TO_VEHICLE_START_ENDS = 6;
|
||||
}
|
||||
|
||||
// The properties used to sort insertion entries in the local cheapest
|
||||
|
||||
@@ -2683,10 +2683,12 @@ void LocalCheapestInsertionFilteredHeuristic::ComputeInsertionOrder() {
|
||||
auto get_insertion_properties = [this](int64_t penalty,
|
||||
int64_t num_allowed_vehicles,
|
||||
int64_t avg_distance_to_vehicle,
|
||||
int64_t neg_max_distance_to_vehicles) {
|
||||
int64_t neg_min_distance_to_vehicles) {
|
||||
DCHECK_NE(0, num_allowed_vehicles);
|
||||
absl::InlinedVector<int64_t, 8> properties;
|
||||
properties.reserve(insertion_sorting_properties_.size());
|
||||
|
||||
bool neg_min_distance_to_vehicles_appended = false;
|
||||
for (const int property : insertion_sorting_properties_) {
|
||||
switch (property) {
|
||||
case RoutingSearchParameters::SORTING_PROPERTY_ALLOWED_VEHICLES:
|
||||
@@ -2700,13 +2702,18 @@ void LocalCheapestInsertionFilteredHeuristic::ComputeInsertionOrder() {
|
||||
properties.push_back(CapOpp(penalty / num_allowed_vehicles));
|
||||
break;
|
||||
case RoutingSearchParameters::
|
||||
SORTING_PROPERTY_HIGHEST_ARC_COST_TO_VEHICLE_START_ENDS:
|
||||
SORTING_PROPERTY_HIGHEST_AVG_ARC_COST_TO_VEHICLE_START_ENDS:
|
||||
properties.push_back(CapOpp(avg_distance_to_vehicle));
|
||||
break;
|
||||
case RoutingSearchParameters::
|
||||
SORTING_PROPERTY_LOWEST_ARC_COST_TO_VEHICLE_START_ENDS:
|
||||
SORTING_PROPERTY_LOWEST_AVG_ARC_COST_TO_VEHICLE_START_ENDS:
|
||||
properties.push_back(avg_distance_to_vehicle);
|
||||
break;
|
||||
case RoutingSearchParameters::
|
||||
SORTING_PROPERTY_LOWEST_MIN_ARC_COST_TO_VEHICLE_START_ENDS:
|
||||
properties.push_back(neg_min_distance_to_vehicles);
|
||||
neg_min_distance_to_vehicles_appended = true;
|
||||
break;
|
||||
default:
|
||||
LOG(DFATAL)
|
||||
<< "Unknown RoutingSearchParameter::InsertionSortingProperty "
|
||||
@@ -2717,11 +2724,11 @@ void LocalCheapestInsertionFilteredHeuristic::ComputeInsertionOrder() {
|
||||
|
||||
// Historically the negative max distance to vehicles has always been
|
||||
// considered to be the last property in the hierarchy defining how nodes
|
||||
// are sorted for the LCI heuristic.
|
||||
// TODO(user): add a specific property enum for
|
||||
// neg_max_distance_to_vehicles and only append it here if it hasn't already
|
||||
// been done above.
|
||||
properties.push_back(neg_max_distance_to_vehicles);
|
||||
// are sorted for the LCI heuristic, so we add it here iff it wasn't added
|
||||
// before
|
||||
if (!neg_min_distance_to_vehicles_appended) {
|
||||
properties.push_back(neg_min_distance_to_vehicles);
|
||||
}
|
||||
|
||||
return properties;
|
||||
};
|
||||
@@ -2733,10 +2740,10 @@ void LocalCheapestInsertionFilteredHeuristic::ComputeInsertionOrder() {
|
||||
insertion_sorting_properties_) {
|
||||
if (property ==
|
||||
RoutingSearchParameters::
|
||||
SORTING_PROPERTY_HIGHEST_ARC_COST_TO_VEHICLE_START_ENDS ||
|
||||
SORTING_PROPERTY_HIGHEST_AVG_ARC_COST_TO_VEHICLE_START_ENDS ||
|
||||
property ==
|
||||
RoutingSearchParameters::
|
||||
SORTING_PROPERTY_LOWEST_ARC_COST_TO_VEHICLE_START_ENDS) {
|
||||
SORTING_PROPERTY_LOWEST_AVG_ARC_COST_TO_VEHICLE_START_ENDS) {
|
||||
compute_avg_pickup_delivery_pair_distance_from_vehicles = true;
|
||||
break;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user