Skip to content

Commit 8b097bb

Browse files
committed
graph: bring back dag_ stuff
1 parent ee2ab2f commit 8b097bb

21 files changed

+5083
-0
lines changed

ortools/graph/BUILD.bazel

+99
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,31 @@ cc_test(
628628
],
629629
)
630630

631+
cc_library(
632+
name = "dag_connectivity",
633+
srcs = ["dag_connectivity.cc"],
634+
hdrs = ["dag_connectivity.h"],
635+
deps = [
636+
":topologicalsorter",
637+
"//ortools/base",
638+
"//ortools/base:container_logging",
639+
"//ortools/util:bitset",
640+
"@com_google_absl//absl/types:span",
641+
],
642+
)
643+
644+
cc_test(
645+
name = "dag_connectivity_test",
646+
srcs = ["dag_connectivity_test.cc"],
647+
deps = [
648+
":dag_connectivity",
649+
"//ortools/base:gmock_main",
650+
"//ortools/util:bitset",
651+
"@com_google_absl//absl/strings",
652+
"@com_google_absl//absl/types:span",
653+
],
654+
)
655+
631656
cc_library(
632657
name = "perfect_matching",
633658
srcs = ["perfect_matching.cc"],
@@ -646,6 +671,40 @@ cc_library(
646671
],
647672
)
648673

674+
cc_library(
675+
name = "dag_shortest_path",
676+
srcs = ["dag_shortest_path.cc"],
677+
hdrs = ["dag_shortest_path.h"],
678+
deps = [
679+
":graph",
680+
":topologicalsorter",
681+
"@com_google_absl//absl/algorithm:container",
682+
"@com_google_absl//absl/log",
683+
"@com_google_absl//absl/log:check",
684+
"@com_google_absl//absl/status",
685+
"@com_google_absl//absl/strings:str_format",
686+
"@com_google_absl//absl/types:span",
687+
],
688+
)
689+
690+
cc_library(
691+
name = "dag_constrained_shortest_path",
692+
srcs = ["dag_constrained_shortest_path.cc"],
693+
hdrs = ["dag_constrained_shortest_path.h"],
694+
deps = [
695+
":dag_shortest_path",
696+
":graph",
697+
":topologicalsorter",
698+
"//ortools/base:threadpool",
699+
"@com_google_absl//absl/algorithm:container",
700+
"@com_google_absl//absl/base:log_severity",
701+
"@com_google_absl//absl/log:check",
702+
"@com_google_absl//absl/status:statusor",
703+
"@com_google_absl//absl/strings:str_format",
704+
"@com_google_absl//absl/types:span",
705+
],
706+
)
707+
649708
cc_library(
650709
name = "rooted_tree",
651710
hdrs = ["rooted_tree.h"],
@@ -696,6 +755,46 @@ cc_test(
696755
],
697756
)
698757

