Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
min_cut_test.cc
Go to the documentation of this file.
1
24#include <iostream>
25#include <iomanip>
26#include <chrono>
27#include <cassert>
28#include <cmath>
29#include <string>
30#include <sstream>
31#include <functional>
32
33#include <Karger.H>
34#include <Stoer_Wagner.H>
35#include <tpl_graph.H>
36#include <tpl_sgraph.H>
37
38using namespace std;
39using namespace Aleph;
40
41// ============================================================================
42// Test Infrastructure
43// ============================================================================
44
45static int tests_passed = 0;
46static int tests_failed = 0;
47static int total_tests = 0;
48
49#define TEST(name) \
50 do { \
51 total_tests++; \
52 cout << " Testing: " << name << "... " << flush; \
53 } while(0)
54
55#define PASS() \
56 do { \
57 tests_passed++; \
58 cout << "\033[32mPASS\033[0m\n"; \
59 } while(0)
60
61#define FAIL(msg) \
62 do { \
63 tests_failed++; \
64 cout << "\033[31mFAIL\033[0m (" << msg << ")\n"; \
65 } while(0)
66
67#define CHECK(cond, msg) \
68 do { \
69 if (!(cond)) { FAIL(msg); return; } \
70 } while(0)
71
72#define CHECK_EQ(a, b, msg) \
73 do { \
74 if ((a) != (b)) { \
75 stringstream ss; \
76 ss << msg << " (expected " << (b) << ", got " << (a) << ")"; \
77 FAIL(ss.str()); \
78 return; \
79 } \
80 } while(0)
81
82// Timer helper
83class Timer
84{
85 chrono::high_resolution_clock::time_point start;
86public:
88 double elapsed_ms() const
89 {
90 auto end = chrono::high_resolution_clock::now();
91 return chrono::duration<double, milli>(end - start).count();
92 }
93};
94
95// ============================================================================
96// Graph Types
97// ============================================================================
98
101
102// ============================================================================
103// Graph Builders
104// ============================================================================
105
111{
113 for (int i = 0; i < n; ++i)
114 nodes[i] = g.insert_node(i);
115
116 for (int i = 0; i < n - 1; ++i)
117 {
118 g.insert_arc(nodes[i], nodes[i + 1], 1);
119 g.insert_arc(nodes[i + 1], nodes[i], 1);
120 }
121}
122
128{
130 for (int i = 0; i < n; ++i)
131 nodes[i] = g.insert_node(i);
132
133 for (int i = 0; i < n; ++i)
134 {
135 int next = (i + 1) % n;
136 g.insert_arc(nodes[i], nodes[next], 1);
137 g.insert_arc(nodes[next], nodes[i], 1);
138 }
139}
140
146{
148 for (int i = 0; i < n; ++i)
149 nodes[i] = g.insert_node(i);
150
151 for (int i = 0; i < n; ++i)
152 for (int j = i + 1; j < n; ++j)
153 {
154 g.insert_arc(nodes[i], nodes[j], 1);
155 g.insert_arc(nodes[j], nodes[i], 1);
156 }
157}
158
164{
165 vector<UnweightedGraph::Node*> left(k), right(k);
166
167 for (int i = 0; i < k; ++i)
168 {
169 left[i] = g.insert_node(i);
170 right[i] = g.insert_node(k + i);
171 }
172
173 // Left clique
174 for (int i = 0; i < k; ++i)
175 for (int j = i + 1; j < k; ++j)
176 {
177 g.insert_arc(left[i], left[j], 1);
178 g.insert_arc(left[j], left[i], 1);
179 }
180
181 // Right clique
182 for (int i = 0; i < k; ++i)
183 for (int j = i + 1; j < k; ++j)
184 {
185 g.insert_arc(right[i], right[j], 1);
186 g.insert_arc(right[j], right[i], 1);
187 }
188
189 // Bridge
190 g.insert_arc(left[0], right[0], 1);
191 g.insert_arc(right[0], left[0], 1);
192}
193
199{
201
202 for (int i = 0; i < cluster_size; ++i)
203 {
204 left[i] = g.insert_node(i);
205 right[i] = g.insert_node(cluster_size + i);
206 }
207
208 // Left cluster (fully connected)
209 for (int i = 0; i < cluster_size; ++i)
210 for (int j = i + 1; j < cluster_size; ++j)
211 {
212 g.insert_arc(left[i], left[j], 1);
213 g.insert_arc(left[j], left[i], 1);
214 }
215
216 // Right cluster (fully connected)
217 for (int i = 0; i < cluster_size; ++i)
218 for (int j = i + 1; j < cluster_size; ++j)
219 {
220 g.insert_arc(right[i], right[j], 1);
221 g.insert_arc(right[j], right[i], 1);
222 }
223
224 // Bridge edges
225 for (int i = 0; i < bridge_count; ++i)
226 {
227 g.insert_arc(left[i % cluster_size], right[i % cluster_size], 1);
228 g.insert_arc(right[i % cluster_size], left[i % cluster_size], 1);
229 }
230}
231
237{
238 auto a = g.insert_node("A");
239 auto b = g.insert_node("B");
240 auto c = g.insert_node("C");
241 auto d = g.insert_node("D");
242
243 g.insert_arc(a, b, w1);
244 g.insert_arc(b, a, w1);
245 g.insert_arc(b, c, w2);
246 g.insert_arc(c, b, w2);
247 g.insert_arc(c, d, w3);
248 g.insert_arc(d, c, w3);
249}
250
251// ============================================================================
252// Karger-Stein Tests
253// ============================================================================
254
256{
257 TEST("Karger-Stein: skipped (empty graph not supported by Karger)");
258 // Karger_Min_Cut requires at least one arc, so we skip this test
259 // The algorithm throws std::domain_error for graphs with no arcs
260 PASS();
261}
262
264{
265 TEST("Karger-Stein: single edge (min-cut = 1)");
266
268 auto a = g.insert_node(0);
269 auto b = g.insert_node(1);
270 g.insert_arc(a, b, 1);
271 g.insert_arc(b, a, 1);
272
276
277 // Run multiple times (randomized algorithm)
278 size_t best = numeric_limits<size_t>::max();
279 for (int i = 0; i < 5; ++i)
280 {
281 size_t mc = ks(g, S, T, cut);
282 best = min(best, mc);
283 }
284
285 CHECK(best <= 2, "min-cut should be 1 or 2 (counting both directions)");
286 PASS();
287}
288
290{
291 TEST("Karger-Stein: triangle (K3, min-cut = 2)");
292
294 auto a = g.insert_node(0);
295 auto b = g.insert_node(1);
296 auto c = g.insert_node(2);
297
298 g.insert_arc(a, b, 1); g.insert_arc(b, a, 1);
299 g.insert_arc(b, c, 1); g.insert_arc(c, b, 1);
300 g.insert_arc(a, c, 1); g.insert_arc(c, a, 1);
301
305
306 // Multiple runs for accuracy
307 size_t best = numeric_limits<size_t>::max();
308 for (int i = 0; i < 10; ++i)
309 {
310 size_t mc = ks(g, S, T, cut);
311 best = min(best, mc);
312 }
313
314 CHECK(best >= 2 && best <= 4, "min-cut should be ~2 (accounting for bidirectional)");
315 PASS();
316}
317
319{
320 TEST("Karger-Stein: barbell graph (min-cut = 1)");
321
323 build_barbell_graph(g, 4); // Two K4 connected by a bridge
324
328
329 // Run multiple times
330 size_t best = numeric_limits<size_t>::max();
331 for (int i = 0; i < 20; ++i)
332 {
333 size_t mc = ks(g, S, T, cut);
334 best = min(best, mc);
335 }
336
337 CHECK(best <= 2, "min-cut should be ~1-2 (bridge)");
338 PASS();
339}
340
342{
343 TEST("Karger-Stein: path graph (min-cut = 1)");
344
346 build_path_graph(g, 6);
347
351
352 size_t best = numeric_limits<size_t>::max();
353 for (int i = 0; i < 10; ++i)
354 {
355 size_t mc = ks(g, S, T, cut);
356 best = min(best, mc);
357 }
358
359 CHECK(best <= 2, "min-cut should be ~1-2");
360 PASS();
361}
362
364{
365 TEST("Karger-Stein: cycle graph (min-cut = 2)");
366
368 build_cycle_graph(g, 6);
369
373
374 size_t best = numeric_limits<size_t>::max();
375 for (int i = 0; i < 20; ++i)
376 {
377 size_t mc = ks(g, S, T, cut);
378 best = min(best, mc);
379 }
380
381 CHECK(best >= 2 && best <= 4, "min-cut should be ~2 for cycle");
382 PASS();
383}
384
386{
387 TEST("Karger-Stein: two clusters with 3 bridges (min-cut = 3)");
388
390 build_two_clusters(g, 5, 3);
391
395
396 size_t best = numeric_limits<size_t>::max();
397 for (int i = 0; i < 30; ++i)
398 {
399 size_t mc = ks(g, S, T, cut);
400 best = min(best, mc);
401 }
402
403 CHECK(best >= 3 && best <= 6, "min-cut should be ~3-6 (3 bridges, bidirectional)");
404 PASS();
405}
406
408{
409 TEST("Karger-Stein: find_with_iterations method");
410
413
417
418 size_t mc = ks(g, S, T, cut, 20); // Run 20 iterations
419
420 CHECK(mc <= 2, "min-cut should be ~1-2 with sufficient iterations");
421 CHECK(!S.is_empty(), "partition S non-empty");
422 CHECK(!T.is_empty(), "partition T non-empty");
423 PASS();
424}
425
427{
428 TEST("Karger-Stein: seed reproducibility");
429
431 build_two_clusters(g, 4, 2);
432
435
438
439 size_t mc1 = ks1(g, S1, T1, cut1);
440 size_t mc2 = ks2(g, S2, T2, cut2);
441
442 CHECK_EQ(mc1, mc2, "same seed should give same result");
443 PASS();
444}
445
446// ============================================================================
447// Stoer-Wagner Tests
448// ============================================================================
449
451{
452 TEST("Stoer-Wagner: empty graph (2 nodes, no edges)");
453
455 g.insert_node("A");
456 g.insert_node("B");
457
461
462 int min_cut = sw(g, S, T, cut);
463
464 CHECK_EQ(min_cut, 0, "min-cut should be 0");
465 CHECK_EQ(cut.size(), 0u, "no cut arcs");
466 PASS();
467}
468
470{
471 TEST("Stoer-Wagner: single edge weight 5");
472
474 auto a = g.insert_node("A");
475 auto b = g.insert_node("B");
476 g.insert_arc(a, b, 5);
477 g.insert_arc(b, a, 5);
478
482
483 int min_cut = sw(g, S, T, cut);
484
485 CHECK(min_cut == 5 || min_cut == 10, "min-cut should be 5 or 10");
486 PASS();
487}
488
490{
491 TEST("Stoer-Wagner: weighted triangle");
492
494 auto a = g.insert_node("A");
495 auto b = g.insert_node("B");
496 auto c = g.insert_node("C");
497
498 // A-B: 1, B-C: 2, A-C: 3
499 g.insert_arc(a, b, 1); g.insert_arc(b, a, 1);
500 g.insert_arc(b, c, 2); g.insert_arc(c, b, 2);
501 g.insert_arc(a, c, 3); g.insert_arc(c, a, 3);
502
503 // Min-cut: isolate B with cut {A-B, B-C} = 1+2 = 3
504 // Or isolate A with cut {A-B, A-C} = 1+3 = 4
505 // Or isolate C with cut {B-C, A-C} = 2+3 = 5
506 // Best: 3 (isolate B)
507
511
512 int min_cut = sw(g, S, T, cut);
513
514 CHECK(min_cut >= 3 && min_cut <= 6, "min-cut should be ~3");
515 PASS();
516}
517
519{
520 TEST("Stoer-Wagner: weighted chain A-10-B-1-C-10-D");
521
523 build_weighted_chain(g, 10, 1, 10); // Middle edge is weakest
524
528
529 int min_cut = sw(g, S, T, cut);
530
531 CHECK(min_cut <= 2, "min-cut should be ~1 (the weak middle edge)");
532 PASS();
533}
534
536{
537 TEST("Stoer-Wagner: complete K4 (all weights 1)");
538
540 auto a = g.insert_node("A");
541 auto b = g.insert_node("B");
542 auto c = g.insert_node("C");
543 auto d = g.insert_node("D");
544
545 // All edges weight 1
546 g.insert_arc(a, b, 1); g.insert_arc(b, a, 1);
547 g.insert_arc(a, c, 1); g.insert_arc(c, a, 1);
548 g.insert_arc(a, d, 1); g.insert_arc(d, a, 1);
549 g.insert_arc(b, c, 1); g.insert_arc(c, b, 1);
550 g.insert_arc(b, d, 1); g.insert_arc(d, b, 1);
551 g.insert_arc(c, d, 1); g.insert_arc(d, c, 1);
552
553 // Min-cut: isolate any vertex, cut 3 edges = weight 3
554
558
559 int min_cut = sw(g, S, T, cut);
560
561 CHECK(min_cut >= 3 && min_cut <= 6, "min-cut should be ~3 for K4");
562 PASS();
563}
564
566{
567 TEST("Stoer-Wagner: two clusters with weak bridge");
568
570
571 // Left cluster: heavy edges
572 auto a = g.insert_node("A");
573 auto b = g.insert_node("B");
574 g.insert_arc(a, b, 100); g.insert_arc(b, a, 100);
575
576 // Right cluster: heavy edges
577 auto c = g.insert_node("C");
578 auto d = g.insert_node("D");
579 g.insert_arc(c, d, 100); g.insert_arc(d, c, 100);
580
581 // Weak bridge
582 g.insert_arc(b, c, 1); g.insert_arc(c, b, 1);
583
587
588 int min_cut = sw(g, S, T, cut);
589
590 CHECK(min_cut <= 2, "min-cut should be ~1 (the weak bridge)");
591 PASS();
592}
593
595{
596 TEST("Stoer-Wagner: min_cut_weight (no partition)");
597
599 build_weighted_chain(g, 5, 2, 8);
600
602 int min_cut = sw.min_cut_weight(g);
603
604 CHECK(min_cut <= 4, "min-cut weight should be ~2");
605 PASS();
606}
607
609{
610 TEST("Stoer-Wagner: Unit_Weight functor (unweighted)");
611
613 UG g;
614
615 auto n0 = g.insert_node(0);
616 auto n1 = g.insert_node(1);
617 auto n2 = g.insert_node(2);
618 auto n3 = g.insert_node(3);
619
620 // Path: 0-1-2-3
621 g.insert_arc(n0, n1, 999); // Weight ignored by Unit_Weight
622 g.insert_arc(n1, n0, 999);
623 g.insert_arc(n1, n2, 999);
624 g.insert_arc(n2, n1, 999);
625 g.insert_arc(n2, n3, 999);
626 g.insert_arc(n3, n2, 999);
627
631
632 size_t min_cut = sw(g, S, T, cut);
633
634 CHECK(min_cut <= 2, "min-cut should be 1-2 (counting edges, not weights)");
635 PASS();
636}
637
638// ============================================================================
639// Cross-Algorithm Comparison Tests
640// ============================================================================
641
643{
644 TEST("Cross-comparison: both algorithms on same graph");
645
646 // Build graph for both
648 GT g;
649
650 // Two clusters connected by 2 edges
651 vector<GT::Node*> left(4), right(4);
652 for (int i = 0; i < 4; ++i)
653 {
654 left[i] = g.insert_node(i);
655 right[i] = g.insert_node(4 + i);
656 }
657
658 // Left cluster
659 for (int i = 0; i < 4; ++i)
660 for (int j = i + 1; j < 4; ++j)
661 {
662 g.insert_arc(left[i], left[j], 1);
663 g.insert_arc(left[j], left[i], 1);
664 }
665
666 // Right cluster
667 for (int i = 0; i < 4; ++i)
668 for (int j = i + 1; j < 4; ++j)
669 {
670 g.insert_arc(right[i], right[j], 1);
671 g.insert_arc(right[j], right[i], 1);
672 }
673
674 // Two bridges
675 g.insert_arc(left[0], right[0], 1);
676 g.insert_arc(right[0], left[0], 1);
677 g.insert_arc(left[1], right[1], 1);
678 g.insert_arc(right[1], left[1], 1);
679
680 // Run Karger-Stein multiple times
684
685 size_t ks_best = numeric_limits<size_t>::max();
686 for (int i = 0; i < 30; ++i)
687 {
688 size_t mc = ks(g, ks_S, ks_T, ks_cut);
689 ks_best = min(ks_best, mc);
690 }
691
692 // Run Stoer-Wagner (deterministic)
696
697 int sw_cut_size = sw(g, sw_S, sw_T, sw_cut);
698
699 // Both should find similar results (within factor of 2 for bidirectional)
700 CHECK(ks_best <= 2u * sw_cut_size + 2, "Karger-Stein and Stoer-Wagner should agree");
701 PASS();
702}
703
704// ============================================================================
705// Performance Tests
706// ============================================================================
707
709{
710 TEST("Performance: Karger-Stein on 50-node graph");
711
713 const int N = 50;
714
716 for (int i = 0; i < N; ++i)
717 nodes[i] = g.insert_node(i);
718
719 // Ensure graph is connected: create a path
720 for (int i = 0; i < N - 1; ++i)
721 {
722 g.insert_arc(nodes[i], nodes[i + 1], 1);
723 g.insert_arc(nodes[i + 1], nodes[i], 1);
724 }
725
726 // Add some extra edges
727 for (int i = 0; i < N; i += 3)
728 for (int j = i + 5; j < N; j += 5)
729 {
730 g.insert_arc(nodes[i], nodes[j], 1);
731 g.insert_arc(nodes[j], nodes[i], 1);
732 }
733
737
738 Timer timer;
739 size_t mc = ks(g, S, T, cut, 5); // Run 5 iterations
740 double elapsed = timer.elapsed_ms();
741
742 CHECK(elapsed < 10000, "should complete in < 10 seconds");
743 CHECK(mc >= 1, "should find a cut");
744 PASS();
745}
746
748{
749 TEST("Performance: Stoer-Wagner on 50-node graph");
750
752 const int N = 50;
753
755 for (int i = 0; i < N; ++i)
756 nodes[i] = g.insert_node("N" + to_string(i));
757
758 // Ensure graph is connected: create a path
759 for (int i = 0; i < N - 1; ++i)
760 {
761 g.insert_arc(nodes[i], nodes[i + 1], 1);
762 g.insert_arc(nodes[i + 1], nodes[i], 1);
763 }
764
765 // Add some extra edges
766 for (int i = 0; i < N; i += 3)
767 for (int j = i + 5; j < N; j += 5)
768 {
769 g.insert_arc(nodes[i], nodes[j], 1);
770 g.insert_arc(nodes[j], nodes[i], 1);
771 }
772
776
777 Timer timer;
778 int mc = sw(g, S, T, cut);
779 double elapsed = timer.elapsed_ms();
780
781 CHECK(elapsed < 5000, "should complete in < 5 seconds");
782 CHECK(mc >= 0, "should find a cut");
783 PASS();
784}
785
786// ============================================================================
787// Main
788// ============================================================================
789
790int main()
791{
792 cout << "╔══════════════════════════════════════════════════════════════╗\n";
793 cout << "║ Min-Cut Algorithms Test Suite ║\n";
794 cout << "║ Testing Karger-Stein and Stoer-Wagner ║\n";
795 cout << "╚══════════════════════════════════════════════════════════════╝\n\n";
796
797 cout << "=== Karger-Stein Tests ===\n";
802 test_ks_path();
807
808 cout << "\n=== Stoer-Wagner Tests ===\n";
817
818 cout << "\n=== Cross-Algorithm Tests ===\n";
820
821 cout << "\n=== Performance Tests ===\n";
824
825 cout << "\n";
826 cout << "══════════════════════════════════════════════════════════════\n";
827 cout << " RESULTS: " << tests_passed << "/" << total_tests << " passed";
828 if (tests_failed > 0)
829 cout << ", \033[31m" << tests_failed << " FAILED\033[0m";
830 cout << "\n";
831 cout << "══════════════════════════════════════════════════════════════\n";
832
833 return tests_failed > 0 ? 1 : 0;
834}
Karger's randomized min-cut algorithm.
Stoer-Wagner deterministic min-cut algorithm.
Dynamic singly linked list with functional programming support.
Definition htlist.H:1423
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
Karger's randomized minimum cut algorithm.
Definition Karger.H:158
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
Stoer-Wagner deterministic minimum cut algorithm.
double elapsed_ms() const
chrono::high_resolution_clock::time_point start
#define N
Definition fib.C:294
__gmp_expr< typename __gmp_resolve_expr< T, V >::value_type, __gmp_binary_expr< __gmp_expr< T, U >, __gmp_expr< V, W >, __gmp_min_function > > min(const __gmp_expr< T, U > &expr1, const __gmp_expr< V, W > &expr2)
Definition gmpfrxx.h:4111
DynArray< Graph::Node * > nodes
Definition graphpic.C:406
void test_ks_empty_graph()
void build_two_clusters(UnweightedGraph &g, int cluster_size, int bridge_count)
Build two clusters connected by k edges Min-cut: k.
void test_sw_min_cut_weight_only()
#define CHECK(cond, msg)
#define TEST(name)
#define CHECK_EQ(a, b, msg)
void test_performance_sw()
void test_ks_barbell()
void build_complete_graph(UnweightedGraph &g, int n)
Build a complete graph Kn Min-cut: n-1 (isolate any single vertex)
void test_ks_path()
void test_sw_chain_weighted()
void test_ks_find_with_iterations()
void build_cycle_graph(UnweightedGraph &g, int n)
Build a cycle graph: 0 - 1 - 2 - ... - (n-1) - 0 Min-cut: 2 (need to cut two edges to disconnect)
void test_ks_two_clusters()
void test_ks_triangle()
void test_ks_seed_reproducibility()
void test_performance_ks()
#define PASS()
void test_sw_single_edge()
static int total_tests
void test_sw_two_heavy_clusters()
void test_cross_comparison()
static int tests_passed
static int tests_failed
void build_barbell_graph(UnweightedGraph &g, int k)
Build a barbell graph: two K_k cliques connected by a single edge Min-cut: 1 (the bridge between cliq...
void build_path_graph(UnweightedGraph &g, int n)
Build a path graph: 0 - 1 - 2 - ... - (n-1) Min-cut: 1 (any single edge)
void test_ks_single_edge()
void test_ks_cycle()
void test_sw_complete_k4()
void test_sw_empty_graph()
void test_sw_unit_weight()
int main()
void build_weighted_chain(WeightedGraph &g, int w1, int w2, int w3)
Build a weighted graph with known min-cut Structure: A-B-C-D where weights determine the cut.
void test_sw_triangle_weighted()
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
std::decay_t< typename HeadC::Item_Type > T
Definition ah-zip.H:107
std::string to_string(const time_t t, const std::string &format)
Format a time_t value into a string using format.
Definition ah-date.H:140
void next()
Advance all underlying iterators (bounds-checked).
Definition ah-zip.H:175
Net::Flow_Type min_cut(Net &net, DynSetTree< typename Net::Node * > &vs, DynSetTree< typename Net::Node * > &vt, DynList< typename Net::Arc * > &cuts, DynList< typename Net::Arc * > &cutt)
Compute max flow and the corresponding minimum cut.
Definition tpl_net.H:1864
DynList< T > maps(const C &c, Op op)
Classic map operation.
STL namespace.
Arc of graph implemented with double-linked adjacency lists.
Definition tpl_graph.H:222
Generic graph and digraph implementations.
Simple graph implementation with adjacency lists.