From 5b917829cb2e607364c6a9395452aa4d62a3828c Mon Sep 17 00:00:00 2001
From: tzaeschke <zoodb@gmx.de>
Date: Thu, 15 Dec 2022 17:41:16 +0100
Subject: [PATCH 1/6] initial

---
 include/phtree/v16/phtree_v16.h    | 133 ++++++++++++++++++++++++++++-
 test/phtree_multimap_box_d_test.cc |  49 ++---------
 2 files changed, 139 insertions(+), 43 deletions(-)

diff --git a/include/phtree/v16/phtree_v16.h b/include/phtree/v16/phtree_v16.h
index dc7e951f..965b38e4 100644
--- a/include/phtree/v16/phtree_v16.h
+++ b/include/phtree/v16/phtree_v16.h
@@ -459,7 +459,7 @@ class PhTreeV16 {
      * - CREATES the destination entry if it does not exist!
      */
     template <typename RELOCATE, typename COUNT>
-    size_t _relocate_mm(
+    size_t _relocate_mm2(
         const KeyT& old_key,
         const KeyT& new_key,
         bool verify_exists,
@@ -540,6 +540,137 @@ class PhTreeV16 {
         return result;
     }
 
+    template <typename RELOCATE, typename COUNT>
+    size_t _relocate_mm(
+        const KeyT& old_key,
+        const KeyT& new_key,
+        bool verify_exists,
+        RELOCATE&& relocate_fn,
+        COUNT&& count_fn) {
+        bit_width_t n_diverging_bits = NumberOfDivergingBits(old_key, new_key);
+
+        // EntryIterator<DIM, EntryT> iter = root_.GetNode().End();
+        auto iter = root_.GetNode().End();
+        EntryT* current_entry = &root_;           // An entry.
+        EntryT* old_node_entry = nullptr;         // Node that contains entry to be removed
+        EntryT* old_node_entry_parent = nullptr;  // Parent of the old_node_entry
+        EntryT* new_node_entry = nullptr;         // Node that will contain  new entry
+        // Find node for removal
+        while (current_entry && current_entry->IsNode()) {
+            old_node_entry_parent = old_node_entry;
+            old_node_entry = current_entry;
+            auto postfix_len = old_node_entry->GetNodePostfixLen();
+            if (postfix_len + 1 >= n_diverging_bits) {
+                new_node_entry = old_node_entry;
+            }
+            // TODO stop earlier, we are going to have to redo this after insert....
+            bool is_found = false;
+            iter = current_entry->GetNode().LowerBound(old_key, postfix_len, is_found);
+            current_entry = is_found ? &iter->second : nullptr;
+        }
+        EntryT* old_entry = current_entry;  // Entry to be removed
+
+        // Can we stop already?
+        if (old_entry == nullptr) {
+            return 0;  // old_key not found!
+        }
+
+        // Are the keys equal?
+        if (n_diverging_bits == 0) {
+            return count_fn(old_entry->GetValue());
+        }
+        // Are the keys in the same quadrant? -> same entry
+        if (old_node_entry->GetNodePostfixLen() >= n_diverging_bits) {
+            if (old_entry->GetValue().size() == 1) {
+                auto result = count_fn(old_entry->GetValue());
+                if (result > 0) {
+                    old_entry->SetKey(new_key);
+                }
+                return result;
+            }
+
+            // insert new entry and recalculate old_entry.
+//            bool is_inserted;
+//            auto new_entry = &new_node_entry->GetNode().Emplace(
+//                iter, is_inserted, new_key, new_node_entry->GetNodePostfixLen());
+//            ++num_entries_;
+//            assert(is_inserted);
+//            old_entry = old_node_entry;
+//            while (old_entry && old_entry->IsNode()) {
+//                old_node_entry = old_entry;
+//                old_entry = old_entry->GetNode().Find(old_key, old_entry->GetNodePostfixLen());
+//            }
+//            auto result = relocate_fn(old_entry->GetValue(), new_entry->GetValue());
+//            if (result == 0) {
+//                clean_up(new_key, new_entry, new_node_entry);
+//            } else {
+//                clean_up(old_key, old_entry, old_node_entry);
+//            }
+//            return result;
+        }
+
+        bool same_node = old_node_entry == new_node_entry;
+
+        // Find node for insertion
+        auto new_entry = new_node_entry;
+        bool is_found = false;
+        while (new_entry && new_entry->IsNode()) {
+            is_found = false;  // TODO fix for non-MM?!?!
+            new_node_entry = new_entry;
+            iter =
+                new_entry->GetNode().LowerBound(new_key, new_entry->GetNodePostfixLen(), is_found);
+            new_entry = is_found ? &iter->second : nullptr;
+        }
+        if (!is_found) {
+            bool is_inserted = false;
+            new_entry = &new_node_entry->GetNode().Emplace(
+                iter,
+                is_inserted,
+                new_key,
+                new_node_entry->GetNodePostfixLen());  // TODO only MM
+            num_entries_ += is_inserted;
+
+            // adjust old_entry if necessary
+            if (same_node) {
+                old_entry = old_node_entry;
+                while (old_entry && old_entry->IsNode()) {
+                    old_node_entry = old_entry;
+                    old_entry = old_entry->GetNode().Find(old_key, old_entry->GetNodePostfixLen());
+                }
+            }
+        }
+
+        // vanilla relocate
+        auto result = relocate_fn(old_entry->GetValue(), new_entry->GetValue());
+
+        // Erase old value. See comments in try_emplace(iterator) for details.
+        if (old_node_entry_parent == new_node_entry) {
+            // In this case the old_node_entry may have been invalidated by the previous
+            // insertion.
+            old_node_entry = old_node_entry_parent;
+        }
+
+        clean_up(new_key, new_entry, new_node_entry);
+        clean_up(old_key, old_entry, old_node_entry);
+        return result;
+    }
+
+  private:
+    void clean_up(const KeyT& key, EntryT* entry, EntryT* node_entry) {
+        // It may happen that node_entry is not the immediate parent, but that is okay!
+        // TODO use iterators?
+        if (entry != nullptr && entry->GetValue().empty()) {
+            bool found = false;  // TODO fix in non-MM
+            while (node_entry != nullptr && node_entry->IsNode()) {
+                found = false;
+                node_entry =
+                    node_entry->GetNode().Erase(key, node_entry, node_entry != &root_, found);
+            }
+            num_entries_ -= found;
+        }
+    }
+
+  public:
     auto _find_or_create_two_mm(const KeyT& old_key, const KeyT& new_key, bool count_equals) {
         using Iter = IteratorWithParent<T, CONVERT>;
         bit_width_t n_diverging_bits = NumberOfDivergingBits(old_key, new_key);
diff --git a/test/phtree_multimap_box_d_test.cc b/test/phtree_multimap_box_d_test.cc
index 386de516..abb4ada7 100644
--- a/test/phtree_multimap_box_d_test.cc
+++ b/test/phtree_multimap_box_d_test.cc
@@ -499,41 +499,6 @@ TEST(PhTreeMMBoxDTest, TestUpdateWithEmplaceHint) {
     tree.clear();
 }
 
-// TEST(PhTreeMMDTest, TestUpdateWithRelocate) {
-//     const dimension_t dim = 3;
-//     TestTree<dim, Id> tree;
-//     size_t N = 10000;
-//     std::array<double, 4> deltas{0, 0.1, 1, 10};
-//     std::vector<TestPoint<dim>> points;
-//     populate(tree, points, N);
-//
-//     for (auto delta : deltas) {
-//         size_t i = 0;
-//         for (auto& p : points) {
-//             auto pOld = p;
-//             TestPoint<dim> pNew;
-//             if (relocate_to_existing_coordinate) {
-//                 pNew = delta > 0.0 ? points[(i + 17) % N] : pOld;
-//             } else {
-//                 pNew = {pOld[0] + delta, pOld[1] + delta, pOld[2] + delta};
-//             }
-//             PhPointD<dim> min{pOld.min()[0] + delta, pOld.min()[1] + delta, pOld.min()[2] +
-//             delta}; PhPointD<dim> max{pOld.max()[0] + delta, pOld.max()[1] + delta, pOld.max()[2]
-//             + delta}; TestPoint<dim> pNew{min, max}; ASSERT_EQ(1, tree.relocate(pOld, pNew,
-//             Id(i))); if (delta > 0.0) {
-//                 // second time fails because value has already been moved
-//                 ASSERT_EQ(0, tree.relocate(pOld, pNew, Id(i)));
-//             }
-//             ASSERT_EQ(Id(i), *tree.find(pNew, Id(i)));
-//             p = pNew;
-//             ++i;
-//         }
-//     }
-//
-//     ASSERT_EQ(N, tree.size());
-//     tree.clear();
-// }
-
 void TestUpdateWithRelocate(bool relocate_to_existing_coordinate) {
     const dimension_t dim = 3;
     TestTree<dim, Id> tree;
@@ -575,15 +540,15 @@ void TestUpdateWithRelocate(bool relocate_to_existing_coordinate) {
     tree.clear();
 }
 
-TEST(PhTreeMMDTest, TestUpdateWithRelocateDelta) {
+TEST(PhTreeMMBoxDTest, TestUpdateWithRelocateDelta) {
     TestUpdateWithRelocate(false);
 }
 
-TEST(PhTreeMMDTest, TestUpdateWithRelocateToExisting) {
+TEST(PhTreeMMBoxDTest, TestUpdateWithRelocateToExisting) {
     TestUpdateWithRelocate(true);
 }
 
-TEST(PhTreeMMDTest, TestUpdateWithRelocateCornerCases) {
+TEST(PhTreeMMBoxDTest, TestUpdateWithRelocateCornerCases) {
     const dimension_t dim = 3;
     TestTree<dim, Id> tree;
     TestPoint<dim> point0{{1, 2, 3}, {2, 3, 4}};
@@ -703,7 +668,7 @@ struct FilterEvenId {
     }
 };
 
-TEST(PhTreeMMDTest, TestExtentFilter) {
+TEST(PhTreeMMBoxDTest, TestExtentFilter) {
     const dimension_t dim = 3;
     TestTree<dim, Id> tree;
     size_t N = 10000;
@@ -720,7 +685,7 @@ TEST(PhTreeMMDTest, TestExtentFilter) {
     ASSERT_EQ(N, num_e * 2);
 }
 
-TEST(PhTreeMMDTest, TestExtentForEachFilter) {
+TEST(PhTreeMMBoxDTest, TestExtentForEachFilter) {
     const dimension_t dim = 3;
     TestTree<dim, Id> tree;
     size_t N = 10000;
@@ -764,7 +729,7 @@ TEST(PhTreeMMBoxDTest, TestRangeBasedForLoop) {
     ASSERT_EQ(N, num_e2);
 }
 
-TEST(PhTreeMMDTest, TestEstimateCountIntersect) {
+TEST(PhTreeMMBoxDTest, TestEstimateCountIntersect) {
     const dimension_t dim = 3;
     TestTree<dim, Id> tree;
     size_t N = 1000;
@@ -792,7 +757,7 @@ TEST(PhTreeMMDTest, TestEstimateCountIntersect) {
     ASSERT_EQ(N, n_all);
 }
 
-TEST(PhTreeMMDTest, TestEstimateCountInclude) {
+TEST(PhTreeMMBoxDTest, TestEstimateCountInclude) {
     const dimension_t dim = 3;
     TestTree<dim, Id> tree;
     size_t N = 1000;

From fc8e17f43431571ea87b87c12a04f510ee359e9f Mon Sep 17 00:00:00 2001
From: tzaeschke <zoodb@gmx.de>
Date: Fri, 16 Dec 2022 17:37:54 +0100
Subject: [PATCH 2/6] initial

---
 include/phtree/v16/phtree_v16.h | 230 ++++++++++++++++++++++++++------
 1 file changed, 190 insertions(+), 40 deletions(-)

diff --git a/include/phtree/v16/phtree_v16.h b/include/phtree/v16/phtree_v16.h
index 965b38e4..4d93d1d8 100644
--- a/include/phtree/v16/phtree_v16.h
+++ b/include/phtree/v16/phtree_v16.h
@@ -26,6 +26,7 @@
 #include "iterator_knn_hs.h"
 #include "iterator_with_parent.h"
 #include "node.h"
+#include <iostream>
 
 namespace improbable::phtree::v16 {
 
@@ -459,7 +460,7 @@ class PhTreeV16 {
      * - CREATES the destination entry if it does not exist!
      */
     template <typename RELOCATE, typename COUNT>
-    size_t _relocate_mm2(
+    size_t _relocate_mm1(
         const KeyT& old_key,
         const KeyT& new_key,
         bool verify_exists,
@@ -541,7 +542,7 @@ class PhTreeV16 {
     }
 
     template <typename RELOCATE, typename COUNT>
-    size_t _relocate_mm(
+    size_t _relocate_mm3(
         const KeyT& old_key,
         const KeyT& new_key,
         bool verify_exists,
@@ -549,13 +550,184 @@ class PhTreeV16 {
         COUNT&& count_fn) {
         bit_width_t n_diverging_bits = NumberOfDivergingBits(old_key, new_key);
 
-        // EntryIterator<DIM, EntryT> iter = root_.GetNode().End();
+        if (!verify_exists && n_diverging_bits == 0) {
+            return 1;  // We omit calling because that would require looking up the entry...
+        }
+
         auto iter = root_.GetNode().End();
+        EntryT* new_entry = &root_;        // An entry.
+        EntryT* old_node_entry = nullptr;  // Node that contains entry to be removed
+        EntryT* new_node_entry = nullptr;  // Node that will contain  new entry
+        // Find the deepest common parent node for removal and insertion
+        bool is_inserted = false;
+        while (new_entry && new_entry->IsNode()) {
+            new_node_entry = new_entry;
+            auto postfix_len = new_entry->GetNodePostfixLen();
+            if (postfix_len + 1 >= n_diverging_bits) {
+                old_node_entry = new_node_entry;
+            }
+            new_entry = new_entry->GetNode().Find(new_key, postfix_len);
+            // We could instead use the following, this may be faster for sparse_map/b_plus_map
+            // but is very slow for array_map (even with '-mbmi2'
+//            bool is_found = false;
+//            iter = new_entry->GetNode().LowerBound(new_key, postfix_len, is_found);
+//            new_entry = is_found ? &iter->second : nullptr;
+        }
+        //old_node_entry = new_node_entry;
+
+        if (new_entry != nullptr && n_diverging_bits == 0) {
+            // keys are equal ...
+            return count_fn(new_entry->GetValue());
+        }
+
+        // Are we inserting in same node and same quadrant?
+        // This works only if the predicate has the same result for ALL entries. This can only
+        // be guaranteed if there is only one entry (or if we had proper TRUE/FALSE) predicates.
+        if (new_entry != nullptr &&
+            new_node_entry->GetNodePostfixLen() >= n_diverging_bits &&
+            new_entry->GetValue().size() == 1) {
+            // Technically this should be: (old_entry->GetValue().size() == 1), but that's the same.
+            auto result = count_fn(new_entry->GetValue());
+            if (result > 0) {
+                new_entry->SetKey(new_key);
+            }
+            return result;
+        }
+
+        // Find node for insertion of new bucket
+        if (new_entry == nullptr) {
+            new_entry = &new_node_entry->GetNode().Emplace(
+                is_inserted, new_key, new_node_entry->GetNodePostfixLen());
+            num_entries_ += is_inserted;
+            assert(new_entry != nullptr);
+        }
+
+        auto* old_entry = old_node_entry;
+        while (old_entry && old_entry->IsNode()) {
+            old_node_entry = old_entry;
+            old_entry = old_entry->GetNode().Find(old_key, old_entry->GetNodePostfixLen());
+        }
+
+        size_t result;
+        if (old_entry == nullptr) {
+            // Does old_entry exist?
+            result = 0;  // old_key not found or invalid!
+        } else if (n_diverging_bits == 0) {
+            // keys are equal ...
+            result = count_fn(old_entry->GetValue());
+//        } else if (
+//            old_node_entry->GetNodePostfixLen() >= n_diverging_bits &&
+//            old_entry->GetValue().size() == 1) {
+//            // Are we inserting in same node and same quadrant?
+//            // This works only if the predicate has the same result for ALL entries. This can only
+//            // be guaranteed if there is only one entry (or if we had proper TRUE/FALSE) predicates.
+//            result = count_fn(old_entry->GetValue());
+//            if (result > 0) {
+//                old_entry->SetKey(new_key);
+//            }
+        } else {
+            // vanilla relocate
+            result = relocate_fn(old_entry->GetValue(), new_entry->GetValue());
+        }
+
+        if (old_entry != nullptr && old_entry->GetValue().empty()) {
+            bool found = false;
+            old_node_entry->GetNode().Erase(
+                old_key, old_node_entry, old_node_entry != &root_, found);
+            num_entries_ -= found;
+        } else if (new_entry->GetValue().empty()) {
+            bool found = false;
+            // new_node_entry may not be the immediate parent because Node::emplace() may create
+            // subnodes.
+            while (new_node_entry != nullptr && new_node_entry->IsNode()) {
+                new_node_entry = new_node_entry->GetNode().Erase(
+                    new_key, new_node_entry, new_node_entry != &root_, found);
+            }
+            num_entries_ -= found;
+        }
+
+        return result;
+    }
+
+    inline static ulong N{0};
+    inline static ulong X1{0};
+    inline static ulong X2{0};
+    inline static ulong X3{0};
+    inline static ulong X4{0};
+    inline static ulong X5{0};
+    inline static ulong X6{0};
+    inline static ulong X7{0};
+    inline static ulong PREV_SIZE{0};
+
+    inline static ulong IBOTH{0};
+    inline static ulong INEW{0};
+    inline static ulong IOLD{0};
+    inline static ulong ICLEAN{0};
+
+    inline static ulong I1{0};
+    inline static ulong I2{0};
+    inline static ulong I3{0};
+    inline static ulong I4{0};
+    inline static ulong I5{0};
+    inline static ulong I6{0};
+
+    void print() {
+        if (num_entries_ >= PREV_SIZE * 10 || num_entries_*4 < PREV_SIZE) {
+//            std::cout << "S=" << num_entries_ << "  N=" << N << "  X=" << X1 << "  " << X2 << "  "
+//                      << X3 << "  " << X4 << "  " << X5 << "  " << X6 << "  " << X7 << "  "
+//                      << std::endl;
+//            std::cout << " both/n/o/c=" << IBOTH << " " << INEW << "  " << IOLD << "  " << ICLEAN
+//                      << " I=" << I1 << "  " << I2 << "  " << I3 << "  " << I4 << "  " << I5 << "  "
+//                      << std::endl;
+            PREV_SIZE = num_entries_;
+            N = 0;
+            X1 = 0;
+            X2 = 0;
+            X3 = 0;
+            X4 = 0;
+            X5 = 0;
+            X6 = 0;
+            X7 = 0;
+            IBOTH = 0;
+            ICLEAN = 0;
+            INEW = 0;
+            IOLD = 0;
+            I1 = 0;
+            I2 = 0;
+            I3 = 0;
+            I4 = 0;
+            I5 = 0;
+        }
+
+        ++N;
+        if (N % 200000 == 0 && num_entries_ > 200000) {
+            auto tot = I1 + I2 + I3 + I4 + I5;
+            std::cout << "N=" << N << "  X=" << X1 << "  " << X2 << "  " << X3 << "  " << X4 << "  "
+                      << X5 << "  " << X6 << "  " << X7 << "  " << std::endl;
+            std::cout << " both/n/o/c=" << IBOTH << " " << INEW << "  " << IOLD << "  " << ICLEAN
+                      << " I=" << I1 << "  " << I2 << "  " << I3 << "  " << I4 << "  " << I5
+                      << "  = " << tot << std::endl;
+        }
+    }
+
+    template <typename RELOCATE, typename COUNT>
+    size_t _relocate_mm(
+        const KeyT& old_key,
+        const KeyT& new_key,
+        bool verify_exists,
+        RELOCATE&& relocate_fn,
+        COUNT&& count_fn) {
+        bit_width_t n_diverging_bits = NumberOfDivergingBits(old_key, new_key);
+
+        if (!verify_exists && n_diverging_bits == 0) {
+            return 1;  // We omit calling because that would require looking up the entry...
+        }
+
         EntryT* current_entry = &root_;           // An entry.
         EntryT* old_node_entry = nullptr;         // Node that contains entry to be removed
         EntryT* old_node_entry_parent = nullptr;  // Parent of the old_node_entry
         EntryT* new_node_entry = nullptr;         // Node that will contain  new entry
-        // Find node for removal
+        // Find node or entry for removal
         while (current_entry && current_entry->IsNode()) {
             old_node_entry_parent = old_node_entry;
             old_node_entry = current_entry;
@@ -564,9 +736,10 @@ class PhTreeV16 {
                 new_node_entry = old_node_entry;
             }
             // TODO stop earlier, we are going to have to redo this after insert....
-            bool is_found = false;
-            iter = current_entry->GetNode().LowerBound(old_key, postfix_len, is_found);
-            current_entry = is_found ? &iter->second : nullptr;
+//            bool is_found = false;
+//            iter = current_entry->GetNode().LowerBound(old_key, postfix_len, is_found);
+//            current_entry = is_found ? &iter->second : nullptr;
+            current_entry = current_entry->GetNode().Find(old_key, postfix_len);
         }
         EntryT* old_entry = current_entry;  // Entry to be removed
 
@@ -588,52 +761,28 @@ class PhTreeV16 {
                 }
                 return result;
             }
-
-            // insert new entry and recalculate old_entry.
-//            bool is_inserted;
-//            auto new_entry = &new_node_entry->GetNode().Emplace(
-//                iter, is_inserted, new_key, new_node_entry->GetNodePostfixLen());
-//            ++num_entries_;
-//            assert(is_inserted);
-//            old_entry = old_node_entry;
-//            while (old_entry && old_entry->IsNode()) {
-//                old_node_entry = old_entry;
-//                old_entry = old_entry->GetNode().Find(old_key, old_entry->GetNodePostfixLen());
-//            }
-//            auto result = relocate_fn(old_entry->GetValue(), new_entry->GetValue());
-//            if (result == 0) {
-//                clean_up(new_key, new_entry, new_node_entry);
-//            } else {
-//                clean_up(old_key, old_entry, old_node_entry);
-//            }
-//            return result;
         }
 
         bool same_node = old_node_entry == new_node_entry;
 
         // Find node for insertion
         auto new_entry = new_node_entry;
-        bool is_found = false;
+        bool is_inserted = false;
         while (new_entry && new_entry->IsNode()) {
-            is_found = false;  // TODO fix for non-MM?!?!
+            //is_found = false;  // TODO fix for non-MM?!?!
             new_node_entry = new_entry;
-            iter =
-                new_entry->GetNode().LowerBound(new_key, new_entry->GetNodePostfixLen(), is_found);
-            new_entry = is_found ? &iter->second : nullptr;
-        }
-        if (!is_found) {
-            bool is_inserted = false;
-            new_entry = &new_node_entry->GetNode().Emplace(
-                iter,
-                is_inserted,
-                new_key,
-                new_node_entry->GetNodePostfixLen());  // TODO only MM
+            is_inserted = false;
+            new_entry =
+                &new_entry->GetNode().Emplace(is_inserted, new_key, new_entry->GetNodePostfixLen());
             num_entries_ += is_inserted;
-
+        }
+        if (is_inserted) {
             // adjust old_entry if necessary
             if (same_node) {
                 old_entry = old_node_entry;
                 while (old_entry && old_entry->IsNode()) {
+                    ++I3;
+                    ++IOLD;
                     old_node_entry = old_entry;
                     old_entry = old_entry->GetNode().Find(old_key, old_entry->GetNodePostfixLen());
                 }
@@ -662,6 +811,7 @@ class PhTreeV16 {
         if (entry != nullptr && entry->GetValue().empty()) {
             bool found = false;  // TODO fix in non-MM
             while (node_entry != nullptr && node_entry->IsNode()) {
+                ++ICLEAN;
                 found = false;
                 node_entry =
                     node_entry->GetNode().Erase(key, node_entry, node_entry != &root_, found);

From 3527d2dc4e00c47f4427d353a6c749cd4a3e5d2a Mon Sep 17 00:00:00 2001
From: tzaeschke <zoodb@gmx.de>
Date: Fri, 16 Dec 2022 18:18:10 +0100
Subject: [PATCH 3/6] initial

---
 include/phtree/v16/phtree_v16.h | 317 +++-----------------------------
 1 file changed, 28 insertions(+), 289 deletions(-)

diff --git a/include/phtree/v16/phtree_v16.h b/include/phtree/v16/phtree_v16.h
index 4d93d1d8..e9e5ad6e 100644
--- a/include/phtree/v16/phtree_v16.h
+++ b/include/phtree/v16/phtree_v16.h
@@ -453,263 +453,20 @@ class PhTreeV16 {
 
   public:
     /*
-     * Tries to locate two entries that are 'close' to each other.
+     * This function is used (internally) by the PH-tree multimap.
      *
-     * Special behavior:
-     * - returns end() if old_key does not exist;
-     * - CREATES the destination entry if it does not exist!
+     * Relocate (move) an entry from one position to another, subject to a predicate.
+     *
+     * @param old_key
+     * @param new_key
+     * @param verify_exists. If true, verifies that the keys exists, even if the keys are identical.
+     *              If false, identical keys simply return '1', even if the keys don´t actually
+     *              exist. This avoid searching the tree.
+     * @param RELOCATE  A function that handles relocation between buckets.
+     * @param COUNT     A function that veifies relocation in the same bucket, e.g. for identical
+     *              keys, or if the whole bucket is relocated.
+     * @return  The number of relocated entries.
      */
-    template <typename RELOCATE, typename COUNT>
-    size_t _relocate_mm1(
-        const KeyT& old_key,
-        const KeyT& new_key,
-        bool verify_exists,
-        RELOCATE&& relocate_fn,
-        COUNT&& count_fn) {
-        bit_width_t n_diverging_bits = NumberOfDivergingBits(old_key, new_key);
-
-        if (!verify_exists && n_diverging_bits == 0) {
-            return 1;  // We omit calling because that would require looking up the entry...
-        }
-
-        EntryT* new_entry = &root_;        // An entry.
-        EntryT* old_node_entry = nullptr;  // Node that contains entry to be removed
-        EntryT* new_node_entry = nullptr;  // Node that will contain  new entry
-        // Find the deepest common parent node for removal and insertion
-        bool is_inserted = false;
-        while (new_entry && new_entry->IsNode() &&
-               new_entry->GetNodePostfixLen() + 1 >= n_diverging_bits) {
-            new_node_entry = new_entry;
-            auto postfix_len = new_entry->GetNodePostfixLen();
-            new_entry = &new_entry->GetNode().Emplace(is_inserted, new_key, postfix_len);
-        }
-        old_node_entry = new_node_entry;
-
-        // Find node for insertion of new bucket
-        while (new_entry->IsNode()) {
-            new_node_entry = new_entry;
-            new_entry =
-                &new_entry->GetNode().Emplace(is_inserted, new_key, new_entry->GetNodePostfixLen());
-        }
-        num_entries_ += is_inserted;
-        assert(new_entry != nullptr);
-
-        auto* old_entry = old_node_entry;
-        while (old_entry && old_entry->IsNode()) {
-            old_node_entry = old_entry;
-            old_entry = old_entry->GetNode().Find(old_key, old_entry->GetNodePostfixLen());
-        }
-
-        size_t result;
-        if (old_entry == nullptr) {
-            // Does old_entry exist?
-            result = 0;  // old_key not found or invalid!
-        } else if (n_diverging_bits == 0) {
-            // keys are equal ...
-            result = count_fn(old_entry->GetValue());
-        } else if (
-            old_node_entry->GetNodePostfixLen() >= n_diverging_bits &&
-            old_entry->GetValue().size() == 1) {
-            // Are we inserting in same node and same quadrant?
-            // This works only if the predicate has the same result for ALL entries. This can only
-            // be guaranteed if there is only one entry (or if we had proper TRUE/FALSE) predicates.
-            result = count_fn(old_entry->GetValue());
-            if (result > 0) {
-                old_entry->SetKey(new_key);
-            }
-        } else {
-            // vanilla relocate
-            result = relocate_fn(old_entry->GetValue(), new_entry->GetValue());
-        }
-
-        if (old_entry != nullptr && old_entry->GetValue().empty()) {
-            bool found = false;
-            old_node_entry->GetNode().Erase(
-                old_key, old_node_entry, old_node_entry != &root_, found);
-            num_entries_ -= found;
-        } else if (new_entry->GetValue().empty()) {
-            bool found = false;
-            // new_node_entry may not be the immediate parent because Node::emplace() may create
-            // subnodes.
-            while (new_node_entry != nullptr && new_node_entry->IsNode()) {
-                new_node_entry = new_node_entry->GetNode().Erase(
-                    new_key, new_node_entry, new_node_entry != &root_, found);
-            }
-            num_entries_ -= found;
-        }
-
-        return result;
-    }
-
-    template <typename RELOCATE, typename COUNT>
-    size_t _relocate_mm3(
-        const KeyT& old_key,
-        const KeyT& new_key,
-        bool verify_exists,
-        RELOCATE&& relocate_fn,
-        COUNT&& count_fn) {
-        bit_width_t n_diverging_bits = NumberOfDivergingBits(old_key, new_key);
-
-        if (!verify_exists && n_diverging_bits == 0) {
-            return 1;  // We omit calling because that would require looking up the entry...
-        }
-
-        auto iter = root_.GetNode().End();
-        EntryT* new_entry = &root_;        // An entry.
-        EntryT* old_node_entry = nullptr;  // Node that contains entry to be removed
-        EntryT* new_node_entry = nullptr;  // Node that will contain  new entry
-        // Find the deepest common parent node for removal and insertion
-        bool is_inserted = false;
-        while (new_entry && new_entry->IsNode()) {
-            new_node_entry = new_entry;
-            auto postfix_len = new_entry->GetNodePostfixLen();
-            if (postfix_len + 1 >= n_diverging_bits) {
-                old_node_entry = new_node_entry;
-            }
-            new_entry = new_entry->GetNode().Find(new_key, postfix_len);
-            // We could instead use the following, this may be faster for sparse_map/b_plus_map
-            // but is very slow for array_map (even with '-mbmi2'
-//            bool is_found = false;
-//            iter = new_entry->GetNode().LowerBound(new_key, postfix_len, is_found);
-//            new_entry = is_found ? &iter->second : nullptr;
-        }
-        //old_node_entry = new_node_entry;
-
-        if (new_entry != nullptr && n_diverging_bits == 0) {
-            // keys are equal ...
-            return count_fn(new_entry->GetValue());
-        }
-
-        // Are we inserting in same node and same quadrant?
-        // This works only if the predicate has the same result for ALL entries. This can only
-        // be guaranteed if there is only one entry (or if we had proper TRUE/FALSE) predicates.
-        if (new_entry != nullptr &&
-            new_node_entry->GetNodePostfixLen() >= n_diverging_bits &&
-            new_entry->GetValue().size() == 1) {
-            // Technically this should be: (old_entry->GetValue().size() == 1), but that's the same.
-            auto result = count_fn(new_entry->GetValue());
-            if (result > 0) {
-                new_entry->SetKey(new_key);
-            }
-            return result;
-        }
-
-        // Find node for insertion of new bucket
-        if (new_entry == nullptr) {
-            new_entry = &new_node_entry->GetNode().Emplace(
-                is_inserted, new_key, new_node_entry->GetNodePostfixLen());
-            num_entries_ += is_inserted;
-            assert(new_entry != nullptr);
-        }
-
-        auto* old_entry = old_node_entry;
-        while (old_entry && old_entry->IsNode()) {
-            old_node_entry = old_entry;
-            old_entry = old_entry->GetNode().Find(old_key, old_entry->GetNodePostfixLen());
-        }
-
-        size_t result;
-        if (old_entry == nullptr) {
-            // Does old_entry exist?
-            result = 0;  // old_key not found or invalid!
-        } else if (n_diverging_bits == 0) {
-            // keys are equal ...
-            result = count_fn(old_entry->GetValue());
-//        } else if (
-//            old_node_entry->GetNodePostfixLen() >= n_diverging_bits &&
-//            old_entry->GetValue().size() == 1) {
-//            // Are we inserting in same node and same quadrant?
-//            // This works only if the predicate has the same result for ALL entries. This can only
-//            // be guaranteed if there is only one entry (or if we had proper TRUE/FALSE) predicates.
-//            result = count_fn(old_entry->GetValue());
-//            if (result > 0) {
-//                old_entry->SetKey(new_key);
-//            }
-        } else {
-            // vanilla relocate
-            result = relocate_fn(old_entry->GetValue(), new_entry->GetValue());
-        }
-
-        if (old_entry != nullptr && old_entry->GetValue().empty()) {
-            bool found = false;
-            old_node_entry->GetNode().Erase(
-                old_key, old_node_entry, old_node_entry != &root_, found);
-            num_entries_ -= found;
-        } else if (new_entry->GetValue().empty()) {
-            bool found = false;
-            // new_node_entry may not be the immediate parent because Node::emplace() may create
-            // subnodes.
-            while (new_node_entry != nullptr && new_node_entry->IsNode()) {
-                new_node_entry = new_node_entry->GetNode().Erase(
-                    new_key, new_node_entry, new_node_entry != &root_, found);
-            }
-            num_entries_ -= found;
-        }
-
-        return result;
-    }
-
-    inline static ulong N{0};
-    inline static ulong X1{0};
-    inline static ulong X2{0};
-    inline static ulong X3{0};
-    inline static ulong X4{0};
-    inline static ulong X5{0};
-    inline static ulong X6{0};
-    inline static ulong X7{0};
-    inline static ulong PREV_SIZE{0};
-
-    inline static ulong IBOTH{0};
-    inline static ulong INEW{0};
-    inline static ulong IOLD{0};
-    inline static ulong ICLEAN{0};
-
-    inline static ulong I1{0};
-    inline static ulong I2{0};
-    inline static ulong I3{0};
-    inline static ulong I4{0};
-    inline static ulong I5{0};
-    inline static ulong I6{0};
-
-    void print() {
-        if (num_entries_ >= PREV_SIZE * 10 || num_entries_*4 < PREV_SIZE) {
-//            std::cout << "S=" << num_entries_ << "  N=" << N << "  X=" << X1 << "  " << X2 << "  "
-//                      << X3 << "  " << X4 << "  " << X5 << "  " << X6 << "  " << X7 << "  "
-//                      << std::endl;
-//            std::cout << " both/n/o/c=" << IBOTH << " " << INEW << "  " << IOLD << "  " << ICLEAN
-//                      << " I=" << I1 << "  " << I2 << "  " << I3 << "  " << I4 << "  " << I5 << "  "
-//                      << std::endl;
-            PREV_SIZE = num_entries_;
-            N = 0;
-            X1 = 0;
-            X2 = 0;
-            X3 = 0;
-            X4 = 0;
-            X5 = 0;
-            X6 = 0;
-            X7 = 0;
-            IBOTH = 0;
-            ICLEAN = 0;
-            INEW = 0;
-            IOLD = 0;
-            I1 = 0;
-            I2 = 0;
-            I3 = 0;
-            I4 = 0;
-            I5 = 0;
-        }
-
-        ++N;
-        if (N % 200000 == 0 && num_entries_ > 200000) {
-            auto tot = I1 + I2 + I3 + I4 + I5;
-            std::cout << "N=" << N << "  X=" << X1 << "  " << X2 << "  " << X3 << "  " << X4 << "  "
-                      << X5 << "  " << X6 << "  " << X7 << "  " << std::endl;
-            std::cout << " both/n/o/c=" << IBOTH << " " << INEW << "  " << IOLD << "  " << ICLEAN
-                      << " I=" << I1 << "  " << I2 << "  " << I3 << "  " << I4 << "  " << I5
-                      << "  = " << tot << std::endl;
-        }
-    }
-
     template <typename RELOCATE, typename COUNT>
     size_t _relocate_mm(
         const KeyT& old_key,
@@ -720,25 +477,19 @@ class PhTreeV16 {
         bit_width_t n_diverging_bits = NumberOfDivergingBits(old_key, new_key);
 
         if (!verify_exists && n_diverging_bits == 0) {
-            return 1;  // We omit calling because that would require looking up the entry...
+            return 1;  // We omit calling COUNT because that would require looking up the entry...
         }
 
-        EntryT* current_entry = &root_;           // An entry.
-        EntryT* old_node_entry = nullptr;         // Node that contains entry to be removed
-        EntryT* old_node_entry_parent = nullptr;  // Parent of the old_node_entry
-        EntryT* new_node_entry = nullptr;         // Node that will contain  new entry
+        EntryT* current_entry = &root_;    // An entry.
+        EntryT* old_node_entry = nullptr;  // Node that contains entry to be removed
+        EntryT* new_node_entry = nullptr;  // Node that will contain the new entry
         // Find node or entry for removal
         while (current_entry && current_entry->IsNode()) {
-            old_node_entry_parent = old_node_entry;
             old_node_entry = current_entry;
             auto postfix_len = old_node_entry->GetNodePostfixLen();
             if (postfix_len + 1 >= n_diverging_bits) {
                 new_node_entry = old_node_entry;
             }
-            // TODO stop earlier, we are going to have to redo this after insert....
-//            bool is_found = false;
-//            iter = current_entry->GetNode().LowerBound(old_key, postfix_len, is_found);
-//            current_entry = is_found ? &iter->second : nullptr;
             current_entry = current_entry->GetNode().Find(old_key, postfix_len);
         }
         EntryT* old_entry = current_entry;  // Entry to be removed
@@ -763,43 +514,33 @@ class PhTreeV16 {
             }
         }
 
-        bool same_node = old_node_entry == new_node_entry;
-
         // Find node for insertion
         auto new_entry = new_node_entry;
+        bool same_node = old_node_entry == new_node_entry;
         bool is_inserted = false;
         while (new_entry && new_entry->IsNode()) {
-            //is_found = false;  // TODO fix for non-MM?!?!
             new_node_entry = new_entry;
             is_inserted = false;
             new_entry =
                 &new_entry->GetNode().Emplace(is_inserted, new_key, new_entry->GetNodePostfixLen());
             num_entries_ += is_inserted;
         }
-        if (is_inserted) {
-            // adjust old_entry if necessary
-            if (same_node) {
-                old_entry = old_node_entry;
-                while (old_entry && old_entry->IsNode()) {
-                    ++I3;
-                    ++IOLD;
-                    old_node_entry = old_entry;
-                    old_entry = old_entry->GetNode().Find(old_key, old_entry->GetNodePostfixLen());
-                }
+
+        // Adjust old_entry if necessary, it may have been invalidated by emplace() in the same node
+        if (is_inserted && same_node) {
+            old_entry = old_node_entry;
+            while (old_entry && old_entry->IsNode()) {
+                old_node_entry = old_entry;
+                old_entry = old_entry->GetNode().Find(old_key, old_entry->GetNodePostfixLen());
             }
         }
 
-        // vanilla relocate
+        // relocate
         auto result = relocate_fn(old_entry->GetValue(), new_entry->GetValue());
 
-        // Erase old value. See comments in try_emplace(iterator) for details.
-        if (old_node_entry_parent == new_node_entry) {
-            // In this case the old_node_entry may have been invalidated by the previous
-            // insertion.
-            old_node_entry = old_node_entry_parent;
+        if (result == 0) {
+            clean_up(new_key, new_entry, new_node_entry);
         }
-
-        clean_up(new_key, new_entry, new_node_entry);
         clean_up(old_key, old_entry, old_node_entry);
         return result;
     }
@@ -807,11 +548,9 @@ class PhTreeV16 {
   private:
     void clean_up(const KeyT& key, EntryT* entry, EntryT* node_entry) {
         // It may happen that node_entry is not the immediate parent, but that is okay!
-        // TODO use iterators?
         if (entry != nullptr && entry->GetValue().empty()) {
-            bool found = false;  // TODO fix in non-MM
+            bool found = false;
             while (node_entry != nullptr && node_entry->IsNode()) {
-                ++ICLEAN;
                 found = false;
                 node_entry =
                     node_entry->GetNode().Erase(key, node_entry, node_entry != &root_, found);

From ab9ad8ab4a67835d1635d517daf93c1e03c61979 Mon Sep 17 00:00:00 2001
From: tzaeschke <zoodb@gmx.de>
Date: Fri, 16 Dec 2022 19:30:27 +0100
Subject: [PATCH 4/6] initial

---
 include/phtree/v16/phtree_v16.h | 16 ++++------------
 1 file changed, 4 insertions(+), 12 deletions(-)

diff --git a/include/phtree/v16/phtree_v16.h b/include/phtree/v16/phtree_v16.h
index e9e5ad6e..7483eefc 100644
--- a/include/phtree/v16/phtree_v16.h
+++ b/include/phtree/v16/phtree_v16.h
@@ -330,8 +330,6 @@ class PhTreeV16 {
     auto relocate_if(const KeyT& old_key, const KeyT& new_key, PRED&& pred) {
         bit_width_t n_diverging_bits = NumberOfDivergingBits(old_key, new_key);
 
-        // EntryIterator<DIM, EntryT> iter = root_.GetNode().End();
-        auto iter = root_.GetNode().End();
         EntryT* current_entry = &root_;           // An entry.
         EntryT* old_node_entry = nullptr;         // Node that contains entry to be removed
         EntryT* old_node_entry_parent = nullptr;  // Parent of the old_node_entry
@@ -345,9 +343,7 @@ class PhTreeV16 {
                 new_node_entry = old_node_entry;
             }
             // TODO stop earlier, we are going to have to redo this after insert....
-            bool is_found = false;
-            iter = current_entry->GetNode().LowerBound(old_key, postfix_len, is_found);
-            current_entry = is_found ? &iter->second : nullptr;
+            current_entry = current_entry->GetNode().Find(old_key, postfix_len);
         }
         EntryT* old_entry = current_entry;  // Entry to be removed
 
@@ -364,19 +360,15 @@ class PhTreeV16 {
 
         // Find node for insertion
         auto new_entry = new_node_entry;
-        bool is_found = false;
         while (new_entry && new_entry->IsNode()) {
             new_node_entry = new_entry;
-            iter =
-                new_entry->GetNode().LowerBound(new_key, new_entry->GetNodePostfixLen(), is_found);
-            new_entry = is_found ? &iter->second : nullptr;
+            new_entry = new_entry->GetNode().Find(new_key, new_entry->GetNodePostfixLen());
         }
-        if (is_found) {
+        if (new_entry != nullptr) {
             return 0;  // Entry exists
         }
         bool is_inserted = false;
         new_entry = &new_node_entry->GetNode().Emplace(
-            iter,
             is_inserted,
             new_key,
             new_node_entry->GetNodePostfixLen(),
@@ -389,7 +381,7 @@ class PhTreeV16 {
             old_node_entry = old_node_entry_parent;
         }
 
-        is_found = false;
+        bool is_found = false;
         // TODO use in-node iterator if possible
         while (old_node_entry) {
             old_node_entry = old_node_entry->GetNode().Erase(

From 73eff80a758859a6d5e016b97483a80a0966f42a Mon Sep 17 00:00:00 2001
From: tzaeschke <zoodb@gmx.de>
Date: Fri, 16 Dec 2022 20:08:32 +0100
Subject: [PATCH 5/6] initial

---
 include/phtree/v16/phtree_v16.h | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/include/phtree/v16/phtree_v16.h b/include/phtree/v16/phtree_v16.h
index 7483eefc..4030b738 100644
--- a/include/phtree/v16/phtree_v16.h
+++ b/include/phtree/v16/phtree_v16.h
@@ -353,7 +353,10 @@ class PhTreeV16 {
         }
 
         // Are the keys equal? Or is the quadrant the same? -> same entry
-        if (n_diverging_bits == 0 || old_node_entry->GetNodePostfixLen() >= n_diverging_bits) {
+        if (n_diverging_bits == 0) {
+            return 1;
+        }
+        if (old_node_entry->GetNodePostfixLen() >= n_diverging_bits) {
             old_entry->SetKey(new_key);
             return 1;
         }
@@ -382,7 +385,6 @@ class PhTreeV16 {
         }
 
         bool is_found = false;
-        // TODO use in-node iterator if possible
         while (old_node_entry) {
             old_node_entry = old_node_entry->GetNode().Erase(
                 old_key, old_node_entry, old_node_entry != &root_, is_found);

From 67a74f68832e2068e2ac0bba9626cf76d492d2cb Mon Sep 17 00:00:00 2001
From: tzaeschke <zoodb@gmx.de>
Date: Fri, 16 Dec 2022 20:13:14 +0100
Subject: [PATCH 6/6] initial

---
 CHANGELOG.md                    | 3 ++-
 include/phtree/v16/phtree_v16.h | 1 -
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 15334446..e7ea9ffd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,7 +13,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
 - Rewrote relocate(). This should be much cleaner now and slightly faster. 
   [#98](https://github.com/tzaeschke/phtree-cpp/pull/98), 
   [#99](https://github.com/tzaeschke/phtree-cpp/pull/99),
-  [#101](https://github.com/tzaeschke/phtree-cpp/pull/101) 
+  [#101](https://github.com/tzaeschke/phtree-cpp/pull/101),
+  [#104](https://github.com/tzaeschke/phtree-cpp/pull/104) 
 - Cleaned up HandleCollision() and key comparison functions. [#97](https://github.com/tzaeschke/phtree-cpp/pull/97)
 - Improved performance by eliminating memory indirection for DIM > 3. 
   This was enabled by referencing "Node" directly in "Entry" which was enabled by
diff --git a/include/phtree/v16/phtree_v16.h b/include/phtree/v16/phtree_v16.h
index 4030b738..6db28d41 100644
--- a/include/phtree/v16/phtree_v16.h
+++ b/include/phtree/v16/phtree_v16.h
@@ -26,7 +26,6 @@
 #include "iterator_knn_hs.h"
 #include "iterator_with_parent.h"
 #include "node.h"
-#include <iostream>
 
 namespace improbable::phtree::v16 {
 
