Skip to main content

scirs2_sparse/neural_adaptive_sparse/
processor.rs

1//! Main neural-adaptive sparse matrix processor
2//!
3//! This module contains the main processor that coordinates all neural network,
4//! reinforcement learning, and pattern memory components for adaptive optimization.
5
6use 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
17/// Neural-adaptive sparse matrix processor
18pub 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    /// Reinforcement learning agent
26    rl_agent: Option<RLAgent>,
27    /// Transformer model for attention-based optimization
28    transformer: Option<TransformerModel>,
29    /// Experience replay buffer for RL
30    experience_buffer: ExperienceBuffer,
31    /// Current exploration rate (decays over time)
32    current_exploration_rate: f64,
33    /// Running sum of real attention-weight measurements observed while
34    /// encoding operation states through the transformer (used to report a
35    /// genuine `transformer_attention_score`, never a fabricated constant).
36    attention_score_sum: f64,
37    /// Number of attention-score samples accumulated in `attention_score_sum`.
38    attention_score_samples: usize,
39}
40
41/// Statistics for neural processor performance
42#[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    /// Create a new neural-adaptive sparse matrix processor
56    pub fn new(config: NeuralAdaptiveConfig) -> Self {
57        // Validate configuration
58        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, // Number of optimization strategies
67            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        // Initialize RL agent if enabled
84        let rl_agent = if config.reinforcement_learning {
85            Some(RLAgent::new(
86                config.modeldim,
87                9, // Number of actions (optimization strategies)
88                config.rl_algorithm,
89                config.learningrate,
90                config.exploration_rate,
91            ))
92        } else {
93            None
94        };
95
96        // Initialize transformer if self-attention is enabled
97        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, // Max sequence length
104            ))
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    /// Process sparse matrix operation with adaptive optimization
128    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        // Extract matrix fingerprint
145        let fingerprint = self.extract_matrix_fingerprint(matrix_features, operation_context);
146
147        // Try pattern memory first
148        if let Some(strategy) = self.pattern_memory.get_strategy(&fingerprint) {
149            return Ok(strategy);
150        }
151
152        // Use neural network and RL agent for optimization
153        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        // Store the experience for later learning
162        let experience = Experience {
163            state: state.clone(),
164            action: strategy,
165            reward: 0.0,       // Will be updated after operation execution
166            next_state: state, // Will be updated with next state
167            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    /// Learn from operation performance
180    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        // Compute reward
188        let baseline_time = self.estimate_baseline_performance(matrix_features);
189        let reward = performance.compute_reward(baseline_time);
190
191        // Update experience buffer with reward
192        if let Some(mut experience) = self.experience_buffer.buffer.back_mut() {
193            experience.reward = reward;
194        }
195
196        // Store successful patterns
197        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        // Train RL agent
203        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        // Update performance history
212        self.performance_history.push_back(performance);
213        if self.performance_history.len() > 1000 {
214            self.performance_history.pop_front();
215        }
216
217        // Increment adaptation counter
218        self.adaptation_counter.fetch_add(1, Ordering::Relaxed);
219
220        // Decay exploration rate
221        if let Some(ref mut rl_agent) = self.rl_agent {
222            rl_agent.decay_epsilon(0.995);
223        }
224
225        Ok(())
226    }
227
228    /// Extract matrix fingerprint from features
229    fn extract_matrix_fingerprint(
230        &self,
231        features: &[f64],
232        context: &OperationContext,
233    ) -> MatrixFingerprint {
234        // Extract basic properties from features
235        let rows = context.matrix_shape.0;
236        let cols = context.matrix_shape.1;
237        let nnz = context.nnz;
238
239        // Compute a simple hash of the sparsity pattern
240        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        // Analyze distributions (simplified)
249        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    /// Encode state for neural network/RL agent
263    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        // Matrix properties
271        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); // Sparsity
275
276        // Operation type
277        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        // Matrix features (truncated/padded to fixed size)
285        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        // Use transformer for feature encoding if available. The attention
295        // score returned here is a genuine, input-dependent measurement
296        // (never a fabricated constant) -- it is accumulated so
297        // `get_statistics()` can report a real running average.
298        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    /// Select action using neural network
314    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    /// Estimate baseline performance for reward computation
328    fn estimate_baseline_performance(&self, _features: &[f64]) -> f64 {
329        // Simple baseline estimation
330        if let Some(last_performance) = self.performance_history.back() {
331            last_performance.executiontime
332        } else {
333            1.0 // Default baseline
334        }
335    }
336
337    /// Get processor statistics
338    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            // Estimate current RL performance
359            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        // Neural-network "accuracy": the real, measured fraction of recorded
373        // adaptation outcomes that actually improved on the naive baseline
374        // (reward > 0), derived from genuine execution history rather than a
375        // fixed constant. Honestly reports 0.0 (no fabricated confidence)
376        // when nothing has been measured yet.
377        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        // Transformer attention score: running average of the *actual*
384        // attention weights the transformer produced while encoding real
385        // operation states (0.0, honestly, if the transformer is disabled or
386        // has not yet processed any state -- never a fabricated constant).
387        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    /// Get most effective optimization strategy
406    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    /// Update target networks (for DQN)
430    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    /// Save processor state
437    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    /// Load processor state
450    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    /// Adaptive sparse matrix-vector multiplication
459    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        // Extract matrix features for optimization decision
481        let matrix_features = self.extract_matrix_features(rows, cols, data);
482
483        // Create operation context
484        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        // Get optimization strategy
492        let strategy = self.optimize_operation::<T>(&matrix_features, &context)?;
493
494        // Execute the operation using the selected strategy
495        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        // Measure real efficiency signals from the actual CSR structure that
500        // was processed and the wall-clock time that was actually observed
501        // -- no fabricated constants.
502        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        // Learn from performance
507        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    /// Derive real, matrix-dependent execution-efficiency signals from the
522    /// CSR structure that was actually processed and the wall-clock time
523    /// that was actually observed executing it. These replace the previous
524    /// fixed placeholder constants (0.8/0.9/0.7/0.85, regardless of input) --
525    /// every value below varies with the real input matrix and timing.
526    ///
527    /// `num_workers` is the number of parallel workers to model the load
528    /// balance against; production code passes the real environment thread
529    /// count (`scirs2_core::parallel_ops::num_threads()`), while tests can
530    /// inject a fixed value for deterministic assertions.
531    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        // Cache efficiency: derived from the actual spatial locality of the
542        // column indices touched by consecutive nonzeros within a row --
543        // small jumps between consecutive `x[col]` accesses indicate good
544        // locality, large jumps indicate poor locality.
545        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        // Normalize by a representative cache-line width (8 f64 elements):
563        // a jump of zero yields perfect (1.0) locality, larger jumps
564        // asymptotically approach 0.
565        let cache_efficiency = 1.0 / (1.0 + avg_jump / 8.0);
566
567        // SIMD utilization: real fraction of nonzeros that live in rows long
568        // enough to fill at least one SIMD lane (assumed width 8, a typical
569        // AVX-class lane count for f64).
570        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        // Parallel efficiency: load-balance ratio (min/max nonzeros per
587        // chunk) when rows are partitioned contiguously across `num_workers`.
588        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        // Memory bandwidth: real bytes moved (row data reads + gathered `x`
616        // reads + `y` writes) divided by the *actually observed* wall-clock
617        // execution time, normalized against a representative DDR bandwidth
618        // ceiling so the reported value stays in a comparable [0, 1] range.
619        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    /// Extract matrix features for neural network
638    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        // Basic statistics
645        features.push(rows.len() as f64);
646        features.push(cols.len() as f64);
647        features.push(data.len() as f64);
648
649        // Row statistics
650        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); // row span
656        } else {
657            features.extend(&[0.0, 0.0, 0.0]);
658        }
659
660        // Column statistics
661        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); // column span
667        } else {
668            features.extend(&[0.0, 0.0, 0.0]);
669        }
670
671        // Data statistics (simplified)
672        if !data.is_empty() {
673            // Convert to f64 for statistics
674            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()); // standard deviation
682            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        // Pad/truncate to fixed size for neural network
699        let target_size = 20;
700        features.resize(target_size, 0.0);
701        features
702    }
703
704    /// Execute SpMV with specific strategy
705    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                // Default implementation for other strategies
736                self.execute_basic_spmv(indptr, indices, data, x, y)
737            }
738        }
739    }
740
741    /// Basic CSR SpMV implementation
742    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    /// Row-wise cache-optimized SpMV
770    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        // Same as basic for now - could be optimized with better cache blocking
782        self.execute_basic_spmv(indptr, indices, data, x, y)
783    }
784
785    /// SIMD-vectorized SpMV
786    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        // Use SIMD operations from scirs2-core
798        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                    // SIMD dot product
808                    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    /// Parallel work-stealing SpMV
822    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 parallel operations from scirs2-core
841        use scirs2_core::parallel_ops::*;
842
843        // Sequential implementation for now
844        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/// Context for matrix operations
863#[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/// Types of matrix operations
872#[derive(Debug, Clone, Copy)]
873pub enum OperationType {
874    MatVec,
875    MatMat,
876    Solve,
877    Factorization,
878}
879
880/// Performance optimization targets
881#[derive(Debug, Clone, Copy)]
882pub enum PerformanceTarget {
883    Speed,
884    Memory,
885    Accuracy,
886    Balanced,
887}
888
889/// Serializable processor state
890#[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        // Previously these were UNCONDITIONALLY 0.85 / 0.75 regardless of
940        // whether any operation had ever been performed.
941        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        // 3 genuinely good outcomes (fast + efficient => positive reward
953        // against the fixed baseline used internally by get_statistics) and
954        // 2 genuinely bad outcomes (slow + inefficient => negative reward).
955        // This is non-constant, varied data -- not the all-ones pattern
956        // that can't distinguish real computation from a stub.
957        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        // Must not be the old fabricated constant.
986        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        // Distinct, non-constant feature vectors across calls.
996        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        // A logistic-sigmoid attention weight is always strictly in (0, 1);
1008        // it must also have actually moved away from the "no measurement
1009        // yet" zero default now that real operations were processed.
1010        assert!(stats.transformer_attention_score > 0.0);
1011        assert!(stats.transformer_attention_score < 1.0);
1012        // Must not be the old fabricated constant.
1013        assert_ne!(stats.transformer_attention_score, 0.75);
1014    }
1015
1016    #[test]
1017    fn test_measure_execution_efficiency_cache_locality_varies_with_structure() {
1018        // Row-contiguous (good locality): each row's nonzeros have
1019        // consecutive column indices.
1020        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        // Scattered (poor locality): column indices jump wildly within a row.
1025        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        // Long rows (length 10 each): fully SIMD-capable.
1053        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        // Short rows (length 1 each): cannot fill a SIMD lane.
1058        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        // 8 rows, each with 2 nonzeros: perfectly balanced across 4 workers.
1106        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        // 8 rows, but every nonzero is concentrated in the first 2 rows:
1111        // terrible balance across 4 workers.
1112        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        // A small but non-trivial CSR matrix (non-constant data).
1142        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        // None of these should be exactly the old hardcoded placeholders,
1159        // and all must be valid fractions.
1160        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}