Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
tpl_test_cycle.cc
Go to the documentation of this file.
1
2/*
3 Aleph_w
4
5 Data structures & Algorithms
6 version 2.0.0b
7 https://github.com/lrleon/Aleph-w
8
9 This file is part of Aleph-w library
10
11 Copyright (c) 2002-2026 Leandro Rabindranath Leon
12
13 Permission is hereby granted, free of charge, to any person obtaining a copy
14 of this software and associated documentation files (the "Software"), to deal
15 in the Software without restriction, including without limitation the rights
16 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 copies of the Software, and to permit persons to whom the Software is
18 furnished to do so, subject to the following conditions:
19
20 The above copyright notice and this permission notice shall be included in all
21 copies or substantial portions of the Software.
22
23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29 SOFTWARE.
30*/
31
32
53#include <gtest/gtest.h>
54#include <tpl_test_cycle.H>
55#include <tpl_graph.H>
56#include <tpl_sgraph.H>
57#include <tpl_agraph.H>
58
59using namespace Aleph;
60
61//==============================================================================
62// Graph Type Definitions - All 6 combinations
63//==============================================================================
64
65// List-based graphs (tpl_graph.H)
68
69// Sparse graphs (tpl_sgraph.H)
72
73// Array-based graphs (tpl_agraph.H)
76
77//==============================================================================
78// Test Fixtures for Primary Graph Types
79//==============================================================================
80
81class TestForCycleDirected : public ::testing::Test
82{
83protected:
86};
87
88class TestForCycleUndirected : public ::testing::Test
89{
90protected:
93};
94
95// Custom arc filter for testing
96template <class GT>
98{
99 bool operator () (typename GT::Arc * arc) const
100 {
101 return arc->get_info() % 2 == 0;
102 }
103};
104
105//==============================================================================
106// Basic Functionality Tests
107//==============================================================================
108
110{
112 auto n1 = g.insert_node(1);
113
114 // Single node with no arcs has no cycle
115 EXPECT_FALSE(cycle_test(g, n1));
116}
117
119{
121
122 auto n1 = g.insert_node(1);
123 auto n2 = g.insert_node(2);
124 auto n3 = g.insert_node(3);
125 auto n4 = g.insert_node(4);
126
127 g.insert_arc(n1, n2);
128 g.insert_arc(n2, n3);
129 g.insert_arc(n3, n4);
130
131 // Linear chain has no cycles
132 EXPECT_FALSE(cycle_test(g, n1));
133 EXPECT_FALSE(cycle_test(g, n2));
134 EXPECT_FALSE(cycle_test(g, n3));
135 EXPECT_FALSE(cycle_test(g, n4));
136}
137
139{
141
142 auto n1 = g.insert_node(1);
143 auto n2 = g.insert_node(2);
144 auto n3 = g.insert_node(3);
145
146 g.insert_arc(n1, n2);
147 g.insert_arc(n2, n3);
148 g.insert_arc(n3, n1); // Completes the triangle
149
150 // All nodes in the cycle should detect it
151 EXPECT_TRUE(cycle_test(g, n1));
152 EXPECT_TRUE(cycle_test(g, n2));
153 EXPECT_TRUE(cycle_test(g, n3));
154}
155
157{
159
160 auto n1 = g.insert_node(1);
161 g.insert_arc(n1, n1); // Self-loop
162
163 EXPECT_TRUE(cycle_test(g, n1));
164}
165
167{
169
170 auto n1 = g.insert_node(1);
171 auto n2 = g.insert_node(2);
172
173 g.insert_arc(n1, n2);
174 g.insert_arc(n2, n1); // Back edge creates cycle
175
176 EXPECT_TRUE(cycle_test(g, n1));
177 EXPECT_TRUE(cycle_test(g, n2));
178}
179
181{
183
184 const int N = 100;
186
187 for (int i = 0; i < N; ++i)
188 nodes.append(g.insert_node(i));
189
190 // Create a long cycle: 0 -> 1 -> 2 -> ... -> 99 -> 0
191 for (int i = 0; i < N; ++i)
192 g.insert_arc(nodes(i), nodes((i + 1) % N));
193
194 // All nodes should detect the cycle
195 for (int i = 0; i < N; ++i)
197}
198
199//==============================================================================
200// Single-Source Cycle Detection Tests
201//==============================================================================
202
204{
206
207 // Graph structure: n1 -> n2 -> n3 -> n4 -> n2 (cycle not involving n1)
208 auto n1 = g.insert_node(1);
209 auto n2 = g.insert_node(2);
210 auto n3 = g.insert_node(3);
211 auto n4 = g.insert_node(4);
212
213 g.insert_arc(n1, n2);
214 g.insert_arc(n2, n3);
215 g.insert_arc(n3, n4);
216 g.insert_arc(n4, n2); // Cycle: 2 -> 3 -> 4 -> 2
217
218 // n1 is not part of the cycle, but can reach it
219 EXPECT_FALSE(cycle_test(g, n1));
220
221 // Nodes in the cycle should detect it
222 EXPECT_TRUE(cycle_test(g, n2));
223 EXPECT_TRUE(cycle_test(g, n3));
224 EXPECT_TRUE(cycle_test(g, n4));
225}
226
228{
230
231 auto n1 = g.insert_node(1);
232 auto n2 = g.insert_node(2);
233 auto n3 = g.insert_node(3);
234
235 // Create cycle between n1 and n2
236 g.insert_arc(n1, n2);
237 g.insert_arc(n2, n1);
238
239 // n3 is isolated, should not detect cycle
240 EXPECT_FALSE(cycle_test(g, n3));
241}
242
244{
246
247 auto n1 = g.insert_node(1);
248 auto n2 = g.insert_node(2);
249 auto n3 = g.insert_node(3);
250 auto n4 = g.insert_node(4);
251
252 // n1 -> n2, but n3 -> n4 -> n3 (separate cycle)
253 g.insert_arc(n1, n2);
254 g.insert_arc(n3, n4);
255 g.insert_arc(n4, n3);
256
257 EXPECT_FALSE(cycle_test(g, n1));
258 EXPECT_FALSE(cycle_test(g, n2));
259 EXPECT_TRUE(cycle_test(g, n3));
260 EXPECT_TRUE(cycle_test(g, n4));
261}
262
263//==============================================================================
264// Complex Graph Structure Tests
265//==============================================================================
266
268{
270
271 auto n1 = g.insert_node(1);
272 auto n2 = g.insert_node(2);
273 auto n3 = g.insert_node(3);
274 auto n4 = g.insert_node(4);
275 auto n5 = g.insert_node(5);
276
277 // Cycle 1: n1 -> n2 -> n1
278 g.insert_arc(n1, n2);
279 g.insert_arc(n2, n1);
280
281 // Cycle 2: n3 -> n4 -> n5 -> n3
282 g.insert_arc(n3, n4);
283 g.insert_arc(n4, n5);
284 g.insert_arc(n5, n3);
285
286 // Connection from n2 to n3
287 g.insert_arc(n2, n3);
288
289 // n1 should detect its own cycle
290 EXPECT_TRUE(cycle_test(g, n1));
291
292 // n3, n4, n5 should detect their cycle
293 EXPECT_TRUE(cycle_test(g, n3));
294 EXPECT_TRUE(cycle_test(g, n4));
295 EXPECT_TRUE(cycle_test(g, n5));
296}
297
299{
301
302 auto n1 = g.insert_node(1);
303 auto n2 = g.insert_node(2);
304 auto n3 = g.insert_node(3);
305 auto n4 = g.insert_node(4);
306
307 // Diamond: n1 -> n2, n1 -> n3, n2 -> n4, n3 -> n4
308 g.insert_arc(n1, n2);
309 g.insert_arc(n1, n3);
310 g.insert_arc(n2, n4);
311 g.insert_arc(n3, n4);
312
313 // No cycle in DAG diamond
314 EXPECT_FALSE(cycle_test(g, n1));
315
316 // Add back edge to create cycle
317 g.insert_arc(n4, n1);
318 EXPECT_TRUE(cycle_test(g, n1));
319 EXPECT_TRUE(cycle_test(g, n2));
320 EXPECT_TRUE(cycle_test(g, n3));
321 EXPECT_TRUE(cycle_test(g, n4));
322}
323
325{
327
328 auto n1 = g.insert_node(1);
329 auto n2 = g.insert_node(2);
330 auto n3 = g.insert_node(3);
331 auto n4 = g.insert_node(4);
332
333 // Outer cycle: 1 -> 2 -> 3 -> 4 -> 1
334 g.insert_arc(n1, n2);
335 g.insert_arc(n2, n3);
336 g.insert_arc(n3, n4);
337 g.insert_arc(n4, n1);
338
339 // Inner shortcut: 2 -> 4 (creates additional cycle 2 -> 3 -> 4 -> 2)
340 g.insert_arc(n2, n4);
341
342 EXPECT_TRUE(cycle_test(g, n1));
343 EXPECT_TRUE(cycle_test(g, n2));
344 EXPECT_TRUE(cycle_test(g, n3));
345 EXPECT_TRUE(cycle_test(g, n4));
346}
347
348//==============================================================================
349// Undirected Graph Tests
350//==============================================================================
351
353{
355
356 auto n1 = g.insert_node(1);
357 auto n2 = g.insert_node(2);
358 auto n3 = g.insert_node(3);
359
360 g.insert_arc(n1, n2);
361 g.insert_arc(n2, n3);
362 g.insert_arc(n3, n1);
363
364 // Undirected graph: all edges create bidirectional paths
365 // This will detect trivial 2-cycles (as documented in warnings)
366 EXPECT_TRUE(cycle_test(g, n1));
367 EXPECT_TRUE(cycle_test(g, n2));
368 EXPECT_TRUE(cycle_test(g, n3));
369}
370
372{
374
375 auto n1 = g.insert_node(1);
376 auto n2 = g.insert_node(2);
377 auto n3 = g.insert_node(3);
378 auto n4 = g.insert_node(4);
379
380 // Tree structure (no cycles)
381 g.insert_arc(n1, n2);
382 g.insert_arc(n1, n3);
383 g.insert_arc(n1, n4);
384
385 // Trees have no cycles, even in undirected graphs
386 // (The algorithm marks arcs as visited, preventing trivial back-and-forth)
387 EXPECT_FALSE(cycle_test(g, n1));
388
389 // Add edge to create actual cycle
390 g.insert_arc(n2, n3);
391 EXPECT_TRUE(cycle_test(g, n1));
392}
393
394//==============================================================================
395// Arc Filter Tests
396//==============================================================================
397
399{
400 auto n1 = g.insert_node(1);
401 auto n2 = g.insert_node(2);
402 auto n3 = g.insert_node(3);
403
404 // Create triangle with odd arc values
405 auto a1 = g.insert_arc(n1, n2, 1); // odd
406 [[maybe_unused]] auto a2 = g.insert_arc(n2, n3, 2); // even
407 auto a3 = g.insert_arc(n3, n1, 3); // odd
408
409 // With even filter, only a2 is visible -> no cycle
413
414 // Make all arcs even -> cycle becomes visible
415 a1->get_info() = 2;
416 a3->get_info() = 4;
418}
419
421{
422 auto n1 = g.insert_node(1);
423 auto n2 = g.insert_node(2);
424 auto n3 = g.insert_node(3);
425 auto n4 = g.insert_node(4);
426
427 // Create path: 1 -> 2 -> 3 -> 4 -> 1
428 g.insert_arc(n1, n2, 2); // even
429 g.insert_arc(n2, n3, 2); // even
430 g.insert_arc(n3, n4, 1); // odd
431 g.insert_arc(n4, n1, 2); // even
432
433 // With even filter, path is broken at n3 -> n4
436}
437
438//==============================================================================
439// Error Handling Tests
440//==============================================================================
441
443{
445
446 EXPECT_THROW(cycle_test(g, nullptr), std::invalid_argument);
447}
448
450{
452
453 auto n1 = g.insert_node(1);
454 auto n2 = g.insert_node(2);
455 g.insert_arc(n1, n2);
456 g.insert_arc(n2, n1);
457
458 // Multiple calls should give consistent results
459 EXPECT_TRUE(cycle_test(g, n1));
460 EXPECT_TRUE(cycle_test(g, n1));
461 EXPECT_TRUE(cycle_test(g, n1));
462}
463
464//==============================================================================
465// Stress Tests
466//==============================================================================
467
469{
471
472 const int N = 1000;
474
475 // Create large DAG with no cycles
476 for (int i = 0; i < N; ++i)
477 nodes.append(g.insert_node(i));
478
479 // Connect each node to next few nodes (DAG)
480 for (int i = 0; i < N - 1; ++i)
481 for (int j = i + 1; j < std::min(i + 5, N); ++j)
482 g.insert_arc(nodes(i), nodes(j));
483
484 // No cycles should be detected
488}
489
491{
493
494 const int N = 50;
496
497 for (int i = 0; i < N; ++i)
498 nodes.append(g.insert_node(i));
499
500 // Create dense graph with many forward edges
501 for (int i = 0; i < N; ++i)
502 for (int j = i + 1; j < N; ++j)
503 g.insert_arc(nodes(i), nodes(j));
504
505 // Add single back edge to create cycle
506 g.insert_arc(nodes(N-1), nodes(0));
507
508 // Cycle should be detected from all nodes
512}
513
514//==============================================================================
515// Const Correctness Tests
516//==============================================================================
517
519{
520 auto n1 = g.insert_node(1);
521 auto n2 = g.insert_node(2);
522 g.insert_arc(n1, n2);
523 g.insert_arc(n2, n1);
524
525 const Graph & const_g = g;
527
528 // Should compile and work with const graph
530}
531
533{
534 auto n1 = g.insert_node(1);
535 auto n2 = g.insert_node(2);
536 g.insert_arc(n1, n2);
537 g.insert_arc(n2, n1);
538
540
541 // Should compile and work with const tester
542 EXPECT_TRUE(cycle_test(g, n1));
543}
544
545//==============================================================================
546// Special Cases
547//==============================================================================
548
550{
552
553 auto n1 = g.insert_node(1);
554 auto n2 = g.insert_node(2);
555
556 g.insert_arc(n1, n1); // Self-loop at n1
557 g.insert_arc(n2, n2); // Self-loop at n2
558 g.insert_arc(n1, n2); // Connection
559
560 EXPECT_TRUE(cycle_test(g, n1));
561 EXPECT_TRUE(cycle_test(g, n2));
562}
563
565{
567
568 auto n1 = g.insert_node(1);
569 auto n2 = g.insert_node(2);
570
571 // Multiple arcs between same nodes
572 g.insert_arc(n1, n2);
573 g.insert_arc(n1, n2);
574 g.insert_arc(n2, n1);
575 g.insert_arc(n2, n1);
576
577 EXPECT_TRUE(cycle_test(g, n1));
578 EXPECT_TRUE(cycle_test(g, n2));
579}
580
582{
584
585 // Component 1: cycle
586 auto n1 = g.insert_node(1);
587 auto n2 = g.insert_node(2);
588 g.insert_arc(n1, n2);
589 g.insert_arc(n2, n1);
590
591 // Component 2: no cycle
592 auto n3 = g.insert_node(3);
593 auto n4 = g.insert_node(4);
594 g.insert_arc(n3, n4);
595
596 EXPECT_TRUE(cycle_test(g, n1));
597 EXPECT_TRUE(cycle_test(g, n2));
598 EXPECT_FALSE(cycle_test(g, n3));
599 EXPECT_FALSE(cycle_test(g, n4));
600}
601
602//==============================================================================
603// Typed Tests for All Graph Types
604//==============================================================================
605
606// Helper template to run basic cycle tests on any graph type
607template <typename GraphType>
608class TestForCycleAllGraphs : public ::testing::Test
609{
610protected:
612};
613
614// Define the list of graph types to test
615using GraphTypes = ::testing::Types<
616 ListGraph,
622>;
623
625
627{
628 using Graph = TypeParam;
629 Graph & g = this->g;
631
632 auto n1 = g.insert_node(1);
633 auto n2 = g.insert_node(2);
634 auto n3 = g.insert_node(3);
635
636 g.insert_arc(n1, n2);
637 g.insert_arc(n2, n3);
638
639 EXPECT_FALSE(cycle_test(g, n1));
640 EXPECT_FALSE(cycle_test(g, n2));
641 EXPECT_FALSE(cycle_test(g, n3));
642}
643
645{
646 using Graph = TypeParam;
647 Graph & g = this->g;
649
650 auto n1 = g.insert_node(1);
651 auto n2 = g.insert_node(2);
652 auto n3 = g.insert_node(3);
653
654 g.insert_arc(n1, n2);
655 g.insert_arc(n2, n3);
656 g.insert_arc(n3, n1); // Create cycle
657
658 EXPECT_TRUE(cycle_test(g, n1));
659 EXPECT_TRUE(cycle_test(g, n2));
660 EXPECT_TRUE(cycle_test(g, n3));
661}
662
664{
665 using Graph = TypeParam;
666 Graph & g = this->g;
668
669 auto n1 = g.insert_node(1);
670 g.insert_arc(n1, n1); // Self-loop
671
672 EXPECT_TRUE(cycle_test(g, n1));
673}
674
676{
677 using Graph = TypeParam;
678 Graph & g = this->g;
680
681 auto n1 = g.insert_node(1);
682
683 EXPECT_FALSE(cycle_test(g, n1));
684}
685
687{
688 using Graph = TypeParam;
689 Graph & g = this->g;
691
692 EXPECT_THROW(cycle_test(g, nullptr), std::invalid_argument);
693}
694
696{
697 using Graph = TypeParam;
698 Graph & g = this->g;
700
701 auto n1 = g.insert_node(1);
702 auto n2 = g.insert_node(2);
703
704 g.insert_arc(n1, n2);
705 g.insert_arc(n2, n1);
706
707 EXPECT_TRUE(cycle_test(g, n1));
708 EXPECT_TRUE(cycle_test(g, n2));
709}
710
712{
713 using Graph = TypeParam;
714 Graph & g = this->g;
716
717 auto n1 = g.insert_node(1);
718 auto n2 = g.insert_node(2);
719 auto n3 = g.insert_node(3);
720 auto n4 = g.insert_node(4);
721 auto n5 = g.insert_node(5);
722
723 g.insert_arc(n1, n2);
724 g.insert_arc(n2, n3);
725 g.insert_arc(n3, n4);
726 g.insert_arc(n4, n5);
727 g.insert_arc(n5, n1); // Complete cycle
728
729 EXPECT_TRUE(cycle_test(g, n1));
730 EXPECT_TRUE(cycle_test(g, n3));
731 EXPECT_TRUE(cycle_test(g, n5));
732}
733
734// Main
735int main(int argc, char **argv)
736{
737 ::testing::InitGoogleTest(&argc, argv);
738 return RUN_ALL_TESTS();
739}
int main()
Generic directed graph (digraph) wrapper template.
Definition graph-dry.H:3848
T & append()
Allocate a new entry to the end of array.
Arc for graphs implemented with simple adjacency lists.
Definition tpl_sgraph.H:197
virtual Node * insert_node(Node *node) noexcept
Insertion of a node already allocated.
Definition tpl_graph.H:524
Arc * insert_arc(Node *src_node, Node *tgt_node, void *a)
Definition tpl_graph.H:604
Graph class implemented with singly-linked adjacency lists.
Definition tpl_sgraph.H:274
Test whether a cycle is reachable from a given node.
ArcInfo & get_info() noexcept
Return a modifiable reference to the arc data.
Definition graph-dry.H:595
#define N
Definition fib.C:294
DynArray< Graph::Node * > nodes
Definition graphpic.C:406
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
DynList< T > maps(const C &c, Op op)
Classic map operation.
Arc of graph implemented with double-linked adjacency lists.
Definition tpl_graph.H:222
bool operator()(typename GT::Arc *arc) const
Array-based graph implementation.
::testing::Types< ListGraph, ListDigraph, SparseGraph, SparseDigraph, ArrayGraph, ArrayDigraph > GraphTypes
Generic graph and digraph implementations.
Simple graph implementation with adjacency lists.
Cycle detection in graphs.
TEST_F(TestForCycleDirected, EmptyGraph)
List_Digraph< Graph_Node< int >, Graph_Arc< int > > ListDigraph
List_Graph< Graph_Node< int >, Graph_Arc< int > > ListGraph
Array_Graph< Graph_Anode< int >, Graph_Aarc< int > > ArrayGraph
TYPED_TEST_SUITE(TestForCycleAllGraphs, GraphTypes)
TYPED_TEST(TestForCycleAllGraphs, BasicNoCycle)
List_SDigraph< Graph_Snode< int >, Graph_Sarc< int > > SparseDigraph
List_SGraph< Graph_Snode< int >, Graph_Sarc< int > > SparseGraph