round_pipers 0.2.0

A way to pipe ndarrays using circular buffers
Documentation
//! Helper functions for creating ArrayView and ArrayViewMut from raw data
//!
//! This module eliminates code duplication in array view creation across all pipe types,
//! providing consistent functions for converting slices to ndarray views.

use crate::error::{PipeError, Result};
use crate::pipe_common::ShapeManager;
use crate::traits::SizedDimension;
use ndarray::{ArrayView, ArrayViewMut, Dimension, StrideShape};

/// Create a read-only ArrayView from a slice with bounds checking
pub fn create_read_view<'a, A, D: SizedDimension + Dimension>(
    data: &'a [A],
    start_element: usize,
    n_elements: usize,
    shape_manager: &ShapeManager<D>,
) -> Result<ArrayView<'a, A, D::Larger>>
where
    D::LargerSize: Into<StrideShape<D::Larger>> + Clone,
    D::CurrentSize: Clone,
{
    // Calculate slice bounds
    let (start_idx, end_idx) =
        calculate_slice_bounds(start_element, n_elements, shape_manager.element_size());

    // Bounds checking
    if end_idx > data.len() {
        return Err(PipeError::array_bounds_error(
            start_idx,
            end_idx,
            data.len(),
        ));
    }

    // Create the slice
    let slice = &data[start_idx..end_idx];

    // Create the ArrayView
    let array_view = ArrayView::<A, D::Larger>::from_shape(
        shape_manager.get_larger_array_size(n_elements),
        slice,
    )?;

    Ok(array_view)
}

/// Create a mutable ArrayViewMut from a slice with bounds checking
pub fn create_write_view<'a, A, D: SizedDimension + Dimension>(
    data: &'a mut [A],
    start_element: usize,
    n_elements: usize,
    shape_manager: &ShapeManager<D>,
) -> Result<ArrayViewMut<'a, A, D::Larger>>
where
    D::LargerSize: Into<StrideShape<D::Larger>> + Clone,
    D::CurrentSize: Clone,
{
    // Calculate slice bounds
    let (start_idx, end_idx) =
        calculate_slice_bounds(start_element, n_elements, shape_manager.element_size());

    // Bounds checking
    if end_idx > data.len() {
        return Err(PipeError::array_bounds_error(
            start_idx,
            end_idx,
            data.len(),
        ));
    }

    // Create the slice
    let slice = &mut data[start_idx..end_idx];

    // Create the ArrayViewMut
    let array_view = ArrayViewMut::<A, D::Larger>::from_shape(
        shape_manager.get_larger_array_size(n_elements),
        slice,
    )?;

    Ok(array_view)
}

/// Calculate slice bounds for array access
/// Returns (start_index, end_index) in terms of scalar elements
pub fn calculate_slice_bounds(
    start_element: usize,
    n_elements: usize,
    element_size: usize,
) -> (usize, usize) {
    let start_idx = start_element * element_size;
    let end_idx = start_idx + n_elements * element_size;
    (start_idx, end_idx)
}

/// Validate that a request fits within available data
pub fn validate_bounds(
    start_element: usize,
    n_elements: usize,
    total_elements: usize,
    context: &str,
) -> Result<()> {
    if start_element + n_elements > total_elements {
        return Err(PipeError::insufficient_data(
            context,
            n_elements,
            start_element,
            total_elements,
        ));
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::error::{PipeError, Result};
    use crate::pipe_common::ShapeManager;
    use ndarray::{Ix0, Ix1};

    #[test]
    fn test_calculate_slice_bounds() {
        // Ix0: element_size = 1
        assert_eq!(calculate_slice_bounds(5, 10, 1), (5, 15));

        // Ix1 with shape [3]: element_size = 3
        assert_eq!(calculate_slice_bounds(2, 4, 3), (6, 18));
    }

    #[test]
    fn test_create_read_view_ix0() -> Result<()> {
        let data: Vec<f64> = (0..20).map(|i| i as f64).collect();
        let shape_manager = ShapeManager::<Ix0>::new([]);

        let view = create_read_view(
            &data,
            5,  // start at element 5
            10, // read 10 elements
            &shape_manager,
        )?;

        assert_eq!(view.len(), 10);
        assert_eq!(view[0], 5.0);
        assert_eq!(view[9], 14.0);

        Ok(())
    }

    #[test]
    fn test_create_write_view_ix0() -> Result<()> {
        let mut data: Vec<f64> = vec![0.0; 20];
        let shape_manager = ShapeManager::<Ix0>::new([]);

        {
            let mut view = create_write_view(
                &mut data,
                5,  // start at element 5
                10, // write 10 elements
                &shape_manager,
            )?;

            for (i, val) in view.iter_mut().enumerate() {
                *val = (i + 100) as f64;
            }
        }

        // Verify data was written
        for i in 0..5 {
            assert_eq!(data[i], 0.0); // Unchanged
        }
        for i in 5..15 {
            assert_eq!(data[i], (i - 5 + 100) as f64); // Written
        }
        for i in 15..20 {
            assert_eq!(data[i], 0.0); // Unchanged
        }

        Ok(())
    }

    #[test]
    fn test_create_read_view_ix1() -> Result<()> {
        let data: Vec<f64> = (0..30).map(|i| i as f64).collect();
        let shape_manager = ShapeManager::<Ix1>::new([3]); // Each element has 3 scalars

        let view = create_read_view(
            &data,
            2, // start at element 2 (scalar index 6)
            4, // read 4 elements (12 scalars total)
            &shape_manager,
        )?;

        assert_eq!(view.shape(), &[4, 3]); // 4 elements, each with 3 components
        assert_eq!(view[(0, 0)], 6.0); // First element, first component
        assert_eq!(view[(0, 2)], 8.0); // First element, third component
        assert_eq!(view[(3, 2)], 17.0); // Last element, third component

        Ok(())
    }

    #[test]
    fn test_bounds_validation() {
        // Valid request
        assert!(validate_bounds(5, 10, 20, "test").is_ok());

        // Invalid: goes beyond end
        let result = validate_bounds(15, 10, 20, "test");
        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            PipeError::InsufficientData { .. }
        ));
    }

    #[test]
    fn test_bounds_error_on_read() {
        let data: Vec<f64> = vec![0.0; 10];
        let shape_manager = ShapeManager::<Ix0>::new([]);

        let result = create_read_view(
            &data,
            8, // start at element 8
            5, // try to read 5 elements (would need indices 8..13, but data is only 0..10)
            &shape_manager,
        );

        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            PipeError::ArrayBoundsError { .. }
        ));
    }

    #[test]
    fn test_bounds_error_on_write() {
        let mut data: Vec<f64> = vec![0.0; 10];
        let shape_manager = ShapeManager::<Ix0>::new([]);

        let result = create_write_view(
            &mut data,
            7, // start at element 7
            5, // try to write 5 elements (would need indices 7..12, but data is only 0..10)
            &shape_manager,
        );

        assert!(result.is_err());
        assert!(matches!(
            result.unwrap_err(),
            PipeError::ArrayBoundsError { .. }
        ));
    }
}