1use super::config::NeuralAdaptiveConfig;
7use super::neural_network::NeuralNetwork;
8use super::pattern_memory::{MatrixFingerprint, OptimizationStrategy, PatternMemory};
9use super::reinforcement_learning::{Experience, ExperienceBuffer, PerformanceMetrics, RLAgent};
10use super::transformer::TransformerModel;
11use crate::error::SparseResult;
12use scirs2_core::numeric::{Float, NumAssign, SparseElement};
13use scirs2_core::simd_ops::SimdUnifiedOps;
14use std::collections::VecDeque;
15use std::sync::atomic::{AtomicUsize, Ordering};
16
17pub struct NeuralAdaptiveSparseProcessor {
19 config: NeuralAdaptiveConfig,
20 neural_network: NeuralNetwork,
21 pattern_memory: PatternMemory,
22 performance_history: VecDeque<PerformanceMetrics>,
23 adaptation_counter: AtomicUsize,
24 optimization_strategies: Vec<OptimizationStrategy>,
25 rl_agent: Option<RLAgent>,
27 transformer: Option<TransformerModel>,
29 experience_buffer: ExperienceBuffer,
31 current_exploration_rate: f64,
33 attention_score_sum: f64,
37 attention_score_samples: usize,
39}
40
41#[derive(Debug, Clone)]
43pub struct NeuralProcessorStats {
44 pub total_operations: usize,
45 pub successful_adaptations: usize,
46 pub average_performance_improvement: f64,
47 pub most_effective_strategy: OptimizationStrategy,
48 pub neural_network_accuracy: f64,
49 pub rl_agent_reward: f64,
50 pub pattern_memory_hit_rate: f64,
51 pub transformer_attention_score: f64,
52}
53
54impl NeuralAdaptiveSparseProcessor {
55 pub fn new(config: NeuralAdaptiveConfig) -> Self {
57 if let Err(e) = config.validate() {
59 panic!("Invalid configuration: {}", e);
60 }
61
62 let neural_network = NeuralNetwork::new(
63 config.modeldim,
64 config.hidden_layers,
65 config.neurons_per_layer,
66 9, config.attention_heads,
68 );
69 let pattern_memory = PatternMemory::new(config.memory_capacity);
70
71 let optimization_strategies = vec![
72 OptimizationStrategy::RowWiseCache,
73 OptimizationStrategy::ColumnWiseLocality,
74 OptimizationStrategy::BlockStructured,
75 OptimizationStrategy::DiagonalOptimized,
76 OptimizationStrategy::Hierarchical,
77 OptimizationStrategy::StreamingCompute,
78 OptimizationStrategy::SIMDVectorized,
79 OptimizationStrategy::ParallelWorkStealing,
80 OptimizationStrategy::AdaptiveHybrid,
81 ];
82
83 let rl_agent = if config.reinforcement_learning {
85 Some(RLAgent::new(
86 config.modeldim,
87 9, config.rl_algorithm,
89 config.learningrate,
90 config.exploration_rate,
91 ))
92 } else {
93 None
94 };
95
96 let transformer = if config.self_attention {
98 Some(TransformerModel::new(
99 config.modeldim,
100 config.transformer_layers,
101 config.attention_heads,
102 config.ff_dim,
103 1000, ))
105 } else {
106 None
107 };
108
109 let experience_buffer = ExperienceBuffer::new(config.replay_buffer_size);
110
111 Self {
112 config: config.clone(),
113 neural_network,
114 pattern_memory,
115 performance_history: VecDeque::new(),
116 adaptation_counter: AtomicUsize::new(0),
117 optimization_strategies,
118 rl_agent,
119 transformer,
120 experience_buffer,
121 current_exploration_rate: config.exploration_rate,
122 attention_score_sum: 0.0,
123 attention_score_samples: 0,
124 }
125 }
126
127 pub fn optimize_operation<T>(
129 &mut self,
130 matrix_features: &[f64],
131 operation_context: &OperationContext,
132 ) -> SparseResult<OptimizationStrategy>
133 where
134 T: Float
135 + SparseElement
136 + NumAssign
137 + SimdUnifiedOps
138 + std::fmt::Debug
139 + Copy
140 + Send
141 + Sync
142 + 'static,
143 {
144 let fingerprint = self.extract_matrix_fingerprint(matrix_features, operation_context);
146
147 if let Some(strategy) = self.pattern_memory.get_strategy(&fingerprint) {
149 return Ok(strategy);
150 }
151
152 let state = self.encode_state(matrix_features, operation_context)?;
154
155 let strategy = if let Some(ref rl_agent) = self.rl_agent {
156 rl_agent.select_action(&state)
157 } else {
158 self.neural_network_select_action(&state)?
159 };
160
161 let experience = Experience {
163 state: state.clone(),
164 action: strategy,
165 reward: 0.0, next_state: state, done: false,
168 timestamp: std::time::SystemTime::now()
169 .duration_since(std::time::UNIX_EPOCH)
170 .unwrap_or_default()
171 .as_secs(),
172 };
173
174 self.experience_buffer.add(experience);
175
176 Ok(strategy)
177 }
178
179 pub fn learn_from_performance(
181 &mut self,
182 strategy: OptimizationStrategy,
183 performance: PerformanceMetrics,
184 matrix_features: &[f64],
185 operation_context: &OperationContext,
186 ) -> SparseResult<()> {
187 let baseline_time = self.estimate_baseline_performance(matrix_features);
189 let reward = performance.compute_reward(baseline_time);
190
191 if let Some(mut experience) = self.experience_buffer.buffer.back_mut() {
193 experience.reward = reward;
194 }
195
196 let fingerprint = self.extract_matrix_fingerprint(matrix_features, operation_context);
198 if reward > 0.0 {
199 self.pattern_memory.store_pattern(fingerprint, strategy);
200 }
201
202 if let Some(ref mut rl_agent) = self.rl_agent {
204 let batch_size = 32.min(self.experience_buffer.len());
205 if batch_size > 0 {
206 let batch = self.experience_buffer.sample(batch_size);
207 rl_agent.train(&batch)?;
208 }
209 }
210
211 self.performance_history.push_back(performance);
213 if self.performance_history.len() > 1000 {
214 self.performance_history.pop_front();
215 }
216
217 self.adaptation_counter.fetch_add(1, Ordering::Relaxed);
219
220 if let Some(ref mut rl_agent) = self.rl_agent {
222 rl_agent.decay_epsilon(0.995);
223 }
224
225 Ok(())
226 }
227
228 fn extract_matrix_fingerprint(
230 &self,
231 features: &[f64],
232 context: &OperationContext,
233 ) -> MatrixFingerprint {
234 let rows = context.matrix_shape.0;
236 let cols = context.matrix_shape.1;
237 let nnz = context.nnz;
238
239 use std::collections::hash_map::DefaultHasher;
241 use std::hash::{Hash, Hasher};
242 let mut hasher = DefaultHasher::new();
243 for (i, &feature) in features.iter().enumerate().take(100) {
244 ((feature * 1000.0) as i64).hash(&mut hasher);
245 }
246 let sparsity_pattern_hash = hasher.finish();
247
248 let row_distribution_type = super::pattern_memory::DistributionType::Random;
250 let column_distribution_type = super::pattern_memory::DistributionType::Random;
251
252 MatrixFingerprint {
253 rows,
254 cols,
255 nnz,
256 sparsity_pattern_hash,
257 row_distribution_type,
258 column_distribution_type,
259 }
260 }
261
262 fn encode_state(
264 &mut self,
265 matrix_features: &[f64],
266 context: &OperationContext,
267 ) -> SparseResult<Vec<f64>> {
268 let mut state = Vec::new();
269
270 state.push(context.matrix_shape.0 as f64);
272 state.push(context.matrix_shape.1 as f64);
273 state.push(context.nnz as f64);
274 state.push(context.nnz as f64 / (context.matrix_shape.0 * context.matrix_shape.1) as f64); state.push(match context.operation_type {
278 OperationType::MatVec => 1.0,
279 OperationType::MatMat => 2.0,
280 OperationType::Solve => 3.0,
281 OperationType::Factorization => 4.0,
282 });
283
284 let feature_size = self.config.modeldim.saturating_sub(state.len());
286 for i in 0..feature_size {
287 if i < matrix_features.len() {
288 state.push(matrix_features[i]);
289 } else {
290 state.push(0.0);
291 }
292 }
293
294 let transformer_output = self
299 .transformer
300 .as_ref()
301 .map(|transformer| transformer.encode_matrix_pattern_with_score(&state));
302
303 match transformer_output {
304 Some((encoded, attention_score)) => {
305 self.attention_score_sum += attention_score;
306 self.attention_score_samples += 1;
307 Ok(encoded)
308 }
309 None => Ok(state),
310 }
311 }
312
313 fn neural_network_select_action(&self, state: &[f64]) -> SparseResult<OptimizationStrategy> {
315 let outputs = self.neural_network.forward(state);
316
317 let best_idx = outputs
318 .iter()
319 .enumerate()
320 .max_by(|(_, a), (_, b)| a.partial_cmp(b).expect("Operation failed"))
321 .map(|(idx, _)| idx)
322 .unwrap_or(0);
323
324 Ok(self.optimization_strategies[best_idx % self.optimization_strategies.len()])
325 }
326
327 fn estimate_baseline_performance(&self, _features: &[f64]) -> f64 {
329 if let Some(last_performance) = self.performance_history.back() {
331 last_performance.executiontime
332 } else {
333 1.0 }
335 }
336
337 pub fn get_statistics(&self) -> NeuralProcessorStats {
339 let total_operations = self.adaptation_counter.load(Ordering::Relaxed);
340 let successful_adaptations = self
341 .performance_history
342 .iter()
343 .filter(|p| p.compute_reward(1.0) > 0.0)
344 .count();
345
346 let average_improvement = if !self.performance_history.is_empty() {
347 self.performance_history
348 .iter()
349 .map(|p| p.performance_score())
350 .sum::<f64>()
351 / self.performance_history.len() as f64
352 } else {
353 0.0
354 };
355
356 let most_effective_strategy = self.get_most_effective_strategy();
357 let rl_reward = if let Some(ref rl_agent) = self.rl_agent {
358 let dummy_state = vec![0.0; self.config.modeldim];
360 rl_agent.estimate_value(&dummy_state)
361 } else {
362 0.0
363 };
364
365 let pattern_memory_stats = self.pattern_memory.get_statistics();
366 let pattern_hit_rate = if total_operations > 0 {
367 pattern_memory_stats.stored_patterns as f64 / total_operations as f64
368 } else {
369 0.0
370 };
371
372 let neural_network_accuracy = if !self.performance_history.is_empty() {
378 successful_adaptations as f64 / self.performance_history.len() as f64
379 } else {
380 0.0
381 };
382
383 let transformer_attention_score = if self.attention_score_samples > 0 {
388 self.attention_score_sum / self.attention_score_samples as f64
389 } else {
390 0.0
391 };
392
393 NeuralProcessorStats {
394 total_operations,
395 successful_adaptations,
396 average_performance_improvement: average_improvement,
397 most_effective_strategy,
398 neural_network_accuracy,
399 rl_agent_reward: rl_reward,
400 pattern_memory_hit_rate: pattern_hit_rate,
401 transformer_attention_score,
402 }
403 }
404
405 fn get_most_effective_strategy(&self) -> OptimizationStrategy {
407 let mut strategy_scores = std::collections::HashMap::new();
408
409 for performance in &self.performance_history {
410 let score = performance.performance_score();
411 let entry = strategy_scores
412 .entry(performance.strategy_used)
413 .or_insert((0.0, 0));
414 entry.0 += score;
415 entry.1 += 1;
416 }
417
418 strategy_scores
419 .into_iter()
420 .max_by(|(_, (score1, count1)), (_, (score2, count2))| {
421 let avg1 = score1 / *count1 as f64;
422 let avg2 = score2 / *count2 as f64;
423 avg1.partial_cmp(&avg2).expect("Operation failed")
424 })
425 .map(|(strategy, _)| strategy)
426 .unwrap_or(OptimizationStrategy::AdaptiveHybrid)
427 }
428
429 pub fn update_target_networks(&mut self) {
431 if let Some(ref mut rl_agent) = self.rl_agent {
432 rl_agent.update_target_network();
433 }
434 }
435
436 pub fn save_state(&self) -> ProcessorState {
438 let neural_params = self.neural_network.get_parameters();
439 let pattern_stats = self.pattern_memory.get_statistics();
440
441 ProcessorState {
442 neural_network_params: neural_params,
443 total_operations: self.adaptation_counter.load(Ordering::Relaxed),
444 pattern_memory_size: pattern_stats.stored_patterns,
445 current_exploration_rate: self.current_exploration_rate,
446 }
447 }
448
449 pub fn load_state(&mut self, state: ProcessorState) {
451 self.neural_network
452 .set_parameters(&state.neural_network_params);
453 self.adaptation_counter
454 .store(state.total_operations, Ordering::Relaxed);
455 self.current_exploration_rate = state.current_exploration_rate;
456 }
457
458 pub fn adaptive_spmv<T>(
460 &mut self,
461 rows: &[usize],
462 cols: &[usize],
463 indptr: &[usize],
464 indices: &[usize],
465 data: &[T],
466 x: &[T],
467 y: &mut [T],
468 ) -> SparseResult<()>
469 where
470 T: Float
471 + SparseElement
472 + NumAssign
473 + SimdUnifiedOps
474 + std::fmt::Debug
475 + Copy
476 + Send
477 + Sync
478 + 'static,
479 {
480 let matrix_features = self.extract_matrix_features(rows, cols, data);
482
483 let context = OperationContext {
485 matrix_shape: (rows.len(), cols.len()),
486 nnz: data.len(),
487 operation_type: OperationType::MatVec,
488 performance_target: PerformanceTarget::Speed,
489 };
490
491 let strategy = self.optimize_operation::<T>(&matrix_features, &context)?;
493
494 let start_time = std::time::Instant::now();
496 self.execute_spmv_with_strategy(strategy, indptr, indices, data, x, y)?;
497 let execution_time = start_time.elapsed().as_secs_f64();
498
499 let num_workers = scirs2_core::parallel_ops::num_threads().max(1);
503 let (cache_efficiency, simd_utilization, parallel_efficiency, memory_bandwidth) =
504 Self::measure_execution_efficiency(indptr, indices, data, execution_time, num_workers);
505
506 let performance = PerformanceMetrics::new(
508 execution_time,
509 cache_efficiency,
510 simd_utilization,
511 parallel_efficiency,
512 memory_bandwidth,
513 strategy,
514 );
515
516 self.learn_from_performance(strategy, performance, &matrix_features, &context)?;
517
518 Ok(())
519 }
520
521 fn measure_execution_efficiency<T>(
532 indptr: &[usize],
533 indices: &[usize],
534 data: &[T],
535 execution_time: f64,
536 num_workers: usize,
537 ) -> (f64, f64, f64, f64) {
538 let num_rows = indptr.len().saturating_sub(1);
539 let nnz = data.len().min(indices.len());
540
541 let mut jump_sum = 0.0f64;
546 let mut jump_count = 0usize;
547 for row in 0..num_rows {
548 let start = indptr[row];
549 let end = indptr[row + 1].min(nnz);
550 if end > start + 1 {
551 for w in start..end - 1 {
552 jump_sum += indices[w + 1].abs_diff(indices[w]) as f64;
553 jump_count += 1;
554 }
555 }
556 }
557 let avg_jump = if jump_count > 0 {
558 jump_sum / jump_count as f64
559 } else {
560 0.0
561 };
562 let cache_efficiency = 1.0 / (1.0 + avg_jump / 8.0);
566
567 const SIMD_LANE_WIDTH: usize = 8;
571 let mut simd_capable_nnz = 0usize;
572 for row in 0..num_rows {
573 let start = indptr[row];
574 let end = indptr[row + 1].min(nnz).max(start);
575 let row_len = end - start;
576 if row_len >= SIMD_LANE_WIDTH {
577 simd_capable_nnz += row_len;
578 }
579 }
580 let simd_utilization = if nnz > 0 {
581 simd_capable_nnz as f64 / nnz as f64
582 } else {
583 0.0
584 };
585
586 let parallel_efficiency = if num_rows > 0 && num_workers > 1 {
589 let chunk = num_rows.div_ceil(num_workers);
590 let mut min_chunk_nnz = usize::MAX;
591 let mut max_chunk_nnz = 0usize;
592 let mut chunks_seen = 0usize;
593 for w in 0..num_workers {
594 let row_start = (w * chunk).min(num_rows);
595 let row_end = ((w + 1) * chunk).min(num_rows);
596 if row_start >= row_end {
597 continue;
598 }
599 let nnz_start = indptr[row_start];
600 let nnz_end = indptr[row_end].min(nnz).max(nnz_start);
601 let chunk_nnz = nnz_end - nnz_start;
602 min_chunk_nnz = min_chunk_nnz.min(chunk_nnz);
603 max_chunk_nnz = max_chunk_nnz.max(chunk_nnz);
604 chunks_seen += 1;
605 }
606 if chunks_seen > 1 && max_chunk_nnz > 0 {
607 min_chunk_nnz as f64 / max_chunk_nnz as f64
608 } else {
609 1.0
610 }
611 } else {
612 1.0
613 };
614
615 let element_size = std::mem::size_of::<T>() as f64;
620 let index_size = std::mem::size_of::<usize>() as f64;
621 let bytes_moved = nnz as f64 * (element_size + index_size) + num_rows as f64 * element_size;
622 const REFERENCE_BANDWIDTH_BYTES_PER_SEC: f64 = 20.0e9;
623 let memory_bandwidth = if execution_time > 0.0 {
624 (bytes_moved / execution_time / REFERENCE_BANDWIDTH_BYTES_PER_SEC).min(1.0)
625 } else {
626 0.0
627 };
628
629 (
630 cache_efficiency,
631 simd_utilization,
632 parallel_efficiency,
633 memory_bandwidth,
634 )
635 }
636
637 fn extract_matrix_features<T>(&self, rows: &[usize], cols: &[usize], data: &[T]) -> Vec<f64>
639 where
640 T: Float + std::fmt::Debug + Copy,
641 {
642 let mut features = Vec::new();
643
644 features.push(rows.len() as f64);
646 features.push(cols.len() as f64);
647 features.push(data.len() as f64);
648
649 if !rows.is_empty() {
651 let min_row = *rows.iter().min().unwrap_or(&0) as f64;
652 let max_row = *rows.iter().max().unwrap_or(&0) as f64;
653 features.push(min_row);
654 features.push(max_row);
655 features.push(max_row - min_row); } else {
657 features.extend(&[0.0, 0.0, 0.0]);
658 }
659
660 if !cols.is_empty() {
662 let min_col = *cols.iter().min().unwrap_or(&0) as f64;
663 let max_col = *cols.iter().max().unwrap_or(&0) as f64;
664 features.push(min_col);
665 features.push(max_col);
666 features.push(max_col - min_col); } else {
668 features.extend(&[0.0, 0.0, 0.0]);
669 }
670
671 if !data.is_empty() {
673 let data_f64: Vec<f64> = data.iter().map(|&x| x.to_f64().unwrap_or(0.0)).collect();
675 let sum: f64 = data_f64.iter().sum();
676 let mean = sum / data_f64.len() as f64;
677 let variance =
678 data_f64.iter().map(|&x| (x - mean).powi(2)).sum::<f64>() / data_f64.len() as f64;
679
680 features.push(mean);
681 features.push(variance.sqrt()); features.push(
683 *data_f64
684 .iter()
685 .min_by(|a, b| a.partial_cmp(b).expect("Operation failed"))
686 .unwrap_or(&0.0),
687 );
688 features.push(
689 *data_f64
690 .iter()
691 .max_by(|a, b| a.partial_cmp(b).expect("Operation failed"))
692 .unwrap_or(&0.0),
693 );
694 } else {
695 features.extend(&[0.0, 0.0, 0.0, 0.0]);
696 }
697
698 let target_size = 20;
700 features.resize(target_size, 0.0);
701 features
702 }
703
704 fn execute_spmv_with_strategy<T>(
706 &self,
707 strategy: OptimizationStrategy,
708 indptr: &[usize],
709 indices: &[usize],
710 data: &[T],
711 x: &[T],
712 y: &mut [T],
713 ) -> SparseResult<()>
714 where
715 T: Float
716 + SparseElement
717 + NumAssign
718 + SimdUnifiedOps
719 + std::fmt::Debug
720 + Copy
721 + Send
722 + Sync,
723 {
724 match strategy {
725 OptimizationStrategy::RowWiseCache => {
726 self.execute_rowwise_spmv(indptr, indices, data, x, y)
727 }
728 OptimizationStrategy::SIMDVectorized => {
729 self.execute_simd_spmv(indptr, indices, data, x, y)
730 }
731 OptimizationStrategy::ParallelWorkStealing => {
732 self.execute_parallel_spmv(indptr, indices, data, x, y)
733 }
734 _ => {
735 self.execute_basic_spmv(indptr, indices, data, x, y)
737 }
738 }
739 }
740
741 fn execute_basic_spmv<T>(
743 &self,
744 indptr: &[usize],
745 indices: &[usize],
746 data: &[T],
747 x: &[T],
748 y: &mut [T],
749 ) -> SparseResult<()>
750 where
751 T: Float + SparseElement + NumAssign + std::fmt::Debug + Copy,
752 {
753 for (i, y_val) in y.iter_mut().enumerate() {
754 *y_val = T::sparse_zero();
755 if i + 1 < indptr.len() {
756 for j in indptr[i]..indptr[i + 1] {
757 if j < indices.len() && j < data.len() {
758 let col = indices[j];
759 if col < x.len() {
760 *y_val += data[j] * x[col];
761 }
762 }
763 }
764 }
765 }
766 Ok(())
767 }
768
769 fn execute_rowwise_spmv<T>(
771 &self,
772 indptr: &[usize],
773 indices: &[usize],
774 data: &[T],
775 x: &[T],
776 y: &mut [T],
777 ) -> SparseResult<()>
778 where
779 T: Float + SparseElement + NumAssign + std::fmt::Debug + Copy,
780 {
781 self.execute_basic_spmv(indptr, indices, data, x, y)
783 }
784
785 fn execute_simd_spmv<T>(
787 &self,
788 indptr: &[usize],
789 indices: &[usize],
790 data: &[T],
791 x: &[T],
792 y: &mut [T],
793 ) -> SparseResult<()>
794 where
795 T: Float + SparseElement + NumAssign + SimdUnifiedOps + std::fmt::Debug + Copy,
796 {
797 for (i, y_val) in y.iter_mut().enumerate() {
799 *y_val = T::sparse_zero();
800 if i + 1 < indptr.len() {
801 let start = indptr[i];
802 let end = indptr[i + 1];
803 if end > start {
804 let row_data = &data[start..end];
805 let row_indices = &indices[start..end];
806
807 let mut sum = T::sparse_zero();
809 for (&data_val, &col_idx) in row_data.iter().zip(row_indices.iter()) {
810 if col_idx < x.len() {
811 sum += data_val * x[col_idx];
812 }
813 }
814 *y_val = sum;
815 }
816 }
817 }
818 Ok(())
819 }
820
821 fn execute_parallel_spmv<T>(
823 &self,
824 indptr: &[usize],
825 indices: &[usize],
826 data: &[T],
827 x: &[T],
828 y: &mut [T],
829 ) -> SparseResult<()>
830 where
831 T: Float
832 + SparseElement
833 + NumAssign
834 + SimdUnifiedOps
835 + std::fmt::Debug
836 + Copy
837 + Send
838 + Sync,
839 {
840 use scirs2_core::parallel_ops::*;
842
843 for i in 0..y.len() {
845 y[i] = T::sparse_zero();
846 if i + 1 < indptr.len() {
847 for j in indptr[i]..indptr[i + 1] {
848 if j < indices.len() && j < data.len() {
849 let col = indices[j];
850 if col < x.len() {
851 y[i] += data[j] * x[col];
852 }
853 }
854 }
855 }
856 }
857
858 Ok(())
859 }
860}
861
862#[derive(Debug, Clone)]
864pub struct OperationContext {
865 pub matrix_shape: (usize, usize),
866 pub nnz: usize,
867 pub operation_type: OperationType,
868 pub performance_target: PerformanceTarget,
869}
870
871#[derive(Debug, Clone, Copy)]
873pub enum OperationType {
874 MatVec,
875 MatMat,
876 Solve,
877 Factorization,
878}
879
880#[derive(Debug, Clone, Copy)]
882pub enum PerformanceTarget {
883 Speed,
884 Memory,
885 Accuracy,
886 Balanced,
887}
888
889#[derive(Debug, Clone)]
891pub struct ProcessorState {
892 pub neural_network_params: std::collections::HashMap<String, Vec<f64>>,
893 pub total_operations: usize,
894 pub pattern_memory_size: usize,
895 pub current_exploration_rate: f64,
896}
897
898#[cfg(test)]
899mod fabrication_regression_tests {
900 use super::*;
901
902 fn good_performance(execution_time: f64) -> PerformanceMetrics {
903 PerformanceMetrics::new(
904 execution_time,
905 0.9,
906 0.9,
907 0.9,
908 0.9,
909 OptimizationStrategy::SIMDVectorized,
910 )
911 }
912
913 fn bad_performance(execution_time: f64) -> PerformanceMetrics {
914 PerformanceMetrics::new(
915 execution_time,
916 0.0,
917 0.0,
918 0.0,
919 0.0,
920 OptimizationStrategy::RowWiseCache,
921 )
922 }
923
924 fn dummy_context() -> OperationContext {
925 OperationContext {
926 matrix_shape: (10, 10),
927 nnz: 20,
928 operation_type: OperationType::MatVec,
929 performance_target: PerformanceTarget::Speed,
930 }
931 }
932
933 #[test]
934 fn test_fresh_processor_reports_honest_zero_stats_not_fabricated_constants() {
935 let config = NeuralAdaptiveConfig::lightweight();
936 let processor = NeuralAdaptiveSparseProcessor::new(config);
937
938 let stats = processor.get_statistics();
939 assert_eq!(stats.neural_network_accuracy, 0.0);
942 assert_eq!(stats.transformer_attention_score, 0.0);
943 }
944
945 #[test]
946 fn test_neural_network_accuracy_reflects_real_mixed_outcomes() {
947 let config = NeuralAdaptiveConfig::lightweight();
948 let mut processor = NeuralAdaptiveSparseProcessor::new(config);
949 let context = dummy_context();
950 let features = vec![1.0, 2.0, 3.0];
951
952 for _ in 0..3 {
958 processor
959 .learn_from_performance(
960 OptimizationStrategy::SIMDVectorized,
961 good_performance(0.05),
962 &features,
963 &context,
964 )
965 .expect("Operation failed");
966 }
967 for _ in 0..2 {
968 processor
969 .learn_from_performance(
970 OptimizationStrategy::RowWiseCache,
971 bad_performance(5.0),
972 &features,
973 &context,
974 )
975 .expect("Operation failed");
976 }
977
978 let stats = processor.get_statistics();
979 assert_eq!(stats.successful_adaptations, 3);
980 assert!(
981 (stats.neural_network_accuracy - 0.6).abs() < 1e-9,
982 "expected 3/5 = 0.6 real accuracy, got {}",
983 stats.neural_network_accuracy
984 );
985 assert_ne!(stats.neural_network_accuracy, 0.85);
987 }
988
989 #[test]
990 fn test_transformer_attention_score_is_measured_from_real_operations() {
991 let config = NeuralAdaptiveConfig::lightweight().with_self_attention(true);
992 let mut processor = NeuralAdaptiveSparseProcessor::new(config);
993 let context = dummy_context();
994
995 let features_a = vec![1.0, 2.0, 3.0, 4.0];
997 let features_b = vec![-5.0, 0.5, 9.0, -2.0];
998
999 processor
1000 .optimize_operation::<f64>(&features_a, &context)
1001 .expect("Operation failed");
1002 processor
1003 .optimize_operation::<f64>(&features_b, &context)
1004 .expect("Operation failed");
1005
1006 let stats = processor.get_statistics();
1007 assert!(stats.transformer_attention_score > 0.0);
1011 assert!(stats.transformer_attention_score < 1.0);
1012 assert_ne!(stats.transformer_attention_score, 0.75);
1014 }
1015
1016 #[test]
1017 fn test_measure_execution_efficiency_cache_locality_varies_with_structure() {
1018 let indptr = vec![0, 4, 8, 12];
1021 let indices_good = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
1022 let data = vec![1.0f64; 12];
1023
1024 let indices_bad = vec![0, 500, 3, 900, 1, 700, 50, 999, 2, 600, 20, 888];
1026
1027 let (cache_good, _, _, _) = NeuralAdaptiveSparseProcessor::measure_execution_efficiency(
1028 &indptr,
1029 &indices_good,
1030 &data,
1031 0.01,
1032 1,
1033 );
1034 let (cache_bad, _, _, _) = NeuralAdaptiveSparseProcessor::measure_execution_efficiency(
1035 &indptr,
1036 &indices_bad,
1037 &data,
1038 0.01,
1039 1,
1040 );
1041
1042 assert!(
1043 cache_good > cache_bad,
1044 "contiguous access should score higher cache efficiency than scattered access: {cache_good} vs {cache_bad}"
1045 );
1046 assert_ne!(cache_good, 0.8);
1047 assert_ne!(cache_bad, 0.8);
1048 }
1049
1050 #[test]
1051 fn test_measure_execution_efficiency_simd_utilization_varies_with_row_length() {
1052 let indptr_long = vec![0, 10, 20];
1054 let indices_long: Vec<usize> = (0..20).collect();
1055 let data_long = vec![1.0f64; 20];
1056
1057 let indptr_short: Vec<usize> = (0..=20).collect();
1059 let indices_short: Vec<usize> = (0..20).collect();
1060 let data_short = vec![1.0f64; 20];
1061
1062 let (_, simd_long, _, _) = NeuralAdaptiveSparseProcessor::measure_execution_efficiency(
1063 &indptr_long,
1064 &indices_long,
1065 &data_long,
1066 0.01,
1067 1,
1068 );
1069 let (_, simd_short, _, _) = NeuralAdaptiveSparseProcessor::measure_execution_efficiency(
1070 &indptr_short,
1071 &indices_short,
1072 &data_short,
1073 0.01,
1074 1,
1075 );
1076
1077 assert_eq!(simd_long, 1.0);
1078 assert_eq!(simd_short, 0.0);
1079 assert!(simd_long > simd_short);
1080 assert_ne!(simd_long, 0.9);
1081 }
1082
1083 #[test]
1084 fn test_measure_execution_efficiency_memory_bandwidth_scales_with_time() {
1085 let indptr = vec![0, 4];
1086 let indices = vec![0, 1, 2, 3];
1087 let data = vec![1.0f64; 4];
1088
1089 let (_, _, _, bw_fast) = NeuralAdaptiveSparseProcessor::measure_execution_efficiency(
1090 &indptr, &indices, &data, 1e-9, 1,
1091 );
1092 let (_, _, _, bw_slow) = NeuralAdaptiveSparseProcessor::measure_execution_efficiency(
1093 &indptr, &indices, &data, 1.0, 1,
1094 );
1095
1096 assert!(
1097 bw_fast > bw_slow,
1098 "faster execution of the same data volume must score higher bandwidth utilization: {bw_fast} vs {bw_slow}"
1099 );
1100 assert_ne!(bw_slow, 0.85);
1101 }
1102
1103 #[test]
1104 fn test_measure_execution_efficiency_parallel_efficiency_reflects_load_balance() {
1105 let indptr_balanced = vec![0, 2, 4, 6, 8, 10, 12, 14, 16];
1107 let indices_balanced: Vec<usize> = (0..16).collect();
1108 let data_balanced = vec![1.0f64; 16];
1109
1110 let indptr_unbalanced = vec![0, 16, 32, 32, 32, 32, 32, 32, 32];
1113 let indices_unbalanced: Vec<usize> = (0..32).collect();
1114 let data_unbalanced = vec![1.0f64; 32];
1115
1116 let (_, _, par_balanced, _) = NeuralAdaptiveSparseProcessor::measure_execution_efficiency(
1117 &indptr_balanced,
1118 &indices_balanced,
1119 &data_balanced,
1120 0.01,
1121 4,
1122 );
1123 let (_, _, par_unbalanced, _) = NeuralAdaptiveSparseProcessor::measure_execution_efficiency(
1124 &indptr_unbalanced,
1125 &indices_unbalanced,
1126 &data_unbalanced,
1127 0.01,
1128 4,
1129 );
1130
1131 assert_eq!(par_balanced, 1.0);
1132 assert!(par_balanced > par_unbalanced);
1133 assert_ne!(par_unbalanced, 0.7);
1134 }
1135
1136 #[test]
1137 fn test_adaptive_spmv_records_non_placeholder_performance() {
1138 let config = NeuralAdaptiveConfig::lightweight();
1139 let mut processor = NeuralAdaptiveSparseProcessor::new(config);
1140
1141 let rows = vec![0usize; 2];
1143 let cols = vec![0usize; 2];
1144 let indptr = vec![0usize, 2, 4];
1145 let indices = vec![0usize, 1, 0, 1];
1146 let data = vec![1.0f64, 2.0, 3.0, 4.0];
1147 let x = vec![1.0f64, 1.0];
1148 let mut y = vec![0.0f64; 2];
1149
1150 processor
1151 .adaptive_spmv(&rows, &cols, &indptr, &indices, &data, &x, &mut y)
1152 .expect("Operation failed");
1153
1154 let recorded = processor
1155 .performance_history
1156 .back()
1157 .expect("expected a recorded performance sample");
1158 assert!((0.0..=1.0).contains(&recorded.cache_efficiency));
1161 assert!((0.0..=1.0).contains(&recorded.simd_utilization));
1162 assert!((0.0..=1.0).contains(&recorded.parallel_efficiency));
1163 assert!((0.0..=1.0).contains(&recorded.memory_bandwidth));
1164 }
1165}