Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
test_bellman_ford.cc
Go to the documentation of this file.
1
13#include <gtest/gtest.h>
14
15#include <Bellman_Ford.H>
16#include <tpl_graph.H>
17
18using namespace Aleph;
19
20// Graph type for tests
22using Node = GT::Node;
23using Arc = GT::Arc;
24
25// ========== TEST 1: Simple Graph without Negative Cycles ==========
27
28 GT g;
29 Node* n0 = g.insert_node(0);
30 Node* n1 = g.insert_node(1);
31 Node* n2 = g.insert_node(2);
32 Node* n3 = g.insert_node(3);
33
34 [[maybe_unused]] Arc* a01 = g.insert_arc(n0, n1, 1);
35 [[maybe_unused]] Arc* a02 = g.insert_arc(n0, n2, 4);
36 [[maybe_unused]] Arc* a12 = g.insert_arc(n1, n2, 2);
37 [[maybe_unused]] Arc* a13 = g.insert_arc(n1, n3, 5);
38 [[maybe_unused]] Arc* a23 = g.insert_arc(n2, n3, 1);
39
41
42 // Should not detect any negative cycles
43 bool has_negative_cycle = bf.has_negative_cycle(n0);
44 ASSERT_FALSE(has_negative_cycle);
45}
46
47// ========== TEST 2: Graph with Negative Cycle ==========
49
50 GT g;
51 Node* n0 = g.insert_node(0);
52 Node* n1 = g.insert_node(1);
53 Node* n2 = g.insert_node(2);
54
55 [[maybe_unused]] Arc* a01 = g.insert_arc(n0, n1, 1);
56 [[maybe_unused]] Arc* a12 = g.insert_arc(n1, n2, -3);
57 [[maybe_unused]] Arc* a20 = g.insert_arc(n2, n0, 1); // Cycle: 0->1->2->0 with total weight -1
58
60
61 // Must detect a negative cycle
62 bool has_negative_cycle = bf.has_negative_cycle(n0);
63 ASSERT_TRUE(has_negative_cycle);
64}
65
66// ========== TEST 3: Minimal Paths Spanning Tree ==========
68
69 GT g;
70 Node* n0 = g.insert_node(0);
71 Node* n1 = g.insert_node(1);
72 Node* n2 = g.insert_node(2);
73 Node* n3 = g.insert_node(3);
74
75 [[maybe_unused]] Arc* a01 = g.insert_arc(n0, n1, 1);
76 [[maybe_unused]] Arc* a02 = g.insert_arc(n0, n2, 4);
77 [[maybe_unused]] Arc* a12 = g.insert_arc(n1, n2, 2);
78 [[maybe_unused]] Arc* a13 = g.insert_arc(n1, n3, 5);
79 [[maybe_unused]] Arc* a23 = g.insert_arc(n2, n3, 1);
80
82 bool negative_cycle = bf.paint_spanning_tree(n0);
83
84 ASSERT_FALSE(negative_cycle);
85
86 // Verify that the tree arcs are marked
87 int painted_arcs = 0;
88 for (Arc_Iterator<GT> it(g); it.has_curr(); it.next()) {
89 if (IS_ARC_VISITED(it.get_curr(), Aleph::Spanning_Tree)) {
91 }
92 }
93
94 // The spanning tree must have n-1 arcs
96}
97
98// ========== TEST 4: Faster Version of the Algorithm ==========
100
101 GT g;
102 Node* n0 = g.insert_node(0);
103 Node* n1 = g.insert_node(1);
104 Node* n2 = g.insert_node(2);
105 Node* n3 = g.insert_node(3);
106 Node* n4 = g.insert_node(4);
107
108 g.insert_arc(n0, n1, 6);
109 g.insert_arc(n0, n2, 7);
110 g.insert_arc(n1, n2, 8);
111 g.insert_arc(n1, n3, 5);
112 g.insert_arc(n1, n4, -4);
113 g.insert_arc(n2, n3, -3);
114 g.insert_arc(n2, n4, 9);
115 g.insert_arc(n3, n1, -2);
116 g.insert_arc(n4, n0, 2);
117 g.insert_arc(n4, n3, 7);
118
120 bool negative_cycle = bf.faster_paint_spanning_tree(n0);
121
122 ASSERT_FALSE(negative_cycle);
123}
124
125// ========== TEST 5: Negative Cycle Detection (full version) ==========
127
128 GT g;
129 Node* n0 = g.insert_node(0);
130 Node* n1 = g.insert_node(1);
131 Node* n2 = g.insert_node(2);
132 Node* n3 = g.insert_node(3);
133
134 g.insert_arc(n0, n1, 1);
135 g.insert_arc(n1, n2, -1);
136 g.insert_arc(n2, n3, -1);
137 g.insert_arc(n3, n1, -1); // Negative cycle: 1->2->3->1 with weight -3
138
140 Path<GT> cycle = bf.test_negative_cycle(n0);
141
142 // The resulting cycle must not be empty
144}
145
146// ========== TEST 6: Search Negative Cycle with Parameters ==========
148
149 GT g;
150 Node* n0 = g.insert_node(0);
151 Node* n1 = g.insert_node(1);
152 Node* n2 = g.insert_node(2);
153 Node* n3 = g.insert_node(3);
154
155 g.insert_arc(n0, n1, 1);
156 g.insert_arc(n1, n2, -2);
157 g.insert_arc(n2, n3, -2);
158 g.insert_arc(n3, n1, -1); // Negative cycle
159
161 auto result = bf.search_negative_cycle(n0, 0.5, 2);
162
163 Path<GT>& cycle = get<0>(result);
164 size_t iterations = get<1>(result);
165
167 ASSERT_GT(iterations, 0u);
168}
169
170// ========== TEST 7: Trivial Graph without Negative Cycles ==========
172
173 GT g;
174 Node* n0 = g.insert_node(0);
175
177 bool has_negative_cycle = bf.has_negative_cycle(n0);
178
179 ASSERT_FALSE(has_negative_cycle);
180}
181
182// ========== TEST 8: Graph with Zero Weights ==========
184
185 GT g;
186 Node* n0 = g.insert_node(0);
187 Node* n1 = g.insert_node(1);
188 Node* n2 = g.insert_node(2);
189
190 g.insert_arc(n0, n1, 0);
191 g.insert_arc(n1, n2, 0);
192 g.insert_arc(n2, n0, 0);
193
195 bool has_negative_cycle = bf.has_negative_cycle(n0);
196
197 // A cycle with zero total weight is not negative
198 ASSERT_FALSE(has_negative_cycle);
199}
200
201// ========== TEST 9: Disconnected Graph ==========
203
204 GT g;
205 Node* n0 = g.insert_node(0);
206 Node* n1 = g.insert_node(1);
207 Node* n2 = g.insert_node(2);
208 Node* n3 = g.insert_node(3);
209
210 // Component 1
211 g.insert_arc(n0, n1, 1);
212
213 // Component 2 (disconnected)
214 g.insert_arc(n2, n3, 2);
215
217 bool has_negative_cycle = bf.has_negative_cycle(n0);
218
219 ASSERT_FALSE(has_negative_cycle);
220}
221
222// ========== TEST 10: Negative Weights without Negative Cycle ==========
224
225 GT g;
226 Node* n0 = g.insert_node(0);
227 Node* n1 = g.insert_node(1);
228 Node* n2 = g.insert_node(2);
229 Node* n3 = g.insert_node(3);
230
231 g.insert_arc(n0, n1, -1);
232 g.insert_arc(n1, n2, -2);
233 g.insert_arc(n2, n3, -3);
234 // There is no cycle
235
237 bool has_negative_cycle = bf.has_negative_cycle(n0);
238
239 ASSERT_FALSE(has_negative_cycle);
240}
241
242// ========== TEST 11: Large Graph with Multiple Paths ==========
244
245 GT g;
246 constexpr int num_nodes = 100;
247 std::vector<Node*> nodes;
248
249 for (int i = 0; i < num_nodes; ++i) {
250 nodes.push_back(g.insert_node(i));
251 }
252
253 // Create a long chain
254 for (int i = 0; i < num_nodes - 1; ++i) {
255 g.insert_arc(nodes[i], nodes[i+1], 1);
256 }
257
258 // Add some extra arcs
259 for (int i = 0; i < num_nodes - 5; i += 5) {
260 g.insert_arc(nodes[i], nodes[i+5], 2);
261 }
262
264 bool has_negative_cycle = bf.has_negative_cycle(nodes[0]);
265
266 ASSERT_FALSE(has_negative_cycle);
267}
268
269// ========== TEST 12: Complex Negative Cycle ==========
271
272 GT g;
273 Node* n0 = g.insert_node(0);
274 Node* n1 = g.insert_node(1);
275 Node* n2 = g.insert_node(2);
276 Node* n3 = g.insert_node(3);
277 Node* n4 = g.insert_node(4);
278
279 g.insert_arc(n0, n1, 1);
280 g.insert_arc(n1, n2, 3);
281 g.insert_arc(n2, n3, -2);
282 g.insert_arc(n3, n4, -1);
283 g.insert_arc(n4, n2, -3); // Negative cycle: 2->3->4->2 with weight -6
284
286 Path<GT> cycle = bf.test_negative_cycle(n0);
287
289}
290
291// ========== TEST 13: test_negative_cycle with Output Parameter ==========
293
294 GT g;
295 Node* n0 = g.insert_node(0);
296 Node* n1 = g.insert_node(1);
297 Node* n2 = g.insert_node(2);
298
299 g.insert_arc(n0, n1, 1);
300 g.insert_arc(n1, n2, -3);
301 g.insert_arc(n2, n0, 1);
302
305 bool has_cycle = bf.test_negative_cycle(n0, cycle);
306
309}
310
311// ========== TEST 14: search_negative_cycle without Start Node ==========
313
314 GT g;
315 Node* n0 = g.insert_node(0);
316 Node* n1 = g.insert_node(1);
317 Node* n2 = g.insert_node(2);
318 Node* n3 = g.insert_node(3);
319
320 // Component without negative cycle
321 g.insert_arc(n0, n1, 1);
322
323 // Component with negative cycle
324 g.insert_arc(n1, n2, 1);
325 g.insert_arc(n2, n3, -2);
326 g.insert_arc(n3, n1, -1);
327
329 Path<GT> cycle = bf.search_negative_cycle();
330
332}
333
334// ========== TEST 15: Bellman_Ford_Negative_Cycle Functor ==========
336
337 GT g;
338 Node* n0 = g.insert_node(0);
339 Node* n1 = g.insert_node(1);
340 Node* n2 = g.insert_node(2);
341
342 g.insert_arc(n0, n1, 1);
343 g.insert_arc(n1, n2, -3);
344 g.insert_arc(n2, n0, 1);
345
348 bool has_cycle = detector(g, cycle);
349
352}
353
354// ========== TEST 16: Graph with Negative Self-loop ==========
356
357 GT g;
358 Node* n0 = g.insert_node(0);
359 Node* n1 = g.insert_node(1);
360
361 g.insert_arc(n0, n1, 1);
362 g.insert_arc(n1, n1, -1); // Negative self-loop
363
365 bool has_negative_cycle = bf.has_negative_cycle(n0);
366
367 ASSERT_TRUE(has_negative_cycle);
368}
369
370// ========== TEST 17: All Negative Weights without Cycle ==========
372
373 GT g;
374 Node* n0 = g.insert_node(0);
375 Node* n1 = g.insert_node(1);
376 Node* n2 = g.insert_node(2);
377 Node* n3 = g.insert_node(3);
378
379 g.insert_arc(n0, n1, -1);
380 g.insert_arc(n1, n2, -2);
381 g.insert_arc(n2, n3, -3);
382 g.insert_arc(n0, n2, -4);
383
385 bool has_negative_cycle = bf.has_negative_cycle(n0);
386
387 ASSERT_FALSE(has_negative_cycle);
388}
389
390// ========== TEST 18: Nullptr Validation ==========
392
393 GT g;
394 Node* n0 = g.insert_node(0);
395 Node* n1 = g.insert_node(1);
396 g.insert_arc(n0, n1, 1);
397
399
400 bool caught_exception = false;
401 try {
402 bf.paint_spanning_tree(nullptr);
403 } catch (const std::domain_error&) {
404 caught_exception = true;
405 }
407
408 caught_exception = false;
409 try {
410 bf.faster_paint_spanning_tree(nullptr);
411 } catch (const std::domain_error&) {
412 caught_exception = true;
413 }
415
416 caught_exception = false;
417 try {
418 bf.has_negative_cycle(nullptr);
419 } catch (const std::domain_error&) {
420 caught_exception = true;
421 }
423}
424
425// ========== TEST 19: Completely Empty Graph ==========
427
428 GT g;
429 // No nodes inserted
430
432
433 // has_negative_cycle() without parameters must work
434 bool has_cycle = bf.has_negative_cycle();
436}
437
438// ========== TEST 20: Build Tree from Spanning Tree ==========
440
441 GT g;
442 Node* n0 = g.insert_node(0);
443 Node* n1 = g.insert_node(1);
444 Node* n2 = g.insert_node(2);
445 Node* n3 = g.insert_node(3);
446
447 g.insert_arc(n0, n1, 1);
448 g.insert_arc(n0, n2, 4);
449 g.insert_arc(n1, n2, 2);
450 g.insert_arc(n1, n3, 5);
451 g.insert_arc(n2, n3, 1);
452
454 bf.paint_spanning_tree(n0);
455
456 GT tree;
457 bf.build_tree(tree, false);
458
459 // The tree must have the same number of nodes
461
462 // The tree must have n-1 arcs
463 ASSERT_EQ(tree.get_num_arcs(), g.get_num_nodes() - 1);
464}
465
466// ========== TEST 21: Extract Min Spanning Tree ==========
468
469 GT g;
470 Node* n0 = g.insert_node(0);
471 Node* n1 = g.insert_node(1);
472 Node* n2 = g.insert_node(2);
473 Node* n3 = g.insert_node(3);
474
475 g.insert_arc(n0, n1, 1);
476 g.insert_arc(n0, n2, 4);
477 g.insert_arc(n1, n2, 2);
478 g.insert_arc(n1, n3, 5);
479 g.insert_arc(n2, n3, 1);
480
482 bf.paint_spanning_tree(n0);
483
484 DynArray<Arc*> tree_arcs = bf.extract_min_spanning_tree();
485
486 // There must be n-1 arcs (some entries may be nullptr)
487 int non_null_arcs = 0;
488 for (size_t i = 0; i < tree_arcs.size(); ++i) {
489 if (tree_arcs(i) != nullptr) {
491 }
492 }
493
494 ASSERT_EQ(non_null_arcs, 3); // n-1 for 4 nodes
495}
496
497// ========== TEST 22: Compute Nodes Weights (Johnson) ==========
499
500 GT g;
501 Node* n0 = g.insert_node(0);
502 Node* n1 = g.insert_node(1);
503 Node* n2 = g.insert_node(2);
504
505 g.insert_arc(n0, n1, 1);
506 g.insert_arc(n1, n2, 2);
507 g.insert_arc(n2, n0, 3);
508
510
511 auto weights = bf.compute_nodes_weights();
512
513 // There must be one weight for each node
515}
516
517// ========== TEST 23: Compute Nodes Weights with Negative Cycle ==========
519
520 GT g;
521 Node* n0 = g.insert_node(0);
522 Node* n1 = g.insert_node(1);
523 Node* n2 = g.insert_node(2);
524
525 g.insert_arc(n0, n1, 1);
526 g.insert_arc(n1, n2, -3);
527 g.insert_arc(n2, n0, 1); // Negative cycle
528
530
531 bool caught_exception = false;
532 try {
533 bf.compute_nodes_weights();
534 } catch (const std::domain_error&) {
535 caught_exception = true;
536 }
537
539}
540
541// ========== TEST 24: has_negative_cycle Without Start Node ==========
543
544 GT g;
545 Node* n0 = g.insert_node(0);
546 Node* n1 = g.insert_node(1);
547 Node* n2 = g.insert_node(2);
548
549 g.insert_arc(n0, n1, 1);
550 g.insert_arc(n1, n2, -3);
551 g.insert_arc(n2, n0, 1); // Negative cycle
552
554 bool has_cycle = bf.has_negative_cycle();
555
557}
558
559// ========== TEST 25: test_negative_cycle Without Parameters ==========
561
562 GT g;
563 Node* n0 = g.insert_node(0);
564 Node* n1 = g.insert_node(1);
565 Node* n2 = g.insert_node(2);
566
567 g.insert_arc(n0, n1, 1);
568 g.insert_arc(n1, n2, -3);
569 g.insert_arc(n2, n0, 1);
570
573 const bool has_cycle = bf.test_negative_cycle(cycle);
574
577}
578
579// ========== TEST 26: get_min_path - CRITICAL MISSING TEST ==========
581
582 GT g;
583 Node* n0 = g.insert_node(0);
584 Node* n1 = g.insert_node(1);
585 Node* n2 = g.insert_node(2);
586 Node* n3 = g.insert_node(3);
587
588 g.insert_arc(n0, n1, 1);
589 g.insert_arc(n0, n2, 4);
590 g.insert_arc(n1, n2, 2);
591 g.insert_arc(n1, n3, 5);
592 g.insert_arc(n2, n3, 1);
593
595 bf.paint_spanning_tree(n0);
596
597 // Test path to n3 (should be: n0->n1->n2->n3 with cost 4)
598 Path<GT> path(g);
599 int distance = bf.get_min_path(n3, path);
600
601 ASSERT_EQ(distance, 4);
602 ASSERT_FALSE(path.is_empty());
603
604 // Verify path contains correct nodes
605 size_t path_length = 0;
606 for (typename Path<GT>::Iterator it(path); it.has_current_node(); it.next_ne())
607 path_length++;
608
609 ASSERT_EQ(path_length, 4); // n0, n1, n2, n3
610}
611
612// ========== TEST 27: get_min_path to All Nodes ==========
614
615 GT g;
616 Node* n0 = g.insert_node(0);
617 Node* n1 = g.insert_node(1);
618 Node* n2 = g.insert_node(2);
619
620 g.insert_arc(n0, n1, 5);
621 g.insert_arc(n0, n2, 10);
622 g.insert_arc(n1, n2, 3);
623
625 bf.paint_spanning_tree(n0);
626
627 // Path to n1
628 Path<GT> path1(g);
629 int dist1 = bf.get_min_path(n1, path1);
630 ASSERT_EQ(dist1, 5);
631
632 // Path to n2 (via n1: 5+3=8, better than direct 10)
633 Path<GT> path2(g);
634 int dist2 = bf.get_min_path(n2, path2);
635 ASSERT_EQ(dist2, 8);
636}
637
638// ========== TEST 28: extract_min_spanning_tree Validation ==========
640
641 GT g;
642 Node* n0 = g.insert_node(0);
643 Node* n1 = g.insert_node(1);
644 Node* n2 = g.insert_node(2);
645
646 [[maybe_unused]] Arc* a01 = g.insert_arc(n0, n1, 1);
647 [[maybe_unused]] Arc* a02 = g.insert_arc(n0, n2, 5);
648 [[maybe_unused]] Arc* a12 = g.insert_arc(n1, n2, 2);
649
651 bf.paint_spanning_tree(n0);
652
653 auto arcs = bf.extract_min_spanning_tree();
654
655 // Verify arc count (should be n-1 = 2 arcs)
656 size_t valid_arcs = 0;
657 for (size_t i = 0; i < arcs.size(); ++i)
658 if (arcs(i) != nullptr)
659 valid_arcs++;
660
662
663 // Verify arcs are actually from the tree (a01 and a12, not a02)
664 bool found_a01 = false;
665 bool found_a12 = false;
666 for (size_t i = 0; i < arcs.size(); ++i) {
667 if (arcs(i) == a01) found_a01 = true;
668 if (arcs(i) == a12) found_a12 = true;
669 }
670
673}
674
675// ========== TEST 29: build_tree with with_map=true ==========
677
678 GT g;
679 Node* n0 = g.insert_node(0);
680 Node* n1 = g.insert_node(1);
681 Node* n2 = g.insert_node(2);
682
683 g.insert_arc(n0, n1, 1);
684 g.insert_arc(n0, n2, 5);
685 g.insert_arc(n1, n2, 2);
686
688 bf.paint_spanning_tree(n0);
689
690 GT tree;
691 bf.build_tree(tree, true); // with_map = true
692
693 // Verify tree has same number of nodes
694 ASSERT_EQ(tree.get_num_nodes(), 3);
695
696 // Verify tree has n-1 arcs
697 ASSERT_EQ(tree.get_num_arcs(), 2);
698
699 // Verify nodes have correct info
700 bool found_0 = false, found_1 = false, found_2 = false;
701 for (typename GT::Node_Iterator it(tree); it.has_curr(); it.next()) {
702 auto node = it.get_curr();
703 int info = node->get_info();
704 if (info == 0) found_0 = true;
705 if (info == 1) found_1 = true;
706 if (info == 2) found_2 = true;
707 }
708
712}
713
714// ========== TEST 30: compute_nodes_weights Values Validation ==========
716
717 GT g;
718 Node* n0 = g.insert_node(0);
719 Node* n1 = g.insert_node(1);
720 Node* n2 = g.insert_node(2);
721
722 g.insert_arc(n0, n1, 5);
723 g.insert_arc(n1, n2, 3);
724 g.insert_arc(n0, n2, 10);
725
727 auto weights = bf.compute_nodes_weights();
728
729 ASSERT_EQ(weights.size(), 3);
730
731 // All weights should be 0 (Johnson's algorithm uses these to reweight)
732 // The actual values depend on the dummy node distances
733 for (auto it = weights.get_it(); it.has_curr(); it.next()) {
734 auto & pair = it.get_curr();
735 // Weight should be >= 0 after Johnson preprocessing
736 ASSERT_GE(pair.second, std::numeric_limits<int>::min());
737 }
738}
739
740// ========== TEST 31: Bellman_Ford_Negative_Cycle Functor (variant 1) ==========
742
743 GT g;
744 Node* n0 = g.insert_node(0);
745 Node* n1 = g.insert_node(1);
746 Node* n2 = g.insert_node(2);
747
748 g.insert_arc(n0, n1, 1);
749 g.insert_arc(n1, n2, -3);
750 g.insert_arc(n2, n0, 1); // Negative cycle
751
752 Path<GT> path(g);
754
755 bool has_cycle = detector(g, path);
756
758 ASSERT_FALSE(path.is_empty());
759}
760
761// ========== TEST 32: Bellman_Ford_Negative_Cycle Functor (variant 2 with start node) ==========
763
764 GT g;
765 Node* n0 = g.insert_node(0);
766 Node* n1 = g.insert_node(1);
767 Node* n2 = g.insert_node(2);
768
769 g.insert_arc(n0, n1, 1);
770 g.insert_arc(n1, n2, -3);
771 g.insert_arc(n2, n0, 1);
772
773 Path<GT> path(g);
775
776 bool has_cycle = detector(g, n0, path);
777
779 ASSERT_FALSE(path.is_empty());
780}
781
782// ========== TEST 33: search_negative_cycle with different it_factor ==========
784
785 GT g;
786 Node* n0 = g.insert_node(0);
787 Node* n1 = g.insert_node(1);
788 Node* n2 = g.insert_node(2);
789
790 g.insert_arc(n0, n1, 1);
791 g.insert_arc(n1, n2, -3);
792 g.insert_arc(n2, n0, 1);
793
795
796 // Test with it_factor = 0.1
797 auto [path1, iter1] = bf.search_negative_cycle(n0, 0.1, 1);
799 ASSERT_GT(iter1, 0u);
800
801 // Test with it_factor = 0.9
802 auto [path2, iter2] = bf.search_negative_cycle(n0, 0.9, 1);
804 ASSERT_GT(iter2, 0u);
805}
806
807// ========== TEST 34: Empty Graph Edge Case ==========
809
810 GT g;
812
813 bool has_cycle = bf.has_negative_cycle();
815}
816
817// ========== TEST 35: Large Graph Performance Test ==========
819
820 GT g;
821 const size_t N = 100;
822
823 // Create nodes
824 std::vector<Node*> nodes;
825 for (size_t i = 0; i < N; ++i)
826 nodes.push_back(g.insert_node(i));
827
828 // Create chain of arcs
829 for (size_t i = 0; i < N - 1; ++i)
830 g.insert_arc(nodes[i], nodes[i + 1], 1);
831
833 bool negative_cycle = bf.paint_spanning_tree(nodes[0]);
834
835 ASSERT_FALSE(negative_cycle);
836
837 // Verify path to last node
838 Path<GT> path(g);
839 int distance = bf.get_min_path(nodes[N - 1], path);
840
841 ASSERT_EQ(distance, N - 1);
842}
843
844// ========== TEST 36: State Getters ==========
846
847 GT g;
848 Node* n0 = g.insert_node(0);
849 Node* n1 = g.insert_node(1);
850 g.insert_arc(n0, n1, 1);
851
853
854 // Before painting
855 ASSERT_FALSE(bf.is_painted());
856 ASSERT_FALSE(bf.has_computation());
857 ASSERT_EQ(bf.get_start_node(), nullptr);
858 ASSERT_EQ(&bf.get_graph(), &g);
859
860 // After painting
861 bf.paint_spanning_tree(n0);
862 ASSERT_TRUE(bf.is_painted());
863 ASSERT_TRUE(bf.has_computation());
864 ASSERT_EQ(bf.get_start_node(), n0);
865}
866
867// ========== TEST 37: get_min_path without Painting ==========
869
870 GT g;
871 Node* n0 = g.insert_node(0);
872 Node* n1 = g.insert_node(1);
873 g.insert_arc(n0, n1, 1);
874
876
877 Path<GT> path(g);
878 bool caught = false;
879 try {
880 bf.get_min_path(n1, path);
881 } catch (const std::domain_error&) {
882 caught = true;
883 }
885}
886
887// ========== TEST 38: Negative Cycle Path Validity ==========
889
890 GT g;
891 Node* n0 = g.insert_node(0);
892 Node* n1 = g.insert_node(1);
893 Node* n2 = g.insert_node(2);
894
895 g.insert_arc(n0, n1, 1);
896 g.insert_arc(n1, n2, -2);
897 g.insert_arc(n2, n0, -1); // Total: -2 (negative cycle)
898
900 Path<GT> cycle = bf.test_negative_cycle(n0);
901
903
904 // Verify cycle properties:
905 // 1. Has at least 2 nodes (for a cycle)
906 size_t cycle_length = 0;
907 for (typename Path<GT>::Iterator it(cycle); it.has_current_node(); it.next_ne())
908 cycle_length++;
909
911}
912
913// ========== TEST 39: Single Node with Self-loop (Positive) ==========
915
916 GT g;
917 Node* n0 = g.insert_node(0);
918 g.insert_arc(n0, n0, 1); // Positive self-loop
919
921 bool has_neg_cycle = bf.has_negative_cycle(n0);
922
924}
925
926// ========== TEST 40: Faster Version with Negative Cycle ==========
928
929 GT g;
930 Node* n0 = g.insert_node(0);
931 Node* n1 = g.insert_node(1);
932 Node* n2 = g.insert_node(2);
933
934 g.insert_arc(n0, n1, 1);
935 g.insert_arc(n1, n2, -3);
936 g.insert_arc(n2, n0, 1);
937
939 bool has_neg_cycle = bf.faster_paint_spanning_tree(n0);
940
942}
943
944// ========== TEST 41: Search Negative Cycle (Double Overload) ==========
946
947 GT g;
948 Node* n0 = g.insert_node(0);
949 Node* n1 = g.insert_node(1);
950 Node* n2 = g.insert_node(2);
951
952 g.insert_arc(n0, n1, 1);
953 g.insert_arc(n1, n2, -3);
954 g.insert_arc(n2, n0, 1);
955
957
958 // search_negative_cycle(start, it_factor, step)
959 auto [path, iter] = bf.search_negative_cycle(n0, 0.5, 1);
960
961 ASSERT_FALSE(path.is_empty());
962}
963
964// ========== TEST 42: search_negative_cycle (No Params Overload) ==========
966
967 GT g;
968 [[maybe_unused]] Node* n0 = g.insert_node(0);
969 Node* n1 = g.insert_node(1);
970 Node* n2 = g.insert_node(2);
971
972 // Disconnected negative cycle
973 g.insert_arc(n1, n2, -2);
974 g.insert_arc(n2, n1, -1);
975
977
978 // Using the overload without start node (uses dummy node)
979 Path<GT> path = bf.search_negative_cycle();
980
981 ASSERT_FALSE(path.is_empty());
982}
983
984// ========== TEST 43: Build Tree Without Prior Painting ==========
986
987 GT g;
988 Node* n0 = g.insert_node(0);
989 Node* n1 = g.insert_node(1);
990 g.insert_arc(n0, n1, 1);
991
993
994 GT tree;
995 bool caught = false;
996 try {
997 bf.build_tree(tree, true); // with_map = true requires painting
998 } catch (const std::domain_error&) {
999 caught = true;
1000 }
1002}
1003
1004// ========== TEST 44: Multiple Calls to paint_spanning_tree ==========
1006
1007 GT g;
1008 Node* n0 = g.insert_node(0);
1009 Node* n1 = g.insert_node(1);
1010 Node* n2 = g.insert_node(2);
1011
1012 g.insert_arc(n0, n1, 1);
1013 g.insert_arc(n0, n2, 10);
1014 g.insert_arc(n1, n2, 2);
1015
1017
1018 // First call
1019 bf.paint_spanning_tree(n0);
1020 ASSERT_TRUE(bf.is_painted());
1021 ASSERT_EQ(bf.get_start_node(), n0);
1022
1023 Path<GT> path1(g);
1024 int dist1 = bf.get_min_path(n2, path1);
1025 ASSERT_EQ(dist1, 3);
1026
1027 // Can we paint again from a different node? (fresh graph state)
1028 GT g2;
1029 Node* m0 = g2.insert_node(0);
1030 Node* m1 = g2.insert_node(1);
1031 g2.insert_arc(m0, m1, 5);
1032
1034 bf2.paint_spanning_tree(m0);
1035 ASSERT_TRUE(bf2.is_painted());
1036
1037 Path<GT> path2(g2);
1038 int dist2 = bf2.get_min_path(m1, path2);
1039 ASSERT_EQ(dist2, 5);
1040}
1041
1042// ========== TEST 45: Functor With Different Signatures ==========
1044
1045 GT g;
1046 Node* n0 = g.insert_node(0);
1047 Node* n1 = g.insert_node(1);
1048 Node* n2 = g.insert_node(2);
1049
1050 g.insert_arc(n0, n1, 1);
1051 g.insert_arc(n1, n2, -3);
1052 g.insert_arc(n2, n0, 1);
1053
1055
1056 // Signature: (g, s, d, sa) -> Path
1057 Dft_Dist<GT> dist;
1059 Path<GT> path1 = detector(g, n0, dist, sa);
1061
1062 // Signature: (g, d, sa) -> Path
1063 Path<GT> path2 = detector(g, dist, sa);
1065
1066 // Signature: (g) -> Path with default args
1067 Path<GT> path3 = detector(g);
1069}
1070
1071// ========== TEST 46: Strongly Connected Component with Negative Cycle ==========
1073
1074 GT g;
1075 Node* n0 = g.insert_node(0);
1076 Node* n1 = g.insert_node(1);
1077 Node* n2 = g.insert_node(2);
1078 Node* n3 = g.insert_node(3);
1079
1080 // Connect all nodes strongly
1081 g.insert_arc(n0, n1, 1);
1082 g.insert_arc(n1, n2, 1);
1083 g.insert_arc(n2, n3, 1);
1084 g.insert_arc(n3, n0, 1);
1085
1086 // Add negative cycle within
1087 g.insert_arc(n1, n3, -5);
1088 g.insert_arc(n3, n1, 1);
1089
1091 bool has_neg = bf.has_negative_cycle(n0);
1092
1094}
1095
1096// ========== TEST 47: Very Long Chain Performance ==========
1098
1099 GT g;
1100 const size_t N = 500;
1101
1102 std::vector<Node*> nodes;
1103 for (size_t i = 0; i < N; ++i)
1104 nodes.push_back(g.insert_node(static_cast<int>(i)));
1105
1106 for (size_t i = 0; i < N - 1; ++i)
1107 g.insert_arc(nodes[i], nodes[i + 1], 1);
1108
1110 bool neg_cycle = bf.paint_spanning_tree(nodes[0]);
1111
1113
1114 Path<GT> path(g);
1115 int dist = bf.get_min_path(nodes[N - 1], path);
1116
1117 ASSERT_EQ(dist, static_cast<int>(N - 1));
1118}
1119
1120// ========== TEST 48: Bidirectional Edges ==========
1122
1123 GT g;
1124 Node* n0 = g.insert_node(0);
1125 Node* n1 = g.insert_node(1);
1126
1127 g.insert_arc(n0, n1, 3);
1128 g.insert_arc(n1, n0, 5);
1129
1131 bool neg_cycle = bf.paint_spanning_tree(n0);
1132
1134
1135 Path<GT> path(g);
1136 int dist = bf.get_min_path(n1, path);
1137
1138 ASSERT_EQ(dist, 3);
1139}
1140
1141// ========== TEST 49: Unreachable Node ==========
1143
1144 GT g;
1145 Node* n0 = g.insert_node(0);
1146 Node* n1 = g.insert_node(1);
1147 Node* n2 = g.insert_node(2); // Not connected
1148
1149 g.insert_arc(n0, n1, 1);
1150
1152 bf.paint_spanning_tree(n0);
1153
1154 // Node n2 is unreachable - get_min_path should throw
1155 Path<GT> path(g);
1156 bool caught = false;
1157 try {
1158 bf.get_min_path(n2, path);
1159 } catch (const std::domain_error&) {
1160 caught = true;
1161 }
1162
1164}
1165
1166// ========== TEST 50: Dense Graph ==========
1168
1169 GT g;
1170 const size_t N = 20;
1171
1172 std::vector<Node*> nodes;
1173 for (size_t i = 0; i < N; ++i)
1174 nodes.push_back(g.insert_node(static_cast<int>(i)));
1175
1176 // Create dense graph (almost complete)
1177 for (size_t i = 0; i < N; ++i)
1178 for (size_t j = 0; j < N; ++j)
1179 if (i != j)
1180 g.insert_arc(nodes[i], nodes[j], static_cast<int>(i + j));
1181
1183 bool neg_cycle = bf.paint_spanning_tree(nodes[0]);
1184
1186}
1187
1188// ========== TEST 51: get_distance Method ==========
1190
1191 GT g;
1192 Node* n0 = g.insert_node(0);
1193 Node* n1 = g.insert_node(1);
1194 Node* n2 = g.insert_node(2);
1195 Node* n3 = g.insert_node(3);
1196
1197 g.insert_arc(n0, n1, 1);
1198 g.insert_arc(n1, n2, 2);
1199 g.insert_arc(n2, n3, 3);
1200
1202 bf.paint_spanning_tree(n0);
1203
1204 // Distance to n3 should be 1+2+3 = 6
1205 int dist_n3 = bf.get_distance(n3);
1206 ASSERT_EQ(dist_n3, 6);
1207
1208 // Distance to n1 should be 1
1209 int dist_n1 = bf.get_distance(n1);
1210 ASSERT_EQ(dist_n1, 1);
1211
1212 // Distance to start node should be 0
1213 int dist_n0 = bf.get_distance(n0);
1214 ASSERT_EQ(dist_n0, 0);
1215}
1216
1217// ========== TEST 52: get_distance Without Painting ==========
1219
1220 GT g;
1221 Node* n0 = g.insert_node(0);
1222 Node* n1 = g.insert_node(1);
1223 g.insert_arc(n0, n1, 1);
1224
1226
1227 bool caught = false;
1228 try {
1229 bf.get_distance(n1);
1230 } catch (const std::domain_error&) {
1231 caught = true;
1232 }
1234}
1235
1236// ========== TEST 53: get_distance Unreachable Node ==========
1238
1239 GT g;
1240 Node* n0 = g.insert_node(0);
1241 Node* n1 = g.insert_node(1);
1242 Node* n2 = g.insert_node(2); // Not connected
1243
1244 g.insert_arc(n0, n1, 1);
1245
1247 bf.paint_spanning_tree(n0);
1248
1249 bool caught = false;
1250 try {
1251 bf.get_distance(n2);
1252 } catch (const std::domain_error&) {
1253 caught = true;
1254 }
1256}
1257
1258// ========== TEST 54: extract_min_spanning_tree Without Painting ==========
1260
1261 GT g;
1262 Node* n0 = g.insert_node(0);
1263 Node* n1 = g.insert_node(1);
1264 g.insert_arc(n0, n1, 1);
1265
1267
1268 bool caught = false;
1269 try {
1270 bf.extract_min_spanning_tree();
1271 } catch (const std::domain_error&) {
1272 caught = true;
1273 }
1275}
1276
1277// ========== TEST 55: get_distance with Null Node ==========
1279
1280 GT g;
1281 Node* n0 = g.insert_node(0);
1282 Node* n1 = g.insert_node(1);
1283 g.insert_arc(n0, n1, 1);
1284
1286 bf.paint_spanning_tree(n0);
1287
1288 bool caught = false;
1289 try {
1290 bf.get_distance(nullptr);
1291 } catch (const std::domain_error&) {
1292 caught = true;
1293 }
1295}
1296
1297// ========== TEST 56: get_distance vs get_min_path Consistency ==========
1299
1300 GT g;
1301 Node* n0 = g.insert_node(0);
1302 Node* n1 = g.insert_node(1);
1303 Node* n2 = g.insert_node(2);
1304 Node* n3 = g.insert_node(3);
1305
1306 g.insert_arc(n0, n1, 5);
1307 g.insert_arc(n0, n2, 10);
1308 g.insert_arc(n1, n2, 2);
1309 g.insert_arc(n2, n3, 3);
1310
1312 bf.paint_spanning_tree(n0);
1313
1314 // Verify that get_distance returns the same value as get_min_path
1315 for (Node* target : {n1, n2, n3}) {
1316 Path<GT> path(g);
1317 int path_dist = bf.get_min_path(target, path);
1318 int direct_dist = bf.get_distance(target);
1320 }
1321}
1322
1323// ========== TEST: Parallel Arcs (Multigraph) ==========
1324// Test that Bellman-Ford correctly handles graphs with multiple arcs between same nodes
1326 GT g;
1327 Node* n0 = g.insert_node(0);
1328 Node* n1 = g.insert_node(1);
1329 Node* n2 = g.insert_node(2);
1330
1331 // Multiple arcs between n0 and n1 with different weights
1332 g.insert_arc(n0, n1, 10); // expensive
1333 g.insert_arc(n0, n1, 3); // cheap - should be chosen
1334 g.insert_arc(n0, n1, 7); // medium
1335
1336 // Single arc to destination
1337 g.insert_arc(n1, n2, 2);
1338
1339 // Check if graph supports parallel arcs
1340 if (g.get_num_arcs() < 4) {
1341 GTEST_SKIP() << "Graph type does not support parallel arcs (multigraph)";
1342 }
1343
1345 bool has_neg = bf.paint_spanning_tree(n0);
1347
1348 Path<GT> path(g);
1349 int d = bf.get_min_path(n2, path);
1350
1351 // Shortest path should use the 3 arc: 3 + 2 = 5
1352 EXPECT_EQ(d, 5);
1353}
1354
1355// ========== TEST: Parallel Arcs with Negative Weights ==========
1357 GT g;
1358 Node* n0 = g.insert_node(0);
1359 Node* n1 = g.insert_node(1);
1360 Node* n2 = g.insert_node(2);
1361
1362 // Multiple arcs including negative
1363 g.insert_arc(n0, n1, 5);
1364 g.insert_arc(n0, n1, -2); // Best (negative)
1365 g.insert_arc(n1, n2, 3);
1366
1367 // Check if graph supports parallel arcs
1368 if (g.get_num_arcs() < 3) {
1369 GTEST_SKIP() << "Graph type does not support parallel arcs (multigraph)";
1370 }
1371
1373 bool has_neg = bf.paint_spanning_tree(n0);
1374 EXPECT_FALSE(has_neg); // No negative cycle
1375
1376 Path<GT> path(g);
1377 int d = bf.get_min_path(n2, path);
1378
1379 // Best path: -2 + 3 = 1
1380 EXPECT_EQ(d, 1);
1381}
1382
1383// ========== TEST: Complex Multigraph ==========
1385 GT g;
1386 Node* n0 = g.insert_node(0);
1387 Node* n1 = g.insert_node(1);
1388 Node* n2 = g.insert_node(2);
1389 Node* n3 = g.insert_node(3);
1390
1391 // Multiple paths with parallel arcs
1392 g.insert_arc(n0, n1, 4);
1393 g.insert_arc(n0, n1, 2); // Best n0->n1
1394 g.insert_arc(n1, n2, 3);
1395 g.insert_arc(n1, n2, 1); // Best n1->n2
1396 g.insert_arc(n2, n3, 5);
1397 g.insert_arc(n2, n3, 2); // Best n2->n3
1398
1399 // Alternative direct path
1400 g.insert_arc(n0, n3, 10);
1401
1402 // Check if graph supports parallel arcs
1403 if (g.get_num_arcs() < 7) {
1404 GTEST_SKIP() << "Graph type does not support parallel arcs (multigraph)";
1405 }
1406
1408 bool has_neg = bf.paint_spanning_tree(n0);
1410
1411 Path<GT> path(g);
1412 int d = bf.get_min_path(n3, path);
1413
1414 // Best path: 2 + 1 + 2 = 5
1415 EXPECT_EQ(d, 5);
1416}
1417
1418// ========== TEST: Parallel Arcs Creating Negative Cycle ==========
1420 GT g;
1421 Node* n0 = g.insert_node(0);
1422 Node* n1 = g.insert_node(1);
1423
1424 // Parallel arcs that form a negative cycle in undirected interpretation
1425 g.insert_arc(n0, n1, 1);
1426 g.insert_arc(n1, n0, -5); // Creates negative cycle: 1 + (-5) = -4
1427
1428 // Check if graph supports parallel arcs
1429 if (g.get_num_arcs() < 2) {
1430 GTEST_SKIP() << "Graph type does not support parallel arcs (multigraph)";
1431 }
1432
1434 bool has_neg = bf.paint_spanning_tree(n0);
1435
1436 // Should detect negative cycle
1438}
1439
1440// ========== GoogleTest main ==========
1441int main(int argc, char **argv)
1442{
1443 ::testing::InitGoogleTest(&argc, argv);
1444 return RUN_ALL_TESTS();
1445}
Bellman-Ford algorithm for single-source shortest paths.
int main()
WeightedDigraph::Node Node
WeightedDigraph::Arc Arc
int num_nodes
Definition btreepic.C:410
Bellman-Ford algorithm for shortest paths with negative weights.
Default distance accessor for arc weights.
Generic directed graph (digraph) wrapper template.
Definition graph-dry.H:3848
typename BaseGraph::Arc Arc
Definition graph-dry.H:3852
typename BaseGraph::Node Node
Definition graph-dry.H:3851
size_t size() const noexcept
Return the current dimension of array.
void next()
Advances the iterator to the next filtered element.
constexpr bool is_empty() const noexcept
Return true if list is empty.
Definition htlist.H:523
size_t size() const noexcept
Count the number of elements of the list.
Definition htlist.H:1319
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
Iterator on nodes and arcs of a path.
Definition tpl_graph.H:3201
bool has_current_node() const noexcept
Return true if the iterator has a current node.
Definition tpl_graph.H:3314
Path on a graph.
Definition tpl_graph.H:2669
bool is_empty() const noexcept
Return true if the path is empty.
Definition tpl_graph.H:2813
constexpr size_t get_num_nodes() const noexcept
Return the total of nodes of graph.
Definition graph-dry.H:695
constexpr size_t get_num_arcs() const noexcept
Definition graph-dry.H:778
auto get_it() const
Return a properly initialized iterator positioned at the first item on the container.
Definition ah-dry.H:190
#define TEST(name)
#define N
Definition fib.C:294
DynArray< Graph::Node * > nodes
Definition graphpic.C:406
DynArray< Graph::Arc * > arcs
Definition graphpic.C:408
bool has_cycle(const GT &g)
Return true if an undirected graph has at least one cycle.
#define IS_ARC_VISITED(p, bit)
Determine whether the bit field is or not set to one.
@ Spanning_Tree
Definition aleph-graph.H:79
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
std::pair< First, Second > pair
Alias to std::pair kept for backwards compatibility.
Definition ahPair.H:89
DynList< T > maps(const C &c, Op op)
Classic map operation.
Filtered iterator on all the arcs of a graph.
Definition tpl_graph.H:1164
Detects if a negative cycle exists and eventually computes it.
Default filter for filtered iterators on arcs.
Definition tpl_graph.H:1000
Arc of graph implemented with double-linked adjacency lists.
Definition tpl_graph.H:222
Generic graph and digraph implementations.