Python/data_structures/kd_tree/nearest_neighbour_search.py
Ramy f16d38f26f
kd tree data structure implementation (#11532)
* Implemented KD-Tree Data Structure

* Implemented KD-Tree Data Structure. updated DIRECTORY.md.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Create __init__.py

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Replaced legacy `np.random.rand` call with `np.random.Generator` in kd_tree/example_usage.py

* Replaced legacy `np.random.rand` call with `np.random.Generator` in kd_tree/hypercube_points.py

* added typehints and docstrings

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* docstring for search()

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Added tests. Updated docstrings/typehints

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* updated tests and used | for type annotations

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* E501 for build_kdtree.py, hypercube_points.py, nearest_neighbour_search.py

* I001 for example_usage.py and test_kdtree.py

* I001 for example_usage.py and test_kdtree.py

* Update data_structures/kd_tree/build_kdtree.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* Update data_structures/kd_tree/example/hypercube_points.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* Update data_structures/kd_tree/example/hypercube_points.py

Co-authored-by: Christian Clauss <cclauss@me.com>

* Added new test cases requested in Review. Refactored the test_build_kdtree() to include various checks.

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Considered ruff errors

* Considered ruff errors

* Apply suggestions from code review

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Update kd_node.py

* imported annotations from __future__

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Christian Clauss <cclauss@me.com>
2024-09-03 14:39:09 +02:00

72 lines
2.4 KiB
Python

from data_structures.kd_tree.kd_node import KDNode
def nearest_neighbour_search(
root: KDNode | None, query_point: list[float]
) -> tuple[list[float] | None, float, int]:
"""
Performs a nearest neighbor search in a KD-Tree for a given query point.
Args:
root (KDNode | None): The root node of the KD-Tree.
query_point (list[float]): The point for which the nearest neighbor
is being searched.
Returns:
tuple[list[float] | None, float, int]:
- The nearest point found in the KD-Tree to the query point,
or None if no point is found.
- The squared distance to the nearest point.
- The number of nodes visited during the search.
"""
nearest_point: list[float] | None = None
nearest_dist: float = float("inf")
nodes_visited: int = 0
def search(node: KDNode | None, depth: int = 0) -> None:
"""
Recursively searches for the nearest neighbor in the KD-Tree.
Args:
node: The current node in the KD-Tree.
depth: The current depth in the KD-Tree.
"""
nonlocal nearest_point, nearest_dist, nodes_visited
if node is None:
return
nodes_visited += 1
# Calculate the current distance (squared distance)
current_point = node.point
current_dist = sum(
(query_coord - point_coord) ** 2
for query_coord, point_coord in zip(query_point, current_point)
)
# Update nearest point if the current node is closer
if nearest_point is None or current_dist < nearest_dist:
nearest_point = current_point
nearest_dist = current_dist
# Determine which subtree to search first (based on axis and query point)
k = len(query_point) # Dimensionality of points
axis = depth % k
if query_point[axis] <= current_point[axis]:
nearer_subtree = node.left
further_subtree = node.right
else:
nearer_subtree = node.right
further_subtree = node.left
# Search the nearer subtree first
search(nearer_subtree, depth + 1)
# If the further subtree has a closer point
if (query_point[axis] - current_point[axis]) ** 2 < nearest_dist:
search(further_subtree, depth + 1)
search(root, 0)
return nearest_point, nearest_dist, nodes_visited