From 603fd09e66762a61c4ff8f7c0c47fc0486aea64b Mon Sep 17 00:00:00 2001 From: Tilmann Date: Wed, 31 Jul 2024 17:52:57 +0200 Subject: [PATCH] Fix root-node precision problems --- src/main/java/org/tinspin/index/PointMap.java | 33 ++++++++++--------- .../java/org/tinspin/index/PointMultimap.java | 33 ++++++++++--------- .../tinspin/index/qthypercube/QuadTreeKD.java | 20 ++++++----- .../index/qthypercube2/QuadTreeKD2.java | 20 ++++++----- .../tinspin/index/qtplain/QuadTreeKD0.java | 18 +++++----- .../org/tinspin/index/util/MathTools.java | 14 ++++++++ 6 files changed, 82 insertions(+), 56 deletions(-) diff --git a/src/main/java/org/tinspin/index/PointMap.java b/src/main/java/org/tinspin/index/PointMap.java index 327ac62..efc9cd7 100644 --- a/src/main/java/org/tinspin/index/PointMap.java +++ b/src/main/java/org/tinspin/index/PointMap.java @@ -196,7 +196,7 @@ static PointMap createQuadtree(int dims) { * @param radius Estimated maximum orthogonal distance from center for all coordinates. * @param Value type * @return New Quadtree - * @deprecated Please use {@link #createAlignedQuadtree(int, int, double[], double)} + * @deprecated Please use {@link #createQuadtree(double[], double, boolean, int)} */ @Deprecated static PointMap createQuadtree(int dims, int maxNodeCapacity, double[] center, double radius) { @@ -210,15 +210,16 @@ static PointMap createQuadtree(int dims, int maxNodeCapacity, double[] ce *

* Center and radius will be aligned with powers of two to avoid precision problems. * - * @param dims Number of dimensions. - * @param maxNodeCapacity Maximum entries in a node before the node is split. The default is 10. * @param center Estimated center of all coordinates. * @param radius Estimated maximum orthogonal distance from center for all coordinates. + * @param align Whether center and radius should be aligned to powers of two. Aligning considerably + * reduces risk of precision problems. Recommended: "true". + * @param maxNodeCapacity Maximum entries in a node before the node is split. The default is 10. * @param Value type * @return New Quadtree */ - static PointMap createAlignedQuadtree(int dims, int maxNodeCapacity, double[] center, double radius) { - return QuadTreeKD0.createAligned(dims, maxNodeCapacity, center, radius); + static PointMap createQuadtree(double[] center, double radius, boolean align, int maxNodeCapacity) { + return QuadTreeKD0.create(center, radius, align, maxNodeCapacity); } /** @@ -244,7 +245,7 @@ static PointMap createQuadtreeHC(int dims) { * @param radius Estimated maximum orthogonal distance from center for all coordinates. * @param Value type * @return New QuadtreeHC - * @deprecated Please use {@link #createAlignedQuadtreeHC(int, int, double[], double)} + * @deprecated Please use {@link #createQuadtreeHC(double[], double, boolean, int)} */ @Deprecated static PointMap createQuadtreeHC(int dims, int maxNodeCapacity, double[] center, double radius) { @@ -258,15 +259,16 @@ static PointMap createQuadtreeHC(int dims, int maxNodeCapacity, double[] *

* Center and radius will be aligned with powers of two to avoid precision problems. * - * @param dims Number of dimensions. - * @param maxNodeCapacity Maximum entries in a node before the node is split. The default is 10. * @param center Estimated center of all coordinates. * @param radius Estimated maximum orthogonal distance from center for all coordinates. + * @param align Whether center and radius should be aligned to powers of two. Aligning considerably + * reduces risk of precision problems. Recommended: "true". + * @param maxNodeCapacity Maximum entries in a node before the node is split. The default is 10. * @param Value type * @return New QuadtreeHC */ - static PointMap createAlignedQuadtreeHC(int dims, int maxNodeCapacity, double[] center, double radius) { - return QuadTreeKD.createAligned(dims, maxNodeCapacity, center, radius); + static PointMap createQuadtreeHC(double[] center, double radius, boolean align, int maxNodeCapacity) { + return QuadTreeKD.create(center, radius, align, maxNodeCapacity); } /** @@ -291,7 +293,7 @@ static PointMap createQuadtreeHC2(int dims) { * @param radius Estimated maximum orthogonal distance from center for all coordinates. * @param Value type * @return New QuadtreeHC2 - * @deprecated PLease use {@link #createAlignedQuadtreeHC2(int, int, double[], double)} + * @deprecated PLease use {@link #createQuadtreeHC2(double[], double, boolean, int)} */ @Deprecated static PointMap createQuadtreeHC2(int dims, int maxNodeCapacity, double[] center, double radius) { @@ -305,15 +307,16 @@ static PointMap createQuadtreeHC2(int dims, int maxNodeCapacity, double[] *

* Center and radius will be aligned with powers of two to avoid precision problems. * - * @param dims Number of dimensions. - * @param maxNodeCapacity Maximum entries in a node before the node is split. The default is 10. * @param center Estimated center of all coordinates. * @param radius Estimated maximum orthogonal distance from center for all coordinates. + * @param align Whether center and radius should be aligned to powers of two. Aligning considerably + * reduces risk of precision problems. Recommended: "true". + * @param maxNodeCapacity Maximum entries in a node before the node is split. The default is 10. * @param Value type * @return New QuadtreeHC2 */ - static PointMap createAlignedQuadtreeHC2(int dims, int maxNodeCapacity, double[] center, double radius) { - return QuadTreeKD2.createAligned(dims, maxNodeCapacity, center, radius); + static PointMap createQuadtreeHC2(double[] center, double radius, boolean align, int maxNodeCapacity) { + return QuadTreeKD2.create(center, radius, align, maxNodeCapacity); } /** diff --git a/src/main/java/org/tinspin/index/PointMultimap.java b/src/main/java/org/tinspin/index/PointMultimap.java index 5306fc2..9c9ca53 100644 --- a/src/main/java/org/tinspin/index/PointMultimap.java +++ b/src/main/java/org/tinspin/index/PointMultimap.java @@ -210,7 +210,7 @@ static PointMultimap createQuadtree(int dims) { * @param radius Estimated maximum orthogonal distance from center for all coordinates. * @param Value type * @return New Quadtree - * @deprecated Please use {@link #createAlignedQuadtree(int, int, double[], double)} + * @deprecated Please use {@link #createQuadtree(double[], double, boolean, int)} */ @Deprecated static PointMultimap createQuadtree(int dims, int maxNodeCapacity, double[] center, double radius) { @@ -224,15 +224,16 @@ static PointMultimap createQuadtree(int dims, int maxNodeCapacity, double *

* Center and radius will be aligned with powers of two to avoid precision problems. * - * @param dims Number of dimensions. - * @param maxNodeCapacity Maximum entries in a node before the node is split. The default is 10. * @param center Estimated center of all coordinates. * @param radius Estimated maximum orthogonal distance from center for all coordinates. + * @param align Whether center and radius should be aligned to powers of two. Aligning considerably + * reduces risk of precision problems. Recommended: "true". + * @param maxNodeCapacity Maximum entries in a node before the node is split. The default is 10. * @param Value type * @return New Quadtree */ - static PointMultimap createAlignedQuadtree(int dims, int maxNodeCapacity, double[] center, double radius) { - return QuadTreeKD0.createAligned(dims, maxNodeCapacity, center, radius); + static PointMultimap createQuadtree(double[] center, double radius, boolean align, int maxNodeCapacity) { + return QuadTreeKD0.create(center, radius, align, maxNodeCapacity); } /** @@ -258,7 +259,7 @@ static PointMultimap createQuadtreeHC(int dims) { * @param radius Estimated maximum orthogonal distance from center for all coordinates. * @param Value type * @return New QuadtreeHC - * @deprecated Please use {@link #createAlignedQuadtreeHC(int, int, double[], double)} + * @deprecated Please use {@link #createQuadtreeHC(double[], double, boolean, int)} */ @Deprecated static PointMultimap createQuadtreeHC(int dims, int maxNodeCapacity, double[] center, double radius) { @@ -272,15 +273,16 @@ static PointMultimap createQuadtreeHC(int dims, int maxNodeCapacity, doub *

* Center and radius will be aligned with powers of two to avoid precision problems. * - * @param dims Number of dimensions. - * @param maxNodeCapacity Maximum entries in a node before the node is split. The default is 10. * @param center Estimated center of all coordinates. * @param radius Estimated maximum orthogonal distance from center for all coordinates. + * @param align Whether center and radius should be aligned to powers of two. Aligning considerably + * reduces risk of precision problems. Recommended: "true". + * @param maxNodeCapacity Maximum entries in a node before the node is split. The default is 10. * @param Value type * @return New QuadtreeHC */ - static PointMultimap createAlignedQuadtreeHC(int dims, int maxNodeCapacity, double[] center, double radius) { - return QuadTreeKD.createAligned(dims, maxNodeCapacity, center, radius); + static PointMultimap createQuadtreeHC(double[] center, double radius, boolean align, int maxNodeCapacity) { + return QuadTreeKD.create(center, radius, align, maxNodeCapacity); } /** @@ -305,7 +307,7 @@ static PointMultimap createQuadtreeHC2(int dims) { * @param radius Estimated maximum orthogonal distance from center for all coordinates. * @param Value type * @return New QuadtreeHC2 - * @deprecated PLease use {@link #createAlignedQuadtreeHC2(int, int, double[], double)} + * @deprecated PLease use {@link #createQuadtreeHC2(double[], double, boolean, int)} */ @Deprecated static PointMultimap createQuadtreeHC2(int dims, int maxNodeCapacity, double[] center, double radius) { @@ -319,15 +321,16 @@ static PointMultimap createQuadtreeHC2(int dims, int maxNodeCapacity, dou *

* Center and radius will be aligned with powers of two to avoid precision problems. * - * @param dims Number of dimensions. - * @param maxNodeCapacity Maximum entries in a node before the node is split. The default is 10. * @param center Estimated center of all coordinates. * @param radius Estimated maximum orthogonal distance from center for all coordinates. + * @param align Whether center and radius should be aligned to powers of two. Aligning considerably + * reduces risk of precision problems. Recommended: "true". + * @param maxNodeCapacity Maximum entries in a node before the node is split. The default is 10. * @param Value type * @return New QuadtreeHC2 */ - static PointMultimap createAlignedQuadtreeHC2(int dims, int maxNodeCapacity, double[] center, double radius) { - return QuadTreeKD2.createAligned(dims, maxNodeCapacity, center, radius); + static PointMultimap createQuadtreeHC2(double[] center, double radius, boolean align, int maxNodeCapacity) { + return QuadTreeKD2.create(center, radius, align, maxNodeCapacity); } /** diff --git a/src/main/java/org/tinspin/index/qthypercube/QuadTreeKD.java b/src/main/java/org/tinspin/index/qthypercube/QuadTreeKD.java index 0644862..b3fe260 100644 --- a/src/main/java/org/tinspin/index/qthypercube/QuadTreeKD.java +++ b/src/main/java/org/tinspin/index/qthypercube/QuadTreeKD.java @@ -76,22 +76,24 @@ public static QuadTreeKD create(int dims, int maxNodeSize) { /** * Note: This will align center and radius to a power of two before creating a tree. - * @param dims dimensions, usually 2 or 3 - * @param maxNodeSize maximum entries per node, default is 10 * @param center center of initial root node * @param radius radius of initial root node + * @param maxNodeSize maximum entries per node, default is 10 + * @param align Whether center and radius should be aligned to powers of two. Aligning considerably + * reduces risk of precision problems. Recommended: "true". * @return New quadtree * @param Value type */ - public static QuadTreeKD createAligned(int dims, int maxNodeSize, - double[] center, double radius) { - QuadTreeKD t = new QuadTreeKD<>(dims, maxNodeSize); + public static QuadTreeKD create(double[] center, double radius, boolean align, int maxNodeSize) { + QuadTreeKD t = new QuadTreeKD<>(center.length, maxNodeSize); if (radius <= 0) { throw new IllegalArgumentException("Radius must be > 0 but was " + radius); } - double[] alignedCenter = MathTools.floorPowerOfTwoCopy(center); - double alignedRadius = MathTools.ceilPowerOfTwo(radius); - t.root = new QNode<>(Arrays.copyOf(alignedCenter, alignedCenter.length), alignedRadius); + if (align) { + center = MathTools.floorPowerOfTwoCopy(center); + radius = MathTools.ceilPowerOfTwo(radius); + } + t.root = new QNode<>(Arrays.copyOf(center, center.length), radius); return t; } @@ -103,7 +105,7 @@ public static QuadTreeKD createAligned(int dims, int maxNodeSize, * @param radius radius of initial root node * @return New quadtree * @param Value type - * @deprecated Please use {@link #createAligned(int, int, double[], double)} + * @deprecated Please use {@link #create(double[], double, boolean, int)} */ @Deprecated public static QuadTreeKD create(int dims, int maxNodeSize, diff --git a/src/main/java/org/tinspin/index/qthypercube2/QuadTreeKD2.java b/src/main/java/org/tinspin/index/qthypercube2/QuadTreeKD2.java index 378a49a..9c8fd01 100644 --- a/src/main/java/org/tinspin/index/qthypercube2/QuadTreeKD2.java +++ b/src/main/java/org/tinspin/index/qthypercube2/QuadTreeKD2.java @@ -101,22 +101,24 @@ public static QuadTreeKD2 create(int dims, int maxNodeSize) { /** * Note: This will align center and radius to a power of two before creating a tree. - * @param dims dimensions, usually 2 or 3 - * @param maxNodeSize maximum entries per node, default is 10 * @param center center of initial root node * @param radius radius of initial root node + * @param align Whether center and radius should be aligned to powers of two. Aligning considerably + * reduces risk of precision problems. Recommended: "true". + * @param maxNodeSize maximum entries per node, default is 10 * @return New quadtree * @param Value type */ - public static QuadTreeKD2 createAligned(int dims, int maxNodeSize, - double[] center, double radius) { - QuadTreeKD2 t = new QuadTreeKD2<>(dims, maxNodeSize); + public static QuadTreeKD2 create(double[] center, double radius, boolean align, int maxNodeSize) { + QuadTreeKD2 t = new QuadTreeKD2<>(center.length, maxNodeSize); if (radius <= 0) { throw new IllegalArgumentException("Radius must be > 0 but was " + radius); } - double[] alignedCenter = MathTools.floorPowerOfTwoCopy(center); - double alignedRadius = MathTools.ceilPowerOfTwo(radius); - t.root = new QNode<>(Arrays.copyOf(alignedCenter, alignedCenter.length), alignedRadius); + if (align) { + center = MathTools.floorPowerOfTwoCopy(center); + radius = MathTools.ceilPowerOfTwo(radius); + } + t.root = new QNode<>(Arrays.copyOf(center, center.length), radius); return t; } @@ -128,7 +130,7 @@ public static QuadTreeKD2 createAligned(int dims, int maxNodeSize, * @param radius radius of initial root node * @return New quadtree * @param Value type - * @deprecated Please use {@link #createAligned(int, int, double[], double)} + * @deprecated Please use {@link #create(double[], double, boolean, int)} */ @Deprecated public static QuadTreeKD2 create(int dims, int maxNodeSize, diff --git a/src/main/java/org/tinspin/index/qtplain/QuadTreeKD0.java b/src/main/java/org/tinspin/index/qtplain/QuadTreeKD0.java index 4631459..1f8a708 100644 --- a/src/main/java/org/tinspin/index/qtplain/QuadTreeKD0.java +++ b/src/main/java/org/tinspin/index/qtplain/QuadTreeKD0.java @@ -67,21 +67,23 @@ public static QuadTreeKD0 create(int dims, int maxNodeSize) { /** * Note: This will align center and radius to a power of two before creating a tree. - * @param dims dimensions, usually 2 or 3 * @param maxNodeSize maximum entries per node, default is 10 * @param center center of initial root node * @param radius radius of initial root node + * @param align Whether center and radius should be aligned to powers of two. Aligning considerably + * reduces risk of precision problems. Recommended: "true". * @return New quadtree * @param Value type - */ public static QuadTreeKD0 createAligned(int dims, int maxNodeSize, - double[] center, double radius) { - QuadTreeKD0 t = new QuadTreeKD0<>(dims, maxNodeSize); + */ public static QuadTreeKD0 create(double[] center, double radius, boolean align, int maxNodeSize) { + QuadTreeKD0 t = new QuadTreeKD0<>(center.length, maxNodeSize); if (radius <= 0) { throw new IllegalArgumentException("Radius must be > 0 but was " + radius); } - double[] alignedCenter = MathTools.floorPowerOfTwoCopy(center); - double alignedRadius = MathTools.ceilPowerOfTwo(radius); - t.root = new QNode<>(Arrays.copyOf(alignedCenter, alignedCenter.length), alignedRadius); + if (align) { + center = MathTools.floorPowerOfTwoCopy(center); + radius = MathTools.ceilPowerOfTwo(radius); + } + t.root = new QNode<>(Arrays.copyOf(center, center.length), radius); return t; } @@ -93,7 +95,7 @@ public static QuadTreeKD0 create(int dims, int maxNodeSize) { * @param radius radius of initial root node * @return New quadtree * @param Value type - * @deprecated Please use {@link #createAligned(int, int, double[], double)} + * @deprecated Please use {@link #create(double[], double, boolean, int)} */ @Deprecated public static QuadTreeKD0 create(int dims, int maxNodeSize, diff --git a/src/main/java/org/tinspin/index/util/MathTools.java b/src/main/java/org/tinspin/index/util/MathTools.java index 2507478..1f9b2d9 100644 --- a/src/main/java/org/tinspin/index/util/MathTools.java +++ b/src/main/java/org/tinspin/index/util/MathTools.java @@ -32,6 +32,20 @@ public static double ceilPowerOfTwo(double d) { return ceil == d ? ceil : ceil * 2; } + /** + * Calculates the {@link #ceilPowerOfTwo(double)} of an array. + * @param d input vector + * @return copied vector with next power of two above or equal to 'input' + * @see #floorPowerOfTwo(double) + */ + public static double[] ceilPowerOfTwoCopy(double[] d) { + double[] d2 = new double[d.length]; + for (int i = 0; i < d.length; i++) { + d2[i] = ceilPowerOfTwo(d[i]); + } + return d2; + } + /** * Similar to Math.floor() with the floor being the next lower power of 2. * The resulting number can repeatedly and (almost) always be divided by two without loss of precision.