Line | Branch | Decision | Exec | Source |
---|---|---|---|---|
1 | // Copyright 2019-2022 Cambridge Quantum Computing | |||
2 | // | |||
3 | // Licensed under the Apache License, Version 2.0 (the "License"); | |||
4 | // you may not use this file except in compliance with the License. | |||
5 | // You may obtain a copy of the License at | |||
6 | // | |||
7 | // http://www.apache.org/licenses/LICENSE-2.0 | |||
8 | // | |||
9 | // Unless required by applicable law or agreed to in writing, software | |||
10 | // distributed under the License is distributed on an "AS IS" BASIS, | |||
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
12 | // See the License for the specific language governing permissions and | |||
13 | // limitations under the License. | |||
14 | ||||
15 | #pragma once | |||
16 | ||||
17 | #include <algorithm> | |||
18 | #include <boost/range/adaptor/transformed.hpp> | |||
19 | #include <boost/range/iterator_range_core.hpp> | |||
20 | #include <functional> | |||
21 | #include <iostream> | |||
22 | #include <map> | |||
23 | #include <memory> | |||
24 | #include <optional> | |||
25 | #include <stdexcept> | |||
26 | #include <type_traits> | |||
27 | ||||
28 | #include "Graphs/AbstractGraph.hpp" | |||
29 | #include "Graphs/TreeSearch.hpp" | |||
30 | #include "Graphs/Utils.hpp" | |||
31 | #include "Utils/GraphHeaders.hpp" | |||
32 | ||||
33 | namespace tket::graphs { | |||
34 | ||||
35 | /** | |||
36 | * Exception thrown because two nodes are disconnected from one another. | |||
37 | * | |||
38 | * @tparam T node type | |||
39 | */ | |||
40 | template <typename T> | |||
41 | class NodesNotConnected : public std::logic_error { | |||
42 | public: | |||
43 | ✗ | NodesNotConnected(const T& node1, const T& node2) | ||
44 | : std::logic_error( | |||
45 | ✗ | node1.repr() + " and " + node2.repr() + " are not connected") {} | ||
46 | }; | |||
47 | ||||
48 | /** Weighted edge */ | |||
49 | struct WeightedEdge { | |||
50 | ✗ | explicit WeightedEdge(unsigned w = 1) : weight(w) {} | ||
51 | unsigned weight; | |||
52 | }; | |||
53 | ||||
54 | template <typename T> | |||
55 | class DirectedGraphBase : public AbstractGraph<T> { | |||
56 | protected: | |||
57 | using ConnGraph = boost::adjacency_list< | |||
58 | boost::vecS, boost::vecS, boost::bidirectionalS, T, WeightedEdge>; | |||
59 | using UndirectedConnGraph = boost::adjacency_list< | |||
60 | boost::setS, boost::vecS, boost::undirectedS, T, WeightedEdge>; | |||
61 | using Vertex = utils::vertex<ConnGraph>; | |||
62 | using UndirectedVertex = utils::vertex<UndirectedConnGraph>; | |||
63 | using Connection = typename AbstractGraph<T>::Edge; | |||
64 | using AbstractGraph<T>::nodes_; | |||
65 | ||||
66 | public: | |||
67 | using AbstractGraph<T>::node_exists; | |||
68 | using AbstractGraph<T>::edge_exists; | |||
69 | using AbstractGraph<T>::get_all_nodes_vec; | |||
70 | using AbstractGraph<T>::get_all_edges_vec; | |||
71 | using AbstractGraph<T>::n_nodes; | |||
72 | ||||
73 | /** Construct an empty graph. */ | |||
74 |
2/4✓ Branch 2 taken 19 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 19 times.
✗ Branch 7 not taken.
|
19 | DirectedGraphBase() : AbstractGraph<T>(), graph(), node_to_vertex() {} | |
75 | ||||
76 | /** Construct a graph with no edges. */ | |||
77 | 79 | explicit DirectedGraphBase(const std::vector<T>& nodes) | ||
78 |
2/4✓ Branch 2 taken 79 times.
✗ Branch 3 not taken.
✓ Branch 6 taken 79 times.
✗ Branch 7 not taken.
|
79 | : AbstractGraph<T>(nodes), graph() { | |
79 |
2/2✓ Branch 5 taken 1444 times.
✓ Branch 6 taken 79 times.
|
2/2✓ Decision 'true' taken 1444 times.
✓ Decision 'false' taken 79 times.
|
1523 | for (const T& node : nodes) { |
80 |
1/2✓ Branch 1 taken 1444 times.
✗ Branch 2 not taken.
|
1444 | add_node(node); | |
81 | } | |||
82 | 79 | } | ||
83 | ||||
84 | /** Construct a graph from its edges. */ | |||
85 | ✗ | explicit DirectedGraphBase(const std::vector<Connection>& edges) : graph() { | ||
86 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | for (auto [node1, node2] : edges) { | |
87 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (!node_exists(node1)) { | |
88 | ✗ | add_node(node1); | ||
89 | } | |||
90 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (!node_exists(node2)) { | |
91 | ✗ | add_node(node2); | ||
92 | } | |||
93 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (node1 == node2) { | |
94 | ✗ | throw std::invalid_argument( | ||
95 | "An edge can not be added from a node to itself."); | |||
96 | } | |||
97 | ✗ | add_connection(node1, node2); | ||
98 | } | |||
99 | } | |||
100 | ||||
101 | /** Test whether two nodes are connected. */ | |||
102 | ✗ | bool edge_exists(const T& node1, const T& node2) const override { | ||
103 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (!node_exists(node1) || !node_exists(node2)) { | |
104 | ✗ | throw NodeDoesNotExistError( | ||
105 | "The nodes passed to DirectedGraph::edge_exists must exist"); | |||
106 | } | |||
107 | ✗ | auto [_, exists] = | ||
108 | ✗ | boost::edge(to_vertices(node1), to_vertices(node2), graph); | ||
109 | ✗ | return exists; | ||
110 | } | |||
111 | ||||
112 | /** Add a new node to the graph. */ | |||
113 | ✗ | void add_node(const T& node) { | ||
114 | ✗ | nodes_.insert(node); | ||
115 | ✗ | Vertex v = boost::add_vertex(node, graph); | ||
116 | ✗ | node_to_vertex.left.insert({node, v}); | ||
117 | } | |||
118 | ||||
119 | /** Remove a node from the graph. */ | |||
120 | 1068 | void remove_node(const T& node) { | ||
121 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1068 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1068 times.
|
1068 | if (!node_exists(node)) { |
122 | ✗ | throw NodeDoesNotExistError( | ||
123 | "The node passed to DirectedGraph::remove_node must exist!"); | |||
124 | } | |||
125 | 1068 | nodes_.erase(node); | ||
126 | 1068 | Vertex v = to_vertices(node); | ||
127 | 1068 | boost::clear_vertex(v, graph); | ||
128 | 1068 | utils::remove_vertex_with_map(v, graph, node_to_vertex.right); | ||
129 | 1068 | } | ||
130 | ||||
131 | /** Add a weighted edge to the graph. */ | |||
132 | ✗ | void add_connection(const T& node1, const T& node2, unsigned weight = 1) { | ||
133 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (!node_exists(node1) || !node_exists(node2)) { | |
134 | ✗ | throw NodeDoesNotExistError( | ||
135 | "The nodes passed to DirectedGraph::add_connection must exist"); | |||
136 | } | |||
137 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (node1 == node2) { | |
138 | ✗ | throw std::invalid_argument( | ||
139 | "A connection can not be added between a node to itself."); | |||
140 | } | |||
141 | ✗ | boost::add_edge( | ||
142 | ✗ | to_vertices(node1), to_vertices(node2), WeightedEdge(weight), graph); | ||
143 | } | |||
144 | ||||
145 | /** Remove an edge from the graph. */ | |||
146 | 1 | void remove_connection(const Connection& edge) { | ||
147 |
5/10✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 1 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 1 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 1 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1 times.
|
1 | if (!node_exists(edge.first) || !node_exists(edge.second)) { |
148 | ✗ | throw NodeDoesNotExistError( | ||
149 | "Trying to remove an edge with non-existent vertices"); | |||
150 | } | |||
151 | 1 | auto [e, exists] = | ||
152 |
3/6✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 1 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 1 times.
✗ Branch 8 not taken.
|
1 | boost::edge(to_vertices(edge.first), to_vertices(edge.second), graph); | |
153 |
1/2✗ Branch 0 not taken.
✓ Branch 1 taken 1 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1 times.
|
1 | if (!exists) { |
154 | ✗ | throw EdgeDoesNotExistError( | ||
155 | "The edge (" + edge.first.repr() + ", " + edge.second.repr() + | |||
156 | ") cannot be removed as it does not exist"); | |||
157 | } | |||
158 |
1/2✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
|
1 | utils::remove_edge_with_map(e, graph, node_to_vertex.right); | |
159 | 1 | } | ||
160 | ||||
161 | /** Remove an edge between two nodes. */ | |||
162 | ✗ | void remove_connection(const T& node1, const T& node2) { | ||
163 | ✗ | remove_connection({node1, node2}); | ||
164 | } | |||
165 | ||||
166 | /** | |||
167 | * Get the weight of an edge between two nodes. | |||
168 | * | |||
169 | * Returns 0 if the edge does not exist. | |||
170 | */ | |||
171 | 10734 | unsigned get_connection_weight(const T& node1, const T& node2) const { | ||
172 |
5/10✓ Branch 1 taken 10734 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 10734 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 10734 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 10734 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 10734 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 10734 times.
|
10734 | if (!node_exists(node1) || !node_exists(node2)) { |
173 | ✗ | throw NodeDoesNotExistError( | ||
174 | "Trying to retrieve edge weight from non-existent vertices"); | |||
175 | } | |||
176 | 10734 | auto [e, exists] = | ||
177 |
3/6✓ Branch 1 taken 10734 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 10734 times.
✗ Branch 5 not taken.
✓ Branch 7 taken 10734 times.
✗ Branch 8 not taken.
|
10734 | boost::edge(to_vertices(node1), to_vertices(node2), graph); | |
178 |
2/2✓ Branch 0 taken 4262 times.
✓ Branch 1 taken 6472 times.
|
2/2✓ Decision 'true' taken 4262 times.
✓ Decision 'false' taken 6472 times.
|
10734 | if (!exists) { |
179 | 4262 | return 0; | ||
180 | } | |||
181 | ||||
182 |
1/2✓ Branch 1 taken 6472 times.
✗ Branch 2 not taken.
|
6472 | return graph[e].weight; | |
183 | } | |||
184 | ||||
185 | /** Get the degree of a node. */ | |||
186 | 1371 | unsigned get_degree(const T& node) const { | ||
187 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 1371 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 1371 times.
|
1371 | if (!node_exists(node)) { |
188 | ✗ | throw NodeDoesNotExistError( | ||
189 | "Trying to retrieve vertex degree from non-existent vertex"); | |||
190 | } | |||
191 | 1371 | return boost::degree(to_vertices(node), graph); | ||
192 | } | |||
193 | ||||
194 | /** Get the out-degree of a node. */ | |||
195 | ✗ | unsigned get_out_degree(const T& node) const { | ||
196 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (!node_exists(node)) { | |
197 | ✗ | throw NodeDoesNotExistError( | ||
198 | "Trying to get outdegree from non-existent vertex"); | |||
199 | } | |||
200 | ✗ | return boost::out_degree(to_vertices(node), graph); | ||
201 | } | |||
202 | ||||
203 | /** Number of edges. */ | |||
204 | ✗ | inline unsigned n_connections() const { return boost::num_edges(graph); } | ||
205 | ||||
206 | /** Number of nodes with degree > 0. */ | |||
207 | ✗ | inline unsigned n_connected() const { | ||
208 | ✗ | auto [beg, end] = boost::vertices(graph); | ||
209 | ✗ | auto nonzero_deg = [this](Vertex v) { return boost::degree(v, graph) > 0; }; | ||
210 | ✗ | return std::count_if(beg, end, nonzero_deg); | ||
211 | } | |||
212 | ||||
213 | /** All edges in the graph. */ | |||
214 | ✗ | std::set<Connection> edges() const { | ||
215 | ✗ | std::vector<Connection> vec = get_all_edges_vec(); | ||
216 | ✗ | return std::set(vec.begin(), vec.end()); | ||
217 | } | |||
218 | ||||
219 | /** All edges as a vector. */ | |||
220 | ✗ | std::vector<Connection> get_all_edges_vec() const override { | ||
221 | ✗ | std::vector<Connection> out; | ||
222 |
0/1? Decision couldn't be analyzed.
|
✗ | for (auto e : get_edges_it()) { | |
223 | ✗ | T source = graph[boost::source(e, graph)]; | ||
224 | ✗ | T target = graph[boost::target(e, graph)]; | ||
225 | ✗ | out.push_back({source, target}); | ||
226 | } | |||
227 | ✗ | return out; | ||
228 | } | |||
229 | ||||
230 | /** Return an unweighted undirected graph with the same connectivity. */ | |||
231 | ✗ | UndirectedConnGraph get_undirected_connectivity() const { | ||
232 | ✗ | return graphs::utils::symmetrise<UndirectedConnGraph>(graph); | ||
233 | } | |||
234 | ||||
235 | /** Get all distances between pairs of nodes. */ | |||
236 | ✗ | std::vector<std::size_t> get_distances(const T& root) const { | ||
237 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (!node_exists(root)) { | |
238 | ✗ | throw NodeDoesNotExistError( | ||
239 | "Trying to get distances from non-existent root vertex"); | |||
240 | } | |||
241 | return run_bfs(to_vertices(root), get_undirected_connectivity()) | |||
242 | ✗ | .get_dists(); | ||
243 | } | |||
244 | ||||
245 | /** Get the distance between two nodes. */ | |||
246 | ✗ | unsigned get_distance(const T& node1, const T& node2) const override { | ||
247 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (node1 == node2) { | |
248 | ✗ | return 0; | ||
249 | } | |||
250 | ✗ | size_t d = get_distances(node1)[to_vertices(node2)]; | ||
251 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (d == 0) { | |
252 | ✗ | throw NodesNotConnected(node1, node2); | ||
253 | } | |||
254 | ✗ | return d; | ||
255 | } | |||
256 | ||||
257 | /** Remove nodes with degree 0 */ | |||
258 | 61 | inline void remove_stray_nodes() { | ||
259 | 61 | std::set<T> strays; | ||
260 |
2/2✓ Branch 5 taken 1323 times.
✓ Branch 6 taken 61 times.
|
2/2✓ Decision 'true' taken 1323 times.
✓ Decision 'false' taken 61 times.
|
1384 | for (const T& node : nodes_) { |
261 |
3/4✓ Branch 1 taken 1323 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 1068 times.
✓ Branch 4 taken 255 times.
|
2/2✓ Decision 'true' taken 1068 times.
✓ Decision 'false' taken 255 times.
|
1323 | if (get_degree(node) == 0) { |
262 |
1/2✓ Branch 1 taken 1068 times.
✗ Branch 2 not taken.
|
1068 | strays.insert(node); | |
263 | } | |||
264 | } | |||
265 |
2/2✓ Branch 5 taken 1068 times.
✓ Branch 6 taken 61 times.
|
2/2✓ Decision 'true' taken 1068 times.
✓ Decision 'false' taken 61 times.
|
1129 | for (const T& node : strays) { |
266 |
1/2✓ Branch 1 taken 1068 times.
✗ Branch 2 not taken.
|
1068 | remove_node(node); | |
267 | } | |||
268 | 61 | } | ||
269 | ||||
270 | /* Return nodes with maximum degree. */ | |||
271 | 20 | std::set<T> max_degree_nodes() const { | ||
272 | 20 | std::set<T> out; | ||
273 |
1/2✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
|
20 | auto max_vertices = graphs::utils::max_degree_nodes(graph); | |
274 |
2/4✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 20 times.
✗ Branch 8 not taken.
|
20 | std::transform( | |
275 | max_vertices.begin(), max_vertices.end(), | |||
276 | std::inserter(out, out.begin()), | |||
277 | 131 | [this](Vertex v) { return get_node(v); }); | ||
278 | 40 | return out; | ||
279 | 20 | } | ||
280 | ||||
281 | /** Return nodes with minimum degree. */ | |||
282 | 446 | std::set<T> min_degree_nodes() const { | ||
283 | 446 | std::set<T> out; | ||
284 |
1/2✓ Branch 1 taken 446 times.
✗ Branch 2 not taken.
|
446 | auto min_vertices = graphs::utils::min_degree_nodes(graph); | |
285 |
2/4✓ Branch 2 taken 446 times.
✗ Branch 3 not taken.
✓ Branch 7 taken 446 times.
✗ Branch 8 not taken.
|
446 | std::transform( | |
286 | min_vertices.begin(), min_vertices.end(), | |||
287 | std::inserter(out, out.begin()), | |||
288 | 893 | [this](Vertex v) { return get_node(v); }); | ||
289 | 892 | return out; | ||
290 | 446 | } | ||
291 | ||||
292 | /** Returns path between two nodes. */ | |||
293 | 309 | std::vector<T> get_path(const T& root, const T& target) const { | ||
294 |
5/10✓ Branch 1 taken 309 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 309 times.
✗ Branch 4 not taken.
✓ Branch 6 taken 309 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✓ Branch 9 taken 309 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 309 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 309 times.
|
309 | if (!node_exists(root) || !node_exists(target)) { |
295 | ✗ | throw NodeDoesNotExistError( | ||
296 | "Trying to get path between non-existent vertices"); | |||
297 | } | |||
298 | using parent_vec = typename BFS<UndirectedConnGraph>::parent_vec; | |||
299 | ||||
300 |
1/2✓ Branch 1 taken 309 times.
✗ Branch 2 not taken.
|
309 | const UndirectedConnGraph& g = get_undirected_connectivity(); | |
301 |
2/4✓ Branch 1 taken 309 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 309 times.
✗ Branch 5 not taken.
|
309 | auto bfs = run_bfs(to_vertices(root), g); | |
302 | ||||
303 | 930 | auto to_node = [&g](UndirectedVertex v) { return g[v]; }; | ||
304 |
2/4✓ Branch 1 taken 309 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 309 times.
✗ Branch 5 not taken.
|
309 | parent_vec path = bfs.path_to_root(to_vertices(target)); | |
305 |
1/2✓ Branch 3 taken 309 times.
✗ Branch 4 not taken.
|
309 | std::vector<T> converted_path(path.size()); | |
306 |
1/2✓ Branch 4 taken 309 times.
✗ Branch 5 not taken.
|
309 | std::transform(path.begin(), path.end(), converted_path.begin(), to_node); | |
307 | 618 | return converted_path; | ||
308 | 309 | } | ||
309 | ||||
310 | /** Get all neighbours of a node. */ | |||
311 | 56706 | std::set<T> get_neighbour_nodes(const T& node) const { | ||
312 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 56706 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 56706 times.
|
56706 | if (!node_exists(node)) { |
313 | ✗ | throw NodeDoesNotExistError( | ||
314 | "Trying to get neighbours from non-existent vertex"); | |||
315 | } | |||
316 | 56706 | std::set<T> neighbours; | ||
317 |
5/8✓ Branch 1 taken 56706 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 56706 times.
✗ Branch 5 not taken.
✓ Branch 9 taken 133987 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 77281 times.
✓ Branch 12 taken 56706 times.
|
0/1? Decision couldn't be analyzed.
|
133987 | for (auto [it, end] = boost::out_edges(to_vertices(node), graph); it != end; |
318 | 77281 | ++it) { | ||
319 |
4/8✓ Branch 1 taken 77281 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 77281 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 77281 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 77281 times.
✗ Branch 12 not taken.
|
77281 | neighbours.insert(get_node(boost::target(*it, graph))); | |
320 | } | |||
321 |
5/8✓ Branch 1 taken 56706 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 56706 times.
✗ Branch 5 not taken.
✓ Branch 9 taken 133780 times.
✗ Branch 10 not taken.
✓ Branch 11 taken 77074 times.
✓ Branch 12 taken 56706 times.
|
0/1? Decision couldn't be analyzed.
|
133780 | for (auto [it, end] = boost::in_edges(to_vertices(node), graph); it != end; |
322 | 77074 | ++it) { | ||
323 |
4/8✓ Branch 1 taken 77074 times.
✗ Branch 2 not taken.
✓ Branch 5 taken 77074 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 77074 times.
✗ Branch 9 not taken.
✓ Branch 11 taken 77074 times.
✗ Branch 12 not taken.
|
77074 | neighbours.insert(get_node(boost::source(*it, graph))); | |
324 | } | |||
325 | 56706 | return neighbours; | ||
326 | } | |||
327 | ||||
328 | 4 | bool operator==(const DirectedGraphBase<T>& other) const { | ||
329 |
1/2✗ Branch 1 not taken.
✓ Branch 2 taken 4 times.
|
0/1? Decision couldn't be analyzed.
|
4 | if (nodes_ != other.nodes_) return false; |
330 |
2/2✓ Branch 5 taken 15 times.
✓ Branch 6 taken 4 times.
|
2/2✓ Decision 'true' taken 15 times.
✓ Decision 'false' taken 4 times.
|
19 | for (const T& u : nodes_) { |
331 |
2/2✓ Branch 5 taken 63 times.
✓ Branch 6 taken 15 times.
|
2/2✓ Decision 'true' taken 63 times.
✓ Decision 'false' taken 15 times.
|
78 | for (const T& v : nodes_) { |
332 |
3/4✓ Branch 1 taken 63 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 15 times.
✓ Branch 4 taken 48 times.
|
2/2✓ Decision 'true' taken 15 times.
✓ Decision 'false' taken 48 times.
|
63 | if (this->edge_exists(u, v)) { |
333 |
2/4✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 15 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 15 times.
|
15 | if (!other.edge_exists(u, v)) |
334 | ✗ | return false; | ||
335 | 15 | else if ( | ||
336 |
1/2✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
|
15 | this->get_connection_weight(u, v) != | |
337 |
2/4✓ Branch 1 taken 15 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 15 times.
|
15 | other.get_connection_weight(u, v)) | |
338 | ✗ | return false; | ||
339 |
2/4✓ Branch 1 taken 48 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 48 times.
|
1/2✗ Decision 'true' not taken.
✓ Decision 'false' taken 48 times.
|
48 | } else if (other.edge_exists(u, v)) |
340 | ✗ | return false; | ||
341 | } | |||
342 | } | |||
343 | 4 | return true; | ||
344 | } | |||
345 | ||||
346 | protected: | |||
347 | /** get edge iterator */ | |||
348 | ✗ | auto get_edges_it() const { | ||
349 | ✗ | return boost::make_iterator_range(boost::edges(graph)); | ||
350 | } | |||
351 | ||||
352 | /** get vertex iterator */ | |||
353 | ✗ | auto get_vertices_it() const { | ||
354 | ✗ | return boost::make_iterator_range(boost::vertices(graph)); | ||
355 | } | |||
356 | ||||
357 | using vertex_bimap = boost::bimap<T, Vertex>; | |||
358 | using left_map = typename vertex_bimap::left_map; | |||
359 | using right_map = typename vertex_bimap::right_map; | |||
360 | /* get node from vertex */ | |||
361 | 201101 | const T& get_node(Vertex v) const { return graph[v]; } | ||
362 | ||||
363 | ✗ | left_map& to_vertices() { return node_to_vertex.left; } | ||
364 | ✗ | const left_map& to_vertices() const { return node_to_vertex.left; } | ||
365 | ✗ | Vertex to_vertices(const T& node) const { return to_vertices().at(node); } | ||
366 | ||||
367 | ✗ | right_map& from_vertices() { return node_to_vertex.right; } | ||
368 | ✗ | const right_map& from_vertices() const { return node_to_vertex.right; } | ||
369 | ✗ | T from_vertices(Vertex v) const { return from_vertices().at(v); } | ||
370 | ||||
371 | ConnGraph graph; | |||
372 | vertex_bimap node_to_vertex; | |||
373 | }; | |||
374 | ||||
375 | /** | |||
376 | * DirectedGraph instances are loop-free directed graphs. It is a wrapper around | |||
377 | * a BGL graph that provides a clean class API, taking care of mapping all BGL | |||
378 | * vertices and edge pointers to nodes, respectively pairs of nodes. | |||
379 | * | |||
380 | * The vertices and edges can be given weights of type double if desired, and | |||
381 | * the underlying undirected graph can be computed. | |||
382 | * | |||
383 | * All functionality for this class is implemented in the base class | |||
384 | * DirectedGraphBase. This class only adds caching of some function calls for | |||
385 | * efficiency, invalidating cache in case of changes on the underlying graph. | |||
386 | */ | |||
387 | template <typename T> | |||
388 | class DirectedGraph : public DirectedGraphBase<T> { | |||
389 | private: | |||
390 | using Base = DirectedGraphBase<T>; | |||
391 | ||||
392 | public: | |||
393 | using Base::Base; | |||
394 | using Base::get_all_nodes_vec; | |||
395 | using Base::n_nodes; | |||
396 | using Base::node_exists; | |||
397 | /* useful type synonyms */ | |||
398 | using UndirectedConnGraph = typename Base::UndirectedConnGraph; | |||
399 | using ConnGraph = typename Base::ConnGraph; | |||
400 | using Connection = typename Base::Connection; | |||
401 | using Vertex = typename Base::Vertex; | |||
402 | ||||
403 | /** | |||
404 | * Get all distances between nodes. | |||
405 | */ | |||
406 | 21992 | const std::vector<std::size_t>& get_distances(const T& root) const& { | ||
407 | // We cache distances. A value of zero in the cache implies that the nodes | |||
408 | // are disconnected (unless they are equal). | |||
409 |
3/4✓ Branch 2 taken 21992 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 1606 times.
✓ Branch 6 taken 20386 times.
|
2/2✓ Decision 'true' taken 1606 times.
✓ Decision 'false' taken 20386 times.
|
21992 | if (distance_cache.find(root) == distance_cache.end()) { |
410 |
1/2✓ Branch 2 taken 1606 times.
✗ Branch 3 not taken.
|
1606 | distance_cache[root] = Base::get_distances(root); | |
411 | } | |||
412 | 21992 | return distance_cache[root]; | ||
413 | } | |||
414 | ||||
415 | /** | |||
416 | * Get all distances between nodes. | |||
417 | */ | |||
418 | ✗ | std::vector<std::size_t>&& get_distances(const T& root) const&& { | ||
419 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (distance_cache.find(root) == distance_cache.end()) { | |
420 | ✗ | distance_cache[root] = Base::get_distances(root); | ||
421 | } | |||
422 | ✗ | return std::move(distance_cache[root]); | ||
423 | } | |||
424 | ||||
425 | ✗ | unsigned get_distance(const T& node1, const T& node2) const override { | ||
426 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (node1 == node2) { | |
427 | ✗ | return 0; | ||
428 | } | |||
429 | size_t d; | |||
430 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (distance_cache.find(node1) != distance_cache.end()) { | |
431 | ✗ | d = distance_cache[node1][this->to_vertices(node2)]; | ||
432 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | } else if (distance_cache.find(node2) != distance_cache.end()) { | |
433 | ✗ | d = distance_cache[node2][this->to_vertices(node1)]; | ||
434 | } else { | |||
435 | ✗ | distance_cache[node1] = Base::get_distances(node1); | ||
436 | ✗ | d = distance_cache[node1][this->to_vertices(node2)]; | ||
437 | } | |||
438 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (d == 0) { | |
439 | ✗ | throw NodesNotConnected(node1, node2); | ||
440 | } | |||
441 | ✗ | return d; | ||
442 | } | |||
443 | ||||
444 | ✗ | unsigned get_diameter() override { | ||
445 | ✗ | unsigned N = n_nodes(); | ||
446 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (N == 0) { | |
447 | ✗ | throw std::logic_error("Graph is empty."); | ||
448 | } | |||
449 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (!this->diameter_) { | |
450 | ✗ | this->diameter_ = 0; | ||
451 | ✗ | const std::vector<T> nodes = get_all_nodes_vec(); | ||
452 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | for (unsigned i = 0; i < N; i++) { | |
453 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | for (unsigned j = i + 1; j < N; j++) { | |
454 | ✗ | unsigned d = get_distance(nodes[i], nodes[j]); | ||
455 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (d > *this->diameter_) this->diameter_ = d; | |
456 | } | |||
457 | } | |||
458 | } | |||
459 | ✗ | return *this->diameter_; | ||
460 | } | |||
461 | ||||
462 | /** Returns all nodes at a given distance from a given 'source' node */ | |||
463 | 19368 | std::vector<T> nodes_at_distance(const T& root, std::size_t distance) const { | ||
464 |
2/4✓ Branch 1 taken 19368 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 19368 times.
✗ Branch 5 not taken.
|
19368 | auto dists = get_distances(root); | |
465 | 19368 | std::vector<T> out; | ||
466 |
2/2✓ Branch 1 taken 492719 times.
✓ Branch 2 taken 19368 times.
|
2/2✓ Decision 'true' taken 492719 times.
✓ Decision 'false' taken 19368 times.
|
512087 | for (unsigned i = 0; i < dists.size(); ++i) { |
467 |
2/2✓ Branch 1 taken 45722 times.
✓ Branch 2 taken 446997 times.
|
2/2✓ Decision 'true' taken 45722 times.
✓ Decision 'false' taken 446997 times.
|
492719 | if (dists[i] == distance) { |
468 |
2/4✓ Branch 1 taken 45722 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 45722 times.
✗ Branch 5 not taken.
|
45722 | out.push_back(this->get_node(i)); | |
469 | } | |||
470 | } | |||
471 | 38736 | return out; | ||
472 | 19368 | } | ||
473 | ||||
474 | /** Return an unweighted undirected graph with the same connectivity. */ | |||
475 | 85 | const UndirectedConnGraph& get_undirected_connectivity() const& { | ||
476 | // we cache the undirected graph | |||
477 |
2/2✓ Branch 1 taken 83 times.
✓ Branch 2 taken 2 times.
|
2/2✓ Decision 'true' taken 83 times.
✓ Decision 'false' taken 2 times.
|
85 | if (!undir_graph) { |
478 |
1/2✓ Branch 2 taken 83 times.
✗ Branch 3 not taken.
|
83 | undir_graph = Base::get_undirected_connectivity(); | |
479 | } | |||
480 | 85 | return undir_graph.value(); | ||
481 | } | |||
482 | ||||
483 | /** Return an unweighted undirected graph with the same connectivity. */ | |||
484 | ✗ | UndirectedConnGraph&& get_undirected_connectivity() const&& { | ||
485 |
0/2✗ Decision 'true' not taken.
✗ Decision 'false' not taken.
|
✗ | if (!undir_graph) { | |
486 | ✗ | undir_graph = Base::get_undirected_connectivity(); | ||
487 | } | |||
488 | ✗ | return std::move(undir_graph.value()); | ||
489 | } | |||
490 | ||||
491 | // The following functions invalidate caching. | |||
492 | ||||
493 | /** Remove a node from the graph. */ | |||
494 | 448 | void remove_node(const T& node) { | ||
495 | 448 | invalidate_cache(); | ||
496 | 448 | Base::remove_node(node); | ||
497 | 448 | } | ||
498 | ||||
499 | /** Add a node to the graph. */ | |||
500 | ✗ | void add_node(const T& node) { | ||
501 | ✗ | invalidate_cache(); | ||
502 | ✗ | Base::add_node(node); | ||
503 | } | |||
504 | ||||
505 | /** Remove nodes with degree 0. */ | |||
506 | 61 | void remove_stray_nodes() { | ||
507 | 61 | invalidate_cache(); | ||
508 | 61 | Base::remove_stray_nodes(); | ||
509 | 61 | } | ||
510 | ||||
511 | /** Add a (weighted) edge between two nodes. */ | |||
512 | ✗ | void add_connection(const T& node1, const T& node2, unsigned weight = 1) { | ||
513 | ✗ | invalidate_cache(); | ||
514 | ✗ | Base::add_connection(node1, node2, weight); | ||
515 | } | |||
516 | ||||
517 | /** Remove an edge. */ | |||
518 | 3 | void remove_connection(const Connection& edge) { | ||
519 | 3 | invalidate_cache(); | ||
520 | 3 | Base::remove_connection(edge); | ||
521 | 3 | } | ||
522 | ||||
523 | /** Remove a connection between two nodes. */ | |||
524 | ✗ | void remove_connection(const T& node1, const T& node2) { | ||
525 | ✗ | invalidate_cache(); | ||
526 | ✗ | Base::remove_connection(node1, node2); | ||
527 | } | |||
528 | ||||
529 | private: | |||
530 | ✗ | inline void invalidate_cache() { | ||
531 | ✗ | distance_cache.clear(); | ||
532 | ✗ | undir_graph = std::nullopt; | ||
533 | } | |||
534 | mutable std::map<T, std::vector<std::size_t>> distance_cache; | |||
535 | mutable std::optional<UndirectedConnGraph> undir_graph; | |||
536 | }; | |||
537 | ||||
538 | } // namespace tket::graphs | |||
539 |