#[cfg(test)]
use crate::cpu_op;
#[must_use]
pub fn functor_apply_cpu(source_row: &[u32], mapping: &[u32], target_size: u32) -> Vec<u32> {
assert_eq!(source_row.len(), mapping.len());
let mut out = vec![0u32; target_size as usize];
for (i, &dst) in mapping.iter().enumerate() {
out[dst as usize] = source_row[i];
}
out
}
#[must_use]
pub fn monoidal_compose_cpu(f: &[f64], g: &[f64], a: u32, b: u32, c: u32) -> Vec<f64> {
let a = a as usize;
let b = b as usize;
let c = c as usize;
assert_eq!(f.len(), a * b);
assert_eq!(g.len(), b * c);
let mut out = vec![0.0; a * c];
for i in 0..a {
let out_row = &mut out[i * c..(i + 1) * c];
for k in 0..b {
let f_ik = f[i * b + k];
let g_row = &g[k * c..(k + 1) * c];
for j in 0..c {
out_row[j] += f_ik * g_row[j];
}
}
}
out
}
#[must_use]
pub fn homotopy_euler_predictor_cpu(x_curr: &[f64], v: &[f64], dt: f64) -> Vec<f64> {
x_curr
.iter()
.zip(v.iter())
.map(|(&x, &dv)| x + dt * dv)
.collect()
}
#[must_use]
pub fn linear_homotopy_cpu(g_x: &[f64], f_x: &[f64], t: f64) -> Vec<f64> {
let s = 1.0 - t;
g_x.iter()
.zip(f_x.iter())
.map(|(&g, &f)| s * g + t * f)
.collect()
}
#[must_use]
pub fn matroid_exchange_bfs_step_cpu(
f_in: &[u32],
adj: &[u32],
v: &[u32],
n: usize,
) -> (Vec<u32>, bool) {
let mut out = vec![0u32; n];
let mut any = false;
let frontier: Vec<usize> = (0..n).filter(|&k| f_in[k] != 0).collect();
for k in &frontier {
let row = &adj[k * n..(k + 1) * n];
for j in 0..n {
if v[j] == 0 && row[j] != 0 && out[j] == 0 {
out[j] = 1;
any = true;
}
}
}
(out, any)
}
#[must_use]
pub fn jacobi_smooth_step_cpu(a: &[f64], b: &[f64], x: &[f64], w: f64, n: u32) -> Vec<f64> {
let n = n as usize;
let inv_diag: Vec<f64> = (0..n)
.map(|i| {
let d = a[i * n + i];
if d.abs() > 1e-30 {
1.0 / d
} else {
1.0
}
})
.collect();
(0..n)
.map(|i| {
let row = &a[i * n..i * n + n];
let residual: f64 = b[i]
- row
.iter()
.zip(x.iter())
.map(|(&aij, &xj)| aij * xj)
.sum::<f64>();
x[i] + w * residual * inv_diag[i]
})
.collect()
}
#[cfg(test)]
pub(crate) fn primitive_math_div_cpu(input: &[u8], output: &mut Vec<u8>) {
output.clear();
if input.len() < 8 {
output.extend_from_slice(&0u32.to_le_bytes());
return;
}
let lhs = u32::from_le_bytes([input[0], input[1], input[2], input[3]]);
let rhs = u32::from_le_bytes([input[4], input[5], input[6], input[7]]);
output.extend_from_slice(&if rhs == 0 { 0 } else { lhs / rhs }.to_le_bytes());
}
#[cfg(test)]
pub(crate) fn cpu_fn_for_composition(id: &str) -> Option<cpu_op::CpuFn> {
match id {
"primitive.math.div" => Some(primitive_math_div_cpu),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn functor_apply_identity_mapping() {
let source = vec![10, 20, 30];
let mapping = vec![0, 1, 2];
let result = functor_apply_cpu(&source, &mapping, 3);
assert_eq!(result, vec![10, 20, 30]);
}
#[test]
fn functor_apply_permutation() {
let source = vec![10, 20, 30];
let mapping = vec![2, 1, 0];
let result = functor_apply_cpu(&source, &mapping, 3);
assert_eq!(result, vec![30, 20, 10]);
}
#[test]
fn functor_apply_expansion() {
let source = vec![5, 7];
let mapping = vec![1, 3];
let result = functor_apply_cpu(&source, &mapping, 5);
assert_eq!(result, vec![0, 5, 0, 7, 0]);
}
#[test]
fn compose_identity_matrices() {
let id = vec![1.0, 0.0, 0.0, 1.0];
let result = monoidal_compose_cpu(&id, &id, 2, 2, 2);
assert_eq!(result, vec![1.0, 0.0, 0.0, 1.0]);
}
#[test]
fn compose_known_product() {
let f = vec![1.0, 2.0, 3.0, 4.0];
let id = vec![1.0, 0.0, 0.0, 1.0];
let result = monoidal_compose_cpu(&f, &id, 2, 2, 2);
assert_eq!(result, vec![1.0, 2.0, 3.0, 4.0]);
}
#[test]
fn compose_non_square() {
let f = vec![1.0, 2.0, 3.0];
let g = vec![1.0, 0.0, 1.0];
let result = monoidal_compose_cpu(&f, &g, 1, 3, 1);
assert_eq!(result, vec![4.0]);
}
#[test]
fn euler_zero_dt_is_identity() {
let x = vec![1.0, 2.0, 3.0];
let v = vec![10.0, 20.0, 30.0];
let result = homotopy_euler_predictor_cpu(&x, &v, 0.0);
assert_eq!(result, vec![1.0, 2.0, 3.0]);
}
#[test]
fn euler_unit_step() {
let x = vec![0.0, 0.0];
let v = vec![1.0, -1.0];
let result = homotopy_euler_predictor_cpu(&x, &v, 1.0);
assert_eq!(result, vec![1.0, -1.0]);
}
#[test]
fn euler_half_step() {
let x = vec![2.0];
let v = vec![4.0];
let result = homotopy_euler_predictor_cpu(&x, &v, 0.5);
assert_eq!(result, vec![4.0]); }
#[test]
fn homotopy_t0_returns_g() {
let g = vec![1.0, 2.0];
let f = vec![10.0, 20.0];
let result = linear_homotopy_cpu(&g, &f, 0.0);
assert_eq!(result, vec![1.0, 2.0]);
}
#[test]
fn homotopy_t1_returns_f() {
let g = vec![1.0, 2.0];
let f = vec![10.0, 20.0];
let result = linear_homotopy_cpu(&g, &f, 1.0);
assert_eq!(result, vec![10.0, 20.0]);
}
#[test]
fn homotopy_midpoint() {
let g = vec![0.0, 0.0];
let f = vec![10.0, 20.0];
let result = linear_homotopy_cpu(&g, &f, 0.5);
assert_eq!(result, vec![5.0, 10.0]);
}
#[test]
fn bfs_step_reaches_adjacent_unvisited() {
let f_in = vec![1, 0, 0];
#[rustfmt::skip]
let adj = vec![
0, 1, 1,
0, 0, 0,
0, 0, 0,
];
let visited = vec![0, 0, 0];
let (reached, any) = matroid_exchange_bfs_step_cpu(&f_in, &adj, &visited, 3);
assert!(any);
assert_eq!(reached[1], 1);
assert_eq!(reached[2], 1);
}
#[test]
fn bfs_step_skips_visited() {
let f_in = vec![1, 0, 0];
#[rustfmt::skip]
let adj = vec![
0, 1, 1,
0, 0, 0,
0, 0, 0,
];
let visited = vec![0, 1, 0]; let (reached, any) = matroid_exchange_bfs_step_cpu(&f_in, &adj, &visited, 3);
assert!(any);
assert_eq!(reached[1], 0, "should skip visited node 1");
assert_eq!(reached[2], 1);
}
#[test]
fn bfs_step_no_progress_reports_false() {
let f_in = vec![1, 0, 0];
let adj = vec![0; 9];
let visited = vec![0, 0, 0];
let (_, any) = matroid_exchange_bfs_step_cpu(&f_in, &adj, &visited, 3);
assert!(!any);
}
#[test]
fn jacobi_identity_system_converges() {
let a = vec![1.0, 0.0, 0.0, 1.0];
let b = vec![1.0, 2.0];
let x = vec![0.0, 0.0];
let result = jacobi_smooth_step_cpu(&a, &b, &x, 1.0, 2);
assert!((result[0] - 1.0).abs() < 1e-12);
assert!((result[1] - 2.0).abs() < 1e-12);
}
#[test]
fn jacobi_zero_weight_preserves_x() {
let a = vec![1.0, 0.0, 0.0, 1.0];
let b = vec![10.0, 20.0];
let x = vec![3.0, 7.0];
let result = jacobi_smooth_step_cpu(&a, &b, &x, 0.0, 2);
assert_eq!(result, vec![3.0, 7.0]);
}
#[test]
fn div_normal() {
let input: Vec<u8> = [10u32.to_le_bytes(), 3u32.to_le_bytes()].concat();
let mut output = Vec::new();
primitive_math_div_cpu(&input, &mut output);
let result = u32::from_le_bytes(output[0..4].try_into().unwrap());
assert_eq!(result, 3); }
#[test]
fn div_by_zero_returns_zero() {
let input: Vec<u8> = [42u32.to_le_bytes(), 0u32.to_le_bytes()].concat();
let mut output = Vec::new();
primitive_math_div_cpu(&input, &mut output);
let result = u32::from_le_bytes(output[0..4].try_into().unwrap());
assert_eq!(result, 0, "division by zero must return 0, not panic");
}
#[test]
fn cpu_fn_lookup_known() {
assert!(cpu_fn_for_composition("primitive.math.div").is_some());
}
#[test]
fn cpu_fn_lookup_unknown() {
assert!(cpu_fn_for_composition("nonexistent.op").is_none());
}
}