Skip to content

Commit

Permalink
routing: backport changes from main
Browse files Browse the repository at this point in the history
  • Loading branch information
Mizux committed Nov 29, 2024
1 parent 650e072 commit 348f4e4
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 46 deletions.
71 changes: 44 additions & 27 deletions ortools/constraint_solver/routing_filters.cc
Original file line number Diff line number Diff line change
Expand Up @@ -612,7 +612,7 @@ void BasePathFilter::SynchronizeFullAssignment() {
new_nexts_[touched] = kUnassigned;
}
delta_touched_.clear();
OnBeforeSynchronizePaths();
OnBeforeSynchronizePaths(/*synchronizing_all_paths=*/true);
UpdateAllRanks();
OnAfterSynchronizePaths();
}
Expand Down Expand Up @@ -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()) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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 &&
Expand All @@ -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();
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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(), {});
}
Expand Down
2 changes: 1 addition & 1 deletion ortools/constraint_solver/routing_filters.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
1 change: 1 addition & 0 deletions ortools/constraint_solver/routing_lp_scheduling.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include <cstdlib>
#include <functional>
#include <limits>
#include <memory>
#include <numeric>
#include <optional>
#include <string>
Expand Down
10 changes: 8 additions & 2 deletions ortools/constraint_solver/routing_lp_scheduling.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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_;
Expand All @@ -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())) {
Expand Down
38 changes: 35 additions & 3 deletions ortools/constraint_solver/routing_neighborhoods.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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();
Expand All @@ -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)) {
Expand Down
19 changes: 18 additions & 1 deletion ortools/constraint_solver/routing_neighborhoods.h
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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_;
};
Expand Down
6 changes: 4 additions & 2 deletions ortools/constraint_solver/routing_parameters.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 17 additions & 10 deletions ortools/constraint_solver/routing_search.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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 "
Expand All @@ -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;
};
Expand All @@ -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;
}
Expand Down

0 comments on commit 348f4e4

Please sign in to comment.