jaxknn 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. jaxknn-0.1.0/.github/workflows/build_docs.yml +86 -0
  2. jaxknn-0.1.0/.github/workflows/cd.yml +53 -0
  3. jaxknn-0.1.0/.github/workflows/ci.yml +145 -0
  4. jaxknn-0.1.0/.gitignore +14 -0
  5. jaxknn-0.1.0/.pre-commit-config.yaml +42 -0
  6. jaxknn-0.1.0/.python-version +1 -0
  7. jaxknn-0.1.0/LICENSE +22 -0
  8. jaxknn-0.1.0/PKG-INFO +97 -0
  9. jaxknn-0.1.0/README.md +66 -0
  10. jaxknn-0.1.0/conftest.py +19 -0
  11. jaxknn-0.1.0/docs/_overrides/partials/source.html +10 -0
  12. jaxknn-0.1.0/docs/_static/custom_css.css +167 -0
  13. jaxknn-0.1.0/docs/api/jaxknn.md +17 -0
  14. jaxknn-0.1.0/docs/api/tree.md +17 -0
  15. jaxknn-0.1.0/docs/how-it-works.md +28 -0
  16. jaxknn-0.1.0/docs/how-to/find-the-nearest-point-with-another-label.md +23 -0
  17. jaxknn-0.1.0/docs/how-to/tune-for-large-k-or-high-dimension.md +19 -0
  18. jaxknn-0.1.0/docs/how-to/use-under-jit-vmap-grad.md +49 -0
  19. jaxknn-0.1.0/docs/index.md +37 -0
  20. jaxknn-0.1.0/docs/when-to-use-scipy-instead.md +19 -0
  21. jaxknn-0.1.0/mkdocs.yml +82 -0
  22. jaxknn-0.1.0/pyproject.toml +159 -0
  23. jaxknn-0.1.0/src/jaxknn/__init__.py +21 -0
  24. jaxknn-0.1.0/src/jaxknn/_api.py +249 -0
  25. jaxknn-0.1.0/src/jaxknn/_version.py +24 -0
  26. jaxknn-0.1.0/src/jaxknn/tree/__init__.py +20 -0
  27. jaxknn-0.1.0/src/jaxknn/tree/_brute.py +48 -0
  28. jaxknn-0.1.0/src/jaxknn/tree/_build.py +116 -0
  29. jaxknn-0.1.0/src/jaxknn/tree/_layout.py +36 -0
  30. jaxknn-0.1.0/src/jaxknn/tree/_query.py +446 -0
  31. jaxknn-0.1.0/src/jaxknn/tree/_select.py +140 -0
  32. jaxknn-0.1.0/tests/_compat.py +8 -0
  33. jaxknn-0.1.0/tests/benchmarks/test_tree.py +69 -0
  34. jaxknn-0.1.0/tests/test_api.py +397 -0
  35. jaxknn-0.1.0/tests/test_tree.py +569 -0
  36. jaxknn-0.1.0/tests/test_tree_properties.py +93 -0
  37. jaxknn-0.1.0/uv.lock +1364 -0
