diff --git a/ortools/constraint_solver/routing_filters.cc b/ortools/constraint_solver/routing_filters.cc index a740c60544..ee3d82bfb7 100644 --- a/ortools/constraint_solver/routing_filters.cc +++ b/ortools/constraint_solver/routing_filters.cc @@ -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()); 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(), {}); } diff --git a/ortools/constraint_solver/routing_filters.h b/ortools/constraint_solver/routing_filters.h index b35a169461..f3b8d8d261 100644 --- a/ortools/constraint_solver/routing_filters.h +++ b/ortools/constraint_solver/routing_filters.h @@ -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; } diff --git a/ortools/constraint_solver/routing_lp_scheduling.cc b/ortools/constraint_solver/routing_lp_scheduling.cc index 8d231e5d8a..049ebb1425 100644 --- a/ortools/constraint_solver/routing_lp_scheduling.cc +++ b/ortools/constraint_solver/routing_lp_scheduling.cc @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include diff --git a/ortools/constraint_solver/routing_lp_scheduling.h b/ortools/constraint_solver/routing_lp_scheduling.h index 66f51171d8..337927986f 100644 --- a/ortools/constraint_solver/routing_lp_scheduling.h +++ b/ortools/constraint_solver/routing_lp_scheduling.h @@ -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())) { diff --git a/ortools/constraint_solver/routing_neighborhoods.cc b/ortools/constraint_solver/routing_neighborhoods.cc index 86ae3f44bc..b78a9afa79 100644 --- a/ortools/constraint_solver/routing_neighborhoods.cc +++ b/ortools/constraint_solver/routing_neighborhoods.cc @@ -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 start_empty_path_class, std::vector> 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)) { diff --git a/ortools/constraint_solver/routing_neighborhoods.h b/ortools/constraint_solver/routing_neighborhoods.h index fa7c6d8fd5..cd1baca88b 100644 --- a/ortools/constraint_solver/routing_neighborhoods.h +++ b/ortools/constraint_solver/routing_neighborhoods.h @@ -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 chain_; }; diff --git a/ortools/constraint_solver/routing_parameters.proto b/ortools/constraint_solver/routing_parameters.proto index f788e8f018..23e247dd13 100644 --- a/ortools/constraint_solver/routing_parameters.proto +++ b/ortools/constraint_solver/routing_parameters.proto @@ -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 diff --git a/ortools/constraint_solver/routing_search.cc b/ortools/constraint_solver/routing_search.cc index 00100d9b56..a26f1bf45f 100644 --- a/ortools/constraint_solver/routing_search.cc +++ b/ortools/constraint_solver/routing_search.cc @@ -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 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; }