diff --git a/CHANGELOG.md b/CHANGELOG.md index fda0b24..e421343 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Changed `bpt_vectot` to use `std::destroy` i.o. default dstr. [#132](https://github.com/tzaeschke/phtree-cpp/pull/132) - Moved B+trees into own namespace. [#131](https://github.com/tzaeschke/phtree-cpp/pull/131) - Moved some stuff in `common` into `nemaspace detail`. [#129](https://github.com/tzaeschke/phtree-cpp/issues/129) +- Improved kNN search implementation. This also deprecates the post-increment iterator. + [#118](https://github.com/tzaeschke/phtree-cpp/issues/118) ### Fixed - Replaced deprecated `` imports with ``. [#134](https://github.com/tzaeschke/phtree-cpp/pull/134) diff --git a/benchmark/BUILD b/benchmark/BUILD index 4b84294..1152c5d 100644 --- a/benchmark/BUILD +++ b/benchmark/BUILD @@ -248,6 +248,21 @@ cc_binary( ], ) +cc_binary( + name = "knn_mm_d_benchmark", + testonly = True, + srcs = [ + "knn_mm_d_benchmark.cc", + ], + linkstatic = True, + deps = [ + ":benchmark", + "//:phtree", + "@gbenchmark//:benchmark", + "@spdlog", + ], +) + cc_binary( name = "query_benchmark", testonly = True, diff --git a/benchmark/bpt_insert_benchmark.cc b/benchmark/bpt_insert_benchmark.cc index 9e25b7b..7497055 100644 --- a/benchmark/bpt_insert_benchmark.cc +++ b/benchmark/bpt_insert_benchmark.cc @@ -1,5 +1,5 @@ /* -* Copyright 2022-2023 Tilmann Zäschke + * Copyright 2022-2023 Tilmann Zäschke * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ const int GLOBAL_MAX = 10000; enum Scenario { MAP, MULTIMAP, + MULTIMAP2, HASH_MAP, STD_MAP, STD_MULTIMAP, @@ -118,7 +119,22 @@ void IndexBenchmark::SetupWorld(benchmark::State& state) { template void IndexBenchmark::Insert(benchmark::State& state, Index& tree) { switch (TYPE) { - default: { + case MAP: { + for (size_t i = 0; i < num_entities_; ++i) { + tree.emplace(points_[i][0], (payload_t)i); + } + break; + } + case MULTIMAP: + case MULTIMAP2: + case STD_MULTIMAP: { + for (size_t i = 0; i < num_entities_; ++i) { + tree.emplace(points_[i][0], (payload_t)i); + } + break; + } + case HASH_MAP: + case STD_MAP: { for (size_t i = 0; i < num_entities_; ++i) { tree.emplace(points_[i][0], (payload_t)i); } diff --git a/benchmark/hd_erase_d_benchmark.cc b/benchmark/hd_erase_d_benchmark.cc index f2650c1..4f03e24 100644 --- a/benchmark/hd_erase_d_benchmark.cc +++ b/benchmark/hd_erase_d_benchmark.cc @@ -34,7 +34,7 @@ using payload_t = std::uint32_t; template class IndexBenchmark { public: - IndexBenchmark(benchmark::State& state); + explicit IndexBenchmark(benchmark::State& state); void Benchmark(benchmark::State& state); private: diff --git a/benchmark/knn_d_benchmark.cc b/benchmark/knn_d_benchmark.cc index dcf5abf..bbec72c 100644 --- a/benchmark/knn_d_benchmark.cc +++ b/benchmark/knn_d_benchmark.cc @@ -86,8 +86,6 @@ void IndexBenchmark::SetupWorld(benchmark::State& state) { tree_.emplace(points_[i], (int)i); } - state.counters["total_result_count"] = benchmark::Counter(0); - state.counters["total_query_count"] = benchmark::Counter(0); state.counters["query_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); state.counters["result_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); state.counters["avg_result_count"] = benchmark::Counter(0, benchmark::Counter::kAvgIterations); @@ -104,8 +102,6 @@ void IndexBenchmark::QueryWorld(benchmark::State& state, PhPointD& cen ++n; } - state.counters["total_query_count"] += 1; - state.counters["total_result_count"] += n; state.counters["query_rate"] += 1; state.counters["result_rate"] += n; state.counters["avg_result_count"] += n; @@ -140,6 +136,12 @@ BENCHMARK_CAPTURE(PhTree3D, KNN_CU_10_of_10K, TestGenerator::CUBE, 10000, 10) BENCHMARK_CAPTURE(PhTree3D, KNN_CU_10_of_1M, TestGenerator::CUBE, 1000000, 10) ->Unit(benchmark::kMillisecond); +BENCHMARK_CAPTURE(PhTree3D, KNN_CU_100_of_10K, TestGenerator::CUBE, 10000, 100) + ->Unit(benchmark::kMillisecond); + +BENCHMARK_CAPTURE(PhTree3D, KNN_CU_100_of_1M, TestGenerator::CUBE, 1000000, 100) + ->Unit(benchmark::kMillisecond); + // index type, scenario name, data_type, num_entities, query_result_size // PhTree 3D CLUSTER BENCHMARK_CAPTURE(PhTree3D, KNN_CL_1_of_10K, TestGenerator::CLUSTER, 10000, 1) @@ -154,4 +156,10 @@ BENCHMARK_CAPTURE(PhTree3D, KNN_CL_10_of_10K, TestGenerator::CLUSTER, 10000, 10) BENCHMARK_CAPTURE(PhTree3D, KNN_CL_10_of_1M, TestGenerator::CLUSTER, 1000000, 10) ->Unit(benchmark::kMillisecond); +BENCHMARK_CAPTURE(PhTree3D, KNN_CL_100_of_10K, TestGenerator::CLUSTER, 10000, 100) + ->Unit(benchmark::kMillisecond); + +BENCHMARK_CAPTURE(PhTree3D, KNN_CL_100_of_1M, TestGenerator::CLUSTER, 1000000, 100) + ->Unit(benchmark::kMillisecond); + BENCHMARK_MAIN(); diff --git a/benchmark/knn_mm_d_benchmark.cc b/benchmark/knn_mm_d_benchmark.cc new file mode 100644 index 0000000..391fc59 --- /dev/null +++ b/benchmark/knn_mm_d_benchmark.cc @@ -0,0 +1,282 @@ +/* + * Copyright 2020 Improbable Worlds Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "benchmark_util.h" +#include "logging.h" +#include "phtree/phtree.h" +#include "phtree/phtree_multimap.h" +#include +#include + +using namespace improbable; +using namespace improbable::phtree; +using namespace improbable::phtree::phbenchmark; + +/* + * Benchmark for k-nearest-neighbour queries in multi-map implementations. + */ +namespace { + +const double GLOBAL_MAX = 10000; +const dimension_t DIM = 3; + +enum Scenario { + TREE_SET, + PHTREE_MM, + PHTREE_MM_STD, + PHTREE2, + TS_KD, + TS_QT, +}; + +using payload_t = int64_t; +using payload2_t = uint32_t; + +using TestPoint = PhPointD; +using QueryBox = PhBoxD; +using BucketType = std::set; + +template +using CONVERTER = ConverterIEEE; + +template +using TestMap = typename std::conditional_t< + SCENARIO == TREE_SET, + PhTreeD>, + typename std::conditional_t< + SCENARIO == PHTREE_MM, + PhTreeMultiMap, b_plus_tree_hash_set>, +// typename std::conditional_t< +// SCENARIO == PHTREE2, +// PhTreeMultiMap2D, + typename std::conditional_t< + SCENARIO == PHTREE_MM_STD, + PhTreeMultiMap, BucketType>, + void>>>; + +template +class IndexBenchmark { + public: + IndexBenchmark(benchmark::State& state, int knn_result_size_); + + void Benchmark(benchmark::State& state); + + private: + void SetupWorld(benchmark::State& state); + void QueryWorld(benchmark::State& state, TestPoint& center); + void CreateQuery(TestPoint& center); + + const TestGenerator data_type_; + const size_t num_entities_; + const size_t knn_result_size_; + + TestMap tree_; + std::default_random_engine random_engine_; + std::uniform_real_distribution<> cube_distribution_; + std::vector points_; +}; + +template +IndexBenchmark::IndexBenchmark(benchmark::State& state, int knn_result_size) +: data_type_{static_cast(state.range(1))} +, num_entities_(state.range(0)) +, knn_result_size_(knn_result_size) +, random_engine_{1} +, cube_distribution_{0, GLOBAL_MAX} +, points_(state.range(0)) { + logging::SetupDefaultLogging(); + SetupWorld(state); +} + +template +void IndexBenchmark::Benchmark(benchmark::State& state) { + for (auto _ : state) { + state.PauseTiming(); + TestPoint center; + CreateQuery(center); + state.ResumeTiming(); + + QueryWorld(state, center); + } +} + +template < + dimension_t DIM, + Scenario SCENARIO, + std::enable_if_t<(SCENARIO == Scenario::TREE_SET), int> = 0> +void InsertEntries(TestMap& tree, const std::vector& points) { + for (size_t i = 0; i < points.size(); ++i) { + BucketType& bucket = tree.emplace(points[i]).first; + bucket.emplace((payload_t)i); + } +} + +template = 0> +void InsertEntries(TestMap& tree, const std::vector& points) { + for (size_t i = 0; i < points.size(); ++i) { + tree.emplace(points[i], (payload_t)i); + } +} + +template +size_t QueryAll(TestMap& tree, const TestPoint& center, const size_t k) { + size_t n = 0; + for (auto q = tree.begin_knn_query(k, center, DistanceEuclidean()); q != tree.end(); ++q) { + ++n; + } + return n; +} + +struct CounterTreeWithMap { + void operator()(const TestPoint&, const BucketType& value) { + for (auto& x : value) { + (void)x; + n_ += 1; + } + } + size_t n_; +}; + +struct CounterMultiMap { + void operator()(const TestPoint&, const payload_t&) { + n_ += 1; + } + size_t n_; +}; + +template +void IndexBenchmark::SetupWorld(benchmark::State& state) { + logging::info("Setting up world with {} entities and {} dimensions.", num_entities_, DIM); + CreatePointData(points_, data_type_, num_entities_, 0, GLOBAL_MAX); + InsertEntries(tree_, points_); + + state.counters["query_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); + state.counters["result_rate"] = benchmark::Counter(0, benchmark::Counter::kIsRate); + state.counters["avg_result_count"] = benchmark::Counter(0, benchmark::Counter::kAvgIterations); + logging::info("World setup complete."); +} + +template +void IndexBenchmark::QueryWorld(benchmark::State& state, TestPoint& center) { + size_t n = QueryAll(tree_, center, knn_result_size_); + + state.counters["query_rate"] += 1; + state.counters["result_rate"] += n; + state.counters["avg_result_count"] += n; +} + +template +void IndexBenchmark::CreateQuery(TestPoint& center) { + for (dimension_t d = 0; d < DIM; ++d) { + center[d] = cube_distribution_(random_engine_) * GLOBAL_MAX; + } +} + +} // namespace + +// template +// void TinspinKDTree(benchmark::State& state, Arguments&&... arguments) { +// IndexBenchmark benchmark{state, arguments...}; +// benchmark.Benchmark(state); +// } +// +// template +// void TinspinQuadtree(benchmark::State& state, Arguments&&... arguments) { +// IndexBenchmark benchmark{state, arguments...}; +// benchmark.Benchmark(state); +// } + +template +void PhTree3D(benchmark::State& state, Arguments&&... arguments) { + IndexBenchmark benchmark{state, arguments...}; + benchmark.Benchmark(state); +} + +template +void PhTreeMM(benchmark::State& state, Arguments&&... arguments) { + IndexBenchmark benchmark{state, arguments...}; + benchmark.Benchmark(state); +} + +//template +//void PhTreeMM2(benchmark::State& state, Arguments&&... arguments) { +// IndexBenchmark benchmark{state, arguments...}; +// benchmark.Benchmark(state); +//} + +template +void PhTreeMMStdSet(benchmark::State& state, Arguments&&... arguments) { + IndexBenchmark benchmark{state, arguments...}; + benchmark.Benchmark(state); +} + +// index type, scenario name, data_type, num_entities, query_result_size + +// PhTree multi-map 1.0 +BENCHMARK_CAPTURE(PhTreeMM, KNN_1, 1) + ->RangeMultiplier(10) + ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) + ->Unit(benchmark::kMillisecond); + +BENCHMARK_CAPTURE(PhTreeMM, KNN_10, 10) + ->RangeMultiplier(10) + ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) + ->Unit(benchmark::kMillisecond); + +//// Multimap 2.0 +//BENCHMARK_CAPTURE(PhTreeMM2, KNN_1, 1) +// ->RangeMultiplier(10) +// ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) +// ->Unit(benchmark::kMillisecond); +// +//BENCHMARK_CAPTURE(PhTreeMM2, KNN_10, 10) +// ->RangeMultiplier(10) +// ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) +// ->Unit(benchmark::kMillisecond); + +//// KD-tree +// BENCHMARK_CAPTURE(TinspinKDTree, KNN_1, 1) +// ->RangeMultiplier(10) +// ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) +// ->Unit(benchmark::kMillisecond); +// +// BENCHMARK_CAPTURE(TinspinKDTree, KNN_10, 10) +// ->RangeMultiplier(10) +// ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) +// ->Unit(benchmark::kMillisecond); +// +//// Quadtree +// BENCHMARK_CAPTURE(TinspinQuadtree, KNN_1, 1) +// ->RangeMultiplier(10) +// ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) +// ->Unit(benchmark::kMillisecond); +// +// BENCHMARK_CAPTURE(TinspinQuadtree, KNN_10, 10) +// ->RangeMultiplier(10) +// ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) +// ->Unit(benchmark::kMillisecond); + +// PhTree 3D with set +BENCHMARK_CAPTURE(PhTree3D, KNN_1, 1) + ->RangeMultiplier(10) + ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) + ->Unit(benchmark::kMillisecond); + +BENCHMARK_CAPTURE(PhTree3D, KNN_10, 10) + ->RangeMultiplier(10) + ->Ranges({{1000, 1000 * 1000}, {TestGenerator::CUBE, TestGenerator::CLUSTER}}) + ->Unit(benchmark::kMillisecond); + +BENCHMARK_MAIN(); diff --git a/include/phtree/common/BUILD b/include/phtree/common/BUILD index 95fe051..08e237a 100644 --- a/include/phtree/common/BUILD +++ b/include/phtree/common/BUILD @@ -10,6 +10,7 @@ cc_library( "base_types.h", "bits.h", "bpt_fixed_vector.h", + "bpt_priority_queue.h", "common.h", "debug_helper.h", "flat_array_map.h", diff --git a/include/phtree/common/bpt_priority_queue.h b/include/phtree/common/bpt_priority_queue.h new file mode 100644 index 0000000..2b02e21 --- /dev/null +++ b/include/phtree/common/bpt_priority_queue.h @@ -0,0 +1,269 @@ +/* + * Copyright 2023 Tilmann Zäschke + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PHTREE_COMMON_BPT_PRIORITY_QUEUE_H +#define PHTREE_COMMON_BPT_PRIORITY_QUEUE_H + +#include +#include +#include +#include +#include +#include + +namespace phtree::bptree::detail { + +/** + * A priority queue based on a sorted vector. + */ +template > +class priority_queue { + public: + // Member types + using value_type = V; + // using allocator_type = Allocator + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = value_type&; + using const_reference = const value_type&; + + public: + explicit priority_queue() noexcept : data_{}, comp_{} {} + + explicit priority_queue(size_t initial_size) noexcept : data_{}, comp_{} { + data_.reserve(initial_size); + } + + priority_queue(const priority_queue& rhs) noexcept : data_(rhs.data_), comp_{} {} + + priority_queue(priority_queue&& rhs) noexcept : data_{std::move(rhs.data_)}, comp_{} {} + + // TODO use default functions? + priority_queue& operator=(const priority_queue& rhs) noexcept { + data_ = rhs.data_; + comp_ = rhs.comp_; + return *this; + } + + priority_queue& operator=(priority_queue&& rhs) noexcept { + data_ = std::move(rhs.data_); + comp_ = std::move(rhs.comp_); + return *this; + } + + ~priority_queue() noexcept = default; + + const V& top() const { + assert(!data_.empty()); + return data_.back(); + } + + // TODO rename bottom() + const V& top_max() const { + assert(!data_.empty()); + return data_.front(); + } + + V& top_max() { + assert(!data_.empty()); + return data_.front(); + } + + void pop() { + assert(!data_.empty()); + data_.pop_back(); + } + + void pop_max() { + assert(!data_.empty()); + data_.erase(data_.begin()); + // data_.pop_front(); + } + + V& operator[](size_t index) noexcept { + assert(index < data_.size()); + return data_[index]; + } + + const V& operator[](size_t index) const noexcept { + assert(index < data_.size()); + return data_[index]; + } + + template + void emplace(Args&&... args) { + V v{std::forward(args)...}; + // TODO this is bad!!! We should ask for key/value separately.... and avoid "first" + auto pos = std::lower_bound(data_.begin(), data_.end(), v, comp_); + data_.emplace(pos, std::move(v)); + } + + void emplace_back(const V& v) { + // TODO this is bad!!! We should ask for key/value separately.... and avoid "first" + auto pos = std::lower_bound(data_.begin(), data_.end(), v, comp_); + data_.emplace(pos, std::move(v)); + } + + [[nodiscard]] bool empty() const noexcept { + return data_.empty(); + } + + [[nodiscard]] size_t size() const noexcept { + return data_.size(); + } + + void reserve(size_t size) noexcept { + data_.reserve(size); + } + + constexpr reference front() noexcept { + return data_.front(); + } + + constexpr const_reference front() const noexcept { + return data_.front(); + } + + constexpr reference back() noexcept { + return data_.back(); + } + + constexpr const_reference back() const noexcept { + return data_.back(); + } + + private: + std::vector data_; + Compare comp_; +}; + +/** + * A priority queue based on a sorted vector. + */ +template > +class priority_dequeue { + public: + // Member types + using value_type = V; + // using allocator_type = Allocator + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = value_type&; + using const_reference = const value_type&; + + public: + explicit priority_dequeue(size_t initial_size = 16) noexcept : data_{}, comp_{} {} + + priority_dequeue(const priority_dequeue& rhs) noexcept : data_(rhs.data_), comp_{} {} + + priority_dequeue(priority_dequeue&& rhs) noexcept : data_{std::move(rhs.data_)}, comp_{} {} + + // TODO use default functions? + // TODO ommit comp_? -> check std::priority_queue + priority_dequeue& operator=(const priority_dequeue& rhs) noexcept { + data_ = rhs.data_; + comp_ = rhs.comp_; + return *this; + } + + priority_dequeue& operator=(priority_dequeue&& rhs) noexcept { + data_ = std::move(rhs.data_); + comp_ = std::move(rhs.comp_); + return *this; + } + + ~priority_dequeue() noexcept = default; + + const V& top() const { + assert(!data_.empty()); + return data_.back(); + } + + // TODO rename bottom() + const V& top_max() const { + assert(!data_.empty()); + return data_.front(); + } + + V& top_max() { + assert(!data_.empty()); + return data_.front(); + } + + void pop() { + assert(!data_.empty()); + data_.pop_back(); + } + + void pop_max() { + assert(!data_.empty()); + data_.pop_front(); + } + + V& operator[](size_t index) noexcept { + assert(index < data_.size()); + return data_[index]; + } + + const V& operator[](size_t index) const noexcept { + assert(index < data_.size()); + return data_[index]; + } + + template + void emplace(Args&&... args) { + V v{std::forward(args)...}; + // TODO this is bad!!! We should ask for key/value separately.... and avoid "first" + auto pos = std::lower_bound(data_.begin(), data_.end(), v, comp_); + data_.emplace(pos, std::move(v)); + } + + [[nodiscard]] bool empty() const noexcept { + return data_.empty(); + } + + [[nodiscard]] size_t size() const noexcept { + return data_.size(); + } + + void reserve(size_t size) noexcept { + data_.reserve(size); + } + + constexpr reference front() noexcept { + return data_.front(); + } + + constexpr const_reference front() const noexcept { + return data_.front(); + } + + constexpr reference back() noexcept { + return data_.back(); + } + + constexpr const_reference back() const noexcept { + return data_.back(); + } + + private: + std::deque data_; + Compare comp_; +}; + +} // namespace phtree::bptree::detail + +#endif // PHTREE_COMMON_BPT_PRIORITY_QUEUE_H diff --git a/include/phtree/v16/iterator_knn_hs.h b/include/phtree/v16/iterator_knn_hs.h index 2791089..1b916da 100644 --- a/include/phtree/v16/iterator_knn_hs.h +++ b/include/phtree/v16/iterator_knn_hs.h @@ -18,6 +18,7 @@ #define PHTREE_V16_QUERY_KNN_HS_H #include "iterator_base.h" +#include "phtree/common/bpt_priority_queue.h" #include "phtree/common/common.h" #include @@ -26,7 +27,7 @@ namespace improbable::phtree::v16 { /* * kNN query implementation that uses preprocessors and distance functions. * - * Implementation after Hjaltason and Samet (with some deviations: no MinDist or MaxDist used). + * Implementation (roughly) after Hjaltason and Samet. * G. R. Hjaltason and H. Samet., "Distance browsing in spatial databases.", ACM TODS * 24(2):265--318. 1999 */ @@ -36,7 +37,7 @@ template using EntryDist = std::pair*>; template -struct CompareEntryDistByDistance { +struct CompareEntryDist { bool operator()(const ENTRY& left, const ENTRY& right) const { return left.first > right.first; }; @@ -65,8 +66,9 @@ class IteratorKnnHS : public IteratorWithFilter { , center_{center} , center_post_{converter->post(center)} , current_distance_{std::numeric_limits::max()} - , num_found_results_(0) - , num_requested_results_(min_results) + , remaining_{min_results} + , queue_n_{} + , queue_v_{min_results + 1} , distance_(std::forward(dist)) { if (min_results <= 0 || root.GetNode().GetEntryCount() == 0) { this->SetFinished(); @@ -74,7 +76,9 @@ class IteratorKnnHS : public IteratorWithFilter { } // Initialize queue, use d=0 because every imaginable point lies inside the root Node - queue_.emplace(0, &root); + assert(root.IsNode()); + queue_n_.emplace(EntryDistT{0, &root}); + FindNextElement(); } @@ -87,7 +91,9 @@ class IteratorKnnHS : public IteratorWithFilter { return *this; } - IteratorKnnHS operator++(int) noexcept { + [[deprecated]] // it++ is MUCH slower than ++it! + IteratorKnnHS + operator++(int) noexcept { IteratorKnnHS iterator(*this); ++(*this); return iterator; @@ -95,31 +101,52 @@ class IteratorKnnHS : public IteratorWithFilter { private: void FindNextElement() { - while (num_found_results_ < num_requested_results_ && !queue_.empty()) { - auto& candidate = queue_.top(); - auto* o = candidate.second; - if (!o->IsNode()) { + while (remaining_ > 0 && !(queue_n_.empty() && queue_v_.empty())) { + bool use_v = !queue_v_.empty(); + if (use_v && !queue_n_.empty()) { + use_v = queue_v_.top().first <= queue_n_.top().first; + } + if (use_v) { // data entry - ++num_found_results_; - this->SetCurrentResult(o); - current_distance_ = candidate.first; - // We need to pop() AFTER we processed the value, otherwise the reference is - // overwritten. - queue_.pop(); + auto& result = queue_v_.top(); + --remaining_; + this->SetCurrentResult(result.second); + current_distance_ = result.first; + queue_v_.pop(); return; } else { // inner node - auto& node = o->GetNode(); - queue_.pop(); + auto top = queue_n_.top(); + auto& node = top.second->GetNode(); + auto d_node = top.first; + queue_n_.pop(); + + if (d_node > max_node_dist_ && queue_v_.size() >= remaining_) { + // ignore this node + continue; + } + for (auto& entry : node.Entries()) { - auto& e2 = entry.second; + const auto& e2 = entry.second; if (this->ApplyFilter(e2)) { if (e2.IsNode()) { double d = DistanceToNode(e2.GetKey(), e2.GetNodePostfixLen() + 1); - queue_.emplace(d, &e2); + if (d <= max_node_dist_) { + queue_n_.emplace(d, &e2); + } } else { double d = distance_(center_post_, this->post(e2.GetKey())); - queue_.emplace(d, &e2); + // Using '<=' allows dealing with infinite distances. + if (d <= max_node_dist_) { + queue_v_.emplace(d, &e2); + if (queue_v_.size() >= remaining_) { + if (queue_v_.size() > remaining_) { + queue_v_.pop_max(); + } + double d_max = queue_v_.top_max().first; + max_node_dist_ = std::min(max_node_dist_, d_max); + } + } } } } @@ -150,11 +177,11 @@ class IteratorKnnHS : public IteratorWithFilter { // center after post processing == the external representation const KeyExternal center_post_; double current_distance_; - std::priority_queue, CompareEntryDistByDistance> - queue_; - size_t num_found_results_; - size_t num_requested_results_; + size_t remaining_; + std::priority_queue, CompareEntryDist> queue_n_; + ::phtree::bptree::detail::priority_queue> queue_v_; DISTANCE distance_; + double max_node_dist_ = std::numeric_limits::infinity(); }; } // namespace improbable::phtree::v16 diff --git a/test/common/BUILD b/test/common/BUILD index 0f9434f..0805fe4 100644 --- a/test/common/BUILD +++ b/test/common/BUILD @@ -52,6 +52,19 @@ cc_test( ], ) +cc_test( + name = "b_priority_queue_test", + timeout = "long", + srcs = [ + "b_priority_queue_test.cc", + ], + linkstatic = True, + deps = [ + "//:phtree", + "@gtest//:gtest_main", + ], +) + cc_test( name = "b_vector_test", timeout = "long", diff --git a/test/common/CMakeLists.txt b/test/common/CMakeLists.txt index 9fd11f9..d110973 100644 --- a/test/common/CMakeLists.txt +++ b/test/common/CMakeLists.txt @@ -3,6 +3,7 @@ include(scripts.cmake) package_add_test(b_plus_tree_hash_map_test b_plus_tree_hash_map_test.cc) package_add_test(b_plus_tree_map_test b_plus_tree_map_test.cc) package_add_test(b_plus_tree_multimap_test b_plus_tree_multimap_test.cc) +package_add_test(b_priority_queue_test b_priority_queue_test.cc) package_add_test(base_types_test base_types_test.cc) package_add_test(bits_test bits_test.cc) package_add_test(b_vector_test b_vector_test.cc) diff --git a/test/common/b_priority_queue_test.cc b/test/common/b_priority_queue_test.cc new file mode 100644 index 0000000..4f24bde --- /dev/null +++ b/test/common/b_priority_queue_test.cc @@ -0,0 +1,350 @@ +/* + * Copyright 2023 Tilmann Zäschke + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "phtree/common/bpt_priority_queue.h" +#include +#include +#include +#include + +using namespace phtree::bptree; + +static int default_construct_count_ = 0; +static int construct_count_ = 0; +static int copy_construct_count_ = 0; +static int move_construct_count_ = 0; +static int copy_assign_count_ = 0; +static int move_assign_count_ = 0; +static int destruct_count_ = 0; + +[[maybe_unused]] static void reset_id_counters() { + default_construct_count_ = 0; + construct_count_ = 0; + copy_construct_count_ = 0; + move_construct_count_ = 0; + copy_assign_count_ = 0; + move_assign_count_ = 0; + destruct_count_ = 0; +} + +[[maybe_unused]] static void print_id_counters() { + std::cout << "dc=" << default_construct_count_ << " c=" << construct_count_ + << " cc=" << copy_construct_count_ << " mc=" << move_construct_count_ + << " ca=" << copy_assign_count_ << " ma=" << move_assign_count_ + << " d=" << destruct_count_ << std::endl; +} + +template +void populate( + const size_t N, + detail::priority_queue& test_map, + std::multimap& reference_map, + std::vector>& reverse_map, + std::default_random_engine& random_engine) { + std::uniform_int_distribution<> cube_distribution(0, (int)N / 2); + for (size_t j = 0; j < N; j++) { + Key key = cube_distribution(random_engine); + Value value = j; + bool hasVal = test_map.find(key) != test_map.end(); + bool hasValRef = reference_map.find(key) != reference_map.end(); + assert(hasVal == hasValRef); + reference_map.emplace(key, value); + test_map.try_emplace(key, value); + reverse_map.emplace_back(value, key); + } +} + +struct Id { + Id() : first{-1}, _i{0} { + ++default_construct_count_; + } + + explicit Id(double dist, const size_t i) : first{dist}, _i{static_cast(i)} { + ++construct_count_; + } + + explicit Id(double dist, const int i) : first{dist}, _i{i} { + ++construct_count_; + } + + Id(const Id& other) { + ++copy_construct_count_; + first = other.first; + _i = other._i; + } + + Id(Id&& other) noexcept { + ++move_construct_count_; + first = other.first; + _i = other._i; + } + + Id& operator=(const Id& other) noexcept { + ++copy_assign_count_; + first = other.first; + _i = other._i; + return *this; + } + Id& operator=(Id&& other) noexcept { + ++move_assign_count_; + first = other.first; + _i = other._i; + return *this; + } + + bool operator==(const Id& rhs) const { + return _i == rhs._i && first == rhs.first; + } + + ~Id() { + ++destruct_count_; + } + + double first; + int _i; +}; + +struct IdComparator { + bool operator()(const Id& left, const Id& right) const { + return left.first > right.first; + } +}; + +template +struct SwapComp { + Compare comp; + template + bool operator()(const Key& x, const Key& y) const { + return !comp(x, y); + } +}; + +void SmokeTest() { + const size_t N = 1000; + std::default_random_engine random_engine{0}; + std::uniform_int_distribution<> cube_distribution(0, N / 2); + std::uniform_real_distribution dist_distribution(0, 100); + + using ValueT = Id; + for (int i = 0; i < 100; i++) { + detail::priority_queue test_map; + std::priority_queue, IdComparator> ref_heap{}; + + // populate + for (size_t j = 0; j < N; j++) { + double dist = ((i + 1) * (j % 2)) == 0 ? 0 : dist_distribution(random_engine); + ValueT val{dist, j}; + test_map.emplace(val); + // test_map._check(); + ref_heap.emplace(val); + + ASSERT_EQ(test_map.size(), ref_heap.size()); + ASSERT_EQ(test_map.size(), j + 1u); + + ASSERT_EQ(test_map.top().first, ref_heap.top().first); + } + + // update + for (size_t j = 0; j < N; j++) { + // pop + double d1 = test_map.top().first; + test_map.pop(); + ref_heap.pop(); + ASSERT_EQ(test_map.top().first, ref_heap.top().first); + ASSERT_LE(d1, test_map.top().first); + + // push + double dist = (i * (j % 2)) == 0 ? 0 : dist_distribution(random_engine); + ValueT val{dist, j}; + test_map.emplace(val); + ref_heap.emplace(val); + + ASSERT_EQ(test_map.size(), ref_heap.size()); + ASSERT_EQ(test_map.size(), N); + + ASSERT_EQ(test_map.top().first, ref_heap.top().first); + } + + // drain + double prev_dist = 0; + for (size_t j = 0; j < N; j++) { + ASSERT_EQ(test_map.top().first, ref_heap.top().first); + ASSERT_LE(prev_dist, test_map.top().first); + double dist = test_map.top().first; + test_map.pop(); + ref_heap.pop(); + prev_dist = dist; + } + + ASSERT_EQ(0u, test_map.size()); + ASSERT_TRUE(test_map.empty()); + } +} + +TEST(PhTreeBptHeapTest, SmokeTest) { + SmokeTest(); +} + +void SmokeTestTop() { + const size_t N = 1000; + std::default_random_engine random_engine{0}; + std::uniform_int_distribution<> cube_distribution(0, N / 2); + std::uniform_real_distribution dist_distribution(0, 100); + + using ValueT = Id; + for (int i = 0; i < 100; i++) { + detail::priority_queue test_map; + std::priority_queue, SwapComp> ref_heap_inverse{}; + + // populate + for (size_t j = 0; j < N; j++) { + double dist = ((i + 1) * (j % 2)) == 0 ? 0 : dist_distribution(random_engine); + ValueT val{dist, j}; + test_map.emplace(val); + // test_map._check(); + ref_heap_inverse.emplace(val); + + ASSERT_EQ(test_map.size(), ref_heap_inverse.size()); + ASSERT_EQ(test_map.size(), j + 1u); + + ASSERT_EQ(test_map.top_max().first, ref_heap_inverse.top().first); + } + + // update + for (size_t j = 0; j < N; j++) { + // pop + double d1 = test_map.top().first; + test_map.pop_max(); + ref_heap_inverse.pop(); + ASSERT_EQ(test_map.top_max().first, ref_heap_inverse.top().first); + ASSERT_LE(d1, test_map.top().first); + + // push + double dist = (i * (j % 2)) == 0 ? 0 : dist_distribution(random_engine); + ValueT val{dist, j}; + test_map.emplace(val); + ref_heap_inverse.emplace(val); + + ASSERT_EQ(test_map.size(), ref_heap_inverse.size()); + ASSERT_EQ(test_map.size(), N); + + ASSERT_EQ(test_map.top_max().first, ref_heap_inverse.top().first); + } + + // drain + double prev_dist = 0; + for (size_t j = 0; j < N; j++) { + ASSERT_EQ(test_map.top_max().first, ref_heap_inverse.top().first); + ASSERT_LE(prev_dist, test_map.top().first); + double dist = test_map.top().first; + test_map.pop_max(); + ref_heap_inverse.pop(); + prev_dist = dist; + } + + ASSERT_EQ(0u, test_map.size()); + ASSERT_TRUE(test_map.empty()); + } +} + +TEST(PhTreeBptHeapTest, SmokeTestTop) { + SmokeTestTop(); +} + +TEST(PhTreeBptHeapTest, DestructionTest) { + const size_t N = 1000; + std::default_random_engine random_engine{0}; + std::uniform_int_distribution<> cube_distribution(0, N / 2); + std::uniform_real_distribution dist_distribution(0, 100); + + using ValueT = Id; + for (int i = 0; i < 10; i++) { + detail::priority_queue test_map; + + // populate + for (size_t j = 0; j < N; j++) { + double dist = ((i + 1) * (j % 2)) == 0 ? 0 : dist_distribution(random_engine); + ValueT val{dist, j}; + test_map.emplace(val); + } + // Remove some (or not) + for (size_t j = 0; j < i * N / 100; j++) { + double dist = ((i + 1) * (j % 2)) == 0 ? 0 : dist_distribution(random_engine); + ValueT val{dist, j}; + test_map.pop(); + test_map.pop_max(); + } + // Automatic destruction happens here. + } +} + +template +void test_tree(TREE& tree) { + // test various operations + tree.emplace(Id(43, 2)); + Id id3{44, 3}; + tree.emplace(id3); + ASSERT_EQ(tree.size(), 3u); + + tree.pop(); + ASSERT_EQ(2u, tree.size()); + tree.pop(); + tree.pop(); + ASSERT_EQ(0u, tree.size()); + ASSERT_TRUE(tree.empty()); +} + +TEST(PhTreeBptMulitmapTest, TestCopyConstruct) { + using TestTree = detail::priority_queue; + TestTree tree1; + tree1.emplace(Id(42, 1)); + + TestTree tree{tree1}; + test_tree(tree); + // The old tree should still work! + test_tree(tree1); +} + +TEST(PhTreeBptMulitmapTest, TestCopyAssign) { + using TestTree = detail::priority_queue; + TestTree tree1; + tree1.emplace(Id(42, 1)); + + TestTree tree{}; + tree = tree1; + test_tree(tree); + // The old tree should still work! + test_tree(tree1); +} + +TEST(PhTreeBptMulitmapTest, TestMoveConstruct) { + using TestTree = detail::priority_queue; + TestTree tree1; + tree1.emplace(Id(42, 1)); + + TestTree tree{std::move(tree1)}; + test_tree(tree); +} + +TEST(PhTreeBptMulitmapTest, TestMoveAssign) { + using TestTree = detail::priority_queue; + TestTree tree1; + tree1.emplace(Id(42, 1)); + + TestTree tree{}; + tree = std::move(tree1); + test_tree(tree); +} diff --git a/test/phtree_box_d_test_query_types.cc b/test/phtree_box_d_test_query_types.cc index 84a77a8..411a742 100644 --- a/test/phtree_box_d_test_query_types.cc +++ b/test/phtree_box_d_test_query_types.cc @@ -52,14 +52,14 @@ TEST(PhTreeBoxDTestQueryTypes, SmokeTestQuery) { auto q2 = tree.begin_query({{-11, -11}, {9, 9}}, FilterNoOp(), query_type); ASSERT_NE(q2, tree.end()); ASSERT_EQ(-1, (*q2)); - q2++; + ++q2; ASSERT_EQ(q2, tree.end()); // Find box11 but not box00 auto q3 = tree.begin_query({{-9, -9}, {11, 11}}, FilterNoOp(), query_type); ASSERT_NE(q3, tree.end()); ASSERT_EQ(1, (*q3)); - q3++; + ++q3; ASSERT_EQ(q3, tree.end()); } diff --git a/test/phtree_d_test.cc b/test/phtree_d_test.cc index 5afd03d..f847b66 100644 --- a/test/phtree_d_test.cc +++ b/test/phtree_d_test.cc @@ -154,7 +154,7 @@ void SmokeTestBasicOps(size_t N) { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } @@ -263,7 +263,7 @@ TEST(PhTreeDTest, TestInsert) { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } @@ -310,7 +310,7 @@ TEST(PhTreeDTest, TestEmplace) { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } @@ -354,7 +354,7 @@ TEST(PhTreeDTest, TestSquareBrackets) { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } @@ -905,7 +905,7 @@ TEST(PhTreeDTest, TestWindowQuery1) { // just read the entry auto& x = *q; ASSERT_EQ(i, x._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); n++; } @@ -1022,9 +1022,9 @@ TEST(PhTreeDTest, TestWindowQueryIterators) { ASSERT_NE(q1, tree.end()); ASSERT_NE(q2, tree.end()); ASSERT_EQ(q1, q2); - q1++; + ++q1; ASSERT_NE(q1, q2); - q2++; + ++q2; n++; } ASSERT_EQ(N, n); @@ -1085,7 +1085,7 @@ TEST(PhTreeDTest, TestKnnQuery) { ASSERT_EQ(sorted_data[n]._id, q.second()._i); ASSERT_GE(q.distance(), prevDist); prevDist = q.distance(); - q++; + ++q; n++; } ASSERT_EQ(Nq, n); @@ -1138,7 +1138,7 @@ TEST(PhTreeDTest, TestKnnQueryFilterAndDistanceL1) { ASSERT_EQ(sorted_data[n]._id, q.second()._i); ASSERT_GE(q.distance(), prevDist); prevDist = q.distance(); - q++; + ++q; n++; } ASSERT_EQ(Nq, n); @@ -1164,9 +1164,9 @@ TEST(PhTreeDTest, TestKnnQueryIterator) { ASSERT_NE(q1, tree.end()); ASSERT_NE(q2, tree.end()); ASSERT_EQ(q1, q2); - q1++; + ++q1; ASSERT_NE(q1, q2); - q2++; + ++q2; n++; } ASSERT_EQ(Nq, n); @@ -1194,7 +1194,7 @@ TEST(PhTreeDTest, SmokeTestPoint0) { } TEST(PhTreeDTest, SmokeTestPointInfinity) { - // Test inifnity. + // Test infinity. double positive_infinity = std::numeric_limits::infinity(); double negative_infinity = -positive_infinity; PhPointD<3> p_pos{positive_infinity, positive_infinity, positive_infinity}; diff --git a/test/phtree_d_test_copy_move.cc b/test/phtree_d_test_copy_move.cc index d4c6abb..1f30997 100644 --- a/test/phtree_d_test_copy_move.cc +++ b/test/phtree_d_test_copy_move.cc @@ -133,7 +133,7 @@ void SmokeTestBasicOps_QueryAndErase(TestTree& tree, std::vector& tree, std::vector()); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } diff --git a/test/phtree_d_test_custom_key.cc b/test/phtree_d_test_custom_key.cc index 3e2c536..24fbd00 100644 --- a/test/phtree_d_test_custom_key.cc +++ b/test/phtree_d_test_custom_key.cc @@ -181,7 +181,7 @@ void SmokeTestBasicOps() { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } diff --git a/test/phtree_d_test_preprocessor.cc b/test/phtree_d_test_preprocessor.cc index 588a2a2..f68a8f2 100644 --- a/test/phtree_d_test_preprocessor.cc +++ b/test/phtree_d_test_preprocessor.cc @@ -116,7 +116,7 @@ void SmokeTestBasicOps() { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } diff --git a/test/phtree_f_test.cc b/test/phtree_f_test.cc index 5ea9464..02d4896 100644 --- a/test/phtree_f_test.cc +++ b/test/phtree_f_test.cc @@ -150,7 +150,7 @@ void SmokeTestBasicOps(size_t N) { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } @@ -257,7 +257,7 @@ TEST(PhTreeFTest, TestInsert) { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } @@ -304,7 +304,7 @@ TEST(PhTreeFTest, TestEmplace) { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } @@ -348,7 +348,7 @@ TEST(PhTreeFTest, TestSquareBrackets) { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } @@ -628,7 +628,7 @@ TEST(PhTreeFTest, TestWindowQuery1) { // just read the entry auto& x = *q; ASSERT_EQ(i, x._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); n++; } @@ -753,9 +753,9 @@ TEST(PhTreeFTest, TestWindowQueryIterators) { ASSERT_NE(q1, tree.end()); ASSERT_NE(q2, tree.end()); ASSERT_EQ(q1, q2); - q1++; + ++q1; ASSERT_NE(q1, q2); - q2++; + ++q2; n++; } ASSERT_EQ(N, n); @@ -816,7 +816,7 @@ TEST(PhTreeFTest, TestKnnQuery) { ASSERT_EQ(sorted_data[n]._id, q.second()._i); ASSERT_GE(q.distance(), prevDist); prevDist = q.distance(); - q++; + ++q; n++; } ASSERT_EQ(Nq, n); @@ -869,7 +869,7 @@ TEST(PhTreeFTest, TestKnnQueryFilterAndDistanceL1) { ASSERT_EQ(sorted_data[n]._id, q.second()._i); ASSERT_GE(q.distance(), prevDist); prevDist = q.distance(); - q++; + ++q; n++; } ASSERT_EQ(Nq, n); @@ -895,9 +895,9 @@ TEST(PhTreeFTest, TestKnnQueryIterator) { ASSERT_NE(q1, tree.end()); ASSERT_NE(q2, tree.end()); ASSERT_EQ(q1, q2); - q1++; + ++q1; ASSERT_NE(q1, q2); - q2++; + ++q2; n++; } ASSERT_EQ(Nq, n); diff --git a/test/phtree_multimap_d_test.cc b/test/phtree_multimap_d_test.cc index eb7d7f5..8c87efb 100644 --- a/test/phtree_multimap_d_test.cc +++ b/test/phtree_multimap_d_test.cc @@ -176,7 +176,7 @@ void SmokeTestBasicOps(size_t N) { ASSERT_NE(q, tree.end()); for (size_t j = 0; j < NUM_DUPL; j++) { ASSERT_EQ(i / NUM_DUPL, (*q)._i / NUM_DUPL); - q++; + ++q; } ASSERT_EQ(q, tree.end()); } @@ -293,7 +293,7 @@ TEST(PhTreeMMDTest, TestInsert) { ASSERT_NE(q, tree.end()); for (size_t j = 0; j < NUM_DUPL; j++) { ASSERT_EQ(i / NUM_DUPL, (*q)._i / NUM_DUPL); - q++; + ++q; } ASSERT_EQ(q, tree.end()); } @@ -346,7 +346,7 @@ TEST(PhTreeMMDTest, TestEmplace) { ASSERT_NE(q, tree.end()); for (size_t j = 0; j < NUM_DUPL; j++) { ASSERT_EQ(i / NUM_DUPL, (*q)._i / NUM_DUPL); - q++; + ++q; } ASSERT_EQ(q, tree.end()); } @@ -927,7 +927,7 @@ TEST(PhTreeMMDTest, TestWindowQuery1) { auto& x = *q; for (size_t j = 0; j < NUM_DUPL; j++) { ASSERT_EQ(i / NUM_DUPL, x._i / NUM_DUPL); - q++; + ++q; } ASSERT_EQ(q, tree.end()); n++; @@ -1039,9 +1039,9 @@ TEST(PhTreeMMDTest, TestWindowQueryIterators) { ASSERT_NE(q1, tree.end()); ASSERT_NE(q2, tree.end()); ASSERT_EQ(q1, q2); - q1++; + ++q1; ASSERT_NE(q1, q2); - q2++; + ++q2; n++; } ASSERT_EQ(N, n); @@ -1102,7 +1102,7 @@ TEST(PhTreeMMDTest, TestKnnQuery) { ASSERT_EQ(sorted_data[n]._id / NUM_DUPL, q->_i / NUM_DUPL); ASSERT_GE(q.distance(), prevDist); prevDist = q.distance(); - q++; + ++q; n++; } ASSERT_EQ(Nq * NUM_DUPL, n); @@ -1157,7 +1157,7 @@ TEST(PhTreeMMDTest, TestKnnQueryFilterAndDistanceL1) { ASSERT_GE(q.distance(), prevDist); prevDist = q.distance(); - q++; + ++q; n++; } std::sort(sorted_results.begin(), sorted_results.end(), comparePointDistanceAndId); @@ -1190,9 +1190,9 @@ TEST(PhTreeMMDTest, TestKnnQueryIterator) { ASSERT_NE(q1, tree.end()); ASSERT_NE(q2, tree.end()); ASSERT_EQ(q1, q2); - q1++; + ++q1; ASSERT_NE(q1, q2); - q2++; + ++q2; n++; } ASSERT_EQ(Nq * NUM_DUPL, n); diff --git a/test/phtree_multimap_d_test_copy_move.cc b/test/phtree_multimap_d_test_copy_move.cc index ed3c652..6bc14d5 100644 --- a/test/phtree_multimap_d_test_copy_move.cc +++ b/test/phtree_multimap_d_test_copy_move.cc @@ -174,7 +174,7 @@ void SmokeTestBasicOps_QueryAndErase(TestTree& tree, std::vector_i / NUM_DUPL); - q++; + ++q; } ASSERT_EQ(q, tree.end()); } diff --git a/test/phtree_test.cc b/test/phtree_test.cc index 7b57f16..e03aae8 100644 --- a/test/phtree_test.cc +++ b/test/phtree_test.cc @@ -205,7 +205,7 @@ void SmokeTestBasicOps(size_t N) { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } @@ -334,7 +334,7 @@ TEST(PhTreeTest, TestInsert) { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } @@ -381,7 +381,7 @@ TEST(PhTreeTest, TestEmplace) { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } @@ -425,7 +425,7 @@ TEST(PhTreeTest, TestSquareBrackets) { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } @@ -987,7 +987,7 @@ TEST(PhTreeTest, TestWindowQuery1) { // just read the entry auto& x = *q; ASSERT_EQ(i, x._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); n++; } @@ -1149,9 +1149,9 @@ TEST(PhTreeTest, TestWindowQueryIterators) { ASSERT_NE(q1, tree.end()); ASSERT_NE(q2, tree.end()); ASSERT_EQ(q1, q2); - q1++; + ++q1; ASSERT_NE(q1, q2); - q2++; + ++q2; n++; } ASSERT_EQ(N, n); @@ -1212,7 +1212,7 @@ TEST(PhTreeTest, TestKnnQuery) { ASSERT_EQ(sorted_data[n]._id, q.second()._i); ASSERT_GE(q.distance(), prevDist); prevDist = q.distance(); - q++; + ++q; n++; } ASSERT_EQ(Nq, n); @@ -1262,7 +1262,7 @@ TEST(PhTreeTest, TestKnnQueryFilterAndDistanceL1) { // entries with the same distance but with different ordering than sorted_data. ASSERT_GE(q.distance(), prevDist); prevDist = q.distance(); - q++; + ++q; n++; } ASSERT_EQ(Nq, n); @@ -1288,9 +1288,9 @@ TEST(PhTreeTest, TestKnnQueryIterator) { ASSERT_NE(q1, tree.end()); ASSERT_NE(q2, tree.end()); ASSERT_EQ(q1, q2); - q1++; + ++q1; ASSERT_NE(q1, q2); - q2++; + ++q2; n++; } ASSERT_EQ(Nq, n); diff --git a/test/phtree_test_const_values.cc b/test/phtree_test_const_values.cc index 64dd432..bca5db7 100644 --- a/test/phtree_test_const_values.cc +++ b/test/phtree_test_const_values.cc @@ -146,7 +146,7 @@ void SmokeTestBasicOps() { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } @@ -248,7 +248,7 @@ TEST(PhTreeTestConst, TestInsert) { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } @@ -289,7 +289,7 @@ TEST(PhTreeTestConst, TestEmplace) { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } @@ -322,7 +322,7 @@ TEST(PhTreeTestConst, TestSquareBrackets) { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(0, (*q)._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } } @@ -554,7 +554,7 @@ TEST(PhTreeTestConst, TestWindowQuery1) { // just read the entry auto& x = *q; ASSERT_EQ(i, x._i); - q++; + ++q; ASSERT_EQ(q, tree.end()); n++; } @@ -630,9 +630,9 @@ TEST(PhTreeTestConst, TestWindowQueryIterators) { ASSERT_NE(q1, tree.end()); ASSERT_NE(q2, tree.end()); ASSERT_EQ(q1, q2); - q1++; + ++q1; ASSERT_NE(q1, q2); - q2++; + ++q2; n++; } ASSERT_EQ(N, n); @@ -693,7 +693,7 @@ TEST(PhTreeTestConst, TestKnnQuery) { ASSERT_EQ(sorted_data[n]._id, q.second()._i); ASSERT_GE(q.distance(), prevDist); prevDist = q.distance(); - q++; + ++q; n++; } ASSERT_EQ(Nq, n); diff --git a/test/phtree_test_ptr_values.cc b/test/phtree_test_ptr_values.cc index 6368b47..4d990a0 100644 --- a/test/phtree_test_ptr_values.cc +++ b/test/phtree_test_ptr_values.cc @@ -146,7 +146,7 @@ void SmokeTestBasicOps() { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)->_i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } @@ -257,7 +257,7 @@ TEST(PhTreeTestPtr, TestInsert) { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)->_i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } @@ -311,7 +311,7 @@ TEST(PhTreeTestPtr, TestEmplace) { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)->_i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } @@ -362,7 +362,7 @@ TEST(PhTreeTestPtr, TestSquareBrackets) { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)->_i); - q++; + ++q; ASSERT_EQ(q, tree.end()); } @@ -628,7 +628,7 @@ TEST(PhTreeTestPtr, TestWindowQuery1) { // just read the entry auto x = *q; ASSERT_EQ(i, x->_i); - q++; + ++q; ASSERT_EQ(q, tree.end()); n++; } @@ -708,9 +708,9 @@ TEST(PhTreeTestPtr, TestWindowQueryIterators) { ASSERT_NE(q1, tree.end()); ASSERT_NE(q2, tree.end()); ASSERT_EQ(q1, q2); - q1++; + ++q1; ASSERT_NE(q1, q2); - q2++; + ++q2; n++; } ASSERT_EQ(N, n); @@ -775,7 +775,7 @@ TEST(PhTreeTestPtr, TestKnnQuery) { ASSERT_EQ(sorted_data[n]._id, q.second()->_i); ASSERT_GE(q.distance(), prevDist); prevDist = q.distance(); - q++; + ++q; n++; } ASSERT_EQ(Nq, n); diff --git a/test/phtree_test_unique_ptr_values.cc b/test/phtree_test_unique_ptr_values.cc index 1be2bc0..841562c 100644 --- a/test/phtree_test_unique_ptr_values.cc +++ b/test/phtree_test_unique_ptr_values.cc @@ -147,7 +147,7 @@ void SmokeTestBasicOps(int N) { auto q = tree.begin_query({p, p}); ASSERT_NE(q, tree.end()); ASSERT_EQ(i, (*q)->_i); - q++; + ++q; ASSERT_EQ(q, tree.end()); }