1use super::config::TransformerBasedOptimizerConfig;
4use crate::error::Result;
5use scirs2_core::ndarray::{Array1, Array2};
6use scirs2_core::numeric::Float;
7use std::collections::VecDeque;
8use std::fmt::Debug;
9
10type SequenceDataTuple<T> = (Vec<Array2<T>>, Vec<Array2<T>>, Vec<Array1<T>>);
12
13#[derive(Debug, Clone, Copy, PartialEq)]
15pub enum SequenceProcessingStrategy {
16 SlidingWindow,
18 Hierarchical,
20 AttentionBased,
22 Adaptive,
24 TruncatedBPTT,
26}
27
28pub struct OptimizationSequenceProcessor<
30 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
31> {
32 strategy: SequenceProcessingStrategy,
34
35 max_sequence_length: usize,
37
38 window_size: usize,
40
41 window_overlap: usize,
43
44 sequence_buffer: SequenceBuffer<T>,
46
47 statistics: SequenceStatistics<T>,
49
50 preprocessor: SequencePreprocessor<T>,
52}
53
54impl<
55 T: Float
56 + Debug
57 + scirs2_core::numeric::FromPrimitive
58 + scirs2_core::ndarray::ScalarOperand
59 + Send
60 + Sync,
61 > OptimizationSequenceProcessor<T>
62{
63 pub fn new(config: &TransformerBasedOptimizerConfig<T>) -> Result<Self> {
73 let strategy = SequenceProcessingStrategy::SlidingWindow;
74 let max_sequence_length = config.sequence_length;
75 if max_sequence_length == 0 {
76 return Err(crate::error::OptimError::InvalidConfig(
77 "sequence_length must be greater than 0".to_string(),
78 ));
79 }
80 let window_size = (max_sequence_length / 2).max(1);
81 let window_overlap = (window_size / 4).min(window_size - 1);
83 let model_dimension = config.model_dimension;
84
85 let sequence_buffer = SequenceBuffer::new(1000)?;
86 let statistics = SequenceStatistics::new();
87 let preprocessor = SequencePreprocessor::new(model_dimension)?;
88
89 Ok(Self {
90 strategy,
91 max_sequence_length,
92 window_size,
93 window_overlap,
94 sequence_buffer,
95 statistics,
96 preprocessor,
97 })
98 }
99
100 pub fn process_optimization_sequence(
102 &mut self,
103 gradient_history: &Array2<T>,
104 parameter_history: &Array2<T>,
105 loss_history: &Array1<T>,
106 ) -> Result<Array2<T>> {
107 self.statistics
109 .update(gradient_history, parameter_history, loss_history)?;
110
111 self.sequence_buffer
113 .add_sequence(gradient_history, parameter_history, loss_history)?;
114
115 match self.strategy {
116 SequenceProcessingStrategy::SlidingWindow => {
117 self.process_sliding_window(gradient_history, parameter_history, loss_history)
118 }
119 SequenceProcessingStrategy::Hierarchical => {
120 self.process_hierarchical(gradient_history, parameter_history, loss_history)
121 }
122 SequenceProcessingStrategy::AttentionBased => {
123 self.process_attention_based(gradient_history, parameter_history, loss_history)
124 }
125 SequenceProcessingStrategy::Adaptive => {
126 self.process_adaptive(gradient_history, parameter_history, loss_history)
127 }
128 SequenceProcessingStrategy::TruncatedBPTT => {
129 self.process_truncated_bptt(gradient_history, parameter_history, loss_history)
130 }
131 }
132 }
133
134 fn process_sliding_window(
136 &mut self,
137 gradient_history: &Array2<T>,
138 parameter_history: &Array2<T>,
139 loss_history: &Array1<T>,
140 ) -> Result<Array2<T>> {
141 let sequence_length = gradient_history.shape()[0];
142
143 if sequence_length <= self.max_sequence_length {
144 return self.combine_sequences(gradient_history, parameter_history, loss_history);
146 }
147
148 let mut processed_chunks = Vec::new();
152 let step_size = self.window_size.saturating_sub(self.window_overlap).max(1);
153
154 for start in (0..sequence_length).step_by(step_size) {
155 let end = (start + self.window_size).min(sequence_length);
156
157 let grad_chunk = gradient_history.slice(s![start..end, ..]);
158 let param_chunk = parameter_history.slice(s![start..end, ..]);
159 let loss_chunk = loss_history.slice(s![start..end]);
160
161 let processed_chunk = self.combine_sequences(
162 &grad_chunk.to_owned(),
163 ¶m_chunk.to_owned(),
164 &loss_chunk.to_owned(),
165 )?;
166
167 processed_chunks.push(processed_chunk);
168 }
169
170 self.combine_chunks(&processed_chunks)
172 }
173
174 fn process_hierarchical(
176 &mut self,
177 gradient_history: &Array2<T>,
178 parameter_history: &Array2<T>,
179 loss_history: &Array1<T>,
180 ) -> Result<Array2<T>> {
181 let sequence_length = gradient_history.shape()[0];
182
183 let mut levels = Vec::new();
185 let mut current_level =
186 self.combine_sequences(gradient_history, parameter_history, loss_history)?;
187 levels.push(current_level.clone());
188
189 let mut current_length = sequence_length;
191 while current_length > self.max_sequence_length {
192 current_length /= 2;
193 current_level = self.downsample_sequence(¤t_level, current_length)?;
194 levels.push(current_level.clone());
195 }
196
197 levels.pop().ok_or_else(|| {
201 crate::error::OptimError::ComputationError(
202 "hierarchical processing produced no levels".to_string(),
203 )
204 })
205 }
206
207 fn process_attention_based(
209 &mut self,
210 gradient_history: &Array2<T>,
211 parameter_history: &Array2<T>,
212 loss_history: &Array1<T>,
213 ) -> Result<Array2<T>> {
214 let sequence_length = gradient_history.shape()[0];
215
216 if sequence_length <= self.max_sequence_length {
217 return self.combine_sequences(gradient_history, parameter_history, loss_history);
218 }
219
220 let importance_scores = self.compute_importance_scores(gradient_history, loss_history)?;
222
223 let selected_indices =
225 self.select_top_k_indices(&importance_scores, self.max_sequence_length)?;
226
227 let mut selected_gradients =
229 Array2::zeros((self.max_sequence_length, gradient_history.shape()[1]));
230 let mut selected_parameters =
231 Array2::zeros((self.max_sequence_length, parameter_history.shape()[1]));
232 let mut selected_losses = Array1::zeros(self.max_sequence_length);
233
234 for (i, &idx) in selected_indices.iter().enumerate() {
235 selected_gradients
236 .row_mut(i)
237 .assign(&gradient_history.row(idx));
238 selected_parameters
239 .row_mut(i)
240 .assign(¶meter_history.row(idx));
241 selected_losses[i] = loss_history[idx];
242 }
243
244 self.combine_sequences(&selected_gradients, &selected_parameters, &selected_losses)
245 }
246
247 fn process_adaptive(
249 &mut self,
250 gradient_history: &Array2<T>,
251 parameter_history: &Array2<T>,
252 loss_history: &Array1<T>,
253 ) -> Result<Array2<T>> {
254 let change_points = self.detect_change_points(loss_history)?;
256
257 let segments = self.segment_by_change_points(
259 gradient_history,
260 parameter_history,
261 loss_history,
262 &change_points,
263 )?;
264
265 let mut processed_segments = Vec::new();
267 for segment in segments {
268 let processed =
269 self.combine_sequences(&segment.gradients, &segment.parameters, &segment.losses)?;
270 processed_segments.push(processed);
271 }
272
273 self.combine_chunks(&processed_segments)
274 }
275
276 fn process_truncated_bptt(
278 &mut self,
279 gradient_history: &Array2<T>,
280 parameter_history: &Array2<T>,
281 loss_history: &Array1<T>,
282 ) -> Result<Array2<T>> {
283 let sequence_length = gradient_history.shape()[0];
284
285 if sequence_length <= self.max_sequence_length {
286 return self.combine_sequences(gradient_history, parameter_history, loss_history);
287 }
288
289 let start_idx = sequence_length - self.max_sequence_length;
291 let grad_chunk = gradient_history.slice(s![start_idx.., ..]);
292 let param_chunk = parameter_history.slice(s![start_idx.., ..]);
293 let loss_chunk = loss_history.slice(s![start_idx..]);
294
295 self.combine_sequences(
296 &grad_chunk.to_owned(),
297 ¶m_chunk.to_owned(),
298 &loss_chunk.to_owned(),
299 )
300 }
301
302 pub fn trajectory_to_sequences(
304 &mut self,
305 trajectory: &super::OptimizationTrajectory<T>,
306 ) -> Result<Vec<super::TrainingSequence<T>>> {
307 let sequence_length = trajectory.gradient_sequence.shape()[0];
308 let mut sequences = Vec::new();
309
310 let stride = (self.window_size / 2).max(1);
313 for start in (0..sequence_length).step_by(stride) {
314 let end = (start + self.window_size).min(sequence_length);
315
316 if end - start < stride {
317 break; }
319
320 let input_end = end - 1;
321 let target_start = start + 1;
322
323 let input_gradients = trajectory.gradient_sequence.slice(s![start..input_end, ..]);
324 let input_parameters = trajectory
325 .parameter_sequence
326 .slice(s![start..input_end, ..]);
327 let input_losses = trajectory.loss_sequence.slice(s![start..input_end]);
328
329 let target_gradients = trajectory
330 .gradient_sequence
331 .slice(s![target_start..end, ..]);
332 let target_parameters = trajectory
333 .parameter_sequence
334 .slice(s![target_start..end, ..]);
335 let target_losses = trajectory.loss_sequence.slice(s![target_start..end]);
336
337 let input = self.combine_sequences(
338 &input_gradients.to_owned(),
339 &input_parameters.to_owned(),
340 &input_losses.to_owned(),
341 )?;
342
343 let target = self.combine_sequences(
344 &target_gradients.to_owned(),
345 &target_parameters.to_owned(),
346 &target_losses.to_owned(),
347 )?;
348
349 sequences.push(super::TrainingSequence {
350 input,
351 target,
352 sequence_length: input_end - start,
353 });
354 }
355
356 Ok(sequences)
357 }
358
359 fn combine_sequences(
361 &self,
362 gradients: &Array2<T>,
363 parameters: &Array2<T>,
364 losses: &Array1<T>,
365 ) -> Result<Array2<T>> {
366 self.preprocessor
367 .combine_sequences(gradients, parameters, losses)
368 }
369
370 fn combine_chunks(&self, chunks: &[Array2<T>]) -> Result<Array2<T>> {
372 if chunks.is_empty() {
373 return Err(crate::error::OptimError::Other(
374 "No chunks to combine".to_string(),
375 ));
376 }
377
378 if chunks.len() == 1 {
379 return Ok(chunks[0].clone());
380 }
381
382 let total_length: usize = chunks.iter().map(|chunk| chunk.shape()[0]).sum();
384 let feature_dim = chunks[0].shape()[1];
385
386 let mut combined = Array2::zeros((total_length.min(self.max_sequence_length), feature_dim));
387 let mut current_pos = 0;
388
389 for chunk in chunks {
390 let chunk_len = chunk.shape()[0];
391 let copy_len = (chunk_len).min(self.max_sequence_length - current_pos);
392
393 if copy_len == 0 {
394 break;
395 }
396
397 combined
398 .slice_mut(s![current_pos..current_pos + copy_len, ..])
399 .assign(&chunk.slice(s![..copy_len, ..]));
400
401 current_pos += copy_len;
402
403 if current_pos >= self.max_sequence_length {
404 break;
405 }
406 }
407
408 Ok(combined)
409 }
410
411 fn downsample_sequence(&self, sequence: &Array2<T>, target_length: usize) -> Result<Array2<T>> {
413 let current_length = sequence.shape()[0];
414 let feature_dim = sequence.shape()[1];
415
416 if current_length <= target_length {
417 return Ok(sequence.clone());
418 }
419
420 let mut downsampled = Array2::zeros((target_length, feature_dim));
421 let step = current_length as f64 / target_length as f64;
422
423 for i in 0..target_length {
424 let source_idx = (i as f64 * step) as usize;
425 downsampled
426 .row_mut(i)
427 .assign(&sequence.row(source_idx.min(current_length - 1)));
428 }
429
430 Ok(downsampled)
431 }
432
433 fn compute_importance_scores(
435 &self,
436 gradients: &Array2<T>,
437 losses: &Array1<T>,
438 ) -> Result<Array1<T>> {
439 let sequence_length = gradients.shape()[0];
440 let mut scores = Array1::zeros(sequence_length);
441
442 for i in 0..sequence_length {
443 let grad_norm = gradients
445 .row(i)
446 .iter()
447 .map(|&x| x * x)
448 .fold(T::zero(), |acc, x| acc + x)
449 .sqrt();
450
451 let loss_change = if i > 0 {
452 (losses[i] - losses[i - 1]).abs()
453 } else {
454 T::zero()
455 };
456
457 scores[i] = grad_norm + loss_change;
458 }
459
460 Ok(scores)
461 }
462
463 fn select_top_k_indices(&self, scores: &Array1<T>, k: usize) -> Result<Vec<usize>> {
465 let mut indexed_scores: Vec<(usize, T)> = scores
466 .iter()
467 .enumerate()
468 .map(|(i, &score)| (i, score))
469 .collect();
470
471 indexed_scores.sort_by(|a, b| {
474 b.1.to_f64()
475 .unwrap_or(f64::NAN)
476 .total_cmp(&a.1.to_f64().unwrap_or(f64::NAN))
477 });
478
479 let mut selected_indices: Vec<usize> = indexed_scores
480 .into_iter()
481 .take(k)
482 .map(|(idx, _)| idx)
483 .collect();
484
485 selected_indices.sort();
486 Ok(selected_indices)
487 }
488
489 fn detect_change_points(&self, losses: &Array1<T>) -> Result<Vec<usize>> {
491 let mut change_points = vec![0]; let window_size = 5;
493 let threshold = scirs2_core::numeric::NumCast::from(0.1).unwrap_or_else(|| T::zero());
494
495 let last_candidate = losses.len().saturating_sub(window_size);
500 for i in window_size..last_candidate {
501 let before_mean = match losses.slice(s![i - window_size..i]).mean() {
502 Some(m) => m,
503 None => continue,
504 };
505 let after_mean = match losses.slice(s![i..i + window_size]).mean() {
506 Some(m) => m,
507 None => continue,
508 };
509
510 if (before_mean - after_mean).abs() > threshold {
511 change_points.push(i);
512 }
513 }
514
515 change_points.push(losses.len() - 1); Ok(change_points)
517 }
518
519 fn segment_by_change_points(
521 &self,
522 gradients: &Array2<T>,
523 parameters: &Array2<T>,
524 losses: &Array1<T>,
525 change_points: &[usize],
526 ) -> Result<Vec<SequenceSegment<T>>> {
527 let mut segments = Vec::new();
528
529 for i in 0..change_points.len() - 1 {
530 let start = change_points[i];
531 let end = change_points[i + 1];
532
533 let segment = SequenceSegment {
534 gradients: gradients.slice(s![start..end, ..]).to_owned(),
535 parameters: parameters.slice(s![start..end, ..]).to_owned(),
536 losses: losses.slice(s![start..end]).to_owned(),
537 start_index: start,
538 end_index: end,
539 };
540
541 segments.push(segment);
542 }
543
544 Ok(segments)
545 }
546
547 pub fn set_strategy(&mut self, strategy: SequenceProcessingStrategy) {
549 self.strategy = strategy;
550 }
551
552 pub fn get_strategy(&self) -> SequenceProcessingStrategy {
554 self.strategy
555 }
556
557 pub fn get_statistics(&self) -> &SequenceStatistics<T> {
559 &self.statistics
560 }
561
562 pub fn reset(&mut self) -> Result<()> {
564 self.sequence_buffer.clear();
565 self.statistics.reset();
566 Ok(())
567 }
568}
569
570pub struct SequenceBuffer<
572 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
573> {
574 gradient_buffer: VecDeque<Array2<T>>,
576
577 parameter_buffer: VecDeque<Array2<T>>,
579
580 loss_buffer: VecDeque<Array1<T>>,
582
583 max_size: usize,
585}
586
587impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
588 SequenceBuffer<T>
589{
590 pub fn new(max_size: usize) -> Result<Self> {
597 Ok(Self {
598 gradient_buffer: VecDeque::new(),
599 parameter_buffer: VecDeque::new(),
600 loss_buffer: VecDeque::new(),
601 max_size,
602 })
603 }
604
605 pub fn add_sequence(
606 &mut self,
607 gradients: &Array2<T>,
608 parameters: &Array2<T>,
609 losses: &Array1<T>,
610 ) -> Result<()> {
611 self.gradient_buffer.push_back(gradients.clone());
612 self.parameter_buffer.push_back(parameters.clone());
613 self.loss_buffer.push_back(losses.clone());
614
615 while self.gradient_buffer.len() > self.max_size {
616 self.gradient_buffer.pop_front();
617 self.parameter_buffer.pop_front();
618 self.loss_buffer.pop_front();
619 }
620
621 Ok(())
622 }
623
624 pub fn clear(&mut self) {
625 self.gradient_buffer.clear();
626 self.parameter_buffer.clear();
627 self.loss_buffer.clear();
628 }
629
630 pub fn get_recent_sequences(&self, count: usize) -> SequenceDataTuple<T> {
631 let actual_count = count.min(self.gradient_buffer.len());
632
633 let gradients = self
634 .gradient_buffer
635 .iter()
636 .rev()
637 .take(actual_count)
638 .cloned()
639 .collect();
640 let parameters = self
641 .parameter_buffer
642 .iter()
643 .rev()
644 .take(actual_count)
645 .cloned()
646 .collect();
647 let losses = self
648 .loss_buffer
649 .iter()
650 .rev()
651 .take(actual_count)
652 .cloned()
653 .collect();
654
655 (gradients, parameters, losses)
656 }
657}
658
659pub struct SequenceStatistics<
661 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
662> {
663 gradient_stats: StatisticsAccumulator<T>,
665
666 parameter_stats: StatisticsAccumulator<T>,
668
669 loss_stats: StatisticsAccumulator<T>,
671
672 length_stats: StatisticsAccumulator<T>,
674}
675
676impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static> Default
677 for SequenceStatistics<T>
678{
679 fn default() -> Self {
680 Self::new()
681 }
682}
683
684impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
685 SequenceStatistics<T>
686{
687 pub fn new() -> Self {
688 Self {
689 gradient_stats: StatisticsAccumulator::new(),
690 parameter_stats: StatisticsAccumulator::new(),
691 loss_stats: StatisticsAccumulator::new(),
692 length_stats: StatisticsAccumulator::new(),
693 }
694 }
695
696 pub fn update(
697 &mut self,
698 gradients: &Array2<T>,
699 parameters: &Array2<T>,
700 losses: &Array1<T>,
701 ) -> Result<()> {
702 self.gradient_stats.update_from_array2(gradients);
703 self.parameter_stats.update_from_array2(parameters);
704 self.loss_stats.update_from_array1(losses);
705 self.length_stats
706 .update(crate::common::cast_scalar(gradients.shape()[0])?);
707
708 Ok(())
709 }
710
711 pub fn reset(&mut self) {
712 self.gradient_stats.reset();
713 self.parameter_stats.reset();
714 self.loss_stats.reset();
715 self.length_stats.reset();
716 }
717
718 pub fn get_gradient_stats(&self) -> &StatisticsAccumulator<T> {
719 &self.gradient_stats
720 }
721
722 pub fn get_parameter_stats(&self) -> &StatisticsAccumulator<T> {
723 &self.parameter_stats
724 }
725
726 pub fn get_loss_stats(&self) -> &StatisticsAccumulator<T> {
727 &self.loss_stats
728 }
729}
730
731pub struct SequencePreprocessor<
738 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
739> {
740 model_dimension: usize,
742
743 _element: std::marker::PhantomData<T>,
744}
745
746impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
747 SequencePreprocessor<T>
748{
749 pub fn new(model_dimension: usize) -> Result<Self> {
750 Ok(Self {
751 model_dimension,
752 _element: std::marker::PhantomData,
753 })
754 }
755
756 pub fn combine_sequences(
757 &self,
758 gradients: &Array2<T>,
759 parameters: &Array2<T>,
760 losses: &Array1<T>,
761 ) -> Result<Array2<T>> {
762 let sequence_length = gradients.shape()[0];
763 let grad_dim = gradients.shape()[1];
764 let param_dim = parameters.shape()[1];
765
766 let feature_dim = grad_dim + param_dim + 2;
768 let mut combined = Array2::zeros((sequence_length, feature_dim.min(self.model_dimension)));
769
770 for i in 0..sequence_length {
771 let mut feature_idx = 0;
772
773 for j in 0..grad_dim.min(self.model_dimension / 3) {
775 if feature_idx < combined.shape()[1] {
776 combined[[i, feature_idx]] = gradients[[i, j]];
777 feature_idx += 1;
778 }
779 }
780
781 for j in 0..param_dim.min(self.model_dimension / 3) {
783 if feature_idx < combined.shape()[1] {
784 combined[[i, feature_idx]] = parameters[[i, j]];
785 feature_idx += 1;
786 }
787 }
788
789 if feature_idx < combined.shape()[1] {
791 combined[[i, feature_idx]] = losses[i];
792 feature_idx += 1;
793 }
794
795 if feature_idx < combined.shape()[1] && i > 0 {
797 let normalized_loss = if losses[0] != T::zero() {
798 losses[i] / losses[0]
799 } else {
800 T::one()
801 };
802 combined[[i, feature_idx]] = normalized_loss;
803 }
804 }
805
806 Ok(combined)
807 }
808}
809
810pub struct ChunkingStrategy<
812 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
813> {
814 max_chunk_size: usize,
816
817 overlap_size: usize,
819
820 chunk_stats: StatisticsAccumulator<T>,
822}
823
824impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
825 ChunkingStrategy<T>
826{
827 pub fn new(max_chunk_size: usize, overlap_size: usize) -> Result<Self> {
833 if max_chunk_size == 0 {
834 return Err(crate::error::OptimError::InvalidConfig(
835 "max_chunk_size must be greater than 0".to_string(),
836 ));
837 }
838 if overlap_size >= max_chunk_size {
839 return Err(crate::error::OptimError::InvalidConfig(format!(
840 "overlap_size ({overlap_size}) must be smaller than max_chunk_size ({max_chunk_size})"
841 )));
842 }
843 Ok(Self {
844 max_chunk_size,
845 overlap_size,
846 chunk_stats: StatisticsAccumulator::new(),
847 })
848 }
849
850 pub fn chunk_stats(&self) -> &StatisticsAccumulator<T> {
856 &self.chunk_stats
857 }
858
859 pub fn create_chunks(&mut self, sequence: &Array2<T>) -> Result<Vec<Array2<T>>> {
860 let sequence_length = sequence.shape()[0];
861 let mut chunks = Vec::new();
862
863 if sequence_length <= self.max_chunk_size {
864 self.chunk_stats.update_from_array2(sequence);
865 chunks.push(sequence.clone());
866 return Ok(chunks);
867 }
868
869 let step_size = self.max_chunk_size.saturating_sub(self.overlap_size).max(1);
870
871 for start in (0..sequence_length).step_by(step_size) {
872 let end = (start + self.max_chunk_size).min(sequence_length);
873 let chunk = sequence.slice(s![start..end, ..]).to_owned();
874 self.chunk_stats.update_from_array2(&chunk);
875 chunks.push(chunk);
876
877 if end >= sequence_length {
878 break;
879 }
880 }
881
882 Ok(chunks)
883 }
884}
885
886#[derive(Debug, Clone)]
888pub struct SequenceSegment<
889 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
890> {
891 pub gradients: Array2<T>,
892 pub parameters: Array2<T>,
893 pub losses: Array1<T>,
894 pub start_index: usize,
895 pub end_index: usize,
896}
897
898pub struct StatisticsAccumulator<
899 T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static,
900> {
901 count: usize,
902 sum: T,
903 sum_sq: T,
904 min: T,
905 max: T,
906}
907
908impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static> Default
909 for StatisticsAccumulator<T>
910{
911 fn default() -> Self {
912 Self::new()
913 }
914}
915
916impl<T: Float + Debug + scirs2_core::ndarray::ScalarOperand + Send + Sync + 'static>
917 StatisticsAccumulator<T>
918{
919 pub fn new() -> Self {
920 Self {
921 count: 0,
922 sum: T::zero(),
923 sum_sq: T::zero(),
924 min: T::infinity(),
925 max: T::neg_infinity(),
926 }
927 }
928
929 pub fn update(&mut self, value: T) {
930 self.count += 1;
931 self.sum = self.sum + value;
932 self.sum_sq = self.sum_sq + value * value;
933 self.min = self.min.min(value);
934 self.max = self.max.max(value);
935 }
936
937 pub fn update_from_array1(&mut self, array: &Array1<T>) {
938 for &value in array.iter() {
939 self.update(value);
940 }
941 }
942
943 pub fn update_from_array2(&mut self, array: &Array2<T>) {
944 for &value in array.iter() {
945 self.update(value);
946 }
947 }
948
949 pub fn mean(&self) -> T {
950 if self.count > 0 {
951 self.sum / scirs2_core::numeric::NumCast::from(self.count).unwrap_or_else(|| T::zero())
952 } else {
953 T::zero()
954 }
955 }
956
957 pub fn variance(&self) -> T {
958 if self.count > 1 {
959 let mean = self.mean();
960 (self.sum_sq
961 / scirs2_core::numeric::NumCast::from(self.count).unwrap_or_else(|| T::zero()))
962 - (mean * mean)
963 } else {
964 T::zero()
965 }
966 }
967
968 pub fn std_dev(&self) -> T {
969 self.variance().sqrt()
970 }
971
972 pub fn min(&self) -> T {
973 self.min
974 }
975
976 pub fn max(&self) -> T {
977 self.max
978 }
979
980 pub fn reset(&mut self) {
981 self.count = 0;
982 self.sum = T::zero();
983 self.sum_sq = T::zero();
984 self.min = T::infinity();
985 self.max = T::neg_infinity();
986 }
987}
988
989use scirs2_core::ndarray::s;
991
992#[cfg(test)]
993mod tests {
994 use super::*;
995
996 #[test]
997 fn test_sequence_processor_creation() {
998 let config = super::super::config::TransformerBasedOptimizerConfig::<f32>::default();
999 let processor = OptimizationSequenceProcessor::new(&config);
1000 assert!(processor.is_ok());
1001 }
1002
1003 #[test]
1004 fn test_sequence_buffer() {
1005 let buffer = SequenceBuffer::<f32>::new(10);
1006 assert!(buffer.is_ok());
1007
1008 let mut buf = buffer.expect("SequenceBuffer::new should succeed");
1009 let gradients = Array2::<f32>::ones((5, 64));
1010 let parameters = Array2::<f32>::ones((5, 64));
1011 let losses = Array1::<f32>::ones(5);
1012
1013 assert!(buf.add_sequence(&gradients, ¶meters, &losses).is_ok());
1014 }
1015
1016 #[test]
1017 fn test_sequence_statistics() {
1018 let mut stats = SequenceStatistics::<f32>::new();
1019
1020 let gradients = Array2::<f32>::ones((10, 5));
1021 let parameters = Array2::<f32>::ones((10, 5));
1022 let losses = Array1::<f32>::ones(10);
1023
1024 assert!(stats.update(&gradients, ¶meters, &losses).is_ok());
1025 assert!(stats.get_gradient_stats().mean() > 0.0);
1026 }
1027
1028 #[test]
1029 fn test_statistics_accumulator() {
1030 let mut acc = StatisticsAccumulator::<f32>::new();
1031
1032 acc.update(1.0);
1033 acc.update(2.0);
1034 acc.update(3.0);
1035
1036 assert_eq!(acc.mean(), 2.0);
1037 assert!(acc.std_dev() > 0.0);
1038 assert_eq!(acc.min(), 1.0);
1039 assert_eq!(acc.max(), 3.0);
1040 }
1041
1042 #[test]
1043 fn test_chunking_strategy() {
1044 let chunking = ChunkingStrategy::<f32>::new(10, 2);
1045 assert!(chunking.is_ok());
1046
1047 let mut strategy = chunking.expect("ChunkingStrategy::new should succeed");
1048 let sequence = Array2::<f32>::ones((25, 5));
1049
1050 let chunks = strategy.create_chunks(&sequence);
1051 assert!(chunks.is_ok());
1052
1053 let chunk_vec = chunks.expect("create_chunks should succeed");
1054 assert!(chunk_vec.len() > 1);
1055 }
1056}