use std::collections::BTreeMap;
use cubecl::prelude::*;
use cubecl::server::Handle;
use dspk_core::compute::LaunchGeometry;
use super::subtraction::{TemplateFilter, MIN_TEMPLATE_ENERGY};
use super::kernels::{template_scores_kernel, template_subtract_kernel};
use crate::core::{buffer, cast, cast_f32, DspFloat};
fn layers(starts: &[i64], window: i64) -> Vec<usize> {
let mut placed: BTreeMap<i64, Vec<usize>> = BTreeMap::new();
starts
.iter()
.map(|&s| {
let layer = placed
.range(s - window + 1..s + window)
.flat_map(|(_, ls)| ls.iter().copied())
.max()
.map_or(0, |l| l + 1);
placed.entry(s).or_default().push(layer);
layer
})
.collect()
}
impl TemplateFilter {
pub fn apply_device<F: DspFloat>(
&self,
client: &Client,
signal: &Handle,
channels: usize,
samples: usize,
event_indices: &[u64],
) {
assert_eq!(channels, self.template_channels, "channel count must match between signal and template");
let t_len = self.template_samples;
if t_len == 0 || samples == 0 || event_indices.is_empty() {
return;
}
let energies: Vec<f32> = self.template.chunks(t_len).map(|row| row.iter().map(|v| v * v).sum()).collect();
let template = buffer::upload(client, &cast_f32::<F>(&self.template));
let energies = buffer::upload(client, &cast_f32::<F>(&energies));
let lags = 2 * self.max_lag + 1;
let starts: Vec<i64> = event_indices.iter().map(|&ev| ev as i64 - self.center_offset as i64).collect();
let event_layer = layers(&starts, (t_len + 2 * self.max_lag) as i64);
let num_layers = event_layer.iter().max().map_or(0, |l| l + 1);
for layer in 0..num_layers {
let layer_starts: Vec<i32> =
starts.iter().zip(&event_layer).filter(|(_, l)| **l == layer).map(|(s, _)| *s as i32).collect();
let n = layer_starts.len();
let starts_h = buffer::upload(client, &layer_starts);
let scores = buffer::empty::<F>(client, n * lags);
let geom = LaunchGeometry::elementwise(client, n * lags);
unsafe {
template_scores_kernel::launch::<F>(
client,
geom.cube_count,
geom.cube_dim,
BufferArg::from_raw_parts(signal.clone(), channels * samples),
BufferArg::from_raw_parts(template.clone(), channels * t_len),
BufferArg::from_raw_parts(starts_h.clone(), n),
BufferArg::from_raw_parts(scores.clone(), n * lags),
channels as u32,
samples as u32,
t_len as u32,
n as u32,
lags as u32,
self.max_lag as u32,
);
}
let geom = LaunchGeometry::elementwise(client, n * channels);
unsafe {
template_subtract_kernel::launch::<F>(
client,
geom.cube_count,
geom.cube_dim,
BufferArg::from_raw_parts(signal.clone(), channels * samples),
BufferArg::from_raw_parts(template.clone(), channels * t_len),
BufferArg::from_raw_parts(energies.clone(), channels),
BufferArg::from_raw_parts(starts_h, n),
BufferArg::from_raw_parts(scores, n * lags),
channels as u32,
samples as u32,
t_len as u32,
n as u32,
lags as u32,
self.max_lag as u32,
self.dynamic_scaling as u32,
cast::<F>(MIN_TEMPLATE_ENERGY as f64),
);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn overlapping_events_get_later_layers() {
assert_eq!(layers(&[0, 5, 30, 8], 10), vec![0, 1, 0, 2]);
}
fn matches_host(client: &Client) {
let (channels, samples, t_len) = (3usize, 400usize, 9usize);
let template: Vec<f32> = (0..channels * t_len).map(|i| ((i * 37) % 17) as f32 - 8.0).collect();
let filter = TemplateFilter::new_multichannel(template.clone(), channels, t_len, 4).with_max_lag(3);
let mut signal: Vec<f32> = (0..channels * samples).map(|i| ((i * 7919) % 23) as f32 * 0.1).collect();
let events = [50u64, 55, 200, 60, 390, 2];
for (k, &ev) in events.iter().enumerate() {
for c in 0..channels {
for t in 0..t_len {
let at = ev as isize - 4 + t as isize + (k % 3) as isize - 1;
if (0..samples as isize).contains(&at) {
signal[c * samples + at as usize] += (1.5 + k as f32 * 0.2) * template[c * t_len + t];
}
}
}
}
let mut host = signal.clone();
filter.apply_multichannel(&mut host, channels, samples, &events);
let device = buffer::upload(client, &signal);
filter.apply_device::<f32>(client, &device, channels, samples, &events);
let got = buffer::download::<f32>(client, device);
for (i, (g, h)) in got.iter().zip(&host).enumerate() {
assert!((g - h).abs() < 1e-3, "{} sample {i}: {g} vs {h}", client.name());
}
}
runtime_test!(test_template_device_matches_host, matches_host);
}