Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
min_cost_matching_test.cc
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
31
44# include <gtest/gtest.h>
45
46# include <algorithm>
47# include <bit>
48# include <chrono>
49# include <cstdint>
50# include <functional>
51# include <initializer_list>
52# include <limits>
53# include <ostream>
54# include <random>
55# include <stdexcept>
56# include <tuple>
57# include <utility>
58
59# include <Blossom_Weighted.H>
60# include <Min_Cost_Matching.H>
61# include <tpl_agraph.H>
62# include <tpl_array.H>
63# include <tpl_dynMapTree.H>
64# include <tpl_dynSetTree.H>
65# include <tpl_graph.H>
66# include <tpl_sgraph.H>
67
68using namespace Aleph;
69
70namespace
71{
72 using Cost_Edge = std::tuple<size_t, size_t, long long>;
74 using List_Backend =
76 using SGraph_Backend =
78 using AGraph_Backend =
80
81 struct Objective
82 {
83 size_t cardinality = 0;
84 long long total_cost = 0;
85
86 bool operator==(const Objective & other) const noexcept
87 {
88 return cardinality == other.cardinality
89 and total_cost == other.total_cost;
90 }
91 };
92
93
94 void PrintTo(const Objective & obj, std::ostream * os)
95 {
96 *os << "{card=" << obj.cardinality
97 << ", cost=" << obj.total_cost << "}";
98 }
99
100 Cost_Edges make_cost_edges(std::initializer_list<Cost_Edge> init)
101 {
103 ret.reserve(init.size());
104 for (const auto & e : init)
105 ret.append(e);
106 return ret;
107 }
108
109
110 template <class GT>
111 GT build_graph(size_t n, const Cost_Edges & edges)
112 {
113 GT g;
115
116 for (size_t i = 0; i < n; ++i)
117 nodes(i) = g.insert_node(static_cast<int>(i));
118
119 for (const auto & [u, v, c] : edges)
120 {
121 if (u >= n or v >= n)
122 continue;
123 g.insert_arc(nodes(u), nodes(v), c);
124 }
125
126 return g;
127 }
128
129
130 template <class GT>
131 bool verify_matching(const GT & g,
133 {
135 for (typename GT::Arc_Iterator it(g); it.has_curr(); it.next_ne())
136 arcs_in_graph.insert(it.get_curr());
137
139 for (auto it = matching.get_it(); it.has_curr(); it.next_ne())
140 {
141 auto * arc = it.get_curr();
142 if (arc == nullptr)
143 return false;
144
145 if (arcs_in_graph.search(arc) == nullptr)
146 return false;
147
148 auto * src = g.get_src_node(arc);
149 auto * tgt = g.get_tgt_node(arc);
150 if (src == nullptr or tgt == nullptr or src == tgt)
151 return false;
152
153 if (used_nodes.search(src) != nullptr)
154 return false;
155 used_nodes.insert(src);
156
157 if (used_nodes.search(tgt) != nullptr)
158 return false;
159 used_nodes.insert(tgt);
160 }
161
162 return true;
163 }
164
165
166 bool better(const Objective & lhs,
167 const Objective & rhs,
168 bool max_cardinality) noexcept
169 {
170 if (max_cardinality)
171 {
172 if (lhs.cardinality != rhs.cardinality)
173 return lhs.cardinality > rhs.cardinality;
174 return lhs.total_cost < rhs.total_cost;
175 }
176
177 if (lhs.total_cost != rhs.total_cost)
178 return lhs.total_cost < rhs.total_cost;
179
180 return lhs.cardinality > rhs.cardinality;
181 }
182
183
185 normalize_simple_edges(size_t n, const Cost_Edges & edges)
186 {
187 using Pair = std::pair<size_t, size_t>;
189
190 for (auto [u, v, c] : edges)
191 {
192 if (u >= n or v >= n or u == v)
193 continue;
194
195 if (u > v)
196 std::swap(u, v);
197
198 const Pair key{u, v};
199 auto * it = best.search(key);
200 if (it == nullptr)
201 best.insert(key, c);
202 else if (c < it->second)
203 it->second = c;
204 }
205
206 Cost_Edges result;
207 result.reserve(best.size());
208 for (auto it = best.get_it(); it.has_curr(); it.next_ne())
209 {
210 const auto & kv = it.get_curr();
211 result.append(std::make_tuple(kv.first.first, kv.first.second, kv.second));
212 }
213
214 quicksort_op(result,
215 [](const Cost_Edge & a, const Cost_Edge & b)
216 {
217 if (std::get<0>(a) != std::get<0>(b))
218 return std::get<0>(a) < std::get<0>(b);
219 if (std::get<1>(a) != std::get<1>(b))
220 return std::get<1>(a) < std::get<1>(b);
221 return std::get<2>(a) < std::get<2>(b);
222 });
223
224 return result;
225 }
226
227
228 Objective exact_minimum_cost_matching_optimum(size_t n,
229 const Cost_Edges & edges,
230 bool max_cardinality)
231 {
232 if (n == 0)
233 return Objective{0, 0};
234
235 ah_runtime_error_if(n > 24)
236 << "exact_minimum_cost_matching_optimum supports n <= 24";
237
238 constexpr long long NO_EDGE = std::numeric_limits<long long>::max() / 4;
239 auto c = Array<Array<long long>>::create(n);
240 for (size_t i = 0; i < n; ++i)
241 c(i) = Array<long long>(n, NO_EDGE);
242
243 for (auto [u, v, cost] : normalize_simple_edges(n, edges))
244 {
245 c(u)(v) = cost;
246 c(v)(u) = cost;
247 }
248
249 const uint64_t full_mask = (uint64_t{1} << n) - 1;
250 const size_t state_count = static_cast<size_t>(full_mask + 1);
251
252 Array<Objective> memo(state_count);
253 Array<char> seen(state_count, 0);
254
255 std::function<Objective(uint64_t)> solve = [&](uint64_t mask) -> Objective
256 {
257 if (mask == 0)
258 return Objective{0, 0};
259
260 const size_t pos = static_cast<size_t>(mask);
261 if (seen(pos))
262 return memo(pos);
263
264 const size_t i = static_cast<size_t>(std::countr_zero(mask));
265 const uint64_t rest = mask & ~(uint64_t{1} << i);
266
267 Objective best = solve(rest); // i unmatched
268
270 while (options != 0)
271 {
272 const size_t j = static_cast<size_t>(std::countr_zero(options));
273 options &= (options - 1);
274
275 if (c(i)(j) == NO_EDGE)
276 continue;
277
278 Objective candidate = solve(rest & ~(uint64_t{1} << j));
279 ++candidate.cardinality;
280 candidate.total_cost += c(i)(j);
281
282 if (better(candidate, best, max_cardinality))
283 best = candidate;
284 }
285
286 seen(pos) = 1;
287 memo(pos) = best;
288 return best;
289 };
290
291 return solve(full_mask);
292 }
293
294
297 const Cost_Edges & edges)
298 {
299 if (n == 0)
301
302 if (n % 2 == 1)
304
306 n, edges, true);
307 if (best.cardinality != n / 2)
309
311 true, best.total_cost, best.cardinality};
312 }
313
314
315 template <class GT>
317 {
318 Objective obj;
319 for (auto it = matching.get_it(); it.has_curr(); it.next_ne())
320 {
321 auto * arc = it.get_curr();
322 obj.total_cost += static_cast<long long>(arc->get_info());
323 ++obj.cardinality;
324 }
325 return obj;
326 }
327
328
329 template <class GT>
330 Objective solve_min_cost(const GT & g,
332 bool max_cardinality)
333 {
335 g,
336 matching,
337 Dft_Dist<GT>(),
340
341 EXPECT_EQ(res.cardinality, matching.size());
343 EXPECT_EQ(res.cardinality, obj.cardinality);
344 EXPECT_EQ(res.total_cost, obj.total_cost);
345
346 return obj;
347 }
348
349
350 template <class GT>
351 Objective solve_backend(size_t n,
352 const Cost_Edges & edges,
353 bool max_cardinality)
354 {
355 GT g = build_graph<GT>(n, edges);
357 const Objective got = solve_min_cost(g, matching, max_cardinality);
359 return got;
360 }
361
362
363 template <class GT>
365 solve_backend_perfect(size_t n,
366 const Cost_Edges & edges)
367 {
368 GT g = build_graph<GT>(n, edges);
371
372 if (got.feasible)
373 {
374 EXPECT_EQ(got.cardinality, n / 2);
375 EXPECT_EQ(matching.size(), got.cardinality);
377 }
378 else
379 {
380 EXPECT_TRUE(matching.is_empty());
381 }
382
383 return got;
384 }
385} // namespace
386
387
389{
390 using Graph = List_Backend;
391
392 Graph g;
394
395 const auto result = blossom_minimum_cost_matching<Graph>(g, matching);
396 EXPECT_EQ(result.cardinality, 0u);
397 EXPECT_EQ(result.total_cost, 0);
398 EXPECT_TRUE(matching.is_empty());
399}
400
401
403{
404 using Graph = List_Backend;
405
406 Graph g;
407 auto * n0 = g.insert_node(0);
408 auto * n1 = g.insert_node(1);
409 g.insert_arc(n0, n1, 9);
410
413 EXPECT_EQ(pure.cardinality, 0u);
414 EXPECT_EQ(pure.total_cost, 0);
415 EXPECT_TRUE(pure_matching.is_empty());
416
419 g,
423 true);
424 EXPECT_EQ(card.cardinality, 1u);
425 EXPECT_EQ(card.total_cost, 9);
426 EXPECT_EQ(card_matching.size(), 1u);
428}
429
430
432{
433 using Graph = List_Backend;
434
435 Graph g;
436 auto * n0 = g.insert_node(0);
437 auto * n1 = g.insert_node(1);
438 auto * n2 = g.insert_node(2);
439 auto * n3 = g.insert_node(3);
440
441 g.insert_arc(n0, n1, 11);
442 auto * cheapest = g.insert_arc(n0, n1, -3);
443 g.insert_arc(n2, n3, 4);
444 g.insert_arc(n1, n2, 10);
445
448
449 EXPECT_EQ(result.cardinality, 2u);
450 EXPECT_EQ(result.total_cost, 1);
452
453 bool uses_cheapest_parallel = false;
454 for (auto it = matching.get_it(); it.has_curr(); it.next_ne())
455 if (it.get_curr() == cheapest)
457
459}
460
461
463{
465
466 Digraph g;
467 auto * n0 = g.insert_node(0);
468 auto * n1 = g.insert_node(1);
469 g.insert_arc(n0, n1, 7);
470
473 std::domain_error);
474}
475
476
478{
479 using Graph = List_Backend;
480
481 Graph g;
482 auto * n0 = g.insert_node(0);
483 auto * n1 = g.insert_node(1);
484 g.insert_arc(n0, n1, std::numeric_limits<long long>::min());
485
488 std::overflow_error);
489}
490
491
493{
495
496 UGraph g;
497 auto * n0 = g.insert_node(0);
498 auto * n1 = g.insert_node(1);
499 const auto huge = static_cast<unsigned long long>(std::numeric_limits<long long>::max())
500 + 42ULL;
501 g.insert_arc(n0, n1, huge);
502
505 std::overflow_error);
506}
507
508
510{
511 using Graph = List_Backend;
512
513 const Cost_Edges edges = make_cost_edges({
514 {0, 1, 5},
515 {1, 2, -2},
516 {2, 3, 7},
517 {0, 3, 1},
518 {0, 2, 4},
519 {1, 3, 3}
520 });
521
522 Graph g = build_graph<Graph>(4, edges);
523
527
530
533
536 const auto functor_res = solver(g, functor_matching);
537
538 EXPECT_EQ(free_res.cardinality, alias_res.cardinality);
539 EXPECT_EQ(free_res.total_cost, alias_res.total_cost);
540 EXPECT_EQ(alias_res.cardinality, functor_res.cardinality);
541 EXPECT_EQ(alias_res.total_cost, functor_res.total_cost);
542}
543
544
546{
547 using Graph = List_Backend;
548
549 const Cost_Edges edges = make_cost_edges({
550 {0, 1, 8},
551 {0, 2, -4},
552 {1, 3, 2},
553 {2, 3, 7},
554 {2, 4, -3},
555 {3, 5, 6},
556 {4, 5, 1}
557 });
558
559 Graph g = build_graph<Graph>(6, edges);
560
561 struct Negated_Cost
562 {
563 long long operator()(Graph::Arc * arc) const
564 {
565 return -arc->get_info();
566 }
567 };
568
569 for (bool max_cardinality : {false, true})
570 {
574
578
579 EXPECT_EQ(min_res.cardinality, weighted_res.cardinality);
580 EXPECT_EQ(min_res.total_cost, -weighted_res.total_weight);
581 }
582}
583
584
586{
587 using Graph = List_Backend;
588
589 Graph g;
592
593 EXPECT_TRUE(result.feasible);
594 EXPECT_EQ(result.cardinality, 0u);
595 EXPECT_EQ(result.total_cost, 0);
596 EXPECT_TRUE(matching.is_empty());
597}
598
599
601{
602 using Graph = List_Backend;
603
604 Graph g;
605 auto * n0 = g.insert_node(0);
606 auto * n1 = g.insert_node(1);
607 auto * n2 = g.insert_node(2);
608 g.insert_arc(n0, n1, 1);
609 g.insert_arc(n1, n2, 2);
610 g.insert_arc(n0, n2, 3);
611
614
615 EXPECT_FALSE(result.feasible);
616 EXPECT_TRUE(matching.is_empty());
617}
618
619
621{
622 using Graph = List_Backend;
623
624 Graph g;
625 auto * n0 = g.insert_node(0);
626 auto * n1 = g.insert_node(1);
627 auto * n2 = g.insert_node(2);
628 g.insert_node(3);
629 g.insert_arc(n0, n1, 5);
630 g.insert_arc(n1, n2, 1);
631
634
635 EXPECT_FALSE(result.feasible);
636 EXPECT_TRUE(matching.is_empty());
637}
638
639
641{
643
644 Digraph g;
645 auto * n0 = g.insert_node(0);
646 auto * n1 = g.insert_node(1);
647 g.insert_arc(n0, n1, 4);
648
651 std::domain_error);
652}
653
654
656{
657 using Graph = List_Backend;
658
659 const Cost_Edges edges = make_cost_edges({
660 {0, 1, 9},
661 {0, 2, 4},
662 {0, 5, 2},
663 {1, 2, 7},
664 {1, 3, 3},
665 {2, 4, 6},
666 {3, 4, 5},
667 {3, 5, 1},
668 {4, 5, 8}
669 });
670
671 Graph g = build_graph<Graph>(6, edges);
673
676
677 ASSERT_TRUE(expected.feasible);
678 EXPECT_TRUE(got.feasible);
679 EXPECT_EQ(got.cardinality, expected.cardinality);
680 EXPECT_EQ(got.total_cost, expected.total_cost);
681 EXPECT_EQ(matching.size(), got.cardinality);
683}
684
685
687{
688 using Graph = List_Backend;
689
690 const Cost_Edges edges = make_cost_edges({
691 {0, 1, 5},
692 {0, 3, 9},
693 {1, 2, 2},
694 {2, 3, 4}
695 });
696
697 Graph g = build_graph<Graph>(4, edges);
698
702
704 g, alias_matching);
706 g, free_matching);
707
709 const auto functor_res = solver(g, functor_matching);
710
711 EXPECT_EQ(alias_res.feasible, free_res.feasible);
712 EXPECT_EQ(alias_res.feasible, functor_res.feasible);
713 EXPECT_EQ(alias_res.cardinality, free_res.cardinality);
714 EXPECT_EQ(alias_res.cardinality, functor_res.cardinality);
715 EXPECT_EQ(alias_res.total_cost, free_res.total_cost);
716 EXPECT_EQ(alias_res.total_cost, functor_res.total_cost);
717}
718
719
721{
722 const Cost_Edges edges = make_cost_edges({
723 {0, 1, 5},
724 {1, 2, -3},
725 {2, 3, 4},
726 {3, 4, -6},
727 {4, 5, 5},
728 {5, 0, 2},
729 {0, 3, 1},
730 {1, 4, 7},
731 {2, 5, -2},
732 {1, 3, 3}
733 });
734
735 for (bool max_cardinality : {false, true})
736 {
738 6, edges, max_cardinality);
739
740 const Objective list_obj = solve_backend<List_Backend>(6, edges, max_cardinality);
741 const Objective sgraph_obj = solve_backend<SGraph_Backend>(6, edges, max_cardinality);
742 const Objective agraph_obj = solve_backend<AGraph_Backend>(6, edges, max_cardinality);
743
747 }
748}
749
750
752{
753 std::mt19937_64 rng(0xA11CE55ULL);
754 std::uniform_int_distribution<int> n_half_dist(1, 5); // n in {2,4,6,8,10}
755 std::uniform_int_distribution<int> c_dist(-15, 20);
756 std::bernoulli_distribution edge_coin(0.45);
757 std::bernoulli_distribution duplicate_coin(0.15);
758 std::bernoulli_distribution loop_coin(0.08);
759
760 for (size_t trial = 0; trial < 100; ++trial)
761 {
762 const size_t n = static_cast<size_t>(2 * n_half_dist(rng));
763 Cost_Edges edges;
764 edges.reserve(n * (n - 1));
765
766 for (size_t i = 0; i < n; ++i)
767 for (size_t j = i + 1; j < n; ++j)
768 if (edge_coin(rng))
769 {
770 edges.append(std::make_tuple(i, j, static_cast<long long>(c_dist(rng))));
771 if (duplicate_coin(rng))
772 edges.append(std::make_tuple(i, j, static_cast<long long>(c_dist(rng))));
773 }
774
775 for (size_t i = 0; i < n; ++i)
776 if (loop_coin(rng))
777 edges.append(std::make_tuple(i, i, static_cast<long long>(c_dist(rng))));
778
780 const auto list_obj = solve_backend_perfect<List_Backend>(n, edges);
783
784 EXPECT_EQ(list_obj.feasible, expected.feasible) << "trial=" << trial;
785 EXPECT_EQ(sgraph_obj.feasible, expected.feasible) << "trial=" << trial;
786 EXPECT_EQ(agraph_obj.feasible, expected.feasible) << "trial=" << trial;
787
788 if (expected.feasible)
789 {
790 EXPECT_EQ(list_obj.cardinality, expected.cardinality) << "trial=" << trial;
791 EXPECT_EQ(sgraph_obj.cardinality, expected.cardinality) << "trial=" << trial;
792 EXPECT_EQ(agraph_obj.cardinality, expected.cardinality) << "trial=" << trial;
793 EXPECT_EQ(list_obj.total_cost, expected.total_cost) << "trial=" << trial;
794 EXPECT_EQ(sgraph_obj.total_cost, expected.total_cost) << "trial=" << trial;
795 EXPECT_EQ(agraph_obj.total_cost, expected.total_cost) << "trial=" << trial;
796 }
797 }
798}
799
800
802{
803 std::mt19937_64 rng(0xC0FFEEULL);
804 std::uniform_int_distribution<int> n_dist(2, 10);
805 std::uniform_int_distribution<int> c_dist(-15, 20);
806 std::bernoulli_distribution edge_coin(0.45);
807 std::bernoulli_distribution duplicate_coin(0.15);
808 std::bernoulli_distribution loop_coin(0.10);
809
810 for (size_t trial = 0; trial < 120; ++trial)
811 {
812 const size_t n = static_cast<size_t>(n_dist(rng));
813 Cost_Edges edges;
814 edges.reserve(n * (n - 1));
815
816 for (size_t i = 0; i < n; ++i)
817 for (size_t j = i + 1; j < n; ++j)
818 if (edge_coin(rng))
819 {
820 edges.append(std::make_tuple(i, j, static_cast<long long>(c_dist(rng))));
821 if (duplicate_coin(rng))
822 edges.append(std::make_tuple(i, j, static_cast<long long>(c_dist(rng))));
823 }
824
825 for (size_t i = 0; i < n; ++i)
826 if (loop_coin(rng))
827 edges.append(std::make_tuple(i, i, static_cast<long long>(c_dist(rng))));
828
829 for (bool max_cardinality : {false, true})
830 {
832 n, edges, max_cardinality);
833
834 const Objective list_obj = solve_backend<List_Backend>(
835 n, edges, max_cardinality);
837 n, edges, max_cardinality);
839 n, edges, max_cardinality);
840
842 << "trial=" << trial << ", mode=" << max_cardinality;
844 << "trial=" << trial << ", mode=" << max_cardinality;
846 << "trial=" << trial << ", mode=" << max_cardinality;
847 }
848 }
849}
850
851
853{
854 using Clock = std::chrono::steady_clock;
855
856 constexpr size_t n = 1400;
857 constexpr size_t oracle_n = 24; // exact_* supports n <= 24
858
859# ifdef NDEBUG
860 constexpr long long kBudgetGeneralMs = 12000;
861 constexpr long long kBudgetPerfectMs = 12000;
862# else
863 constexpr long long kBudgetGeneralMs = 30000;
864 constexpr long long kBudgetPerfectMs = 30000;
865# endif
866
867 std::mt19937_64 rng(0xBADC0FFEE0DDF00DULL);
868 std::uniform_int_distribution<int> c_dist(-100, 150);
869 std::bernoulli_distribution edge_coin(0.015);
870 std::bernoulli_distribution duplicate_coin(0.08);
871 std::bernoulli_distribution loop_coin(0.02);
872
873 Cost_Edges edges;
874 edges.reserve(n * 18);
875
876 for (size_t i = 0; i < n; ++i)
877 for (size_t j = i + 1; j < n; ++j)
878 if (edge_coin(rng))
879 {
880 edges.append(std::make_tuple(i, j, static_cast<long long>(c_dist(rng))));
881 if (duplicate_coin(rng))
882 edges.append(std::make_tuple(i, j, static_cast<long long>(c_dist(rng))));
883 }
884
885 for (size_t i = 0; i < n; ++i)
886 if (loop_coin(rng))
887 edges.append(std::make_tuple(i, i, static_cast<long long>(c_dist(rng))));
888
889 if (edges.is_empty())
890 edges.append(std::make_tuple(0, 1, 1));
891
893 oracle_edges.reserve(edges.size() / 4 + 1);
894 for (const auto & [u, v, c] : edges)
895 if (u < oracle_n and v < oracle_n)
896 oracle_edges.append(std::make_tuple(u, v, c));
897
898 // Sanity pass through the same exact/solver entry points used by
899 // correctness tests, but on a bounded oracle-sized subproblem.
900 for (bool max_cardinality : {false, true})
901 {
904 const Objective list_obj = solve_backend<List_Backend>(
910
914 }
915
924
928 if (expected_perfect.feasible)
929 {
930 ASSERT_EQ(list_perfect_small.cardinality, expected_perfect.cardinality);
931 ASSERT_EQ(sgraph_perfect_small.cardinality, expected_perfect.cardinality);
932 ASSERT_EQ(agraph_perfect_small.cardinality, expected_perfect.cardinality);
933 ASSERT_EQ(list_perfect_small.total_cost, expected_perfect.total_cost);
934 ASSERT_EQ(sgraph_perfect_small.total_cost, expected_perfect.total_cost);
935 ASSERT_EQ(agraph_perfect_small.total_cost, expected_perfect.total_cost);
936 }
937
938 const auto general_start = Clock::now();
939 const Objective list_general = solve_backend<List_Backend>(n, edges, true);
940 const Objective sgraph_general = solve_backend<SGraph_Backend>(n, edges, true);
941 const Objective agraph_general = solve_backend<AGraph_Backend>(n, edges, true);
942 const auto general_end = Clock::now();
943 const auto general_elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
944 general_end - general_start).count();
945
949 << "general perf regression: n=" << n
950 << ", edges=" << edges.size()
951 << ", elapsed_ms=" << general_elapsed_ms
952 << ", budget_ms=" << kBudgetGeneralMs;
953
954 const auto perfect_start = Clock::now();
958 const auto perfect_end = Clock::now();
959 const auto perfect_elapsed_ms = std::chrono::duration_cast<std::chrono::milliseconds>(
960 perfect_end - perfect_start).count();
961
962 ASSERT_EQ(sgraph_perfect.feasible, list_perfect.feasible);
963 ASSERT_EQ(agraph_perfect.feasible, list_perfect.feasible);
964 if (list_perfect.feasible)
965 {
966 ASSERT_EQ(sgraph_perfect.cardinality, list_perfect.cardinality);
967 ASSERT_EQ(agraph_perfect.cardinality, list_perfect.cardinality);
968 ASSERT_EQ(sgraph_perfect.total_cost, list_perfect.total_cost);
969 ASSERT_EQ(agraph_perfect.total_cost, list_perfect.total_cost);
970 }
971
973 << "perfect perf regression: n=" << n
974 << ", edges=" << edges.size()
975 << ", elapsed_ms=" << perfect_elapsed_ms
976 << ", budget_ms=" << kBudgetPerfectMs;
977}
Maximum-weight matching in general undirected graphs.
Minimum-cost matching in general undirected graphs.
#define ah_runtime_error_if(C)
Throws std::runtime_error if condition holds.
Definition ah-errors.H:266
bool verify_matching(const Graph &g, const DynDlist< Graph::Arc * > &matching)
Verifies that a matching is valid:
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:139
static Array create(size_t n)
Create an array with n logical elements.
Definition tpl_array.H:194
Functor wrapper for minimum-cost general matching.
Functor wrapper for minimum-cost perfect general matching.
Default distance accessor for arc weights.
Generic directed graph (digraph) wrapper template.
Definition graph-dry.H:3854
Dynamic doubly linked list with O(1) size and bidirectional access.
Generic key-value map implemented on top of a binary search tree.
Dynamic set backed by balanced binary search trees with automatic memory management.
Key * insert(const Key &key)
Inserts a key into the dynamic set.
Arc for graphs implemented with simple adjacency lists.
Definition tpl_sgraph.H:197
Graph implemented with double-linked adjacency lists.
Definition tpl_graph.H:428
virtual Node * insert_node(Node *node) noexcept
Insertion of a node already allocated.
Definition tpl_graph.H:524
Arc Arc
The node class type.
Definition tpl_graph.H:433
Arc * insert_arc(Node *src_node, Node *tgt_node, void *a)
Definition tpl_graph.H:604
Graph class implemented with singly-linked adjacency lists.
Definition tpl_sgraph.H:274
Node * get_src_node(Arc *arc) const noexcept
Return the source node of arc (only for directed graphs)
Definition graph-dry.H:737
Node * get_tgt_node(Arc *arc) const noexcept
Return the target node of arc (only for directed graphs)
Definition graph-dry.H:743
#define TEST(name)
static mt19937 rng
std::chrono::steady_clock Clock
DynArray< Graph::Node * > nodes
Definition graphpic.C:406
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
and
Check uniqueness with explicit hash + equality functors.
bool operator==(const DynList< T > &l1, const DynList< T > &l2)
Equality operator for DynList.
Divide_Conquer_DP_Result< Cost > divide_and_conquer_partition_dp(const size_t groups, const size_t n, Transition_Cost_Fn transition_cost, const Cost inf=dp_optimization_detail::default_inf< Cost >())
Optimize partition DP using divide-and-conquer optimization.
static std::atomic< bool > init
Definition hash-fct.C:53
void quicksort_op(C< T > &a, const Compare &cmp=Compare(), const size_t threshold=Quicksort_Threshold)
Optimized quicksort for containers using operator().
static struct argp_option options[]
Definition ntreepic.C:1886
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
Result of minimum-cost perfect matching.
Cost_Type total_cost
Total cost if feasible.
Array-based graph implementation.
Dynamic array container with automatic resizing.
Dynamic key-value map based on balanced binary search trees.
Dynamic set implementations based on balanced binary search trees.
Generic graph and digraph implementations.
Simple graph implementation with adjacency lists.