From 2be217627ca71059d19e09876528e9d435a05383 Mon Sep 17 00:00:00 2001 From: Tilmann Date: Sun, 11 Aug 2024 14:34:14 +0200 Subject: [PATCH] BallTree WIP --- src/main/java/org/tinspin/index/PointMap.java | 16 +- .../{QIterator0.java => BTIterator.java} | 19 +- .../{QIteratorKnn.java => BTIteratorKnn.java} | 22 +- .../org/tinspin/index/balltree/BTNode.java | 14 +- .../org/tinspin/index/balltree/BTUtil.java | 17 +- .../org/tinspin/index/balltree/BallTree.java | 18 +- .../tinspin/index/balltree/QIterator1.java | 182 ---------------- .../tinspin/index/balltree/QIterator2.java | 203 ------------------ .../org/tinspin/index/test/PointMapTest.java | 2 + 9 files changed, 65 insertions(+), 428 deletions(-) rename src/main/java/org/tinspin/index/balltree/{QIterator0.java => BTIterator.java} (88%) rename src/main/java/org/tinspin/index/balltree/{QIteratorKnn.java => BTIteratorKnn.java} (84%) delete mode 100644 src/main/java/org/tinspin/index/balltree/QIterator1.java delete mode 100644 src/main/java/org/tinspin/index/balltree/QIterator2.java diff --git a/src/main/java/org/tinspin/index/PointMap.java b/src/main/java/org/tinspin/index/PointMap.java index 96be83a..84b5667 100644 --- a/src/main/java/org/tinspin/index/PointMap.java +++ b/src/main/java/org/tinspin/index/PointMap.java @@ -18,6 +18,7 @@ package org.tinspin.index; import org.tinspin.index.array.PointArray; +import org.tinspin.index.balltree.BallTree; import org.tinspin.index.covertree.CoverTree; import org.tinspin.index.kdtree.KDTree; import org.tinspin.index.phtree.PHTreeP; @@ -130,11 +131,22 @@ static PointMap createArray(int dims, int size) { } /** - * Create a COverTree. + * Create a BallTree. * * @param dims Number of dimensions. * @param Value type - * @return New PH-Tree + * @return New BallTree + */ + static PointMap createBallTree(int dims) { + return BallTree.create(dims); + } + + /** + * Create a CoverTree. + * + * @param dims Number of dimensions. + * @param Value type + * @return New CoverTree */ static PointMap createCoverTree(int dims) { return CoverTree.create(dims); diff --git a/src/main/java/org/tinspin/index/balltree/QIterator0.java b/src/main/java/org/tinspin/index/balltree/BTIterator.java similarity index 88% rename from src/main/java/org/tinspin/index/balltree/QIterator0.java rename to src/main/java/org/tinspin/index/balltree/BTIterator.java index 1107601..4cfc72e 100644 --- a/src/main/java/org/tinspin/index/balltree/QIterator0.java +++ b/src/main/java/org/tinspin/index/balltree/BTIterator.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2017 Tilmann Zaeschke + * Copyright 2016-2024 Tilmann Zaeschke * * This file is part of TinSpin. * @@ -28,7 +28,7 @@ * * @param Value type */ -public class QIterator0 implements PointIterator { +public class BTIterator implements PointIterator { private class IteratorStack { private final ArrayList> stack; @@ -73,18 +73,20 @@ public void clear() { private static class StackEntry { int pos; - BTNode[] subs; + BTNode left; + BTNode right; ArrayList> vals; int len; void set(BTNode node) { this.pos = 0; this.vals = node.getEntries(); - this.subs = node.getChildNodes(); + this.left = node.getLeftChild(); + this.right = node.getRightChild(); if (this.vals != null) { len = this.vals.size(); } else { - len = this.subs.length; + len = 2; } } @@ -94,7 +96,7 @@ public boolean isLeaf() { } - QIterator0(BallTree tree, double[] min, double[] max) { + BTIterator(BallTree tree, double[] min, double[] max) { this.stack = new IteratorStack(); this.tree = tree; reset(min, max); @@ -112,9 +114,8 @@ private void findNext() { return; } } else { - BTNode node = se.subs[pos]; - if (node != null && - BTUtil.overlap(min, max, node.getCenter(), node.getRadius())) { + BTNode node = pos == 0 ? se.left : se.right; + if (BTUtil.overlap(min, max, node.getCenter(), node.getRadius())) { se = stack.prepareAndPush(node); } } diff --git a/src/main/java/org/tinspin/index/balltree/QIteratorKnn.java b/src/main/java/org/tinspin/index/balltree/BTIteratorKnn.java similarity index 84% rename from src/main/java/org/tinspin/index/balltree/QIteratorKnn.java rename to src/main/java/org/tinspin/index/balltree/BTIteratorKnn.java index e4d87d5..3158d39 100644 --- a/src/main/java/org/tinspin/index/balltree/QIteratorKnn.java +++ b/src/main/java/org/tinspin/index/balltree/BTIteratorKnn.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2023 Tilmann Zaeschke. All rights reserved. + * Copyright 2016-2024 Tilmann Zaeschke. All rights reserved. * * This file is part of TinSpin. * @@ -25,7 +25,7 @@ import static org.tinspin.index.Index.*; -public class QIteratorKnn implements PointIteratorKnn { +public class BTIteratorKnn implements PointIteratorKnn { private final BTNode root; private final PointDistance distFn; @@ -38,7 +38,7 @@ public class QIteratorKnn implements PointIteratorKnn { private double[] center; private double currentDistance; - QIteratorKnn(BTNode root, int minResults, double[] center, PointDistance distFn, PointFilterKnn filterFn) { + BTIteratorKnn(BTNode root, int minResults, double[] center, PointDistance distFn, PointFilterKnn filterFn) { this.filterFn = filterFn; this.distFn = distFn; this.root = root; @@ -124,13 +124,15 @@ private void findNextElement() { } } } else { - for (BTNode subnode : node.getChildNodes()) { - if (subnode != null) { - double dist = distFn.dist(center, subnode.getCenter()) - subnode.getRadius(); - if (dist <= maxNodeDist) { - queueN.push(new NodeDistT(dist, subnode)); - } - } + BTNode leftChild = node.getLeftChild(); + double distL = distFn.dist(center, leftChild.getCenter()) - leftChild.getRadius(); + if (distL <= maxNodeDist) { + queueN.push(new NodeDistT(distL, leftChild)); + } + BTNode rightChild = node.getLeftChild(); + double distR = distFn.dist(center, rightChild.getCenter()) - rightChild.getRadius(); + if (distR <= maxNodeDist) { + queueN.push(new NodeDistT(distR, rightChild)); } } } diff --git a/src/main/java/org/tinspin/index/balltree/BTNode.java b/src/main/java/org/tinspin/index/balltree/BTNode.java index 11ab4f9..2de2dbb 100644 --- a/src/main/java/org/tinspin/index/balltree/BTNode.java +++ b/src/main/java/org/tinspin/index/balltree/BTNode.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2017 Tilmann Zaeschke + * Copyright 2016-2024 Tilmann Zaeschke * * This file is part of TinSpin. * @@ -41,7 +41,6 @@ public class BTNode { private double radius; // null indicates that we have sub-node i.o. values private ArrayList> values; - private BTNode[] subs; private BTNode left; private BTNode right; @@ -89,7 +88,6 @@ BTNode tryPut(PointEntry e, int maxNodeSize, boolean enforceLeaf) { //split ArrayList> vals = values; values = null; - subs = new BTNode[2]; PointEntry start = vals.get(0); int dims = start.point().length; double[][] ordered = BTUtil.orderCoordinates(vals); @@ -395,7 +393,7 @@ void checkNode(BallTree.BTStats s, BTNode parent, int depth) { throw new IllegalStateException(); } } - if (subs != null) { + if (left != null || right != null) { throw new IllegalStateException(); } } else { @@ -409,8 +407,12 @@ boolean isLeaf() { return values != null; } - BTNode[] getChildNodes() { - return subs; + BTNode getLeftChild() { + return left; + } + + BTNode getRightChild() { + return right; } // TODO add radii as argument diff --git a/src/main/java/org/tinspin/index/balltree/BTUtil.java b/src/main/java/org/tinspin/index/balltree/BTUtil.java index d4128cd..6ec969f 100644 --- a/src/main/java/org/tinspin/index/balltree/BTUtil.java +++ b/src/main/java/org/tinspin/index/balltree/BTUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2017 Tilmann Zaeschke + * Copyright 2016-2024 Tilmann Zaeschke * * This file is part of TinSpin. * @@ -23,8 +23,6 @@ import java.util.ArrayList; import java.util.Arrays; -import static org.tinspin.index.Index.BoxEntry; - class BTUtil { static final double EPS_MUL = 1.000000001; @@ -81,12 +79,19 @@ public static boolean isPointEqual(double[] p1, double[] p2) { // } public static boolean overlap(double[] min, double[] max, double[] center, double radius) { + double[] p = new double[min.length]; for (int d = 0; d < min.length; d++) { - if (max[d] < center[d] - radius || min[d] > center[d] + radius) { - return false; + if (center[d] <= min[d]) { + p[d] = min[d]; + } else if (center[d] >= max[d]) { + p[d] = max[d]; + } else { + p[d] = center[d]; } + } - return true; + // TODO sqr-dist? + return PointDistance.l2(p, center) <= radius; } // public static boolean isRectEnclosed(double[] minEnclosed, double[] maxEnclosed, double[] minOuter, double[] maxOuter) { diff --git a/src/main/java/org/tinspin/index/balltree/BallTree.java b/src/main/java/org/tinspin/index/balltree/BallTree.java index 0028825..b36791c 100644 --- a/src/main/java/org/tinspin/index/balltree/BallTree.java +++ b/src/main/java/org/tinspin/index/balltree/BallTree.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2017 Tilmann Zaeschke + * Copyright 2016-2024 Tilmann Zaeschke * * This file is part of TinSpin. * @@ -306,7 +306,7 @@ public PointIterator queryExactPoint(double[] point) { @Override public PointIterator query(double[] min, double[] max) { //This does not use min/max but is really very basic. - return new QIterator0<>(this, min, max); + return new BTIterator<>(this, min, max); //return new QIterator<>(this, min, max); } @@ -325,7 +325,7 @@ public PointEntryKnn query1nn(double[] center) { */ @Override public PointIteratorKnn queryKnn(double[] center, int k, PointDistance dist) { - return new QIteratorKnn<>(root, k, center, dist, (e, d) -> true); + return new BTIteratorKnn<>(root, k, center, dist, (e, d) -> true); } /** @@ -349,13 +349,11 @@ private void toStringTree(StringBuilderLn sb, BTNode node, int depth, int pos sb.append(" " + Arrays.toString(node.getCenter())); sb.appendLn("/" + node.getRadius()); prefix += " "; - if (node.getChildNodes() != null) { - for (int i = 0; i < node.getChildNodes().length; i++) { - BTNode sub = node.getChildNodes()[i]; - if (sub != null) { - toStringTree(sb, sub, depth+1, i); - } - } + if (node.getLeftChild() != null) { + toStringTree(sb, node.getLeftChild(), depth+1, 0); + } + if (node.getRightChild() != null) { + toStringTree(sb, node.getRightChild(), depth+1, 1); } if (node.getEntries() != null) { for (int i = 0; i < node.getEntries().size(); i++) { diff --git a/src/main/java/org/tinspin/index/balltree/QIterator1.java b/src/main/java/org/tinspin/index/balltree/QIterator1.java deleted file mode 100644 index a86002f..0000000 --- a/src/main/java/org/tinspin/index/balltree/QIterator1.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2016-2017 Tilmann Zaeschke - * - * This file is part of TinSpin. - * - * 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. - */ -package org.tinspin.index.balltree; - -import java.util.ArrayList; -import java.util.NoSuchElementException; - -import static org.tinspin.index.Index.PointEntry; -import static org.tinspin.index.Index.PointIterator; - -/** - * Resettable query iterator. - * - * @param Value type - */ -public class QIterator1 implements PointIterator { - - private class IteratorStack { - private final ArrayList> stack; - private int size = 0; - - IteratorStack() { - stack = new ArrayList<>(); - } - - boolean isEmpty() { - return size == 0; - } - - StackEntry prepareAndPush(BTNode node, double[] min, double[] max) { - if (size == stack.size()) { - stack.add(new StackEntry<>()); - } - StackEntry ni = stack.get(size++); - - ni.set(node, min, max); - return ni; - } - - StackEntry peek() { - return stack.get(size-1); - } - - void pop() { - --size; - } - - public void clear() { - size = 0; - } - } - - private final BallTree tree; - private final IteratorStack stack; - private PointEntry next = null; - private double[] min; - private double[] max; - - private static class StackEntry { - long pos; - long m0; - long m1; - BTNode[] subs; - ArrayList> vals; - int len; - - void set(BTNode node, double[] min, double[] max) { - this.vals = node.getEntries(); - this.subs = node.getChildNodes(); - - if (this.vals != null) { - len = this.vals.size(); - this.pos = 0; - } else { - len = this.subs.length; - double[] center = node.getCenter(); - m0 = 0; - m1 = 0; - for (int d = 0; d < center.length; d++) { - m0 <<= 1; - m1 <<= 1; - if (max[d] >= center[d]) { - m1 |= 1; - if (min[d] >= center[d]) { - m0 |= 1; - } - } - } - pos = m0; - } - } - - public boolean isLeaf() { - return vals != null; - } - - boolean checkHcPos(long pos) { - return ((pos | m0) & m1) == pos; - } - } - - - QIterator1(BallTree tree, double[] min, double[] max) { - this.stack = new IteratorStack(); - this.tree = tree; - reset(min, max); - } - - private void findNext() { - while(!stack.isEmpty()) { - StackEntry se = stack.peek(); - while (se.pos < se.len) { - int pos = (int) se.pos++; - if (se.isLeaf()) { - PointEntry e = se.vals.get(pos); - if (BTUtil.isPointEnclosed(e.point(), min, max)) { - next = e; - return; - } - } else { - if (se.checkHcPos(pos) && se.subs[pos] != null) { - se = stack.prepareAndPush(se.subs[pos], min, max); - } - } - } - stack.pop(); - } - next = null; - } - - - @Override - public boolean hasNext() { - return next != null; - } - - @Override - public PointEntry next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - PointEntry ret = next; - findNext(); - return ret; - } - - /** - * Reset the iterator. This iterator can be reused in order to reduce load on the - * garbage collector. - * - * @param min lower left corner of query - * @param max upper right corner of query - * @return this. - */ - @Override - public PointIterator reset(double[] min, double[] max) { - stack.clear(); - this.min = min; - this.max = max; - next = null; - if (tree.getRoot() != null) { - stack.prepareAndPush(tree.getRoot(), min, max); - findNext(); - } - return this; - } -} \ No newline at end of file diff --git a/src/main/java/org/tinspin/index/balltree/QIterator2.java b/src/main/java/org/tinspin/index/balltree/QIterator2.java deleted file mode 100644 index a473636..0000000 --- a/src/main/java/org/tinspin/index/balltree/QIterator2.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright 2016-2017 Tilmann Zaeschke - * - * This file is part of TinSpin. - * - * 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. - */ -package org.tinspin.index.balltree; - -import java.util.ArrayList; -import java.util.NoSuchElementException; - -import static org.tinspin.index.Index.PointEntry; -import static org.tinspin.index.Index.PointIterator; - -/** - * Resettable query iterator. - * - * @param Value type - */ -public class QIterator2 implements PointIterator { - - private class IteratorStack { - private final ArrayList> stack; - private int size = 0; - - IteratorStack() { - stack = new ArrayList<>(); - } - - boolean isEmpty() { - return size == 0; - } - - StackEntry prepareAndPush(BTNode node, double[] min, double[] max) { - if (size == stack.size()) { - stack.add(new StackEntry<>()); - } - StackEntry ni = stack.get(size++); - - ni.set(node, min, max); - return ni; - } - - StackEntry peek() { - return stack.get(size-1); - } - - void pop() { - --size; - } - - public void clear() { - size = 0; - } - } - - private final BallTree tree; - private final IteratorStack stack; - private PointEntry next = null; - private double[] min; - private double[] max; - - private static class StackEntry { - long pos; - long m0; - long m1; - BTNode[] subs; - ArrayList> vals; - int len; - - void set(BTNode node, double[] min, double[] max) { - this.vals = node.getEntries(); - this.subs = node.getChildNodes(); - - if (this.vals != null) { - len = this.vals.size(); - pos = 0; - } else { - len = this.subs.length; - m0 = 0; - m1 = 0; - double[] center = node.getCenter(); - for (int d = 0; d < center.length; d++) { - m0 <<= 1; - m1 <<= 1; - if (max[d] >= center[d]) { - m1 |= 1; - if (min[d] >= center[d]) { - m0 |= 1; - } - } - } - pos = m0; - } - } - - public boolean isLeaf() { - return vals != null; - } - - //boolean checkHcPos(long pos) { - // return ((pos | m0) & m1) == pos; - //} - - void inc() { - //first, fill all 'invalid' bits with '1' (bits that can have only one value). - long r = pos | (~m1); - //increment. The '1's in the invalid bits will cause bitwise overflow to the next valid bit. - r++; - //remove invalid bits. - pos = (r & m1) | m0; - - //return -1 if we exceed 'max' and cause an overflow or return the original value. The - //latter can happen if there is only one possible value (all filter bits are set). - //The <= is also owed to the bug tested in testBugDecrease() - //return (r <= v) ? -1 : r; - } - } - - - QIterator2(BallTree tree, double[] min, double[] max) { - this.stack = new IteratorStack(); - this.tree = tree; - reset(min, max); - } - - private void findNext() { - while(!stack.isEmpty()) { - StackEntry se = stack.peek(); - while (se.pos < se.len) { - if (se.isLeaf()) { - PointEntry e = se.vals.get((int) se.pos++); - if (BTUtil.isPointEnclosed(e.point(), min, max)) { - next = e; - return; - } - } else { - int pos = (int) se.pos; - se.inc(); - //abort in next round if no increment is detected - if (se.pos <= pos) { - se.pos = Long.MAX_VALUE; - } - BTNode node = se.subs[pos]; - if (node != null) { - se = stack.prepareAndPush(node, min, max); - } - } - } - stack.pop(); - } - next = null; - } - - - - @Override - public boolean hasNext() { - return next != null; - } - - @Override - public PointEntry next() { - if (!hasNext()) { - throw new NoSuchElementException(); - } - PointEntry ret = next; - findNext(); - return ret; - } - - /** - * Reset the iterator. This iterator can be reused in order to reduce load on the - * garbage collector. - * - * @param min lower left corner of query - * @param max upper right corner of query - * @return this. - */ - @Override - public PointIterator reset(double[] min, double[] max) { - stack.clear(); - this.min = min; - this.max = max; - next = null; - if (tree.getRoot() != null) { - stack.prepareAndPush(tree.getRoot(), min, max); - findNext(); - } - return this; - } -} \ No newline at end of file diff --git a/src/test/java/org/tinspin/index/test/PointMapTest.java b/src/test/java/org/tinspin/index/test/PointMapTest.java index 28b43ac..10cfb07 100644 --- a/src/test/java/org/tinspin/index/test/PointMapTest.java +++ b/src/test/java/org/tinspin/index/test/PointMapTest.java @@ -342,6 +342,8 @@ private PointMap createTree(int size, int dims) { return PointMap.Factory.createRStarTree(dims); case COVER: return PointMap.Factory.createCoverTree(dims); + case BALL: + return PointMap.Factory.createBallTree(dims); default: throw new UnsupportedOperationException(candidate.name()); }