use cubecl::prelude::*;
use dspk_core::compute::element_position;
use dspk_ops::core::DspFloat;
pub const NO_LABEL: u32 = u32::MAX;
#[cube(launch)]
pub fn sq_norms_kernel<F: Float>(x: &[F], out: &mut [F], n: u32, d: u32) {
let i = element_position() as u32;
if i < n {
let mut acc = F::new(0.0f32);
let mut f = 0u32;
while f < d {
let v = x[(f * n + i) as usize];
acc += v * v;
f += 1u32;
}
out[i as usize] = acc;
}
}
#[cube(launch)]
#[allow(clippy::too_many_arguments)]
pub fn select_neighbours_kernel<F: Float>(
dots: &[F],
left_norms: &[F],
right_norms: &[F],
subset: &[u32],
nb: &mut [u32],
row0: u32,
rows: u32,
m: u32,
#[comptime] k: u32,
) {
let r = element_position() as u32;
if r < rows {
let row = row0 + r;
let last = comptime!(k - 1);
let mut kd = Array::<F>::new(comptime!(k as usize));
let mut kj = Array::<u32>::new(comptime!(k as usize));
#[unroll]
for s in 0..k {
kd[s as usize] = F::max_value();
kj[s as usize] = 0u32;
}
let own = left_norms[row as usize];
let mut j = 0u32;
while j < m {
if subset[j as usize] != row {
let dist = own + right_norms[j as usize] - F::new(2.0f32) * dots[(r * m + j) as usize];
if dist < kd[last as usize] {
kd[last as usize] = dist;
kj[last as usize] = j;
#[unroll]
for step in 0..last {
let s = comptime!(last - step);
if kd[s as usize] < kd[(s - 1) as usize] {
let (dd, jj) = (kd[s as usize], kj[s as usize]);
kd[s as usize] = kd[(s - 1) as usize];
kj[s as usize] = kj[(s - 1) as usize];
kd[(s - 1) as usize] = dd;
kj[(s - 1) as usize] = jj;
}
}
}
}
j += 1u32;
}
#[unroll]
for s in 0..k {
nb[(row * k + s) as usize] = kj[s as usize];
}
}
}
#[cube(launch)]
pub fn count_values_kernel(values: &[u32], counts: &mut [Atomic<u32>], n: u32, bins: u32) {
let i = element_position() as u32;
if i < n {
let v = values[i as usize];
if v < bins {
Atomic::fetch_add(&counts[v as usize], 1u32);
}
}
}
#[cube(launch)]
pub fn right_counts_kernel(nb: &[u32], left: &[u32], counts: &mut [Atomic<u32>], edges: u32, k: u32, c: u32) {
let e = element_position() as u32;
if e < edges {
let t = e / k;
let label = left[t as usize];
if label < c {
Atomic::fetch_add(&counts[(nb[e as usize] * c + label) as usize], 1u32);
}
}
}
#[cube(launch)]
#[allow(clippy::too_many_arguments)]
pub fn right_assign_kernel<F: DspFloat>(
counts: &[u32],
degree: &[u32],
k_left: &[u32],
right: &mut [u32],
m: u32,
c: u32,
penalty: F,
) {
let r = element_position() as u32;
if r < m {
let deg = F::cast_from(degree[r as usize]);
let mut best = F::min_value();
let mut best_c = right[r as usize];
let mut found = false;
let mut j = 0u32;
while j < c {
let n_rc = counts[(r * c + j) as usize];
if n_rc > 0u32 {
let gain = F::cast_from(n_rc) - penalty * deg * F::cast_from(k_left[j as usize]);
if !found || gain > best {
best = gain;
best_c = j;
found = true;
}
}
j += 1u32;
}
right[r as usize] = best_c;
}
}
#[cube(launch)]
pub fn degree_sums_kernel(labels: &[u32], degree: &[u32], sums: &mut [Atomic<u32>], m: u32, c: u32) {
let r = element_position() as u32;
if r < m {
let label = labels[r as usize];
if label < c {
Atomic::fetch_add(&sums[label as usize], degree[r as usize]);
}
}
}
#[cube(launch)]
#[allow(clippy::too_many_arguments)]
pub fn left_assign_kernel<F: DspFloat>(
nb: &[u32],
right: &[u32],
k_right: &[u32],
left: &mut [u32],
changed: &mut [Atomic<u32>],
n: u32,
c: u32,
penalty: F,
#[comptime] k: u32,
) {
let t = element_position() as u32;
if t < n {
let mut labels = Array::<u32>::new(comptime!(k as usize));
#[unroll]
for s in 0..k {
labels[s as usize] = right[nb[(t * k + s) as usize] as usize];
}
let deg = F::cast_from(k);
let mut best = F::min_value();
let mut best_c = left[t as usize];
let mut found = false;
#[unroll]
for s in 0..k {
let label = labels[s as usize];
if label < c {
let mut n_tc = 0u32;
#[unroll]
for q in 0..k {
if labels[q as usize] == label {
n_tc += 1u32;
}
}
let gain = F::cast_from(n_tc) - penalty * deg * F::cast_from(k_right[label as usize]);
if !found || gain > best || (gain == best && label < best_c) {
best = gain;
best_c = label;
found = true;
}
}
}
if best_c != left[t as usize] {
left[t as usize] = best_c;
Atomic::fetch_add(&changed[0], 1u32);
}
}
}
#[cube(launch)]
pub fn relabel_kernel(labels: &mut [u32], map: &[u32], n: u32, c: u32) {
let i = element_position() as u32;
if i < n {
let label = labels[i as usize];
if label < c {
labels[i as usize] = map[label as usize];
} else {
labels[i as usize] = NO_LABEL;
}
}
}
#[cube(launch)]
pub fn cluster_edges_kernel(nb: &[u32], left: &[u32], right: &[u32], edges: &mut [Atomic<u32>], n_edges: u32, k: u32, c: u32) {
let e = element_position() as u32;
if e < n_edges {
let a = left[(e / k) as usize];
let b = right[nb[e as usize] as usize];
if a < c && b < c {
Atomic::fetch_add(&edges[(a * c + b) as usize], 1u32);
}
}
}
#[cube(launch)]
#[allow(clippy::too_many_arguments)]
pub fn trial_potentials_kernel<F: Float>(
x: &[F],
closest: &[F],
cand: &[u32],
partial: &mut [F],
n: u32,
d: u32,
trials: u32,
block: u32,
blocks: u32,
) {
let unit = element_position() as u32;
if unit < trials * blocks {
let t = unit / blocks;
let b = unit - t * blocks;
let c = cand[t as usize];
let end = u32::min((b + 1u32) * block, n);
let mut acc = F::new(0.0f32);
let mut i = b * block;
while i < end {
let mut dist = F::new(0.0f32);
let mut f = 0u32;
while f < d {
let diff = x[(f * n + i) as usize] - x[(f * n + c) as usize];
dist += diff * diff;
f += 1u32;
}
acc += F::min(dist, closest[i as usize]);
i += 1u32;
}
partial[unit as usize] = acc;
}
}
#[cube(launch)]
#[allow(clippy::too_many_arguments)]
pub fn draw_candidates_kernel<F: Float>(
closest: &[F],
sums: &[F],
uniforms: &[F],
cand: &mut [u32],
n: u32,
block: u32,
blocks: u32,
trials: u32,
s: u32,
) {
let t = element_position() as u32;
if t < trials {
let mut total = F::new(0.0f32);
let mut b = 0u32;
while b < blocks {
total += sums[b as usize];
b += 1u32;
}
let mut r = uniforms[(s * trials + t) as usize] * total;
let mut chosen = blocks - 1u32;
let mut found = false;
b = 0u32;
while b < blocks {
if !found {
let sb = sums[b as usize];
if r < sb {
chosen = b;
found = true;
} else {
r -= sb;
}
}
b += 1u32;
}
let start = chosen * block;
let end = u32::min(start + block, n);
let mut pick = end - 1u32;
let mut hit = false;
let mut i = start;
while i < end {
if !hit {
let w = closest[i as usize];
if r < w {
pick = i;
hit = true;
} else {
r -= w;
}
}
i += 1u32;
}
cand[t as usize] = pick;
}
}
#[cube(launch)]
pub fn pick_seed_kernel<F: Float>(partial: &[F], cand: &[u32], seeds: &mut [u32], chosen: &mut [u32], trials: u32, blocks: u32, s: u32) {
if element_position() == 0 {
let mut best = F::max_value();
let mut best_t = 0u32;
let mut t = 0u32;
while t < trials {
let mut pot = F::new(0.0f32);
let mut b = 0u32;
while b < blocks {
pot += partial[(t * blocks + b) as usize];
b += 1u32;
}
if pot < best {
best = pot;
best_t = t;
}
t += 1u32;
}
seeds[s as usize] = cand[best_t as usize];
chosen[0] = cand[best_t as usize];
}
}
#[cube(launch)]
pub fn closest_update_chosen_kernel<F: Float>(x: &[F], closest: &mut [F], chosen: &[u32], n: u32, d: u32) {
let i = element_position() as u32;
if i < n {
let c = chosen[0];
let mut dist = F::new(0.0f32);
let mut f = 0u32;
while f < d {
let diff = x[(f * n + i) as usize] - x[(f * n + c) as usize];
dist += diff * diff;
f += 1u32;
}
closest[i as usize] = F::min(closest[i as usize], dist);
}
}