#[cfg(all(
feature = "simd",
target_feature = "simd128" // This will never be true, forcing scalar path
))]
use std::simd::num::SimdInt;
#[cfg(all(
feature = "simd",
target_feature = "simd128" // This will never be true, forcing scalar path
))]
use std::simd::prelude::*;
pub const BATCH_SIZE: usize = 8;
#[inline]
pub fn batch_add_costs(costs: &mut [i32], addition: i32) {
for cost in costs.iter_mut() {
*cost = cost.saturating_add(addition);
}
}
#[inline]
pub fn find_min(costs: &[i32]) -> Option<(usize, i32)> {
if costs.is_empty() {
return None;
}
Some(find_min_scalar(costs))
}
#[inline]
fn find_min_scalar(costs: &[i32]) -> (usize, i32) {
let mut min_idx = 0;
let mut min_val = costs[0];
for (i, &cost) in costs.iter().enumerate().skip(1) {
if cost < min_val {
min_val = cost;
min_idx = i;
}
}
(min_idx, min_val)
}
#[inline]
pub fn find_best_predecessor(prev_costs: &[i32], connection_costs: &[i16]) -> Option<(usize, i32)> {
if prev_costs.is_empty() || connection_costs.is_empty() {
return None;
}
let len = prev_costs.len().min(connection_costs.len());
let mut best_idx = 0;
let mut best_cost = prev_costs[0].saturating_add(connection_costs[0] as i32);
for i in 1..len {
let cost = prev_costs[i].saturating_add(connection_costs[i] as i32);
if cost < best_cost {
best_cost = cost;
best_idx = i;
}
}
Some((best_idx, best_cost))
}
pub fn predecessor_batch_simd(prev_costs: &[i32], connection_costs: &[i16]) -> Vec<i32> {
let len = prev_costs.len().min(connection_costs.len());
let mut result = Vec::with_capacity(len);
for i in 0..len {
result.push(prev_costs[i].saturating_add(connection_costs[i] as i32));
}
result
}
pub fn predecessor_batch(
prev_costs: &[i32],
connection_matrix: &[i16],
right_id: u16,
matrix_width: usize,
) -> Vec<i32> {
prev_costs
.iter()
.enumerate()
.map(|(i, &cost)| {
let conn_cost = connection_matrix
.get(i * matrix_width + right_id as usize)
.copied()
.unwrap_or(0) as i32;
cost.saturating_add(conn_cost)
})
.collect()
}
#[derive(Debug, Default)]
pub struct SimdStats {
pub portable_simd: bool,
pub batch_size: usize,
}
impl SimdStats {
pub fn detect() -> Self {
Self {
portable_simd: true, batch_size: BATCH_SIZE,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_batch_add_costs() {
let mut costs = vec![10, 20, 30, 40, 50, 60, 70, 80];
batch_add_costs(&mut costs, 5);
assert_eq!(costs, vec![15, 25, 35, 45, 55, 65, 75, 85]);
}
#[test]
fn test_batch_add_costs_with_remainder() {
let mut costs = vec![10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
batch_add_costs(&mut costs, 5);
assert_eq!(costs, vec![15, 25, 35, 45, 55, 65, 75, 85, 95, 105]);
}
#[test]
fn test_find_min_empty() {
let costs: Vec<i32> = vec![];
assert_eq!(find_min(&costs), None);
}
#[test]
fn test_find_min_single() {
let costs = vec![42];
assert_eq!(find_min(&costs), Some((0, 42)));
}
#[test]
fn test_find_min_first() {
let costs = vec![1, 2, 3, 4, 5];
assert_eq!(find_min(&costs), Some((0, 1)));
}
#[test]
fn test_find_min_last() {
let costs = vec![5, 4, 3, 2, 1];
assert_eq!(find_min(&costs), Some((4, 1)));
}
#[test]
fn test_find_min_large() {
let costs: Vec<i32> = (0..100).rev().collect();
assert_eq!(find_min(&costs), Some((99, 0)));
}
#[test]
fn test_find_min_simd_path() {
let costs = vec![50, 40, 30, 20, 10, 5, 15, 25, 35];
assert_eq!(find_min(&costs), Some((5, 5)));
}
#[test]
fn test_find_best_predecessor() {
let prev = vec![10, 20, 5, 15];
let conn = vec![2, 1, 3, 1];
let result = find_best_predecessor(&prev, &conn);
assert_eq!(result, Some((2, 8)));
}
#[test]
fn test_predecessor_batch_simd() {
let prev = vec![10, 20, 30, 40, 50, 60, 70, 80];
let conn: Vec<i16> = vec![1, 2, 3, 4, 5, 6, 7, 8];
let result = predecessor_batch_simd(&prev, &conn);
assert_eq!(result, vec![11, 22, 33, 44, 55, 66, 77, 88]);
}
#[test]
fn test_predecessor_batch() {
let prev = vec![10, 20, 30];
let matrix: Vec<i16> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9]; let result = predecessor_batch(&prev, &matrix, 1, 3);
assert_eq!(result, vec![12, 25, 38]);
}
#[test]
fn test_simd_stats() {
let stats = SimdStats::detect();
assert_eq!(stats.batch_size, 8);
assert!(stats.portable_simd);
}
}