KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_union_find.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20#include <boost/test/unit_test.hpp>
21
22#include <core/union_find.h>
23
24#include <algorithm>
25#include <atomic>
26#include <map>
27#include <random>
28#include <thread>
29#include <vector>
30
31
32namespace
33{
34
39class REFERENCE_SET
40{
41public:
42 explicit REFERENCE_SET( size_t aCount ) : m_parent( aCount ), m_size( aCount, 1 )
43 {
44 for( size_t ii = 0; ii < aCount; ++ii )
45 m_parent[ii] = ii;
46 }
47
48 size_t Find( size_t aX )
49 {
50 while( m_parent[aX] != aX )
51 {
52 m_parent[aX] = m_parent[m_parent[aX]];
53 aX = m_parent[aX];
54 }
55
56 return aX;
57 }
58
59 bool Unite( size_t aA, size_t aB )
60 {
61 aA = Find( aA );
62 aB = Find( aB );
63
64 if( aA == aB )
65 return false;
66
67 if( m_size[aA] < m_size[aB] )
68 std::swap( aA, aB );
69
70 m_parent[aB] = aA;
71 m_size[aA] += m_size[aB];
72
73 return true;
74 }
75
76private:
77 std::vector<size_t> m_parent;
78 std::vector<size_t> m_size;
79};
80
81
82std::vector<std::pair<size_t, size_t>> randomEdges( std::mt19937& aRng, size_t aNodes,
83 size_t aCount )
84{
85 std::vector<std::pair<size_t, size_t>> edges;
86 edges.reserve( aCount );
87
88 for( size_t ii = 0; ii < aCount; ++ii )
89 edges.emplace_back( aRng() % aNodes, aRng() % aNodes );
90
91 return edges;
92}
93
94} // namespace
95
96
97BOOST_AUTO_TEST_SUITE( UnionFind )
98
99
100
104BOOST_AUTO_TEST_CASE( MatchesReferenceImplementation )
105{
106 std::mt19937 rng( 4242 );
107
108 for( int trial = 0; trial < 100; ++trial )
109 {
110 const size_t nodes = 1 + rng() % 300;
111 const size_t count = rng() % ( 3 * nodes + 1 );
112
113 std::vector<std::pair<size_t, size_t>> edges = randomEdges( rng, nodes, count );
114
115 KI_UNION_FIND under( nodes );
116 REFERENCE_SET reference( nodes );
117
118 size_t merges = 0;
119 size_t referenceMerges = 0;
120
121 for( const auto& [a, b] : edges )
122 {
123 merges += under.Unite( a, b ) ? 1 : 0;
124 referenceMerges += reference.Unite( a, b ) ? 1 : 0;
125 }
126
127 BOOST_REQUIRE_EQUAL( merges, referenceMerges );
128 BOOST_REQUIRE_EQUAL( under.ComponentCount(), nodes - referenceMerges );
129
130 for( size_t i = 0; i < nodes; ++i )
131 {
132 for( size_t j = 0; j < nodes; ++j )
133 {
134 BOOST_REQUIRE_EQUAL( under.Connected( i, j ),
135 reference.Find( i ) == reference.Find( j ) );
136 }
137 }
138 }
139}
140
141
147BOOST_AUTO_TEST_CASE( RootIsMinimumOfComponent )
148{
149 std::mt19937 rng( 99 );
150 const size_t nodes = 2000;
151 KI_UNION_FIND under( nodes );
152 REFERENCE_SET reference( nodes );
153
154 for( const auto& [a, b] : randomEdges( rng, nodes, 5000 ) )
155 {
156 under.Unite( a, b );
157 reference.Unite( a, b );
158 }
159
160 std::map<size_t, size_t> minimumOf;
161
162 for( size_t ii = 0; ii < nodes; ++ii )
163 {
164 size_t root = reference.Find( ii );
165 auto it = minimumOf.find( root );
166
167 if( it == minimumOf.end() )
168 minimumOf[root] = ii;
169 }
170
171 for( size_t ii = 0; ii < nodes; ++ii )
172 BOOST_REQUIRE_EQUAL( under.Find( ii ), minimumOf[reference.Find( ii )] );
173}
174
175
180BOOST_AUTO_TEST_CASE( CompressionPreservesPartition )
181{
182 std::mt19937 rng( 7 );
183 const size_t nodes = 4000;
184 KI_UNION_FIND under( nodes );
185 REFERENCE_SET reference( nodes );
186
187 for( const auto& [a, b] : randomEdges( rng, nodes, 9000 ) )
188 {
189 under.Unite( a, b );
190 reference.Unite( a, b );
191 }
192
193 std::vector<size_t> before( nodes );
194
195 for( size_t ii = 0; ii < nodes; ++ii )
196 before[ii] = under.Find( ii );
197
198 for( size_t ii = 0; ii < nodes; ++ii )
199 BOOST_REQUIRE_EQUAL( under.FindCompress( ii ), before[ii] );
200
201 for( size_t ii = 0; ii < nodes; ++ii )
202 BOOST_REQUIRE_EQUAL( under.Find( ii ), before[ii] );
203}
204
205
211BOOST_AUTO_TEST_CASE( ConcurrentUniteMatchesSerial )
212{
213 std::mt19937 rng( 31337 );
214 const size_t nodes = 20000;
215
216 std::vector<std::pair<size_t, size_t>> edges = randomEdges( rng, nodes, 50000 );
217
218 KI_UNION_FIND under( nodes );
219 std::atomic<int> merges{ 0 };
220
221 const unsigned threadCount = std::max( 2u, std::thread::hardware_concurrency() );
222 std::vector<std::thread> threads;
223
224 for( unsigned t = 0; t < threadCount; ++t )
225 {
226 threads.emplace_back(
227 [&, t]
228 {
229 int local = 0;
230
231 for( size_t ii = t; ii < edges.size(); ii += threadCount )
232 local += under.Unite( edges[ii].first, edges[ii].second ) ? 1 : 0;
233
234 merges += local;
235 } );
236 }
237
238 for( std::thread& thread : threads )
239 thread.join();
240
241 REFERENCE_SET reference( nodes );
242 int referenceMerges = 0;
243
244 for( const auto& [a, b] : edges )
245 referenceMerges += reference.Unite( a, b ) ? 1 : 0;
246
247 BOOST_CHECK_EQUAL( merges.load(), referenceMerges );
248 BOOST_CHECK_EQUAL( under.ComponentCount(), nodes - referenceMerges );
249
250 for( size_t ii = 0; ii < nodes; ++ii )
251 {
252 size_t other = ( ii * 7919 ) % nodes;
253 BOOST_REQUIRE_EQUAL( under.Connected( ii, other ),
254 reference.Find( ii ) == reference.Find( other ) );
255 }
256}
257
258
264BOOST_AUTO_TEST_CASE( ReverseChainUnderContention )
265{
266 const size_t nodes = 50000;
267 KI_UNION_FIND under( nodes );
268 std::atomic<int> merges{ 0 };
269
270 const unsigned threadCount = std::max( 2u, std::thread::hardware_concurrency() );
271 std::vector<std::thread> threads;
272
273 for( unsigned t = 0; t < threadCount; ++t )
274 {
275 threads.emplace_back(
276 [&, t]
277 {
278 int local = 0;
279
280 for( long ii = (long) nodes - 2 - t; ii >= 0; ii -= threadCount )
281 local += under.Unite( ii, ii + 1 ) ? 1 : 0;
282
283 merges += local;
284 } );
285 }
286
287 for( std::thread& thread : threads )
288 thread.join();
289
290 BOOST_CHECK_EQUAL( merges.load(), (int) nodes - 1 );
291 BOOST_CHECK_EQUAL( under.ComponentCount(), 1 );
292
293 for( size_t ii = 0; ii < nodes; ii += 997 )
294 BOOST_REQUIRE_EQUAL( under.Find( ii ), 0 );
295}
296
297
298BOOST_AUTO_TEST_CASE( DegenerateSizes )
299{
300 KI_UNION_FIND empty( 0 );
301 BOOST_CHECK_EQUAL( empty.Size(), 0 );
302 BOOST_CHECK_EQUAL( empty.ComponentCount(), 0 );
303
304 KI_UNION_FIND singleton( 1 );
305 BOOST_CHECK( !singleton.Unite( 0, 0 ) );
306 BOOST_CHECK_EQUAL( singleton.ComponentCount(), 1 );
307 BOOST_CHECK( singleton.Connected( 0, 0 ) );
308
309 KI_UNION_FIND pair( 2 );
310 BOOST_CHECK( pair.Unite( 1, 0 ) );
311 BOOST_CHECK( !pair.Unite( 0, 1 ) );
313 BOOST_CHECK_EQUAL( pair.Find( 1 ), 0 );
314}
315
316
317BOOST_AUTO_TEST_CASE( ResetClearsState )
318{
319 KI_UNION_FIND under( 10 );
320
321 for( size_t ii = 1; ii < 10; ++ii )
322 under.Unite( 0, ii );
323
324 BOOST_CHECK_EQUAL( under.ComponentCount(), 1 );
325
326 under.Reset( 5 );
327
328 BOOST_CHECK_EQUAL( under.Size(), 5 );
329 BOOST_CHECK_EQUAL( under.ComponentCount(), 5 );
330
331 for( size_t ii = 0; ii < 5; ++ii )
332 BOOST_CHECK_EQUAL( under.Find( ii ), ii );
333}
334
335
Lock-free disjoint-set over a dense range of indices.
Definition union_find.h:48
size_t FindCompress(size_t aX)
Shorten the path from aX to its root so that later queries walk less of it.
Definition union_find.h:150
size_t ComponentCount() const
Definition union_find.h:75
bool Connected(size_t aA, size_t aB) const
Definition union_find.h:167
size_t Size() const
Definition union_find.h:72
void Reset(size_t aCount)
Definition union_find.h:56
bool Unite(size_t aA, size_t aB)
Merge the components that hold aA and aB.
Definition union_find.h:82
size_t Find(size_t aX) const
Definition union_find.h:130
static bool empty(const wxTextEntryBase *aCtrl)
static thread_local boost::mt19937 rng
Definition kiid.cpp:49
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_SUITE_END()
BOOST_CHECK_EQUAL(result, "25.4")
BOOST_AUTO_TEST_CASE(MatchesReferenceImplementation)
The partition, the merge count and the component count must all track a reference implementation exac...