#![allow(unsafe_code)]
#[cfg(all(target_arch = "wasm32", feature = "simd"))]
use core::arch::wasm32::*;
use nalgebra::Vector2;
const MIN_DISTANCE_SQ: f32 = 0.0001;
#[derive(Debug, Clone)]
pub struct SimdPositions {
pub x: Vec<f32>,
pub y: Vec<f32>,
pub mass: Vec<f32>,
pub len: usize,
}
impl SimdPositions {
pub fn from_positions(positions: &[(f64, f64)]) -> Self {
let len = positions.len();
let mut x = Vec::with_capacity(len);
let mut y = Vec::with_capacity(len);
let mut mass = Vec::with_capacity(len);
for (px, py) in positions {
x.push(*px as f32);
y.push(*py as f32);
mass.push(1.0f32);
}
Self { x, y, mass, len }
}
pub fn from_positions_weighted(positions: &[(f64, f64, f64)]) -> Self {
let len = positions.len();
let mut x = Vec::with_capacity(len);
let mut y = Vec::with_capacity(len);
let mut mass = Vec::with_capacity(len);
for (px, py, w) in positions {
x.push(*px as f32);
y.push(*py as f32);
mass.push(*w as f32);
}
Self { x, y, mass, len }
}
}
#[derive(Debug, Clone)]
pub struct SimdForces {
pub fx: Vec<f32>,
pub fy: Vec<f32>,
pub len: usize,
}
impl SimdForces {
pub fn zeros(len: usize) -> Self {
Self {
fx: vec![0.0f32; len],
fy: vec![0.0f32; len],
len,
}
}
pub fn to_vectors(&self) -> Vec<Vector2<f64>> {
(0..self.len)
.map(|i| Vector2::new(self.fx[i] as f64, self.fy[i] as f64))
.collect()
}
pub fn add(&mut self, other: &SimdForces) {
for i in 0..self.len.min(other.len) {
self.fx[i] += other.fx[i];
self.fy[i] += other.fy[i];
}
}
}
#[cfg(all(target_arch = "wasm32", feature = "simd"))]
#[target_feature(enable = "simd128")]
pub unsafe fn calculate_repulsion_simd(positions: &SimdPositions, strength: f32) -> SimdForces {
let mut forces = SimdForces::zeros(positions.len);
let n = positions.len;
for i in 0..n {
let xi = positions.x[i];
let yi = positions.y[i];
let xi_v = f32x4_splat(xi);
let yi_v = f32x4_splat(yi);
let mut fx_acc = f32x4_splat(0.0);
let mut fy_acc = f32x4_splat(0.0);
let chunks = (n - i - 1) / 4;
for chunk in 0..chunks {
let base_j = i + 1 + chunk * 4;
let xj_v = f32x4(
positions.x[base_j],
positions.x[base_j + 1],
positions.x[base_j + 2],
positions.x[base_j + 3],
);
let yj_v = f32x4(
positions.y[base_j],
positions.y[base_j + 1],
positions.y[base_j + 2],
positions.y[base_j + 3],
);
let dx = f32x4_sub(xi_v, xj_v);
let dy = f32x4_sub(yi_v, yj_v);
let dx_sq = f32x4_mul(dx, dx);
let dy_sq = f32x4_mul(dy, dy);
let dist_sq = f32x4_add(dx_sq, dy_sq);
let min_dist = f32x4_splat(MIN_DISTANCE_SQ);
let dist_sq_clamped = f32x4_pmax(dist_sq, min_dist);
let strength_v = f32x4_splat(strength);
let force_mag = f32x4_div(strength_v, dist_sq_clamped);
let dist = f32x4_sqrt(dist_sq_clamped);
let inv_dist = f32x4_div(f32x4_splat(1.0), dist);
let fx = f32x4_mul(f32x4_mul(dx, inv_dist), force_mag);
let fy = f32x4_mul(f32x4_mul(dy, inv_dist), force_mag);
fx_acc = f32x4_add(fx_acc, fx);
fy_acc = f32x4_add(fy_acc, fy);
forces.fx[base_j] -= f32x4_extract_lane::<0>(fx);
forces.fx[base_j + 1] -= f32x4_extract_lane::<1>(fx);
forces.fx[base_j + 2] -= f32x4_extract_lane::<2>(fx);
forces.fx[base_j + 3] -= f32x4_extract_lane::<3>(fx);
forces.fy[base_j] -= f32x4_extract_lane::<0>(fy);
forces.fy[base_j + 1] -= f32x4_extract_lane::<1>(fy);
forces.fy[base_j + 2] -= f32x4_extract_lane::<2>(fy);
forces.fy[base_j + 3] -= f32x4_extract_lane::<3>(fy);
}
forces.fx[i] += f32x4_extract_lane::<0>(fx_acc)
+ f32x4_extract_lane::<1>(fx_acc)
+ f32x4_extract_lane::<2>(fx_acc)
+ f32x4_extract_lane::<3>(fx_acc);
forces.fy[i] += f32x4_extract_lane::<0>(fy_acc)
+ f32x4_extract_lane::<1>(fy_acc)
+ f32x4_extract_lane::<2>(fy_acc)
+ f32x4_extract_lane::<3>(fy_acc);
let remainder_start = i + 1 + chunks * 4;
for j in remainder_start..n {
let dx = xi - positions.x[j];
let dy = yi - positions.y[j];
let dist_sq = (dx * dx + dy * dy).max(MIN_DISTANCE_SQ);
let dist = dist_sq.sqrt();
let force_mag = strength / dist_sq;
let fx = (dx / dist) * force_mag;
let fy = (dy / dist) * force_mag;
forces.fx[i] += fx;
forces.fy[i] += fy;
forces.fx[j] -= fx;
forces.fy[j] -= fy;
}
}
forces
}
#[cfg(not(all(target_arch = "wasm32", feature = "simd")))]
pub fn calculate_repulsion_simd(positions: &SimdPositions, strength: f32) -> SimdForces {
calculate_repulsion_scalar(positions, strength)
}
pub fn calculate_repulsion_scalar(positions: &SimdPositions, strength: f32) -> SimdForces {
let mut forces = SimdForces::zeros(positions.len);
let n = positions.len;
for i in 0..n {
let xi = positions.x[i];
let yi = positions.y[i];
for j in (i + 1)..n {
let dx = xi - positions.x[j];
let dy = yi - positions.y[j];
let dist_sq = (dx * dx + dy * dy).max(MIN_DISTANCE_SQ);
let dist = dist_sq.sqrt();
let force_mag = strength / dist_sq;
let fx = (dx / dist) * force_mag;
let fy = (dy / dist) * force_mag;
forces.fx[i] += fx;
forces.fy[i] += fy;
forces.fx[j] -= fx;
forces.fy[j] -= fy;
}
}
forces
}
#[cfg(all(target_arch = "wasm32", feature = "simd"))]
#[target_feature(enable = "simd128")]
pub unsafe fn calculate_center_force_simd(
positions: &SimdPositions,
center_x: f32,
center_y: f32,
strength: f32,
) -> SimdForces {
let mut forces = SimdForces::zeros(positions.len);
let n = positions.len;
let cx_v = f32x4_splat(center_x);
let cy_v = f32x4_splat(center_y);
let strength_v = f32x4_splat(strength);
let chunks = n / 4;
for chunk in 0..chunks {
let base = chunk * 4;
let x_v = f32x4(
positions.x[base],
positions.x[base + 1],
positions.x[base + 2],
positions.x[base + 3],
);
let y_v = f32x4(
positions.y[base],
positions.y[base + 1],
positions.y[base + 2],
positions.y[base + 3],
);
let fx = f32x4_mul(f32x4_sub(cx_v, x_v), strength_v);
let fy = f32x4_mul(f32x4_sub(cy_v, y_v), strength_v);
forces.fx[base] = f32x4_extract_lane::<0>(fx);
forces.fx[base + 1] = f32x4_extract_lane::<1>(fx);
forces.fx[base + 2] = f32x4_extract_lane::<2>(fx);
forces.fx[base + 3] = f32x4_extract_lane::<3>(fx);
forces.fy[base] = f32x4_extract_lane::<0>(fy);
forces.fy[base + 1] = f32x4_extract_lane::<1>(fy);
forces.fy[base + 2] = f32x4_extract_lane::<2>(fy);
forces.fy[base + 3] = f32x4_extract_lane::<3>(fy);
}
for i in (chunks * 4)..n {
forces.fx[i] = (center_x - positions.x[i]) * strength;
forces.fy[i] = (center_y - positions.y[i]) * strength;
}
forces
}
#[cfg(not(all(target_arch = "wasm32", feature = "simd")))]
pub fn calculate_center_force_simd(
positions: &SimdPositions,
center_x: f32,
center_y: f32,
strength: f32,
) -> SimdForces {
calculate_center_force_scalar(positions, center_x, center_y, strength)
}
pub fn calculate_center_force_scalar(
positions: &SimdPositions,
center_x: f32,
center_y: f32,
strength: f32,
) -> SimdForces {
let mut forces = SimdForces::zeros(positions.len);
for i in 0..positions.len {
forces.fx[i] = (center_x - positions.x[i]) * strength;
forces.fy[i] = (center_y - positions.y[i]) * strength;
}
forces
}
#[cfg(all(target_arch = "wasm32", feature = "simd"))]
#[target_feature(enable = "simd128")]
pub unsafe fn calculate_leaf_force_simd(
target_x: f32,
target_y: f32,
sources: &SimdPositions,
strength: f32,
) -> (f32, f32) {
let n = sources.len;
if n == 0 {
return (0.0, 0.0);
}
let tx_v = f32x4_splat(target_x);
let ty_v = f32x4_splat(target_y);
let strength_v = f32x4_splat(strength);
let min_dist_v = f32x4_splat(MIN_DISTANCE_SQ);
let one_v = f32x4_splat(1.0);
let mut fx_acc = f32x4_splat(0.0);
let mut fy_acc = f32x4_splat(0.0);
let chunks = n / 4;
for chunk in 0..chunks {
let base = chunk * 4;
let sx_v = f32x4(
sources.x[base],
sources.x[base + 1],
sources.x[base + 2],
sources.x[base + 3],
);
let sy_v = f32x4(
sources.y[base],
sources.y[base + 1],
sources.y[base + 2],
sources.y[base + 3],
);
let mass_v = f32x4(
sources.mass[base],
sources.mass[base + 1],
sources.mass[base + 2],
sources.mass[base + 3],
);
let dx = f32x4_sub(tx_v, sx_v);
let dy = f32x4_sub(ty_v, sy_v);
let dist_sq = f32x4_add(f32x4_mul(dx, dx), f32x4_mul(dy, dy));
let dist_sq_clamped = f32x4_pmax(dist_sq, min_dist_v);
let force_mag = f32x4_div(f32x4_mul(strength_v, mass_v), dist_sq_clamped);
let dist = f32x4_sqrt(dist_sq_clamped);
let inv_dist = f32x4_div(one_v, dist);
let fx = f32x4_mul(f32x4_mul(dx, inv_dist), force_mag);
let fy = f32x4_mul(f32x4_mul(dy, inv_dist), force_mag);
fx_acc = f32x4_add(fx_acc, fx);
fy_acc = f32x4_add(fy_acc, fy);
}
let mut total_fx = f32x4_extract_lane::<0>(fx_acc)
+ f32x4_extract_lane::<1>(fx_acc)
+ f32x4_extract_lane::<2>(fx_acc)
+ f32x4_extract_lane::<3>(fx_acc);
let mut total_fy = f32x4_extract_lane::<0>(fy_acc)
+ f32x4_extract_lane::<1>(fy_acc)
+ f32x4_extract_lane::<2>(fy_acc)
+ f32x4_extract_lane::<3>(fy_acc);
for i in (chunks * 4)..n {
let dx = target_x - sources.x[i];
let dy = target_y - sources.y[i];
let dist_sq = (dx * dx + dy * dy).max(MIN_DISTANCE_SQ);
let dist = dist_sq.sqrt();
let force_mag = strength * sources.mass[i] / dist_sq;
total_fx += (dx / dist) * force_mag;
total_fy += (dy / dist) * force_mag;
}
(total_fx, total_fy)
}
#[cfg(not(all(target_arch = "wasm32", feature = "simd")))]
pub fn calculate_leaf_force_simd(
target_x: f32,
target_y: f32,
sources: &SimdPositions,
strength: f32,
) -> (f32, f32) {
calculate_leaf_force_scalar(target_x, target_y, sources, strength)
}
pub fn calculate_leaf_force_scalar(
target_x: f32,
target_y: f32,
sources: &SimdPositions,
strength: f32,
) -> (f32, f32) {
let mut fx = 0.0f32;
let mut fy = 0.0f32;
for i in 0..sources.len {
let dx = target_x - sources.x[i];
let dy = target_y - sources.y[i];
let dist_sq = (dx * dx + dy * dy).max(MIN_DISTANCE_SQ);
let dist = dist_sq.sqrt();
let force_mag = strength * sources.mass[i] / dist_sq;
fx += (dx / dist) * force_mag;
fy += (dy / dist) * force_mag;
}
(fx, fy)
}
#[cfg(target_arch = "wasm32")]
pub fn is_simd_available() -> bool {
cfg!(feature = "simd")
}
#[cfg(not(target_arch = "wasm32"))]
pub fn is_simd_available() -> bool {
false
}
#[cfg(all(target_arch = "wasm32", feature = "simd"))]
#[target_feature(enable = "simd128")]
pub unsafe fn apply_velocity_update_simd(
vx: &mut [f32],
vy: &mut [f32],
forces: &SimdForces,
alpha: f32,
damping: f32,
) {
let n = vx.len().min(vy.len()).min(forces.len);
let alpha_v = f32x4_splat(alpha);
let damping_v = f32x4_splat(damping);
let chunks = n / 4;
for chunk in 0..chunks {
let base = chunk * 4;
let vx_v = f32x4(vx[base], vx[base + 1], vx[base + 2], vx[base + 3]);
let vy_v = f32x4(vy[base], vy[base + 1], vy[base + 2], vy[base + 3]);
let fx_v = f32x4(
forces.fx[base],
forces.fx[base + 1],
forces.fx[base + 2],
forces.fx[base + 3],
);
let fy_v = f32x4(
forces.fy[base],
forces.fy[base + 1],
forces.fy[base + 2],
forces.fy[base + 3],
);
let new_vx = f32x4_mul(f32x4_add(vx_v, f32x4_mul(fx_v, alpha_v)), damping_v);
let new_vy = f32x4_mul(f32x4_add(vy_v, f32x4_mul(fy_v, alpha_v)), damping_v);
vx[base] = f32x4_extract_lane::<0>(new_vx);
vx[base + 1] = f32x4_extract_lane::<1>(new_vx);
vx[base + 2] = f32x4_extract_lane::<2>(new_vx);
vx[base + 3] = f32x4_extract_lane::<3>(new_vx);
vy[base] = f32x4_extract_lane::<0>(new_vy);
vy[base + 1] = f32x4_extract_lane::<1>(new_vy);
vy[base + 2] = f32x4_extract_lane::<2>(new_vy);
vy[base + 3] = f32x4_extract_lane::<3>(new_vy);
}
for i in (chunks * 4)..n {
vx[i] = (vx[i] + forces.fx[i] * alpha) * damping;
vy[i] = (vy[i] + forces.fy[i] * alpha) * damping;
}
}
#[cfg(not(all(target_arch = "wasm32", feature = "simd")))]
pub fn apply_velocity_update_simd(
vx: &mut [f32],
vy: &mut [f32],
forces: &SimdForces,
alpha: f32,
damping: f32,
) {
apply_velocity_update_scalar(vx, vy, forces, alpha, damping)
}
pub fn apply_velocity_update_scalar(
vx: &mut [f32],
vy: &mut [f32],
forces: &SimdForces,
alpha: f32,
damping: f32,
) {
let n = vx.len().min(vy.len()).min(forces.len);
for i in 0..n {
vx[i] = (vx[i] + forces.fx[i] * alpha) * damping;
vy[i] = (vy[i] + forces.fy[i] * alpha) * damping;
}
}
#[cfg(all(target_arch = "wasm32", feature = "simd"))]
#[target_feature(enable = "simd128")]
pub unsafe fn apply_position_update_simd(x: &mut [f32], y: &mut [f32], vx: &[f32], vy: &[f32]) {
let n = x.len().min(y.len()).min(vx.len()).min(vy.len());
let chunks = n / 4;
for chunk in 0..chunks {
let base = chunk * 4;
let x_v = f32x4(x[base], x[base + 1], x[base + 2], x[base + 3]);
let y_v = f32x4(y[base], y[base + 1], y[base + 2], y[base + 3]);
let vx_v = f32x4(vx[base], vx[base + 1], vx[base + 2], vx[base + 3]);
let vy_v = f32x4(vy[base], vy[base + 1], vy[base + 2], vy[base + 3]);
let new_x = f32x4_add(x_v, vx_v);
let new_y = f32x4_add(y_v, vy_v);
x[base] = f32x4_extract_lane::<0>(new_x);
x[base + 1] = f32x4_extract_lane::<1>(new_x);
x[base + 2] = f32x4_extract_lane::<2>(new_x);
x[base + 3] = f32x4_extract_lane::<3>(new_x);
y[base] = f32x4_extract_lane::<0>(new_y);
y[base + 1] = f32x4_extract_lane::<1>(new_y);
y[base + 2] = f32x4_extract_lane::<2>(new_y);
y[base + 3] = f32x4_extract_lane::<3>(new_y);
}
for i in (chunks * 4)..n {
x[i] += vx[i];
y[i] += vy[i];
}
}
#[cfg(not(all(target_arch = "wasm32", feature = "simd")))]
pub fn apply_position_update_simd(x: &mut [f32], y: &mut [f32], vx: &[f32], vy: &[f32]) {
apply_position_update_scalar(x, y, vx, vy)
}
pub fn apply_position_update_scalar(x: &mut [f32], y: &mut [f32], vx: &[f32], vy: &[f32]) {
let n = x.len().min(y.len()).min(vx.len()).min(vy.len());
for i in 0..n {
x[i] += vx[i];
y[i] += vy[i];
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simd_positions_creation() {
let positions = vec![(0.0, 0.0), (1.0, 1.0), (2.0, 2.0)];
let simd_pos = SimdPositions::from_positions(&positions);
assert_eq!(simd_pos.len, 3);
assert_eq!(simd_pos.x[0], 0.0);
assert_eq!(simd_pos.y[1], 1.0);
assert_eq!(simd_pos.mass[2], 1.0);
}
#[test]
fn test_simd_forces_zeros() {
let forces = SimdForces::zeros(5);
assert_eq!(forces.len, 5);
assert!(forces.fx.iter().all(|&f| f == 0.0));
assert!(forces.fy.iter().all(|&f| f == 0.0));
}
#[test]
fn test_scalar_repulsion() {
let positions = vec![(0.0, 0.0), (10.0, 0.0)];
let simd_pos = SimdPositions::from_positions(&positions);
let forces = calculate_repulsion_scalar(&simd_pos, -100.0);
assert!(forces.fx[0] != 0.0, "Force on node 0 should be non-zero");
assert!(
(forces.fx[0] + forces.fx[1]).abs() < 0.001,
"Forces should be equal and opposite"
);
assert!(forces.fy[0].abs() < 0.001);
assert!(forces.fy[1].abs() < 0.001);
}
#[test]
fn test_scalar_center_force() {
let positions = vec![(10.0, 10.0), (-10.0, -10.0)];
let simd_pos = SimdPositions::from_positions(&positions);
let forces = calculate_center_force_scalar(&simd_pos, 0.0, 0.0, 0.1);
assert!(forces.fx[0] < 0.0);
assert!(forces.fy[0] < 0.0);
assert!(forces.fx[1] > 0.0);
assert!(forces.fy[1] > 0.0);
}
#[test]
fn test_scalar_leaf_force() {
let sources = SimdPositions::from_positions(&[(10.0, 0.0), (0.0, 10.0)]);
let (fx, fy) = calculate_leaf_force_scalar(0.0, 0.0, &sources, -100.0);
assert!(fx != 0.0, "fx should be non-zero");
assert!(fy != 0.0, "fy should be non-zero");
assert!(
(fx.abs() - fy.abs()).abs() < 0.001,
"Forces should be symmetric"
);
}
#[test]
fn test_velocity_update_scalar() {
let mut vx = vec![1.0, 2.0, 3.0];
let mut vy = vec![1.0, 2.0, 3.0];
let forces = SimdForces {
fx: vec![1.0, 1.0, 1.0],
fy: vec![1.0, 1.0, 1.0],
len: 3,
};
apply_velocity_update_scalar(&mut vx, &mut vy, &forces, 1.0, 0.5);
assert!((vx[0] - 1.0).abs() < 0.001);
}
#[test]
fn test_position_update_scalar() {
let mut x = vec![0.0, 1.0, 2.0];
let mut y = vec![0.0, 1.0, 2.0];
let vx = vec![1.0, 1.0, 1.0];
let vy = vec![1.0, 1.0, 1.0];
apply_position_update_scalar(&mut x, &mut y, &vx, &vy);
assert_eq!(x[0], 1.0);
assert_eq!(y[1], 2.0);
}
#[test]
fn test_forces_to_vectors() {
let forces = SimdForces {
fx: vec![1.0, 2.0],
fy: vec![3.0, 4.0],
len: 2,
};
let vectors = forces.to_vectors();
assert_eq!(vectors.len(), 2);
assert!((vectors[0].x - 1.0).abs() < 0.001);
assert!((vectors[0].y - 3.0).abs() < 0.001);
}
#[test]
fn test_forces_add() {
let mut forces1 = SimdForces {
fx: vec![1.0, 2.0],
fy: vec![3.0, 4.0],
len: 2,
};
let forces2 = SimdForces {
fx: vec![1.0, 1.0],
fy: vec![1.0, 1.0],
len: 2,
};
forces1.add(&forces2);
assert_eq!(forces1.fx[0], 2.0);
assert_eq!(forces1.fy[1], 5.0);
}
}