use crate::core::{Result, types::Point2f};
#[cfg(feature = "simd")]
use wide::f32x4;
#[cfg(not(feature = "simd"))]
mod disabled {
use crate::core::types::{Point2f, Transform2D};
#[derive(Debug, Clone)]
pub struct SIMDTransformer {
enabled: bool,
}
impl SIMDTransformer {
pub fn new() -> Self {
Self { enabled: false }
}
pub fn is_available() -> bool {
false
}
pub fn transform_coordinates_scalar(
&self,
points: &[(f64, f64)],
scale: f32,
offset: f32,
) -> Vec<Point2f> {
points
.iter()
.map(|&(x, y)| {
Point2f::new((x as f32) * scale + offset, (y as f32) * scale + offset)
})
.collect()
}
pub fn transform_coordinates_2d_scalar(
&self,
points: &[(f64, f64)],
x_scale: f32,
y_scale: f32,
x_offset: f32,
y_offset: f32,
) -> Vec<Point2f> {
points
.iter()
.map(|&(x, y)| {
Point2f::new(
(x as f32) * x_scale + x_offset,
(y as f32) * y_scale + y_offset,
)
})
.collect()
}
pub fn distance_from_point_scalar(
&self,
points: &[Point2f],
reference: Point2f,
) -> Vec<f32> {
points
.iter()
.map(|p| {
let dx = p.x - reference.x;
let dy = p.y - reference.y;
(dx * dx + dy * dy).sqrt()
})
.collect()
}
pub fn apply_transform_scalar(
&self,
points: &[Point2f],
transform: &Transform2D,
) -> Vec<Point2f> {
points
.iter()
.map(|p| {
Point2f::new(
transform.m11 * p.x + transform.m12 * p.y + transform.tx,
transform.m21 * p.x + transform.m22 * p.y + transform.ty,
)
})
.collect()
}
}
}
#[cfg(not(feature = "simd"))]
pub use disabled::SIMDTransformer;
#[cfg(feature = "simd")]
#[derive(Debug, Clone)]
pub struct SIMDTransformer {
enabled: bool,
simd_threshold: usize,
lane_width: usize,
}
impl SIMDTransformer {
pub fn new() -> Self {
Self {
enabled: Self::detect_simd_support(),
simd_threshold: 16, lane_width: 4, }
}
pub fn with_threshold(threshold: usize) -> Self {
let mut transformer = Self::new();
transformer.simd_threshold = threshold.max(4); transformer
}
fn detect_simd_support() -> bool {
true
}
pub fn should_use_simd(&self, data_size: usize) -> bool {
self.enabled && data_size >= self.simd_threshold
}
pub fn transform_x_coordinates_simd(
&self,
x_data: &[f64],
x_min: f64,
x_max: f64,
left: f32,
right: f32,
) -> Vec<f32> {
if !self.should_use_simd(x_data.len()) {
return self.transform_x_coordinates_scalar(x_data, x_min, x_max, left, right);
}
let x_range = (x_max - x_min) as f32;
let pixel_range = right - left;
let scale = pixel_range / x_range;
let offset = left - (x_min as f32) * scale;
let scale_vec = f32x4::splat(scale);
let offset_vec = f32x4::splat(offset);
let mut result = Vec::with_capacity(x_data.len());
let chunks = x_data.chunks_exact(4);
let remainder = chunks.remainder();
for chunk in chunks {
let x_vec = f32x4::new([
chunk[0] as f32,
chunk[1] as f32,
chunk[2] as f32,
chunk[3] as f32,
]);
let pixel_vec = x_vec * scale_vec + offset_vec;
let pixels = pixel_vec.to_array();
result.extend_from_slice(&pixels);
}
for &x in remainder {
let pixel = (x as f32) * scale + offset;
result.push(pixel);
}
result
}
pub fn transform_y_coordinates_simd(
&self,
y_data: &[f64],
y_min: f64,
y_max: f64,
bottom: f32,
top: f32,
) -> Vec<f32> {
if !self.should_use_simd(y_data.len()) {
return self.transform_y_coordinates_scalar(y_data, y_min, y_max, bottom, top);
}
let y_range = (y_max - y_min) as f32;
let pixel_range = top - bottom;
let scale = pixel_range / y_range;
let offset = bottom - (y_min as f32) * scale;
let scale_vec = f32x4::splat(scale);
let offset_vec = f32x4::splat(offset);
let mut result = Vec::with_capacity(y_data.len());
let chunks = y_data.chunks_exact(4);
let remainder = chunks.remainder();
for chunk in chunks {
let y_vec = f32x4::new([
chunk[0] as f32,
chunk[1] as f32,
chunk[2] as f32,
chunk[3] as f32,
]);
let pixel_vec = y_vec * scale_vec + offset_vec;
let pixels = pixel_vec.to_array();
result.extend_from_slice(&pixels);
}
for &y in remainder {
let pixel = (y as f32) * scale + offset;
result.push(pixel);
}
result
}
pub fn transform_coordinates_simd(
&self,
x_data: &[f64],
y_data: &[f64],
bounds: CoordinateBounds,
viewport: PixelViewport,
) -> Result<Vec<Point2f>> {
if x_data.len() != y_data.len() {
return Err(crate::core::PlottingError::DataLengthMismatch {
x_len: x_data.len(),
y_len: y_data.len(),
series_index: None,
});
}
if !self.should_use_simd(x_data.len()) {
return Ok(self.transform_coordinates_scalar(x_data, y_data, bounds, viewport));
}
let x_range = (bounds.x_max - bounds.x_min) as f32;
let y_range = (bounds.y_max - bounds.y_min) as f32;
let x_scale = (viewport.right - viewport.left) / x_range;
let y_scale = (viewport.top - viewport.bottom) / y_range;
let x_offset = viewport.left - (bounds.x_min as f32) * x_scale;
let y_offset = viewport.bottom - (bounds.y_min as f32) * y_scale;
let x_scale_vec = f32x4::splat(x_scale);
let y_scale_vec = f32x4::splat(y_scale);
let x_offset_vec = f32x4::splat(x_offset);
let y_offset_vec = f32x4::splat(y_offset);
let mut result = Vec::with_capacity(x_data.len());
let chunks = x_data.chunks_exact(4);
let remainder = chunks.remainder();
let y_chunks = y_data.chunks_exact(4);
for (x_chunk, y_chunk) in chunks.zip(y_chunks) {
let x_vec = f32x4::new([
x_chunk[0] as f32,
x_chunk[1] as f32,
x_chunk[2] as f32,
x_chunk[3] as f32,
]);
let y_vec = f32x4::new([
y_chunk[0] as f32,
y_chunk[1] as f32,
y_chunk[2] as f32,
y_chunk[3] as f32,
]);
let px_vec = x_vec * x_scale_vec + x_offset_vec;
let py_vec = y_vec * y_scale_vec + y_offset_vec;
let px_array = px_vec.to_array();
let py_array = py_vec.to_array();
for i in 0..4 {
result.push(Point2f::new(px_array[i], py_array[i]));
}
}
let y_remainder = &y_data[x_data.len() - remainder.len()..];
for (i, (&x, &y)) in remainder.iter().zip(y_remainder.iter()).enumerate() {
let px = (x as f32) * x_scale + x_offset;
let py = (y as f32) * y_scale + y_offset;
result.push(Point2f::new(px, py));
}
Ok(result)
}
pub fn batch_distance_squared_simd(&self, points: &[Point2f], reference: Point2f) -> Vec<f32> {
if !self.should_use_simd(points.len()) {
return self.batch_distance_squared_scalar(points, reference);
}
let ref_x_vec = f32x4::splat(reference.x);
let ref_y_vec = f32x4::splat(reference.y);
let mut result = Vec::with_capacity(points.len());
let chunks = points.chunks_exact(4);
let remainder = chunks.remainder();
for chunk in chunks {
let x_vec = f32x4::new([chunk[0].x, chunk[1].x, chunk[2].x, chunk[3].x]);
let y_vec = f32x4::new([chunk[0].y, chunk[1].y, chunk[2].y, chunk[3].y]);
let dx_vec = x_vec - ref_x_vec;
let dy_vec = y_vec - ref_y_vec;
let dist_sq_vec = dx_vec * dx_vec + dy_vec * dy_vec;
let distances = dist_sq_vec.to_array();
result.extend_from_slice(&distances);
}
for point in remainder {
let dx = point.x - reference.x;
let dy = point.y - reference.y;
result.push(dx * dx + dy * dy);
}
result
}
pub fn apply_affine_transform_simd(
&self,
points: &[Point2f],
transform: &AffineTransform2D,
) -> Vec<Point2f> {
if !self.should_use_simd(points.len()) {
return self.apply_affine_transform_scalar(points, transform);
}
let m11_vec = f32x4::splat(transform.m11);
let m12_vec = f32x4::splat(transform.m12);
let m21_vec = f32x4::splat(transform.m21);
let m22_vec = f32x4::splat(transform.m22);
let tx_vec = f32x4::splat(transform.tx);
let ty_vec = f32x4::splat(transform.ty);
let mut result = Vec::with_capacity(points.len());
let chunks = points.chunks_exact(4);
let remainder = chunks.remainder();
for chunk in chunks {
let x_vec = f32x4::new([chunk[0].x, chunk[1].x, chunk[2].x, chunk[3].x]);
let y_vec = f32x4::new([chunk[0].y, chunk[1].y, chunk[2].y, chunk[3].y]);
let new_x_vec = m11_vec * x_vec + m12_vec * y_vec + tx_vec;
let new_y_vec = m21_vec * x_vec + m22_vec * y_vec + ty_vec;
let x_array = new_x_vec.to_array();
let y_array = new_y_vec.to_array();
for i in 0..4 {
result.push(Point2f::new(x_array[i], y_array[i]));
}
}
for point in remainder {
let new_x = transform.m11 * point.x + transform.m12 * point.y + transform.tx;
let new_y = transform.m21 * point.x + transform.m22 * point.y + transform.ty;
result.push(Point2f::new(new_x, new_y));
}
result
}
pub fn performance_info(&self) -> SIMDPerformanceInfo {
SIMDPerformanceInfo {
simd_enabled: self.enabled,
simd_threshold: self.simd_threshold,
lane_width: self.lane_width,
target_arch: Self::get_target_arch(),
estimated_speedup: if self.enabled { 3.5 } else { 1.0 },
}
}
fn get_target_arch() -> &'static str {
#[cfg(target_arch = "x86_64")]
return "x86_64";
#[cfg(target_arch = "aarch64")]
return "aarch64";
#[cfg(target_arch = "x86")]
return "x86";
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64", target_arch = "x86")))]
return "other";
}
fn transform_x_coordinates_scalar(
&self,
x_data: &[f64],
x_min: f64,
x_max: f64,
left: f32,
right: f32,
) -> Vec<f32> {
let x_range = (x_max - x_min) as f32;
let pixel_range = right - left;
let scale = pixel_range / x_range;
let offset = left - (x_min as f32) * scale;
x_data
.iter()
.map(|&x| (x as f32) * scale + offset)
.collect()
}
fn transform_y_coordinates_scalar(
&self,
y_data: &[f64],
y_min: f64,
y_max: f64,
bottom: f32,
top: f32,
) -> Vec<f32> {
let y_range = (y_max - y_min) as f32;
let pixel_range = top - bottom;
let scale = pixel_range / y_range;
let offset = bottom - (y_min as f32) * scale;
y_data
.iter()
.map(|&y| (y as f32) * scale + offset)
.collect()
}
fn transform_coordinates_scalar(
&self,
x_data: &[f64],
y_data: &[f64],
bounds: CoordinateBounds,
viewport: PixelViewport,
) -> Vec<Point2f> {
let x_range = (bounds.x_max - bounds.x_min) as f32;
let y_range = (bounds.y_max - bounds.y_min) as f32;
let x_scale = (viewport.right - viewport.left) / x_range;
let y_scale = (viewport.top - viewport.bottom) / y_range;
let x_offset = viewport.left - (bounds.x_min as f32) * x_scale;
let y_offset = viewport.bottom - (bounds.y_min as f32) * y_scale;
x_data
.iter()
.zip(y_data.iter())
.map(|(&x, &y)| {
let px = (x as f32) * x_scale + x_offset;
let py = (y as f32) * y_scale + y_offset;
Point2f::new(px, py)
})
.collect()
}
fn batch_distance_squared_scalar(&self, points: &[Point2f], reference: Point2f) -> Vec<f32> {
points
.iter()
.map(|point| {
let dx = point.x - reference.x;
let dy = point.y - reference.y;
dx * dx + dy * dy
})
.collect()
}
fn apply_affine_transform_scalar(
&self,
points: &[Point2f],
transform: &AffineTransform2D,
) -> Vec<Point2f> {
points
.iter()
.map(|point| {
let new_x = transform.m11 * point.x + transform.m12 * point.y + transform.tx;
let new_y = transform.m21 * point.x + transform.m22 * point.y + transform.ty;
Point2f::new(new_x, new_y)
})
.collect()
}
}
impl Default for SIMDTransformer {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct CoordinateBounds {
pub x_min: f64,
pub x_max: f64,
pub y_min: f64,
pub y_max: f64,
}
#[derive(Debug, Clone)]
pub struct PixelViewport {
pub left: f32,
pub right: f32,
pub top: f32,
pub bottom: f32,
}
#[derive(Debug, Clone)]
pub struct AffineTransform2D {
pub m11: f32,
pub m12: f32,
pub tx: f32,
pub m21: f32,
pub m22: f32,
pub ty: f32,
}
impl AffineTransform2D {
pub fn identity() -> Self {
Self {
m11: 1.0,
m12: 0.0,
tx: 0.0,
m21: 0.0,
m22: 1.0,
ty: 0.0,
}
}
pub fn translation(tx: f32, ty: f32) -> Self {
Self {
m11: 1.0,
m12: 0.0,
tx,
m21: 0.0,
m22: 1.0,
ty,
}
}
pub fn scale(scale: f32) -> Self {
Self {
m11: scale,
m12: 0.0,
tx: 0.0,
m21: 0.0,
m22: scale,
ty: 0.0,
}
}
pub fn rotation(angle: f32) -> Self {
let cos_a = angle.cos();
let sin_a = angle.sin();
Self {
m11: cos_a,
m12: -sin_a,
tx: 0.0,
m21: sin_a,
m22: cos_a,
ty: 0.0,
}
}
}
#[derive(Debug, Clone)]
pub struct SIMDPerformanceInfo {
pub simd_enabled: bool,
pub simd_threshold: usize,
pub lane_width: usize,
pub target_arch: &'static str,
pub estimated_speedup: f32,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simd_transformer_creation() {
let transformer = SIMDTransformer::new();
assert!(transformer.enabled);
assert_eq!(transformer.lane_width, 4);
}
#[test]
fn test_simd_threshold() {
let transformer = SIMDTransformer::with_threshold(32);
assert!(!transformer.should_use_simd(16));
assert!(transformer.should_use_simd(32));
assert!(transformer.should_use_simd(64));
}
#[test]
fn test_coordinate_transformation_consistency() {
let transformer = SIMDTransformer::new();
let x_data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let y_data = vec![10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0];
let bounds = CoordinateBounds {
x_min: 1.0,
x_max: 8.0,
y_min: 10.0,
y_max: 80.0,
};
let viewport = PixelViewport {
left: 0.0,
right: 100.0,
top: 0.0,
bottom: 100.0,
};
let simd_result = transformer
.transform_coordinates_simd(&x_data, &y_data, bounds.clone(), viewport.clone())
.unwrap();
let scalar_result =
transformer.transform_coordinates_scalar(&x_data, &y_data, bounds, viewport);
assert_eq!(simd_result.len(), scalar_result.len());
for (simd_point, scalar_point) in simd_result.iter().zip(scalar_result.iter()) {
assert!((simd_point.x - scalar_point.x).abs() < 0.001);
assert!((simd_point.y - scalar_point.y).abs() < 0.001);
}
}
#[test]
fn test_distance_calculation() {
let transformer = SIMDTransformer::new();
let points = vec![
Point2f::new(0.0, 0.0),
Point2f::new(3.0, 4.0),
Point2f::new(-1.0, -1.0),
Point2f::new(5.0, 12.0),
];
let reference = Point2f::new(0.0, 0.0);
let distances = transformer.batch_distance_squared_simd(&points, reference);
assert_eq!(distances.len(), 4);
assert_eq!(distances[0], 0.0); assert_eq!(distances[1], 25.0); assert_eq!(distances[2], 2.0); assert_eq!(distances[3], 169.0); }
#[test]
fn test_affine_transformation() {
let transformer = SIMDTransformer::new();
let points = vec![
Point2f::new(1.0, 0.0),
Point2f::new(0.0, 1.0),
Point2f::new(1.0, 1.0),
Point2f::new(-1.0, -1.0),
];
let translation = AffineTransform2D::translation(5.0, 3.0);
let translated = transformer.apply_affine_transform_simd(&points, &translation);
assert_eq!(translated[0].x, 6.0); assert_eq!(translated[0].y, 3.0);
let scale = AffineTransform2D::scale(2.0);
let scaled = transformer.apply_affine_transform_simd(&points, &scale);
assert_eq!(scaled[0].x, 2.0); assert_eq!(scaled[0].y, 0.0); }
#[test]
fn test_performance_info() {
let transformer = SIMDTransformer::new();
let info = transformer.performance_info();
assert!(info.simd_enabled);
assert_eq!(info.lane_width, 4);
assert!(info.estimated_speedup > 1.0);
}
}