Skip to content

Commit

Permalink
Add power of 2 for all quadtrees (#44)
Browse files Browse the repository at this point in the history
* Power of 2
  • Loading branch information
tzaeschke authored Jul 31, 2024
1 parent fc48350 commit 7ef1133
Show file tree
Hide file tree
Showing 12 changed files with 543 additions and 83 deletions.
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Fixed tree consistency (single-entry leaf after remove)
- Fixed tree consistency (nValues) -> verify
- Fixed bug in qt2.contains()
- Fixed QT2 inconsistency after root resizing after insert(). [#42](https://github.com/tzaeschke/tinspin-indexes/issues/42)
- Fixed QT2 inconsistency after root resizing after insert().
Essentially, we force all radii and the center of the root to be powers of two.
This should immensely reduce precision problems.
This should immensely reduce precision problems.
[#42](https://github.com/tzaeschke/tinspin-indexes/issues/42)
- Removed unnecessary JUnit test console output. [#45](https://github.com/tzaeschke/tinspin-indexes/pull/45)

## [2.1.3] - 2023-11-19
Expand Down
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ Indexes can be created via factories in the interfaces, e.g. `PointMap.Factory.c
Note:
- **STR-Trees** are simply R-Trees that are preloaded using the STR algorithm. THis can be done with
the factory methods `....Factory.createAndLoadStrRTree(...)`.
- **Quadtree precision problems**. Many quadtree implementations are vulnerable to precision problem due to repeated
division by 2.0 of radius and subnode centers. Our quadtree implementations avoid this problem by aligning node center
coordinates and radius to a power of two. Power of two values are mostly immune to precision problems when dividing by 2.0.
- `PointArray` and `BoxArray` are simple array based implementations. They scale badly with size, their only use is for verifying correctness of other indexes.

## Changelog
Expand Down
46 changes: 45 additions & 1 deletion src/main/java/org/tinspin/index/BoxMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ static <T> BoxMap<T> createQuadtree(int dims) {
}

/**
* WARNING: Unaligned center and radius can cause precision problems, see README.
* Create a plain Quadtree.
* Min/max are used to find a good initial root. They do not need to be exact. If possible, min/max should
* span an area that is somewhat larger rather than smaller than the actual data.
Expand All @@ -157,11 +158,32 @@ static <T> BoxMap<T> createQuadtree(int dims) {
* @param max Estimated maximum of all coordinates.
* @param <T> Value type
* @return New Quadtree
* @deprecated Please use {@link #createQuadtree(double[], double[], boolean, int)}
*/
@Deprecated
static <T> BoxMap<T> createQuadtree(int dims, int maxNodeCapacity, double[] min, double[] max) {
return QuadTreeRKD0.create(dims, maxNodeCapacity, min, max);
}

/**
* Create a plain Quadtree.
* Min/max are used to find a good initial root. They do not need to be exact. If possible, min/max should
* span an area that is somewhat larger rather than smaller than the actual data.
* <p>
* Center and radius will be aligned with powers of two to avoid precision problems.
*
* @param min Estimated minimum of all coordinates.
* @param max Estimated maximum of 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> BoxMap<T> createQuadtree(double[] min, double[] max, boolean align, int maxNodeCapacity) {
return QuadTreeRKD0.create(min, max, align, maxNodeCapacity);
}

/**
* Create a Quadtree with hypercube navigation.
*
Expand All @@ -174,7 +196,8 @@ static <T> BoxMap<T> createQuadtreeHC(int dims) {
}

/**
* Create a plain Quadtree.
* WARNING: Unaligned center and radius can cause precision problems, see README.
* Create a Quadtree with hypercube navigation.
* Min/max are used to find a good initial root. They do not need to be exact. If possible, min/max should
* span an area that is somewhat larger rather than smaller than the actual data.
*
Expand All @@ -184,11 +207,32 @@ static <T> BoxMap<T> createQuadtreeHC(int dims) {
* @param max Estimated maximum of all coordinates.
* @param <T> Value type
* @return New QuadtreeHC
* @deprecated Please use {@link #createQuadtree(double[], double[], boolean, int)}
*/
@Deprecated
static <T> BoxMap<T> createQuadtreeHC(int dims, int maxNodeCapacity, double[] min, double[] max) {
return QuadTreeRKD.create(dims, maxNodeCapacity, min, max);
}

/**
* Create a Quadtree with hypercube navigation.
* Min/max are used to find a good initial root. They do not need to be exact. If possible, min/max should
* span an area that is somewhat larger rather than smaller than the actual data.
* <p>
* Center and radius will be aligned with powers of two to avoid precision problems.
*
* @param min Estimated minimum of all coordinates.
* @param max Estimated maximum of 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> BoxMap<T> createQuadtreeHC(double[] min, double[] max, boolean align, int maxNodeCapacity) {
return QuadTreeRKD.create(min, max, align, maxNodeCapacity);
}

/**
* Create an R*Tree.
*
Expand Down
48 changes: 46 additions & 2 deletions src/main/java/org/tinspin/index/BoxMultimap.java
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ default BoxEntryKnn<T> query1nn(double[] center) {

interface Factory {
/**
* Create an array backed BoxMap. This is only for testing and rather inefficient for large data sets.
* Create an array backed BoxMultiMap. This is only for testing and rather inefficient for large data sets.
*
* @param dims Number of dimensions.
* @param size Number of entries.
Expand All @@ -168,6 +168,7 @@ static <T> BoxMultimap<T> createQuadtree(int dims) {
}

/**
* WARNING: Unaligned center and radius can cause precision problems, see README.
* Create a plain Quadtree.
* Min/max are used to find a good initial root. They do not need to be exact. If possible, min/max should
* span an area that is somewhat larger rather than smaller than the actual data.
Expand All @@ -178,11 +179,32 @@ static <T> BoxMultimap<T> createQuadtree(int dims) {
* @param max Estimated maximum of all coordinates.
* @param <T> Value type
* @return New Quadtree
* @deprecated Please use {@link #createQuadtree(double[], double[], boolean, int)}
*/
@Deprecated
static <T> BoxMultimap<T> createQuadtree(int dims, int maxNodeCapacity, double[] min, double[] max) {
return QuadTreeRKD0.create(dims, maxNodeCapacity, min, max);
}

/**
* Create a plain Quadtree.
* Min/max are used to find a good initial root. They do not need to be exact. If possible, min/max should
* span an area that is somewhat larger rather than smaller than the actual data.
* <p>
* Center and radius will be aligned with powers of two to avoid precision problems.
*
* @param min Estimated minimum of all coordinates.
* @param max Estimated maximum of 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> BoxMultimap<T> createQuadtree(double[] min, double[] max, boolean align, int maxNodeCapacity) {
return QuadTreeRKD0.create(min, max, align, maxNodeCapacity);
}

/**
* Create a Quadtree with hypercube navigation.
*
Expand All @@ -195,7 +217,8 @@ static <T> BoxMultimap<T> createQuadtreeHC(int dims) {
}

/**
* Create a plain Quadtree.
* WARNING: Unaligned center and radius can cause precision problems, see README.
* Create a Quadtree with hypercube navigation.
* Min/max are used to find a good initial root. They do not need to be exact. If possible, min/max should
* span an area that is somewhat larger rather than smaller than the actual data.
*
Expand All @@ -205,11 +228,32 @@ static <T> BoxMultimap<T> createQuadtreeHC(int dims) {
* @param max Estimated maximum of all coordinates.
* @param <T> Value type
* @return New QuadtreeHC
* @deprecated Please use {@link #createQuadtree(double[], double[], boolean, int)}
*/
@Deprecated
static <T> BoxMultimap<T> createQuadtreeHC(int dims, int maxNodeCapacity, double[] min, double[] max) {
return QuadTreeRKD.create(dims, maxNodeCapacity, min, max);
}

/**
* Create a Quadtree with hypercube navigation.
* Min/max are used to find a good initial root. They do not need to be exact. If possible, min/max should
* span an area that is somewhat larger rather than smaller than the actual data.
* <p>
* Center and radius will be aligned with powers of two to avoid precision problems.
*
* @param min Estimated minimum of all coordinates.
* @param max Estimated maximum of 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> BoxMultimap<T> createQuadtreeHC(double[] min, double[] max, boolean align, int maxNodeCapacity) {
return QuadTreeRKD.create(min, max, align, maxNodeCapacity);
}

/**
* Create an R*Tree.
*
Expand Down
70 changes: 68 additions & 2 deletions src/main/java/org/tinspin/index/PointMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ static <T> PointMap<T> createQuadtree(int dims) {
}

/**
* WARNING: Unaligned center and radius can cause precision problems, see README.
* Create a plain Quadtree.
* Center/radius are used to find a good initial root. They do not need to be exact. If possible, they should
* span an area that is somewhat larger rather than smaller than the actual data.
Expand All @@ -195,11 +196,32 @@ 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 #createQuadtree(double[], double, boolean, int)}
*/
@Deprecated
static <T> PointMap<T> createQuadtree(int dims, int maxNodeCapacity, double[] center, double radius) {
return QuadTreeKD0.create(dims, maxNodeCapacity, center, radius);
}

/**
* Create a plain Quadtree.
* Center/radius are used to find a good initial root. They do not need to be exact. If possible, they should
* span an area that is somewhat larger rather than smaller than the actual data.
* <p>
* Center and radius will be aligned with powers of two to avoid precision problems.
*
* @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> createQuadtree(double[] center, double radius, boolean align, int maxNodeCapacity) {
return QuadTreeKD0.create(center, radius, align, maxNodeCapacity);
}

/**
* Create a Quadtree with hypercube navigation.
*
Expand All @@ -212,7 +234,8 @@ static <T> PointMap<T> createQuadtreeHC(int dims) {
}

/**
* Create a plain Quadtree.
* WARNING: Unaligned center and radius can cause precision problems, see README.
* Create a Quadtree with hypercube navigation.
* Center/radius are used to find a good initial root. They do not need to be exact. If possible, they should
* span an area that is somewhat larger rather than smaller than the actual data.
*
Expand All @@ -222,13 +245,34 @@ 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 #createQuadtreeHC(double[], double, boolean, int)}
*/
@Deprecated
static <T> PointMap<T> createQuadtreeHC(int dims, int maxNodeCapacity, double[] center, double radius) {
return QuadTreeKD.create(dims, maxNodeCapacity, center, radius);
}

/**
* Create a Quadtree with hypercube navigation.
* Center/radius are used to find a good initial root. They do not need to be exact. If possible, they should
* span an area that is somewhat larger rather than smaller than the actual data.
* <p>
* Center and radius will be aligned with powers of two to avoid precision problems.
*
* @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> createQuadtreeHC(double[] center, double radius, boolean align, int maxNodeCapacity) {
return QuadTreeKD.create(center, radius, align, maxNodeCapacity);
}

/**
* Create a Quadtree with extended hypercube navigation.
*
* @param dims Number of dimensions.
* @param <T> Value type
Expand All @@ -239,7 +283,8 @@ static <T> PointMap<T> createQuadtreeHC2(int dims) {
}

/**
* Create a plain Quadtree.
* WARNING: Unaligned center and radius can cause precision problems, see README.
* Create a Quadtree with extended hypercube navigation.
* Center/radius are used to find a good initial root. They do not need to be exact. If possible, they should
* span an area that is somewhat larger rather than smaller than the actual data.
*
Expand All @@ -249,11 +294,32 @@ 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 #createQuadtreeHC2(double[], double, boolean, int)}
*/
@Deprecated
static <T> PointMap<T> createQuadtreeHC2(int dims, int maxNodeCapacity, double[] center, double radius) {
return QuadTreeKD2.create(dims, maxNodeCapacity, center, radius);
}

/**
* Create a Quadtree with extended hypercube navigation.
* Center/radius are used to find a good initial root. They do not need to be exact. If possible, they should
* span an area that is somewhat larger rather than smaller than the actual data.
* <p>
* Center and radius will be aligned with powers of two to avoid precision problems.
*
* @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> createQuadtreeHC2(double[] center, double radius, boolean align, int maxNodeCapacity) {
return QuadTreeKD2.create(center, radius, align, maxNodeCapacity);
}

/**
* Create an R*Tree.
*
Expand Down
Loading

0 comments on commit 7ef1133

Please sign in to comment.