From 0c865cba4331ce6b83b5101c4e4ee7c398e37a22 Mon Sep 17 00:00:00 2001
From: Andreas Tille <tille@debian.org>
Date: Thu, 18 Feb 2021 20:13:48 +0100
Subject: [PATCH] New upstream version 0.5.2

---
 PKG-INFO                                      |   5 +-
 README.rst                                    |   3 +
 pynndescent.egg-info/PKG-INFO                 |   5 +-
 pynndescent.egg-info/SOURCES.txt              |   5 +
 pynndescent/distances.py                      |  73 +++++-
 pynndescent/graph_utils.py                    | 242 ++++++++++++++++++
 pynndescent/pynndescent_.py                   |  60 +++--
 pynndescent/rp_trees.py                       |   9 +-
 pynndescent/sparse.py                         |  35 ++-
 .../tests/__pycache__/__init__.cpython-38.pyc | Bin 0 -> 162 bytes
 ...test_distances.cpython-38-pytest-6.2.2.pyc | Bin 0 -> 13744 bytes
 ...t_pynndescent_.cpython-38-pytest-6.2.2.pyc | Bin 0 -> 12971 bytes
 .../test_rank.cpython-38-pytest-6.2.2.pyc     | Bin 0 -> 6286 bytes
 pynndescent/tests/test_distances.py           |   7 +-
 pynndescent/tests/test_pynndescent_.py        |  45 ++++
 pynndescent/utils.py                          |  10 +-
 setup.py                                      |   2 +-
 17 files changed, 450 insertions(+), 51 deletions(-)
 create mode 100644 pynndescent/graph_utils.py
 create mode 100644 pynndescent/tests/__pycache__/__init__.cpython-38.pyc
 create mode 100644 pynndescent/tests/__pycache__/test_distances.cpython-38-pytest-6.2.2.pyc
 create mode 100644 pynndescent/tests/__pycache__/test_pynndescent_.cpython-38-pytest-6.2.2.pyc
 create mode 100644 pynndescent/tests/__pycache__/test_rank.cpython-38-pytest-6.2.2.pyc

diff --git a/PKG-INFO b/PKG-INFO
index 55e1224..1703a98 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.2
 Name: pynndescent
-Version: 0.5.1
+Version: 0.5.2
 Summary: Nearest Neighbor Descent
 Home-page: http://github.com/lmcinnes/pynndescent
 Author: Leland McInnes
@@ -91,8 +91,11 @@ Description: .. image:: https://travis-ci.org/lmcinnes/pynndescent.svg
         **Angular and correlation metrics**
         
         - cosine
+        - dot
         - correlation
         - spearmanr
+        - tsss
+        - true_angular
         
         **Probability metrics**
         
diff --git a/README.rst b/README.rst
index ef5c322..cd0349b 100644
--- a/README.rst
+++ b/README.rst
@@ -81,8 +81,11 @@ supporting a wide variety of distance metrics by default:
 **Angular and correlation metrics**
 
 - cosine
+- dot
 - correlation
 - spearmanr
+- tsss
+- true_angular
 
 **Probability metrics**
 
diff --git a/pynndescent.egg-info/PKG-INFO b/pynndescent.egg-info/PKG-INFO
index 55e1224..1703a98 100644
--- a/pynndescent.egg-info/PKG-INFO
+++ b/pynndescent.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.2
 Name: pynndescent
-Version: 0.5.1
+Version: 0.5.2
 Summary: Nearest Neighbor Descent
 Home-page: http://github.com/lmcinnes/pynndescent
 Author: Leland McInnes
@@ -91,8 +91,11 @@ Description: .. image:: https://travis-ci.org/lmcinnes/pynndescent.svg
         **Angular and correlation metrics**
         
         - cosine
+        - dot
         - correlation
         - spearmanr
+        - tsss
+        - true_angular
         
         **Probability metrics**
         
diff --git a/pynndescent.egg-info/SOURCES.txt b/pynndescent.egg-info/SOURCES.txt
index fbe0a29..5499a38 100644
--- a/pynndescent.egg-info/SOURCES.txt
+++ b/pynndescent.egg-info/SOURCES.txt
@@ -7,6 +7,7 @@ requirements.txt
 setup.py
 pynndescent/__init__.py
 pynndescent/distances.py
+pynndescent/graph_utils.py
 pynndescent/optimal_transport.py
 pynndescent/pynndescent_.py
 pynndescent/rp_trees.py
