/******************************************************************************* * tests/container/radix_heap_test.cpp * * Part of tlx - http://panthema.net/tlx * * Copyright (C) 2018 Manuel Penschuck * * All rights reserved. Published under the Boost Software License, Version 1.0 ******************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include /******************************************************************************/ // Idiot-proof reference implementation of flag field against which we test class naive_bitarray { public: naive_bitarray(size_t n) : bits_(n, false) { } void set_bit(size_t i) { bits_.at(i) = true; } void clear_bit(size_t i) { bits_.at(i) = false; } bool is_set(size_t i) const { return bits_.at(i); } void clear_all() { for (size_t i = 0; i < bits_.size(); i++) bits_[i] = false; } bool empty() { for (size_t i = 0; i < bits_.size(); i++) if (bits_[i]) return false; return true; } size_t find_lsb() { for (size_t i = 0; i < bits_.size(); i++) if (bits_[i]) return i; die("find_lsb on empty field"); } void swap(naive_bitarray& o) { std::swap(bits_, o.bits_); } protected: std::vector bits_; }; // Perform the same tests for a number of different compile-time settings template void bitarray_test(std::mt19937& rng) { Field f; // set each (for large instances, a random subset) bit, search it, clear it { std::uniform_int_distribution skip_dist(0, Size / 4096); for (size_t i = 0; i < Size; i++) { die_unless(f.empty()); f.set_bit(i); die_if(f.empty()); die_unequal(i, f.find_lsb()); f.clear_bit(i); die_unless(f.empty()); if (Size > 64 * 64 + 1) { i += skip_dist(rng); } } } Field f0; std::swap(f0, f); // randomly flip bits std::uniform_int_distribution distr(0, Size - 1); std::uniform_real_distribution rdist; naive_bitarray nf(Size); for (size_t i = 0; i < std::min(100, Size * 10); i++) { const auto idx = distr(rng); die_unequal(nf.is_set(idx), f.is_set(idx)); if (nf.is_set(idx)) { nf.clear_bit(idx); f.clear_bit(idx); } else { nf.set_bit(idx); f.set_bit(idx); } die_unequal(nf.empty(), f.empty()); if (f.empty()) continue; die_unequal(nf.find_lsb(), f.find_lsb()); // clear lsb to increase chance of observing changes on more // significant bits if (rdist(rng) > 0.3) { const auto first = f.find_lsb(); nf.clear_bit(first); f.clear_bit(first); } } f.set_bit(0); die_unless(f0.empty()); Field f1; die_unless(f1.empty()); std::swap(f1, f); die_unless(f.empty()); die_if(f1.empty()); f1.clear_all(); die_unless(f1.empty()); } void test_main_bitarray(std::mt19937& rng) { bitarray_test, 32>(rng); bitarray_test, 64>(rng); // 1 layer bitarray_test, 32>(rng); bitarray_test, 33>(rng); bitarray_test, 63>(rng); bitarray_test, 64>(rng); // 2 layers bitarray_test, 65>(rng); bitarray_test, 500>(rng); bitarray_test, 1001>(rng); bitarray_test, 4004>(rng); bitarray_test, 4096>(rng); // 3 layers bitarray_test, 64 * 64 + 1>( rng); bitarray_test, 1 << 18>(rng); } /******************************************************************************/ template void int_rank_test(std::mt19937& prng) { using IR = tlx::radix_heap_detail::IntegerRank; constexpr T min = std::numeric_limits::min(); constexpr T max = std::numeric_limits::max(); constexpr T zero = 0; constexpr T one = 1; // Check that int_at_rank and rank_of_int are inverse die_unequal(min, IR::int_at_rank(IR::rank_of_int(min))); die_unequal(min + one, IR::int_at_rank(IR::rank_of_int(min + one))); die_unequal(zero, IR::int_at_rank(IR::rank_of_int(zero))); die_unequal(one, IR::int_at_rank(IR::rank_of_int(one))); die_unequal(max - one, IR::int_at_rank(IR::rank_of_int(max - one))); die_unequal(max, IR::int_at_rank(IR::rank_of_int(max))); // Check that random pairs keep their relative order std::uniform_int_distribution dist; for (size_t i = 0; i < 1000; i++) { // draw two random numbers a, b s.t. a < b T a = dist(prng); T b; do { b = dist(prng); } while (b == a); if (a > b) std::swap(a, b); die_unless(IR::rank_of_int(a) < IR::rank_of_int(b)); // Check inverse for random numbers die_unequal(a, IR::int_at_rank(IR::rank_of_int(a))); die_unequal(b, IR::int_at_rank(IR::rank_of_int(b))); } } void test_main_int_rank(std::mt19937& prng) { int_rank_test(prng); int_rank_test(prng); int_rank_test(prng); int_rank_test(prng); int_rank_test(prng); int_rank_test(prng); } /******************************************************************************/ //! Check that the computation of lower- and upper bounds of each bucket //! works properly, and that they are mapped back to bucket indices properly template void test_bucket_bounds() { constexpr bool debug = false; using Comp = tlx::radix_heap_detail::BucketComputation; Comp comp; LOG << "test_bucket_bounds<" "Radix=" << Radix << ", sizeof(T)=" << sizeof(T) << ">: " "num_buckets=" << size_t(comp.num_buckets) << "\n"; die_unequal(comp.lower_bound(0u), std::numeric_limits::min()); die_unequal(comp.upper_bound(comp.num_buckets - 1u), std::numeric_limits::max()); for (size_t i = 1; i < comp.num_buckets; i++) { die_unless(comp.lower_bound(i - 1) < comp.lower_bound(i)); } for (size_t i = 0; i < comp.num_buckets; i++) { const auto lb = comp.lower_bound(i); const auto ub = comp.upper_bound(i); const auto mid = (ub - lb) / 2 + lb; die_unless(lb >= ub - lb); die_unless(lb <= mid); die_unless(mid <= ub); die_unequal(comp(lb, 0u), i); die_unequal(comp(mid, 0u), i); die_unequal(comp(ub, 0u), i); } } void test_main_bucket(std::mt19937&) { test_bucket_bounds<2, uint32_t>(); test_bucket_bounds<8, uint32_t>(); test_bucket_bounds<64, uint32_t>(); test_bucket_bounds<2, uint64_t>(); test_bucket_bounds<8, uint64_t>(); test_bucket_bounds<64, uint64_t>(); } /******************************************************************************/ namespace tlx { using payload_type = uint64_t; using value_type = std::tuple; class KeyExtractor { public: int64_t operator () (const value_type& p) const { return std::get<0>(p); } }; // force instantiations template class RadixHeap; } // namespace tlx /******************************************************************************/ //! Fill radix heap completely and then check whether values are output //! in the correct order. This function also checks that the heap is stable template void allin_allout(std::mt19937& prng, KeyType min, KeyType max, size_t n) { constexpr bool debug = false; LOG << "allin_allout<" "sizeof(T)=" << sizeof(KeyType) << ", Radix=" << Radix << ">(" "min=" << min << ", " "max=" << max << ", " "n=" << n << ")\n"; using payload_type = uint64_t; using value_type = std::tuple; auto heap = tlx::make_radix_heap( [](const value_type& p) { return std::get<0>(p); }); std::vector > values; values.reserve(n); std::uniform_int_distribution distr(min, max); die_unless(heap.empty()); KeyType running_min = std::numeric_limits::max(); for (size_t i = 0; i < n; i++) { die_unequal(heap.size(), i); const auto key = distr(prng); heap.push(value_type{ key, i }); values.emplace_back(key, i); running_min = std::min(running_min, key); die_unequal(running_min, heap.peak_top_key()); die_if(heap.empty()); } std::sort(values.begin(), values.end()); for (size_t i = 0; i < n; i++) { die_unequal(heap.size(), n - i); const auto exp_key = values[i].first; die_unequal(heap.peak_top_key(), exp_key); die_unequal(std::get<0>(heap.top()), exp_key); heap.pop(); } die_unless(heap.empty()); } //! Fill radix heap completely and then check whether values are output //! in the correct order. This function also checks that the heap is stable template void allin_allout_pair(std::mt19937& prng, KeyType min, KeyType max, size_t n) { constexpr bool debug = false; LOG << "allin_allout_pair<" "sizeof(T)=" << sizeof(KeyType) << ", Radix=" << Radix << ">(" "min=" << min << ", " "max=" << max << ", " "n=" << n << ")\n"; using payload_type = uint64_t; tlx::RadixHeapPair heap; std::vector > values; values.reserve(n); std::uniform_int_distribution distr(min, max); die_unless(heap.empty()); KeyType running_min = std::numeric_limits::max(); for (size_t i = 0; i < n; i++) { die_unequal(heap.size(), i); const auto key = distr(prng); heap.push({ key, i }); values.emplace_back(key, i); running_min = std::min(running_min, key); die_unequal(running_min, heap.peak_top_key()); die_if(heap.empty()); } std::sort(values.begin(), values.end()); for (size_t i = 0; i < n; i++) { die_unequal(heap.size(), n - i); const auto exp_key = values[i].first; die_unequal(heap.peak_top_key(), exp_key); die_unequal(heap.top().first, exp_key); heap.pop(); } die_unless(heap.empty()); } enum class PullMode { Single, AllOfKey, ExtractBucket }; //! Performs interleaved insertions and pop operations and checks it against //! the STL priority queue template void random_inout(std::mt19937& prng, const KeyType min, const KeyType max, const KeyType iters, const double insert_prob, const size_t prefill_n, const PullMode pm) { constexpr bool debug = false; LOG << "random_inout<" "sizeof(T)=" << sizeof(KeyType) << ", Radix=" << Radix << ">(" "min=" << min << ", " "max=" << max << ", " "iters=" << iters << ", " "insert_prob=" << insert_prob << ", " "prefill_n=" << prefill_n << ", " "pm=" << static_cast(pm) << ")\n"; using payload_type = uint32_t; using value_type = std::tuple; auto heap = tlx::make_radix_heap( [](const value_type& p) { return std::get<0>(p); }); using bucket_type = typename decltype(heap)::bucket_data_type; using pq_type = std::pair; std::priority_queue, std::greater > pq; auto insert = [&](KeyType minv, KeyType maxv) { std::uniform_int_distribution kdistr(minv, maxv); std::uniform_int_distribution pdistr; auto key = kdistr(prng); auto pay = pdistr(prng); pq.emplace(key, pay); heap.emplace(key, key, pay); }; for (size_t i = 0; i < prefill_n; i++) { insert(min, max - iters); } std::uniform_real_distribution rdist; KeyType running_min = min; std::vector ref_data, heap_data; for (int64_t i = 0; i < static_cast(iters) || !pq.empty(); i++) { while (i < static_cast(iters) && rdist(prng) < insert_prob) insert(running_min, max - std::max(0, iters - i)); die_unequal(pq.empty(), heap.empty()); if (pq.empty()) continue; if (pm == PullMode::Single) { die_unequal(pq.top().first, std::get<0>(heap.top())); running_min = std::get<0>(heap.top()); pq.pop(); heap.pop(); } else { const KeyType key = pq.top().first; for (ref_data.clear(); !pq.empty() && pq.top().first == key; pq.pop()) { ref_data.push_back(pq.top().second); } running_min = key; die_unequal(std::get<0>(heap.top()), key); if (pm == PullMode::AllOfKey) { for (heap_data.clear(); !heap.empty() && heap.peak_top_key() == key; heap.pop()) { heap_data.push_back(std::get<1>(heap.top())); } } else { bucket_type bucket; heap.swap_top_bucket(bucket); heap_data.clear(); heap_data.reserve(bucket.size()); for (const auto& x : bucket) { heap_data.push_back(std::get<1>(x)); } } std::sort(ref_data.begin(), ref_data.end()); std::sort(heap_data.begin(), heap_data.end()); die_unequal(ref_data.size(), heap_data.size()); for (size_t j = 0; j < heap_data.size(); j++) die_unequal(heap_data[j], ref_data[j]); } } } //! Performs interleaved insertions and pop operations and checks it against //! the STL priority queue template void random_inout_pair(std::mt19937& prng, const KeyType min, const KeyType max, const KeyType iters, const double insert_prob, const size_t prefill_n, const PullMode pm) { constexpr bool debug = false; LOG << "random_inout_pair<" "sizeof(T)=" << sizeof(KeyType) << ", Radix=" << Radix << ">(" "min=" << min << ", " "max=" << max << ", " "iters=" << iters << ", " "insert_prob=" << insert_prob << ", " "prefill_n=" << prefill_n << ", " "pm=" << static_cast(pm) << ")\n"; using payload_type = uint32_t; using pq_type = std::pair; std::priority_queue, std::greater > pq; using heap_type = tlx::RadixHeapPair; using bucket_type = typename tlx::RadixHeapPair::bucket_data_type; heap_type heap; auto insert = [&](KeyType minv, KeyType maxv) { std::uniform_int_distribution kdistr(minv, maxv); std::uniform_int_distribution pdistr; auto key = kdistr(prng); auto pay = pdistr(prng); pq.emplace(key, pay); heap.emplace_keyfirst(key, pay); }; for (size_t i = 0; i < prefill_n; i++) { insert(min, max - iters); } std::uniform_real_distribution rdist; KeyType running_min = min; std::vector ref_data, heap_data; for (int64_t i = 0; i < static_cast(iters) || !pq.empty(); i++) { while (i < static_cast(iters) && rdist(prng) < insert_prob) { insert(running_min, static_cast(max - std::max(0, iters - i))); } die_unequal(pq.empty(), heap.empty()); if (pq.empty()) continue; if (pm == PullMode::Single) { die_unequal(pq.top().first, heap.top().first); running_min = heap.top().first; pq.pop(); heap.pop(); } else { const KeyType key = pq.top().first; for (ref_data.clear(); !pq.empty() && pq.top().first == key; pq.pop()) { ref_data.push_back(pq.top().second); } running_min = key; die_unequal(heap.top().first, key); if (pm == PullMode::AllOfKey) { for (heap_data.clear(); !heap.empty() && heap.peak_top_key() == key; heap.pop()) { heap_data.push_back(heap.top().second); } } else { bucket_type bucket; heap.swap_top_bucket(bucket); heap_data.clear(); heap_data.reserve(bucket.size()); for (const auto& x : bucket) { heap_data.push_back(x.second); } } std::sort(ref_data.begin(), ref_data.end()); std::sort(heap_data.begin(), heap_data.end()); die_unequal(ref_data.size(), heap_data.size()); for (size_t j = 0; j < heap_data.size(); j++) die_unequal(heap_data[j], ref_data[j]); } } } void random_inout_all(std::mt19937& prng, const int64_t min, const int64_t max, const size_t iters, const double insert_prob, const size_t prefill_n, const PullMode pm) { #define RADIXHEAP_TESTSET(T) \ random_inout(prng, static_cast(min), static_cast(max), \ iters, insert_prob, prefill_n, pm); \ random_inout(prng, static_cast(min), static_cast(max), \ iters, insert_prob, prefill_n, pm); \ random_inout_pair(prng, static_cast(min), static_cast(max), \ iters, insert_prob, prefill_n, pm); \ random_inout_pair(prng, static_cast(min), static_cast(max), \ iters, insert_prob, prefill_n, pm); die_unless(min < max); if (min >= 0 && max >= 0) { RADIXHEAP_TESTSET(uint32_t) RADIXHEAP_TESTSET(uint64_t) } RADIXHEAP_TESTSET(int32_t) RADIXHEAP_TESTSET(int64_t) #undef RADIXHEAP_TESTSET } void allin_allout_all(std::mt19937& prng, int64_t min, int64_t max, size_t n) { #define RADIXHEAP_TESTSET(T) \ allin_allout(prng, static_cast(min), static_cast(max), n); \ allin_allout(prng, static_cast(min), static_cast(max), n); \ allin_allout_pair(prng, static_cast(min), static_cast(max), n); \ allin_allout_pair(prng, static_cast(min), static_cast(max), n); if (min >= 0) { RADIXHEAP_TESTSET(uint32_t); RADIXHEAP_TESTSET(uint64_t); } RADIXHEAP_TESTSET(int32_t); RADIXHEAP_TESTSET(int64_t); #undef RADIXHEAP_TESTSET } void test_main_radix_heap_pair(std::mt19937& prng) { for (int64_t min : { 0, 1000000, -1000000 }) { for (size_t x : { 1000, 1000000 }) { allin_allout_all(prng, min, min + x, 500); } } for (PullMode pm : { PullMode::Single, PullMode::AllOfKey, PullMode::ExtractBucket }) { for (int64_t prefill : { 0, 100 }) { for (int64_t min : { 0, 10000000, -10000000, -250 }) { for (size_t length : { 500 }) { random_inout_all(prng, min, min + length, 200, 0.9, prefill, pm); } } } } } /******************************************************************************/ int main() { std::mt19937 prng(1); test_main_bitarray(prng); test_main_int_rank(prng); test_main_bucket(prng); test_main_radix_heap_pair(prng); return 0; } /******************************************************************************/