Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
bipartite_test.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
49#include <gtest/gtest.h>
50#include <tpl_bipartite.H>
51#include <tpl_graph.H>
52
53using namespace std;
54using namespace Aleph;
55
56// Graph type for testing - using Empty_Class for arc info as required by bipartite algorithms
58
59// ============================================================================
60// Helper Functions
61// ============================================================================
62
69{
70 Graph g;
71
72 // Create left partition nodes
73 vector<Graph::Node *> left(m);
74 for (size_t i = 0; i < m; ++i)
75 left[i] = g.insert_node(static_cast<int>(i));
76
77 // Create right partition nodes
78 vector<Graph::Node *> right(n);
79 for (size_t j = 0; j < n; ++j)
80 right[j] = g.insert_node(static_cast<int>(m + j));
81
82 // Connect every node in left to every node in right
83 for (size_t i = 0; i < m; ++i)
84 for (size_t j = 0; j < n; ++j)
85 g.insert_arc(left[i], right[j]);
86
87 return g;
88}
89
95{
96 Graph g;
97
98 if (n == 0)
99 return g;
100
101 vector<Graph::Node *> nodes(n);
102 for (size_t i = 0; i < n; ++i)
103 nodes[i] = g.insert_node(static_cast<int>(i));
104
105 for (size_t i = 0; i + 1 < n; ++i)
106 g.insert_arc(nodes[i], nodes[i + 1]);
107
108 return g;
109}
110
116{
117 Graph g;
118
119 if (n < 3)
120 return g;
121
122 vector<Graph::Node *> nodes(n);
123 for (size_t i = 0; i < n; ++i)
124 nodes[i] = g.insert_node(static_cast<int>(i));
125
126 for (size_t i = 0; i < n; ++i)
127 g.insert_arc(nodes[i], nodes[(i + 1) % n]);
128
129 return g;
130}
131
137{
138 Graph g;
139
140 auto * center = g.insert_node(0);
141
142 for (size_t i = 0; i < n; ++i)
143 {
144 auto * leaf = g.insert_node(static_cast<int>(i + 1));
145 g.insert_arc(center, leaf);
146 }
147
148 return g;
149}
150
155{
156 Graph g;
157
158 auto * a = g.insert_node(0);
159 auto * b = g.insert_node(1);
160 auto * c = g.insert_node(2);
161
162 g.insert_arc(a, b);
163 g.insert_arc(b, c);
164 g.insert_arc(c, a);
165
166 return g;
167}
168
173{
174 Graph g;
175
176 // Component 1: path 0--1
177 auto * a = g.insert_node(0);
178 auto * b = g.insert_node(1);
179 g.insert_arc(a, b);
180
181 // Component 2: path 2--3
182 auto * c = g.insert_node(2);
183 auto * d = g.insert_node(3);
184 g.insert_arc(c, d);
185
186 return g;
187}
188
199 const DynDlist<Graph::Node *> & r)
200{
201 // Create sets for fast lookup
203
204 for (auto it = l.get_it(); it.has_curr(); it.next_ne())
205 left_set.insert(it.get_curr());
206
207 for (auto it = r.get_it(); it.has_curr(); it.next_ne())
208 right_set.insert(it.get_curr());
209
210 // Check that partitions don't overlap
211 for (auto it = l.get_it(); it.has_curr(); it.next_ne())
212 if (right_set.contains(it.get_curr()))
213 return false;
214
215 // Check that every edge between partitioned nodes connects different partitions
216 for (Arc_Iterator<Graph> it(g); it.has_curr(); it.next_ne())
217 {
218 auto * arc = it.get_curr();
219 auto * src = g.get_src_node(arc);
220 auto * tgt = g.get_tgt_node(arc);
221
222 bool src_in_left = left_set.contains(src);
223 bool src_in_right = right_set.contains(src);
224 bool tgt_in_left = left_set.contains(tgt);
225 bool tgt_in_right = right_set.contains(tgt);
226
227 // Skip edges involving nodes that weren't partitioned
229 continue;
230
231 // Both in same partition = invalid
233 return false;
234 }
235
236 return true;
237}
238
243bool verify_matching(const Graph & g,
245{
247
248 for (auto it = matching.get_it(); it.has_curr(); it.next_ne())
249 {
250 auto * arc = it.get_curr();
251 auto * src = g.get_src_node(arc);
252 auto * tgt = g.get_tgt_node(arc);
253
254 // Check if either node is already matched
255 if (matched_nodes.contains(src) || matched_nodes.contains(tgt))
256 return false;
257
260 }
261
262 return true;
263}
264
265// ============================================================================
266// Basic Bipartite Detection Tests
267// ============================================================================
268
269// KNOWN BUG: Empty graph is not handled - throws range_error
270// TODO: Fix tpl_bipartite.H to handle empty graphs gracefully
272{
273 Graph g;
275
276 // Empty graph should be trivially bipartite
277 // Note: Current implementation throws on empty graph - this tests the fix
281}
282
283// This test documents the current (buggy) behavior
285{
286 Graph g;
288
289 // BUG: Empty graph throws range_error instead of succeeding with empty partitions
290 EXPECT_THROW(compute_bipartite<Graph>(g, l, r), std::range_error);
291}
292
294{
295 Graph g;
296 g.insert_node(1);
297
299
301
302 // Single node goes into one partition
303 EXPECT_EQ(l.size() + r.size(), 1u);
304}
305
307{
308 Graph g;
309 auto * a = g.insert_node(1);
310 auto * b = g.insert_node(2);
311 g.insert_arc(a, b);
312
314
316
317 EXPECT_EQ(l.size(), 1u);
318 EXPECT_EQ(r.size(), 1u);
320}
321
323{
324 auto g = create_path_graph(4); // 0--1--2--3
325
327
329
330 EXPECT_EQ(l.size() + r.size(), 4u);
331 EXPECT_EQ(l.size(), 2u);
332 EXPECT_EQ(r.size(), 2u);
334}
335
337{
338 auto g = create_path_graph(5); // 0--1--2--3--4
339
341
343
344 EXPECT_EQ(l.size() + r.size(), 5u);
346}
347
349{
350 auto g = create_star_graph(5); // Center with 5 leaves
351
353
355
356 EXPECT_EQ(l.size() + r.size(), 6u);
357 // One partition has the center, other has all leaves
358 EXPECT_TRUE((l.size() == 1 && r.size() == 5) ||
359 (l.size() == 5 && r.size() == 1));
361}
362
364{
365 auto g = create_complete_bipartite(2, 2);
366
368
370
371 EXPECT_EQ(l.size(), 2u);
372 EXPECT_EQ(r.size(), 2u);
374}
375
377{
378 auto g = create_complete_bipartite(3, 3);
379
381
383
384 EXPECT_EQ(l.size(), 3u);
385 EXPECT_EQ(r.size(), 3u);
387}
388
390{
391 auto g = create_complete_bipartite(2, 5);
392
394
396
397 EXPECT_EQ(l.size() + r.size(), 7u);
399}
400
402{
403 auto g = create_cycle_graph(6); // 6-cycle is bipartite
404
406
408
409 EXPECT_EQ(l.size(), 3u);
410 EXPECT_EQ(r.size(), 3u);
412}
413
414// ============================================================================
415// Non-Bipartite Graph Detection Tests
416// ============================================================================
417
419{
420 auto g = create_triangle();
421
423
424 EXPECT_THROW(compute_bipartite<Graph>(g, l, r), domain_error);
425}
426
428{
429 auto g = create_cycle_graph(5); // 5-cycle is NOT bipartite
430
432
433 EXPECT_THROW(compute_bipartite<Graph>(g, l, r), domain_error);
434}
435
437{
438 auto g = create_cycle_graph(11); // 11-cycle is NOT bipartite
439
441
442 EXPECT_THROW(compute_bipartite<Graph>(g, l, r), domain_error);
443}
444
446{
447 // K_3 is a triangle
448 auto g = create_triangle();
449
451
452 EXPECT_THROW(compute_bipartite<Graph>(g, l, r), domain_error);
453}
454
456{
457 Graph g;
458
459 // Create a bipartite part
460 auto * a = g.insert_node(0);
461 auto * b = g.insert_node(1);
462 g.insert_arc(a, b);
463
464 // Attach an odd cycle (3 nodes = triangle) to node b
465 auto * c = g.insert_node(2);
466 auto * d = g.insert_node(3);
467 g.insert_arc(b, c);
468 g.insert_arc(c, d);
469 g.insert_arc(d, b); // Creates odd cycle b-c-d-b (3 nodes)
470
472
473 EXPECT_THROW(compute_bipartite<Graph>(g, l, r), domain_error);
474}
475
476// ============================================================================
477// Disconnected Graph Tests
478// ============================================================================
479
480// Note: These tests document the expected behavior.
481// The current implementation may not handle disconnected graphs correctly.
482
484{
485 Graph g;
486 g.insert_node(1);
487 g.insert_node(2);
488 // No edges - two isolated nodes
489
491
492 // Two isolated nodes should be bipartite
493 // Current implementation only processes first node's component
495
496 // At minimum, should process one node without crashing
497 EXPECT_GE(l.size() + r.size(), 1u);
498}
499
501{
503
505
507
508 // Should process at least the first component
509 EXPECT_GE(l.size() + r.size(), 2u);
510
511 // Verify what was partitioned is correct
513}
514
515// ============================================================================
516// Class Wrapper Tests
517// ============================================================================
518
520{
521 auto g = create_complete_bipartite(3, 4);
522
524
526
527 EXPECT_EQ(l.size() + r.size(), 7u);
529}
530
539
540// ============================================================================
541// Maximum Matching Tests
542// ============================================================================
543// KNOWN BUG: The maximum matching algorithm is not returning correct results.
544// The flow network-based algorithm returns 0 matches for all cases.
545// Empty graph throws range_error (consistent with compute_bipartite behavior)
556
571
573{
574 auto g = create_path_graph(4); // 0--1--2--3
575
577
579
580 // Maximum matching in path of 4 nodes is 2 edges
581 EXPECT_EQ(matching.size(), 2u);
583}
584
586{
587 auto g = create_path_graph(5); // 0--1--2--3--4
588
590
592
593 // Maximum matching in path of 5 nodes is 2 edges
594 EXPECT_EQ(matching.size(), 2u);
596}
597
610
623
636
638{
639 auto g = create_complete_bipartite(2, 5);
640
642
644
645 // Maximum matching limited by smaller partition: 2 edges
646 EXPECT_EQ(matching.size(), 2u);
648}
649
651{
652 auto g = create_complete_bipartite(5, 2);
653
655
657
658 // Maximum matching limited by smaller partition: 2 edges
659 EXPECT_EQ(matching.size(), 2u);
661}
662
664{
665 auto g = create_star_graph(5);
666
668
670
671 // Star can only have 1 edge in matching (center is shared)
672 EXPECT_EQ(matching.size(), 1u);
674}
675
677{
678 auto g = create_cycle_graph(6);
679
681
683
684 // 6-cycle has perfect matching: 3 edges
685 EXPECT_EQ(matching.size(), 3u);
687}
688
699
700// ============================================================================
701// Matching Class Wrapper Tests
702// ============================================================================
703
715
726
727// ============================================================================
728// Stress Tests
729// ============================================================================
730
732{
733 auto g = create_complete_bipartite(50, 50);
734
736
738
739 EXPECT_EQ(l.size() + r.size(), 100u);
741}
742
744{
745 auto g = create_path_graph(100);
746
748
750
751 EXPECT_EQ(l.size(), 50u);
752 EXPECT_EQ(r.size(), 50u);
754}
755
767
769{
770 auto g = create_cycle_graph(100);
771
773
775
776 EXPECT_EQ(l.size(), 50u);
777 EXPECT_EQ(r.size(), 50u);
779}
780
781// ============================================================================
782// Edge Cases
783// ============================================================================
784
786{
787 Graph g;
788 auto * a = g.insert_node(1);
789 auto * b = g.insert_node(2);
790
791 // Multiple edges between same nodes (multigraph)
792 g.insert_arc(a, b);
793 g.insert_arc(a, b);
794 g.insert_arc(a, b);
795
797
799
800 EXPECT_EQ(l.size(), 1u);
801 EXPECT_EQ(r.size(), 1u);
802}
803
805{
806 Graph g;
807
808 // Bipartite component
809 auto * a = g.insert_node(0);
810 auto * b = g.insert_node(1);
811 g.insert_arc(a, b);
812
813 // Isolated node
814 g.insert_node(2);
815
817
819
820 // At least the connected component should be processed
821 EXPECT_GE(l.size() + r.size(), 2u);
822}
823
824// ============================================================================
825// Color Enum Tests
826// ============================================================================
827
829{
830 // Verify the color enum values
832 EXPECT_EQ(Bp_Red, 1);
833 EXPECT_EQ(Bp_Blue, 2);
834}
835
836// ============================================================================
837// Hopcroft-Karp Matching Tests
838// ============================================================================
839
840// --- Base cases ---
841
850
861
863{
864 Graph g;
865 auto * a = g.insert_node(0);
866 auto * b = g.insert_node(1);
867 g.insert_arc(a, b);
868
871
872 EXPECT_EQ(matching.size(), 1u);
874}
875
876// --- Standard bipartite graphs ---
877
888
899
910
921
932
943
954
965
966// --- Non-bipartite detection ---
967
976
985
986// --- Disconnected graphs ---
987
998
1000{
1001 Graph g;
1002
1003 // Component 1: single edge
1004 auto * a = g.insert_node(0);
1005 auto * b = g.insert_node(1);
1006 g.insert_arc(a, b);
1007
1008 // Isolated nodes
1009 g.insert_node(2);
1010 g.insert_node(3);
1011
1012 // Component 2: K_{2,2}
1013 auto * c = g.insert_node(10);
1014 auto * d = g.insert_node(11);
1015 auto * e = g.insert_node(12);
1016 auto * f = g.insert_node(13);
1017 g.insert_arc(c, e);
1018 g.insert_arc(c, f);
1019 g.insert_arc(d, e);
1020 g.insert_arc(d, f);
1021
1024
1025 // 1 from edge + 2 from K_{2,2} = 3
1026 EXPECT_EQ(matching.size(), 3u);
1028}
1029
1031{
1032 Graph g;
1033
1034 // Component 1: K_{3,3}
1035 vector<Graph::Node *> l1(3), r1(3);
1036 for (int i = 0; i < 3; ++i)
1037 l1[i] = g.insert_node(i);
1038 for (int i = 0; i < 3; ++i)
1039 r1[i] = g.insert_node(10 + i);
1040 for (int i = 0; i < 3; ++i)
1041 for (int j = 0; j < 3; ++j)
1042 g.insert_arc(l1[i], r1[j]);
1043
1044 // Component 2: K_{2,2}
1045 vector<Graph::Node *> l2(2), r2(2);
1046 for (int i = 0; i < 2; ++i)
1047 l2[i] = g.insert_node(20 + i);
1048 for (int i = 0; i < 2; ++i)
1049 r2[i] = g.insert_node(30 + i);
1050 for (int i = 0; i < 2; ++i)
1051 for (int j = 0; j < 2; ++j)
1052 g.insert_arc(l2[i], r2[j]);
1053
1056
1057 // 3 + 2 = 5
1058 EXPECT_EQ(matching.size(), 5u);
1060}
1061
1062// --- Cross-validation with max-flow ---
1063
1077
1091
1105
1106// --- Functor wrapper ---
1107
1118
1129
1130// --- Stress tests ---
1131
1142
1153
1164
1165int main(int argc, char **argv)
1166{
1167 ::testing::InitGoogleTest(&argc, argv);
1168 return RUN_ALL_TESTS();
1169}
int main()
Graph create_complete_bipartite(size_t m, size_t n)
Creates a complete bipartite graph K_{m,n} Left partition: nodes 0..m-1 Right partition: nodes m....
Graph create_path_graph(size_t n)
Creates a path graph with n nodes: 0–1–2–...–n-1 Path graphs are always bipartite.
Graph create_disconnected_bipartite()
Creates two disconnected components.
Graph create_triangle()
Creates a triangle (K_3) - the simplest non-bipartite graph.
bool verify_bipartition(const Graph &g, const DynDlist< Graph::Node * > &l, const DynDlist< Graph::Node * > &r)
Verifies that a bipartition is valid:
Graph create_cycle_graph(size_t n)
Creates a cycle graph with n nodes: 0–1–2–...–n-1–0 Even cycles are bipartite, odd cycles are not.
bool verify_matching(const Graph &g, const DynDlist< Graph::Arc * > &matching)
Verifies that a matching is valid:
Graph create_star_graph(size_t n)
Creates a star graph with center and n leaves Star graphs are always bipartite.
Class that takes a bipartite graph and computes the partition sets.
Class for computing the maximum cardinality matching of a bipartite graph.
Dynamic doubly linked list with O(1) size and bidirectional access.
const size_t & size() const noexcept
Return the number of elements (constant time)
T & insert(const T &item)
Insert a new item by copy.
Definition htlist.H:1502
Dynamic set implemented using AVL binary search trees of type Avl_Tree<Key>.
void next_ne() noexcept
Advances the iterator to the next filtered element (noexcept version).
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
Node * get_src_node(Arc *arc) const noexcept
Return the source node of arc (only for directed graphs)
Definition graph-dry.H:731
Node * get_tgt_node(Arc *arc) const noexcept
Return the target node of arc (only for directed graphs)
Definition graph-dry.H:737
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)
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.
STL namespace.
Filtered iterator on all the arcs of a graph.
Definition tpl_graph.H:1164
Arc of graph implemented with double-linked adjacency lists.
Definition tpl_graph.H:222
Bipartite graph detection and 2-coloring.
Generic graph and digraph implementations.
DynList< int > l