CGRA-ME
GraphAlgorithms_tests.cpp
Go to the documentation of this file.
1 /*******************************************************************************
2  * The software programs comprising "CGRA-ME" and the documentation provided
3  * with them are copyright by its authors and the University of Toronto. Only
4  * non-commercial, not-for-profit use of this software is permitted without ex-
5  * plicit permission. This software is provided "as is" with no warranties or
6  * guarantees of support. See the LICENCE for more details. You should have re-
7  * ceived a copy of the full licence along with this software. If not, see
8  * <http://cgra-me.ece.utoronto.ca/license/>.
9  ******************************************************************************/
10 
11 #include <CGRA/GraphAlgorithms.h>
12 
13 #include <iostream>
14 
15 #include <catch2/catch.hpp>
16 
17 namespace {
18 
19 
20 StringIDGraph makeGraph_121() {
21  StringIDGraph mrrg;
22  auto fu1 = mrrg.insert("fu1");
23  auto fu2 = mrrg.insert("fu2");
24 
25  auto i_a = mrrg.insert("i_a");
26  auto i_b = mrrg.insert("i_b");
27 
28  mrrg.link(fu1, i_a);
29  mrrg.link(fu1, i_b);
30  mrrg.link(i_a, fu2);
31  mrrg.link(i_b, fu2);
32 
33  return mrrg;
34 }
35 
36 StringIDGraph makeGraph_11211() {
37  StringIDGraph mrrg;
38  auto fu1 = mrrg.insert("fu1");
39  auto fu2 = mrrg.insert("fu2");
40 
41  auto f1out = mrrg.insert("f1out");
42  auto r_a = mrrg.insert("r_a");
43  auto r_b = mrrg.insert("r_b");
44  auto f2in = mrrg.insert("f2in");
45 
46  mrrg.link(fu1, f1out);
47  mrrg.link(f1out, r_a);
48  mrrg.link(f1out, r_b);
49  mrrg.link(r_a, f2in);
50  mrrg.link(r_b, f2in);
51  mrrg.link(f2in, fu2);
52 
53  return mrrg;
54 }
55 
56 StringIDGraph makeGraph_12x21() {
57  StringIDGraph g;
58  auto fu1 = g.insert("fu1");
59 
60  auto fu1_out_a = g.insert(fu1, "fu1_out_a");
61  auto fu1_out_b = g.insert(fu1, "fu1_out_b");
62  auto xb_1_a = g.insertMultiFanin({fu1_out_a, fu1_out_b}, "xb_1_a");
63  auto xb_1_b = g.insertMultiFanin({fu1_out_a, fu1_out_b}, "xb_1_b");
64 
65  auto fu2 = g.insertMultiFanin({xb_1_a, xb_1_b}, "fu2");
66  (void)fu2;
67 
68  return g;
69 }
70 
71 StringIDGraph makeGraph_Loop3Node() {
72  StringIDGraph g;
73  auto a = g.insert( "a");
74  auto b = g.insert(a, "b");
75  auto c = g.insert(b, "c");
76  g.link(c, a);
77 
78  return g;
79 }
80 
81 StringIDGraph makeGraph_Loop3NodeWithExtraFanout() {
82  auto g = makeGraph_Loop3Node();
83  g.insert("a", "d");
84  return g;
85 }
86 
87 StringIDGraph makeGraph_12x21_with_loop() {
88  auto g = makeGraph_12x21();
89  g.link("fu2", "fu1");
90  return g;
91 }
92 
93 StringIDGraph makeGraph_12x2x21() {
94  StringIDGraph mrrg;
95  auto fu1 = mrrg.insert("fu1");
96 
97  auto fu1_out_a = mrrg.insert(fu1, "fu1_out_a");
98  auto fu1_out_b = mrrg.insert(fu1, "fu1_out_b");
99  auto xb_1_a = mrrg.insertMultiFanin({fu1_out_a, fu1_out_b}, "xb_1_a");
100  auto xb_1_b = mrrg.insertMultiFanin({fu1_out_a, fu1_out_b}, "xb_1_b");
101  auto xb_2_a = mrrg.insertMultiFanin({xb_1_a, xb_1_b}, "xb_2_a");
102  auto xb_2_b = mrrg.insertMultiFanin({xb_1_a, xb_1_b}, "xb_2_b");
103 
104  auto fu2 = mrrg.insertMultiFanin({xb_2_a, xb_2_b}, "fu2");
105  (void)fu2;
106 
107  return mrrg;
108 }
109 
110 StringIDGraph makeGraph_112x211() {
111  StringIDGraph mrrg;
112  auto fu1 = mrrg.insert("fu1");
113 
114  auto fu1_out = mrrg.insert(fu1, "fu1_out");
115  auto xb_1_a = mrrg.insert(fu1_out, "xb_1_a");
116  auto xb_1_b = mrrg.insert(fu1_out, "xb_1_b");
117  auto xb_2_a = mrrg.insertMultiFanin({xb_1_a, xb_1_b}, "xb_2_a");
118  auto xb_2_b = mrrg.insertMultiFanin({xb_1_a, xb_1_b}, "xb_2_b");
119 
120  auto fu2_in = mrrg.insertMultiFanin({xb_2_a, xb_2_b}, "fu2_in");
121  auto fu2 = mrrg.insert(fu2_in, "fu2");
122  (void)fu2;
123 
124  return mrrg;
125 }
126 
127 StringIDGraph makeGraph_12x2x2x21() {
128  StringIDGraph mrrg;
129  auto fu1 = mrrg.insert("fu1");
130 
131  auto fu1_out_a = mrrg.insert(fu1, "fu1_out_a");
132  auto fu1_out_b = mrrg.insert(fu1, "fu1_out_b");
133  auto xb_1_a = mrrg.insertMultiFanin({fu1_out_a, fu1_out_b}, "xb_1_a");
134  auto xb_1_b = mrrg.insertMultiFanin({fu1_out_a, fu1_out_b}, "xb_1_b");
135  auto xb_2_a = mrrg.insertMultiFanin({xb_1_a, xb_1_b}, "xb_2_a");
136  auto xb_2_b = mrrg.insertMultiFanin({xb_1_a, xb_1_b}, "xb_2_b");
137  auto xb_3_a = mrrg.insertMultiFanin({xb_2_a, xb_2_b}, "xb_3_a");
138  auto xb_3_b = mrrg.insertMultiFanin({xb_2_a, xb_2_b}, "xb_3_b");
139 
140  auto fu2 = mrrg.insertMultiFanin({xb_3_a, xb_3_b}, "fu2");
141  (void)fu2;
142 
143  return mrrg;
144 }
145 
146 StringIDGraph makeGraph_2unevenBranches() {
147  StringIDGraph mrrg;
148  auto fu1 = mrrg.insert("fu1");
149  auto fu2 = mrrg.insert("fu2");
150 
151  auto split = mrrg.insert("split");
152  auto b1_1 = mrrg.insert("b1_1");
153  auto b1_2 = mrrg.insert("b1_2");
154  auto b1_3 = mrrg.insert("b1_3");
155  auto join = mrrg.insert("join");
156 
157  mrrg.link(fu1, split);
158  mrrg.link(split, b1_1);
159  mrrg.link(b1_1, b1_2);
160  mrrg.link(b1_2, b1_3);
161  mrrg.link(b1_3, join);
162  mrrg.link(split, join);
163  mrrg.link(join, fu2);
164 
165  return mrrg;
166 }
167 
168 StringIDGraph makeGraph_2branches1withCycle() {
169  StringIDGraph mrrg;
170  auto fu1 = mrrg.insert("fu1");
171  auto fu2 = mrrg.insert("fu2");
172 
173  auto split = mrrg.insert("split");
174  auto c_start = mrrg.insert("c_start");
175  auto c1 = mrrg.insert("c1");
176  auto c_end = mrrg.insert("c_end");
177  auto c_rev = mrrg.insert("c_rev");
178  auto join = mrrg.insert("join");
179 
180  mrrg.link(fu1, split);
181  mrrg.link(split, join);
182  mrrg.link(join, fu2);
183 
184  mrrg.link(split, c_start);
185  mrrg.link(c_start, c1);
186  mrrg.link(c1, c_end);
187  mrrg.link(c_end, c_rev);
188  mrrg.link(c_end, join);
189 
190  mrrg.link(c_rev, c_start);
191 
192  return mrrg;
193 }
194 
195 template<typename Result, typename Expected>
196 void checkAllPathsFound(const Result& result, const Expected& expected_paths) {
197  std::unordered_set<std::ptrdiff_t> expected_paths_found;
198 
199  for (auto& path : result) {
200  std::ptrdiff_t i = 0;
201  for (auto it = begin(expected_paths); it != end(expected_paths); ++it, ++i) {
202  auto& epath = *it;
203  if (are_ranges_same(path, epath)) {
204  expected_paths_found.insert(i);
205  }
206  }
207  }
208 
209  CHECK((std::ptrdiff_t)expected_paths_found.size() == (std::ptrdiff_t)expected_paths.size());
210  CHECK((std::ptrdiff_t)result.size() == (std::ptrdiff_t)expected_paths.size());
211 }
212 
213 } // end anon namespace
214 
215 SCENARIO ("findNShortestPaths Tests", "[graph]") {
216  const auto g_algos = GraphAlgorithms<StringIDGraph::VertexID>();
217 
218  GIVEN ("A 121 graph") {
219  const auto graph = makeGraph_121();
220 
221  const auto sources = singleItemSet("fu1");
222  const auto result = g_algos.findNShortestPaths(-1, graph, sources, findSingleVertex("fu2"), DebugVisitor<StringIDGraph::VertexID>{});
223 
224  std::vector<std::vector<StringIDGraph::VertexID>> expected_paths {
225  { "fu1", "i_a", "fu2", },
226  { "fu1", "i_b", "fu2", },
227  };
228 
229  checkAllPathsFound(result, expected_paths);
230  }
231 
232  GIVEN ("A 121 graph and choice A is ignored") {
233  const auto graph = makeGraph_121();
234 
235  const auto sources = singleItemSet("fu1");
236  const auto result = g_algos.findNShortestPaths(-1, graph, sources, findSingleVertex("fu2"), IgnoreVertexSetVisitor<StringIDGraph::VertexID>{{"i_a"}});
237 
238  std::vector<std::vector<StringIDGraph::VertexID>> expected_paths {
239  { "fu1", "i_b", "fu2", },
240  };
241 
242  checkAllPathsFound(result, expected_paths);
243  }
244 
245  GIVEN ("A 11211 graph") {
246  const auto graph = makeGraph_11211();
247 
248  const auto sources = singleItemSet("fu1");
249  const auto result = g_algos.findNShortestPaths(-1, graph, sources, findSingleVertex("fu2"), DebugVisitor<StringIDGraph::VertexID>{});
250 
251  std::vector<std::vector<StringIDGraph::VertexID>> expected_paths {
252  { "fu1", "f1out", "r_a", "f2in", "fu2", },
253  { "fu1", "f1out", "r_b", "f2in", "fu2", },
254  };
255 
256  checkAllPathsFound(result, expected_paths);
257  }
258 
259  GIVEN ("A graph with uneven branches") {
260  const auto graph = makeGraph_2unevenBranches();
261 
262  const auto sources = singleItemSet("fu1");
263  const auto result = g_algos.findNShortestPaths(-1, graph, sources, findSingleVertex("fu2"), DebugVisitor<StringIDGraph::VertexID>{});
264 
265  std::vector<std::vector<StringIDGraph::VertexID>> expected_paths {
266  { "fu1", "split", "b1_1", "b1_2", "b1_3", "join", "fu2", },
267  { "fu1", "split", "join", "fu2", },
268  };
269 
270  checkAllPathsFound(result, expected_paths);
271  }
272 
273  GIVEN ("A graph with a cycle in one branch") {
274  const auto graph = makeGraph_2branches1withCycle();
275 
276  const auto sources = singleItemSet("fu1");
277  const auto result = g_algos.findNShortestPaths(3, graph, sources, findSingleVertex("fu2"), DebugVisitor<StringIDGraph::VertexID>{});
278 
279  std::vector<std::vector<StringIDGraph::VertexID>> expected_paths {
280  { "fu1", "split", "join", "fu2", },
281  { "fu1", "split", "c_start", "c1", "c_end", "join", "fu2", },
282  };
283 
284  checkAllPathsFound(result, expected_paths);
285  }
286 
287  GIVEN ("A graph with uneven branches") {
288  const auto graph = makeGraph_2unevenBranches();
289 
290  WHEN ("Finding one path") {
291  const auto sources = singleItemSet("fu1");
292  const auto result = g_algos.findNShortestPaths(1, graph, sources, findSingleVertex("fu2"), DebugVisitor<StringIDGraph::VertexID>{});
293 
294  THEN ("the shortest path should be found") {
295  std::vector<std::vector<StringIDGraph::VertexID>> expected_paths {
296  { "fu1", "split", "join", "fu2", },
297  };
298 
299  checkAllPathsFound(result, expected_paths);
300  }
301  }
302  }
303 
304  GIVEN ("A graph with crossbars") {
305  const auto graph = makeGraph_12x2x2x21();
306  for (int i = 0; i <= 35; i += 1) {
307  WHEN ("Finding " << i << " path(s)") {
308  const auto sources = singleItemSet("fu1");
309  const auto result = g_algos.findNShortestPaths(i, graph, sources, findSingleVertex("fu2"), DebugVisitor<StringIDGraph::VertexID>{});
310 
311  const auto expected_num_paths = std::min(i,16);
312  THEN (expected_num_paths << " path(s) should be found") {
313  CHECK(result.size() == expected_num_paths);
314  }
315  }
316  }
317  }
318 
319  GIVEN("A 12x21 graph") {
320  const auto graph = makeGraph_12x21();
321  const int num_paths = GENERATE(range(0,4+1));
322  WHEN ("Finding " << num_paths << " path(s), exept paths with both fu1_out_a and xb_1_a") {
323  const auto sources = singleItemSet("fu1");
325  const auto result = g_algos.findNShortestPaths(num_paths, graph, sources, findSingleVertex("fu2"), v, [](const auto& path) {
326  return std::find(path.begin(), path.end(), "fu1_out_a") != path.end() || std::find(path.begin(), path.end(), "xb_1_a") != path.end();
327  });
328 
329  THEN ("some paths may be found") {
330  CHECK(result.size() == std::min(3,num_paths));
331  }
332  }
333  }
334 
335  GIVEN("A 112x211 graph") {
336  const auto graph = makeGraph_112x211();
337  const int num_paths = GENERATE(range(0,5+1));
338  WHEN ("Finding " << num_paths << " path(s), exept paths with both xb_1_a and xb_2_a") {
339  const auto sources = singleItemSet("fu1");
341  const auto result = g_algos.findNShortestPaths(num_paths, graph, sources, findSingleVertex("fu2"), v, [](const auto& path) {
342  return std::find(path.begin(), path.end(), "xb_1_a") != path.end() || std::find(path.begin(), path.end(), "xb_2_a") != path.end();
343  });
344 
345  THEN ("some paths may be found") {
346  CHECK(result.size() == std::min(3,num_paths));
347  }
348  }
349  }
350 
351  GIVEN("A 112x211 graph") {
352  const auto graph = makeGraph_112x211();
353  const int num_paths = GENERATE(range(0,5+1));
354  WHEN ("Finding " << num_paths << " path(s), exept paths with xb_2_a") {
355  const auto sources = singleItemSet("fu1");
357  const auto result = g_algos.findNShortestPaths(num_paths, graph, sources, findSingleVertex("fu2"), v, [](const auto& path) {
358  return std::find(path.begin(), path.end(), "xb_2_a") != path.end();
359  });
360 
361  THEN ("some paths may be found") {
362  CHECK(result.size() == std::min(2,num_paths));
363  }
364  }
365  }
366 
367  GIVEN("A 12x2x21 graph") {
368  const auto graph = makeGraph_12x2x21();
369  const int num_paths = GENERATE(range(0,7+1));
370  WHEN ("Finding " << num_paths << " path(s), exept paths with both fu1_out_a and xb_2_a") {
371  const auto sources = singleItemSet("fu1");
373  const auto result = g_algos.findNShortestPaths(num_paths, graph, sources, findSingleVertex("fu2"), v, [](const auto& path) {
374  return std::find(path.begin(), path.end(), "fu1_out_a") != path.end() || std::find(path.begin(), path.end(), "xb_2_a") != path.end();
375  });
376 
377  THEN ("some paths may be found") {
378  CHECK(result.size() == std::min(6,num_paths));
379  }
380  }
381  }
382 
383  GIVEN("A 12x21 graph with loop") {
384  const auto graph = makeGraph_12x21_with_loop();
385  const int num_paths = GENERATE(range(0,5+1));
386  WHEN ("Finding " << num_paths << " loop path(s)") {
387  const auto sources = singleItemSet("fu1");
389  const auto result = g_algos.findNShortestPaths(num_paths, graph, sources, findSingleVertex("fu1"), v);
390 
391  THEN ("some paths may be found") {
392  CHECK(result.size() == std::min(4,num_paths));
393  }
394  }
395  }
396 }
397 
398 SCENARIO ("findNShortestSimplePaths Tests", "[graph]") {
399  const auto g_algos = GraphAlgorithms<StringIDGraph::VertexID>();
400 
401  GIVEN ("A 121 graph") {
402  const auto graph = makeGraph_121();
403 
404  const auto sources = singleItemSet("fu1");
405  const auto result = g_algos.findNShortestSimplePaths(-1, graph, sources, findSingleVertex("fu2"), DebugVisitor<StringIDGraph::VertexID>{});
406 
407  std::vector<std::vector<StringIDGraph::VertexID>> expected_paths {
408  { "fu1", "i_a", "fu2", },
409  { "fu1", "i_b", "fu2", },
410  };
411 
412  checkAllPathsFound(result, expected_paths);
413  }
414 
415  GIVEN ("A 121 graph and choice A is ignored") {
416  const auto graph = makeGraph_121();
417 
418  const auto sources = singleItemSet("fu1");
419  const auto result = g_algos.findNShortestSimplePaths(-1, graph, sources, findSingleVertex("fu2"), IgnoreVertexSetVisitor<StringIDGraph::VertexID>{{"i_a"}});
420 
421  std::vector<std::vector<StringIDGraph::VertexID>> expected_paths {
422  { "fu1", "i_b", "fu2", },
423  };
424 
425  checkAllPathsFound(result, expected_paths);
426  }
427 
428  GIVEN ("A 11211 graph") {
429  const auto graph = makeGraph_11211();
430 
431  const auto sources = singleItemSet("fu1");
432  const auto result = g_algos.findNShortestSimplePaths(-1, graph, sources, findSingleVertex("fu2"), DebugVisitor<StringIDGraph::VertexID>{});
433 
434  std::vector<std::vector<StringIDGraph::VertexID>> expected_paths {
435  { "fu1", "f1out", "r_a", "f2in", "fu2", },
436  { "fu1", "f1out", "r_b", "f2in", "fu2", },
437  };
438 
439  checkAllPathsFound(result, expected_paths);
440  }
441 
442  GIVEN ("A graph with uneven branches") {
443  const auto graph = makeGraph_2unevenBranches();
444 
445  const auto sources = singleItemSet("fu1");
446  const auto result = g_algos.findNShortestSimplePaths(-1, graph, sources, findSingleVertex("fu2"), DebugVisitor<StringIDGraph::VertexID>{});
447 
448  std::vector<std::vector<StringIDGraph::VertexID>> expected_paths {
449  { "fu1", "split", "b1_1", "b1_2", "b1_3", "join", "fu2", },
450  { "fu1", "split", "join", "fu2", },
451  };
452 
453  checkAllPathsFound(result, expected_paths);
454  }
455 
456  GIVEN ("A graph with a cycle in one branch") {
457  const auto graph = makeGraph_2branches1withCycle();
458 
459  const auto sources = singleItemSet("fu1");
460  const auto result = g_algos.findNShortestSimplePaths(-1, graph, sources, findSingleVertex("fu2"), DebugVisitor<StringIDGraph::VertexID>{});
461 
462  std::vector<std::vector<StringIDGraph::VertexID>> expected_paths {
463  { "fu1", "split", "join", "fu2", },
464  { "fu1", "split", "c_start", "c1", "c_end", "join", "fu2", },
465  };
466 
467  checkAllPathsFound(result, expected_paths);
468  }
469 
470  GIVEN ("A graph with uneven branches") {
471  const auto graph = makeGraph_2unevenBranches();
472 
473  WHEN ("Finding one path") {
474  const auto sources = singleItemSet("fu1");
475  const auto result = g_algos.findNShortestSimplePaths(1, graph, sources, findSingleVertex("fu2"), DebugVisitor<StringIDGraph::VertexID>{});
476 
477  THEN ("the shortest path should be found") {
478  std::vector<std::vector<StringIDGraph::VertexID>> expected_paths {
479  { "fu1", "split", "join", "fu2", },
480  };
481 
482  checkAllPathsFound(result, expected_paths);
483  }
484  }
485  }
486 
487  GIVEN ("A graph with crossbars") {
488  const auto graph = makeGraph_12x2x2x21();
489  for (int i = 0; i <= 35; i += 1) {
490  WHEN ("Finding " << i << " path(s)") {
491  const auto sources = singleItemSet("fu1");
492  const auto result = g_algos.findNShortestSimplePaths(i, graph, sources, findSingleVertex("fu2"), DebugVisitor<StringIDGraph::VertexID>{});
493 
494  const auto expected_num_paths = std::min(i,16);
495  THEN (expected_num_paths << " path(s) should be found") {
496  CHECK(result.size() == expected_num_paths);
497  }
498  }
499  }
500  }
501 }
502 
503 
504 SCENARIO ("depthFirstVisit Tests", "[graph]") {
505  const auto g_algos = GraphAlgorithms<StringIDGraph::VertexID>();
506 
507  GIVEN ("A 121 graph") {
508  const auto graph = makeGraph_121();
509 
510  const auto sources = singleItemSet("fu1");
511  const auto vertex_data = g_algos.depthFirstVisit(graph, *begin(sources), findSingleVertex("fu2"), DebugVisitor<StringIDGraph::VertexID>{});
512  const auto result = g_algos.singleTraceback(sources, "fu2", vertex_data);
513 
514  std::vector<StringIDGraph::VertexID> expected_path {
515  "fu1", "i_a", "fu2",
516  };
517 
518  CHECK(are_ranges_same(result.path, expected_path));
519  }
520 
521  GIVEN ("A 121 graph and choice A is ignored") {
522  const auto graph = makeGraph_121();
523 
524  const auto sources = singleItemSet("fu1");
525  const auto vertex_data = g_algos.depthFirstVisit(graph, *begin(sources), findSingleVertex("fu2"), IgnoreVertexSetVisitor<StringIDGraph::VertexID>{{"i_a"}});
526  const auto result = g_algos.singleTraceback(sources, "fu2", vertex_data);
527 
528  std::vector<StringIDGraph::VertexID> expected_path {
529  "fu1", "i_b", "fu2",
530  };
531 
532  CHECK(are_ranges_same(result.path, expected_path));
533  }
534 
535  GIVEN ("A 11211 graph") {
536  const auto graph = makeGraph_11211();
537 
538  const auto sources = singleItemSet("fu1");
539  const auto vertex_data = g_algos.depthFirstVisit(graph, *begin(sources), findSingleVertex("fu2"), DebugVisitor<StringIDGraph::VertexID>{});
540  const auto result = g_algos.singleTraceback(sources, "fu2", vertex_data);
541 
542  std::vector<StringIDGraph::VertexID> expected_path {
543  "fu1", "f1out", "r_a", "f2in", "fu2",
544  };
545 
546  CHECK(are_ranges_same(result.path, expected_path));
547  }
548 
549  GIVEN ("A graph with uneven branches") {
550  const auto graph = makeGraph_2unevenBranches();
551 
552  const auto sources = singleItemSet("fu1");
553  const auto vertex_data = g_algos.depthFirstVisit(graph, *begin(sources), findSingleVertex("fu2"), DebugVisitor<StringIDGraph::VertexID>{});
554  const auto result = g_algos.singleTraceback(sources, "fu2", vertex_data);
555 
556  std::vector<StringIDGraph::VertexID> expected_path {
557  "fu1", "split", "b1_1", "b1_2", "b1_3", "join", "fu2",
558  };
559 
560  CHECK(are_ranges_same(result.path, expected_path));
561  }
562 
563  GIVEN ("A graph with uneven branches - find the other path") {
564  const auto graph = makeGraph_2unevenBranches();
565 
566  const auto sources = singleItemSet("fu1");
567  IgnoreEdgeSetVisitor<StringIDGraph::VertexID> path_ignorer {{{"b1_2", "b1_3"}}};
568  const auto vertex_data = g_algos.depthFirstVisit(graph, *begin(sources), findSingleVertex("fu2"), path_ignorer);
569  const auto result = g_algos.singleTraceback(sources, "fu2", vertex_data);
570 
571  std::vector<StringIDGraph::VertexID> expected_path {
572  "fu1", "split", "join", "fu2",
573  };
574 
575  CHECK(are_ranges_same(result.path, expected_path));
576  }
577 
578  GIVEN ("A graph with a cycle in one branch") {
579  const auto graph = makeGraph_2branches1withCycle();
580 
581  const auto sources = singleItemSet("fu1");
582  const auto vertex_data = g_algos.depthFirstVisit(graph, *begin(sources), findSingleVertex("fu2"), DebugVisitor<StringIDGraph::VertexID>{});
583  const auto result = g_algos.singleTraceback(sources, "fu2", vertex_data);
584 
585  std::vector<StringIDGraph::VertexID> expected_path {
586  "fu1", "split", "join", "fu2",
587  };
588 
589  CHECK(are_ranges_same(result.path, expected_path));
590  }
591 }
592 
593 SCENARIO ("wavedBreadthFirstVisit Tests", "[graph]") {
594  const auto g_algos = GraphAlgorithms<StringIDGraph::VertexID>();
595 
596  GIVEN ("A 121 graph") {
597  const auto graph = makeGraph_121();
598 
599  const auto sources = singleItemSet("fu1");
600  const auto vertex_data = g_algos.wavedBreadthFirstVisit(graph, sources, visitAllVertices(), DebugVisitor<StringIDGraph::VertexID>{});
601  const auto result = g_algos.singleTraceback(sources, "fu2", vertex_data);
602 
603  std::vector<StringIDGraph::VertexID> expected_path {
604  "fu1", "i_a", "fu2",
605  };
606 
607  CHECK(are_ranges_same(result.path, expected_path));
608  }
609 
610  GIVEN ("A 11211 graph") {
611  const auto graph = makeGraph_11211();
612 
613  const auto sources = singleItemSet("fu1");
614  const auto vertex_data = g_algos.wavedBreadthFirstVisit(graph, sources, visitAllVertices(), DebugVisitor<StringIDGraph::VertexID>{});
615  const auto result = g_algos.singleTraceback(sources, "fu2", vertex_data);
616 
617  std::vector<StringIDGraph::VertexID> expected_path {
618  "fu1", "f1out", "r_a", "f2in", "fu2",
619  };
620 
621  CHECK(are_ranges_same(result.path, expected_path));
622  }
623 
624  GIVEN ("A graph with uneven branches") {
625  const auto graph = makeGraph_2unevenBranches();
626 
627  const auto sources = singleItemSet("fu1");
628  const auto vertex_data = g_algos.wavedBreadthFirstVisit(graph, sources, visitAllVertices(), DebugVisitor<StringIDGraph::VertexID>{});
629  const auto result = g_algos.singleTraceback(sources, "fu2", vertex_data);
630 
631  std::vector<StringIDGraph::VertexID> expected_path {
632  "fu1", "split", "join", "fu2",
633  };
634 
635  CHECK(are_ranges_same(result.path, expected_path));
636  }
637 
638  GIVEN ("A graph with a cycle in one branch") {
639  const auto graph = makeGraph_2branches1withCycle();
640 
641  const auto sources = singleItemSet("fu1");
642  const auto vertex_data = g_algos.wavedBreadthFirstVisit(graph, sources, visitAllVertices(), DebugVisitor<StringIDGraph::VertexID>{});
643  const auto result = g_algos.singleTraceback(sources, "fu2", vertex_data);
644 
645  std::vector<StringIDGraph::VertexID> expected_path {
646  "fu1", "split", "join", "fu2",
647  };
648 
649  CHECK(are_ranges_same(result.path, expected_path));
650  }
651 }
652 
653 template <typename VertexID = StringIDGraph::VertexID, typename Cost = int>
654 auto vertexCosterWithDefault(Cost default_, std::map<VertexID, Cost> costs) {
655  return [=](const VertexID& v) {
656  auto lookup = costs.find(v);
657  return lookup == costs.end() ? default_ : lookup->second;
658  };
659 }
660 
661 SCENARIO ("dijkstraVisit Tests", "[graph]") {
662  const auto g_algos = GraphAlgorithms<StringIDGraph::VertexID>();
663 
664  GIVEN ("A 121 graph -- prefer b") {
665  const auto graph = makeGraph_121();
666 
667  const auto sources = singleItemSet("fu1");
668  const auto vertex_coster = [](const auto& v) { return v == "i_b" ? 1 : 2; };
669  const auto vertex_data = g_algos.dijkstraVisit(graph, sources, vertex_coster, DebugVisitor<StringIDGraph::VertexID>{});
670  const auto result = g_algos.singleTraceback(sources, "fu2", vertex_data);
671 
672  std::vector<StringIDGraph::VertexID> expected_path {
673  "fu1", "i_b", "fu2",
674  };
675 
676  CHECK(result.path == expected_path);
677  }
678 
679  GIVEN ("A 121 graph -- prefer a") {
680  const auto graph = makeGraph_121();
681 
682  const auto sources = singleItemSet("fu1");
683  const auto vertex_coster = [](const auto& v) { return v == "i_a" ? 1 : 2; };
684  const auto vertex_data = g_algos.dijkstraVisit(graph, sources, vertex_coster, DebugVisitor<StringIDGraph::VertexID>{});
685  const auto result = g_algos.singleTraceback(sources, "fu2", vertex_data);
686 
687  std::vector<StringIDGraph::VertexID> expected_path {
688  "fu1", "i_a", "fu2",
689  };
690 
691  CHECK(result.path == expected_path);
692  }
693 
694  GIVEN ("A 12x21 graph -- neg cost to other source") {
695  const auto graph = makeGraph_12x21();
696 
697  const auto sources = std::set<StringIDGraph::VertexID>{"fu1", "xb_1_a"};
698  const auto vertex_coster = [](const auto& v) {
699  if (v == "fu1_out_a") return -2;
700  if (v == "xb_1_b") return 2;
701  return 1;
702  };
703  const auto vertex_data = g_algos.dijkstraVisit(graph, sources, vertex_coster, DebugVisitor<StringIDGraph::VertexID>{});
704  const auto result = g_algos.singleTraceback(singleItemSet("fu1"), "fu2", vertex_data);
705 
706  std::vector<StringIDGraph::VertexID> expected_path {
707  "fu1", "fu1_out_a", "xb_1_a", "fu2",
708  };
709 
710  CHECK(result.path == expected_path);
711  }
712 }
713 
714 
715 namespace {
716 
717 struct BFSCallbackTestDescription {
718  using VertexID = StringIDGraph::VertexID;
719  using MyGraphAlgorithms = GraphAlgorithms<VertexID>;
720  std::string graph_description;
721  std::set<std::string> sources;
722  int expected_num_edges_not_visited_and_not_skipped; // expected No. of edges that won't be seen in the search
723  std::vector<std::set<std::string>> vertex_order;
724  StringIDGraph graph;
725 };
726 
727 struct BFSCallbackTest_UseWavedBreadthFirstVisit {
728  std::string name = "wavedBreadthFirstVisit";
729 
730  template<typename GAlgo, typename G, typename Sources, typename V>
731  decltype(auto) invoke(GAlgo&& g_algos, G&& graph, Sources&& sources, V&& visitor) const {
732  return g_algos.wavedBreadthFirstVisit(graph, sources, visitAllVertices(), visitor);
733  }
734 
735  template<typename Test, typename RV, typename AllEdges>
736  void check_result(const Test& test, const RV& bfv_result, const AllEdges& edges_visited_or_skipped) const {
737  // check that all edges visited are present
738  std::decay_t<decltype(edges_visited_or_skipped)> edges_in_result;
739  for (const auto& v_and_vdata : bfv_result) {
740  for (const auto& fanin : v_and_vdata.second.fanin) {
741  edges_in_result.insert({fanin, v_and_vdata.first});
742  }
743  }
744  CHECK(edges_in_result == edges_visited_or_skipped);
745  }
746 };
747 
748 struct BFSCallbackTest_UseBreadthFirstVisit {
749  std::string name = "breadthFirstVisit";
750 
751  template<typename GAlgo, typename G, typename Sources, typename V>
752  decltype(auto) invoke(GAlgo&& g_algos, G&& graph, Sources&& sources, V&& visitor) const {
753  return g_algos.breadthFirstVisit(graph, sources, visitor);
754  }
755 
756  template<typename Test, typename RV, typename AllEdges>
757  void check_result(const Test& test, const RV& bfv_result, const AllEdges& edges_visited_or_skipped) const {
758  (void)test; (void)bfv_result; (void)edges_visited_or_skipped; // nothing to check, yet
759  }
760 };
761 
762 struct BFSCallbackTest_UseDijkstraVisit_UniformCost {
763  std::string name = "dijkstraVisit";
764 
765  template<typename GAlgo, typename G, typename Sources, typename V>
766  decltype(auto) invoke(GAlgo&& g_algos, G&& graph, Sources&& sources, V&& visitor) const {
767  const auto vertex_coster = [](auto&&) { return 1; }; // so it behaves like a BFS
768  return g_algos.dijkstraVisit(graph, sources, vertex_coster, visitor);
769  }
770 
771  template<typename Test, typename RV, typename AllEdges>
772  void check_result(const Test& test, const RV& bfv_result, const AllEdges& edges_visited_or_skipped) const {
773  (void)test; (void)bfv_result; (void)edges_visited_or_skipped; // nothing to check, yet
774  }
775 };
776 
777 } // end anon namespace
778 
779 TEMPLATE_TEST_CASE("BreadthFirstVisit Callback Tests", "",
780  BFSCallbackTest_UseWavedBreadthFirstVisit,
781  BFSCallbackTest_UseBreadthFirstVisit,
782  BFSCallbackTest_UseDijkstraVisit_UniformCost
783 ) {
784  const auto& test = GENERATE(values<BFSCallbackTestDescription>({
785  { "121", {"fu1"}, 0, { {"fu1"}, {"i_a","i_b"}, {"fu2"} }, makeGraph_121() },
786  { "121", {"fu1","i_a"}, 0, { {"fu1","i_a"}, {"i_b","fu2"} }, makeGraph_121() },
787  { "121", {"i_a"}, 3, { {"i_a"}, {"fu2"} }, makeGraph_121() },
788  { "121", {"fu1","i_a","i_b","fu2"}, 0, { {"fu1","i_a","i_b","fu2"} }, makeGraph_121() },
789  { "Loop3Node", {"a"}, 0, { {"a"}, {"b"}, {"c"} }, makeGraph_Loop3Node() },
790  { "Loop3Node", {"a","c"}, 0, { {"a","c"}, {"b"}, }, makeGraph_Loop3Node() },
791  { "Loop3NodeWithExtraFanout", {"a"}, 0, { {"a"}, {"b","d"}, {"c"} }, makeGraph_Loop3NodeWithExtraFanout() },
792  { "2unevenBranches", {"fu1"}, 0, { {"fu1"}, {"split"}, {"b1_1","join"}, {"b1_2","fu2"}, {"b1_3"} }, makeGraph_2unevenBranches() },
793  { "2branches1withCycle", {"fu1"}, 0, { {"fu1"}, {"split"}, {"c_start","join"}, {"fu2","c1"}, {"c_end"}, {"c_rev"} }, makeGraph_2branches1withCycle() },
794  }));
795 
796  using Catch::Detail::stringify; // non public... but unlikely to change
797  const auto g_algos = typename std::remove_reference_t<decltype(test)>::MyGraphAlgorithms();
798  using VertexID = typename std::remove_reference_t<decltype(test)>::VertexID;
800 
801  const TestType bfv_name_and_func;
802 
803  GIVEN("A " << test.graph_description << " graph") {
804  const auto& graph = test.graph;
805  WHEN("Running " << bfv_name_and_func.name << ", starting at " << stringify(test.sources)) {
806  auto eventVisitor = Visitor{};
807  const auto bfv_result = bfv_name_and_func.invoke(g_algos, graph, test.sources, eventVisitor);
808 
809  THEN("Vertices and edges were visited in the right order") {
810  auto vertices_visited = std::set<VertexID>{};
811  auto edges_visited = std::set<std::pair<VertexID,VertexID>>{};
812  auto edges_skipped = std::set<std::pair<VertexID,VertexID>>{};
813  auto vertices_visited_this_wave = std::set<VertexID>{};
814  auto targets_of_edges_in_this_wave = std::set<VertexID>{};
815  auto targets_of_edges_visited = std::set<VertexID>{};
816  auto vertex_order_it = test.vertex_order.begin();
817 
818  for (const auto& event : eventVisitor.examine_order) {
819  INFO("Checking event " << event);
820  // skip any events we don't know about
821  if (true
822  && event.type != Visitor::Event::onExamine
823  && event.type != Visitor::Event::onSkipped
824  && event.type != Visitor::Event::onExamineEdge
825  && event.type != Visitor::Event::onSkippedEdge
826  && event.type != Visitor::Event::onWaveEnd
827  ) { continue; }
828 
829  // remaining events, but visited everything? error
830  REQUIRE(vertex_order_it != test.vertex_order.end());
831 
832  INFO(
833  "In wave " << 1 + std::distance(test.vertex_order.begin(), vertex_order_it)
834  << "/" << test.vertex_order.size() << ": " << stringify(*vertex_order_it)
835  );
836 
837  // the next wave, or empty if none.
838  std::decay_t<decltype(*vertex_order_it)> expected_next_wave;
839  if (std::next(vertex_order_it) != test.vertex_order.end()) {
840  expected_next_wave = *std::next(vertex_order_it);
841  }
842 
843  INFO("Expected next wave " << stringify(expected_next_wave));
844 
845  const auto advance_wave = [&]{
846  // have we visited everything?
847  CHECK(vertices_visited_this_wave == *vertex_order_it);
848  CHECK(expected_next_wave == targets_of_edges_in_this_wave);
849 
850  // advance the wave
851  ++vertex_order_it;
852  vertices_visited_this_wave.clear();
853  targets_of_edges_in_this_wave.clear();
854  };
855 
856  if (event.type == Visitor::Event::onExamine) {
857  const auto& v = event.v1;
858 
859  if (not vertex_order_it->count(v)) {
860  advance_wave();
861  }
862 
863  // supposed to be in this wave
864  CHECK(vertex_order_it->count(v));
865  // unique in this wave
866  CHECK(vertices_visited_this_wave.insert(v).second);
867  // unique in this visit
868  CHECK(vertices_visited.insert(v).second);
869 
870 
871  } else if (event.type == Visitor::Event::onSkipped) {
872  const auto& v = event.v1;
873 
874  // should have already visited it
875  CHECK(vertices_visited.count(v));
876 
877  } else if (event.type == Visitor::Event::onExamineEdge) {
878  const auto next_wave_it = std::next(vertex_order_it);
879  const auto& source = event.v1;
880  const auto& target = event.v2;
881 
882  // can't be on the last wave
883  REQUIRE(next_wave_it != test.vertex_order.end());
884  // source is in this wave
885  CHECK(vertex_order_it->count(source));
886  // target is in the next wave
887  CHECK(expected_next_wave.count(target));
888  // can't have already visited it
889  CHECK(edges_visited.insert({source, target}).second);
890  // must not have already skipped it
891  CHECK(edges_skipped.count({source, target}) == 0);
892  // already visited source
893  CHECK(vertices_visited_this_wave.count(source));
894  // one one edge per vertex in next wave (others are onSkippedEdge)
895  CHECK(targets_of_edges_in_this_wave.insert(target).second);
896  // one one edge per vertex ever
897  CHECK(targets_of_edges_visited.insert(target).second);
898 
899  } else if (event.type == Visitor::Event::onSkippedEdge) {
900  const auto& source = event.v1;
901  const auto& target = event.v2;
902 
903  // must have already visited source
904  CHECK(vertices_visited.count(source));
905  // must have already seen an edge with target target, or it's a source
906  CHECK((targets_of_edges_visited.count(target) || test.sources.count(target)));
907  // can't have already skipped it
908  CHECK(edges_skipped.insert({source, target}).second);
909  // must not have visited this edge
910  CHECK(edges_visited.count({source, target}) == 0);
911 
912  } else if (event.type == Visitor::Event::onWaveEnd) {
913  advance_wave();
914  }
915  }
916 
917  // should have reached the end
918  CHECK((vertex_order_it == test.vertex_order.end() || std::next(vertex_order_it) == test.vertex_order.end()));
919  // should be empty at the end
920  CHECK(targets_of_edges_in_this_wave.empty());
921 
922  const auto edges_visited_or_skipped = [&]() { // (immediately invoked)
923  auto result = edges_visited;
924  std::copy(edges_skipped.begin(), edges_skipped.end(), std::inserter(result, result.end()));
925  return result;
926  }();
927 
928  const auto num_edges_expected = std::accumulate(graph.fanout_lists.begin(), graph.fanout_lists.end(), 0,
929  [](const auto& lhs, const auto& elem) { return lhs + (std::ptrdiff_t)elem.second.size(); }
930  ) - test.expected_num_edges_not_visited_and_not_skipped;
931 
932  CHECK(edges_visited_or_skipped.size() == num_edges_expected);
933 
934  AND_THEN("the return value should be as expected") {
935  bfv_name_and_func.check_result(test, bfv_result, edges_visited_or_skipped);
936  }
937  }
938  }
939  }
940 }
DebugVisitor
Definition: GraphAlgorithmHelpers.h:181
GraphAlgorithms
Definition: GraphAlgorithms.h:72
GraphAlgorithms.h
begin
auto begin(const SingleItemImmutableSet< VertexID > &siis)
Definition: Collections.h:137
StringIDGraph::link
void link(const VertexID &source, const VertexID &target)
Definition: GraphAlgorithmHelpers.h:309
SCENARIO
SCENARIO("findNShortestPaths Tests", "[graph]")
Definition: GraphAlgorithms_tests.cpp:215
IgnoreVertexSetVisitor
Definition: GraphAlgorithmHelpers.h:146
visitAllVertices
auto visitAllVertices()
Definition: GraphAlgorithmHelpers.h:38
StringIDGraph::insertMultiFanin
VertexID insertMultiFanin(const VertexIDList &fanins, std::string name)
Definition: GraphAlgorithmHelpers.h:301
findSingleVertex
auto findSingleVertex(VertexID v)
Definition: GraphAlgorithmHelpers.h:22
are_ranges_same
bool are_ranges_same(const R1 &r1, const R2 &r2)
Definition: Collections.h:328
StringIDGraph::VertexID
std::string VertexID
Definition: GraphAlgorithmHelpers.h:287
TEMPLATE_TEST_CASE
TEMPLATE_TEST_CASE("BreadthFirstVisit Callback Tests", "", BFSCallbackTest_UseWavedBreadthFirstVisit, BFSCallbackTest_UseBreadthFirstVisit, BFSCallbackTest_UseDijkstraVisit_UniformCost)
Definition: GraphAlgorithms_tests.cpp:779
StringIDGraph::insert
VertexID insert(std::string name)
Definition: GraphAlgorithmHelpers.h:290
end
auto end(const SingleItemImmutableSet< VertexID > &siis)
Definition: Collections.h:138
IgnoreEdgeSetVisitor
Definition: GraphAlgorithmHelpers.h:164
StringIDGraph
Definition: GraphAlgorithmHelpers.h:286
EventRecordingVisitor
Definition: GraphAlgorithmHelpers.h:215
singleItemSet
SingleItemImmutableSet< VertexID > singleItemSet(VertexID v)
Definition: Collections.h:143
vertexCosterWithDefault
auto vertexCosterWithDefault(Cost default_, std::map< VertexID, Cost > costs)
Definition: GraphAlgorithms_tests.cpp:654