oxits 0.1.0

Time series classification and transformation library for Rust
Documentation
/// Compute segment boundaries for dividing a time series of length `n_timestamps`
/// into `n_segments` segments.
///
/// Returns a vector of (start, end) index pairs. If `overlapping` is true,
/// segments may overlap to produce smoother approximations.
pub fn segmentation(
    n_timestamps: usize,
    n_segments: usize,
    overlapping: bool,
) -> Vec<(usize, usize)> {
    assert!(n_timestamps > 0, "n_timestamps must be positive");
    assert!(n_segments > 0, "n_segments must be positive");
    assert!(
        n_segments <= n_timestamps,
        "n_segments ({n_segments}) must not exceed n_timestamps ({n_timestamps})"
    );

    if overlapping {
        // Overlapping: each segment has width ceil(n_timestamps / n_segments),
        // and centers are evenly spaced.
        let segment_width = (n_timestamps as f64 / n_segments as f64).ceil() as usize;
        let half = segment_width / 2;
        (0..n_segments)
            .map(|i| {
                let center = (i as f64 * (n_timestamps - 1) as f64 / (n_segments - 1).max(1) as f64)
                    .round() as usize;
                let start = center.saturating_sub(half);
                let end = (start + segment_width).min(n_timestamps);
                (start, end)
            })
            .collect()
    } else {
        // Non-overlapping: segments partition [0, n_timestamps) evenly.
        // Matches numpy.array_split logic: first (n_timestamps % n_segments)
        // segments get one extra element.
        let base_size = n_timestamps / n_segments;
        let remainder = n_timestamps % n_segments;
        let mut segments = Vec::with_capacity(n_segments);
        let mut start = 0;
        for i in 0..n_segments {
            let size = base_size + if i < remainder { 1 } else { 0 };
            segments.push((start, start + size));
            start += size;
        }
        segments
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_non_overlapping_even() {
        let segs = segmentation(8, 4, false);
        assert_eq!(segs, vec![(0, 2), (2, 4), (4, 6), (6, 8)]);
    }

    #[test]
    fn test_non_overlapping_uneven() {
        let segs = segmentation(10, 3, false);
        // 10 / 3 = 3 remainder 1: first segment gets 4, rest get 3
        assert_eq!(segs, vec![(0, 4), (4, 7), (7, 10)]);
    }

    #[test]
    fn test_non_overlapping_single() {
        let segs = segmentation(5, 1, false);
        assert_eq!(segs, vec![(0, 5)]);
    }

    #[test]
    fn test_non_overlapping_identity() {
        let segs = segmentation(4, 4, false);
        assert_eq!(segs, vec![(0, 1), (1, 2), (2, 3), (3, 4)]);
    }

    #[test]
    #[should_panic(expected = "must not exceed n_timestamps")]
    fn test_too_many_segments() {
        segmentation(3, 5, false);
    }

    #[test]
    #[should_panic(expected = "n_timestamps must be positive")]
    fn test_zero_timestamps() {
        segmentation(0, 1, false);
    }

    #[test]
    fn test_overlapping_basic() {
        let segs = segmentation(8, 4, true);
        assert_eq!(segs.len(), 4);
        // All segments should be within bounds
        for (s, e) in &segs {
            assert!(*e <= 8);
            assert!(s < e);
        }
    }
}