45#include <gtest/gtest.h>
54#include <unordered_map>
55#include <unordered_set>
68 using Weighted_Edge = std::tuple<size_t, size_t, long long>;
78 size_t cardinality = 0;
79 long long total_weight = 0;
83 return cardinality ==
other.cardinality
84 and total_weight ==
other.total_weight;
90 GT build_graph(
size_t n,
const std::vector<Weighted_Edge> & edges)
93 std::vector<typename GT::Node *>
nodes;
96 for (
size_t i = 0; i < n; ++i)
99 for (
const auto & [u, v,
w] : edges)
101 if (u >= n
or v >= n)
115 for (
typename GT::Arc_Iterator it(g); it.has_curr(); it.next_ne())
118 std::unordered_set<typename GT::Node *>
used_nodes;
119 for (
auto it =
matching.get_it(); it.has_curr(); it.next_ne())
121 auto * arc = it.get_curr();
130 if (src ==
nullptr or tgt ==
nullptr or src == tgt)
144 bool better(
const Objective &
lhs,
145 const Objective &
rhs,
150 if (
lhs.cardinality !=
rhs.cardinality)
151 return lhs.cardinality >
rhs.cardinality;
152 return lhs.total_weight >
rhs.total_weight;
155 if (
lhs.total_weight !=
rhs.total_weight)
156 return lhs.total_weight >
rhs.total_weight;
158 return lhs.cardinality >
rhs.cardinality;
162 std::vector<Weighted_Edge>
165 using Pair = std::pair<size_t, size_t>;
168 size_t operator()(
const Pair & p)
const noexcept
170 const size_t h1 = std::hash<size_t>{}(p.first);
171 const size_t h2 = std::hash<size_t>{}(p.second);
172 return h1 ^ (
h2 + 0x9e3779b97f4a7c15ULL + (
h1 << 6) + (
h1 >> 2));
176 std::unordered_map<Pair, long long, Pair_Hash>
best;
177 best.reserve(edges.size() * 2 + 1);
179 for (
auto [u, v,
w] : edges)
181 if (u >= n
or v >= n
or u == v)
187 const Pair key{u, v};
188 const auto it =
best.find(key);
189 if (it ==
best.end()
or w > it->second)
193 std::vector<Weighted_Edge> result;
194 result.reserve(
best.size());
195 for (
const auto &
kv :
best)
198 std::sort(result.begin(), result.end(),
199 [](
const Weighted_Edge & a,
const Weighted_Edge & b)
201 if (std::get<0>(a) != std::get<0>(b))
202 return std::get<0>(a) < std::get<0>(b);
203 if (std::get<1>(a) != std::get<1>(b))
204 return std::get<1>(a) < std::get<1>(b);
205 return std::get<2>(a) < std::get<2>(b);
213 const std::vector<Weighted_Edge> & edges,
217 return Objective{0, 0};
221 <<
"exact_weighted_matching_optimum supports n <= 24";
223 constexpr long long NO_EDGE = std::numeric_limits<long long>::min() / 4;
224 std::vector<std::vector<long long>>
w(n, std::vector<long long>(n,
NO_EDGE));
233 ? std::numeric_limits<uint64_t>::max()
236 const size_t state_count =
static_cast<size_t>(
full_mask + 1);
237 std::vector<Objective>
memo(state_count);
238 std::vector<char>
seen(state_count, 0);
243 return Objective{0, 0};
245 const size_t pos =
static_cast<size_t>(mask);
285 for (
auto it =
matching.get_it(); it.has_curr(); it.next_ne())
287 auto * arc = it.get_curr();
288 obj.total_weight +=
static_cast<long long>(arc->get_info());
318 std::vector<std::pair<size_t, size_t>>
322 std::vector<std::pair<size_t, size_t>> pairs;
325 for (
auto it =
matching.get_it(); it.has_curr(); it.next_ne())
327 auto * arc = it.get_curr();
328 size_t u =
static_cast<size_t>(g.
get_src_node(arc)->get_info());
329 size_t v =
static_cast<size_t>(g.
get_tgt_node(arc)->get_info());
332 pairs.emplace_back(u, v);
335 std::sort(pairs.begin(), pairs.end());
340 struct Solve_Snapshot
343 std::vector<std::pair<size_t, size_t>> pairs;
349 const std::vector<Weighted_Edge> & edges,
359 return Solve_Snapshot{
367 const std::vector<Weighted_Edge> & edges,
385 class WeightedBlossomTypedTest :
public ::testing::Test
426 const std::vector<Weighted_Edge> edges = {
443 const std::vector<Weighted_Edge> edges = {
460 const std::vector<std::pair<size_t, std::vector<Weighted_Edge>>>
cases = {
461 {4, {{1, 2, 8}, {1, 3, 9}, {2, 3, 10}, {3, 4, 7}}},
462 {6, {{1, 2, 9}, {1, 3, 8}, {2, 3, 10}, {1, 4, 5}, {4, 5, 4}, {1, 6, 3}}},
464 {{1, 2, 23}, {1, 5, 22}, {1, 6, 15}, {2, 3, 25},
465 {3, 4, 22}, {4, 5, 25}, {4, 8, 14}, {5, 7, 13}}},
467 {{1, 2, 45}, {1, 5, 45}, {2, 3, 50}, {3, 4, 45}, {4, 5, 50},
468 {1, 6, 30}, {3, 9, 35}, {4, 8, 28}, {5, 7, 26}, {9, 10, 5}}},
470 {{1, 2, 40}, {1, 3, 40}, {2, 3, 60}, {2, 4, 55}, {3, 5, 55},
471 {4, 5, 50}, {1, 8, 15}, {5, 7, 30}, {7, 6, 10}, {8, 10, 10}, {4, 9, 30}}}
492 std::mt19937_64
rng(0xC0FFEE1234ULL);
493 std::uniform_int_distribution<int>
n_dist(1, 11);
494 std::uniform_real_distribution<double>
p_dist(0.0, 1.0);
495 std::uniform_int_distribution<int>
w_dist(-10, 24);
499 const size_t n =
static_cast<size_t>(
n_dist(
rng));
500 std::vector<Weighted_Edge> edges;
502 for (
size_t u = 0; u < n; ++u)
503 for (
size_t v = u + 1; v < n; ++v)
507 edges.emplace_back(u, v,
static_cast<long long>(
w_dist(
rng)));
511 edges.emplace_back(u, v,
static_cast<long long>(
w_dist(
rng)));
517 edges.emplace_back(
static_cast<size_t>(
rng() % n),
518 static_cast<size_t>(
rng() % n),
529 const std::vector<Weighted_Edge> edges = {
566 std::mt19937_64
rng(0xB10550ULL);
567 std::uniform_int_distribution<int>
n_dist(1, 10);
568 std::uniform_real_distribution<double>
p_dist(0.0, 1.0);
569 std::uniform_int_distribution<int>
w_dist(-12, 25);
573 const size_t n =
static_cast<size_t>(
n_dist(
rng));
574 std::vector<Weighted_Edge> edges;
576 for (
size_t u = 0; u < n; ++u)
577 for (
size_t v = u + 1; v < n; ++v)
581 edges.emplace_back(u, v,
static_cast<long long>(
w_dist(
rng)));
583 edges.emplace_back(u, v,
static_cast<long long>(
w_dist(
rng)));
588 edges.emplace_back(
static_cast<size_t>(
rng() % n),
589 static_cast<size_t>(
rng() % n),
616 struct Positive_Arc_Filter
619 bool operator()(
Arc * arc)
const noexcept
621 return arc->get_info() > 0;
646 Positive_Arc_Filter>(g,
649 Positive_Arc_Filter(),
663 auto * n0 = g.insert_node(0);
664 auto * n1 = g.insert_node(1);
665 g.insert_arc(n0, n1, 42);
678 const std::vector<Weighted_Edge> edges = {
712 namespace mw = blossom_weighted_detail::mwmatching;
716 edges.
append(mw::Edge<double>(0, 1, 1.5));
717 edges.
append(mw::Edge<double>(1, 2, 2.0));
718 edges.
append(mw::Edge<double>(0, 2, 1.0));
722 const auto [u, v] =
sol[0];
727 mw::Edge<double>(0, 1, std::numeric_limits<double>::quiet_NaN()));
729 std::invalid_argument);
731 std::invalid_argument);
735 mw::Edge<double>(0, 1, std::numeric_limits<double>::infinity()));
737 std::invalid_argument);
743 namespace mw = blossom_weighted_detail::mwmatching;
748 std::invalid_argument);
751 duplicate.
append(mw::Edge<long long>(0, 1, 8));
752 duplicate.
append(mw::Edge<long long>(1, 0, 9));
754 std::invalid_argument);
760 namespace mw = blossom_weighted_detail::mwmatching;
762 const long long max_safe = std::numeric_limits<long long>::max() / 4;
763 const long long min_safe = std::numeric_limits<long long>::min() / 4;
768 std::invalid_argument);
772 EXPECT_THROW((mw::adjust_weights_for_maximum_cardinality_matching(
774 std::invalid_argument);
780 std::invalid_argument);
787 (blossom_weighted_detail::to_ll_checked<unsigned long long>(
788 std::numeric_limits<unsigned long long>::max())),
789 std::overflow_error);
792 (blossom_weighted_detail::to_ll_checked<long long>(
793 std::numeric_limits<long long>::max())));
796 (blossom_weighted_detail::to_ll_checked<long long>(
797 std::numeric_limits<long long>::min())));
800 (blossom_weighted_detail::to_ll_checked<unsigned long long>(
801 static_cast<unsigned long long>(
802 std::numeric_limits<long long>::max()))));
805 (blossom_weighted_detail::to_ll_checked<long long>(0
LL)));
811 std::mt19937_64
rng(0x5EED5EED1234ULL);
812 std::uniform_int_distribution<int>
n_dist(45, 85);
813 std::uniform_real_distribution<double>
p_dist(0.0, 1.0);
814 std::uniform_int_distribution<int>
w_dist(-40, 120);
818 const size_t n =
static_cast<size_t>(
n_dist(
rng));
819 std::vector<Weighted_Edge> edges;
820 edges.reserve((n * n) / 5);
822 for (
size_t u = 0; u < n; ++u)
823 for (
size_t v = u + 1; v < n; ++v)
827 edges.emplace_back(u, v,
static_cast<long long>(
w_dist(
rng)));
829 edges.emplace_back(u, v,
static_cast<long long>(
w_dist(
rng)));
Maximum-weight matching in general undirected graphs.
#define ah_runtime_error_if(C)
Throws std::runtime_error if condition holds.
TYPED_TEST_SUITE(AStarHeapTest, HeapTypes)
List_Graph< Node, Arc > Graph
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.
T & append(const T &data)
Append a copy of data
void reserve(size_t cap)
Reserves cap cells into the array.
Default distance accessor for arc weights.
Generic directed graph (digraph) wrapper template.
Dynamic doubly linked list with O(1) size and bidirectional access.
Arc for graphs implemented with simple adjacency lists.
Graph implemented with double-linked adjacency lists.
virtual Node * insert_node(Node *node) noexcept
Insertion of a node already allocated.
Arc * insert_arc(Node *src_node, Node *tgt_node, void *a)
Graph class implemented with singly-linked adjacency lists.
Node * get_src_node(Arc *arc) const noexcept
Return the source node of arc (only for directed graphs)
Node * get_tgt_node(Arc *arc) const noexcept
Return the target node of arc (only for directed graphs)
DynArray< Graph::Node * > nodes
Blossom_Weighted_Result< long long > blossom_maximum_weight_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, Weight weight=Weight(), SA sa=SA(), const bool max_cardinality=false)
Alias for compute_maximum_weight_general_matching().
static Geom_Number objective(const Point &p)
Main namespace for Aleph-w library functions.
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 struct argp_option options[]
Default filter for filtered iterators on arcs.
Arc of graph implemented with double-linked adjacency lists.
Array-based graph implementation.
Generic graph and digraph implementations.
Simple graph implementation with adjacency lists.
TYPED_TEST(WeightedBlossomTypedTest, EmptyGraph)