758+
cc_test(
759+
name = "dag_shortest_path_test",
760+
size = "small",
761+
srcs = ["dag_shortest_path_test.cc"],
762+
deps = [
763+
":dag_shortest_path",
764+
":graph",
765+
":io",
766+
"//ortools/base:dump_vars",
767+
"//ortools/base:gmock_main",
768+
"//ortools/util:flat_matrix",
769+
"@com_google_absl//absl/algorithm:container",
770+
"@com_google_absl//absl/log:check",
771+
"@com_google_absl//absl/random",
772+
"@com_google_absl//absl/status",
773+
"@com_google_absl//absl/types:span",
774+
"@com_google_benchmark//:benchmark",
775+
],
776+
)
777+
778+
cc_test(
779+
name = "dag_constrained_shortest_path_test",
780+
srcs = ["dag_constrained_shortest_path_test.cc"],
781+
deps = [
782+
":dag_constrained_shortest_path",
783+
":graph",
784+
":io",
785+
"//ortools/base:dump_vars",
786+
"//ortools/base:gmock_main",
787+
"//ortools/math_opt/cpp:math_opt",
788+
"//ortools/math_opt/solvers:cp_sat_solver",
789+
"@com_google_absl//absl/algorithm:container",
790+
"@com_google_absl//absl/log:check",
791+
"@com_google_absl//absl/random",
792+
"@com_google_absl//absl/strings",
793+
"@com_google_absl//absl/types:span",
794+
"@com_google_benchmark//:benchmark",
795+
],
796+
)
797+
699798
# From util/graph
700799
cc_library(
701800
name = "connected_components",

ortools/graph/README.md

+10
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@ Generic algorithms for shortest paths:
2828

2929
Specific algorithms for paths:
3030

31+
* [`dag_shortest_path.h`][dag_shortest_path_h]: shortest paths on directed
32+
acyclic graphs. If you have such a graph, this implementation is likely to
33+
be the fastest. Unlike most implementations, these algorithms have two
34+
interfaces: a "simple" one (list of edges and weights) and a standard one
35+
(taking as input a graph data structure from
36+
[`//ortools/graph/graph.h`][graph_h]).
37+
38+
* [`dag_constrained_shortest_path.`][dag_constrained_shortest_path_h]:
39+
shortest paths on directed acyclic graphs with resource constraints.
40+
3141
* [`hamiltonian_path.h`][hamiltonian_path_h]: entry point for computing
3242
minimum [Hamiltonian paths](https://en.wikipedia.org/wiki/Hamiltonian_path)
3343
and cycles on directed graphs with costs on arcs, using a

ortools/graph/dag_connectivity.cc

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright 2010-2025 Google LLC
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
#include "ortools/graph/dag_connectivity.h"
15+
16+
#include <algorithm>
17+
#include <cstdint>
18+
#include <utility>
19+
#include <vector>
20+
21+
#include "absl/types/span.h"
22+
#include "ortools/base/container_logging.h"
23+
#include "ortools/base/logging.h"
24+
#include "ortools/graph/topologicalsorter.h"
25+
26+
namespace operations_research {
27+
28+
// The algorithm is as follows:
29+
// 1. Sort the nodes of the graph topologically. If a cycle is detected,
30+
// terminate
31+
// 2. Build the adjacency list for the graph, i.e., adj_list[i] is the list
32+
// of nodes that can be directly reached from i.
33+
// 3. Create a 2d bool vector x where x[i][j] indicates there is a path from
34+
// i to j, and for each arc in "arcs", set x[i][j] to true
35+
// 4. In reverse topological order (leaves first) for each node i, for each
36+
// child j of i, for each node k reachable for j, set k to be reachable
37+
// from i as well (x[i][k] = true for all k s.t. x[j][k] is true).
38+
//
39+
// The running times of the steps are:
40+
// 1. O(num_arcs)
41+
// 2. O(num_arcs)
42+
// 3. O(num_nodes^2 + num_arcs)
43+
// 4. O(num_nodes*num_arcs)
44+
// Thus the total run time is O(num_nodes^2 + num_nodes*num_arcs).
45+
//
46+
// Implementation note: typically, step 4 will dominate. To speed up the inner
47+
// loop, we use Bitset64, allowing use to merge 64 x[k][j] values at a time with
48+
// the |= operator.
49+
//
50+
// For graphs where num_arcs is o(num_nodes), a different data structure could
51+
// be used in 3, but this isn't really the interesting case (and prevents |=).
52+
//
53+
// A further improvement on this algorithm is possible, step four can run in
54+
// time O(num_nodes*num_arcs_in_transitive_reduction), and as a by product,
55+
// the transitive reduction can also be produced as output. For details, see
56+
// "A REDUCT-AND_CLOSURE ALGORITHM FOR GRAPHS" (Alla Goralcikova and
57+
// Vaclav Koubek 1979). The better typeset paper "AN IMPROVED ALGORITHM FOR
58+
// TRANSITIVE CLOSURE ON ACYCLIC DIGRAPHS" (Klaus Simon 1988) gives a slight
59+
// improvement on the result (less memory, same runtime).
60+
std::vector<Bitset64<int64_t>> ComputeDagConnectivity(
61+
absl::Span<const std::pair<int, int>> arcs, bool* error_was_cyclic,
62+
std::vector<int>* error_cycle_out) {
63+
CHECK(error_was_cyclic != nullptr);
64+
CHECK(error_cycle_out != nullptr);
65+
*error_was_cyclic = false;
66+
error_cycle_out->clear();
67+
if (arcs.empty()) return {};
68+
int num_nodes = 0;
69+
for (const std::pair<int, int>& arc : arcs) {
70+
CHECK_GE(arc.first, 0);
71+
CHECK_GE(arc.second, 0);
72+
num_nodes = std::max(num_nodes, arc.first + 1);
73+
num_nodes = std::max(num_nodes, arc.second + 1);
74+
}
75+
DenseIntStableTopologicalSorter sorter(num_nodes);
76+
for (const auto& arc : arcs) {
77+
sorter.AddEdge(arc.first, arc.second);
78+
}
79+
std::vector<int> topological_order;
80+
int next;
81+
while (sorter.GetNext(&next, error_was_cyclic, error_cycle_out)) {
82+
topological_order.push_back(next);
83+
}
84+
if (*error_was_cyclic) return {};
85+
std::vector<std::vector<int>> adjacency_list(num_nodes);
86+
for (const auto& arc : arcs) {
87+
adjacency_list[arc.first].push_back(arc.second);
88+
}
89+
90+
std::vector<Bitset64<int64_t>> connectivity(num_nodes);
91+
for (Bitset64<int64_t>& bitset : connectivity) {
92+
bitset.Resize(num_nodes);
93+
}
94+
for (const auto& arc : arcs) {
95+
connectivity[arc.first].Set(arc.second);
96+
}
97+
98+
// Iterate over the nodes in reverse topological order.
99+
std::reverse(topological_order.begin(), topological_order.end());
100+
// NOTE(user): these two loops visit every arc in the graph, and each
101+
// union is over a set of size given by the number of nodes. This gives the
102+
// runtime in step 4 of O(num_nodes*num_arcs)
103+
for (const int node : topological_order) {
104+
for (const int child : adjacency_list[node]) {
105+
connectivity[node].Union(connectivity[child]);
106+
}
107+
}
108+
return connectivity;
109+
}
110+
111+
std::vector<Bitset64<int64_t>> ComputeDagConnectivityOrDie(
112+
absl::Span<const std::pair<int, int>> arcs) {
113+
bool error_was_cyclic = false;
114+
std::vector<int> error_cycle;
115+
std::vector<Bitset64<int64_t>> result =
116+
ComputeDagConnectivity(arcs, &error_was_cyclic, &error_cycle);
117+
CHECK(!error_was_cyclic) << "Graph should have been acyclic but has cycle: "
118+
<< gtl::LogContainer(error_cycle);
119+
return result;
120+
}
121+
122+
} // namespace operations_research

ortools/graph/dag_connectivity.h

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Copyright 2010-2025 Google LLC
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
#ifndef OR_TOOLS_GRAPH_DAG_CONNECTIVITY_H_
15+
#define OR_TOOLS_GRAPH_DAG_CONNECTIVITY_H_
16+
17+
#include <cstdint>
18+
#include <utility>
19+
#include <vector>
20+
21+
#include "absl/types/span.h"
22+
#include "ortools/util/bitset.h"
23+
24+
namespace operations_research {
25+
26+
// Given a directed graph, as defined by the arc list "arcs", computes either:
27+
// 1. If the graph is acyclic, the matrix of values x, where x[i][j] indicates
28+
// that there is a directed path from i to j.
29+
// 2. If the graph is cyclic, "error_cycle_out" is set to contain the cycle,
30+
// and the return value is empty.
31+
//
32+
// The algorithm runs in O(num_nodes^2 + num_nodes*num_arcs).
33+
//
34+
// Inputs:
35+
// arcs: each a in "arcs" is a directed edge from a.first to a.second. Must
36+
// have a.first, a.second >= 0. The graph is assumed to have nodes
37+
// {0,1,...,max_{a in arcs} max(a.first, a.second)}, or have no nodes
38+
// if arcs is the empty list.
39+
// error_was_cyclic: output arg, is set to true if a cycle is detected.
40+
// error_cycle_out: output arg, if a cycle is detected, error_cycle_out is
41+
// set to contain the nodes of the cycle in order.
42+
//
43+
// Note: useful for computing the transitive closure of a binary relation, e.g.
44+
// given the relation i < j for i,j in S that is transitive and some known
45+
// values i < j, create a node for each i in S and an arc for each known
46+
// relationship. Then any relationship implied by transitivity is given by
47+
// the resulting matrix produced, or if the relation fails transitivity, a cycle
48+
// proving this is produced.
49+
std::vector<Bitset64<int64_t>> ComputeDagConnectivity(
50+
absl::Span<const std::pair<int, int>> arcs, bool* error_was_cyclic,
51+
std::vector<int>* error_cycle_out);
52+
53+
// Like above, but will CHECK fail if the digraph with arc list "arcs"
54+
// contains a cycle.
55+
std::vector<Bitset64<int64_t>> ComputeDagConnectivityOrDie(
56+
absl::Span<const std::pair<int, int>> arcs);
57+
58+
} // namespace operations_research
59+
60+
#endif // OR_TOOLS_GRAPH_DAG_CONNECTIVITY_H_

0 commit comments

Comments
 (0)