use candle_core::{IndexOp, Tensor};
use crate::error::Result;
pub fn second_argmax(input: &Tensor) -> Result<Tensor> {
let sorted_indices = input.arg_sort_last_dim(false)?;
let second = sorted_indices.i((.., 1))?;
Ok(second)
}
pub fn argmedian(input: &Tensor) -> Result<Tensor> {
let (_, seq_len) = input.dims2()?;
let sorted_indices = input.arg_sort_last_dim(true)?;
let median_idx = seq_len / 2;
let median_pos = sorted_indices.i((.., median_idx))?;
Ok(median_pos)
}
pub fn median(input: &Tensor) -> Result<Tensor> {
let (_, seq_len) = input.dims2()?;
let (sorted, _indices) = input.sort_last_dim(true)?;
let median_idx = seq_len / 2;
let median_val = sorted.i((.., median_idx))?;
Ok(median_val)
}
pub fn longest_cycle(input: &Tensor) -> Result<Tensor> {
let input_vec: Vec<Vec<u32>> = input.to_vec2()?;
let mut results = Vec::with_capacity(input_vec.len());
for row in &input_vec {
let n = row.len();
let mut visited = vec![false; n];
let mut max_cycle = 0_u32;
for start in 0..n {
#[allow(clippy::indexing_slicing)]
if visited[start] {
continue;
}
let mut pos = start;
let mut cycle_len = 0_u32;
#[allow(clippy::indexing_slicing)]
while !visited[pos] {
visited[pos] = true;
#[allow(clippy::as_conversions)]
let next = row[pos] as usize;
pos = next;
cycle_len += 1;
}
if cycle_len > max_cycle {
max_cycle = cycle_len;
}
}
results.push(max_cycle);
}
Ok(Tensor::new(&results[..], input.device())?)
}
#[cfg(test)]
mod tests {
use super::*;
use candle_core::Device;
fn u32_tensor(rows: &[u32], batch: usize, seq_len: usize) -> Tensor {
Tensor::from_vec(rows.to_vec(), (batch, seq_len), &Device::Cpu).unwrap()
}
#[test]
fn longest_cycle_identity_is_one() {
let input = u32_tensor(&[0, 1, 2, 3], 1, 4);
let out: Vec<u32> = longest_cycle(&input).unwrap().to_vec1().unwrap();
assert_eq!(out, vec![1]);
}
#[test]
fn longest_cycle_single_full_cycle() {
let input = u32_tensor(&[1, 2, 3, 0], 1, 4);
let out: Vec<u32> = longest_cycle(&input).unwrap().to_vec1().unwrap();
assert_eq!(out, vec![4]);
}
#[test]
fn longest_cycle_two_two_cycles() {
let input = u32_tensor(&[1, 0, 3, 2], 1, 4);
let out: Vec<u32> = longest_cycle(&input).unwrap().to_vec1().unwrap();
assert_eq!(out, vec![2]);
}
#[test]
fn longest_cycle_mixed_takes_the_max() {
let input = u32_tensor(&[1, 2, 0, 3], 1, 4);
let out: Vec<u32> = longest_cycle(&input).unwrap().to_vec1().unwrap();
assert_eq!(out, vec![3]);
}
#[test]
fn longest_cycle_batches_independently() {
let input = u32_tensor(&[0, 1, 2, 3, 1, 2, 3, 0, 1, 0, 3, 2], 3, 4);
let out: Vec<u32> = longest_cycle(&input).unwrap().to_vec1().unwrap();
assert_eq!(out, vec![1, 4, 2]);
}
#[test]
fn second_argmax_picks_runner_up_position() {
let input = Tensor::new(&[[0.1_f32, 0.9, 0.5, 0.3]], &Device::Cpu).unwrap();
let out: Vec<u32> = second_argmax(&input).unwrap().to_vec1().unwrap();
assert_eq!(out, vec![2]);
}
#[test]
fn median_and_argmedian_agree() {
let input = Tensor::new(&[[1.0_f32, 3.0, 2.0, 4.0]], &Device::Cpu).unwrap();
let med: Vec<f32> = median(&input).unwrap().to_vec1().unwrap();
let pos: Vec<u32> = argmedian(&input).unwrap().to_vec1().unwrap();
assert!((med[0] - 3.0).abs() < 1e-6, "median = {}", med[0]);
assert_eq!(pos, vec![1]);
}
}