@@ -25,7 +26,11 @@ pynndescent/tests/test_distances.py
 pynndescent/tests/test_pynndescent_.py
 pynndescent/tests/test_rank.py
 pynndescent/tests/__pycache__/__init__.cpython-37.pyc
+pynndescent/tests/__pycache__/__init__.cpython-38.pyc
 pynndescent/tests/__pycache__/test_distances.cpython-37.pyc
+pynndescent/tests/__pycache__/test_distances.cpython-38-pytest-6.2.2.pyc
 pynndescent/tests/__pycache__/test_pynndescent_.cpython-37.pyc
+pynndescent/tests/__pycache__/test_pynndescent_.cpython-38-pytest-6.2.2.pyc
 pynndescent/tests/__pycache__/test_rank.cpython-37.pyc
+pynndescent/tests/__pycache__/test_rank.cpython-38-pytest-6.2.2.pyc
 pynndescent/tests/test_data/cosine_hang.npy
\ No newline at end of file
diff --git a/pynndescent/distances.py b/pynndescent/distances.py
index e645cc3..3d40480 100644
--- a/pynndescent/distances.py
+++ b/pynndescent/distances.py
@@ -47,7 +47,7 @@ def euclidean(x, y):
     locals={
         "result": numba.types.float32,
         "diff": numba.types.float32,
-        "dim": numba.types.uint32,
+        "dim": numba.types.intp,
         "i": numba.types.uint16,
     },
 )
@@ -230,7 +230,7 @@ def jaccard(x, y):
         "num_equal": numba.types.float32,
         "x_true": numba.types.uint8,
         "y_true": numba.types.uint8,
-        "dim": numba.types.uint32,
+        "dim": numba.types.intp,
         "i": numba.types.uint16,
     },
 )
@@ -415,7 +415,7 @@ def cosine(x, y):
         "result": numba.types.float32,
         "norm_x": numba.types.float32,
         "norm_y": numba.types.float32,
-        "dim": numba.types.uint32,
+        "dim": numba.types.intp,
         "i": numba.types.uint16,
     },
 )
@@ -445,7 +445,7 @@ def alternative_cosine(x, y):
     fastmath=True,
     locals={
         "result": numba.types.float32,
-        "dim": numba.types.uint32,
+        "dim": numba.types.intp,
         "i": numba.types.uint16,
     },
 )
@@ -472,7 +472,7 @@ def dot(x, y):
     fastmath=True,
     locals={
         "result": numba.types.float32,
-        "dim": numba.types.uint32,
+        "dim": numba.types.intp,
         "i": numba.types.uint16,
     },
 )
@@ -493,6 +493,59 @@ def correct_alternative_cosine(d):
     return 1.0 - pow(2.0, -d)
 
 
+@numba.njit(fastmath=True)
+def tsss(x, y):
+    d_euc_squared = 0.0
+    d_cos = 0.0
+    norm_x = 0.0
+    norm_y = 0.0
+    dim = x.shape[0]
+
+    for i in range(dim):
+        diff = x[i] - y[i]
+        d_euc_squared += diff * diff
+        d_cos += x[i] * y[i]
+        norm_x += x[i] * x[i]
+        norm_y += y[i] * y[i]
+
+    norm_x = np.sqrt(norm_x)
+    norm_y = np.sqrt(norm_y)
+    magnitude_difference = np.abs(norm_x - norm_y)
+    d_cos /= norm_x * norm_y
+    theta = np.arccos(d_cos) + np.radians(10)  # Add 10 degrees as an "epsilon" to
+    # avoid problems
+    sector = ((np.sqrt(d_euc_squared) + magnitude_difference) ** 2) * theta
+    triangle = norm_x * norm_y * np.sin(theta) / 2.0
+    return triangle * sector
+
+
+@numba.njit(fastmath=True)
+def true_angular(x, y):
+    result = 0.0
+    norm_x = 0.0
+    norm_y = 0.0
+    dim = x.shape[0]
+    for i in range(dim):
+        result += x[i] * y[i]
+        norm_x += x[i] * x[i]
+        norm_y += y[i] * y[i]
+
+    if norm_x == 0.0 and norm_y == 0.0:
+        return 0.0
+    elif norm_x == 0.0 or norm_y == 0.0:
+        return FLOAT32_MAX
+    elif result <= 0.0:
+        return FLOAT32_MAX
+    else:
+        result = result / np.sqrt(norm_x * norm_y)
+        return 1.0 - (np.arccos(result) / np.pi)
+
+
+@numba.vectorize(fastmath=True)
+def true_angular_from_alt_cosine(d):
+    return 1.0 - (np.arccos(pow(2.0, -d)) / np.pi)
+
+
 @numba.njit(fastmath=True, cache=True)
 def correlation(x, y):
     mu_x = 0.0
