diff --git a/ortools/graph/BUILD.bazel b/ortools/graph/BUILD.bazel index bddd93c359..e42355d3bb 100644 --- a/ortools/graph/BUILD.bazel +++ b/ortools/graph/BUILD.bazel @@ -295,6 +295,7 @@ cc_library( ":graphs", ":max_flow", "//ortools/base", + "//ortools/base:dump_vars", "//ortools/base:mathutil", "//ortools/util:saturated_arithmetic", "//ortools/util:stats", @@ -360,6 +361,7 @@ cc_library( srcs = ["topologicalsorter.cc"], hdrs = ["topologicalsorter.h"], deps = [ + ":graph", "//ortools/base", "//ortools/base:container_logging", "//ortools/base:map_util", diff --git a/ortools/graph/graph.h b/ortools/graph/graph.h index 54ac3ab500..9030f66cce 100644 --- a/ortools/graph/graph.h +++ b/ortools/graph/graph.h @@ -739,10 +739,10 @@ void PermuteWithExplicitElementType(const IntVector& permutation, Array* array_to_permute, ElementType unused) { std::vector temp(permutation.size()); - for (int i = 0; i < permutation.size(); ++i) { + for (size_t i = 0; i < permutation.size(); ++i) { temp[i] = (*array_to_permute)[i]; } - for (int i = 0; i < permutation.size(); ++i) { + for (size_t i = 0; i < permutation.size(); ++i) { (*array_to_permute)[permutation[i]] = temp[i]; } } @@ -1109,8 +1109,8 @@ void BaseGraph:: using iterator_category = std::input_iterator_tag; \ using difference_type = ptrdiff_t; \ using pointer = const ArcIndexType*; \ - using reference = const ArcIndexType&; \ using value_type = ArcIndexType; \ + using reference = value_type; \ bool operator!=(const iterator_class_name& other) const { \ return this->index_ != other.index_; \ } \ @@ -1401,7 +1401,7 @@ void StaticGraph::Build( } // We use "tail_" (which now contains rubbish) to permute "head_" faster. - CHECK_EQ(tail_.size(), num_arcs_); + CHECK_EQ(tail_.size(), static_cast(num_arcs_)); tail_.swap(head_); for (int i = 0; i < num_arcs_; ++i) { head_[perm[i]] = tail_[i]; diff --git a/ortools/graph/min_cost_flow.cc b/ortools/graph/min_cost_flow.cc index 70012cd543..c72b2090f2 100644 --- a/ortools/graph/min_cost_flow.cc +++ b/ortools/graph/min_cost_flow.cc @@ -22,6 +22,7 @@ #include "absl/strings/str_format.h" #include "ortools/base/commandlineflags.h" +#include "ortools/base/dump_vars.h" #include "ortools/base/mathutil.h" #include "ortools/graph/graph.h" #include "ortools/graph/graphs.h" @@ -261,8 +262,7 @@ bool GenericMinCostFlow::CheckCostRange() if (3 * max_cost_magnitude < quotient) return true; // Common case. if (3 * max_cost_magnitude <= quotient && remainder == 0) return true; LOG(DFATAL) << "max(3 * abs(arc cost)) * num_nodes overflows: " - << "max_cost_magnitude: " << max_cost_magnitude - << "num_nodes: " << num_nodes << "kMaxCost: " << kMaxCost; + << DUMP_VARS(max_cost_magnitude, num_nodes, kMaxCost); return false; } diff --git a/ortools/graph/samples/simple_max_flow_program.py b/ortools/graph/samples/simple_max_flow_program.py index eedf71d2b9..23a525aa17 100755 --- a/ortools/graph/samples/simple_max_flow_program.py +++ b/ortools/graph/samples/simple_max_flow_program.py @@ -38,7 +38,7 @@ def main(): # [END data] # [START constraints] - # Add arcs in bulk. + # Add arcs in bulk. # note: we could have used add_arc_with_capacity(start, end, capacity) all_arcs = smf.add_arcs_with_capacity(start_nodes, end_nodes, capacities) # [END constraints] diff --git a/ortools/graph/topologicalsorter.cc b/ortools/graph/topologicalsorter.cc index a8c788494a..631e18a535 100644 --- a/ortools/graph/topologicalsorter.cc +++ b/ortools/graph/topologicalsorter.cc @@ -16,12 +16,14 @@ #include #include #include +#include #include #include #include #include #include +#include "absl/status/status.h" #include "ortools/base/map_util.h" #include "ortools/base/stl_util.h" @@ -249,66 +251,7 @@ int DenseIntTopologicalSorterTpl::RemoveDuplicates( template void DenseIntTopologicalSorterTpl::ExtractCycle( std::vector* cycle_nodes) const { - const int num_nodes = adjacency_lists_.size(); - cycle_nodes->clear(); - // To find a cycle, we start a DFS from each yet-unvisited node and - // try to find a cycle, if we don't find it then we know for sure that - // no cycle is reachable from any of the explored nodes (so, we don't - // explore them in later DFSs). - std::vector no_cycle_reachable_from(num_nodes, false); - // The DFS stack will contain a chain of nodes, from the root of the - // DFS to the current leaf. - struct DfsState { - int node; - // Points at the first child node that we did *not* yet look at. - std::size_t adj_list_index; - explicit DfsState(int _node) : node(_node), adj_list_index(0) {} - }; - std::vector dfs_stack; - std::vector in_cur_stack(num_nodes, false); - for (int start_node = 0; start_node < num_nodes; ++start_node) { - if (no_cycle_reachable_from[start_node]) { - continue; - } - // Start the DFS. - dfs_stack.push_back(DfsState(start_node)); - in_cur_stack[start_node] = true; - while (!dfs_stack.empty()) { - DfsState* cur_state = &dfs_stack.back(); - if (cur_state->adj_list_index >= - adjacency_lists_[cur_state->node].size()) { - no_cycle_reachable_from[cur_state->node] = true; - in_cur_stack[cur_state->node] = false; - dfs_stack.pop_back(); - continue; - } - // Look at the current child, and increase the current state's - // adj_list_index. - const int child = - adjacency_lists_[cur_state->node][cur_state->adj_list_index]; - ++(cur_state->adj_list_index); - if (no_cycle_reachable_from[child]) { - continue; - } - if (in_cur_stack[child]) { - // We detected a cycle! Fill it and return. - for (;;) { - cycle_nodes->push_back(dfs_stack.back().node); - if (dfs_stack.back().node == child) { - std::reverse(cycle_nodes->begin(), cycle_nodes->end()); - return; - } - dfs_stack.pop_back(); - } - } - // Push the child onto the stack. - dfs_stack.push_back(DfsState(child)); - in_cur_stack[child] = true; - } - } - // If we're here, then all the DFS stopped, and they never encountered - // a cycle (otherwise, we would have returned). Just exit; the output - // vector has been cleared already. + *cycle_nodes = util::graph::FindCycleInGraph(adjacency_lists_).value(); } // Generate the templated code. Including these definitions allows us @@ -317,18 +260,4 @@ template class DenseIntTopologicalSorterTpl; template class DenseIntTopologicalSorterTpl; } // namespace internal - -std::vector FindCycleInDenseIntGraph( - int num_nodes, const std::vector>& arcs) { - std::vector cycle; - if (num_nodes < 1) { - return cycle; - } - internal::DenseIntTopologicalSorterTpl sorter(num_nodes); - for (const auto& arc : arcs) { - sorter.AddEdge(arc.first, arc.second); - } - sorter.ExtractCycle(&cycle); - return cycle; -} } // namespace util diff --git a/ortools/graph/topologicalsorter.h b/ortools/graph/topologicalsorter.h index 6a34cebf29..aebbd27fd0 100644 --- a/ortools/graph/topologicalsorter.h +++ b/ortools/graph/topologicalsorter.h @@ -49,6 +49,7 @@ #include "ortools/base/map_util.h" #include "ortools/base/status_builder.h" #include "ortools/base/stl_util.h" +#include "ortools/graph/graph.h" namespace util { namespace graph { @@ -77,26 +78,24 @@ namespace graph { // ASSIGN_OR_RETURN(std::vector topo_order, FastTopologicalSort(adj)); // // or -// util::StaticGraph<> graph(/*num_nodes=*/10, /*num_edges=*/42); -// graph.AddEdge(1, 3); -// ... -// graph.Build(); -// ASSIGN_OR_RETURN(std::vector topo_order, FastTopologicalSort(graph)); +// std::vector> arcs = {{.., ..}, ..., }; +// ASSIGN_OR_RETURN( +// std::vector topo_order, +// FastTopologicalSort(util::StaticGraph<>::FromArcs(num_nodes, arcs))); // -template +template // vector>, util::StaticGraph<>, .. absl::StatusOr> FastTopologicalSort(const AdjacencyLists& adj); -} // namespace graph - // Finds a cycle in the directed graph given as argument: nodes are dense // integers in 0..num_nodes-1, and (directed) arcs are pairs of nodes // {from, to}. // The returned cycle is a list of nodes that form a cycle, eg. {1, 4, 3} // if the cycle 1->4->3->1 exists. // If the graph is acyclic, returns an empty vector. -// TODO(user): Deprecate this version and promote an adjacency-list based one. -ABSL_MUST_USE_RESULT std::vector FindCycleInDenseIntGraph( - int num_nodes, const std::vector>& arcs); +template // vector>, util::StaticGraph<>, .. +absl::StatusOr> FindCycleInGraph(const AdjacencyLists& adj); + +} // namespace graph // [Stable]TopologicalSort[OrDie]: // @@ -130,6 +129,14 @@ std::vector StableTopologicalSortOrDie( // ______________________ END OF THE RECOMMENDED API ___________________________ +// DEPRECATED. Use util::graph::FindCycleInGraph() directly. +inline ABSL_MUST_USE_RESULT std::vector FindCycleInDenseIntGraph( + int num_nodes, const std::vector>& arcs) { + return util::graph::FindCycleInGraph( + util::StaticGraph<>::FromArcs(num_nodes, arcs)) + .value(); +} + // DEPRECATED: DenseInt[Stable]TopologicalSort[OrDie]. // Kept here for legacy reasons, but most new users should use // FastTopologicalSort(): @@ -613,6 +620,80 @@ absl::StatusOr> FastTopologicalSort( return topo_order; } +template +absl::StatusOr> FindCycleInGraph(const AdjacencyLists& adj) { + const size_t num_nodes = adj.size(); + if (num_nodes > std::numeric_limits::max()) { + return absl::InvalidArgumentError( + absl::StrFormat("Too many nodes: adj.size()=%d", adj.size())); + } + + // To find a cycle, we start a DFS from each yet-unvisited node and + // try to find a cycle, if we don't find it then we know for sure that + // no cycle is reachable from any of the explored nodes (so, we don't + // explore them in later DFSs). + std::vector no_cycle_reachable_from(num_nodes, false); + // The DFS stack will contain a chain of nodes, from the root of the + // DFS to the current leaf. + struct DfsState { + int node; + // Points at the first child node that we did *not* yet look at. + int adj_list_index; + explicit DfsState(int _node) : node(_node), adj_list_index(0) {} + }; + std::vector dfs_stack; + std::vector in_cur_stack(num_nodes, false); + for (int start_node = 0; start_node < static_cast(num_nodes); + ++start_node) { + if (no_cycle_reachable_from[start_node]) continue; + // Start the DFS. + dfs_stack.push_back(DfsState(start_node)); + in_cur_stack[start_node] = true; + while (!dfs_stack.empty()) { + DfsState* cur_state = &dfs_stack.back(); + if (static_cast(cur_state->adj_list_index) >= + adj[cur_state->node].size()) { + no_cycle_reachable_from[cur_state->node] = true; + in_cur_stack[cur_state->node] = false; + dfs_stack.pop_back(); + continue; + } + // Look at the current child, and increase the current state's + // adj_list_index. + // TODO(user): Caching adj[cur_state->node] in a local stack to improve + // locality and so that the [] operator is called exactly once per node. + const int child = adj[cur_state->node][cur_state->adj_list_index++]; + if (static_cast(child) >= num_nodes) { + return absl::InvalidArgumentError(absl::StrFormat( + "Invalid child %d in adj[%d]", child, cur_state->node)); + } + if (no_cycle_reachable_from[child]) continue; + if (in_cur_stack[child]) { + // We detected a cycle! It corresponds to the tail end of dfs_stack, + // in reverse order, until we find "child". + int cycle_start = dfs_stack.size() - 1; + while (dfs_stack[cycle_start].node != child) --cycle_start; + const int cycle_size = dfs_stack.size() - cycle_start; + std::vector cycle(cycle_size); + for (int c = 0; c < cycle_size; ++c) { + cycle[c] = dfs_stack[cycle_start + c].node; + } + return cycle; + } + // Push the child onto the stack. + dfs_stack.push_back(DfsState(child)); + in_cur_stack[child] = true; + // Verify that its adjacency list seems valid. + if (adj[child].size() > std::numeric_limits::max()) { + return absl::InvalidArgumentError(absl::StrFormat( + "Invalid adj[%d].size() = %d", child, adj[child].size())); + } + } + } + // If we're here, then all the DFS stopped, and there is no cycle. + return std::vector{}; +} + } // namespace graph } // namespace util