Skip to content

Commit

Permalink
Fix root-node precision problems
Browse files Browse the repository at this point in the history
  • Loading branch information
tzaeschke committed Jul 31, 2024
1 parent 215624d commit 603fd09
Show file tree
Hide file tree
Showing 6 changed files with 82 additions and 56 deletions.
33 changes: 18 additions & 15 deletions src/main/java/org/tinspin/index/PointMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ static <T> PointMap<T> createQuadtree(int dims) {
* @param radius Estimated maximum orthogonal distance from center for all coordinates.
* @param <T> 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 <T> PointMap<T> createQuadtree(int dims, int maxNodeCapacity, double[] center, double radius) {
Expand All @@ -210,15 +210,16 @@ static <T> PointMap<T> createQuadtree(int dims, int maxNodeCapacity, double[] ce
* <p>
* 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 <T> Value type
* @return New Quadtree
*/
static <T> PointMap<T> createAlignedQuadtree(int dims, int maxNodeCapacity, double[] center, double radius) {
return QuadTreeKD0.createAligned(dims, maxNodeCapacity, center, radius);
static <T> PointMap<T> createQuadtree(double[] center, double radius, boolean align, int maxNodeCapacity) {
return QuadTreeKD0.create(center, radius, align, maxNodeCapacity);
}

/**
Expand All @@ -244,7 +245,7 @@ static <T> PointMap<T> createQuadtreeHC(int dims) {
* @param radius Estimated maximum orthogonal distance from center for all coordinates.
* @param <T> 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 <T> PointMap<T> createQuadtreeHC(int dims, int maxNodeCapacity, double[] center, double radius) {
Expand All @@ -258,15 +259,16 @@ static <T> PointMap<T> createQuadtreeHC(int dims, int maxNodeCapacity, double[]
* <p>
* 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 <T> Value type
* @return New QuadtreeHC
*/
static <T> PointMap<T> createAlignedQuadtreeHC(int dims, int maxNodeCapacity, double[] center, double radius) {
return QuadTreeKD.createAligned(dims, maxNodeCapacity, center, radius);
static <T> PointMap<T> createQuadtreeHC(double[] center, double radius, boolean align, int maxNodeCapacity) {
return QuadTreeKD.create(center, radius, align, maxNodeCapacity);
}

/**
Expand All @@ -291,7 +293,7 @@ static <T> PointMap<T> createQuadtreeHC2(int dims) {
* @param radius Estimated maximum orthogonal distance from center for all coordinates.
* @param <T> 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 <T> PointMap<T> createQuadtreeHC2(int dims, int maxNodeCapacity, double[] center, double radius) {
Expand All @@ -305,15 +307,16 @@ static <T> PointMap<T> createQuadtreeHC2(int dims, int maxNodeCapacity, double[]
* <p>
* 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 <T> Value type
* @return New QuadtreeHC2
*/
static <T> PointMap<T> createAlignedQuadtreeHC2(int dims, int maxNodeCapacity, double[] center, double radius) {
return QuadTreeKD2.createAligned(dims, maxNodeCapacity, center, radius);
static <T> PointMap<T> createQuadtreeHC2(double[] center, double radius, boolean align, int maxNodeCapacity) {
return QuadTreeKD2.create(center, radius, align, maxNodeCapacity);
}

/**
Expand Down
33 changes: 18 additions & 15 deletions src/main/java/org/tinspin/index/PointMultimap.java
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ static <T> PointMultimap<T> createQuadtree(int dims) {
* @param radius Estimated maximum orthogonal distance from center for all coordinates.
* @param <T> 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 <T> PointMultimap<T> createQuadtree(int dims, int maxNodeCapacity, double[] center, double radius) {
Expand All @@ -224,15 +224,16 @@ static <T> PointMultimap<T> createQuadtree(int dims, int maxNodeCapacity, double
* <p>
* 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 <T> Value type
* @return New Quadtree
*/
static <T> PointMultimap<T> createAlignedQuadtree(int dims, int maxNodeCapacity, double[] center, double radius) {
return QuadTreeKD0.createAligned(dims, maxNodeCapacity, center, radius);
static <T> PointMultimap<T> createQuadtree(double[] center, double radius, boolean align, int maxNodeCapacity) {
return QuadTreeKD0.create(center, radius, align, maxNodeCapacity);
}

/**
Expand All @@ -258,7 +259,7 @@ static <T> PointMultimap<T> createQuadtreeHC(int dims) {
* @param radius Estimated maximum orthogonal distance from center for all coordinates.
* @param <T> 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 <T> PointMultimap<T> createQuadtreeHC(int dims, int maxNodeCapacity, double[] center, double radius) {
Expand All @@ -272,15 +273,16 @@ static <T> PointMultimap<T> createQuadtreeHC(int dims, int maxNodeCapacity, doub
* <p>
* 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 <T> Value type
* @return New QuadtreeHC
*/
static <T> PointMultimap<T> createAlignedQuadtreeHC(int dims, int maxNodeCapacity, double[] center, double radius) {
return QuadTreeKD.createAligned(dims, maxNodeCapacity, center, radius);
static <T> PointMultimap<T> createQuadtreeHC(double[] center, double radius, boolean align, int maxNodeCapacity) {
return QuadTreeKD.create(center, radius, align, maxNodeCapacity);
}

/**
Expand All @@ -305,7 +307,7 @@ static <T> PointMultimap<T> createQuadtreeHC2(int dims) {
* @param radius Estimated maximum orthogonal distance from center for all coordinates.
* @param <T> 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 <T> PointMultimap<T> createQuadtreeHC2(int dims, int maxNodeCapacity, double[] center, double radius) {
Expand All @@ -319,15 +321,16 @@ static <T> PointMultimap<T> createQuadtreeHC2(int dims, int maxNodeCapacity, dou
* <p>
* 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 <T> Value type
* @return New QuadtreeHC2
*/
static <T> PointMultimap<T> createAlignedQuadtreeHC2(int dims, int maxNodeCapacity, double[] center, double radius) {
return QuadTreeKD2.createAligned(dims, maxNodeCapacity, center, radius);
static <T> PointMultimap<T> createQuadtreeHC2(double[] center, double radius, boolean align, int maxNodeCapacity) {
return QuadTreeKD2.create(center, radius, align, maxNodeCapacity);
}

/**
Expand Down
20 changes: 11 additions & 9 deletions src/main/java/org/tinspin/index/qthypercube/QuadTreeKD.java
Original file line number Diff line number Diff line change
Expand Up @@ -76,22 +76,24 @@ public static <T> QuadTreeKD<T> 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 <T> Value type
*/
public static <T> QuadTreeKD<T> createAligned(int dims, int maxNodeSize,
double[] center, double radius) {
QuadTreeKD<T> t = new QuadTreeKD<>(dims, maxNodeSize);
public static <T> QuadTreeKD<T> create(double[] center, double radius, boolean align, int maxNodeSize) {
QuadTreeKD<T> 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;
}

Expand All @@ -103,7 +105,7 @@ public static <T> QuadTreeKD<T> createAligned(int dims, int maxNodeSize,
* @param radius radius of initial root node
* @return New quadtree
* @param <T> Value type
* @deprecated Please use {@link #createAligned(int, int, double[], double)}
* @deprecated Please use {@link #create(double[], double, boolean, int)}
*/
@Deprecated
public static <T> QuadTreeKD<T> create(int dims, int maxNodeSize,
Expand Down
20 changes: 11 additions & 9 deletions src/main/java/org/tinspin/index/qthypercube2/QuadTreeKD2.java
Original file line number Diff line number Diff line change
Expand Up @@ -101,22 +101,24 @@ public static <T> QuadTreeKD2<T> 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 <T> Value type
*/
public static <T> QuadTreeKD2<T> createAligned(int dims, int maxNodeSize,
double[] center, double radius) {
QuadTreeKD2<T> t = new QuadTreeKD2<>(dims, maxNodeSize);
public static <T> QuadTreeKD2<T> create(double[] center, double radius, boolean align, int maxNodeSize) {
QuadTreeKD2<T> 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;
}

Expand All @@ -128,7 +130,7 @@ public static <T> QuadTreeKD2<T> createAligned(int dims, int maxNodeSize,
* @param radius radius of initial root node
* @return New quadtree
* @param <T> Value type
* @deprecated Please use {@link #createAligned(int, int, double[], double)}
* @deprecated Please use {@link #create(double[], double, boolean, int)}
*/
@Deprecated
public static <T> QuadTreeKD2<T> create(int dims, int maxNodeSize,
Expand Down
18 changes: 10 additions & 8 deletions src/main/java/org/tinspin/index/qtplain/QuadTreeKD0.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,21 +67,23 @@ public static <T> QuadTreeKD0<T> 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 <T> Value type
*/ public static <T> QuadTreeKD0<T> createAligned(int dims, int maxNodeSize,
double[] center, double radius) {
QuadTreeKD0<T> t = new QuadTreeKD0<>(dims, maxNodeSize);
*/ public static <T> QuadTreeKD0<T> create(double[] center, double radius, boolean align, int maxNodeSize) {
QuadTreeKD0<T> 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;
}

Expand All @@ -93,7 +95,7 @@ public static <T> QuadTreeKD0<T> create(int dims, int maxNodeSize) {
* @param radius radius of initial root node
* @return New quadtree
* @param <T> Value type
* @deprecated Please use {@link #createAligned(int, int, double[], double)}
* @deprecated Please use {@link #create(double[], double, boolean, int)}
*/
@Deprecated
public static <T> QuadTreeKD0<T> create(int dims, int maxNodeSize,
Expand Down
14 changes: 14 additions & 0 deletions src/main/java/org/tinspin/index/util/MathTools.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 603fd09

Please sign in to comment.