@@ -536,7 +589,7 @@ def correlation(x, y):
         "result": numba.types.float32,
         "l1_norm_x": numba.types.float32,
         "l1_norm_y": numba.types.float32,
-        "dim": numba.types.uint32,
+        "dim": numba.types.intp,
         "i": numba.types.uint16,
     },
 )
@@ -572,7 +625,7 @@ def hellinger(x, y):
         "result": numba.types.float32,
         "l1_norm_x": numba.types.float32,
         "l1_norm_y": numba.types.float32,
-        "dim": numba.types.uint32,
+        "dim": numba.types.intp,
         "i": numba.types.uint16,
     },
 )
@@ -738,6 +791,8 @@ named_distances = {
     "spearmanr": spearmanr,
     "kantorovich": kantorovich,
     "wasserstein": kantorovich,
+    "tsss": tsss,
+    "true_angular": true_angular,
     # Binary distances
     "hamming": hamming,
     "jaccard": jaccard,
@@ -762,6 +817,10 @@ fast_distance_alternatives = {
     "l2": {"dist": squared_euclidean, "correction": np.sqrt},
     "cosine": {"dist": alternative_cosine, "correction": correct_alternative_cosine},
     "dot": {"dist": alternative_dot, "correction": correct_alternative_cosine},
+    "true_angular": {
+        "dist": alternative_cosine,
+        "correction": true_angular_from_alt_cosine,
+    },
     "hellinger": {
         "dist": alternative_hellinger,
         "correction": correct_alternative_hellinger,
diff --git a/pynndescent/graph_utils.py b/pynndescent/graph_utils.py
new file mode 100644
index 0000000..337a925
--- /dev/null
+++ b/pynndescent/graph_utils.py
@@ -0,0 +1,242 @@
+import numba
+import numpy as np
+import heapq
+
+from scipy.sparse import coo_matrix
+from scipy.sparse.csgraph import connected_components
+from itertools import combinations
+
+import pynndescent.distances as pynnd_dist
+import joblib
+
+from pynndescent.utils import (
+    rejection_sample,
+    make_heap,
+    deheap_sort,
+    simple_heap_push,
+    has_been_visited,
+    mark_visited,
+)
+
+FLOAT32_EPS = np.finfo(np.float32).eps
+
+
+def create_component_search(index):
+    alternative_dot = pynnd_dist.alternative_dot
+    alternative_cosine = pynnd_dist.alternative_cosine
+
+    data = index._raw_data
+    indptr = index._search_graph.indptr
+    indices = index._search_graph.indices
+    dist = index._distance_func
+
+    @numba.njit(
+        fastmath=True,
+        nogil=True,
+        locals={
+            "current_query": numba.types.float32[::1],
+            "i": numba.types.uint32,
+            "j": numba.types.uint32,
+            "heap_priorities": numba.types.float32[::1],
+            "heap_indices": numba.types.int32[::1],
+            "candidate": numba.types.int32,
+            "vertex": numba.types.int32,
+            "d": numba.types.float32,
+            "d_vertex": numba.types.float32,
+            "visited": numba.types.uint8[::1],
+            "indices": numba.types.int32[::1],
+            "indptr": numba.types.int32[::1],
+            "data": numba.types.float32[:, ::1],
+            "heap_size": numba.types.int16,
+            "distance_scale": numba.types.float32,
+            "distance_bound": numba.types.float32,
+            "seed_scale": numba.types.float32,
+        },
+    )
+    def custom_search_closure(
+        query_points,
+        candidate_indices,
+        k,
+        epsilon,
+        visited,
+    ):
+        result = make_heap(query_points.shape[0], k)
+        distance_scale = 1.0 + epsilon
+
+        for i in range(query_points.shape[0]):
+            visited[:] = 0
+            if dist == alternative_dot or dist == alternative_cosine:
+                norm = np.sqrt((query_points[i] ** 2).sum())
+                if norm > 0.0:
+                    current_query = query_points[i] / norm
+                else:
+                    continue
+            else:
+                current_query = query_points[i]
+
+            heap_priorities = result[1][i]
+            heap_indices = result[0][i]
+            seed_set = [(np.float32(np.inf), np.int32(-1)) for j in range(0)]
+
+            ############ Init ################
+            n_initial_points = candidate_indices.shape[0]
+
+            for j in range(n_initial_points):
+                candidate = np.int32(candidate_indices[j])
+                d = dist(data[candidate], current_query)
+                # indices are guaranteed different
+                simple_heap_push(heap_priorities, heap_indices, d, candidate)
+                heapq.heappush(seed_set, (d, candidate))
+                mark_visited(visited, candidate)
+
+            ############ Search ##############
+            distance_bound = distance_scale * heap_priorities[0]
+
+            # Find smallest seed point
+            d_vertex, vertex = heapq.heappop(seed_set)
+
+            while d_vertex < distance_bound:
+
+                for j in range(indptr[vertex], indptr[vertex + 1]):
+
+                    candidate = indices[j]
+
+                    if has_been_visited(visited, candidate) == 0:
+                        mark_visited(visited, candidate)
+
+                        d = dist(data[candidate], current_query)
+
+                        if d < distance_bound:
+                            simple_heap_push(
+                                heap_priorities, heap_indices, d, candidate
+                            )
+                            heapq.heappush(seed_set, (d, candidate))
+                            # Update bound
+                            distance_bound = distance_scale * heap_priorities[0]
+
+                # find new smallest seed point
+                if len(seed_set) == 0:
+                    break
+                else:
+                    d_vertex, vertex = heapq.heappop(seed_set)
+
+        return result
+
+    return custom_search_closure
+
+
+# @numba.njit(nogil=True)
+def find_component_connection_edge(
+    component1,
+    component2,
+    search_closure,
+    raw_data,
+    visited,
+    rng_state,
+    search_size=10,
+    epsilon=0.0,
+):
+    indices = [np.zeros(1, dtype=np.int64) for i in range(2)]
+    indices[0] = component1[
+        rejection_sample(np.int64(search_size), component1.shape[0], rng_state)
+    ]
+    indices[1] = component2[
+        rejection_sample(np.int64(search_size), component2.shape[0], rng_state)
+    ]
+    query_side = 0
+    query_points = raw_data[indices[query_side]]
+    candidate_indices = indices[1 - query_side].copy()
+    changed = [True, True]
+    best_dist = np.inf
+    best_edge = (indices[0][0], indices[1][0])
+
+    while changed[0] or changed[1]:
+        result = search_closure(
+            query_points, candidate_indices, search_size, epsilon, visited
+        )
+        inds, dists = deheap_sort(result)
+        for i in range(dists.shape[0]):
+            for j in range(dists.shape[1]):
+                if dists[i, j] < best_dist:
+                    best_dist = dists[i, j]
+                    best_edge = (indices[query_side][i], inds[i, j])
+        candidate_indices = indices[query_side]
+        new_indices = np.unique(inds[:, 0])
+        if indices[1 - query_side].shape[0] == new_indices.shape[0]:
+            changed[1 - query_side] = np.any(indices[1 - query_side] != new_indices)
+        indices[1 - query_side] = new_indices
+        query_points = raw_data[indices[1 - query_side]]
+        query_side = 1 - query_side
+
+    return best_edge[0], best_edge[1], best_dist
+
+
+def adjacency_matrix_representation(neighbor_indices, neighbor_distances):
+    result = coo_matrix(
+        (neighbor_indices.shape[0], neighbor_indices.shape[0]), dtype=np.float32
+    )
+
+    # Preserve any distance 0 points
+    neighbor_distances[neighbor_distances == 0.0] = FLOAT32_EPS
+
+    result.row = np.repeat(
+        np.arange(neighbor_indices.shape[0], dtype=np.int32),
+        neighbor_indices.shape[1],
+    )
+    result.col = neighbor_indices.ravel()
+    result.data = neighbor_distances.ravel()
+
+    # Get rid of any -1 index entries
+    result = result.tocsr()
+    result.data[result.indices == -1] = 0.0
+    result.eliminate_zeros()
+
+    # Symmetrize
+    result = result.maximum(result.T)
+
+    return result
+
+
+def connect_graph(graph, index, search_size=10, n_jobs=None):
+
+    search_closure = create_component_search(index)
+    n_components, component_ids = connected_components(graph)
+    result = graph.tolil()
+
+    # Translate component ids into internal vertex order
+    component_ids = component_ids[index._vertex_order]
+
+    def new_edge(c1, c2):
+        component1 = np.where(component_ids == c1)[0]
+        component2 = np.where(component_ids == c2)[0]
+
+        i, j, d = find_component_connection_edge(
+            component1,
+            component2,
+            search_closure,
+            index._raw_data,
+            index._visited,
+            index.rng_state,
+            search_size=search_size,
+        )
+
+        # Correct the distance if required
+        if index._distance_correction is not None:
+            d = index._distance_correction(d)
+
+        # Convert indices to original data order
+        i = index._vertex_order[i]
+        j = index._vertex_order[j]
+
+        return i, j, d
+
+    new_edges = joblib.Parallel(n_jobs=n_jobs, prefer="threads")(
+        joblib.delayed(new_edge)(c1, c2)
+        for c1, c2 in combinations(range(n_components), 2)
+    )
+
+    for i, j, d in new_edges:
+        result[i, j] = d
+        result[j, i] = d
+
+    return result.tocsr()
diff --git a/pynndescent/pynndescent_.py b/pynndescent/pynndescent_.py
index ad7f067..96b357c 100644
--- a/pynndescent/pynndescent_.py
+++ b/pynndescent/pynndescent_.py
@@ -855,7 +855,9 @@ class NNDescent(object):
             if init_graph is None:
                 _init_graph = EMPTY_GRAPH
             else:
-                _init_graph = make_heap(init_graph.shape[0], init_graph.shape[1])
+                if init_graph.shape[0] != self._raw_data.shape[0]:
+                    raise ValueError("Init graph size does not match dataset size!")
+                _init_graph = make_heap(init_graph.shape[0], self.n_neighbors)
                 _init_graph = sparse_initalize_heap_from_graph_indices(
                     _init_graph,
                     init_graph,
@@ -892,7 +894,9 @@ class NNDescent(object):
             if init_graph is None:
                 _init_graph = EMPTY_GRAPH
             else:
-                _init_graph = make_heap(init_graph.shape[0], init_graph.shape[1])
+                if init_graph.shape[0] != self._raw_data.shape[0]:
+                    raise ValueError("Init graph size does not match dataset size!")
+                _init_graph = make_heap(init_graph.shape[0], self.n_neighbors)
                 _init_graph = initalize_heap_from_graph_indices(
                     _init_graph, init_graph, data, self._distance_func
                 )
@@ -952,21 +956,40 @@ class NNDescent(object):
             numba.set_num_threads(self.n_jobs)
 
         if not hasattr(self, "_search_forest"):
-            tree_scores = [
-                score_linked_tree(tree, self._neighbor_graph[0])
-                for tree in self._rp_forest
-            ]
-            if self.verbose:
-                print(ts(), "Worst tree score: {:.8f}".format(np.min(tree_scores)))
-                print(ts(), "Mean tree score: {:.8f}".format(np.mean(tree_scores)))
-                print(ts(), "Best tree score: {:.8f}".format(np.max(tree_scores)))
-            best_tree_indices = np.argsort(tree_scores)[: self.n_search_trees]
-            best_trees = [self._rp_forest[idx] for idx in best_tree_indices]
-            del self._rp_forest
-            self._search_forest = [
-                convert_tree_format(tree, self._raw_data.shape[0])
-                for tree in best_trees
-            ]
+            if self._rp_forest is None:
+                # We don't have a forest, so make a small search forest
+                current_random_state = check_random_state(self.random_state)
+                rp_forest = make_forest(
+                    self._raw_data,
+                    self.n_neighbors,
+                    self.n_search_trees,
+                    self.leaf_size,
+                    self.rng_state,
+                    current_random_state,
+                    self.n_jobs,
+                    self._angular_trees,
+                )
+                self._search_forest = [
+                    convert_tree_format(tree, self._raw_data.shape[0])
+                    for tree in rp_forest
+                ]
+            else:
+                # convert the best trees into a search forest
+                tree_scores = [
+                    score_linked_tree(tree, self._neighbor_graph[0])
+                    for tree in self._rp_forest
+                ]
+                if self.verbose:
+                    print(ts(), "Worst tree score: {:.8f}".format(np.min(tree_scores)))
+                    print(ts(), "Mean tree score: {:.8f}".format(np.mean(tree_scores)))
+                    print(ts(), "Best tree score: {:.8f}".format(np.max(tree_scores)))
+                best_tree_indices = np.argsort(tree_scores)[: self.n_search_trees]
+                best_trees = [self._rp_forest[idx] for idx in best_tree_indices]
+                del self._rp_forest
+                self._search_forest = [
+                    convert_tree_format(tree, self._raw_data.shape[0])
+                    for tree in best_trees
+                ]
 
         nnz_pre_diversify = np.sum(self._neighbor_graph[0] >= 0)
         if self._is_sparse:
@@ -1079,7 +1102,7 @@ class NNDescent(object):
         self._search_graph.sort_indices()
         self._search_graph = self._search_graph.maximum(reverse_graph).tocsr()
 
-        # Eliminate the diagonal0]
+        # Eliminate the diagonal
         self._search_graph.setdiag(0.0)
         self._search_graph.eliminate_zeros()
 
@@ -1531,6 +1554,7 @@ class NNDescent(object):
     def compress_index(self):
         import gc
 
+        self.prepare()
         self.compressed = True
 
         if hasattr(self, "_rp_forest"):
diff --git a/pynndescent/rp_trees.py b/pynndescent/rp_trees.py
index a2a729f..55aff6e 100644
--- a/pynndescent/rp_trees.py
+++ b/pynndescent/rp_trees.py
@@ -853,7 +853,7 @@ def make_sparse_tree(inds, indptr, spdata, rng_state, leaf_size=30, angular=Fals
     fastmath=True,
     locals={
         "margin": numba.types.float32,
-        "dim": numba.types.uint16,
+        "dim": numba.types.intp,
         "d": numba.types.uint16,
     },
 )
@@ -984,7 +984,7 @@ def make_forest(
     )
     try:
         if scipy.sparse.isspmatrix_csr(data):
-            result = joblib.Parallel(n_jobs=n_jobs, prefer="threads")(
+            result = joblib.Parallel(n_jobs=n_jobs, require="sharedmem")(
                 joblib.delayed(make_sparse_tree)(
                     data.indices,
                     data.indptr,
@@ -996,7 +996,7 @@ def make_forest(
                 for i in range(n_trees)
             )
         else:
-            result = joblib.Parallel(n_jobs=n_jobs, prefer="threads")(
+            result = joblib.Parallel(n_jobs=n_jobs, require="sharedmem")(
                 joblib.delayed(make_dense_tree)(data, rng_states[i], leaf_size, angular)
                 for i in range(n_trees)
             )
@@ -1029,10 +1029,9 @@ def get_leaves_from_tree(tree):
 
 
 def rptree_leaf_array_parallel(rp_forest):
-    result = joblib.Parallel(n_jobs=-1, prefer="threads")(
+    result = joblib.Parallel(n_jobs=-1, require="sharedmem")(
         joblib.delayed(get_leaves_from_tree)(rp_tree) for rp_tree in rp_forest
     )
-    # result = [get_leaves_from_tree(rp_tree) for rp_tree in rp_forest]
     return result
 
 
diff --git a/pynndescent/sparse.py b/pynndescent/sparse.py
index 301e99f..8d91bb5 100644
--- a/pynndescent/sparse.py
+++ b/pynndescent/sparse.py
@@ -218,7 +218,7 @@ def sparse_euclidean(ind1, data1, ind2, data2):
         "aux_data": numba.types.float32[::1],
         "result": numba.types.float32,
         "diff": numba.types.float32,
-        "dim": numba.types.uint32,
+        "dim": numba.types.intp,
         "i": numba.types.uint16,
     },
 )
@@ -281,17 +281,30 @@ def sparse_canberra(ind1, data1, ind2, data2):
     return result
 
 
-@numba.njit()
+@numba.njit(
+    [
+        "f4(i4[::1],f4[::1],i4[::1],f4[::1])",
+        numba.types.float32(
+            numba.types.Array(numba.types.int32, 1, "C", readonly=True),
+            numba.types.Array(numba.types.float32, 1, "C", readonly=True),
+            numba.types.Array(numba.types.int32, 1, "C", readonly=True),
+            numba.types.Array(numba.types.float32, 1, "C", readonly=True),
+        ),
+    ],
+    fastmath=True,
+)
 def sparse_bray_curtis(ind1, data1, ind2, data2):  # pragma: no cover
-    abs_data1 = np.abs(data1)
-    abs_data2 = np.abs(data2)
-    _, denom_data = sparse_sum(ind1, abs_data1, ind2, abs_data2)
+    _, denom_data = sparse_sum(ind1, data1, ind2, data2)
+    denom_data = np.abs(denom_data)
 
     if denom_data.shape[0] == 0:
         return 0.0
 
     denominator = np.sum(denom_data)
 
+    if denominator == 0.0:
+        return 0.0
+
     _, numer_data = sparse_diff(ind1, data1, ind2, data2)
     numer_data = np.abs(numer_data)
 
@@ -323,8 +336,8 @@ def sparse_jaccard(ind1, data1, ind2, data2):
     ],
     fastmath=True,
     locals={
-        "num_non_zero": numba.types.float32,
-        "num_equal": numba.types.float32,
+        "num_non_zero": numba.types.intp,
+        "num_equal": numba.types.intp,
     },
 )
 def sparse_alternative_jaccard(ind1, data1, ind2, data2):
@@ -445,7 +458,7 @@ def sparse_cosine(ind1, data1, ind2, data2):
         "result": numba.types.float32,
         "norm_x": numba.types.float32,
         "norm_y": numba.types.float32,
-        "dim": numba.types.int32,
+        "dim": numba.types.intp,
         "i": numba.types.uint16,
     },
 )
@@ -492,7 +505,7 @@ def sparse_dot(ind1, data1, ind2, data2):
     fastmath=True,
     locals={
         "result": numba.types.float32,
-        "dim": numba.types.int32,
+        "dim": numba.types.intp,
         "i": numba.types.uint16,
     },
 )
@@ -598,7 +611,7 @@ def sparse_hellinger(ind1, data1, ind2, data2):
         "result": numba.types.float32,
         "l1_norm_x": numba.types.float32,
         "l1_norm_y": numba.types.float32,
-        "dim": numba.types.uint32,
+        "dim": numba.types.intp,
         "i": numba.types.uint16,
     },
 )