@@ -0,0 +1,86 @@
1
+ name: Build docs
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ branches:
7
+ - main
8
+ pull_request:
9
+
10
+ concurrency:
11
+ # Each push to main gets its own group, so merges never cancel each other
12
+ # and main keeps a complete CI record. All other events keep the per-ref
13
+ # group and still supersede older runs.
14
+ group: >-
15
+ ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
16
+ cancel-in-progress: true
17
+
18
+ permissions:
19
+ contents: read
20
+
21
+ env:
22
+ FORCE_COLOR: 3
23
+
24
+ jobs:
25
+ build:
26
+ runs-on: ubuntu-latest
27
+ steps:
28
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
29
+ with:
30
+ fetch-depth: 0
31
+
32
+ - name: Install uv
33
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
34
+ with:
35
+ python-version: "3.14"
36
+
37
+ - name: Install dependencies
38
+ run: uv sync --no-default-groups --group docs --locked
39
+
40
+
41
+ - name: Build docs
42
+ run: uv run --frozen zensical build --clean
43
+
44
+ upload:
45
+ needs: build
46
+ if: github.ref == 'refs/heads/main'
47
+ runs-on: ubuntu-latest
48
+ permissions:
49
+ contents: read
50
+ actions: write
51
+ steps:
52
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
53
+ with:
54
+ fetch-depth: 0
55
+
56
+ - name: Install uv
57
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
58
+ with:
59
+ python-version: "3.14"
60
+
61
+ - name: Install dependencies
62
+ run: uv sync --no-default-groups --group docs --locked
63
+
64
+
65
+ - name: Build docs
66
+ run: uv run --frozen zensical build --clean
67
+
68
+ - name: Upload pages artifact
69
+ uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
70
+ with:
71
+ path: site
72
+
73
+ deploy:
74
+ needs: upload
75
+ if: github.ref == 'refs/heads/main'
76
+ runs-on: ubuntu-latest
77
+ permissions:
78
+ pages: write
79
+ id-token: write
80
+ environment:
81
+ name: github-pages
82
+ url: ${{ steps.deployment.outputs.page_url }}
83
+ steps:
84
+ - name: Deploy to GitHub Pages
85
+ id: deployment
86
+ uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
@@ -0,0 +1,53 @@
1
+ name: CD
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ push:
6
+ tags:
7
+ - "v*"
8
+
9
+ concurrency:
10
+ group: ${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: false
12
+
13
+ env:
14
+ FORCE_COLOR: 3
15
+
16
+ jobs:
17
+ build:
18
+ name: Build & inspect package
19
+ runs-on: ubuntu-latest
20
+ permissions:
21
+ contents: read
22
+ id-token: write
23
+ attestations: write
24
+
25
+ steps:
26
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
27
+ with:
28
+ fetch-depth: 0
29
+ persist-credentials: false
30
+
31
+ - uses: hynek/build-and-inspect-python-package@2abe76da66d0a6a4a227101f9348ee855797cfa5 # v3.0.1
32
+ with:
33
+ attest-build-provenance-github: true
34
+
35
+ publish-pypi:
36
+ name: Publish to PyPI
37
+ needs: [build]
38
+ runs-on: ubuntu-latest
39
+ environment:
40
+ name: pypi
41
+ url: https://pypi.org/p/jaxknn
42
+ permissions:
43
+ id-token: write
44
+ if: startsWith(github.ref, 'refs/tags/v')
45
+
46
+ steps:
47
+ - name: Download built artifact to dist/
48
+ uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
49
+ with:
50
+ name: Packages
51
+ path: dist
52
+
53
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
@@ -0,0 +1,145 @@
1
+ name: CI
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ pull_request:
6
+ push:
7
+ branches:
8
+ - main
9
+ schedule:
10
+ - cron: "0 0 * * 4" # Every Thursday at midnight UTC
11
+
12
+ permissions:
13
+ contents: read
14
+
15
+ concurrency:
16
+ # Each push to main gets its own group, so merges never cancel each other
17
+ # and main keeps a complete CI record. All other events keep the per-ref
18
+ # group and still supersede older runs.
19
+ group: >-
20
+ ${{ github.workflow }}-${{ github.event_name == 'push' && github.sha || github.ref }}
21
+ cancel-in-progress: true
22
+
23
+ env:
24
+ FORCE_COLOR: 3
25
+
26
+ jobs:
27
+ lint:
28
+ name: Lint
29
+ runs-on: ubuntu-latest
30
+ steps:
31
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
32
+
33
+ - name: Install uv
34
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
35
+
36
+ - name: Install
37
+ run: uv sync --no-default-groups --group lint --locked
38
+
39
+ - name: Checks with prek
40
+ # Not a real commit -- no-commit-to-branch would always fail here.
41
+ run: uv run --frozen prek run --all-files
42
+ env:
43
+ SKIP: no-commit-to-branch
44
+
45
+ tests:
46
+ name: Python ${{ matrix.python-version }} on ${{ matrix.os }}
47
+ runs-on: ${{ matrix.os }}
48
+ needs: [lint]
49
+ permissions:
50
+ contents: read
51
+ id-token: write # Codecov OIDC upload
52
+ strategy:
53
+ fail-fast: false
54
+ matrix:
55
+ python-version: ["3.12", "3.14"]
56
+ os: [ubuntu-latest, macos-latest, windows-latest]
57
+ steps:
58
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
59
+ with:
60
+ fetch-depth: 0
61
+
62
+ - name: Install uv
63
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
64
+ with:
65
+ python-version: ${{ matrix.python-version }}
66
+
67
+ - name: Install
68
+ run: uv sync --no-default-groups --group tests --locked
69
+
70
+ - name: Test with pytest
71
+ run: uv run --frozen pytest --cov --cov-report=xml --cov-report=term
72
+
73
+ - name: Upload coverage report
74
+ uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
75
+ with:
76
+ use_oidc: true
77
+
78
+ oldest-supported:
79
+ name: Oldest supported deps
80
+ runs-on: ubuntu-latest
81
+ needs: [lint]
82
+ steps:
83
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
84
+ with:
85
+ fetch-depth: 0
86
+
87
+ - name: Install uv
88
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
89
+ with:
90
+ python-version: "3.12"
91
+
92
+ - name: Install the project
93
+ run: uv sync --no-default-groups --group tests --resolution lowest-direct
94
+
95
+ - name: Test package
96
+ run: uv run --no-sync pytest
97
+
98
+ newest-supported:
99
+ name: Newest supported deps
100
+ runs-on: ubuntu-latest
101
+ needs: [lint]
102
+ steps:
103
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
104
+ with:
105
+ fetch-depth: 0
106
+
107
+ - name: Install uv
108
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
109
+ with:
110
+ python-version: "3.14"
111
+
112
+ - name: Install the project
113
+ run: uv sync --no-default-groups --group tests --upgrade
114
+
115
+ - name: Test package
116
+ run: uv run --no-sync pytest
117
+
118
+ benchmark-smoke:
119
+ name: Benchmark smoke test
120
+ runs-on: ubuntu-latest
121
+ needs: [lint]
122
+ steps:
123
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
124
+ with:
125
+ fetch-depth: 0
126
+
127
+ - name: Install uv
128
+ uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
129
+
130
+ - name: Install
131
+ run: uv sync --no-default-groups --group bench --locked
132
+
133
+ - name: Run the benchmarks once
134
+ run: uv run --frozen pytest tests/benchmarks --override-ini="addopts=" -p no:randomly
135
+
136
+ ci-pass:
137
+ name: CI Pass
138
+ runs-on: ubuntu-latest
139
+ needs: [lint, tests, oldest-supported, newest-supported, benchmark-smoke]
140
+ if: always()
141
+ steps:
142
+ - name: All required jobs passed
143
+ uses: re-actors/alls-green@b5b5b37504aa4183270bd3d855c52a67f212be35 # v1.3.0
144
+ with:
145
+ jobs: ${{ toJSON(needs) }}
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ build/
5
+ dist/
6
+ *.egg-info/
7
+ .coverage
8
+ coverage.xml
9
+ .pytest_cache/
10
+ .ruff_cache/
11
+ .hypothesis/
12
+ .codspeed/
13
+ site/
14
+ src/jaxknn/_version.py
@@ -0,0 +1,42 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: "v6.0.0"
4
+ hooks:
5
+ - id: no-commit-to-branch
6
+ args: ["--branch", "main"]
7
+ always_run: true
8
+ - id: check-added-large-files
9
+ - id: check-case-conflict
10
+ - id: check-merge-conflict
11
+ - id: check-yaml
12
+ - id: debug-statements
13
+ - id: end-of-file-fixer
14
+ - id: mixed-line-ending
15
+ - id: name-tests-test
16
+ args: ["--pytest-test-first"]
17
+ exclude: ^tests/(benchmarks/|_compat\.py$)
18
+ - id: trailing-whitespace
19
+
20
+ - repo: https://github.com/python-jsonschema/check-jsonschema
21
+ rev: 0.36.0
22
+ hooks:
23
+ - id: check-github-workflows
24
+
25
+ - repo: https://github.com/astral-sh/ruff-pre-commit
26
+ rev: v0.15.14
27
+ hooks:
28
+ - id: ruff-check
29
+ types_or: [python, pyi]
30
+ args: [--fix, --show-fixes]
31
+ - id: ruff-format
32
+ types_or: [python, pyi]
33
+
34
+ - repo: https://github.com/codespell-project/codespell
35
+ rev: "v2.4.1"
36
+ hooks:
37
+ - id: codespell
38
+
39
+ - repo: https://github.com/ComPWA/taplo-pre-commit
40
+ rev: v0.9.3
41
+ hooks:
42
+ - id: taplo-format
@@ -0,0 +1 @@
1
+ 3.12
jaxknn-0.1.0/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-2026, Nathaniel Starkman.
4
+ All rights reserved.
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
jaxknn-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,97 @@
1
+ Metadata-Version: 2.5
2
+ Name: jaxknn
3
+ Version: 0.1.0
4
+ Summary: Exact k-nearest neighbours in JAX: a kd-tree that traces under jit, vmap and grad.
5
+ Project-URL: Bug Tracker, https://github.com/nstarman/jaxknn/issues
6
+ Project-URL: Changelog, https://github.com/nstarman/jaxknn/releases
7
+ Project-URL: Documentation, https://nstarkman.space/jaxknn
8
+ Project-URL: Source Code, https://github.com/nstarman/jaxknn
9
+ Author-email: Nathaniel Starkman <nstarman@users.noreply.github.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: jax,kd-tree,knn,nearest-neighbors
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Natural Language :: English
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Programming Language :: Python :: 3.14
23
+ Classifier: Topic :: Scientific/Engineering
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.12
26
+ Requires-Dist: equinox>=0.13.5
27
+ Requires-Dist: jax>=0.7.2
28
+ Requires-Dist: jaxtyping>=0.3.5
29
+ Requires-Dist: numpy>=2.0
30
+ Description-Content-Type: text/markdown
31
+
32
+ # jaxknn
33
+
34
+ Exact k-nearest neighbours in [JAX](https://github.com/jax-ml/jax): a kd-tree that traces under `jit`, `vmap` and `grad`.
35
+
36
+ ```bash
37
+ pip install jaxknn
38
+ ```
39
+
40
+ ```python
41
+ >>> import jax
42
+ >>> import jax.numpy as jnp
43
+ >>> import jaxknn
44
+
45
+ >>> points = jax.random.normal(jax.random.key(0), (1000, 3))
46
+ >>> idx, dist = jaxknn.knn(points, 5) # each point's 5 nearest *other* points
47
+ >>> idx.shape, dist.shape
48
+ ((1000, 5), (1000, 5))
49
+
50
+ >>> queries = jnp.zeros((2, 3))
51
+ >>> idx, dist = jax.jit(lambda p, q: jaxknn.knn(p, 1, queries=q))(points, queries)
52
+ >>> idx.shape
53
+ (2, 1)
54
+
55
+ ```
56
+
57
+ ## What it gives you
58
+
59
+ - **Exact results.** No approximation. Ties go to the lower index, identically eagerly and under `jit`.
60
+ - **Fully traceable.** `knn` works inside `jit`, `vmap` and `grad`. Distances are differentiable with respect to the coordinates, with the neighbour selection held fixed, and the gradient is 0 rather than NaN at coincident points.
61
+ - **Robust inputs.**
62
+ - NaN or inf input is an error, not a silently wrong answer: `ValueError` eagerly, a runtime error under `jit`.
63
+ - Integers and bfloat16 are promoted to float32.
64
+ - Coordinates are divided by an exact power of two when float32 would overflow or underflow, so values from about 1e-38 to 3e38 keep their neighbours. Typical cases are metres at kiloparsec scale, or tiny lengths.
65
+ - **Compiles once per size bucket.** Eager calls pad `n` to a bucket (2ʲ or 1.5·2ʲ), so inputs of different sizes share compiled code.
66
+ - **A raw tree for custom queries.** `jaxknn.tree` exposes `build_tree`, `knn`, `all_knn` and label-excluding queries (`knn(tree, q, k, exclude=(point_labels, query_labels))`: the nearest point with a *different* label, with whole same-label subtrees pruned).
67
+
68
+ `jaxknn.brute_knn` has the same contract by brute force. It's faster for small inputs.
69
+
70
+ ## When to use it
71
+
72
+ Use jaxknn when the kNN has to live inside a traced JAX program: under `jit`, `vmap` or `grad`, or on an accelerator.
73
+
74
+ On a CPU, eagerly, SciPy's `cKDTree` is faster, because it queries with every core. Here's all-points kNN (k=10, a 3-D stream with 2% interlopers), comparing jaxknn compiled under `jit` with SciPy called eagerly. It was measured on an Apple M2 Pro under load, so the ratios matter more than the absolute times.
75
+
76
+ | n | `jit` compile (first call) | jaxknn under `jit`, steady | SciPy, eager | jaxknn / SciPy |
77
+ |---|---|---|---|---|
78
+ | 2,000 | 9.4 s | 17.8 ms | 6.0 ms | 2.9× |
79
+ | 10,000 | 11.6 s | 106 ms | 16.4 ms | 6.5× |
80
+ | 100,000 | 13.2 s | 1,261 ms | 148 ms | 8.5× |
81
+ | 1,000,000 | 26.8 s | 11,719 ms | 1,990 ms | 5.9× |
82
+
83
+ Against [jaxkd](https://github.com/dodgebc/jaxkd) at n = 100,000 (all-points, k = 10, CPU):
84
+
85
+ | data | jaxknn | jaxkd | SciPy |
86
+ |---|---|---|---|
87
+ | stream | 307 ms | 815 ms | 73 ms |
88
+ | blob | 355 ms | 1,161 ms | 80 ms |
89
+ | stream + interlopers | 513 ms | ~140 s | 78 ms |
90
+
91
+ ## Origin
92
+
93
+ jaxknn was extracted from [phasecurvefit](https://github.com/GalacticDynamics/phasecurvefit), where it is the traceable kNN behind `MSTOrderer`, with its history. See [phasecurvefit#197](https://github.com/GalacticDynamics/phasecurvefit/issues/197).
94
+
95
+ ## License
96
+
97
+ MIT
jaxknn-0.1.0/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # jaxknn
2
+
3
+ Exact k-nearest neighbours in [JAX](https://github.com/jax-ml/jax): a kd-tree that traces under `jit`, `vmap` and `grad`.
4
+
5
+ ```bash
6
+ pip install jaxknn
7
+ ```
8
+
9
+ ```python
10
+ >>> import jax
11
+ >>> import jax.numpy as jnp
12
+ >>> import jaxknn
13
+
14
+ >>> points = jax.random.normal(jax.random.key(0), (1000, 3))
15
+ >>> idx, dist = jaxknn.knn(points, 5) # each point's 5 nearest *other* points
16
+ >>> idx.shape, dist.shape
17
+ ((1000, 5), (1000, 5))
18
+
19
+ >>> queries = jnp.zeros((2, 3))
20
+ >>> idx, dist = jax.jit(lambda p, q: jaxknn.knn(p, 1, queries=q))(points, queries)
21
+ >>> idx.shape
22
+ (2, 1)
23
+
24
+ ```
25
+
26
+ ## What it gives you
27
+
28
+ - **Exact results.** No approximation. Ties go to the lower index, identically eagerly and under `jit`.
29
+ - **Fully traceable.** `knn` works inside `jit`, `vmap` and `grad`. Distances are differentiable with respect to the coordinates, with the neighbour selection held fixed, and the gradient is 0 rather than NaN at coincident points.
30
+ - **Robust inputs.**
31
+ - NaN or inf input is an error, not a silently wrong answer: `ValueError` eagerly, a runtime error under `jit`.
32
+ - Integers and bfloat16 are promoted to float32.
33
+ - Coordinates are divided by an exact power of two when float32 would overflow or underflow, so values from about 1e-38 to 3e38 keep their neighbours. Typical cases are metres at kiloparsec scale, or tiny lengths.
34
+ - **Compiles once per size bucket.** Eager calls pad `n` to a bucket (2ʲ or 1.5·2ʲ), so inputs of different sizes share compiled code.
35
+ - **A raw tree for custom queries.** `jaxknn.tree` exposes `build_tree`, `knn`, `all_knn` and label-excluding queries (`knn(tree, q, k, exclude=(point_labels, query_labels))`: the nearest point with a *different* label, with whole same-label subtrees pruned).
36
+
37
+ `jaxknn.brute_knn` has the same contract by brute force. It's faster for small inputs.
38
+
39
+ ## When to use it
40
+
41
+ Use jaxknn when the kNN has to live inside a traced JAX program: under `jit`, `vmap` or `grad`, or on an accelerator.
42
+
43
+ On a CPU, eagerly, SciPy's `cKDTree` is faster, because it queries with every core. Here's all-points kNN (k=10, a 3-D stream with 2% interlopers), comparing jaxknn compiled under `jit` with SciPy called eagerly. It was measured on an Apple M2 Pro under load, so the ratios matter more than the absolute times.
44
+
45
+ | n | `jit` compile (first call) | jaxknn under `jit`, steady | SciPy, eager | jaxknn / SciPy |
46
+ |---|---|---|---|---|
47
+ | 2,000 | 9.4 s | 17.8 ms | 6.0 ms | 2.9× |
48
+ | 10,000 | 11.6 s | 106 ms | 16.4 ms | 6.5× |
49
+ | 100,000 | 13.2 s | 1,261 ms | 148 ms | 8.5× |
50
+ | 1,000,000 | 26.8 s | 11,719 ms | 1,990 ms | 5.9× |
51
+
52
+ Against [jaxkd](https://github.com/dodgebc/jaxkd) at n = 100,000 (all-points, k = 10, CPU):
53
+
54
+ | data | jaxknn | jaxkd | SciPy |
55
+ |---|---|---|---|
56
+ | stream | 307 ms | 815 ms | 73 ms |
57
+ | blob | 355 ms | 1,161 ms | 80 ms |
58
+ | stream + interlopers | 513 ms | ~140 s | 78 ms |
59
+
60
+ ## Origin
61
+
62
+ jaxknn was extracted from [phasecurvefit](https://github.com/GalacticDynamics/phasecurvefit), where it is the traceable kNN behind `MSTOrderer`, with its history. See [phasecurvefit#197](https://github.com/GalacticDynamics/phasecurvefit/issues/197).
63
+
64
+ ## License
65
+
66
+ MIT
@@ -0,0 +1,19 @@
1
+ """Doctest the code blocks in the README, the docs and the docstrings."""
2
+
3
+ from doctest import ELLIPSIS, NORMALIZE_WHITESPACE
4
+
5
+ from sybil import Sybil
6
+ from sybil.parsers import markdown, rest
7
+
8
+ _flags = ELLIPSIS | NORMALIZE_WHITESPACE
9
+
10
+ pytest_collect_file = (
11
+ Sybil(
12
+ parsers=[
13
+ markdown.PythonCodeBlockParser(doctest_optionflags=_flags),
14
+ markdown.SkipParser(),
15
+ ],
16
+ patterns=["*.md"],
17
+ )
18
+ + Sybil(parsers=[rest.DocTestParser(optionflags=_flags)], patterns=["*.py"])
19
+ ).pytest()
@@ -0,0 +1,10 @@
1
+ {% import "partials/language.html" as lang with context %}
2
+ <a href="{{ config.repo_url }}" title="{{ lang.t('source.link.title') }}" class="md-source" data-md-component="source">
3
+ <div class="md-source__icon md-icon">
4
+ {% set icon = config.theme.icon.repo or "fontawesome/brands/git-alt" %}
5
+ {% include ".icons/" ~ icon ~ ".svg" %}
6
+ </div>
7
+ <div class="md-source__repository">
8
+ {{ config.repo_name }}
9
+ </div>
10
+ </a>