1pub mod anomaly;
7pub mod changepoint;
8pub mod decomposition;
9pub mod forecast;
10pub mod frequency;
11pub mod state_space;
12pub mod utils;
13
14use torsh_tensor::Tensor;
15#[derive(Debug, Clone)]
19pub struct TimeSeries {
20 pub values: Tensor,
22 pub timestamps: Option<Vec<f64>>,
24 pub frequency: Option<f64>,
26 pub features: Option<Vec<String>>,
28}
29
30impl TimeSeries {
31 pub fn new(values: Tensor) -> Self {
33 Self {
34 values,
35 timestamps: None,
36 frequency: None,
37 features: None,
38 }
39 }
40
41 pub fn with_timestamps(mut self, timestamps: Vec<f64>) -> Self {
43 self.timestamps = Some(timestamps);
44 self
45 }
46
47 pub fn with_frequency(mut self, frequency: f64) -> Self {
49 self.frequency = Some(frequency);
50 self
51 }
52
53 pub fn with_features(mut self, features: Vec<String>) -> Self {
55 self.features = Some(features);
56 self
57 }
58
59 pub fn len(&self) -> usize {
61 self.values.shape().dims()[0]
62 }
63
64 pub fn is_empty(&self) -> bool {
66 self.len() == 0
67 }
68
69 pub fn num_features(&self) -> usize {
71 let shape = self.values.shape();
72 let dims = shape.dims();
73 if dims.len() > 1 {
74 dims[1]
75 } else {
76 1
77 }
78 }
79
80 pub fn slice(
82 &self,
83 start: usize,
84 end: usize,
85 ) -> Result<TimeSeries, torsh_core::error::TorshError> {
86 if start > end || end > self.len() {
88 return Err(torsh_core::error::TorshError::InvalidArgument(format!(
89 "Invalid slice bounds: start={}, end={}, len={}",
90 start,
91 end,
92 self.len()
93 )));
94 }
95
96 let slice_len = end - start;
98 let num_features = self.num_features();
99
100 let mut sliced_data = Vec::with_capacity(slice_len * num_features);
102 for t in start..end {
103 for f in 0..num_features {
104 let flat_idx = t * num_features + f;
105 let value = self.values.get_item_flat(flat_idx).map_err(|e| {
106 torsh_core::error::TorshError::InvalidArgument(format!(
107 "Failed to slice tensor: {}",
108 e
109 ))
110 })?;
111 sliced_data.push(value);
112 }
113 }
114
115 let shape = if num_features == 1 {
116 vec![slice_len]
117 } else {
118 vec![slice_len, num_features]
119 };
120 let values = Tensor::from_vec(sliced_data, &shape).map_err(|e| {
121 torsh_core::error::TorshError::InvalidArgument(format!(
122 "Failed to create sliced tensor: {}",
123 e
124 ))
125 })?;
126
127 let timestamps = self.timestamps.as_ref().map(|ts| ts[start..end].to_vec());
128
129 Ok(TimeSeries {
130 values,
131 timestamps,
132 frequency: self.frequency,
133 features: self.features.clone(),
134 })
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141
142 use torsh_tensor::creation::*;
143
144 #[test]
145 fn test_timeseries_creation() {
146 let data = vec![1.0f32, 2.0, 3.0, 4.0, 5.0];
147 let tensor = Tensor::from_vec(data, &[5]).unwrap();
148 let ts = TimeSeries::new(tensor);
149
150 assert_eq!(ts.len(), 5);
151 assert_eq!(ts.num_features(), 1);
152 assert!(!ts.is_empty());
153 }
154
155 #[test]
156 fn test_timeseries_with_metadata() {
157 let data = vec![1.0f32, 2.0, 3.0, 4.0, 5.0];
158 let tensor = Tensor::from_vec(data, &[5]).unwrap();
159 let timestamps = vec![0.0, 1.0, 2.0, 3.0, 4.0];
160 let features = vec!["value".to_string()];
161
162 let ts = TimeSeries::new(tensor)
163 .with_timestamps(timestamps.clone())
164 .with_frequency(1.0)
165 .with_features(features.clone());
166
167 assert_eq!(ts.timestamps, Some(timestamps));
168 assert_eq!(ts.frequency, Some(1.0));
169 assert_eq!(ts.features, Some(features));
170 }
171
172 #[test]
173 fn test_timeseries_multivariate() {
174 let data = vec![1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0];
175 let tensor = Tensor::from_vec(data, &[3, 2]).unwrap();
176 let ts = TimeSeries::new(tensor);
177
178 assert_eq!(ts.len(), 3);
179 assert_eq!(ts.num_features(), 2);
180 }
181
182 #[test]
183 fn test_timeseries_slice() {
184 let data = vec![1.0f32, 2.0, 3.0, 4.0, 5.0];
185 let tensor = Tensor::from_vec(data, &[5]).unwrap();
186 let ts = TimeSeries::new(tensor);
187
188 let sliced = ts.slice(1, 4).unwrap();
189 assert_eq!(sliced.len(), 3);
190 }
191
192 #[test]
193 fn test_empty_timeseries() {
194 let tensor = zeros(&[0]).unwrap();
195 let ts = TimeSeries::new(tensor);
196
197 assert_eq!(ts.len(), 0);
198 assert!(ts.is_empty());
199 }
200}