1use crate::error::{QearError, QearResult};
7use crate::features::FeatureExtractor;
8use crate::fusion::AttentionFusion;
9use crate::reservoir::{QuantumReservoir, ReservoirConfig};
10use ndarray::{Array1, Array2, Axis};
11
12#[cfg(feature = "serde")]
13use serde::{Deserialize, Serialize};
14
15#[derive(Debug, Clone)]
17#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
18pub struct TimeSeriesConfig {
19 pub input_dim: usize,
21 pub window_size: usize,
23 pub step_size: usize,
25 pub forecast_horizon: usize,
27 pub normalize: bool,
29 pub encoding: EncodingMethod,
31}
32
33impl Default for TimeSeriesConfig {
34 fn default() -> Self {
35 Self {
36 input_dim: 1,
37 window_size: 50,
38 step_size: 1,
39 forecast_horizon: 1,
40 normalize: true,
41 encoding: EncodingMethod::Direct,
42 }
43 }
44}
45
46impl TimeSeriesConfig {
47 pub fn new(input_dim: usize, window_size: usize) -> Self {
49 Self {
50 input_dim,
51 window_size,
52 ..Default::default()
53 }
54 }
55
56 pub fn with_forecast_horizon(mut self, horizon: usize) -> Self {
58 self.forecast_horizon = horizon;
59 self
60 }
61
62 pub fn with_step_size(mut self, step_size: usize) -> Self {
64 self.step_size = step_size;
65 self
66 }
67
68 pub fn with_encoding(mut self, encoding: EncodingMethod) -> Self {
70 self.encoding = encoding;
71 self
72 }
73
74 pub fn with_normalize(mut self, normalize: bool) -> Self {
76 self.normalize = normalize;
77 self
78 }
79
80 pub fn validate(&self) -> QearResult<()> {
82 if self.input_dim == 0 {
83 return Err(QearError::invalid_parameter(
84 "input_dim",
85 "must be greater than 0",
86 ));
87 }
88 if self.window_size == 0 {
89 return Err(QearError::invalid_parameter(
90 "window_size",
91 "must be greater than 0",
92 ));
93 }
94 if self.step_size == 0 {
95 return Err(QearError::invalid_parameter(
96 "step_size",
97 "must be greater than 0",
98 ));
99 }
100 Ok(())
101 }
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
107pub enum EncodingMethod {
108 Direct,
110 Phase,
112 Amplitude,
114 Binary,
116 Fourier,
118}
119
120#[derive(Debug, Clone)]
122#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
123pub struct InputEncoder {
124 method: EncodingMethod,
126 output_dim: usize,
128 norm_mean: Option<Array1<f64>>,
130 norm_std: Option<Array1<f64>>,
131}
132
133impl InputEncoder {
134 pub fn new(method: EncodingMethod, output_dim: usize) -> Self {
136 Self {
137 method,
138 output_dim,
139 norm_mean: None,
140 norm_std: None,
141 }
142 }
143
144 pub fn fit(&mut self, data: &Array2<f64>) {
146 let mean = data.mean_axis(Axis(0)).unwrap();
147 let std = data.std_axis(Axis(0), 0.0);
148
149 let std = std.mapv(|s| if s < 1e-10 { 1.0 } else { s });
151
152 self.norm_mean = Some(mean);
153 self.norm_std = Some(std);
154 }
155
156 pub fn normalize(&self, data: &Array2<f64>) -> QearResult<Array2<f64>> {
158 match (&self.norm_mean, &self.norm_std) {
159 (Some(mean), Some(std)) => {
160 let mut normalized = data.clone();
161 for mut row in normalized.rows_mut() {
162 for (j, val) in row.iter_mut().enumerate() {
163 if j < mean.len() {
164 *val = (*val - mean[j]) / std[j];
165 }
166 }
167 }
168 Ok(normalized)
169 }
170 _ => Ok(data.clone()),
171 }
172 }
173
174 pub fn encode(&self, input: &Array1<f64>) -> QearResult<Array1<f64>> {
176 match self.method {
177 EncodingMethod::Direct => Ok(input.clone()),
178 EncodingMethod::Phase => self.phase_encode(input),
179 EncodingMethod::Amplitude => self.amplitude_encode(input),
180 EncodingMethod::Binary => self.binary_encode(input),
181 EncodingMethod::Fourier => self.fourier_encode(input),
182 }
183 }
184
185 pub fn encode_batch(&self, inputs: &Array2<f64>) -> QearResult<Array2<f64>> {
187 let n_samples = inputs.nrows();
188 let encoded_dim = self.encoded_dim(inputs.ncols());
189
190 let mut result = Array2::zeros((n_samples, encoded_dim));
191
192 for i in 0..n_samples {
193 let input = inputs.row(i).to_owned();
194 let encoded = self.encode(&input)?;
195 for j in 0..encoded_dim {
196 result[[i, j]] = encoded[j];
197 }
198 }
199
200 Ok(result)
201 }
202
203 pub fn encoded_dim(&self, input_dim: usize) -> usize {
205 match self.method {
206 EncodingMethod::Direct => input_dim,
207 EncodingMethod::Phase => input_dim * 2, EncodingMethod::Amplitude => input_dim,
209 EncodingMethod::Binary => input_dim,
210 EncodingMethod::Fourier => self.output_dim,
211 }
212 }
213
214 fn phase_encode(&self, input: &Array1<f64>) -> QearResult<Array1<f64>> {
216 let n = input.len();
217 let mut encoded = Array1::zeros(n * 2);
218
219 for i in 0..n {
220 let angle = input[i] * std::f64::consts::PI;
221 encoded[2 * i] = angle.sin();
222 encoded[2 * i + 1] = angle.cos();
223 }
224
225 Ok(encoded)
226 }
227
228 fn amplitude_encode(&self, input: &Array1<f64>) -> QearResult<Array1<f64>> {
230 let norm = input.iter().map(|x| x * x).sum::<f64>().sqrt();
231 if norm < 1e-10 {
232 Ok(input.clone())
233 } else {
234 Ok(input / norm)
235 }
236 }
237
238 fn binary_encode(&self, input: &Array1<f64>) -> QearResult<Array1<f64>> {
240 Ok(input.mapv(|x| if x > 0.0 { 1.0 } else { 0.0 }))
241 }
242
243 fn fourier_encode(&self, input: &Array1<f64>) -> QearResult<Array1<f64>> {
245 let n = input.len();
246 let output_dim = self.output_dim;
247 let n_frequencies = output_dim / (2 * n);
248
249 let mut encoded = Array1::zeros(output_dim);
250 let mut idx = 0;
251
252 for i in 0..n {
253 for k in 1..=n_frequencies {
254 let angle = input[i] * k as f64 * std::f64::consts::PI;
255 if idx < output_dim {
256 encoded[idx] = angle.sin();
257 idx += 1;
258 }
259 if idx < output_dim {
260 encoded[idx] = angle.cos();
261 idx += 1;
262 }
263 }
264 }
265
266 Ok(encoded)
267 }
268
269 pub fn method(&self) -> EncodingMethod {
271 self.method
272 }
273}
274
275#[derive(Debug, Clone)]
277#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
278pub struct SlidingWindow {
279 window_size: usize,
281 step_size: usize,
283 forecast_horizon: usize,
285}
286
287impl SlidingWindow {
288 pub fn new(window_size: usize, step_size: usize, forecast_horizon: usize) -> Self {
290 Self {
291 window_size,
292 step_size,
293 forecast_horizon,
294 }
295 }
296
297 pub fn create_windows(
303 &self,
304 data: &Array2<f64>,
305 ) -> QearResult<(Vec<Array2<f64>>, Vec<Array2<f64>>)> {
306 let n_samples = data.nrows();
307 let input_dim = data.ncols();
308
309 let total_length = self.window_size + self.forecast_horizon;
310 if n_samples < total_length {
311 return Err(QearError::insufficient_data(total_length, n_samples));
312 }
313
314 let n_windows = (n_samples - total_length) / self.step_size + 1;
315 let mut windows = Vec::with_capacity(n_windows);
316 let mut targets = Vec::with_capacity(n_windows);
317
318 for i in 0..n_windows {
319 let start = i * self.step_size;
320 let window_end = start + self.window_size;
321 let _target_end = window_end + self.forecast_horizon;
322
323 let mut window = Array2::zeros((self.window_size, input_dim));
325 for t in 0..self.window_size {
326 for d in 0..input_dim {
327 window[[t, d]] = data[[start + t, d]];
328 }
329 }
330 windows.push(window);
331
332 let mut target = Array2::zeros((self.forecast_horizon, input_dim));
334 for t in 0..self.forecast_horizon {
335 for d in 0..input_dim {
336 target[[t, d]] = data[[window_end + t, d]];
337 }
338 }
339 targets.push(target);
340 }
341
342 Ok((windows, targets))
343 }
344
345 pub fn create_inference_windows(&self, data: &Array2<f64>) -> QearResult<Vec<Array2<f64>>> {
347 let n_samples = data.nrows();
348 let input_dim = data.ncols();
349
350 if n_samples < self.window_size {
351 return Err(QearError::insufficient_data(self.window_size, n_samples));
352 }
353
354 let n_windows = (n_samples - self.window_size) / self.step_size + 1;
355 let mut windows = Vec::with_capacity(n_windows);
356
357 for i in 0..n_windows {
358 let start = i * self.step_size;
359
360 let mut window = Array2::zeros((self.window_size, input_dim));
361 for t in 0..self.window_size {
362 for d in 0..input_dim {
363 window[[t, d]] = data[[start + t, d]];
364 }
365 }
366 windows.push(window);
367 }
368
369 Ok(windows)
370 }
371
372 pub fn window_size(&self) -> usize {
374 self.window_size
375 }
376
377 pub fn step_size(&self) -> usize {
379 self.step_size
380 }
381
382 pub fn forecast_horizon(&self) -> usize {
384 self.forecast_horizon
385 }
386}
387
388#[derive(Debug)]
390pub struct TimeSeriesProcessor {
391 config: TimeSeriesConfig,
393 reservoir: QuantumReservoir,
395 attention: Option<AttentionFusion>,
397 feature_extractor: FeatureExtractor,
399 encoder: InputEncoder,
401 window: SlidingWindow,
403}
404
405impl TimeSeriesProcessor {
406 pub fn new(
408 ts_config: TimeSeriesConfig,
409 reservoir_config: ReservoirConfig,
410 ) -> QearResult<Self> {
411 ts_config.validate()?;
412
413 let reservoir = QuantumReservoir::new(reservoir_config)?;
414 let feature_extractor = FeatureExtractor::default_extractor();
415 let encoder = InputEncoder::new(ts_config.encoding, ts_config.input_dim * 8);
416 let window = SlidingWindow::new(
417 ts_config.window_size,
418 ts_config.step_size,
419 ts_config.forecast_horizon,
420 );
421
422 Ok(Self {
423 config: ts_config,
424 reservoir,
425 attention: None,
426 feature_extractor,
427 encoder,
428 window,
429 })
430 }
431
432 pub fn with_attention(mut self, attention: AttentionFusion) -> Self {
434 self.attention = Some(attention);
435 self
436 }
437
438 pub fn process_window(&mut self, window: &Array2<f64>) -> QearResult<Array2<f64>> {
440 let encoded = self.encoder.encode_batch(window)?;
442
443 self.reservoir.reset();
445 let states = self.reservoir.run(&encoded)?;
446
447 let processed = if let Some(ref mut attention) = self.attention {
449 attention.forward(&states, &encoded)?
450 } else {
451 states
452 };
453
454 let features = self.feature_extractor.extract(&processed)?;
456
457 Ok(features)
458 }
459
460 pub fn process_batch(&mut self, windows: &[Array2<f64>]) -> QearResult<Vec<Array2<f64>>> {
462 let mut results = Vec::with_capacity(windows.len());
463
464 for window in windows {
465 let features = self.process_window(window)?;
466 results.push(features);
467 }
468
469 Ok(results)
470 }
471
472 pub fn prepare_training_data(
474 &mut self,
475 data: &Array2<f64>,
476 ) -> QearResult<(Vec<Array2<f64>>, Vec<Array2<f64>>)> {
477 let (windows, targets) = self.window.create_windows(data)?;
479
480 let features = self.process_batch(&windows)?;
482
483 Ok((features, targets))
484 }
485
486 pub fn config(&self) -> &TimeSeriesConfig {
488 &self.config
489 }
490
491 pub fn reservoir(&self) -> &QuantumReservoir {
493 &self.reservoir
494 }
495
496 pub fn reservoir_mut(&mut self) -> &mut QuantumReservoir {
498 &mut self.reservoir
499 }
500
501 pub fn feature_extractor(&self) -> &FeatureExtractor {
503 &self.feature_extractor
504 }
505
506 pub fn window(&self) -> &SlidingWindow {
508 &self.window
509 }
510}
511
512#[derive(Debug, Clone)]
514#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
515pub struct PredictionHead {
516 output_dim: usize,
518 horizon: usize,
520 weights: Option<Array2<f64>>,
522 bias: Option<Array1<f64>>,
524}
525
526impl PredictionHead {
527 pub fn new(output_dim: usize, horizon: usize) -> Self {
529 Self {
530 output_dim,
531 horizon,
532 weights: None,
533 bias: None,
534 }
535 }
536
537 pub fn initialize(&mut self, feature_dim: usize) {
539 let total_output = self.output_dim * self.horizon;
540
541 let scale = (2.0 / (feature_dim + total_output) as f64).sqrt();
543 let mut rng = rand::thread_rng();
544 use rand::Rng;
545
546 let mut weights = Array2::zeros((total_output, feature_dim));
547 for i in 0..total_output {
548 for j in 0..feature_dim {
549 weights[[i, j]] = (rng.gen::<f64>() - 0.5) * 2.0 * scale;
550 }
551 }
552
553 self.weights = Some(weights);
554 self.bias = Some(Array1::zeros(total_output));
555 }
556
557 pub fn forward(&self, features: &Array1<f64>) -> QearResult<Array2<f64>> {
559 let weights = self.weights.as_ref().ok_or_else(|| {
560 QearError::not_trained("Prediction head not initialized")
561 })?;
562 let bias = self.bias.as_ref().unwrap();
563
564 if features.len() != weights.ncols() {
565 return Err(QearError::dimension_mismatch(weights.ncols(), features.len()));
566 }
567
568 let output = weights.dot(features) + bias;
570
571 let mut predictions = Array2::zeros((self.horizon, self.output_dim));
573 for h in 0..self.horizon {
574 for d in 0..self.output_dim {
575 predictions[[h, d]] = output[h * self.output_dim + d];
576 }
577 }
578
579 Ok(predictions)
580 }
581
582 pub fn set_weights(&mut self, weights: Array2<f64>, bias: Array1<f64>) {
584 self.weights = Some(weights);
585 self.bias = Some(bias);
586 }
587
588 pub fn weights(&self) -> Option<&Array2<f64>> {
590 self.weights.as_ref()
591 }
592
593 pub fn bias(&self) -> Option<&Array1<f64>> {
595 self.bias.as_ref()
596 }
597
598 pub fn is_initialized(&self) -> bool {
600 self.weights.is_some()
601 }
602}
603
604#[cfg(test)]
605mod tests {
606 use super::*;
607
608 #[test]
609 fn test_timeseries_config_default() {
610 let config = TimeSeriesConfig::default();
611 assert_eq!(config.input_dim, 1);
612 assert_eq!(config.window_size, 50);
613 }
614
615 #[test]
616 fn test_timeseries_config_validation() {
617 let config = TimeSeriesConfig::new(0, 50);
618 assert!(config.validate().is_err());
619
620 let config = TimeSeriesConfig::new(1, 50);
621 assert!(config.validate().is_ok());
622 }
623
624 #[test]
625 fn test_input_encoder_direct() {
626 let encoder = InputEncoder::new(EncodingMethod::Direct, 10);
627 let input = Array1::from_vec(vec![1.0, 2.0, 3.0]);
628 let encoded = encoder.encode(&input).unwrap();
629 assert_eq!(encoded.len(), 3);
630 }
631
632 #[test]
633 fn test_input_encoder_phase() {
634 let encoder = InputEncoder::new(EncodingMethod::Phase, 10);
635 let input = Array1::from_vec(vec![0.5, 0.0]);
636 let encoded = encoder.encode(&input).unwrap();
637 assert_eq!(encoded.len(), 4); }
639
640 #[test]
641 fn test_input_encoder_amplitude() {
642 let encoder = InputEncoder::new(EncodingMethod::Amplitude, 10);
643 let input = Array1::from_vec(vec![3.0, 4.0]);
644 let encoded = encoder.encode(&input).unwrap();
645
646 let norm: f64 = encoded.iter().map(|x| x * x).sum::<f64>().sqrt();
648 assert!((norm - 1.0).abs() < 1e-10);
649 }
650
651 #[test]
652 fn test_sliding_window_creation() {
653 let window = SlidingWindow::new(10, 2, 3);
654 let data = Array2::from_shape_fn((30, 2), |(i, j)| (i + j) as f64);
655
656 let (windows, targets) = window.create_windows(&data).unwrap();
657
658 assert_eq!(windows.len(), 9);
660 assert_eq!(targets.len(), 9);
661 assert_eq!(windows[0].nrows(), 10);
662 assert_eq!(targets[0].nrows(), 3);
663 }
664
665 #[test]
666 fn test_sliding_window_insufficient_data() {
667 let window = SlidingWindow::new(10, 1, 3);
668 let data = Array2::from_shape_fn((10, 2), |(i, j)| (i + j) as f64);
669
670 let result = window.create_windows(&data);
671 assert!(result.is_err());
672 }
673
674 #[test]
675 fn test_time_series_processor_creation() {
676 let ts_config = TimeSeriesConfig::new(3, 20);
677 let reservoir_config = ReservoirConfig::new(4).with_seed(42);
678
679 let processor = TimeSeriesProcessor::new(ts_config, reservoir_config);
680 assert!(processor.is_ok());
681 }
682
683 #[test]
684 fn test_time_series_processor_window() {
685 let ts_config = TimeSeriesConfig::new(3, 20);
686 let reservoir_config = ReservoirConfig::new(4).with_seed(42);
687 let mut processor = TimeSeriesProcessor::new(ts_config, reservoir_config).unwrap();
688
689 let window = Array2::from_shape_fn((20, 3), |(i, j)| ((i + j) as f64 / 23.0).sin());
690 let features = processor.process_window(&window).unwrap();
691
692 assert_eq!(features.nrows(), 20);
693 assert!(features.ncols() > 0);
694 }
695
696 #[test]
697 fn test_prediction_head() {
698 let mut head = PredictionHead::new(2, 3);
699 head.initialize(10);
700
701 let features = Array1::from_vec((0..10).map(|i| i as f64 / 10.0).collect());
702 let predictions = head.forward(&features).unwrap();
703
704 assert_eq!(predictions.nrows(), 3); assert_eq!(predictions.ncols(), 2); }
707
708 #[test]
709 fn test_encoder_normalization() {
710 let mut encoder = InputEncoder::new(EncodingMethod::Direct, 10);
711 let data = Array2::from_shape_fn((100, 3), |(i, j)| (i * 10 + j) as f64);
712
713 encoder.fit(&data);
714 let normalized = encoder.normalize(&data).unwrap();
715
716 let mean = normalized.mean_axis(Axis(0)).unwrap();
718 for m in mean.iter() {
719 assert!(m.abs() < 1e-10);
720 }
721 }
722
723 #[test]
724 fn test_inference_windows() {
725 let window = SlidingWindow::new(10, 5, 1);
726 let data = Array2::from_shape_fn((50, 2), |(i, j)| (i + j) as f64);
727
728 let windows = window.create_inference_windows(&data).unwrap();
729
730 assert_eq!(windows.len(), 9);
732 }
733}