Clustering
C++20 header-only: DBSCAN, HDBSCAN, k-means.
Loading...
Searching...
No Matches
brute_force_pairwise.h
Go to the documentation of this file.
1#pragma once
2
3#include <cstddef>
4#include <cstdint>
5#include <type_traits>
6#include <vector>
7
10#include "clustering/ndarray.h"
11
12namespace clustering {
13
28template <class T> class BruteForcePairwise {
29public:
30 static_assert(std::is_same_v<T, float> || std::is_same_v<T, double>,
31 "BruteForcePairwise<T> requires T to be float or double");
32
38 explicit BruteForcePairwise(const NDArray<T, 2> &points) noexcept : m_points(points) {}
39
51 [[nodiscard]] std::vector<std::vector<std::int32_t>> query(T radius, math::Pool pool) const {
52 const std::size_t n = m_points.dim(0);
53 std::vector<std::vector<std::int32_t>> adj(n);
54 if (n == 0) {
55 return adj;
56 }
57
58 // Reserve a small floor per row so the first push_backs do not trigger the vector-doubling
59 // reallocation cascade that otherwise dominates adjacency construction on dense fixtures.
60 for (auto &v : adj) {
61 v.reserve(16);
62 }
63
64 const T radiusSq = radius * radius;
65 // Symmetric eps-neighbour graph: the kernel emits each unique upper-triangular cell once
66 // with `row <= col`. The kernel-side emit only touches `adj[row]` so workers writing
67 // disjoint row chunks remain race-free; the mirror push (`adj[col]`.push(row)) runs as a
68 // single-threaded post-pass below. Halves the pairwise compute on the brute-force path.
69 auto emit = [&adj](std::size_t row, std::size_t col) {
70 adj[row].push_back(static_cast<std::int32_t>(col));
71 };
72 math::pairwiseSqEuclideanThresholdedSymmetric(m_points, radiusSq, pool, emit);
73
74 // Mirror pass: each surviving upper-triangular pair `(i, j)` lives in `adj[i]`; push
75 // @c i to `adj[j]` sequentially so the parallel sweep above never crosses worker
76 // boundaries. The size snapshot stops the loop from walking the freshly-mirrored entries
77 // when the next outer @c i lands on a row that earlier iterations already augmented.
78 for (std::size_t i = 0; i < n; ++i) {
79 const std::size_t origSize = adj[i].size();
80 for (std::size_t k = 0; k < origSize; ++k) {
81 const auto jSigned = adj[i][k];
82 const auto j = static_cast<std::size_t>(jSigned);
83 if (j > i) {
84 adj[j].push_back(static_cast<std::int32_t>(i));
85 }
86 }
87 }
88 return adj;
89 }
90
91private:
92 const NDArray<T, 2> &m_points;
93};
94
95} // namespace clustering
BruteForcePairwise(const NDArray< T, 2 > &points) noexcept
Constructs the backend over a borrowed point matrix.
std::vector< std::vector< std::int32_t > > query(T radius, math::Pool pool) const
Returns the full radius-neighborhood adjacency over the indexed point cloud.
Represents a multidimensional array (NDArray) of a fixed number of dimensions N and element type T.
Definition ndarray.h:136
void pairwiseSqEuclideanThresholdedSymmetric(const NDArray< T, 2, LX > &X, T radiusSq, Pool pool, Emit &&emit)
Symmetric variant of pairwiseSqEuclideanThresholded for the X == Y case.
Definition pairwise.h:583
Thin injection wrapper around a BS::light_thread_pool.
Definition thread.h:63