xref: /aosp_15_r20/external/stg/scc_test.cc (revision 9e3b08ae94a55201065475453d799e8b1378bea6)
1*9e3b08aeSAndroid Build Coastguard Worker // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
2*9e3b08aeSAndroid Build Coastguard Worker // -*- mode: C++ -*-
3*9e3b08aeSAndroid Build Coastguard Worker //
4*9e3b08aeSAndroid Build Coastguard Worker // Copyright 2020-2022 Google LLC
5*9e3b08aeSAndroid Build Coastguard Worker //
6*9e3b08aeSAndroid Build Coastguard Worker // Licensed under the Apache License v2.0 with LLVM Exceptions (the
7*9e3b08aeSAndroid Build Coastguard Worker // "License"); you may not use this file except in compliance with the
8*9e3b08aeSAndroid Build Coastguard Worker // License.  You may obtain a copy of the License at
9*9e3b08aeSAndroid Build Coastguard Worker //
10*9e3b08aeSAndroid Build Coastguard Worker //     https://llvm.org/LICENSE.txt
11*9e3b08aeSAndroid Build Coastguard Worker //
12*9e3b08aeSAndroid Build Coastguard Worker // Unless required by applicable law or agreed to in writing, software
13*9e3b08aeSAndroid Build Coastguard Worker // distributed under the License is distributed on an "AS IS" BASIS,
14*9e3b08aeSAndroid Build Coastguard Worker // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15*9e3b08aeSAndroid Build Coastguard Worker // See the License for the specific language governing permissions and
16*9e3b08aeSAndroid Build Coastguard Worker // limitations under the License.
17*9e3b08aeSAndroid Build Coastguard Worker //
18*9e3b08aeSAndroid Build Coastguard Worker // Author: Giuliano Procida
19*9e3b08aeSAndroid Build Coastguard Worker 
20*9e3b08aeSAndroid Build Coastguard Worker #include "scc.h"
21*9e3b08aeSAndroid Build Coastguard Worker 
22*9e3b08aeSAndroid Build Coastguard Worker #include <algorithm>
23*9e3b08aeSAndroid Build Coastguard Worker #include <cstddef>
24*9e3b08aeSAndroid Build Coastguard Worker #include <cstdint>
25*9e3b08aeSAndroid Build Coastguard Worker #include <iostream>
26*9e3b08aeSAndroid Build Coastguard Worker #include <map>
27*9e3b08aeSAndroid Build Coastguard Worker #include <optional>
28*9e3b08aeSAndroid Build Coastguard Worker #include <random>
29*9e3b08aeSAndroid Build Coastguard Worker #include <set>
30*9e3b08aeSAndroid Build Coastguard Worker #include <sstream>
31*9e3b08aeSAndroid Build Coastguard Worker #include <utility>
32*9e3b08aeSAndroid Build Coastguard Worker #include <vector>
33*9e3b08aeSAndroid Build Coastguard Worker 
34*9e3b08aeSAndroid Build Coastguard Worker #include <catch2/catch.hpp>
35*9e3b08aeSAndroid Build Coastguard Worker 
36*9e3b08aeSAndroid Build Coastguard Worker namespace Test {
37*9e3b08aeSAndroid Build Coastguard Worker 
38*9e3b08aeSAndroid Build Coastguard Worker using stg::SCC;
39*9e3b08aeSAndroid Build Coastguard Worker 
40*9e3b08aeSAndroid Build Coastguard Worker // Nodes are [0, N), the sets are the out-edges.
41*9e3b08aeSAndroid Build Coastguard Worker typedef std::vector<std::set<size_t>> Graph;
42*9e3b08aeSAndroid Build Coastguard Worker 
43*9e3b08aeSAndroid Build Coastguard Worker template <typename G>
invent(size_t n,G & gen)44*9e3b08aeSAndroid Build Coastguard Worker Graph invent(size_t n, G& gen) {
45*9e3b08aeSAndroid Build Coastguard Worker   Graph graph(n);
46*9e3b08aeSAndroid Build Coastguard Worker   std::uniform_int_distribution<int> toss(0, 1);
47*9e3b08aeSAndroid Build Coastguard Worker   for (auto& node : graph) {
48*9e3b08aeSAndroid Build Coastguard Worker     for (size_t o = 0; o < n; ++o) {
49*9e3b08aeSAndroid Build Coastguard Worker       if (toss(gen)) {
50*9e3b08aeSAndroid Build Coastguard Worker         node.insert(o);
51*9e3b08aeSAndroid Build Coastguard Worker       }
52*9e3b08aeSAndroid Build Coastguard Worker     }
53*9e3b08aeSAndroid Build Coastguard Worker   }
54*9e3b08aeSAndroid Build Coastguard Worker   return graph;
55*9e3b08aeSAndroid Build Coastguard Worker }
56*9e3b08aeSAndroid Build Coastguard Worker 
57*9e3b08aeSAndroid Build Coastguard Worker // Generate a graph g' where i -> j iff i and j are strongly connected in g.
symmetric_subset_of_reflexive_transitive_closure(Graph g)58*9e3b08aeSAndroid Build Coastguard Worker Graph symmetric_subset_of_reflexive_transitive_closure(Graph g) {
59*9e3b08aeSAndroid Build Coastguard Worker   const size_t n = g.size();
60*9e3b08aeSAndroid Build Coastguard Worker   // compute reflexive, transitive closure using a modified Floyd-Warshall
61*9e3b08aeSAndroid Build Coastguard Worker   for (size_t o = 0; o < n; ++o) {
62*9e3b08aeSAndroid Build Coastguard Worker     // 1. add edge o -> o, for each node o
63*9e3b08aeSAndroid Build Coastguard Worker     g[o].insert(o);
64*9e3b08aeSAndroid Build Coastguard Worker   }
65*9e3b08aeSAndroid Build Coastguard Worker   for (size_t k = 0; k < n; ++k) {
66*9e3b08aeSAndroid Build Coastguard Worker     // 2. for each node k check for paths of the form: i -> ... -> k -> ... -> j
67*9e3b08aeSAndroid Build Coastguard Worker     // where no node after k appears in the ...
68*9e3b08aeSAndroid Build Coastguard Worker     for (size_t i = 0; i < n; ++i) {
69*9e3b08aeSAndroid Build Coastguard Worker       // since we scan the nodes k in order, it suffices to consider just paths:
70*9e3b08aeSAndroid Build Coastguard Worker       // i -> k -> j
71*9e3b08aeSAndroid Build Coastguard Worker       if (g[i].contains(k)) {
72*9e3b08aeSAndroid Build Coastguard Worker         // we have i -> k
73*9e3b08aeSAndroid Build Coastguard Worker         for (size_t j = 0; j < n; ++j) {
74*9e3b08aeSAndroid Build Coastguard Worker           if (g[k].contains(j)) {
75*9e3b08aeSAndroid Build Coastguard Worker             // and k -> j
76*9e3b08aeSAndroid Build Coastguard Worker             g[i].insert(j);
77*9e3b08aeSAndroid Build Coastguard Worker           }
78*9e3b08aeSAndroid Build Coastguard Worker         }
79*9e3b08aeSAndroid Build Coastguard Worker       }
80*9e3b08aeSAndroid Build Coastguard Worker     }
81*9e3b08aeSAndroid Build Coastguard Worker   }
82*9e3b08aeSAndroid Build Coastguard Worker   // now have edge i -> j iff there is a path from i to j
83*9e3b08aeSAndroid Build Coastguard Worker   for (size_t i = 0; i < n; ++i) {
84*9e3b08aeSAndroid Build Coastguard Worker     for (size_t j = i + 1; j < n; ++j) {
85*9e3b08aeSAndroid Build Coastguard Worker       // discard i -> j if not j -> i and vice versa
86*9e3b08aeSAndroid Build Coastguard Worker       auto ij = g[i].contains(j);
87*9e3b08aeSAndroid Build Coastguard Worker       auto ji = g[j].contains(i);
88*9e3b08aeSAndroid Build Coastguard Worker       if (ij < ji) {
89*9e3b08aeSAndroid Build Coastguard Worker         g[j].erase(i);
90*9e3b08aeSAndroid Build Coastguard Worker       }
91*9e3b08aeSAndroid Build Coastguard Worker       if (ji < ij) {
92*9e3b08aeSAndroid Build Coastguard Worker         g[i].erase(j);
93*9e3b08aeSAndroid Build Coastguard Worker       }
94*9e3b08aeSAndroid Build Coastguard Worker     }
95*9e3b08aeSAndroid Build Coastguard Worker   }
96*9e3b08aeSAndroid Build Coastguard Worker   // now have edge i -> j iff there is a path from i to j and a path from j to i
97*9e3b08aeSAndroid Build Coastguard Worker   return g;
98*9e3b08aeSAndroid Build Coastguard Worker }
99*9e3b08aeSAndroid Build Coastguard Worker 
100*9e3b08aeSAndroid Build Coastguard Worker // Generate a graph where i -> j iff i and j are in the same SCC.
scc_strong_connectivity(const std::vector<std::set<size_t>> & sccs)101*9e3b08aeSAndroid Build Coastguard Worker Graph scc_strong_connectivity(const std::vector<std::set<size_t>>& sccs) {
102*9e3b08aeSAndroid Build Coastguard Worker   size_t n = 0;
103*9e3b08aeSAndroid Build Coastguard Worker   std::map<size_t, const std::set<size_t>*> edges;
104*9e3b08aeSAndroid Build Coastguard Worker   for (const auto& scc : sccs) {
105*9e3b08aeSAndroid Build Coastguard Worker     for (auto o : scc) {
106*9e3b08aeSAndroid Build Coastguard Worker       if (o >= n) {
107*9e3b08aeSAndroid Build Coastguard Worker         n = o + 1;
108*9e3b08aeSAndroid Build Coastguard Worker       }
109*9e3b08aeSAndroid Build Coastguard Worker       edges[o] = &scc;
110*9e3b08aeSAndroid Build Coastguard Worker     }
111*9e3b08aeSAndroid Build Coastguard Worker   }
112*9e3b08aeSAndroid Build Coastguard Worker   Graph g(n);
113*9e3b08aeSAndroid Build Coastguard Worker   for (size_t o = 0; o < n; ++o) {
114*9e3b08aeSAndroid Build Coastguard Worker     g[o] = *edges[o];
115*9e3b08aeSAndroid Build Coastguard Worker   }
116*9e3b08aeSAndroid Build Coastguard Worker   return g;
117*9e3b08aeSAndroid Build Coastguard Worker }
118*9e3b08aeSAndroid Build Coastguard Worker 
dfs(std::set<size_t> & visited,SCC<size_t> & scc,const Graph & g,size_t node,std::vector<std::set<size_t>> & sccs)119*9e3b08aeSAndroid Build Coastguard Worker void dfs(std::set<size_t>& visited, SCC<size_t>& scc, const Graph& g,
120*9e3b08aeSAndroid Build Coastguard Worker          size_t node, std::vector<std::set<size_t>>& sccs) {
121*9e3b08aeSAndroid Build Coastguard Worker   if (visited.contains(node)) {
122*9e3b08aeSAndroid Build Coastguard Worker     return;
123*9e3b08aeSAndroid Build Coastguard Worker   }
124*9e3b08aeSAndroid Build Coastguard Worker   auto handle = scc.Open(node);
125*9e3b08aeSAndroid Build Coastguard Worker   if (!handle) {
126*9e3b08aeSAndroid Build Coastguard Worker     return;
127*9e3b08aeSAndroid Build Coastguard Worker   }
128*9e3b08aeSAndroid Build Coastguard Worker   for (auto o : g[node]) {
129*9e3b08aeSAndroid Build Coastguard Worker     dfs(visited, scc, g, o, sccs);
130*9e3b08aeSAndroid Build Coastguard Worker   }
131*9e3b08aeSAndroid Build Coastguard Worker   auto nodes = scc.Close(*handle);
132*9e3b08aeSAndroid Build Coastguard Worker   if (!nodes.empty()) {
133*9e3b08aeSAndroid Build Coastguard Worker     std::set<size_t> scc_set;
134*9e3b08aeSAndroid Build Coastguard Worker     for (auto o : nodes) {
135*9e3b08aeSAndroid Build Coastguard Worker       CHECK(visited.insert(o).second);
136*9e3b08aeSAndroid Build Coastguard Worker       CHECK(scc_set.insert(o).second);
137*9e3b08aeSAndroid Build Coastguard Worker     }
138*9e3b08aeSAndroid Build Coastguard Worker     sccs.push_back(scc_set);
139*9e3b08aeSAndroid Build Coastguard Worker   }
140*9e3b08aeSAndroid Build Coastguard Worker }
141*9e3b08aeSAndroid Build Coastguard Worker 
process(const Graph & g)142*9e3b08aeSAndroid Build Coastguard Worker void process(const Graph& g) {
143*9e3b08aeSAndroid Build Coastguard Worker   const size_t n = g.size();
144*9e3b08aeSAndroid Build Coastguard Worker 
145*9e3b08aeSAndroid Build Coastguard Worker   // find SCCs
146*9e3b08aeSAndroid Build Coastguard Worker   std::set<size_t> visited;
147*9e3b08aeSAndroid Build Coastguard Worker   std::vector<std::set<size_t>> sccs;
148*9e3b08aeSAndroid Build Coastguard Worker   for (size_t o = 0; o < n; ++o) {
149*9e3b08aeSAndroid Build Coastguard Worker     // could reuse a single SCC finder but assert stronger invariants this way
150*9e3b08aeSAndroid Build Coastguard Worker     SCC<size_t> scc;
151*9e3b08aeSAndroid Build Coastguard Worker     dfs(visited, scc, g, o, sccs);
152*9e3b08aeSAndroid Build Coastguard Worker   }
153*9e3b08aeSAndroid Build Coastguard Worker 
154*9e3b08aeSAndroid Build Coastguard Worker   // check partition and topological order properties
155*9e3b08aeSAndroid Build Coastguard Worker   std::set<size_t> seen;
156*9e3b08aeSAndroid Build Coastguard Worker   for (const auto& nodes : sccs) {
157*9e3b08aeSAndroid Build Coastguard Worker     CHECK(!nodes.empty());
158*9e3b08aeSAndroid Build Coastguard Worker     for (auto node : nodes) {
159*9e3b08aeSAndroid Build Coastguard Worker       // value in range [0, n)
160*9e3b08aeSAndroid Build Coastguard Worker       CHECK(node < n);
161*9e3b08aeSAndroid Build Coastguard Worker       // value seen at most once
162*9e3b08aeSAndroid Build Coastguard Worker       CHECK(seen.insert(node).second);
163*9e3b08aeSAndroid Build Coastguard Worker     }
164*9e3b08aeSAndroid Build Coastguard Worker     for (auto node : nodes) {
165*9e3b08aeSAndroid Build Coastguard Worker       for (auto o : g[node]) {
166*9e3b08aeSAndroid Build Coastguard Worker         // edges point to nodes in this or earlier SCCs
167*9e3b08aeSAndroid Build Coastguard Worker         CHECK(seen.contains(o));
168*9e3b08aeSAndroid Build Coastguard Worker       }
169*9e3b08aeSAndroid Build Coastguard Worker     }
170*9e3b08aeSAndroid Build Coastguard Worker   }
171*9e3b08aeSAndroid Build Coastguard Worker   // exactly n values seen
172*9e3b08aeSAndroid Build Coastguard Worker   CHECK(seen.size() == n);
173*9e3b08aeSAndroid Build Coastguard Worker 
174*9e3b08aeSAndroid Build Coastguard Worker   // check strong connectivity
175*9e3b08aeSAndroid Build Coastguard Worker   auto g_scc_closure = scc_strong_connectivity(sccs);
176*9e3b08aeSAndroid Build Coastguard Worker   auto g_closure = symmetric_subset_of_reflexive_transitive_closure(g);
177*9e3b08aeSAndroid Build Coastguard Worker   CHECK(g_scc_closure == g_closure);
178*9e3b08aeSAndroid Build Coastguard Worker }
179*9e3b08aeSAndroid Build Coastguard Worker 
180*9e3b08aeSAndroid Build Coastguard Worker TEST_CASE("randomly-generated graphs") {
181*9e3b08aeSAndroid Build Coastguard Worker   std::ranlux48 gen;
182*9e3b08aeSAndroid Build Coastguard Worker   auto seed = gen();
183*9e3b08aeSAndroid Build Coastguard Worker   // NOTES:
184*9e3b08aeSAndroid Build Coastguard Worker   //   Graphs of size 6 are plenty big enough to shake out bugs.
185*9e3b08aeSAndroid Build Coastguard Worker   //   There are O(2^k^2) possible directed graphs of size k.
186*9e3b08aeSAndroid Build Coastguard Worker   //   Testing costs are O(k^3) so we restrict accordingly.
187*9e3b08aeSAndroid Build Coastguard Worker   const uint64_t budget = 10000;
188*9e3b08aeSAndroid Build Coastguard Worker   for (size_t k = 0; k < 7; ++k) {
189*9e3b08aeSAndroid Build Coastguard Worker     const uint64_t count = std::min(static_cast<uint64_t>(1) << (k * k),
190*9e3b08aeSAndroid Build Coastguard Worker                                     budget / (k ? k * k * k : 1));
191*9e3b08aeSAndroid Build Coastguard Worker     INFO("testing with " << count << " graphs of size " << k);
192*9e3b08aeSAndroid Build Coastguard Worker     for (uint64_t n = 0; n < count; ++n, ++seed) {
193*9e3b08aeSAndroid Build Coastguard Worker       gen.seed(seed);
194*9e3b08aeSAndroid Build Coastguard Worker       const Graph g = invent(k, gen);
195*9e3b08aeSAndroid Build Coastguard Worker       std::ostringstream os;
196*9e3b08aeSAndroid Build Coastguard Worker       os << "a graph of " << k << " nodes generated using seed " << seed;
197*9e3b08aeSAndroid Build Coastguard Worker       GIVEN(os.str()) {
198*9e3b08aeSAndroid Build Coastguard Worker         process(g);
199*9e3b08aeSAndroid Build Coastguard Worker       }
200*9e3b08aeSAndroid Build Coastguard Worker     }
201*9e3b08aeSAndroid Build Coastguard Worker   }
202*9e3b08aeSAndroid Build Coastguard Worker }
203*9e3b08aeSAndroid Build Coastguard Worker 
204*9e3b08aeSAndroid Build Coastguard Worker }  // namespace Test
205