diff --git a/ortools/graph/dag_constrained_shortest_path.cc b/ortools/graph/dag_constrained_shortest_path.cc index 6b2c57e1e1..8fe8450323 100644 --- a/ortools/graph/dag_constrained_shortest_path.cc +++ b/ortools/graph/dag_constrained_shortest_path.cc @@ -78,8 +78,8 @@ PathWithLength ConstrainedShortestPathsOnDag( std::vector destinations = {destination}; ConstrainedShortestPathsOnDagWrapper> constrained_shortest_path_on_dag(&graph, &arc_lengths, &arc_resources, - &(*topological_order), &sources, - &destinations, &max_resources); + *topological_order, sources, + destinations, &max_resources); PathWithLength path_with_length = constrained_shortest_path_on_dag.RunConstrainedShortestPathOnDag(); diff --git a/ortools/graph/dag_constrained_shortest_path.h b/ortools/graph/dag_constrained_shortest_path.h index b864d8d94f..02993acece 100644 --- a/ortools/graph/dag_constrained_shortest_path.h +++ b/ortools/graph/dag_constrained_shortest_path.h @@ -14,6 +14,8 @@ #ifndef OR_TOOLS_GRAPH_DAG_CONSTRAINED_SHORTEST_PATH_H_ #define OR_TOOLS_GRAPH_DAG_CONSTRAINED_SHORTEST_PATH_H_ +#include + #include #include #include @@ -25,13 +27,25 @@ #include "absl/strings/str_format.h" #include "absl/types/span.h" #include "ortools/graph/dag_shortest_path.h" +#include "ortools/graph/graph.h" namespace operations_research { // This library provides APIs to compute the constrained shortest path (CSP) on // a given directed acyclic graph (DAG) with resources on each arc. A CSP is a // shortest path on a DAG which does not exceed a set of maximum resources -// consumption. The algorithm is exponential and has no guarantee to finish. +// consumption. The algorithm is exponential and has no guarantee to finish. It +// is based on bi-drectionnal search. First is a forward pass from the source to +// nodes “somewhere in the middle” to generate forward labels, just as the +// onedirectional labeling algorithm we discussed; then a symmetric backward +// pass from the destination generates backward labels; and finally at each node +// with both forward and backward labels, it joins any pair of labels to form a +// feasible complete path. Intuitively, the number of labels grows exponentially +// with the number of arcs in the path. The overall number of labels are then +// expected to be smaller with shorter paths. For DAG with a topological +// ordering, we can pick any node (usually right in the middle) as a *midpoint* +// to stop each pass at. Then labels can be joined at only one half of the nodes +// by considering all edges between each half. // // In the DAG, multiple arcs between the same pair of nodes is allowed. However, // self-loop arcs are not allowed. @@ -114,70 +128,156 @@ class ConstrainedShortestPathsOnDagWrapper { // // Validity of arcs and topological order are DCHECKed. // - // If the number of labels in memory exceeds `max_num_created_labels` at any - // point in the algorithm, it returns the best path found so far, most - // particularly the empty path if none were found. + // If the number of labels in memory exceeds `max_num_created_labels / 2` at + // any point in each pass of the algorithm, new labels are not generated + // anymore and it returns the best path found so far, most particularly the + // empty path if none were found. // - // SUBTLE: You can modify the graph, the arc lengths and resources, the - // topological order, sources, destinations or the maximum resource between - // calls to the `RunConstrainedShortestPathOnDag()` function. That's fine. - // Doing so will obviously invalidate the result API of the last constrained - // shortest path run, which could return an upper bound, junk, or crash. + // IMPORTANT: You cannot modify anything except `arc_lengths` between calls to + // the `RunConstrainedShortestPathOnDag()` function. + // TODO(b/330285675): Change API to copy everything internally except the + // lengths. ConstrainedShortestPathsOnDagWrapper( const GraphType* graph, const std::vector* arc_lengths, const std::vector>* arc_resources, - const std::vector* topological_order, - const std::vector* sources, - const std::vector* destinations, + absl::Span topological_order, + absl::Span sources, + absl::Span destinations, const std::vector* max_resources, int max_num_created_labels = 1e9); // Returns {+inf, {}, {}} if there is no constrained path of finite length - // from one node in `sources` to one node in `destinations`. + // wihtin resources constraints from one node in `sources` to one node in + // `destinations`. PathWithLength RunConstrainedShortestPathOnDag(); + // For benchmarking and informational purposes, returns the number of labels + // generated in the call of `RunConstrainedShortestPathOnDag()`. + int label_count() const { + return lengths_from_sources_[FORWARD].size() + + lengths_from_sources_[BACKWARD].size(); + } + private: - // Returns the list of all the arcs of the shortest path from one node in - // `sources` ending by the arc from a given `label_index` if and only if - // `label_index` is between 0 and `labels_from_sources_.size() - 1`. - std::vector BestArcPathEndingWith(int label_index) const; + enum Direction { + FORWARD = 0, + BACKWARD = 1, + }; + + inline static Direction Reverse(Direction d) { + return d == FORWARD ? BACKWARD : FORWARD; + } + + // A LabelPair includes the `length` of a path that can be constructed by + // merging the paths from two *linkable* labels corresponding to + // `label_index`. + struct LabelPair { + double length = 0.0; + int label_index[2]; + }; + + void RunHalfConstrainedShortestPathOnDag( + const GraphType& reverse_graph, absl::Span arc_lengths, + absl::Span> arc_resources, + absl::Span> min_arc_resources, + absl::Span max_resources, int max_num_created_labels, + std::vector& lengths_from_sources, + std::vector>& resources_from_sources, + std::vector& incoming_arc_indices_from_sources, + std::vector& first_label, std::vector& num_labels); + + // Returns the arc index linking two nodes from each pass forming the best + // path. Returns -1 if no better path than the one found from + // `best_label_pair` is found. + ArcIndex MergeHalfRuns( + const GraphType& graph, absl::Span arc_lengths, + absl::Span> arc_resources, + absl::Span max_resources, + const std::vector sub_node_indices[2], + const std::vector lengths_from_sources[2], + const std::vector> resources_from_sources[2], + const std::vector first_label[2], + const std::vector num_labels[2], LabelPair& best_label_pair); + + // Returns the path as list of arc indices that starts from a node in + // `sources` (if `direction` iS FORWARD) or `destinations` (if `direction` is + // BACKWARD) and ends in node represented by `best_label_index`. + std::vector ArcPathTo( + int best_label_index, const GraphType& reverse_graph, + absl::Span arc_lengths, + absl::Span lengths_from_sources, + absl::Span incoming_arc_indices_from_sources, + absl::Span first_label, + absl::Span num_labels) const; + // Returns the list of all the nodes implied by a given `arc_path`. - std::vector NodePathImpliedBy( - const std::vector& arc_path) const; + std::vector NodePathImpliedBy(absl::Span arc_path, + const GraphType& graph) const; + + static constexpr double kTolerance = 1e-6; const GraphType* const graph_; const std::vector* const arc_lengths_; const std::vector>* const arc_resources_; - const std::vector* const topological_order_; - const std::vector* const sources_; - const std::vector* const destinations_; const std::vector* const max_resources_; - int max_num_created_labels_; + absl::Span sources_; + absl::Span destinations_; + const int num_resources_; - std::vector node_is_source_; - std::vector node_is_destination_; - // Using the fact that the graph is a DAG, we can disregard any node that - // comes after the last destination (based on the topological order). - std::vector node_is_after_last_destination_; - - // Data for reverse graph. - GraphType reverse_graph_; - std::vector reverse_inverse_arc_permutation_; + // Data about *reachable* sub-graphs split in two for bidirectional search. + // Reachable nodes are nodes that can be reached given the resources + // constraints, i.e., for each resource, the sum of the minimum resource to + // get to a node from a node in `sources` and to get from a node to a node in + // `destinations` should be less than the maximum resource. Reachable arcs are + // arcs linking reachable nodes. + // + // `sub_reverse_graph_[dir]` is the reachable sub-graph split in *half* with + // an additional linked to sources (resp. destinations) for the forward (resp. + // backward) direction. For the forward (resp. backward) direction, nodes are + // indexed using the original (resp. reverse) topological order. + GraphType sub_reverse_graph_[2]; + std::vector> sub_arc_resources_[2]; + // `sub_full_arc_indices_[dir]` has size `sub_reverse_graph_[dir].num_arcs()` + // such that `sub_full_arc_indices_[dir][sub_arc] = arc` where `sub_arc` is + // the arc in the reachable sub-graph for direction `dir` (i.e. + // `sub_reverse_graph[dir]`) and `arc` is the arc in the original graph (i.e. + // `graph`). + std::vector sub_full_arc_indices_[2]; + // `sub_node_indices_[dir]` has size `graph->num_nodes()` such that + // `sub_node_indices[dir][node] = sub_node` where `node` is the node in the + // original graph (i.e. `graph`) and `sub_node` is the node in the reachable + // sub-graph for direction `dir` (i.e. `sub_reverse_graph[dir]`) and -1 if + // `node` is not present in reachable sub-graph. + std::vector sub_node_indices_[2]; + // `sub_is_source_[dir][sub_dir]` has size + // `sub_reverse_graph_[dir].num_nodes()` such that + // `sub_is_source_[dir][sub_dir][sub_node]` is true if `sub_node` is a node in + // the reachable sub-graph for direction `dir` (i.e. `sub_reverse_graph[dir]`) + // which is a source (resp. destination) is `sub_dir` is FORWARD (resp. + // BACKWARD). + std::vector sub_is_source_[2][2]; + // `sub_min_arc_resources_[dir]` has size `max_resources->size()` and + // `sub_min_arc_resources_[dir][r]`, `sub_reverse_graph_[dir].num_nodes()` + // such that `sub_min_arc_resources_[dir][r][sub_node]` is the minimum of + // resource r needed to get to a destination (resp. come from a source) if + // `dir` is FORWARD (resp. BACKWARD). + std::vector> sub_min_arc_resources_[2]; + // Maximum number of labels created for each sub-graph. + int max_num_created_labels_[2]; // Data about the last call of the RunConstrainedShortestPathOnDag() - // function. A Label includes the cumulative length, resources and the - // previous arc used in the path to get to this node. - struct Label { - double length; - // TODO(b/315786885): Optimize resources in Label struct. - std::vector resources; - ArcIndex incoming_arc; - }; - // A label is present in `labels_from_sources_` if and only if it is feasible - // with respect to all resources. - std::vector