105 const std::size_t n = X.
dim(0);
106 const std::size_t d = X.
dim(1);
118 out.
edges.reserve(n - 1);
121 const T *xData = X.
data();
122 const bool useDenseCore = shouldUseDenseCore(n, d, minSamples);
128 const bool rowsAligned32 =
129 X.template isAligned<32>() && (d % (std::size_t{32} /
sizeof(T)) == 0);
131 std::vector<T> rowNorms;
134 for (std::size_t i = 0; i < n; ++i) {
135 const T *row = xData + (i * d);
136 rowNorms[i] = rowsAligned32 ? math::detail::dotRowAligned32Ptr(row, row, d)
137 : math::detail::dotRowPtr(row, row, d);
139 computeDenseCoreDistances(X, rowNorms, minSamples, rowsAligned32, pool, coreDistData);
145 const auto kSigned =
static_cast<std::int32_t
>(minSamples);
146 auto [knnIdx, knnSqDist] = tree.
knnQuery(kSigned, pool);
148 for (std::size_t i = 0; i < n; ++i) {
149 coreDistData[i] = knnSqDist(i, minSamples - 1);
158 std::vector<std::uint8_t> visited(n, std::uint8_t{0});
159 std::vector<std::int32_t> parent(n, std::int32_t{0});
160 std::vector<T> edgeWeight(n, std::numeric_limits<T>::max());
162 auto sqDistance = [&](
const T *rowT, std::size_t tIdx, std::size_t v)
noexcept {
164 const T *rowV = xData + (v * d);
165 const T dot = rowsAligned32 ? math::detail::dotRowAligned32Ptr(rowT, rowV, d)
166 : math::detail::dotRowPtr(rowT, rowV, d);
167 return math::detail::sqEuclideanFromDot(rowNorms[tIdx], rowNorms[v], dot);
169 return math::detail::sqEuclideanRowPtr(rowT, xData + (v * d), d);
172 auto relaxRange = [&](std::size_t lo, std::size_t hi, std::int32_t target, std::size_t tIdx,
173 T coreT,
const T *rowT)
noexcept {
174 for (std::size_t v = lo; v < hi; ++v) {
175 if (visited[v] != 0U) {
178 const T sq = sqDistance(rowT, tIdx, v);
183 const T coreV = coreDistData[v];
187 if (w < edgeWeight[v]) {
194 auto relaxRangeAndFindNext = [&](std::size_t lo, std::size_t hi, std::int32_t target,
195 std::size_t tIdx, T coreT,
196 const T *rowT)
noexcept -> std::pair<std::int32_t, T> {
197 std::int32_t bestV = -1;
198 T bestW = std::numeric_limits<T>::max();
199 for (std::size_t v = lo; v < hi; ++v) {
200 if (visited[v] != 0U) {
203 const T sq = sqDistance(rowT, tIdx, v);
208 const T coreV = coreDistData[v];
212 if (w < edgeWeight[v]) {
216 if (edgeWeight[v] < bestW) {
217 bestW = edgeWeight[v];
218 bestV =
static_cast<std::int32_t
>(v);
221 return {bestV, bestW};
224 auto findNext = [&]()
noexcept -> std::pair<std::int32_t, T> {
225 std::int32_t bestV = -1;
226 T bestW = std::numeric_limits<T>::max();
227 for (std::size_t v = 0; v < n; ++v) {
228 if (visited[v] != 0U) {
231 if (edgeWeight[v] < bestW) {
232 bestW = edgeWeight[v];
233 bestV =
static_cast<std::int32_t
>(v);
236 return {bestV, bestW};
239 auto persistentRelaxFrom = [&]() ->
bool {
240 if (!shouldUsePersistentParallelRelax(n, d, useDenseCore, pool)) {
244 const std::size_t participantCount = pool.
workerCount();
247 struct alignas(64) LocalBest {
248 std::int32_t vertex = -1;
249 T weight = std::numeric_limits<T>::max();
250 std::int32_t pad0 = 0;
252 std::vector<LocalBest> localBest(participantCount);
254 auto blockBegin = [&](std::size_t id)
noexcept {
return (n *
id) / participantCount; };
255 auto blockEnd = [&](std::size_t id)
noexcept {
return (n * (
id + 1)) / participantCount; };
256 auto relaxBlock = [&](std::size_t id,
257 std::int32_t target)
noexcept -> std::pair<std::int32_t, T> {
258 const auto tIdx =
static_cast<std::size_t
>(target);
259 const T coreT = coreDistData[tIdx];
260 const T *
const rowT = xData + (tIdx * d);
261 std::int32_t bestV = -1;
262 T bestW = std::numeric_limits<T>::max();
263 for (std::size_t v = blockBegin(
id); v < blockEnd(
id); ++v) {
264 if (visited[v] != 0U) {
267 const T sq = sqDistance(rowT, tIdx, v);
272 const T coreV = coreDistData[v];
276 if (w < edgeWeight[v]) {
280 if (edgeWeight[v] < bestW) {
281 bestW = edgeWeight[v];
282 bestV =
static_cast<std::int32_t
>(v);
285 return {bestV, bestW};
288 auto reduceBest = [&]()
noexcept -> std::pair<std::int32_t, T> {
289 std::int32_t bestV = -1;
290 T bestW = std::numeric_limits<T>::max();
291 for (
const LocalBest &candidate : localBest) {
292 if (candidate.vertex >= 0 && candidate.weight < bestW) {
293 bestW = candidate.weight;
294 bestV = candidate.vertex;
297 return {bestV, bestW};
307 edgeWeight[0] = T{0};
308 std::int32_t phaseTarget = 0;
310 auto prePhase = [&](std::size_t phaseIdx)
noexcept {
316 auto [bv, bw] = reduceBest();
318 const auto bIdx =
static_cast<std::size_t
>(bv);
324 auto phaseFn = [&](std::size_t , std::uint32_t slot, std::size_t ,
325 std::size_t ,
void * =
nullptr)
noexcept {
326 auto [bv, bw] = relaxBlock(slot, phaseTarget);
327 localBest[slot].vertex = bv;
328 localBest[slot].weight = bw;
331 const std::size_t totalPhases = n - 1;
332 pool.
parallelRunPlex<citor::HintsDefaults>(totalPhases, n, std::move(phaseFn),
333 std::move(prePhase));
335 auto [bv, bw] = reduceBest();
337 const auto bIdx =
static_cast<std::size_t
>(bv);
343 if (persistentRelaxFrom()) {
347 auto relaxFrom = [&](std::int32_t target)
noexcept -> std::pair<std::int32_t, T> {
348 const auto tIdx =
static_cast<std::size_t
>(target);
349 const T coreT = coreDistData[tIdx];
350 const T *rowT = xData + (tIdx * d);
355 std::size_t{0}, n, std::size_t{0},
356 [&](std::size_t lo, std::size_t hi) { relaxRange(lo, hi, target, tIdx, coreT, rowT); });
359 return relaxRangeAndFindNext(0, n, target, tIdx, coreT, rowT);
365 edgeWeight[0] = T{0};
366 auto [nextV, nextW] = relaxFrom(
static_cast<std::int32_t
>(0));
368 while (out.
edges.size() + 1 < n) {
374 const auto bIdx =
static_cast<std::size_t
>(nextV);
378 if (out.
edges.size() + 1 == n) {
381 auto next = relaxFrom(nextV);