From a2a61f8a9a90400e972672a8e498214ce926bd3c Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Wed, 22 Dec 2021 13:05:34 -0800 Subject: [PATCH 01/35] function declarations --- src/h3lib/include/h3api.h.in | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/h3lib/include/h3api.h.in b/src/h3lib/include/h3api.h.in index 23b27587c..238f0923f 100644 --- a/src/h3lib/include/h3api.h.in +++ b/src/h3lib/include/h3api.h.in @@ -734,6 +734,15 @@ DECLSPEC H3Error H3_EXPORT(experimentalLocalIjToH3)(H3Index origin, H3Index *out); /** @} */ +int isLow52Sorted(const H3Index *cells, const int64_t N); +H3Error low52Sort(H3Index *cells, const int64_t N); + +int isCanonicalCells(const H3Index *cells, const int64_t N); +H3Error canonicalizeCells(H3Index *cells, const int64_t numBefore, + int64_t *numAfter); + +int lower52bsearch(H3Index *cells, int64_t N, H3Index h); + #ifdef __cplusplus } // extern "C" #endif From 7ce1842490e0e372db2bb398cd0e6d96ad189ef2 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Wed, 22 Dec 2021 13:44:44 -0800 Subject: [PATCH 02/35] rfc --- dev-docs/RFCs/canonicalization.md | 95 +++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 dev-docs/RFCs/canonicalization.md diff --git a/dev-docs/RFCs/canonicalization.md b/dev-docs/RFCs/canonicalization.md new file mode 100644 index 000000000..9a5125e90 --- /dev/null +++ b/dev-docs/RFCs/canonicalization.md @@ -0,0 +1,95 @@ +# RFC: Canonicalization for H3 cell sets + +* **Authors**: AJ Friend +* **Date**: 2021-12-122 +* **Status**: Draft + +## Abstract + +We propose a canonical form for **sets** of H3 cells based on +the "lower 52 bit" ordering. We also introduce fast "spatial join" operations +on the cell sets (like cell-in-set, intersection, union, etc.) that exploit +the canonical structure for speed gains. + + +## Motivation + +A canonical form for cell sets is useful when testing if two sets are equal. +That is, we'd like to be able to tell if two H3 cell arrays represent +the same mathematical set of cells, ignoring ordering or duplicated cells. + +If we have a function to canonicalize an H3 cell array, then we would +consider two arrays to be equivalent (as sets) if they each canonicalize +to the exact same (canonical) cell array. + +A canonical form is also useful if a user wanted to deterministically +hash H3 cell sets, and wanted the hash to be independent of ordering +or duplicates. + +The canonical form we'll propose also has the added benefit of allowing +for fast "spatial join" operations on canonicalized sets. For example, +we'll be able to do a fast binary search to see if a cell is a member +of a set, and an **even faster** binary search if the set is both +compacted and canonicalized. + +We'll get the same benefits computing the intersection of sets, or +simply testing for intersection. + +The canonical form also suggests a new "in-memory" cell compaction algorithm, +which avoids any dynamic memory allocation. This new compact algorithm +has the added benefit of returning cell arrays already in canonical form. + + +## Approaches + + + + + +## Proposal + +We propose a canonical form based on the "lower 52 bit" ordering, that is, +the ordering you would get if you only considered the lower 52 bits of the +H3 cell indexes. The lower 52 bits of an H3 index consist of 7 bits for the +base cell, and 3 bits for each of the 15 resolution digits. That sums up +to `7 + 3*15 = 52`. + +We'll only define this ordering for H3 **cells**. We're not considering +vertices or edges in this RFC. + +The lower 52 bit ordering can be implemented, for example, by +the `cmpLow52` comparison function given below. + + +```c +int cmpLow52(H3Index a, H3Index b) { + a <<= 12; + b <<= 12; + + if (a < b) return -1; + if (a > b) return +1; + return 0; +``` + +This ordering has the property that children cells are always less than +their parent cells. Ordered in an array with cells of multiple resolutions, +children cells are always to the left of their parents. + +We can also get slightly richer ordering information with a comparison function +with a declaration like + +```c +int cmpCanon(H3Index a, H3Index b); +``` + +defined so that: + +- `cmpCanon(a, b) == 0` if `a` and `b` are the same cell +- `cmpCanon(a, b) == -1` if `a` is a child (or further descendant) of `b` +- `cmpCanon(a, b) == +1` if `b` ... `a` +- `cmpCanon(a, b) == -2` if `a` < `b` in the low52 ordering, but they are not related +- `cmpCanon(a, b) == +2` if `b` < `a` ... + + + + From ddeaf4260c0f6e8f7617bef863acb912e6157ea0 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Wed, 22 Dec 2021 13:57:50 -0800 Subject: [PATCH 03/35] types of arrays --- dev-docs/RFCs/canonicalization.md | 41 +++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/dev-docs/RFCs/canonicalization.md b/dev-docs/RFCs/canonicalization.md index 9a5125e90..8548236f1 100644 --- a/dev-docs/RFCs/canonicalization.md +++ b/dev-docs/RFCs/canonicalization.md @@ -90,6 +90,47 @@ defined so that: - `cmpCanon(a, b) == -2` if `a` < `b` in the low52 ordering, but they are not related - `cmpCanon(a, b) == +2` if `b` < `a` ... +Note that these two functions produce the same ordering when given to +the C standard library's `qsort`. +### Array classifications +Given these comparison functions, we can define 3 increasingly strict properties +on arrays of H3 cells: + +1. "lower 52" ordered +2. canonical +3. compacted and canonical + +#### Low52 ordered + +An H3 cell array `a` is "low52 ordered" if its elements are such that + +- `cmpLow52(a[i-1], a[i]) <= 0` or, equivalently, +- `cmpCanon(a[i-1], a[i]) <= 0`. + +Note that in this classification, arrays can have duplicated cell. We can also +have the parents, children, ancestors, or descendants of other cells in +the array. + +### Canonical + +We'll define a "canonical" H3 cell array to be one that is low52 ordered and +has the additional property that no duplicates, parents, children, ancestors, +or descendants of other cells are in the array. + +We can check this property by ensuring that + +```c +cmpCanon(a[i-1], a[i]) == -2 +``` + +for each adjacent pair of cells in the array. + +### Compacted and canonical + +A compacted and canonical H3 set is just what it sounds like. + +Many of the fast spatial join operations will work on canonical sets, but +will be faster on compacted canonical sets. From 1c741bde9579f39c688c6f2dd41535d18e09e1a6 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Wed, 22 Dec 2021 13:59:05 -0800 Subject: [PATCH 04/35] merp --- dev-docs/RFCs/canonicalization.md | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/dev-docs/RFCs/canonicalization.md b/dev-docs/RFCs/canonicalization.md index 8548236f1..e8628732f 100644 --- a/dev-docs/RFCs/canonicalization.md +++ b/dev-docs/RFCs/canonicalization.md @@ -39,14 +39,7 @@ The canonical form also suggests a new "in-memory" cell compaction algorithm, which avoids any dynamic memory allocation. This new compact algorithm has the added benefit of returning cell arrays already in canonical form. - -## Approaches - - - - - -## Proposal +## Terminology We propose a canonical form based on the "lower 52 bit" ordering, that is, the ordering you would get if you only considered the lower 52 bits of the @@ -134,3 +127,5 @@ A compacted and canonical H3 set is just what it sounds like. Many of the fast spatial join operations will work on canonical sets, but will be faster on compacted canonical sets. +## Proposal + From f1a6e9a3e30e125df34750f823c5413700e72b45 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Wed, 22 Dec 2021 23:17:14 -0800 Subject: [PATCH 05/35] implementations --- CMakeLists.txt | 3 +- src/h3lib/include/h3api.h.in | 2 +- src/h3lib/include/mathExtensions.h | 1 + src/h3lib/lib/low52.c | 299 +++++++++++++++++++++++++++++ 4 files changed, 303 insertions(+), 2 deletions(-) create mode 100644 src/h3lib/lib/low52.c diff --git a/CMakeLists.txt b/CMakeLists.txt index a356ad84d..dc5c8d75b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -137,7 +137,8 @@ set(LIB_SOURCE_FILES src/h3lib/lib/iterators.c src/h3lib/lib/vertexGraph.c src/h3lib/lib/faceijk.c - src/h3lib/lib/baseCells.c) + src/h3lib/lib/baseCells.c + src/h3lib/lib/low52.c) set(APP_SOURCE_FILES src/apps/applib/include/kml.h src/apps/applib/include/benchmark.h diff --git a/src/h3lib/include/h3api.h.in b/src/h3lib/include/h3api.h.in index 238f0923f..aae8e9e65 100644 --- a/src/h3lib/include/h3api.h.in +++ b/src/h3lib/include/h3api.h.in @@ -741,7 +741,7 @@ int isCanonicalCells(const H3Index *cells, const int64_t N); H3Error canonicalizeCells(H3Index *cells, const int64_t numBefore, int64_t *numAfter); -int lower52bsearch(H3Index *cells, int64_t N, H3Index h); +int lower52Bsearch(H3Index *cells, int64_t N, H3Index h); #ifdef __cplusplus } // extern "C" diff --git a/src/h3lib/include/mathExtensions.h b/src/h3lib/include/mathExtensions.h index 2ed47e331..74d242021 100644 --- a/src/h3lib/include/mathExtensions.h +++ b/src/h3lib/include/mathExtensions.h @@ -26,6 +26,7 @@ * MAX returns the maximum of two values. */ #define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) // Internal functions int64_t _ipow(int64_t base, int64_t exp); diff --git a/src/h3lib/lib/low52.c b/src/h3lib/lib/low52.c new file mode 100644 index 000000000..6f4308612 --- /dev/null +++ b/src/h3lib/lib/low52.c @@ -0,0 +1,299 @@ +#include +#include +#include + +#include "h3Index.h" +#include "mathExtensions.h" + +#define HIGH_BITS(h, t) ((h) >> (64 - (t))) + +static int cmpLow52(H3Index a, H3Index b) { + a <<= 12; + b <<= 12; + + if (a < b) return -1; + if (a > b) return +1; + return 0; +} + +// todo: could have a version that assumes a <= b already. save any time? +static int cmpCanon(H3Index a, H3Index b) { + // skip high, mode, reserved bits + a <<= 8; + b <<= 8; + + // pull 4 resolution bits + int res_a = HIGH_BITS(a, 4); + int res_b = HIGH_BITS(b, 4); + + // move past 4 resolution bits + a <<= 4; + b <<= 4; + + // 7 bits for base cell, 3 for each shared resolution level + int common = 7 + 3 * MIN(res_a, res_b); + bool are_related = (HIGH_BITS(a, common) == HIGH_BITS(b, common)); + + if (are_related) { + if (a < b) return -1; + if (a > b) return +1; + } else { + if (a < b) return -2; + if (a > b) return +2; + } + + return 0; +} + +static int cmpLow52Ptr(const void *a_, const void *b_) { + H3Index a = *(const H3Index *)a_; + H3Index b = *(const H3Index *)b_; + + return cmpLow52(a, b); +} + +int isLow52Sorted(const H3Index *cells, const int64_t N) { + // note: returns true if N == 0 or N == 1 + for (int64_t i = 1; i < N; i++) { + if (cmpLow52(cells[i - 1], cells[i]) <= 0) { + // this pair is OK + } else { + return false; + } + } + return true; +} + +/* +Return True if cells is low52 ordered, and there no children/descendants/dupes. + +TODO: could maybe speed up by keeping resolution calculation since cells +appear in both LHS and RHS + */ +int isCanonicalCells(const H3Index *cells, const int64_t N) { + // note: returns true if N == 0 or N == 1 + for (int64_t i = 1; i < N; i++) { + if (cmpCanon(cells[i - 1], cells[i]) == -2) { + // this pair is OK + } else { + return false; + } + } + return true; +} + +// note that this places any zeros at the start of the array +H3Error low52Sort(H3Index *cells, const int64_t N) { + qsort(cells, N, sizeof(H3Index), cmpLow52Ptr); + return E_SUCCESS; +} + +// if c is a descendant of p (including being the same cell) +static bool isDesc(H3Index c, H3Index p) { + int x = cmpCanon(c, p); + return ((x == -1) || (x == 0)); +} + +/* +Remove cells which have a parent in the sorted array. +Assume the cell array is ordered by the lower52 order. +Walk from the right to the left, looking for descendants. + +TODO: we can maybe do the descendant test even faster, since we +assume the array is sorted! + */ +static void setDescToZero(H3Index *cells, const int64_t N) { + H3Index p = 0; // current parent + + for (int64_t i = N - 1; i >= 0; i--) { + if (cells[i] == 0) { + continue; + } + + if (p == 0) { + p = cells[i]; + } else if (isDesc(cells[i], p)) { + cells[i] = 0; + } else { + p = cells[i]; + } + } +} + +/* +Shift all the nonzero elements of the array to the left while preserving +their order. Return the number of nonzero elements. + */ +static int64_t shiftOutZeros(H3Index *cells, const int64_t N) { + int64_t i, k; + + for (k = 0; k < N; k++) { + if (cells[k] == 0) { + break; + } + } + + for (i = 0; i < N; i++) { + if ((cells[i] != 0) && (k < i)) { + cells[k] = cells[i]; + cells[i] = 0; + k++; + } + } + + return k; +} + +/* +Canonicalize an array of cells. The array is permitted to have +valid H3 cells and H3_NULL as elements. + +numAfter is the number of nonzero cells in the canonicalized array. +All nonzero elements are moved to the front/left of the array. + */ +H3Error canonicalizeCells(H3Index *cells, const int64_t numBefore, + int64_t *numAfter) { + low52Sort(cells, numBefore); + setDescToZero(cells, numBefore); + *numAfter = shiftOutZeros(cells, numBefore); + + return E_SUCCESS; +} + +// bsearch works on any canonical (maybe just low52ordered), but is faster on +// canonical compact low52ordered < canonical < canonical compact? feature +// matrix comparison bool is_canonical_compacted(const H3Index *cells, const +// int64_t N); // don't add this + +/* +bswarch works on low52_sorted. faster on canonical. fastest on canonical +compacted. + */ + +/* + Compact hex set binary search. + + Determine if h is in s_hexes, or a child of any hex in s_hexes. + + s_hexes is sorted asc by lower52(). this means children are to the left + of parents. + + ## Loop invariant + + We know that h is not before i or after j, so we only search + in the interval [i, j]. + + */ + +int lower52Bsearch_slow(H3Index *cells, int64_t N, H3Index h) { + int64_t i = 0; + int64_t j = N; + int64_t k; + + if (N <= 0) { + return false; + } + + while (j - i != 1) { + k = i + (j - i) / 2; + + if (cmpLow52(cells[k - 1], h) < 0) { + i = k; + } else { + j = k; + } + } + + return isDesc(h, cells[i]); +} + +/* + +i = 0 +j = 7 +k = 3 + +| . | . | . | X | . | . | . | +i k j + + +two outcomes if we don't find a match: + +| . | . | . | +i j + + + | . | . | . | + i j + + +i = 0 +j = 2 +k = 1 + +| . | X | + \ \ \ + i k j + + +i = 0 +j = 1 +k = 0 + +| X | +i j +k + + +| X | + \ \ + i,k \ + j + +i = 0 +j = 0 +k = 0 + +| (empty array) + \ + i,j,k + + */ + +/* +At each iteration, we can select any k from i to j-1. +Typically, we'll just select a k midway between i and j. +This strategy makes us test the endpoints of the array first, +hoping for an early exit if h is clearly not in the set. + */ +static inline int64_t kStrategy(int64_t i, int64_t j, int64_t N) { + if (i == 0) return 0; + if (j == N) return N - 1; + + return i + (j - i) / 2; +} + +int lower52Bsearch_rich(H3Index *cells, int64_t N, H3Index h) { + int64_t i = 0; + int64_t j = N; + + while (i < j) { + int64_t k = kStrategy(i, j, N); + int cmp = cmpCanon(h, cells[k]); + + if (cmp == -1 || cmp == 0) { + return true; + } else if (cmp == -2) { + j = k; + } else if (cmp == +2) { + i = k + 1; + } else { + // This conclusion depends on `cells` being canonical. + // Not true if it is only low 52 sorted. + // TODO: also provide one that works on just low 52 arrays? + return false; + } + } + + return false; +} From d37210142b3e9fea5df2c88a6bf2e9da50dbed14 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Thu, 23 Dec 2021 12:34:36 -0800 Subject: [PATCH 06/35] intersection test --- src/h3lib/include/h3api.h.in | 4 +- src/h3lib/lib/low52.c | 95 +++++++++++++++++++++++++++--------- 2 files changed, 75 insertions(+), 24 deletions(-) diff --git a/src/h3lib/include/h3api.h.in b/src/h3lib/include/h3api.h.in index aae8e9e65..aef174138 100644 --- a/src/h3lib/include/h3api.h.in +++ b/src/h3lib/include/h3api.h.in @@ -741,7 +741,9 @@ int isCanonicalCells(const H3Index *cells, const int64_t N); H3Error canonicalizeCells(H3Index *cells, const int64_t numBefore, int64_t *numAfter); -int lower52Bsearch(H3Index *cells, int64_t N, H3Index h); +int canonSearch(const H3Index *cells, const int64_t N, const H3Index h); +int intersectTheyDo(const H3Index *_A, const int64_t aN, const H3Index *_B, + const int64_t bN); #ifdef __cplusplus } // extern "C" diff --git a/src/h3lib/lib/low52.c b/src/h3lib/lib/low52.c index 6f4308612..1b712f8a3 100644 --- a/src/h3lib/lib/low52.c +++ b/src/h3lib/lib/low52.c @@ -185,28 +185,6 @@ compacted. */ -int lower52Bsearch_slow(H3Index *cells, int64_t N, H3Index h) { - int64_t i = 0; - int64_t j = N; - int64_t k; - - if (N <= 0) { - return false; - } - - while (j - i != 1) { - k = i + (j - i) / 2; - - if (cmpLow52(cells[k - 1], h) < 0) { - i = k; - } else { - j = k; - } - } - - return isDesc(h, cells[i]); -} - /* i = 0 @@ -265,6 +243,8 @@ At each iteration, we can select any k from i to j-1. Typically, we'll just select a k midway between i and j. This strategy makes us test the endpoints of the array first, hoping for an early exit if h is clearly not in the set. +This is totally a heuristic, but should work well on typical +"clumps" of geo data. */ static inline int64_t kStrategy(int64_t i, int64_t j, int64_t N) { if (i == 0) return 0; @@ -273,7 +253,7 @@ static inline int64_t kStrategy(int64_t i, int64_t j, int64_t N) { return i + (j - i) / 2; } -int lower52Bsearch_rich(H3Index *cells, int64_t N, H3Index h) { +int canonSearch(const H3Index *cells, const int64_t N, const H3Index h) { int64_t i = 0; int64_t j = N; @@ -297,3 +277,72 @@ int lower52Bsearch_rich(H3Index *cells, int64_t N, H3Index h) { return false; } + +// could also just take in a search interval struct... +// returns -1 if h intersects with cells[i:j] +int64_t disjointInsertionPoint(const H3Index *cells, int64_t i, int64_t j, + const H3Index h) { + while (i < j) { + int64_t k = i + (j - i) / 2; + int cmp = cmpCanon(h, cells[k]); + + if (cmp == -2) { + j = k; + } else if (cmp == +2) { + i = k + 1; + } else { + return -1; // h intersects with cells! + } + } + + return i; +} + +typedef struct { + H3Index *cells; + int64_t N, i, j; +} SearchInterval; + +typedef enum { LEFT = 0, RIGHT = 1 } Endpoint; + +// Yoda naming until we come up with something better +int intersectTheyDo(const H3Index *_A, const int64_t aN, const H3Index *_B, + const int64_t bN) { + SearchInterval A = {.cells = _A, .N = aN, .i = 0, .j = aN}; + SearchInterval B = {.cells = _B, .N = bN, .i = 0, .j = bN}; + H3Index h; + + while ((A.i < A.j) && (B.i < B.j)) { + // ensure A is the smaller of the two sets. + if ((B.j - B.i) < (A.j - A.i)) { + SearchInterval temp = A; + A = B; + B = temp; + } + + // take A[i] or A[j-1] and see what happens when we look into B[i:j] + Endpoint ep = (A.i % 2 == 0); + + if (ep == LEFT) { + h = A.cells[A.i]; + } else if (ep == RIGHT) { + h = A.cells[A.j - 1]; + } + + int64_t k = disjointInsertionPoint(B.cells, B.i, B.j, h); + + if (k == -1) { + return true; // they intersect + } + + if (ep == LEFT) { + B.i = k; + A.i++; + } else if (ep == RIGHT) { + B.j = k; + A.j--; + } + } + + return false; +} From b9d5af283fe54e7eea8fe1486cdf445e136b9747 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Thu, 23 Dec 2021 12:39:06 -0800 Subject: [PATCH 07/35] better with bool --- src/h3lib/lib/low52.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/h3lib/lib/low52.c b/src/h3lib/lib/low52.c index 1b712f8a3..e15fcd9e1 100644 --- a/src/h3lib/lib/low52.c +++ b/src/h3lib/lib/low52.c @@ -303,8 +303,6 @@ typedef struct { int64_t N, i, j; } SearchInterval; -typedef enum { LEFT = 0, RIGHT = 1 } Endpoint; - // Yoda naming until we come up with something better int intersectTheyDo(const H3Index *_A, const int64_t aN, const H3Index *_B, const int64_t bN) { @@ -312,6 +310,8 @@ int intersectTheyDo(const H3Index *_A, const int64_t aN, const H3Index *_B, SearchInterval B = {.cells = _B, .N = bN, .i = 0, .j = bN}; H3Index h; + // TODO: a quick exit check? + while ((A.i < A.j) && (B.i < B.j)) { // ensure A is the smaller of the two sets. if ((B.j - B.i) < (A.j - A.i)) { @@ -321,11 +321,11 @@ int intersectTheyDo(const H3Index *_A, const int64_t aN, const H3Index *_B, } // take A[i] or A[j-1] and see what happens when we look into B[i:j] - Endpoint ep = (A.i % 2 == 0); + bool usingLeft = (A.i % 2 == 0); - if (ep == LEFT) { + if (usingLeft) { h = A.cells[A.i]; - } else if (ep == RIGHT) { + } else { h = A.cells[A.j - 1]; } @@ -335,10 +335,10 @@ int intersectTheyDo(const H3Index *_A, const int64_t aN, const H3Index *_B, return true; // they intersect } - if (ep == LEFT) { + if (usingLeft) { B.i = k; A.i++; - } else if (ep == RIGHT) { + } else { B.j = k; A.j--; } From f87297b898485c86e0bf629bd248a078283dbf04 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Thu, 23 Dec 2021 13:08:05 -0800 Subject: [PATCH 08/35] wayLessThan --- src/h3lib/lib/low52.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/h3lib/lib/low52.c b/src/h3lib/lib/low52.c index e15fcd9e1..f276a4221 100644 --- a/src/h3lib/lib/low52.c +++ b/src/h3lib/lib/low52.c @@ -280,8 +280,8 @@ int canonSearch(const H3Index *cells, const int64_t N, const H3Index h) { // could also just take in a search interval struct... // returns -1 if h intersects with cells[i:j] -int64_t disjointInsertionPoint(const H3Index *cells, int64_t i, int64_t j, - const H3Index h) { +static int64_t disjointInsertionPoint(const H3Index *cells, int64_t i, + int64_t j, const H3Index h) { while (i < j) { int64_t k = i + (j - i) / 2; int cmp = cmpCanon(h, cells[k]); @@ -291,6 +291,7 @@ int64_t disjointInsertionPoint(const H3Index *cells, int64_t i, int64_t j, } else if (cmp == +2) { i = k + 1; } else { + // cmp == -1, +1, or 0 return -1; // h intersects with cells! } } @@ -299,18 +300,27 @@ int64_t disjointInsertionPoint(const H3Index *cells, int64_t i, int64_t j, } typedef struct { - H3Index *cells; + const H3Index *cells; int64_t N, i, j; } SearchInterval; +static bool wayLessThan(const SearchInterval A, const SearchInterval B) { + if (A.N > 0 && B.N > 0 && (cmpCanon(A.cells[A.N - 1], B.cells[0]) == -2)) { + return true; + } else { + return false; + } +} + // Yoda naming until we come up with something better int intersectTheyDo(const H3Index *_A, const int64_t aN, const H3Index *_B, const int64_t bN) { SearchInterval A = {.cells = _A, .N = aN, .i = 0, .j = aN}; SearchInterval B = {.cells = _B, .N = bN, .i = 0, .j = bN}; - H3Index h; - // TODO: a quick exit check? + // check for a quick exit + if (wayLessThan(A, B)) return false; + if (wayLessThan(B, A)) return false; while ((A.i < A.j) && (B.i < B.j)) { // ensure A is the smaller of the two sets. @@ -322,7 +332,7 @@ int intersectTheyDo(const H3Index *_A, const int64_t aN, const H3Index *_B, // take A[i] or A[j-1] and see what happens when we look into B[i:j] bool usingLeft = (A.i % 2 == 0); - + H3Index h; if (usingLeft) { h = A.cells[A.i]; } else { From 8c902009fbfd823a4c377366b810d1283fe8030f Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Thu, 23 Dec 2021 13:22:27 -0800 Subject: [PATCH 09/35] ensureASmaller --- src/h3lib/lib/low52.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/h3lib/lib/low52.c b/src/h3lib/lib/low52.c index f276a4221..c8f58defd 100644 --- a/src/h3lib/lib/low52.c +++ b/src/h3lib/lib/low52.c @@ -312,6 +312,17 @@ static bool wayLessThan(const SearchInterval A, const SearchInterval B) { } } +static void ensureASmaller(SearchInterval *A, SearchInterval *B) { + // ensure A is the smaller of the two sets. + SearchInterval temp; + + if ((B->j - B->i) < (A->j - A->i)) { + temp = *A; + *A = *B; + *B = temp; + } +} + // Yoda naming until we come up with something better int intersectTheyDo(const H3Index *_A, const int64_t aN, const H3Index *_B, const int64_t bN) { @@ -323,12 +334,7 @@ int intersectTheyDo(const H3Index *_A, const int64_t aN, const H3Index *_B, if (wayLessThan(B, A)) return false; while ((A.i < A.j) && (B.i < B.j)) { - // ensure A is the smaller of the two sets. - if ((B.j - B.i) < (A.j - A.i)) { - SearchInterval temp = A; - A = B; - B = temp; - } + ensureASmaller(&A, &B); // take A[i] or A[j-1] and see what happens when we look into B[i:j] bool usingLeft = (A.i % 2 == 0); @@ -339,6 +345,8 @@ int intersectTheyDo(const H3Index *_A, const int64_t aN, const H3Index *_B, h = A.cells[A.j - 1]; } + // H3Index h = (usingLeft) ? A.cells[A.i] : A.cells[A.j - 1]; + int64_t k = disjointInsertionPoint(B.cells, B.i, B.j, h); if (k == -1) { From d7bf43e2c30c8e1ae0448bf3b8d5384ac57478e6 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Thu, 23 Dec 2021 13:25:03 -0800 Subject: [PATCH 10/35] ternary --- src/h3lib/lib/low52.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/h3lib/lib/low52.c b/src/h3lib/lib/low52.c index c8f58defd..748295cf5 100644 --- a/src/h3lib/lib/low52.c +++ b/src/h3lib/lib/low52.c @@ -338,15 +338,7 @@ int intersectTheyDo(const H3Index *_A, const int64_t aN, const H3Index *_B, // take A[i] or A[j-1] and see what happens when we look into B[i:j] bool usingLeft = (A.i % 2 == 0); - H3Index h; - if (usingLeft) { - h = A.cells[A.i]; - } else { - h = A.cells[A.j - 1]; - } - - // H3Index h = (usingLeft) ? A.cells[A.i] : A.cells[A.j - 1]; - + H3Index h = (usingLeft) ? A.cells[A.i] : A.cells[A.j - 1]; int64_t k = disjointInsertionPoint(B.cells, B.i, B.j, h); if (k == -1) { From cbf69b31466417dedb9e05e56be2d0689a42cdea Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Thu, 23 Dec 2021 13:29:51 -0800 Subject: [PATCH 11/35] merp --- src/h3lib/lib/low52.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/h3lib/lib/low52.c b/src/h3lib/lib/low52.c index 748295cf5..c028cc064 100644 --- a/src/h3lib/lib/low52.c +++ b/src/h3lib/lib/low52.c @@ -341,9 +341,7 @@ int intersectTheyDo(const H3Index *_A, const int64_t aN, const H3Index *_B, H3Index h = (usingLeft) ? A.cells[A.i] : A.cells[A.j - 1]; int64_t k = disjointInsertionPoint(B.cells, B.i, B.j, h); - if (k == -1) { - return true; // they intersect - } + if (k == -1) return true; // they intersect! if (usingLeft) { B.i = k; From 8b073720fe566f64f1dfc1052c53d2b9e335b767 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Thu, 23 Dec 2021 13:50:11 -0800 Subject: [PATCH 12/35] notes --- src/h3lib/lib/low52.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/h3lib/lib/low52.c b/src/h3lib/lib/low52.c index c028cc064..844a165ed 100644 --- a/src/h3lib/lib/low52.c +++ b/src/h3lib/lib/low52.c @@ -323,7 +323,12 @@ static void ensureASmaller(SearchInterval *A, SearchInterval *B) { } } -// Yoda naming until we come up with something better +/* +Double binary search for a fast intersection test on canonical sets. +Faster if compact and canonical. + +Yoda naming until we come up with something better + */ int intersectTheyDo(const H3Index *_A, const int64_t aN, const H3Index *_B, const int64_t bN) { SearchInterval A = {.cells = _A, .N = aN, .i = 0, .j = aN}; From 81d7e2f763968d705fafa0357f0790e5b2a9bccf Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Thu, 23 Dec 2021 13:56:00 -0800 Subject: [PATCH 13/35] intersectTheyDo_slow --- src/h3lib/lib/low52.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/h3lib/lib/low52.c b/src/h3lib/lib/low52.c index 844a165ed..fbb3d3fbf 100644 --- a/src/h3lib/lib/low52.c +++ b/src/h3lib/lib/low52.c @@ -359,3 +359,30 @@ int intersectTheyDo(const H3Index *_A, const int64_t aN, const H3Index *_B, return false; } + + +/* +Just for comparison: + +This implementation is also **correct**, but I'm guessing it will be slower +on real data. The implementation above has a few heuristics that I think +will help with speed. + */ +int intersectTheyDo_slow(const H3Index *_A, const int64_t aN, const H3Index *_B, + const int64_t bN) { + SearchInterval A = {.cells = _A, .N = aN, .i = 0, .j = aN}; + SearchInterval B = {.cells = _B, .N = bN, .i = 0, .j = bN}; + + while ((A.i < A.j) && (B.i < B.j)) { + // take A[i] and see what happens when we look into B[i:j] + H3Index h = A.cells[A.i]; + int64_t k = disjointInsertionPoint(B.cells, B.i, B.j, h); + + if (k == -1) return true; // they intersect! + + B.i = k; + A.i++; + } + + return false; +} From b55c886e75405555f84af7859db52f69b098a71b Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Thu, 23 Dec 2021 15:50:44 -0800 Subject: [PATCH 14/35] formatting --- src/h3lib/lib/low52.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/h3lib/lib/low52.c b/src/h3lib/lib/low52.c index fbb3d3fbf..3fd8efaf5 100644 --- a/src/h3lib/lib/low52.c +++ b/src/h3lib/lib/low52.c @@ -360,7 +360,6 @@ int intersectTheyDo(const H3Index *_A, const int64_t aN, const H3Index *_B, return false; } - /* Just for comparison: @@ -369,7 +368,7 @@ on real data. The implementation above has a few heuristics that I think will help with speed. */ int intersectTheyDo_slow(const H3Index *_A, const int64_t aN, const H3Index *_B, - const int64_t bN) { + const int64_t bN) { SearchInterval A = {.cells = _A, .N = aN, .i = 0, .j = aN}; SearchInterval B = {.cells = _B, .N = bN, .i = 0, .j = bN}; From 6368edf695858267ff740cd8bedf9f0b597b29e1 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Fri, 24 Dec 2021 17:04:56 -0800 Subject: [PATCH 15/35] add some tests --- CMakeLists.txt | 2 + src/apps/testapps/testLow52.c | 97 +++++++++++++++++++++++++++++++++++ src/h3lib/lib/low52.c | 6 +++ 3 files changed, 105 insertions(+) create mode 100644 src/apps/testapps/testLow52.c diff --git a/CMakeLists.txt b/CMakeLists.txt index dc5c8d75b..f227b715d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -211,6 +211,7 @@ set(OTHER_SOURCE_FILES src/apps/testapps/testCoordIjk.c src/apps/testapps/testH3Memory.c src/apps/testapps/testH3Iterators.c + src/apps/testapps/testLow52.c src/apps/miscapps/cellToBoundaryHier.c src/apps/miscapps/cellToLatLngHier.c src/apps/miscapps/generateBaseCellNeighbors.c @@ -608,6 +609,7 @@ if(H3_IS_ROOT_PROJECT AND BUILD_TESTING) add_h3_test(testBaseCells src/apps/testapps/testBaseCells.c) add_h3_test(testPentagonIndexes src/apps/testapps/testPentagonIndexes.c) add_h3_test(testH3Iterators src/apps/testapps/testH3Iterators.c) + add_h3_test(testLow52 src/apps/testapps/testLow52.c) add_h3_test_with_arg(testH3NeighborRotations src/apps/testapps/testH3NeighborRotations.c 0) add_h3_test_with_arg(testH3NeighborRotations src/apps/testapps/testH3NeighborRotations.c 1) diff --git a/src/apps/testapps/testLow52.c b/src/apps/testapps/testLow52.c new file mode 100644 index 000000000..436bfeed4 --- /dev/null +++ b/src/apps/testapps/testLow52.c @@ -0,0 +1,97 @@ +/* + * Copyright 2020-2021 Uber Technologies, Inc. + * + * 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. + */ + +/** @file + * @brief tests H3 cell area functions on a few specific cases + * + * usage: `testH3CellArea` + */ + +#include + +#include "h3Index.h" +#include "test.h" + +static const H3Index h = 0x89283082e73ffff; + +// add empty input tests + +SUITE(low52tests) { + TEST(basic_low52) { + int k = 100; + int N = maxGridDiskSize(k); + int64_t numAfter; + + // create disk + H3Index *out = calloc(N, sizeof(H3Index)); + gridDisk(h, k, out); + + // low 52 tests + t_assert(!isLow52Sorted(out, N), "Shouldn't be sorted yet"); + t_assertSuccess(low52Sort(out, N)); + t_assert(isLow52Sorted(out, N), "Should be sorted now!"); + + // canonical tests + t_assert(isCanonicalCells(out, N), + "No duplicates, so should already be canon."); + t_assertSuccess(canonicalizeCells(out, N, &numAfter)); + t_assert(N == numAfter, "Expect no change"); + + // binary search + t_assert(canonSearch(out, N, h), "Needs to be in there!"); + t_assert(!canonSearch(out, 0, h), "h can't be in empty set."); + + // intersection + t_assert(intersectTheyDo(out, N, out, N), + "Set should intersect itself."); + t_assert(!intersectTheyDo(out, 0, out, N), "A is empty."); + t_assert(!intersectTheyDo(out, N, out, 0), "B is empty."); + t_assert(!intersectTheyDo(out, 0, out, 0), "Both empty."); + + free(out); + } + + TEST(handling_zeroes) { + int k = 100; + int N = maxGridDiskSize(k); + int64_t numAfter; + + // create disk + H3Index *out = calloc(N, sizeof(H3Index)); + gridDisk(h, k, out); + t_assertSuccess(canonicalizeCells(out, N, &numAfter)); + t_assert(N == numAfter, "Expect no change"); + + t_assert(isLow52Sorted(out, N), ""); + t_assert(isCanonicalCells(out, N), ""); + + // insert zero at start of array + // isLow52Sorted is OK with zeros/H3_NULL, but isCanonicalCells is not + out[0] = 0; + + t_assert(isLow52Sorted(out, N), ""); + t_assert(!isCanonicalCells(out, N), + "Should not be canon, due to zero."); + + // canonicalizing should remove the zero! + t_assertSuccess(canonicalizeCells(out, N, &numAfter)); + t_assert(numAfter == N - 1, "Lose one cell."); + + t_assert(isCanonicalCells(out, numAfter), ""); + + free(out); + } +} diff --git a/src/h3lib/lib/low52.c b/src/h3lib/lib/low52.c index 3fd8efaf5..f5b39de09 100644 --- a/src/h3lib/lib/low52.c +++ b/src/h3lib/lib/low52.c @@ -71,6 +71,12 @@ TODO: could maybe speed up by keeping resolution calculation since cells appear in both LHS and RHS */ int isCanonicalCells(const H3Index *cells, const int64_t N) { + for (int64_t i = 0; i < N; i++) { + if (!isValidCell(cells[i])) { + return false; + } + } + // note: returns true if N == 0 or N == 1 for (int64_t i = 1; i < N; i++) { if (cmpCanon(cells[i - 1], cells[i]) == -2) { From 34e6bf285a37d13b69515d9aa31c9d60fba31355 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Fri, 24 Dec 2021 19:42:30 -0800 Subject: [PATCH 16/35] trying some stuff --- src/apps/testapps/testLow52.c | 55 +++++++++++++++++++++++++++++++++-- src/h3lib/include/h3api.h.in | 7 +++-- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/src/apps/testapps/testLow52.c b/src/apps/testapps/testLow52.c index 436bfeed4..9c8c8ea55 100644 --- a/src/apps/testapps/testLow52.c +++ b/src/apps/testapps/testLow52.c @@ -25,12 +25,26 @@ #include "h3Index.h" #include "test.h" -static const H3Index h = 0x89283082e73ffff; +typedef struct { + const H3Index *cells; + int64_t N; +} CellArray; + +CellArray getDisk(H3Index h, int k) { + CellArray arr; + arr.N = maxGridDiskSize(k); + arr.cells = calloc(arr.N, sizeof(H3Index)); + gridDisk(h, k, arr.cells); + + return arr; +} // add empty input tests +// uncompact of a canonical set should give you a canonical set SUITE(low52tests) { TEST(basic_low52) { + H3Index h = 0x89283082e73ffff; int k = 100; int N = maxGridDiskSize(k); int64_t numAfter; @@ -55,8 +69,7 @@ SUITE(low52tests) { t_assert(!canonSearch(out, 0, h), "h can't be in empty set."); // intersection - t_assert(intersectTheyDo(out, N, out, N), - "Set should intersect itself."); + t_assert(intersectTheyDo(out, N, out, N), ""); t_assert(!intersectTheyDo(out, 0, out, N), "A is empty."); t_assert(!intersectTheyDo(out, N, out, 0), "B is empty."); t_assert(!intersectTheyDo(out, 0, out, 0), "Both empty."); @@ -65,6 +78,7 @@ SUITE(low52tests) { } TEST(handling_zeroes) { + H3Index h = 0x89283082e73ffff; int k = 100; int N = maxGridDiskSize(k); int64_t numAfter; @@ -94,4 +108,39 @@ SUITE(low52tests) { free(out); } + + TEST(compact_low52) { + H3Index h = 0x89283082e73ffff; + int res = 9; + int k = 100; + int64_t numU = maxGridDiskSize(k); + + H3Index *cellsU = calloc(numU, sizeof(H3Index)); + gridDisk(h, k, cellsU); + canonicalizeCells(cellsU, numU, &numU); + + int64_t numC = numU; + + H3Index *cellsC = calloc(numC, sizeof(H3Index)); + + compactCells(cellsU, cellsC, numU); + canonicalizeCells(cellsC, numC, &numC); + + t_assert(isCanonicalCells(cellsU, numU), ""); + t_assert(isCanonicalCells(cellsC, numC), ""); + + t_assert(canonSearch(cellsC, numC, h), ""); + t_assert(intersectTheyDo(cellsC, numC, cellsC, numC), ""); + + // TODO: macro or function here would be clearer? + // test that uncompact keeps things canonical + H3Index *newUncompacted = calloc(numU, sizeof(H3Index)); + t_assertSuccess( + uncompactCells(cellsC, numC, newUncompacted, numU, res)); + t_assert(isCanonicalCells(newUncompacted, numU), ""); + + free(cellsU); + free(cellsC); + free(newUncompacted); + } } diff --git a/src/h3lib/include/h3api.h.in b/src/h3lib/include/h3api.h.in index aef174138..25793563a 100644 --- a/src/h3lib/include/h3api.h.in +++ b/src/h3lib/include/h3api.h.in @@ -532,9 +532,10 @@ DECLSPEC H3Index H3_EXPORT(cellToCenterChild)(H3Index h, int childRes); * @{ */ /** @brief compacts the given set of hexagons as best as possible */ -DECLSPEC H3Error H3_EXPORT(compactCells)(const H3Index *h3Set, - H3Index *compactedSet, - const int64_t numHexes); +DECLSPEC H3Error H3_EXPORT(compactCells)( + const H3Index *h3Set, H3Index *compactedSet, + const int64_t + numHexes); // compact should tell you how many cells there are /** @} */ /** @defgroup uncompactCells uncompactCells From 04c853d591b81ce30c85f893dbf7de0a7f79ac83 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Fri, 24 Dec 2021 20:47:41 -0800 Subject: [PATCH 17/35] clean up tests --- src/apps/testapps/testLow52.c | 152 +++++++++++++++++++--------------- 1 file changed, 86 insertions(+), 66 deletions(-) diff --git a/src/apps/testapps/testLow52.c b/src/apps/testapps/testLow52.c index 9c8c8ea55..bca38f9ad 100644 --- a/src/apps/testapps/testLow52.c +++ b/src/apps/testapps/testLow52.c @@ -26,19 +26,53 @@ #include "test.h" typedef struct { - const H3Index *cells; + H3Index *cells; int64_t N; } CellArray; +CellArray initCellArray(int64_t N) { + CellArray a = {.cells = calloc(N, sizeof(H3Index)), .N = N}; + + return a; +} + CellArray getDisk(H3Index h, int k) { - CellArray arr; - arr.N = maxGridDiskSize(k); - arr.cells = calloc(arr.N, sizeof(H3Index)); + CellArray arr = initCellArray(maxGridDiskSize(k)); gridDisk(h, k, arr.cells); return arr; } +void doCanon(CellArray *arr) { + int64_t N; + canonicalizeCells(arr->cells, arr->N, &N); + arr->N = N; +} + +CellArray doCompact(CellArray arr) { + CellArray packed = initCellArray(arr.N); + compactCells(arr.cells, packed.cells, arr.N); + + return packed; +} + +CellArray doUncompact(CellArray arr, int res) { + int64_t N; + uncompactCellsSize(arr.cells, arr.N, res, &N); + + CellArray out = initCellArray(N); + uncompactCells(arr.cells, arr.N, out.cells, out.N, res); + + return out; +} + +bool doIntersect(CellArray A, CellArray B) { + return intersectTheyDo(A.cells, A.N, B.cells, B.N); +} + +bool isLow52(CellArray A) { return isLow52Sorted(A.cells, A.N); } +bool isCanon(CellArray A) { return isCanonicalCells(A.cells, A.N); } + // add empty input tests // uncompact of a canonical set should give you a canonical set @@ -46,101 +80,87 @@ SUITE(low52tests) { TEST(basic_low52) { H3Index h = 0x89283082e73ffff; int k = 100; - int N = maxGridDiskSize(k); - int64_t numAfter; - // create disk - H3Index *out = calloc(N, sizeof(H3Index)); - gridDisk(h, k, out); + CellArray A = getDisk(h, k); // low 52 tests - t_assert(!isLow52Sorted(out, N), "Shouldn't be sorted yet"); - t_assertSuccess(low52Sort(out, N)); - t_assert(isLow52Sorted(out, N), "Should be sorted now!"); + t_assert(!isLow52(A), "Shouldn't be sorted yet"); + t_assertSuccess(low52Sort(A.cells, A.N)); + t_assert(isLow52(A), "Should be sorted now!"); // canonical tests - t_assert(isCanonicalCells(out, N), - "No duplicates, so should already be canon."); - t_assertSuccess(canonicalizeCells(out, N, &numAfter)); - t_assert(N == numAfter, "Expect no change"); + t_assert(isCanon(A), "No duplicates, so should already be canon."); + + int64_t numBefore = A.N; + doCanon(&A); + t_assert(A.N == numBefore, "Expect no change from canonicalizing."); // binary search - t_assert(canonSearch(out, N, h), "Needs to be in there!"); - t_assert(!canonSearch(out, 0, h), "h can't be in empty set."); + t_assert(canonSearch(A.cells, A.N, h), "Needs to be in there!"); + t_assert(!canonSearch(A.cells, 0, h), "h can't be in empty set."); // intersection - t_assert(intersectTheyDo(out, N, out, N), ""); - t_assert(!intersectTheyDo(out, 0, out, N), "A is empty."); - t_assert(!intersectTheyDo(out, N, out, 0), "B is empty."); - t_assert(!intersectTheyDo(out, 0, out, 0), "Both empty."); + CellArray Z = {.N = 0, .cells = NULL}; // empty cell array + t_assert(doIntersect(A, A), ""); + t_assert(!doIntersect(Z, A), "First is empty."); + t_assert(!doIntersect(A, Z), "Second is empty."); + t_assert(!doIntersect(Z, Z), "Both are empty."); - free(out); + free(A.cells); } TEST(handling_zeroes) { H3Index h = 0x89283082e73ffff; int k = 100; - int N = maxGridDiskSize(k); - int64_t numAfter; - // create disk - H3Index *out = calloc(N, sizeof(H3Index)); - gridDisk(h, k, out); - t_assertSuccess(canonicalizeCells(out, N, &numAfter)); - t_assert(N == numAfter, "Expect no change"); + CellArray A = getDisk(h, k); - t_assert(isLow52Sorted(out, N), ""); - t_assert(isCanonicalCells(out, N), ""); + int64_t numBefore = A.N; + doCanon(&A); + t_assert(A.N == numBefore, "Expect no change from canonicalizing."); + + t_assert(isLow52(A), ""); + t_assert(isCanon(A), ""); // insert zero at start of array // isLow52Sorted is OK with zeros/H3_NULL, but isCanonicalCells is not - out[0] = 0; - - t_assert(isLow52Sorted(out, N), ""); - t_assert(!isCanonicalCells(out, N), - "Should not be canon, due to zero."); - - // canonicalizing should remove the zero! - t_assertSuccess(canonicalizeCells(out, N, &numAfter)); - t_assert(numAfter == N - 1, "Lose one cell."); + A.cells[0] = 0; + t_assert(isLow52(A), ""); + t_assert(!isCanon(A), "Should not be canon, due to zero."); - t_assert(isCanonicalCells(out, numAfter), ""); + // canonicalizing again should remove the zero + doCanon(&A); + t_assert(A.N == numBefore - 1, "Lose one cell."); + t_assert(isCanon(A), ""); - free(out); + free(A.cells); } TEST(compact_low52) { H3Index h = 0x89283082e73ffff; int res = 9; int k = 100; - int64_t numU = maxGridDiskSize(k); - H3Index *cellsU = calloc(numU, sizeof(H3Index)); - gridDisk(h, k, cellsU); - canonicalizeCells(cellsU, numU, &numU); + CellArray u = getDisk(h, k); // uncompacted set + doCanon(&u); - int64_t numC = numU; + CellArray c = doCompact(u); // compacted set + doCanon(&c); - H3Index *cellsC = calloc(numC, sizeof(H3Index)); + t_assert(isCanon(u), ""); + t_assert(isCanon(c), ""); - compactCells(cellsU, cellsC, numU); - canonicalizeCells(cellsC, numC, &numC); + t_assert(canonSearch(c.cells, c.N, h), ""); + t_assert(doIntersect(c, c), ""); + t_assert(doIntersect(c, u), ""); + t_assert(doIntersect(u, c), ""); - t_assert(isCanonicalCells(cellsU, numU), ""); - t_assert(isCanonicalCells(cellsC, numC), ""); - - t_assert(canonSearch(cellsC, numC, h), ""); - t_assert(intersectTheyDo(cellsC, numC, cellsC, numC), ""); - - // TODO: macro or function here would be clearer? // test that uncompact keeps things canonical - H3Index *newUncompacted = calloc(numU, sizeof(H3Index)); - t_assertSuccess( - uncompactCells(cellsC, numC, newUncompacted, numU, res)); - t_assert(isCanonicalCells(newUncompacted, numU), ""); - - free(cellsU); - free(cellsC); - free(newUncompacted); + CellArray u2 = doUncompact(c, res); + t_assert(isCanon(u2), ""); + + free(c.cells); + free(u.cells); + free(u2.cells); } } From 0355ce7289214798db60eb45f05c266e49c21273 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Fri, 24 Dec 2021 21:06:17 -0800 Subject: [PATCH 18/35] ring_intersect --- src/apps/testapps/testLow52.c | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/apps/testapps/testLow52.c b/src/apps/testapps/testLow52.c index bca38f9ad..5545f5b70 100644 --- a/src/apps/testapps/testLow52.c +++ b/src/apps/testapps/testLow52.c @@ -43,6 +43,13 @@ CellArray getDisk(H3Index h, int k) { return arr; } +CellArray getRing(H3Index h, int k) { + CellArray A = initCellArray(6 * k); + gridRingUnsafe(h, k, A.cells); + + return A; +} + void doCanon(CellArray *arr) { int64_t N; canonicalizeCells(arr->cells, arr->N, &N); @@ -136,7 +143,7 @@ SUITE(low52tests) { free(A.cells); } - TEST(compact_low52) { + TEST(compact_canon) { H3Index h = 0x89283082e73ffff; int res = 9; int k = 100; @@ -163,4 +170,28 @@ SUITE(low52tests) { free(u.cells); free(u2.cells); } + + TEST(ring_intersect) { + H3Index h = 0x89283082e73ffff; + int k = 10; + + CellArray A = getRing(h, k); + CellArray B = getRing(h, k + 1); + doCanon(&A); + doCanon(&B); + + t_assert(!canonSearch(A.cells, A.N, h), ""); + t_assert(!canonSearch(B.cells, B.N, h), ""); + + t_assert(canonSearch(A.cells, A.N, A.cells[0]), ""); + + t_assert(!doIntersect(A, B), ""); + t_assert(!doIntersect(B, A), ""); + + t_assert(doIntersect(A, A), ""); + t_assert(doIntersect(B, B), ""); + + free(A.cells); + free(B.cells); + } } From 39cb4e07ab40e634ff4436e099bb8f9cbd133745 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Sat, 25 Dec 2021 15:31:49 -0500 Subject: [PATCH 19/35] overlapping disks --- src/apps/testapps/testLow52.c | 143 +++++++++++++++++++++++++++++++++- 1 file changed, 141 insertions(+), 2 deletions(-) diff --git a/src/apps/testapps/testLow52.c b/src/apps/testapps/testLow52.c index 5545f5b70..9a772525f 100644 --- a/src/apps/testapps/testLow52.c +++ b/src/apps/testapps/testLow52.c @@ -15,9 +15,10 @@ */ /** @file - * @brief tests H3 cell area functions on a few specific cases + * @brief tests "lower 52 bit" ordering, canonicalization, and spatial join + * algorithms * - * usage: `testH3CellArea` + * usage: `testLow52` */ #include @@ -191,7 +192,145 @@ SUITE(low52tests) { t_assert(doIntersect(A, A), ""); t_assert(doIntersect(B, B), ""); + // add a cell from A to B, so they now intersect + B.cells[B.N / 2] = A.cells[A.N / 2]; + doCanon(&B); + t_assert(doIntersect(A, B), ""); + t_assert(doIntersect(B, A), ""); + + free(A.cells); + free(B.cells); + } + + TEST(overlap_test_not_compated) { + H3Index a = 0x89283082e73ffff; + H3Index b = 0x89283095063ffff; + CellArray A, B; + + int64_t k; + t_assertSuccess(gridDistance(a, b, &k)); + t_assert(k == 20, ""); + + // not yet + A = getDisk(a, 9); + B = getDisk(b, 9); + doCanon(&A); + doCanon(&B); + + t_assert(!doIntersect(A, B), ""); + t_assert(!doIntersect(B, A), ""); + + free(A.cells); + free(B.cells); + + // overlap + A = getDisk(a, 10); + B = getDisk(b, 10); + doCanon(&A); + doCanon(&B); + + t_assert(doIntersect(A, B), ""); + t_assert(doIntersect(B, A), ""); + + free(A.cells); + free(B.cells); + + // more overlap + A = getDisk(a, 11); + B = getDisk(b, 11); + doCanon(&A); + doCanon(&B); + + t_assert(doIntersect(A, B), ""); + t_assert(doIntersect(B, A), ""); + + free(A.cells); + free(B.cells); + + // just barely disjoint + A = getDisk(a, 9); + B = getDisk(b, 10); + doCanon(&A); + doCanon(&B); + + t_assert(!doIntersect(A, B), ""); + t_assert(!doIntersect(B, A), ""); + + free(A.cells); + free(B.cells); + } + + TEST(overlap_test_compated) { + H3Index a = 0x89283082e73ffff; + H3Index b = 0x89283095063ffff; + CellArray A, B, cA, cB; + + int64_t k; + t_assertSuccess(gridDistance(a, b, &k)); + t_assert(k == 20, ""); + + // not yet + A = getDisk(a, 9); + B = getDisk(b, 9); + cA = doCompact(A); + cB = doCompact(B); + doCanon(&cA); + doCanon(&cB); + + t_assert(!doIntersect(cA, cB), ""); + t_assert(!doIntersect(cB, cA), ""); + + free(A.cells); + free(B.cells); + free(cA.cells); + free(cB.cells); + + // overlap + A = getDisk(a, 10); + B = getDisk(b, 10); + cA = doCompact(A); + cB = doCompact(B); + doCanon(&cA); + doCanon(&cB); + + t_assert(doIntersect(cA, cB), ""); + t_assert(doIntersect(cB, cA), ""); + + free(A.cells); + free(B.cells); + free(cA.cells); + free(cB.cells); + + // more overlap + A = getDisk(a, 11); + B = getDisk(b, 11); + cA = doCompact(A); + cB = doCompact(B); + doCanon(&cA); + doCanon(&cB); + + t_assert(doIntersect(cA, cB), ""); + t_assert(doIntersect(cB, cA), ""); + + free(A.cells); + free(B.cells); + free(cA.cells); + free(cB.cells); + + // just barely disjoint + A = getDisk(a, 9); + B = getDisk(b, 10); + cA = doCompact(A); + cB = doCompact(B); + doCanon(&cA); + doCanon(&cB); + + t_assert(!doIntersect(cA, cB), ""); + t_assert(!doIntersect(cB, cA), ""); + free(A.cells); free(B.cells); + free(cA.cells); + free(cB.cells); } } From fc2da34ce5c0175dd9bc77366e2675026498c781 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Sat, 25 Dec 2021 15:38:36 -0500 Subject: [PATCH 20/35] h3api.h --- src/apps/testapps/testLow52.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apps/testapps/testLow52.c b/src/apps/testapps/testLow52.c index 9a772525f..b25038a82 100644 --- a/src/apps/testapps/testLow52.c +++ b/src/apps/testapps/testLow52.c @@ -23,7 +23,7 @@ #include -#include "h3Index.h" +#include "h3api.h" #include "test.h" typedef struct { From bec6d6b2b86c72cd4f4ed706229c596047ee4d8d Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Sat, 25 Dec 2021 15:44:31 -0500 Subject: [PATCH 21/35] oops, wrong one --- src/h3lib/lib/low52.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/h3lib/lib/low52.c b/src/h3lib/lib/low52.c index f5b39de09..f20089fce 100644 --- a/src/h3lib/lib/low52.c +++ b/src/h3lib/lib/low52.c @@ -3,6 +3,7 @@ #include #include "h3Index.h" +#include "h3api.h" #include "mathExtensions.h" #define HIGH_BITS(h, t) ((h) >> (64 - (t))) From 80764744376632e4c9addffd1a78da69851de73f Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Sat, 25 Dec 2021 16:03:25 -0500 Subject: [PATCH 22/35] try again --- src/h3lib/lib/low52.c | 1 - 1 file changed, 1 deletion(-) diff --git a/src/h3lib/lib/low52.c b/src/h3lib/lib/low52.c index f20089fce..231e71b34 100644 --- a/src/h3lib/lib/low52.c +++ b/src/h3lib/lib/low52.c @@ -2,7 +2,6 @@ #include #include -#include "h3Index.h" #include "h3api.h" #include "mathExtensions.h" From 0e810f337d9daddfa568872031d0a89fd5fbddb8 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Sat, 25 Dec 2021 16:14:47 -0500 Subject: [PATCH 23/35] H3_EXPORT might be the trick --- src/h3lib/lib/low52.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/h3lib/lib/low52.c b/src/h3lib/lib/low52.c index 231e71b34..7400d9c96 100644 --- a/src/h3lib/lib/low52.c +++ b/src/h3lib/lib/low52.c @@ -72,7 +72,7 @@ appear in both LHS and RHS */ int isCanonicalCells(const H3Index *cells, const int64_t N) { for (int64_t i = 0; i < N; i++) { - if (!isValidCell(cells[i])) { + if (!H3_EXPORT(isValidCell)(cells[i])) { return false; } } From 5a6c581e8d81a108da36ffd6372c5856791d45a7 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Sat, 25 Dec 2021 16:21:27 -0500 Subject: [PATCH 24/35] H3_EXPORT all the things --- src/apps/testapps/testLow52.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/apps/testapps/testLow52.c b/src/apps/testapps/testLow52.c index b25038a82..fbfee23ec 100644 --- a/src/apps/testapps/testLow52.c +++ b/src/apps/testapps/testLow52.c @@ -38,15 +38,15 @@ CellArray initCellArray(int64_t N) { } CellArray getDisk(H3Index h, int k) { - CellArray arr = initCellArray(maxGridDiskSize(k)); - gridDisk(h, k, arr.cells); + CellArray arr = initCellArray(H3_EXPORT(maxGridDiskSize)(k)); + H3_EXPORT(gridDisk)(h, k, arr.cells); return arr; } CellArray getRing(H3Index h, int k) { CellArray A = initCellArray(6 * k); - gridRingUnsafe(h, k, A.cells); + H3_EXPORT(gridRingUnsafe)(h, k, A.cells); return A; } @@ -59,17 +59,17 @@ void doCanon(CellArray *arr) { CellArray doCompact(CellArray arr) { CellArray packed = initCellArray(arr.N); - compactCells(arr.cells, packed.cells, arr.N); + H3_EXPORT(compactCells)(arr.cells, packed.cells, arr.N); return packed; } CellArray doUncompact(CellArray arr, int res) { int64_t N; - uncompactCellsSize(arr.cells, arr.N, res, &N); + H3_EXPORT(uncompactCellsSize)(arr.cells, arr.N, res, &N); CellArray out = initCellArray(N); - uncompactCells(arr.cells, arr.N, out.cells, out.N, res); + H3_EXPORT(uncompactCells)(arr.cells, arr.N, out.cells, out.N, res); return out; } From 948d2e89a6304ceb2836cf922318b4907b9a6ce1 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Sat, 25 Dec 2021 16:23:51 -0500 Subject: [PATCH 25/35] one last straggler --- src/apps/testapps/testLow52.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apps/testapps/testLow52.c b/src/apps/testapps/testLow52.c index fbfee23ec..345dc5992 100644 --- a/src/apps/testapps/testLow52.c +++ b/src/apps/testapps/testLow52.c @@ -208,7 +208,7 @@ SUITE(low52tests) { CellArray A, B; int64_t k; - t_assertSuccess(gridDistance(a, b, &k)); + t_assertSuccess(H3_EXPORT(gridDistance)(a, b, &k)); t_assert(k == 20, ""); // not yet @@ -266,7 +266,7 @@ SUITE(low52tests) { CellArray A, B, cA, cB; int64_t k; - t_assertSuccess(gridDistance(a, b, &k)); + t_assertSuccess(H3_EXPORT(gridDistance)(a, b, &k)); t_assert(k == 20, ""); // not yet From 201c6c1458cb24e8fb2cfb1552437a820f400e85 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Sat, 25 Dec 2021 17:28:28 -0500 Subject: [PATCH 26/35] some clean up --- src/apps/testapps/testLow52.c | 159 ++++++++++------------------------ 1 file changed, 45 insertions(+), 114 deletions(-) diff --git a/src/apps/testapps/testLow52.c b/src/apps/testapps/testLow52.c index 345dc5992..0ac827030 100644 --- a/src/apps/testapps/testLow52.c +++ b/src/apps/testapps/testLow52.c @@ -81,6 +81,41 @@ bool doIntersect(CellArray A, CellArray B) { bool isLow52(CellArray A) { return isLow52Sorted(A.cells, A.N); } bool isCanon(CellArray A) { return isCanonicalCells(A.cells, A.N); } +void diskIntersect(H3Index a, H3Index b, int ka, int kb, bool shouldIntersect) { + CellArray A = getDisk(a, ka); + CellArray B = getDisk(b, kb); + + doCanon(&A); + doCanon(&B); + + t_assert(shouldIntersect == doIntersect(A, B), ""); + t_assert(shouldIntersect == doIntersect(B, A), ""); + + free(A.cells); + free(B.cells); +} + +void diskIntersectCompact(H3Index a, H3Index b, int ka, int kb, + bool shouldIntersect) { + CellArray A, B, cA, cB; + + A = getDisk(a, ka); + B = getDisk(b, kb); + + cA = doCompact(A); + cB = doCompact(B); + doCanon(&cA); + doCanon(&cB); + + t_assert(shouldIntersect == doIntersect(cA, cB), ""); + t_assert(shouldIntersect == doIntersect(cB, cA), ""); + + free(A.cells); + free(B.cells); + free(cA.cells); + free(cB.cells); +} + // add empty input tests // uncompact of a canonical set should give you a canonical set @@ -202,135 +237,31 @@ SUITE(low52tests) { free(B.cells); } - TEST(overlap_test_not_compated) { + TEST(overlap_test_not_compacted) { H3Index a = 0x89283082e73ffff; H3Index b = 0x89283095063ffff; - CellArray A, B; int64_t k; t_assertSuccess(H3_EXPORT(gridDistance)(a, b, &k)); t_assert(k == 20, ""); - // not yet - A = getDisk(a, 9); - B = getDisk(b, 9); - doCanon(&A); - doCanon(&B); - - t_assert(!doIntersect(A, B), ""); - t_assert(!doIntersect(B, A), ""); - - free(A.cells); - free(B.cells); - - // overlap - A = getDisk(a, 10); - B = getDisk(b, 10); - doCanon(&A); - doCanon(&B); - - t_assert(doIntersect(A, B), ""); - t_assert(doIntersect(B, A), ""); - - free(A.cells); - free(B.cells); - - // more overlap - A = getDisk(a, 11); - B = getDisk(b, 11); - doCanon(&A); - doCanon(&B); - - t_assert(doIntersect(A, B), ""); - t_assert(doIntersect(B, A), ""); - - free(A.cells); - free(B.cells); - - // just barely disjoint - A = getDisk(a, 9); - B = getDisk(b, 10); - doCanon(&A); - doCanon(&B); - - t_assert(!doIntersect(A, B), ""); - t_assert(!doIntersect(B, A), ""); - - free(A.cells); - free(B.cells); + diskIntersect(a, b, 9, 9, false); // not yet + diskIntersect(a, b, 9, 10, false); // just barely disjoint + diskIntersect(a, b, 10, 10, true); // overlap + diskIntersect(a, b, 11, 11, true); // more overlap } - TEST(overlap_test_compated) { + TEST(overlap_test_compacted) { H3Index a = 0x89283082e73ffff; H3Index b = 0x89283095063ffff; - CellArray A, B, cA, cB; int64_t k; t_assertSuccess(H3_EXPORT(gridDistance)(a, b, &k)); t_assert(k == 20, ""); - // not yet - A = getDisk(a, 9); - B = getDisk(b, 9); - cA = doCompact(A); - cB = doCompact(B); - doCanon(&cA); - doCanon(&cB); - - t_assert(!doIntersect(cA, cB), ""); - t_assert(!doIntersect(cB, cA), ""); - - free(A.cells); - free(B.cells); - free(cA.cells); - free(cB.cells); - - // overlap - A = getDisk(a, 10); - B = getDisk(b, 10); - cA = doCompact(A); - cB = doCompact(B); - doCanon(&cA); - doCanon(&cB); - - t_assert(doIntersect(cA, cB), ""); - t_assert(doIntersect(cB, cA), ""); - - free(A.cells); - free(B.cells); - free(cA.cells); - free(cB.cells); - - // more overlap - A = getDisk(a, 11); - B = getDisk(b, 11); - cA = doCompact(A); - cB = doCompact(B); - doCanon(&cA); - doCanon(&cB); - - t_assert(doIntersect(cA, cB), ""); - t_assert(doIntersect(cB, cA), ""); - - free(A.cells); - free(B.cells); - free(cA.cells); - free(cB.cells); - - // just barely disjoint - A = getDisk(a, 9); - B = getDisk(b, 10); - cA = doCompact(A); - cB = doCompact(B); - doCanon(&cA); - doCanon(&cB); - - t_assert(!doIntersect(cA, cB), ""); - t_assert(!doIntersect(cB, cA), ""); - - free(A.cells); - free(B.cells); - free(cA.cells); - free(cB.cells); + diskIntersectCompact(a, b, 9, 9, false); // not yet + diskIntersectCompact(a, b, 9, 10, false); // just barely disjoint + diskIntersectCompact(a, b, 10, 10, true); // overlap + diskIntersectCompact(a, b, 11, 11, true); // more overlap } } From 24f6e2bd2ecafc60a4fd8604dcce2105eadfb8e2 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Sat, 25 Dec 2021 17:37:34 -0500 Subject: [PATCH 27/35] cleaner --- src/apps/testapps/testLow52.c | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/apps/testapps/testLow52.c b/src/apps/testapps/testLow52.c index 0ac827030..50d85c549 100644 --- a/src/apps/testapps/testLow52.c +++ b/src/apps/testapps/testLow52.c @@ -237,7 +237,7 @@ SUITE(low52tests) { free(B.cells); } - TEST(overlap_test_not_compacted) { + TEST(disk_overlap) { H3Index a = 0x89283082e73ffff; H3Index b = 0x89283095063ffff; @@ -245,20 +245,13 @@ SUITE(low52tests) { t_assertSuccess(H3_EXPORT(gridDistance)(a, b, &k)); t_assert(k == 20, ""); + // not compacted diskIntersect(a, b, 9, 9, false); // not yet diskIntersect(a, b, 9, 10, false); // just barely disjoint diskIntersect(a, b, 10, 10, true); // overlap diskIntersect(a, b, 11, 11, true); // more overlap - } - - TEST(overlap_test_compacted) { - H3Index a = 0x89283082e73ffff; - H3Index b = 0x89283095063ffff; - - int64_t k; - t_assertSuccess(H3_EXPORT(gridDistance)(a, b, &k)); - t_assert(k == 20, ""); + // compacted diskIntersectCompact(a, b, 9, 9, false); // not yet diskIntersectCompact(a, b, 9, 10, false); // just barely disjoint diskIntersectCompact(a, b, 10, 10, true); // overlap From 26d02dc1d5adbc0cbb89ec216a87f6822b3529d4 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Sat, 25 Dec 2021 17:51:42 -0500 Subject: [PATCH 28/35] trying out t_isLow52 and t_isCanon --- src/apps/testapps/testLow52.c | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/src/apps/testapps/testLow52.c b/src/apps/testapps/testLow52.c index 50d85c549..f487ac80e 100644 --- a/src/apps/testapps/testLow52.c +++ b/src/apps/testapps/testLow52.c @@ -78,8 +78,12 @@ bool doIntersect(CellArray A, CellArray B) { return intersectTheyDo(A.cells, A.N, B.cells, B.N); } -bool isLow52(CellArray A) { return isLow52Sorted(A.cells, A.N); } -bool isCanon(CellArray A) { return isCanonicalCells(A.cells, A.N); } +void t_isLow52(CellArray A, bool result) { + t_assert(result == isLow52Sorted(A.cells, A.N), ""); +} +void t_isCanon(CellArray A, bool result) { + t_assert(result == isCanonicalCells(A.cells, A.N), ""); +} void diskIntersect(H3Index a, H3Index b, int ka, int kb, bool shouldIntersect) { CellArray A = getDisk(a, ka); @@ -127,13 +131,12 @@ SUITE(low52tests) { CellArray A = getDisk(h, k); // low 52 tests - t_assert(!isLow52(A), "Shouldn't be sorted yet"); + t_isLow52(A, false); // shouldn't be sorted yet t_assertSuccess(low52Sort(A.cells, A.N)); - t_assert(isLow52(A), "Should be sorted now!"); + t_isLow52(A, true); // should be sorted now! // canonical tests - t_assert(isCanon(A), "No duplicates, so should already be canon."); - + t_isCanon(A, true); // No duplicates, so should already be canon int64_t numBefore = A.N; doCanon(&A); t_assert(A.N == numBefore, "Expect no change from canonicalizing."); @@ -162,19 +165,19 @@ SUITE(low52tests) { doCanon(&A); t_assert(A.N == numBefore, "Expect no change from canonicalizing."); - t_assert(isLow52(A), ""); - t_assert(isCanon(A), ""); + t_isLow52(A, true); + t_isCanon(A, true); // insert zero at start of array // isLow52Sorted is OK with zeros/H3_NULL, but isCanonicalCells is not A.cells[0] = 0; - t_assert(isLow52(A), ""); - t_assert(!isCanon(A), "Should not be canon, due to zero."); + t_isLow52(A, true); + t_isCanon(A, false); // canonicalizing again should remove the zero doCanon(&A); t_assert(A.N == numBefore - 1, "Lose one cell."); - t_assert(isCanon(A), ""); + t_isCanon(A, true); free(A.cells); } @@ -190,8 +193,8 @@ SUITE(low52tests) { CellArray c = doCompact(u); // compacted set doCanon(&c); - t_assert(isCanon(u), ""); - t_assert(isCanon(c), ""); + t_isCanon(u, true); + t_isCanon(c, true); t_assert(canonSearch(c.cells, c.N, h), ""); t_assert(doIntersect(c, c), ""); @@ -200,7 +203,7 @@ SUITE(low52tests) { // test that uncompact keeps things canonical CellArray u2 = doUncompact(c, res); - t_assert(isCanon(u2), ""); + t_isCanon(u2, true); free(c.cells); free(u.cells); From a9e1777ef9318e0f924ba5e4c128aadfa979b9b2 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Sat, 25 Dec 2021 17:59:58 -0500 Subject: [PATCH 29/35] t_intersect --- src/apps/testapps/testLow52.c | 40 +++++++++++++++++------------------ 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/apps/testapps/testLow52.c b/src/apps/testapps/testLow52.c index f487ac80e..cdfdbdb85 100644 --- a/src/apps/testapps/testLow52.c +++ b/src/apps/testapps/testLow52.c @@ -74,8 +74,8 @@ CellArray doUncompact(CellArray arr, int res) { return out; } -bool doIntersect(CellArray A, CellArray B) { - return intersectTheyDo(A.cells, A.N, B.cells, B.N); +void t_intersect(CellArray A, CellArray B, bool result) { + t_assert(result == intersectTheyDo(A.cells, A.N, B.cells, B.N), ""); } void t_isLow52(CellArray A, bool result) { @@ -92,8 +92,8 @@ void diskIntersect(H3Index a, H3Index b, int ka, int kb, bool shouldIntersect) { doCanon(&A); doCanon(&B); - t_assert(shouldIntersect == doIntersect(A, B), ""); - t_assert(shouldIntersect == doIntersect(B, A), ""); + t_intersect(A, B, shouldIntersect); + t_intersect(B, A, shouldIntersect); free(A.cells); free(B.cells); @@ -111,8 +111,8 @@ void diskIntersectCompact(H3Index a, H3Index b, int ka, int kb, doCanon(&cA); doCanon(&cB); - t_assert(shouldIntersect == doIntersect(cA, cB), ""); - t_assert(shouldIntersect == doIntersect(cB, cA), ""); + t_intersect(cA, cB, shouldIntersect); + t_intersect(cB, cA, shouldIntersect); free(A.cells); free(B.cells); @@ -147,10 +147,11 @@ SUITE(low52tests) { // intersection CellArray Z = {.N = 0, .cells = NULL}; // empty cell array - t_assert(doIntersect(A, A), ""); - t_assert(!doIntersect(Z, A), "First is empty."); - t_assert(!doIntersect(A, Z), "Second is empty."); - t_assert(!doIntersect(Z, Z), "Both are empty."); + + t_intersect(A, A, true); + t_intersect(Z, A, false); // first is empty + t_intersect(A, Z, false); // second is empty + t_intersect(Z, Z, false); // both are empty free(A.cells); } @@ -197,9 +198,9 @@ SUITE(low52tests) { t_isCanon(c, true); t_assert(canonSearch(c.cells, c.N, h), ""); - t_assert(doIntersect(c, c), ""); - t_assert(doIntersect(c, u), ""); - t_assert(doIntersect(u, c), ""); + t_intersect(c, c, true); + t_intersect(c, u, true); + t_intersect(u, c, true); // test that uncompact keeps things canonical CellArray u2 = doUncompact(c, res); @@ -224,17 +225,16 @@ SUITE(low52tests) { t_assert(canonSearch(A.cells, A.N, A.cells[0]), ""); - t_assert(!doIntersect(A, B), ""); - t_assert(!doIntersect(B, A), ""); - - t_assert(doIntersect(A, A), ""); - t_assert(doIntersect(B, B), ""); + t_intersect(A, B, false); + t_intersect(B, A, false); + t_intersect(A, A, true); + t_intersect(B, B, true); // add a cell from A to B, so they now intersect B.cells[B.N / 2] = A.cells[A.N / 2]; doCanon(&B); - t_assert(doIntersect(A, B), ""); - t_assert(doIntersect(B, A), ""); + t_intersect(A, B, true); + t_intersect(A, B, true); free(A.cells); free(B.cells); From 3eed379d6e909eeb55ed9ea7ed445872ca46e59d Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Sat, 25 Dec 2021 18:21:56 -0500 Subject: [PATCH 30/35] clean up helper functions --- src/apps/testapps/testLow52.c | 120 +++++++++++++++++----------------- 1 file changed, 59 insertions(+), 61 deletions(-) diff --git a/src/apps/testapps/testLow52.c b/src/apps/testapps/testLow52.c index cdfdbdb85..57edb1c9f 100644 --- a/src/apps/testapps/testLow52.c +++ b/src/apps/testapps/testLow52.c @@ -21,8 +21,6 @@ * usage: `testLow52` */ -#include - #include "h3api.h" #include "test.h" @@ -31,44 +29,44 @@ typedef struct { int64_t N; } CellArray; -CellArray initCellArray(int64_t N) { +CellArray ca_init(int64_t N) { CellArray a = {.cells = calloc(N, sizeof(H3Index)), .N = N}; return a; } -CellArray getDisk(H3Index h, int k) { - CellArray arr = initCellArray(H3_EXPORT(maxGridDiskSize)(k)); +CellArray ca_disk(H3Index h, int k) { + CellArray arr = ca_init(H3_EXPORT(maxGridDiskSize)(k)); H3_EXPORT(gridDisk)(h, k, arr.cells); return arr; } -CellArray getRing(H3Index h, int k) { - CellArray A = initCellArray(6 * k); +CellArray ca_ring(H3Index h, int k) { + CellArray A = ca_init(6 * k); H3_EXPORT(gridRingUnsafe)(h, k, A.cells); return A; } -void doCanon(CellArray *arr) { +void ca_canon(CellArray *arr) { int64_t N; canonicalizeCells(arr->cells, arr->N, &N); arr->N = N; } -CellArray doCompact(CellArray arr) { - CellArray packed = initCellArray(arr.N); +CellArray ca_compact(CellArray arr) { + CellArray packed = ca_init(arr.N); H3_EXPORT(compactCells)(arr.cells, packed.cells, arr.N); return packed; } -CellArray doUncompact(CellArray arr, int res) { +CellArray ca_uncompact(CellArray arr, int res) { int64_t N; H3_EXPORT(uncompactCellsSize)(arr.cells, arr.N, res, &N); - CellArray out = initCellArray(N); + CellArray out = ca_init(N); H3_EXPORT(uncompactCells)(arr.cells, arr.N, out.cells, out.N, res); return out; @@ -78,6 +76,10 @@ void t_intersect(CellArray A, CellArray B, bool result) { t_assert(result == intersectTheyDo(A.cells, A.N, B.cells, B.N), ""); } +void t_contains(CellArray A, H3Index h, bool result) { + t_assert(result == canonSearch(A.cells, A.N, h), ""); +} + void t_isLow52(CellArray A, bool result) { t_assert(result == isLow52Sorted(A.cells, A.N), ""); } @@ -85,12 +87,13 @@ void t_isCanon(CellArray A, bool result) { t_assert(result == isCanonicalCells(A.cells, A.N), ""); } -void diskIntersect(H3Index a, H3Index b, int ka, int kb, bool shouldIntersect) { - CellArray A = getDisk(a, ka); - CellArray B = getDisk(b, kb); +void t_diskIntersect(H3Index a, H3Index b, int ka, int kb, + bool shouldIntersect) { + CellArray A = ca_disk(a, ka); + CellArray B = ca_disk(b, kb); - doCanon(&A); - doCanon(&B); + ca_canon(&A); + ca_canon(&B); t_intersect(A, B, shouldIntersect); t_intersect(B, A, shouldIntersect); @@ -99,17 +102,17 @@ void diskIntersect(H3Index a, H3Index b, int ka, int kb, bool shouldIntersect) { free(B.cells); } -void diskIntersectCompact(H3Index a, H3Index b, int ka, int kb, - bool shouldIntersect) { +void t_diskIntersectCompact(H3Index a, H3Index b, int ka, int kb, + bool shouldIntersect) { CellArray A, B, cA, cB; - A = getDisk(a, ka); - B = getDisk(b, kb); + A = ca_disk(a, ka); + B = ca_disk(b, kb); - cA = doCompact(A); - cB = doCompact(B); - doCanon(&cA); - doCanon(&cB); + cA = ca_compact(A); + cB = ca_compact(B); + ca_canon(&cA); + ca_canon(&cB); t_intersect(cA, cB, shouldIntersect); t_intersect(cB, cA, shouldIntersect); @@ -120,15 +123,13 @@ void diskIntersectCompact(H3Index a, H3Index b, int ka, int kb, free(cB.cells); } -// add empty input tests -// uncompact of a canonical set should give you a canonical set - SUITE(low52tests) { TEST(basic_low52) { H3Index h = 0x89283082e73ffff; int k = 100; - CellArray A = getDisk(h, k); + CellArray A = ca_disk(h, k); + CellArray Z = {.N = 0, .cells = NULL}; // empty cell array // low 52 tests t_isLow52(A, false); // shouldn't be sorted yet @@ -136,18 +137,16 @@ SUITE(low52tests) { t_isLow52(A, true); // should be sorted now! // canonical tests - t_isCanon(A, true); // No duplicates, so should already be canon + t_isCanon(A, true); // no duplicates, so should already be canon int64_t numBefore = A.N; - doCanon(&A); + ca_canon(&A); t_assert(A.N == numBefore, "Expect no change from canonicalizing."); // binary search - t_assert(canonSearch(A.cells, A.N, h), "Needs to be in there!"); - t_assert(!canonSearch(A.cells, 0, h), "h can't be in empty set."); + t_contains(A, h, true); // Needs to be in there! + t_contains(Z, h, false); // h can't be in an empty set // intersection - CellArray Z = {.N = 0, .cells = NULL}; // empty cell array - t_intersect(A, A, true); t_intersect(Z, A, false); // first is empty t_intersect(A, Z, false); // second is empty @@ -160,10 +159,10 @@ SUITE(low52tests) { H3Index h = 0x89283082e73ffff; int k = 100; - CellArray A = getDisk(h, k); + CellArray A = ca_disk(h, k); int64_t numBefore = A.N; - doCanon(&A); + ca_canon(&A); t_assert(A.N == numBefore, "Expect no change from canonicalizing."); t_isLow52(A, true); @@ -176,7 +175,7 @@ SUITE(low52tests) { t_isCanon(A, false); // canonicalizing again should remove the zero - doCanon(&A); + ca_canon(&A); t_assert(A.N == numBefore - 1, "Lose one cell."); t_isCanon(A, true); @@ -188,22 +187,22 @@ SUITE(low52tests) { int res = 9; int k = 100; - CellArray u = getDisk(h, k); // uncompacted set - doCanon(&u); + CellArray u = ca_disk(h, k); // uncompacted set + ca_canon(&u); - CellArray c = doCompact(u); // compacted set - doCanon(&c); + CellArray c = ca_compact(u); // compacted set + ca_canon(&c); t_isCanon(u, true); t_isCanon(c, true); - t_assert(canonSearch(c.cells, c.N, h), ""); + t_contains(c, h, true); t_intersect(c, c, true); t_intersect(c, u, true); t_intersect(u, c, true); // test that uncompact keeps things canonical - CellArray u2 = doUncompact(c, res); + CellArray u2 = ca_uncompact(c, res); t_isCanon(u2, true); free(c.cells); @@ -215,15 +214,14 @@ SUITE(low52tests) { H3Index h = 0x89283082e73ffff; int k = 10; - CellArray A = getRing(h, k); - CellArray B = getRing(h, k + 1); - doCanon(&A); - doCanon(&B); - - t_assert(!canonSearch(A.cells, A.N, h), ""); - t_assert(!canonSearch(B.cells, B.N, h), ""); + CellArray A = ca_ring(h, k); + CellArray B = ca_ring(h, k + 1); + ca_canon(&A); + ca_canon(&B); - t_assert(canonSearch(A.cells, A.N, A.cells[0]), ""); + t_contains(A, h, false); + t_contains(B, h, false); + t_contains(A, A.cells[0], true); t_intersect(A, B, false); t_intersect(B, A, false); @@ -232,7 +230,7 @@ SUITE(low52tests) { // add a cell from A to B, so they now intersect B.cells[B.N / 2] = A.cells[A.N / 2]; - doCanon(&B); + ca_canon(&B); t_intersect(A, B, true); t_intersect(A, B, true); @@ -249,15 +247,15 @@ SUITE(low52tests) { t_assert(k == 20, ""); // not compacted - diskIntersect(a, b, 9, 9, false); // not yet - diskIntersect(a, b, 9, 10, false); // just barely disjoint - diskIntersect(a, b, 10, 10, true); // overlap - diskIntersect(a, b, 11, 11, true); // more overlap + t_diskIntersect(a, b, 9, 9, false); // not yet + t_diskIntersect(a, b, 9, 10, false); // just barely disjoint + t_diskIntersect(a, b, 10, 10, true); // overlap + t_diskIntersect(a, b, 11, 11, true); // more overlap // compacted - diskIntersectCompact(a, b, 9, 9, false); // not yet - diskIntersectCompact(a, b, 9, 10, false); // just barely disjoint - diskIntersectCompact(a, b, 10, 10, true); // overlap - diskIntersectCompact(a, b, 11, 11, true); // more overlap + t_diskIntersectCompact(a, b, 9, 9, false); // not yet + t_diskIntersectCompact(a, b, 9, 10, false); // just barely disjoint + t_diskIntersectCompact(a, b, 10, 10, true); // overlap + t_diskIntersectCompact(a, b, 11, 11, true); // more overlap } } From d624f97b84f06d6145272ef8e5fcd8dbe750f11e Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Sat, 25 Dec 2021 18:29:56 -0500 Subject: [PATCH 31/35] sets use capital letters --- src/apps/testapps/testLow52.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/apps/testapps/testLow52.c b/src/apps/testapps/testLow52.c index 57edb1c9f..a76529313 100644 --- a/src/apps/testapps/testLow52.c +++ b/src/apps/testapps/testLow52.c @@ -187,27 +187,27 @@ SUITE(low52tests) { int res = 9; int k = 100; - CellArray u = ca_disk(h, k); // uncompacted set - ca_canon(&u); + CellArray U = ca_disk(h, k); // uncompacted set + ca_canon(&U); - CellArray c = ca_compact(u); // compacted set - ca_canon(&c); + CellArray C = ca_compact(U); // compacted set + ca_canon(&C); - t_isCanon(u, true); - t_isCanon(c, true); + t_isCanon(U, true); + t_isCanon(C, true); - t_contains(c, h, true); - t_intersect(c, c, true); - t_intersect(c, u, true); - t_intersect(u, c, true); + t_contains(C, h, true); + t_intersect(C, C, true); + t_intersect(C, U, true); + t_intersect(U, C, true); // test that uncompact keeps things canonical - CellArray u2 = ca_uncompact(c, res); - t_isCanon(u2, true); + CellArray U2 = ca_uncompact(C, res); + t_isCanon(U2, true); - free(c.cells); - free(u.cells); - free(u2.cells); + free(C.cells); + free(U.cells); + free(U2.cells); } TEST(ring_intersect) { From 54758312b27b858b8105501a95eede72ecd94068 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Sat, 25 Dec 2021 19:55:30 -0500 Subject: [PATCH 32/35] t_intersects --- src/apps/testapps/testLow52.c | 49 ++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/src/apps/testapps/testLow52.c b/src/apps/testapps/testLow52.c index a76529313..dc50747e5 100644 --- a/src/apps/testapps/testLow52.c +++ b/src/apps/testapps/testLow52.c @@ -37,42 +37,43 @@ CellArray ca_init(int64_t N) { CellArray ca_disk(H3Index h, int k) { CellArray arr = ca_init(H3_EXPORT(maxGridDiskSize)(k)); - H3_EXPORT(gridDisk)(h, k, arr.cells); + t_assertSuccess(H3_EXPORT(gridDisk)(h, k, arr.cells)); return arr; } CellArray ca_ring(H3Index h, int k) { CellArray A = ca_init(6 * k); - H3_EXPORT(gridRingUnsafe)(h, k, A.cells); + t_assertSuccess(H3_EXPORT(gridRingUnsafe)(h, k, A.cells)); return A; } void ca_canon(CellArray *arr) { int64_t N; - canonicalizeCells(arr->cells, arr->N, &N); + t_assertSuccess(canonicalizeCells(arr->cells, arr->N, &N)); arr->N = N; } CellArray ca_compact(CellArray arr) { CellArray packed = ca_init(arr.N); - H3_EXPORT(compactCells)(arr.cells, packed.cells, arr.N); + t_assertSuccess(H3_EXPORT(compactCells)(arr.cells, packed.cells, arr.N)); return packed; } CellArray ca_uncompact(CellArray arr, int res) { int64_t N; - H3_EXPORT(uncompactCellsSize)(arr.cells, arr.N, res, &N); + t_assertSuccess(H3_EXPORT(uncompactCellsSize)(arr.cells, arr.N, res, &N)); CellArray out = ca_init(N); - H3_EXPORT(uncompactCells)(arr.cells, arr.N, out.cells, out.N, res); + t_assertSuccess( + H3_EXPORT(uncompactCells)(arr.cells, arr.N, out.cells, out.N, res)); return out; } -void t_intersect(CellArray A, CellArray B, bool result) { +void t_intersects(CellArray A, CellArray B, bool result) { t_assert(result == intersectTheyDo(A.cells, A.N, B.cells, B.N), ""); } @@ -95,8 +96,8 @@ void t_diskIntersect(H3Index a, H3Index b, int ka, int kb, ca_canon(&A); ca_canon(&B); - t_intersect(A, B, shouldIntersect); - t_intersect(B, A, shouldIntersect); + t_intersects(A, B, shouldIntersect); + t_intersects(B, A, shouldIntersect); free(A.cells); free(B.cells); @@ -114,8 +115,8 @@ void t_diskIntersectCompact(H3Index a, H3Index b, int ka, int kb, ca_canon(&cA); ca_canon(&cB); - t_intersect(cA, cB, shouldIntersect); - t_intersect(cB, cA, shouldIntersect); + t_intersects(cA, cB, shouldIntersect); + t_intersects(cB, cA, shouldIntersect); free(A.cells); free(B.cells); @@ -147,10 +148,10 @@ SUITE(low52tests) { t_contains(Z, h, false); // h can't be in an empty set // intersection - t_intersect(A, A, true); - t_intersect(Z, A, false); // first is empty - t_intersect(A, Z, false); // second is empty - t_intersect(Z, Z, false); // both are empty + t_intersects(A, A, true); + t_intersects(Z, A, false); // first is empty + t_intersects(A, Z, false); // second is empty + t_intersects(Z, Z, false); // both are empty free(A.cells); } @@ -197,9 +198,9 @@ SUITE(low52tests) { t_isCanon(C, true); t_contains(C, h, true); - t_intersect(C, C, true); - t_intersect(C, U, true); - t_intersect(U, C, true); + t_intersects(C, C, true); + t_intersects(C, U, true); + t_intersects(U, C, true); // test that uncompact keeps things canonical CellArray U2 = ca_uncompact(C, res); @@ -223,16 +224,16 @@ SUITE(low52tests) { t_contains(B, h, false); t_contains(A, A.cells[0], true); - t_intersect(A, B, false); - t_intersect(B, A, false); - t_intersect(A, A, true); - t_intersect(B, B, true); + t_intersects(A, B, false); + t_intersects(B, A, false); + t_intersects(A, A, true); + t_intersects(B, B, true); // add a cell from A to B, so they now intersect B.cells[B.N / 2] = A.cells[A.N / 2]; ca_canon(&B); - t_intersect(A, B, true); - t_intersect(A, B, true); + t_intersects(A, B, true); + t_intersects(A, B, true); free(A.cells); free(B.cells); From ea81e279b9f4e7372fc75209d9ea73e4f2aecb36 Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Sun, 26 Dec 2021 01:38:36 -0500 Subject: [PATCH 33/35] do some simpler flipping between left and right side of A --- src/h3lib/lib/low52.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/h3lib/lib/low52.c b/src/h3lib/lib/low52.c index 7400d9c96..bf8ff80be 100644 --- a/src/h3lib/lib/low52.c +++ b/src/h3lib/lib/low52.c @@ -344,15 +344,17 @@ int intersectTheyDo(const H3Index *_A, const int64_t aN, const H3Index *_B, if (wayLessThan(A, B)) return false; if (wayLessThan(B, A)) return false; + bool usingLeft = true; + while ((A.i < A.j) && (B.i < B.j)) { ensureASmaller(&A, &B); // take A[i] or A[j-1] and see what happens when we look into B[i:j] - bool usingLeft = (A.i % 2 == 0); + usingLeft = !usingLeft; H3Index h = (usingLeft) ? A.cells[A.i] : A.cells[A.j - 1]; int64_t k = disjointInsertionPoint(B.cells, B.i, B.j, h); - if (k == -1) return true; // they intersect! + if (k == -1) return true; // h found in B, so they intersect! if (usingLeft) { B.i = k; From 6e3fcafce87960eec4e7fe4e9d757d73c161cd7d Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Sun, 26 Dec 2021 11:09:05 -0500 Subject: [PATCH 34/35] simplify input for disjointInsertionPoint --- src/h3lib/lib/low52.c | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/h3lib/lib/low52.c b/src/h3lib/lib/low52.c index bf8ff80be..311510831 100644 --- a/src/h3lib/lib/low52.c +++ b/src/h3lib/lib/low52.c @@ -284,13 +284,19 @@ int canonSearch(const H3Index *cells, const int64_t N, const H3Index h) { return false; } -// could also just take in a search interval struct... +typedef struct { + const H3Index *cells; + int64_t N, i, j; +} SearchInterval; + // returns -1 if h intersects with cells[i:j] -static int64_t disjointInsertionPoint(const H3Index *cells, int64_t i, - int64_t j, const H3Index h) { +static int64_t disjointInsertionPoint(const SearchInterval A, const H3Index h) { + int64_t i = A.i; + int64_t j = A.j; + while (i < j) { int64_t k = i + (j - i) / 2; - int cmp = cmpCanon(h, cells[k]); + int cmp = cmpCanon(h, A.cells[k]); if (cmp == -2) { j = k; @@ -305,11 +311,6 @@ static int64_t disjointInsertionPoint(const H3Index *cells, int64_t i, return i; } -typedef struct { - const H3Index *cells; - int64_t N, i, j; -} SearchInterval; - static bool wayLessThan(const SearchInterval A, const SearchInterval B) { if (A.N > 0 && B.N > 0 && (cmpCanon(A.cells[A.N - 1], B.cells[0]) == -2)) { return true; @@ -352,7 +353,7 @@ int intersectTheyDo(const H3Index *_A, const int64_t aN, const H3Index *_B, // take A[i] or A[j-1] and see what happens when we look into B[i:j] usingLeft = !usingLeft; H3Index h = (usingLeft) ? A.cells[A.i] : A.cells[A.j - 1]; - int64_t k = disjointInsertionPoint(B.cells, B.i, B.j, h); + int64_t k = disjointInsertionPoint(B, h); if (k == -1) return true; // h found in B, so they intersect! @@ -383,7 +384,7 @@ int intersectTheyDo_slow(const H3Index *_A, const int64_t aN, const H3Index *_B, while ((A.i < A.j) && (B.i < B.j)) { // take A[i] and see what happens when we look into B[i:j] H3Index h = A.cells[A.i]; - int64_t k = disjointInsertionPoint(B.cells, B.i, B.j, h); + int64_t k = disjointInsertionPoint(B, h); if (k == -1) return true; // they intersect! From 428854c0a0f5038acfd7a438dab593921c6c127b Mon Sep 17 00:00:00 2001 From: AJ Friend Date: Sun, 26 Dec 2021 12:14:25 -0500 Subject: [PATCH 35/35] tricky ring tests --- src/apps/testapps/testLow52.c | 81 ++++++++++++++++++++++++++++++++++- src/h3lib/lib/low52.c | 2 +- 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/src/apps/testapps/testLow52.c b/src/apps/testapps/testLow52.c index dc50747e5..98bee0d7c 100644 --- a/src/apps/testapps/testLow52.c +++ b/src/apps/testapps/testLow52.c @@ -73,6 +73,55 @@ CellArray ca_uncompact(CellArray arr, int res) { return out; } +/* +Return all cells that are distance k1 <= d <= k2 from h. + +So: + +- ca_thickRing(h, k, k) is the same as gridRing(h, k) +- ca_thickRing(h, 0, k) is the same as gridDisk(h, k) + + */ +CellArray ca_thickRing(H3Index h, int k1, int k2) { + CellArray A = ca_disk(h, k2); + + for (int64_t i = 0; i < A.N; i++) { + int64_t d; + H3_EXPORT(gridDistance)(h, A.cells[i], &d); + + if (d < k1) { + A.cells[i] = 0; + } + } + + CellArray B = ca_compact(A); + ca_canon(&B); + + free(A.cells); + + return B; +} + +CellArray ca_missingRing(H3Index h, int k1, int k2, int K) { + CellArray A = ca_disk(h, K); + + for (int64_t i = 0; i < A.N; i++) { + int64_t d; + H3_EXPORT(gridDistance)(h, A.cells[i], &d); + + if ((k1 <= d) && (d <= k2)) { + A.cells[i] = 0; + } + } + + CellArray B = ca_compact(A); + ca_canon(&B); + + free(A.cells); + + return B; +} + void t_intersects(CellArray A, CellArray B, bool result) { t_assert(result == intersectTheyDo(A.cells, A.N, B.cells, B.N), ""); } @@ -244,7 +293,7 @@ SUITE(low52tests) { H3Index b = 0x89283095063ffff; int64_t k; - t_assertSuccess(H3_EXPORT(gridDistance)(a, b, &k)); + H3_EXPORT(gridDistance)(a, b, &k); t_assert(k == 20, ""); // not compacted @@ -259,4 +308,34 @@ SUITE(low52tests) { t_diskIntersectCompact(a, b, 10, 10, true); // overlap t_diskIntersectCompact(a, b, 11, 11, true); // more overlap } + + TEST(tricky_rings1) { + H3Index h = 0x89283082e73ffff; + int K = 100; + int k1 = 40; + int k2 = 60; + + CellArray A = ca_thickRing(h, k1, k2); + CellArray B = ca_missingRing(h, k1, k2, K); + + t_intersects(A, B, false); + + free(A.cells); + free(B.cells); + } + + TEST(tricky_rings2) { + H3Index h = 0x89283082e73ffff; + int K = 100; + int k1 = 40; + int k2 = 60; + + CellArray A = ca_thickRing(h, k1, k2 + 1); + CellArray B = ca_missingRing(h, k1, k2, K); + + t_intersects(A, B, true); + + free(A.cells); + free(B.cells); + } } diff --git a/src/h3lib/lib/low52.c b/src/h3lib/lib/low52.c index 311510831..fec21eb44 100644 --- a/src/h3lib/lib/low52.c +++ b/src/h3lib/lib/low52.c @@ -352,7 +352,7 @@ int intersectTheyDo(const H3Index *_A, const int64_t aN, const H3Index *_B, // take A[i] or A[j-1] and see what happens when we look into B[i:j] usingLeft = !usingLeft; - H3Index h = (usingLeft) ? A.cells[A.i] : A.cells[A.j - 1]; + H3Index h = usingLeft ? A.cells[A.i] : A.cells[A.j - 1]; int64_t k = disjointInsertionPoint(B, h); if (k == -1) return true; // h found in B, so they intersect!