From 404aa7054d732c3c8cf735ace8cdb598ebcf4aee Mon Sep 17 00:00:00 2001 From: Tilmann Date: Sun, 10 Nov 2019 18:28:58 +0100 Subject: [PATCH] public API for filter queries --- README.md | 7 +- .../java/ch/ethz/globis/phtree/PhTree.java | 15 +++- .../globis/phtree/v11/PhIteratorNoGC.java | 5 +- .../globis/phtree/v13/PhIteratorNoGC.java | 5 +- .../ch/ethz/globis/phtree/v13/PhTree13.java | 19 ++++ .../phtree/v13SynchedPool/PhIteratorNoGC.java | 5 +- .../globis/phtree/v16/PhIteratorNoGC.java | 5 +- .../ch/ethz/globis/phtree/v16/PhTree16.java | 19 ++++ .../globis/phtree/v16hd/PhIteratorNoGC.java | 5 +- .../ethz/globis/phtree/v16hd/PhTree16HD.java | 19 ++++ .../ethz/globis/phtree/v8/PhIteratorNoGC.java | 5 +- .../globis/phtree/test/TestRangeQuery.java | 86 ++++++++++++++++++- 12 files changed, 179 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 69304e0..8789b46 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Maven: ch.ethz.globis.phtree phtree - 2.3.0 + 2.4.0 ``` @@ -33,12 +33,15 @@ A C++ version of the PH-Tree (with slightly different design) is available [here # News +### 2019-11-10 +Release 2.4.0 +- Added missing public API for filtered queries: `PhTree.query(min, max, filter)` + ### 2019-03-19 Release 2.3.0 - Added missing compute functions for `PhTreeF`, `PhTreeSolid` and `PhTreeSolidF` - Fixed bug in `compute()`/`computeIfPresent()` in V13 - ### 2019-03-15 Release 2.2.0 diff --git a/src/main/java/ch/ethz/globis/phtree/PhTree.java b/src/main/java/ch/ethz/globis/phtree/PhTree.java index b1d71bd..a6f4e1c 100644 --- a/src/main/java/ch/ethz/globis/phtree/PhTree.java +++ b/src/main/java/ch/ethz/globis/phtree/PhTree.java @@ -113,6 +113,18 @@ public interface PhTree { */ PhQuery query(long[] min, long[] max); + /** + * Performs a rectangular window query. The parameters are the min and max keys which + * contain the minimum respectively the maximum keys in every dimension. + * @param min Minimum values + * @param max Maximum values + * @param filter A filter function. The iterator will only return results that match the filter. + * @return Result iterator. + */ + default PhQuery query(long[] min, long[] max, PhFilter filter) { + throw new UnsupportedOperationException("This is only supported in V13, V16 and V16HD."); + } + /** * * @return the number of dimensions of the tree @@ -278,8 +290,9 @@ interface PhQuery extends PhIterator { * Reset the query with the new 'min' and 'max' boundaries. * @param min min values * @param max max values + * @return the query itself */ - void reset(long[] min, long[] max); + PhQuery reset(long[] min, long[] max); } /** diff --git a/src/main/java/ch/ethz/globis/phtree/v11/PhIteratorNoGC.java b/src/main/java/ch/ethz/globis/phtree/v11/PhIteratorNoGC.java index 628a444..2f5aad8 100644 --- a/src/main/java/ch/ethz/globis/phtree/v11/PhIteratorNoGC.java +++ b/src/main/java/ch/ethz/globis/phtree/v11/PhIteratorNoGC.java @@ -82,7 +82,7 @@ public PhIteratorNoGC(PhTree11 pht, PhFilter checker) { } @Override - public void reset(long[] rangeMin, long[] rangeMax) { + public PhIteratorNoGC reset(long[] rangeMin, long[] rangeMax) { this.rangeMin = rangeMin; this.rangeMax = rangeMax; this.stack.size = 0; @@ -91,11 +91,12 @@ public void reset(long[] rangeMin, long[] rangeMax) { if (pht.getRoot() == null) { //empty index isFinished = true; - return; + return this; } stack.prepareAndPush(pht.getRoot()); findNextElement(); + return this; } private void findNextElement() { diff --git a/src/main/java/ch/ethz/globis/phtree/v13/PhIteratorNoGC.java b/src/main/java/ch/ethz/globis/phtree/v13/PhIteratorNoGC.java index ba4e307..21258d7 100644 --- a/src/main/java/ch/ethz/globis/phtree/v13/PhIteratorNoGC.java +++ b/src/main/java/ch/ethz/globis/phtree/v13/PhIteratorNoGC.java @@ -84,7 +84,7 @@ public PhIteratorNoGC(PhTree13 pht, PhFilter checker) { } @Override - public void reset(long[] rangeMin, long[] rangeMax) { + public PhIteratorNoGC reset(long[] rangeMin, long[] rangeMax) { this.rangeMin = rangeMin; this.rangeMax = rangeMax; this.stack.size = 0; @@ -93,11 +93,12 @@ public void reset(long[] rangeMin, long[] rangeMax) { if (pht.getRoot() == null) { //empty index isFinished = true; - return; + return this; } stack.prepareAndPush(pht.getRoot()); findNextElement(); + return this; } private void findNextElement() { diff --git a/src/main/java/ch/ethz/globis/phtree/v13/PhTree13.java b/src/main/java/ch/ethz/globis/phtree/v13/PhTree13.java index a654ab7..ec9ee18 100644 --- a/src/main/java/ch/ethz/globis/phtree/v13/PhTree13.java +++ b/src/main/java/ch/ethz/globis/phtree/v13/PhTree13.java @@ -556,6 +556,25 @@ public PhQuery query(long[] min, long[] max) { return q; } + /** + * Performs a rectangular window query. The parameters are the min and max keys which + * contain the minimum respectively the maximum keys in every dimension. + * @param min Minimum values + * @param max Maximum values + * @param filter A filter function. The iterator will only return results that match the filter. + * @return Result iterator. + */ + @Override + public PhQuery query(long[] min, long[] max, PhFilter filter) { + if (min.length != dims || max.length != dims) { + throw new IllegalArgumentException("Invalid number of arguments: " + min.length + + " / " + max.length + " DIM=" + dims); + } + PhQuery q = new PhIteratorNoGC<>(this, filter); + q.reset(min, max); + return q; + } + /** * Performs a rectangular window query. The parameters are the min and max keys which * contain the minimum respectively the maximum keys in every dimension. diff --git a/src/main/java/ch/ethz/globis/phtree/v13SynchedPool/PhIteratorNoGC.java b/src/main/java/ch/ethz/globis/phtree/v13SynchedPool/PhIteratorNoGC.java index 2adaf2b..344de1b 100644 --- a/src/main/java/ch/ethz/globis/phtree/v13SynchedPool/PhIteratorNoGC.java +++ b/src/main/java/ch/ethz/globis/phtree/v13SynchedPool/PhIteratorNoGC.java @@ -84,7 +84,7 @@ public PhIteratorNoGC(PhTree13SP pht, PhFilter checker) { } @Override - public void reset(long[] rangeMin, long[] rangeMax) { + public PhIteratorNoGC reset(long[] rangeMin, long[] rangeMax) { this.rangeMin = rangeMin; this.rangeMax = rangeMax; this.stack.size = 0; @@ -93,11 +93,12 @@ public void reset(long[] rangeMin, long[] rangeMax) { if (pht.getRoot() == null) { //empty index isFinished = true; - return; + return this; } stack.prepareAndPush(pht.getRoot()); findNextElement(); + return this; } private void findNextElement() { diff --git a/src/main/java/ch/ethz/globis/phtree/v16/PhIteratorNoGC.java b/src/main/java/ch/ethz/globis/phtree/v16/PhIteratorNoGC.java index cfe7224..2ea83ac 100644 --- a/src/main/java/ch/ethz/globis/phtree/v16/PhIteratorNoGC.java +++ b/src/main/java/ch/ethz/globis/phtree/v16/PhIteratorNoGC.java @@ -94,7 +94,7 @@ public PhIteratorNoGC(PhTree16 pht, PhFilter checker) { } @Override - public void reset(long[] rangeMin, long[] rangeMax) { + public PhIteratorNoGC reset(long[] rangeMin, long[] rangeMax) { this.rangeMin = rangeMin; this.rangeMax = rangeMax; this.stack.size = 0; @@ -103,11 +103,12 @@ public void reset(long[] rangeMin, long[] rangeMax) { if (pht.getRoot() == null) { //empty index isFinished = true; - return; + return this; } stack.prepareAndPush(pht.getRoot(), null); findNextElement(); + return this; } private void findNextElement() { diff --git a/src/main/java/ch/ethz/globis/phtree/v16/PhTree16.java b/src/main/java/ch/ethz/globis/phtree/v16/PhTree16.java index ec52a5f..92743ac 100644 --- a/src/main/java/ch/ethz/globis/phtree/v16/PhTree16.java +++ b/src/main/java/ch/ethz/globis/phtree/v16/PhTree16.java @@ -607,6 +607,25 @@ public PhQuery query(long[] min, long[] max) { return q; } + /** + * Performs a rectangular window query. The parameters are the min and max keys which + * contain the minimum respectively the maximum keys in every dimension. + * @param min Minimum values + * @param max Maximum values + * @param filter A filter function. The iterator will only return results that match the filter. + * @return Result iterator. + */ + @Override + public PhQuery query(long[] min, long[] max, PhFilter filter) { + if (min.length != dims || max.length != dims) { + throw new IllegalArgumentException("Invalid number of arguments: " + min.length + + " / " + max.length + " DIM=" + dims); + } + PhQuery q = new PhIteratorNoGC<>(this, filter); + q.reset(min, max); + return q; + } + /** * Performs a rectangular window query. The parameters are the min and max keys which * contain the minimum respectively the maximum keys in every dimension. diff --git a/src/main/java/ch/ethz/globis/phtree/v16hd/PhIteratorNoGC.java b/src/main/java/ch/ethz/globis/phtree/v16hd/PhIteratorNoGC.java index 7dcc26e..2039672 100644 --- a/src/main/java/ch/ethz/globis/phtree/v16hd/PhIteratorNoGC.java +++ b/src/main/java/ch/ethz/globis/phtree/v16hd/PhIteratorNoGC.java @@ -93,7 +93,7 @@ public PhIteratorNoGC(PhTree16HD pht, PhFilter checker) { } @Override - public void reset(long[] rangeMin, long[] rangeMax) { + public PhIteratorNoGC reset(long[] rangeMin, long[] rangeMax) { this.rangeMin = rangeMin; this.rangeMax = rangeMax; this.stack.size = 0; @@ -102,11 +102,12 @@ public void reset(long[] rangeMin, long[] rangeMax) { if (pht.getRoot() == null) { //empty index isFinished = true; - return; + return this; } stack.prepareAndPush(pht.getRoot(), null); findNextElement(); + return this; } private void findNextElement() { diff --git a/src/main/java/ch/ethz/globis/phtree/v16hd/PhTree16HD.java b/src/main/java/ch/ethz/globis/phtree/v16hd/PhTree16HD.java index a6e3464..54a4a7c 100644 --- a/src/main/java/ch/ethz/globis/phtree/v16hd/PhTree16HD.java +++ b/src/main/java/ch/ethz/globis/phtree/v16hd/PhTree16HD.java @@ -455,6 +455,25 @@ public PhQuery query(long[] min, long[] max) { return q; } + /** + * Performs a rectangular window query. The parameters are the min and max keys which + * contain the minimum respectively the maximum keys in every dimension. + * @param min Minimum values + * @param max Maximum values + * @param filter A filter function. The iterator will only return results that match the filter. + * @return Result iterator. + */ + @Override + public PhQuery query(long[] min, long[] max, PhFilter filter) { + if (min.length != dims || max.length != dims) { + throw new IllegalArgumentException("Invalid number of arguments: " + min.length + + " / " + max.length + " DIM=" + dims); + } + PhQuery q = new PhIteratorNoGC<>(this, filter); + q.reset(min, max); + return q; + } + /** * Performs a rectangular window query. The parameters are the min and max keys which * contain the minimum respectively the maximum keys in every dimension. diff --git a/src/main/java/ch/ethz/globis/phtree/v8/PhIteratorNoGC.java b/src/main/java/ch/ethz/globis/phtree/v8/PhIteratorNoGC.java index a54dce0..f1a81ca 100644 --- a/src/main/java/ch/ethz/globis/phtree/v8/PhIteratorNoGC.java +++ b/src/main/java/ch/ethz/globis/phtree/v8/PhIteratorNoGC.java @@ -95,7 +95,7 @@ public PhIteratorNoGC(PhTree8 pht, PhFilter checker) { } @Override - public void reset(long[] rangeMin, long[] rangeMax) { + public PhIteratorNoGC reset(long[] rangeMin, long[] rangeMax) { this.rangeMin = rangeMin; this.rangeMax = rangeMax; this.stack.size = 0; @@ -104,7 +104,7 @@ public void reset(long[] rangeMin, long[] rangeMax) { if (pht.getRoot() == null) { //empty index isFinished = true; - return; + return this; } if (stack.prepare(pht.getRoot())) { @@ -112,6 +112,7 @@ public void reset(long[] rangeMin, long[] rangeMax) { } else { isFinished = true; } + return this; } private void findNextElement() { diff --git a/src/test/java/ch/ethz/globis/phtree/test/TestRangeQuery.java b/src/test/java/ch/ethz/globis/phtree/test/TestRangeQuery.java index 75bd4ba..fef5a8a 100644 --- a/src/test/java/ch/ethz/globis/phtree/test/TestRangeQuery.java +++ b/src/test/java/ch/ethz/globis/phtree/test/TestRangeQuery.java @@ -15,14 +15,20 @@ import java.util.Arrays; import java.util.List; import java.util.Random; +import java.util.function.IntFunction; import org.junit.Test; +import ch.ethz.globis.phtree.PhDistanceL; +import ch.ethz.globis.phtree.PhFilter; import ch.ethz.globis.phtree.PhRangeQuery; import ch.ethz.globis.phtree.PhTree; import ch.ethz.globis.phtree.PhTree.PhIterator; import ch.ethz.globis.phtree.test.util.TestUtil; import ch.ethz.globis.phtree.util.Bits; +import ch.ethz.globis.phtree.v13.PhTree13; +import ch.ethz.globis.phtree.v16.PhTree16; +import ch.ethz.globis.phtree.v16hd.PhTree16HD; public class TestRangeQuery { @@ -112,7 +118,7 @@ public void testQueryND64Random1() { final int range = MAXV/2; final Random R = new Random(0); for (int d = 0; d < LOOP; d++) { - PhTree ind = TestUtil.newTree(DIM, 32); + PhTree ind = TestUtil.newTree(DIM); PhRangeQuery q = ind.rangeQuery(1, new long[DIM]); for (int i = 0; i < N; i++) { long[] v = new long[DIM]; @@ -142,6 +148,76 @@ public void testQueryND64Random1() { } } + @Test + public void testQueryNDRandomFilter13() { + testQueryNDRandomFilter(dim -> new PhTree13(dim)); + } + + @Test + public void testQueryNDRandomFilter16() { + testQueryNDRandomFilter(dim -> new PhTree16(dim)); + } + + @Test + public void testQueryNDRandomFilter16HD() { + testQueryNDRandomFilter(dim -> new PhTree16HD(dim)); + } + + private void testQueryNDRandomFilter(IntFunction> factory) { + final int DIM = 5; + final int LOOP = 10; + final int N = 1000; + final int NQ = 1000; + final int MAXV = 1000; + final int range = MAXV/2; + final Random R = new Random(0); + + for (int d = 0; d < LOOP; d++) { + long[] v = new long[DIM]; + long[] min = new long[DIM]; + long[] max = new long[DIM]; + PhFilter filter = filter(v, range); + PhTree ind = factory.apply(DIM); + PhTree.PhQuery q = ind.query(min, max, filter); + for (int i = 0; i < N; i++) { + long[] v2 = new long[DIM]; + for (int j = 0; j < DIM; j++) { + v2[j] = Math.abs(R.nextInt(MAXV)); + } + ind.put(v2, null); + } + for (int i = 0; i < NQ; i++) { + for (int j = 0; j < DIM; j++) { + v[j] = Math.abs(R.nextInt(MAXV)); + min[j] = v[j]-range; + max[j] = v[j]+range; + } + long[] exp = rangeQuery(ind, range, v).get(0); + List nnList = toList(q.reset(min, max)); + assertTrue("i=" + i + " d=" + d, !nnList.isEmpty()); + long[] nn = nnList.get(0); + check(v, exp, nn); + } + } + } + + private PhFilter filter(long[] v, double radius) { + return new PhFilter() { + private static final long serialVersionUID = 1L; + @Override + public boolean isValid(int bitsToIgnore, long[] prefix) { + //if (true) throw new IllegalStateException(); + return true; + } + + @Override + public boolean isValid(long[] key) { + //return true; + return PhDistanceL.THIS.dist(v, key) <= radius; + } + }; + } + /** * This used to return an empty result set. */ @@ -473,6 +549,14 @@ private boolean contains(List l, long ... v) { return false; } + private List toList(PhTree.PhQuery q) { + ArrayList ret = new ArrayList<>(); + while (q.hasNext()) { + ret.add(q.nextKey()); + } + return ret; + } + private List toList(PhRangeQuery q) { ArrayList ret = new ArrayList<>(); while (q.hasNext()) {