@@ -810,7 +823,7 @@ sparse_named_distances = {
     "canberra": sparse_canberra,
     "kantorovich": sparse_kantorovich,
     "wasserstein": sparse_kantorovich,
-    # 'braycurtis': sparse_bray_curtis,
+    "braycurtis": sparse_bray_curtis,
     # Binary distances
     "hamming": sparse_hamming,
     "jaccard": sparse_jaccard,
diff --git a/pynndescent/tests/__pycache__/__init__.cpython-38.pyc b/pynndescent/tests/__pycache__/__init__.cpython-38.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9fac04a06e41948fd60c54410e7328ef04d387d4
GIT binary patch
literal 162
zcmWIL<>g`k0#iYTSP=afL?8o3AjbiSi&=m~3PUi1CZpd<h9ZzKg81dGA6lGRRIHzq
znv<B9q90J1oRL_R8&H&=m6}{qtY1)>mzR=SoSd3hg2FCIEe6WQ$7kkcmc+;F6;$5h
Su*uC&Da}c>16lAHh#3HULnpZa

literal 0
HcmV?d00001

diff --git a/pynndescent/tests/__pycache__/test_distances.cpython-38-pytest-6.2.2.pyc b/pynndescent/tests/__pycache__/test_distances.cpython-38-pytest-6.2.2.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..737be21baf662888c3cfe14da58af8874571dcb4
GIT binary patch
literal 13744
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
Ye=xp2c`!Z{5C2O3t%=jWo_OMa01iM@W&i*H

literal 0
HcmV?d00001

diff --git a/pynndescent/tests/__pycache__/test_pynndescent_.cpython-38-pytest-6.2.2.pyc b/pynndescent/tests/__pycache__/test_pynndescent_.cpython-38-pytest-6.2.2.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..fcc558db4928153de3967e57ee1e413f0dd5db39
GIT binary patch
literal 12971
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
aGmhFiXAABooH1uRu5oA78F4n@KlVS}Znc*H

literal 0
HcmV?d00001

diff --git a/pynndescent/tests/__pycache__/test_rank.cpython-38-pytest-6.2.2.pyc b/pynndescent/tests/__pycache__/test_rank.cpython-38-pytest-6.2.2.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4d5cb61c799f9b03c7a18f57edd33bb51cfee4fb
GIT binary patch
literal 6286
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
C4op`7

literal 0
HcmV?d00001

diff --git a/pynndescent/tests/test_distances.py b/pynndescent/tests/test_distances.py
index 91a0e99..ab6056e 100644
--- a/pynndescent/tests/test_distances.py
+++ b/pynndescent/tests/test_distances.py
@@ -1,11 +1,10 @@
 import numpy as np
-from numpy.testing import assert_array_equal
+from numpy.testing import assert_array_equal, assert_array_almost_equal
 import pynndescent.distances as dist
 import pynndescent.sparse as spdist
 from scipy import sparse, stats
 from sklearn.metrics import pairwise_distances
 from sklearn.neighbors import BallTree
-from sklearn.utils.testing import assert_array_almost_equal
 
 np.random.seed(42)
 spatial_data = np.random.randn(10, 20)
@@ -315,6 +314,10 @@ def test_sparse_sokalsneath():
     sparse_binary_check("sokalsneath")
 
 
+def test_sparse_braycurtis():
+    sparse_spatial_check("braycurtis")
+
+
 def test_seuclidean():
     v = np.abs(np.random.randn(spatial_data.shape[1]))
     dist_matrix = pairwise_distances(spatial_data, metric="seuclidean", V=v)
diff --git a/pynndescent/tests/test_pynndescent_.py b/pynndescent/tests/test_pynndescent_.py
index c58fc24..83d7e7f 100644
--- a/pynndescent/tests/test_pynndescent_.py
+++ b/pynndescent/tests/test_pynndescent_.py
@@ -413,6 +413,51 @@ def test_pickle_unpickle():
     np.testing.assert_equal(distances1, distances2)
 
 
+def test_compressed_pickle_unpickle():
+    seed = np.random.RandomState(42)
+
+    x1 = seed.normal(0, 100, (1000, 50))
+    x2 = seed.normal(0, 100, (1000, 50))
+
+    index1 = NNDescent(
+        x1,
+        "euclidean",
+        {},
+        10,
+        random_state=None,
+        compressed=True,
+    )
+    neighbors1, distances1 = index1.query(x2)
+
+    pickle.dump(index1, open("test_tmp.pkl", "wb"))
+    index2 = pickle.load(open("test_tmp.pkl", "rb"))
+    os.remove("test_tmp.pkl")
+
+    neighbors2, distances2 = index2.query(x2)
+
+    np.testing.assert_equal(neighbors1, neighbors2)
+    np.testing.assert_equal(distances1, distances2)
+
+
+def test_transformer_pickle_unpickle():
+    seed = np.random.RandomState(42)
+
+    x1 = seed.normal(0, 100, (1000, 50))
+    x2 = seed.normal(0, 100, (1000, 50))
+
+    index1 = PyNNDescentTransformer(n_neighbors=10).fit(x1)
+    result1 = index1.transform(x2)
+
+    pickle.dump(index1, open("test_tmp.pkl", "wb"))
+    index2 = pickle.load(open("test_tmp.pkl", "rb"))
+    os.remove("test_tmp.pkl")
+
+    result2 = index2.transform(x2)
+
+    np.testing.assert_equal(result1.indices, result2.indices)
+    np.testing.assert_equal(result1.data, result2.data)
+
+
 def test_joblib_dump():
     seed = np.random.RandomState(42)
 
diff --git a/pynndescent/utils.py b/pynndescent/utils.py
index 23ce2e0..befe4bd 100644
--- a/pynndescent/utils.py
+++ b/pynndescent/utils.py
@@ -67,7 +67,7 @@ def tau_rand(state):
         ),
     ],
     locals={
-        "dim": numba.types.uint32,
+        "dim": numba.types.intp,
         "i": numba.types.uint32,
         "result": numba.types.float32,
     },
@@ -620,7 +620,7 @@ def mark_visited(table, candidate):
     "i4(f4[::1],i4[::1],f4,i4)",
     fastmath=True,
     locals={
-        "size": numba.types.uint16,
+        "size": numba.types.intp,
         "i": numba.types.uint16,
         "ic1": numba.types.uint16,
         "ic2": numba.types.uint16,
@@ -676,7 +676,7 @@ def simple_heap_push(priorities, indices, p, n):
     "i4(f4[::1],i4[::1],f4,i4)",
     fastmath=True,
     locals={
-        "size": numba.types.uint16,
+        "size": numba.types.intp,
         "i": numba.types.uint16,
         "ic1": numba.types.uint16,
         "ic2": numba.types.uint16,
@@ -737,7 +737,7 @@ def checked_heap_push(priorities, indices, p, n):
     "i4(f4[::1],i4[::1],u1[::1],f4,i4,u1)",
     fastmath=True,
     locals={
-        "size": numba.types.uint16,
+        "size": numba.types.intp,
         "i": numba.types.uint16,
         "ic1": numba.types.uint16,
         "ic2": numba.types.uint16,
@@ -796,7 +796,7 @@ def flagged_heap_push(priorities, indices, flags, p, n, f):
     "i4(f4[::1],i4[::1],u1[::1],f4,i4,u1)",
     fastmath=True,
     locals={
-        "size": numba.types.uint16,
+        "size": numba.types.intp,
         "i": numba.types.uint16,
         "ic1": numba.types.uint16,
         "ic2": numba.types.uint16,
diff --git a/setup.py b/setup.py
index 9b15d5d..193b4e0 100644
--- a/setup.py
+++ b/setup.py
@@ -8,7 +8,7 @@ def readme():
 
 configuration = {
     "name": "pynndescent",
-    "version": "0.5.1",
+    "version": "0.5.2",
     "description": "Nearest Neighbor Descent",
     "long_description": readme(),
     "classifiers": [
-- 
GitLab