KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_filter_kruskal.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/filter_kruskal.h>
23#include <core/union_find.h>
24
25#include <algorithm>
26#include <random>
27#include <tuple>
28#include <vector>
29
30
31namespace
32{
33
34struct TEST_EDGE
35{
36 size_t u;
37 size_t v;
38 int64_t weight;
39};
40
41
42bool edgeOrder( const TEST_EDGE& aLhs, const TEST_EDGE& aRhs )
43{
44 return std::tie( aLhs.weight, aLhs.u, aLhs.v ) < std::tie( aRhs.weight, aRhs.u, aRhs.v );
45}
46
47
48std::pair<size_t, size_t> edgeEnds( const TEST_EDGE& aEdge )
49{
50 return { aEdge.u, aEdge.v };
51}
52
53
59std::vector<TEST_EDGE> referenceKruskal( std::vector<TEST_EDGE> aEdges, size_t aNodes,
60 const std::vector<std::pair<size_t, size_t>>& aSeeds )
61{
62 KI_UNION_FIND forest( aNodes );
63
64 for( const auto& [a, b] : aSeeds )
65 forest.Unite( a, b );
66
67 std::sort( aEdges.begin(), aEdges.end(), edgeOrder );
68
69 std::vector<TEST_EDGE> tree;
70
71 for( const TEST_EDGE& edge : aEdges )
72 {
73 if( forest.Unite( edge.u, edge.v ) )
74 tree.push_back( edge );
75 }
76
77 return tree;
78}
79
80
81std::vector<TEST_EDGE> filterKruskal( std::vector<TEST_EDGE> aEdges, size_t aNodes,
82 const std::vector<std::pair<size_t, size_t>>& aSeeds,
83 size_t& aReturnedCount )
84{
85 KI_UNION_FIND forest( aNodes );
86
87 for( const auto& [a, b] : aSeeds )
88 forest.Unite( a, b );
89
90 std::vector<TEST_EDGE> tree;
91
92 aReturnedCount = KI_MST::FilterKruskal<TEST_EDGE>(
93 aEdges, forest, edgeOrder, edgeEnds,
94 [&]( const TEST_EDGE& aEdge ) { tree.push_back( aEdge ); } );
95
96 return tree;
97}
98
99
100int64_t totalWeight( const std::vector<TEST_EDGE>& aTree )
101{
102 int64_t total = 0;
103
104 for( const TEST_EDGE& edge : aTree )
105 total += edge.weight;
106
107 return total;
108}
109
110
116void checkAgainstReference( const std::vector<TEST_EDGE>& aEdges, size_t aNodes,
117 const std::vector<std::pair<size_t, size_t>>& aSeeds = {} )
118{
119 size_t reportedCount = 0;
120 std::vector<TEST_EDGE> got = filterKruskal( aEdges, aNodes, aSeeds, reportedCount );
121 std::vector<TEST_EDGE> want = referenceKruskal( aEdges, aNodes, aSeeds );
122
123 BOOST_REQUIRE_EQUAL( reportedCount, got.size() );
124 BOOST_REQUIRE_EQUAL( got.size(), want.size() );
125 BOOST_REQUIRE_EQUAL( totalWeight( got ), totalWeight( want ) );
126
127 // Emission must be in non-decreasing order; a caller relying on Kruskal semantics may
128 // depend on that even though the set is what matters.
129 for( size_t ii = 1; ii < got.size(); ++ii )
130 BOOST_REQUIRE( !edgeOrder( got[ii], got[ii - 1] ) );
131
132 std::vector<TEST_EDGE> sortedGot = got;
133 std::vector<TEST_EDGE> sortedWant = want;
134 std::sort( sortedGot.begin(), sortedGot.end(), edgeOrder );
135 std::sort( sortedWant.begin(), sortedWant.end(), edgeOrder );
136
137 for( size_t ii = 0; ii < sortedGot.size(); ++ii )
138 {
139 BOOST_REQUIRE_EQUAL( sortedGot[ii].u, sortedWant[ii].u );
140 BOOST_REQUIRE_EQUAL( sortedGot[ii].v, sortedWant[ii].v );
141 BOOST_REQUIRE_EQUAL( sortedGot[ii].weight, sortedWant[ii].weight );
142 }
143}
144
145
146std::vector<TEST_EDGE> randomEdges( std::mt19937& aRng, size_t aNodes, size_t aCount,
147 int64_t aWeightRange )
148{
149 std::vector<TEST_EDGE> edges;
150 edges.reserve( aCount );
151
152 for( size_t ii = 0; ii < aCount; ++ii )
153 {
154 edges.push_back( { aRng() % aNodes, aRng() % aNodes,
155 (int64_t) ( aRng() % (uint32_t) aWeightRange ) } );
156 }
157
158 return edges;
159}
160
161} // namespace
162
163
164BOOST_AUTO_TEST_SUITE( FilterKruskal )
165
166
167
171BOOST_AUTO_TEST_CASE( MatchesPlainKruskal )
172{
173 std::mt19937 rng( 555 );
174
175 for( int trial = 0; trial < 60; ++trial )
176 {
177 const size_t nodes = 2 + rng() % 2000;
178 const size_t count = rng() % ( 5 * nodes );
179
180 checkAgainstReference( randomEdges( rng, nodes, count, 100000 ), nodes );
181 }
182}
183
184
189BOOST_AUTO_TEST_CASE( AllWeightsEqual )
190{
191 std::mt19937 rng( 12 );
192
193 for( int trial = 0; trial < 8; ++trial )
194 {
195 const size_t nodes = 500 + rng() % 2000;
196 std::vector<TEST_EDGE> edges = randomEdges( rng, nodes, 5 * nodes, 100000 );
197
198 for( TEST_EDGE& edge : edges )
199 edge.weight = 0;
200
201 checkAgainstReference( edges, nodes );
202 }
203}
204
205
206BOOST_AUTO_TEST_CASE( FewDistinctWeights )
207{
208 std::mt19937 rng( 606 );
209
210 for( int trial = 0; trial < 8; ++trial )
211 {
212 const size_t nodes = 1000 + rng() % 1500;
213
214 checkAgainstReference( randomEdges( rng, nodes, 6 * nodes, 2 ), nodes );
215 }
216}
217
218
223BOOST_AUTO_TEST_CASE( DisconnectedGraphYieldsForest )
224{
225 std::mt19937 rng( 808 );
226
227 const size_t nodes = 4000;
228 const size_t islands = 8;
229 const size_t islandSize = nodes / islands;
230
231 std::vector<TEST_EDGE> edges;
232
233 for( size_t ii = 0; ii < 20000; ++ii )
234 {
235 size_t island = rng() % islands;
236 edges.push_back( { island * islandSize + rng() % islandSize,
237 island * islandSize + rng() % islandSize,
238 (int64_t) ( rng() % 1000 ) } );
239 }
240
241 checkAgainstReference( edges, nodes );
242}
243
244
249BOOST_AUTO_TEST_CASE( HonoursPreSeededForest )
250{
251 std::mt19937 rng( 909 );
252
253 const size_t nodes = 3000;
254
255 std::vector<std::pair<size_t, size_t>> seeds;
256
257 for( size_t ii = 0; ii < 2000; ++ii )
258 seeds.emplace_back( rng() % nodes, rng() % nodes );
259
260 std::vector<TEST_EDGE> edges = randomEdges( rng, nodes, 15000, 5000 );
261
262 for( TEST_EDGE& edge : edges )
263 edge.weight += 1;
264
265 checkAgainstReference( edges, nodes, seeds );
266}
267
268
273BOOST_AUTO_TEST_CASE( SelectionIsReproducible )
274{
275 std::mt19937 rng( 246 );
276
277 const size_t nodes = 5000;
278 std::vector<TEST_EDGE> edges = randomEdges( rng, nodes, 25000, 50 );
279
280 size_t count = 0;
281 std::vector<TEST_EDGE> first = filterKruskal( edges, nodes, {}, count );
282
283 for( int repeat = 0; repeat < 4; ++repeat )
284 {
285 std::vector<TEST_EDGE> again = filterKruskal( edges, nodes, {}, count );
286
287 BOOST_REQUIRE_EQUAL( again.size(), first.size() );
288
289 for( size_t ii = 0; ii < again.size(); ++ii )
290 {
291 BOOST_REQUIRE_EQUAL( again[ii].u, first[ii].u );
292 BOOST_REQUIRE_EQUAL( again[ii].v, first[ii].v );
293 BOOST_REQUIRE_EQUAL( again[ii].weight, first[ii].weight );
294 }
295 }
296}
297
298
305BOOST_AUTO_TEST_CASE( SelectionIgnoresInputOrderAndOrientation )
306{
307 auto canonical =
308 []( const TEST_EDGE& aEdge )
309 {
310 size_t low = std::min( aEdge.u, aEdge.v );
311 size_t high = std::max( aEdge.u, aEdge.v );
312
313 return std::tuple<int64_t, size_t, size_t>( aEdge.weight, low, high );
314 };
315
316 auto canonicalOrder =
317 [&canonical]( const TEST_EDGE& aLhs, const TEST_EDGE& aRhs )
318 {
319 return canonical( aLhs ) < canonical( aRhs );
320 };
321
322 std::mt19937 rng( 1357 );
323
324 const size_t nodes = 4000;
325 std::vector<TEST_EDGE> edges = randomEdges( rng, nodes, 20000, 40 );
326
327 auto select =
328 [&]( std::vector<TEST_EDGE> aInput )
329 {
330 KI_UNION_FIND forest( nodes );
331 std::vector<TEST_EDGE> tree;
332
334 aInput, forest, canonicalOrder, edgeEnds,
335 [&]( const TEST_EDGE& aEdge ) { tree.push_back( aEdge ); } );
336
337 std::vector<std::tuple<int64_t, size_t, size_t>> keys;
338
339 for( const TEST_EDGE& edge : tree )
340 keys.push_back( canonical( edge ) );
341
342 std::sort( keys.begin(), keys.end() );
343
344 return keys;
345 };
346
347 std::vector<std::tuple<int64_t, size_t, size_t>> expected = select( edges );
348
349 for( int trial = 0; trial < 8; ++trial )
350 {
351 std::vector<TEST_EDGE> permuted = edges;
352
353 std::shuffle( permuted.begin(), permuted.end(), rng );
354
355 for( TEST_EDGE& edge : permuted )
356 {
357 if( rng() & 1u )
358 std::swap( edge.u, edge.v );
359 }
360
361 BOOST_REQUIRE( select( permuted ) == expected );
362 }
363}
364
365
366BOOST_AUTO_TEST_CASE( DegenerateInputs )
367{
368 checkAgainstReference( {}, 1 );
369 checkAgainstReference( { { 0, 1, 5 } }, 2 );
370
371 // Self loops can never be selected.
372 checkAgainstReference( { { 0, 0, 1 }, { 1, 1, 2 }, { 0, 1, 3 } }, 2 );
373
374 // Parallel edges: only the lightest of each pair may be taken.
375 size_t count = 0;
376 std::vector<TEST_EDGE> tree =
377 filterKruskal( { { 0, 1, 9 }, { 0, 1, 2 }, { 0, 1, 7 } }, 2, {}, count );
378
379 BOOST_CHECK_EQUAL( tree.size(), 1 );
380 BOOST_CHECK_EQUAL( tree[0].weight, 2 );
381}
382
383
Lock-free disjoint-set over a dense range of indices.
Definition union_find.h:48
static thread_local boost::mt19937 rng
Definition kiid.cpp:49
size_t FilterKruskal(std::span< EDGE > aEdges, KI_UNION_FIND &aForest, LESS aLess, ENDPOINTS aEndpoints, EMIT aEmit)
Build a minimum spanning forest over aEdges.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_AUTO_TEST_CASE(MatchesPlainKruskal)
Sizes are chosen to straddle KRUSKAL_THRESHOLD so both the recursive partitioning path and the direct...
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
VECTOR3I expected(15, 30, 45)
BOOST_CHECK_EQUAL(result, "25.4")