Skip to main content

qualia_core_db/specialized_libs/linear_algebra/
optimization.rs

1use std::collections::HashMap;
2
3use super::computation::*;
4use super::core_types::*;
5use super::storage::*;
6
7/// Optimization engine for matrix operations
8pub struct OptimizationEngine {
9    pub optimizer: MatrixOptimizer,
10    pub analyzer: MatrixAnalyzer,
11    pub transformer: MatrixTransformer,
12}
13
14/// Matrix optimizer
15pub struct MatrixOptimizer {
16    pub optimization_strategies: Vec<OptimizationStrategy>,
17    pub optimization_history: Vec<OptimizationRecord>,
18}
19
20/// Optimization strategies
21#[derive(Debug, Clone, PartialEq)]
22pub enum OptimizationStrategy {
23    CacheOptimization,
24    MemoryLayoutOptimization,
25    AlgorithmSelection,
26    Parallelization,
27    Vectorization,
28    Fusion,
29}
30
31/// Optimization record
32#[derive(Debug, Clone)]
33pub struct OptimizationRecord {
34    pub timestamp: u64,
35    pub matrix_id: String,
36    pub strategy: OptimizationStrategy,
37    pub performance_improvement: f64,
38    pub memory_reduction: f64,
39}
40
41/// Matrix analyzer
42pub struct MatrixAnalyzer {
43    pub analysis_algorithms: Vec<AnalysisAlgorithm>,
44    pub pattern_recognition: PatternRecognition,
45}
46
47/// Analysis algorithms
48#[derive(Debug, Clone, PartialEq)]
49pub enum AnalysisAlgorithm {
50    SparsityAnalysis,
51    StructureAnalysis,
52    AccessPatternAnalysis,
53    PerformanceAnalysis,
54}
55
56/// Pattern recognition
57pub struct PatternRecognition {
58    pub recognized_patterns: Vec<MatrixPattern>,
59    pub pattern_library: PatternLibrary,
60}
61
62/// Matrix patterns
63#[derive(Debug, Clone, PartialEq, Eq, Hash)]
64pub enum MatrixPattern {
65    Diagonal,
66    Triangular,
67    Banded,
68    Symmetric,
69    PositiveDefinite,
70    Orthogonal,
71    Sparse,
72    Dense,
73    BlockDiagonal,
74    Toeplitz,
75    Hankel,
76    Circulant,
77}
78
79/// Pattern library
80pub struct PatternLibrary {
81    pub patterns: HashMap<String, MatrixPattern>,
82    pub optimization_hints: HashMap<MatrixPattern, OptimizationHint>,
83}
84
85/// Optimization hints
86#[derive(Debug, Clone)]
87pub struct OptimizationHint {
88    pub preferred_algorithm: String,
89    pub memory_layout: StorageFormat,
90    pub parallelization_strategy: String,
91    pub vectorization_hints: Vec<String>,
92    /// Estimated speedup factor (e.g. 2.0 = 2x faster than naive)
93    pub estimated_speedup: f64,
94}
95
96/// Matrix transformer
97pub struct MatrixTransformer {
98    pub transformation_rules: Vec<TransformationRule>,
99    pub transformation_history: Vec<TransformationRecord>,
100}
101
102/// Transformation rules
103#[derive(Debug, Clone, PartialEq)]
104pub enum TransformationRule {
105    RowColumnSwap,
106    BlockReordering,
107    DataTypeConversion,
108    CompressionDecompression,
109    LayoutConversion,
110}
111
112/// Transformation record
113#[derive(Debug, Clone)]
114pub struct TransformationRecord {
115    pub timestamp: u64,
116    pub matrix_id: String,
117    pub transformation: TransformationRule,
118    pub performance_impact: f64,
119}
120
121/// Target memory layout for a `MatrixTransformer` layout conversion.
122///
123/// The `Matrix.data` buffer is always a flat `Vec<f64>`; the layout describes
124/// how logical element `(i, j)` is addressed within that buffer. Converting
125/// between layouts reorganises the buffer in place and updates
126/// `Matrix.storage_format` accordingly.
127#[derive(Debug, Clone, PartialEq)]
128pub enum MatrixLayout {
129    /// Row-major storage: element `(i, j)` lives at `data[i * cols + j]`.
130    /// This is the canonical layout used throughout the linear-algebra
131    /// library and the default for sequential row-wise access.
132    RowMajor,
133    /// Column-major storage: element `(i, j)` lives at `data[j * rows + i]`.
134    /// Preferred for column-heavy (strided) access patterns.
135    ColMajor,
136    /// Cache-friendly blocked (tiled) storage. The matrix is partitioned into
137    /// `block_size` x `block_size` sub-blocks. Blocks themselves are laid out
138    /// in row-major block order (block row, then block column); within each
139    /// block the element order follows `inner` (typically `RowMajor`).
140    ///
141    /// Edge blocks that fall outside the matrix dimensions contain only the
142    /// valid elements, so the total buffer length remains `rows * cols`.
143    Blocked(Box<MatrixLayout>, usize),
144    /// SIMD-packed storage: each row is zero-padded to a multiple of
145    /// [`SIMD_WIDTH`] so that a full SIMD vector load never crosses a row
146    /// boundary. Element `(i, j)` lives at `data[i * stride + j]` where
147    /// `stride = ceil(cols / SIMD_WIDTH) * SIMD_WIDTH`; padding slots are `0.0`.
148    Packed,
149}
150
151/// SIMD vector width (in `f64` elements) used by the [`MatrixLayout::Packed`]
152/// layout. The value `4` corresponds to a 256-bit AVX2 register holding four
153/// double-precision lanes.
154const SIMD_WIDTH: usize = 4;
155
156impl OptimizationEngine {
157    pub fn new() -> Self {
158        Self {
159            optimizer: MatrixOptimizer::new(),
160            analyzer: MatrixAnalyzer::new(),
161            transformer: MatrixTransformer::new(),
162        }
163    }
164
165    pub fn initialize(&mut self) -> Result<(), LinearAlgebraError> {
166        self.optimizer.initialize()?;
167        self.analyzer.initialize()?;
168        self.transformer.initialize()?;
169        Ok(())
170    }
171
172    pub fn optimize_multiplication(
173        &mut self,
174        left: &Matrix,
175        right: &Matrix,
176    ) -> Result<OptimizedMultiplication, LinearAlgebraError> {
177        // Analyze matrices
178        let _left_analysis = self.analyzer.analyze_matrix(left)?;
179        let _right_analysis = self.analyzer.analyze_matrix(right)?;
180
181        // Create optimized operation
182        let optimized = OptimizedMultiplication {
183            left: left.clone(),
184            right: right.clone(),
185            optimization_strategy: OptimizationStrategy::Vectorization,
186            expected_performance_gain: 2.0,
187        };
188
189        Ok(optimized)
190    }
191}
192
193impl MatrixOptimizer {
194    pub fn new() -> Self {
195        Self {
196            optimization_strategies: vec![
197                OptimizationStrategy::Vectorization,
198                OptimizationStrategy::CacheOptimization,
199                OptimizationStrategy::Parallelization,
200            ],
201            optimization_history: Vec::new(),
202        }
203    }
204
205    pub fn initialize(&mut self) -> Result<(), LinearAlgebraError> {
206        Ok(())
207    }
208}
209
210impl MatrixAnalyzer {
211    pub fn new() -> Self {
212        Self {
213            analysis_algorithms: vec![
214                AnalysisAlgorithm::StructureAnalysis,
215                AnalysisAlgorithm::SparsityAnalysis,
216            ],
217            pattern_recognition: PatternRecognition::new(),
218        }
219    }
220
221    pub fn initialize(&mut self) -> Result<(), LinearAlgebraError> {
222        self.pattern_recognition.initialize()?;
223        Ok(())
224    }
225
226    /// Analyze a matrix: detect all structural patterns, compute sparsity,
227    /// and determine recommended algorithms from optimization hints.
228    pub fn analyze_matrix(
229        &mut self,
230        matrix: &Matrix,
231    ) -> Result<MatrixAnalysis, LinearAlgebraError> {
232        let detected = self.detect_structure(matrix);
233        let sparsity = self.calculate_sparsity(matrix);
234
235        // Determine recommended algorithms from optimization hints
236        let mut recommended_algorithms = Vec::new();
237        let mut hint_strings = Vec::new();
238        for pattern in &detected {
239            if let Some(hint) = self
240                .pattern_recognition
241                .pattern_library
242                .get_optimization_hint(pattern)
243            {
244                recommended_algorithms.push(hint.preferred_algorithm.clone());
245                hint_strings.push(format!(
246                    "{:?}: {} (speedup ~{:.1}x)",
247                    pattern, hint.preferred_algorithm, hint.estimated_speedup
248                ));
249            }
250        }
251
252        // Pick the primary structure: prefer the most specific pattern
253        let structure = detected.first().cloned().unwrap_or(MatrixPattern::Dense);
254
255        Ok(MatrixAnalysis {
256            matrix_id: matrix.matrix_id.clone(),
257            sparsity,
258            structure,
259            detected_patterns: detected,
260            access_pattern: AccessPattern::Sequential,
261            optimization_hints: hint_strings,
262            recommended_algorithms,
263        })
264    }
265
266    fn calculate_sparsity(&self, matrix: &Matrix) -> f64 {
267        let non_zero = matrix.data.iter().filter(|&&x| x.abs() > 1e-10).count();
268        1.0 - (non_zero as f64 / matrix.data.len().max(1) as f64)
269    }
270
271    /// Detect ALL applicable structural patterns in the matrix.
272    /// Uses a tolerance of 1e-10 for floating-point comparisons.
273    pub fn detect_structure(&self, matrix: &Matrix) -> Vec<MatrixPattern> {
274        const TOL: f64 = 1e-10;
275        let rows = matrix.rows;
276        let cols = matrix.cols;
277        let data = &matrix.data;
278        let mut patterns = Vec::new();
279
280        // Helper: get element (i, j)
281        let at = |i: usize, j: usize| -> f64 { data[i * cols + j] };
282
283        // --- Sparse: fraction of non-zero elements < 0.3 ---
284        let non_zero_count = data.iter().filter(|&&x| x.abs() > TOL).count();
285        let total = data.len();
286        let non_zero_frac = if total > 0 {
287            non_zero_count as f64 / total as f64
288        } else {
289            0.0
290        };
291        let is_sparse = non_zero_frac < 0.3;
292        if is_sparse {
293            patterns.push(MatrixPattern::Sparse);
294        }
295
296        // Only square matrices can be diagonal, triangular, symmetric, etc.
297        let is_square = rows == cols && rows > 0;
298
299        // --- Diagonal: all off-diagonal elements are ~0 ---
300        if is_square {
301            let mut is_diagonal = true;
302            for i in 0..rows {
303                for j in 0..cols {
304                    if i != j && at(i, j).abs() > TOL {
305                        is_diagonal = false;
306                        break;
307                    }
308                }
309                if !is_diagonal {
310                    break;
311                }
312            }
313            if is_diagonal {
314                patterns.push(MatrixPattern::Diagonal);
315            }
316        }
317
318        // --- Triangular (Upper/Lower) ---
319        if is_square {
320            // Upper triangular: all elements below diagonal are ~0
321            let mut is_upper = true;
322            for i in 1..rows {
323                for j in 0..i {
324                    if at(i, j).abs() > TOL {
325                        is_upper = false;
326                        break;
327                    }
328                }
329                if !is_upper {
330                    break;
331                }
332            }
333            // Lower triangular: all elements above diagonal are ~0
334            let mut is_lower = true;
335            for i in 0..rows {
336                for j in (i + 1)..cols {
337                    if at(i, j).abs() > TOL {
338                        is_lower = false;
339                        break;
340                    }
341                }
342                if !is_lower {
343                    break;
344                }
345            }
346            if is_upper || is_lower {
347                patterns.push(MatrixPattern::Triangular);
348            }
349        }
350
351        // --- Symmetric: matrix == transpose (within tolerance) ---
352        if is_square {
353            let mut is_symmetric = true;
354            for i in 0..rows {
355                for j in (i + 1)..cols {
356                    if (at(i, j) - at(j, i)).abs() > TOL {
357                        is_symmetric = false;
358                        break;
359                    }
360                }
361                if !is_symmetric {
362                    break;
363                }
364            }
365            if is_symmetric {
366                patterns.push(MatrixPattern::Symmetric);
367
368                // --- PositiveDefinite: symmetric + all eigenvalues > 0 ---
369                // Use Sylvester's criterion: all leading principal minors > 0
370                if Self::is_positive_definite(rows, data, TOL) {
371                    patterns.push(MatrixPattern::PositiveDefinite);
372                }
373            }
374        }
375
376        // --- Banded: non-zero only within a band around diagonal ---
377        if rows > 0 && cols > 0 {
378            let max_dim = rows.max(cols);
379            // Determine the bandwidth
380            let mut bandwidth = 0usize;
381            for i in 0..rows {
382                for j in 0..cols {
383                    if at(i, j).abs() > TOL {
384                        let dist = if i >= j { i - j } else { j - i };
385                        if dist > bandwidth {
386                            bandwidth = dist;
387                        }
388                    }
389                }
390            }
391            // Banded if bandwidth is small relative to matrix dimension
392            // and bandwidth > 0 (not purely diagonal)
393            if bandwidth > 0 && (bandwidth as f64) < (max_dim as f64) / 3.0 {
394                patterns.push(MatrixPattern::Banded);
395            }
396        }
397
398        // --- BlockDiagonal: non-zero blocks along diagonal ---
399        if is_square && rows >= 4 {
400            if Self::is_block_diagonal(rows, data, TOL) {
401                patterns.push(MatrixPattern::BlockDiagonal);
402            }
403        }
404
405        // --- Toeplitz: each diagonal has constant value ---
406        if rows > 1 && cols > 1 {
407            let mut is_toeplitz = true;
408            // Check each diagonal
409            for d in -(rows as isize - 1)..(cols as isize) {
410                // Get the first value on this diagonal
411                let first = if d >= 0 {
412                    at(0, d as usize)
413                } else {
414                    at((-d) as usize, 0)
415                };
416                // Check all elements on this diagonal
417                let start_i = if d >= 0 { 0 } else { (-d) as usize };
418                let start_j = if d >= 0 { d as usize } else { 0 };
419                let mut i = start_i;
420                let mut j = start_j;
421                while i < rows && j < cols {
422                    if (at(i, j) - first).abs() > TOL {
423                        is_toeplitz = false;
424                        break;
425                    }
426                    i += 1;
427                    j += 1;
428                }
429                if !is_toeplitz {
430                    break;
431                }
432            }
433            if is_toeplitz {
434                patterns.push(MatrixPattern::Toeplitz);
435            }
436        }
437
438        // --- Orthogonal: A * A^T ≈ I ---
439        if is_square && rows > 0 {
440            if Self::is_orthogonal(rows, data, TOL) {
441                patterns.push(MatrixPattern::Orthogonal);
442            }
443        }
444
445        // --- Circulant: each row is a cyclic shift of the previous ---
446        if is_square && rows > 1 {
447            let mut is_circulant = true;
448            for i in 1..rows {
449                // Row i should be row (i-1) shifted right by 1 (cyclically)
450                for j in 0..cols {
451                    let expected = at(i - 1, if j == 0 { cols - 1 } else { j - 1 });
452                    if (at(i, j) - expected).abs() > TOL {
453                        is_circulant = false;
454                        break;
455                    }
456                }
457                if !is_circulant {
458                    break;
459                }
460            }
461            if is_circulant {
462                patterns.push(MatrixPattern::Circulant);
463            }
464        }
465
466        // --- Hankel: constant along anti-diagonals ---
467        if rows > 1 && cols > 1 {
468            let mut is_hankel = true;
469            // Anti-diagonal d ranges from 0 to (rows-1)+(cols-1)
470            // Element (i, j) is on anti-diagonal i + j
471            for d in 0..(rows + cols - 1) {
472                // Get first element on this anti-diagonal
473                let mut first_val: Option<f64> = None;
474                for i in 0..rows {
475                    let j = d as isize - i as isize;
476                    if j >= 0 && (j as usize) < cols {
477                        let v = at(i, j as usize);
478                        if first_val.is_none() {
479                            first_val = Some(v);
480                        } else if (v - first_val.unwrap()).abs() > TOL {
481                            is_hankel = false;
482                            break;
483                        }
484                    }
485                }
486                if !is_hankel {
487                    break;
488                }
489            }
490            if is_hankel {
491                patterns.push(MatrixPattern::Hankel);
492            }
493        }
494
495        // Always include Dense if no other pattern was detected
496        if patterns.is_empty() {
497            patterns.push(MatrixPattern::Dense);
498        }
499
500        patterns
501    }
502
503    /// Check if a square matrix is positive definite using Sylvester's criterion:
504    /// all leading principal minors must be positive.
505    fn is_positive_definite(n: usize, data: &[f64], tol: f64) -> bool {
506        for k in 1..=n {
507            // Compute the determinant of the k×k leading principal submatrix
508            let mut sub = vec![0.0; k * k];
509            for i in 0..k {
510                for j in 0..k {
511                    sub[i * k + j] = data[i * n + j];
512                }
513            }
514            // Compute determinant via LU-like recursive expansion for small k
515            let det = Self::determinant(k, &sub);
516            if det <= tol {
517                return false;
518            }
519        }
520        true
521    }
522
523    /// Compute the determinant of an n×n matrix via LU decomposition
524    fn determinant(n: usize, data: &[f64]) -> f64 {
525        if n == 0 {
526            return 1.0;
527        }
528        if n == 1 {
529            return data[0];
530        }
531        // LU decomposition with partial pivoting
532        let mut a = data.to_vec();
533        let mut sign = 1.0_f64;
534        for i in 0..n {
535            // Find pivot
536            let mut max_row = i;
537            let mut max_val = a[i * n + i].abs();
538            for k in (i + 1)..n {
539                if a[k * n + i].abs() > max_val {
540                    max_val = a[k * n + i].abs();
541                    max_row = k;
542                }
543            }
544            if max_val < 1e-15 {
545                return 0.0; // singular
546            }
547            if max_row != i {
548                for j in 0..n {
549                    let tmp = a[i * n + j];
550                    a[i * n + j] = a[max_row * n + j];
551                    a[max_row * n + j] = tmp;
552                }
553                sign = -sign;
554            }
555            // Eliminate
556            for k in (i + 1)..n {
557                let factor = a[k * n + i] / a[i * n + i];
558                for j in i..n {
559                    a[k * n + j] -= factor * a[i * n + j];
560                }
561            }
562        }
563        let mut det = sign;
564        for i in 0..n {
565            det *= a[i * n + i];
566        }
567        det
568    }
569
570    /// Check if a square matrix is block diagonal (non-zero blocks along the diagonal)
571    fn is_block_diagonal(n: usize, data: &[f64], tol: f64) -> bool {
572        // Heuristic: check if there's a consistent block size where off-block
573        // elements are zero. Try block sizes 2, 4, etc.
574        let at = |i: usize, j: usize| -> f64 { data[i * n + j] };
575        for block_size in [2, 4, 8].iter() {
576            if *block_size >= n {
577                continue;
578            }
579            if n % block_size != 0 {
580                continue;
581            }
582            let mut is_block_diag = true;
583            'outer: for bi in 0..(n / block_size) {
584                for bj in 0..(n / block_size) {
585                    if bi == bj {
586                        continue; // diagonal block
587                    }
588                    // Check off-diagonal block is all zeros
589                    for i in 0..*block_size {
590                        for j in 0..*block_size {
591                            if at(bi * block_size + i, bj * block_size + j).abs() > tol {
592                                is_block_diag = false;
593                                break 'outer;
594                            }
595                        }
596                    }
597                }
598            }
599            if is_block_diag {
600                return true;
601            }
602        }
603        false
604    }
605
606    /// Check if a square matrix is orthogonal: A * A^T ≈ I
607    fn is_orthogonal(n: usize, data: &[f64], tol: f64) -> bool {
608        let at = |i: usize, j: usize| -> f64 { data[i * n + j] };
609        // Compute A * A^T and check if it's approximately I
610        for i in 0..n {
611            for j in 0..n {
612                let mut dot = 0.0;
613                for k in 0..n {
614                    dot += at(i, k) * at(j, k);
615                }
616                let expected = if i == j { 1.0 } else { 0.0 };
617                if (dot - expected).abs() > tol * 10.0 {
618                    return false;
619                }
620            }
621        }
622        true
623    }
624}
625
626impl PatternRecognition {
627    pub fn new() -> Self {
628        Self {
629            recognized_patterns: Vec::new(),
630            pattern_library: PatternLibrary::new(),
631        }
632    }
633
634    pub fn initialize(&mut self) -> Result<(), LinearAlgebraError> {
635        self.pattern_library.initialize()?;
636        Ok(())
637    }
638}
639
640impl PatternLibrary {
641    pub fn new() -> Self {
642        Self {
643            patterns: HashMap::new(),
644            optimization_hints: HashMap::new(),
645        }
646    }
647
648    pub fn initialize(&mut self) -> Result<(), LinearAlgebraError> {
649        // Populate the patterns HashMap with names for each MatrixPattern variant
650        self.patterns
651            .insert("diagonal".to_string(), MatrixPattern::Diagonal);
652        self.patterns
653            .insert("triangular".to_string(), MatrixPattern::Triangular);
654        self.patterns
655            .insert("banded".to_string(), MatrixPattern::Banded);
656        self.patterns
657            .insert("symmetric".to_string(), MatrixPattern::Symmetric);
658        self.patterns.insert(
659            "positive_definite".to_string(),
660            MatrixPattern::PositiveDefinite,
661        );
662        self.patterns
663            .insert("orthogonal".to_string(), MatrixPattern::Orthogonal);
664        self.patterns
665            .insert("sparse".to_string(), MatrixPattern::Sparse);
666        self.patterns
667            .insert("dense".to_string(), MatrixPattern::Dense);
668        self.patterns
669            .insert("block_diagonal".to_string(), MatrixPattern::BlockDiagonal);
670        self.patterns
671            .insert("toeplitz".to_string(), MatrixPattern::Toeplitz);
672        self.patterns
673            .insert("hankel".to_string(), MatrixPattern::Hankel);
674        self.patterns
675            .insert("circulant".to_string(), MatrixPattern::Circulant);
676
677        // Populate optimization_hints with recommended algorithms for each pattern
678        self.optimization_hints.insert(
679            MatrixPattern::Diagonal,
680            OptimizationHint {
681                preferred_algorithm: "diagonal_scale".to_string(),
682                memory_layout: StorageFormat::CompressedSparseRow,
683                parallelization_strategy: "element_wise".to_string(),
684                vectorization_hints: vec!["scalar_multiply".to_string()],
685                estimated_speedup: 10.0,
686            },
687        );
688        self.optimization_hints.insert(
689            MatrixPattern::Triangular,
690            OptimizationHint {
691                preferred_algorithm: "triangular_solve".to_string(),
692                memory_layout: StorageFormat::RowMajor,
693                parallelization_strategy: "row_parallel".to_string(),
694                vectorization_hints: vec!["forward_substitution".to_string()],
695                estimated_speedup: 3.0,
696            },
697        );
698        self.optimization_hints.insert(
699            MatrixPattern::Banded,
700            OptimizationHint {
701                preferred_algorithm: "banded_gemm".to_string(),
702                memory_layout: StorageFormat::Blocked,
703                parallelization_strategy: "band_parallel".to_string(),
704                vectorization_hints: vec!["band_vectorization".to_string()],
705                estimated_speedup: 5.0,
706            },
707        );
708        self.optimization_hints.insert(
709            MatrixPattern::Symmetric,
710            OptimizationHint {
711                preferred_algorithm: "symmetric_gemm".to_string(),
712                memory_layout: StorageFormat::RowMajor,
713                parallelization_strategy: "block_parallel".to_string(),
714                vectorization_hints: vec!["symmetric_pack".to_string()],
715                estimated_speedup: 2.0,
716            },
717        );
718        self.optimization_hints.insert(
719            MatrixPattern::PositiveDefinite,
720            OptimizationHint {
721                preferred_algorithm: "cholesky_decomposition".to_string(),
722                memory_layout: StorageFormat::RowMajor,
723                parallelization_strategy: "block_parallel".to_string(),
724                vectorization_hints: vec!["cholesky_vectorized".to_string()],
725                estimated_speedup: 4.0,
726            },
727        );
728        self.optimization_hints.insert(
729            MatrixPattern::Orthogonal,
730            OptimizationHint {
731                preferred_algorithm: "orthogonal_transform".to_string(),
732                memory_layout: StorageFormat::RowMajor,
733                parallelization_strategy: "column_parallel".to_string(),
734                vectorization_hints: vec!["transpose_free".to_string()],
735                estimated_speedup: 3.0,
736            },
737        );
738        self.optimization_hints.insert(
739            MatrixPattern::Sparse,
740            OptimizationHint {
741                preferred_algorithm: "sparse_gemm".to_string(),
742                memory_layout: StorageFormat::CompressedSparseRow,
743                parallelization_strategy: "row_parallel".to_string(),
744                vectorization_hints: vec!["sparse_vectorization".to_string()],
745                estimated_speedup: 8.0,
746            },
747        );
748        self.optimization_hints.insert(
749            MatrixPattern::Dense,
750            OptimizationHint {
751                preferred_algorithm: "blocked_gemm".to_string(),
752                memory_layout: StorageFormat::Blocked,
753                parallelization_strategy: "block_parallel".to_string(),
754                vectorization_hints: vec!["avx2_vectorization".to_string()],
755                estimated_speedup: 1.0,
756            },
757        );
758        self.optimization_hints.insert(
759            MatrixPattern::BlockDiagonal,
760            OptimizationHint {
761                preferred_algorithm: "block_diagonal_gemm".to_string(),
762                memory_layout: StorageFormat::Blocked,
763                parallelization_strategy: "block_parallel".to_string(),
764                vectorization_hints: vec!["block_vectorization".to_string()],
765                estimated_speedup: 6.0,
766            },
767        );
768        self.optimization_hints.insert(
769            MatrixPattern::Toeplitz,
770            OptimizationHint {
771                preferred_algorithm: "toeplitz_fft".to_string(),
772                memory_layout: StorageFormat::RowMajor,
773                parallelization_strategy: "diagonal_parallel".to_string(),
774                vectorization_hints: vec!["fft_convolution".to_string()],
775                estimated_speedup: 7.0,
776            },
777        );
778        self.optimization_hints.insert(
779            MatrixPattern::Hankel,
780            OptimizationHint {
781                preferred_algorithm: "hankel_fft".to_string(),
782                memory_layout: StorageFormat::RowMajor,
783                parallelization_strategy: "anti_diagonal_parallel".to_string(),
784                vectorization_hints: vec!["fft_convolution".to_string()],
785                estimated_speedup: 7.0,
786            },
787        );
788        self.optimization_hints.insert(
789            MatrixPattern::Circulant,
790            OptimizationHint {
791                preferred_algorithm: "circulant_fft".to_string(),
792                memory_layout: StorageFormat::RowMajor,
793                parallelization_strategy: "row_parallel".to_string(),
794                vectorization_hints: vec!["fft_convolution".to_string()],
795                estimated_speedup: 8.0,
796            },
797        );
798
799        Ok(())
800    }
801
802    /// Return the optimization hint for a given pattern
803    pub fn get_optimization_hint(&self, pattern: &MatrixPattern) -> Option<&OptimizationHint> {
804        self.optimization_hints.get(pattern)
805    }
806}
807
808impl MatrixTransformer {
809    pub fn new() -> Self {
810        Self {
811            transformation_rules: vec![
812                TransformationRule::LayoutConversion,
813                TransformationRule::RowColumnSwap,
814                TransformationRule::BlockReordering,
815                TransformationRule::DataTypeConversion,
816            ],
817            transformation_history: Vec::new(),
818        }
819    }
820
821    pub fn initialize(&mut self) -> Result<(), LinearAlgebraError> {
822        Ok(())
823    }
824
825    /// Transform a matrix's in-memory storage layout to `target_layout`.
826    ///
827    /// The source layout is inferred from `matrix.storage_format`. The
828    /// returned matrix has its `data` buffer reorganised and its
829    /// `storage_format` (and `metadata.storage_format`) updated to reflect
830    /// the new layout. Row/column dimensions and element values are preserved.
831    ///
832    /// Supported conversions:
833    /// - `RowMajor` <-> `ColMajor` (transpose storage layout)
834    /// - `Blocked(inner, block_size)` (reorganise into cache-friendly tiles)
835    /// - `Packed` (pad each row to a multiple of the SIMD vector width)
836    ///
837    /// Reading from a `Blocked` source is not supported because the block size
838    /// is not carried in `Matrix` metadata; reading from sparse formats
839    /// (`CompressedSparseRow` / `CompressedSparseColumn`) is likewise
840    /// unsupported. Both return an `OptimizationError`.
841    pub fn transform_matrix(
842        &self,
843        matrix: &Matrix,
844        target_layout: MatrixLayout,
845    ) -> Result<Matrix, LinearAlgebraError> {
846        let rows = matrix.rows;
847        let cols = matrix.cols;
848
849        // Validate the source buffer is consistent with its declared layout.
850        validate_source_buffer(matrix)?;
851
852        // Read a logical element (i, j) from the source according to its
853        // current storage_format.
854        let src =
855            |i: usize, j: usize| -> Result<f64, LinearAlgebraError> { read_element(matrix, i, j) };
856
857        let (data, storage_format) = match target_layout {
858            MatrixLayout::RowMajor => {
859                let mut data = Vec::with_capacity(rows * cols);
860                for i in 0..rows {
861                    for j in 0..cols {
862                        data.push(src(i, j)?);
863                    }
864                }
865                (data, StorageFormat::RowMajor)
866            }
867            MatrixLayout::ColMajor => {
868                let mut data = Vec::with_capacity(rows * cols);
869                // Column-major: contiguous down each column.
870                for j in 0..cols {
871                    for i in 0..rows {
872                        data.push(src(i, j)?);
873                    }
874                }
875                (data, StorageFormat::ColumnMajor)
876            }
877            MatrixLayout::Blocked(inner, block_size) => {
878                if block_size == 0 {
879                    return Err(LinearAlgebraError::OptimizationError(
880                        "Blocked layout requires block_size > 0".to_string(),
881                    ));
882                }
883                let mut data = Vec::with_capacity(rows * cols);
884                let block_rows = (rows + block_size - 1) / block_size;
885                let block_cols = (cols + block_size - 1) / block_size;
886                // Within-block element order follows the inner layout; default
887                // to row-major for any non-column-major inner layout.
888                let col_major_inner = *inner == MatrixLayout::ColMajor;
889                for bi in 0..block_rows {
890                    for bj in 0..block_cols {
891                        let i_start = bi * block_size;
892                        let i_end = rows.min((bi + 1) * block_size);
893                        let j_start = bj * block_size;
894                        let j_end = cols.min((bj + 1) * block_size);
895                        if col_major_inner {
896                            for j in j_start..j_end {
897                                for i in i_start..i_end {
898                                    data.push(src(i, j)?);
899                                }
900                            }
901                        } else {
902                            for i in i_start..i_end {
903                                for j in j_start..j_end {
904                                    data.push(src(i, j)?);
905                                }
906                            }
907                        }
908                    }
909                }
910                (data, StorageFormat::Blocked)
911            }
912            MatrixLayout::Packed => {
913                let stride = padded_stride(cols);
914                let mut data = vec![0.0; rows * stride];
915                for i in 0..rows {
916                    for j in 0..cols {
917                        data[i * stride + j] = src(i, j)?;
918                    }
919                }
920                (data, StorageFormat::Packed)
921            }
922        };
923
924        let mut result = matrix.clone();
925        result.data = data;
926        result.storage_format = storage_format.clone();
927        result.metadata.storage_format = storage_format;
928        Ok(result)
929    }
930
931    /// Analyse an access pattern and automatically pick the best layout for
932    /// the given matrix, then transform it.
933    ///
934    /// Layout selection heuristic:
935    /// - [`AccessPattern::Sequential`] (row-wise traversal) -> `RowMajor`
936    /// - [`AccessPattern::Strided`] (column-wise traversal) -> `ColMajor`
937    /// - [`AccessPattern::Blocked`] -> `Blocked(RowMajor, block_size)` where
938    ///   `block_size` is a cache-friendly tile (capped at 16)
939    /// - [`AccessPattern::Random`] -> `RowMajor` (no clear winner; keep the
940    ///   canonical layout)
941    /// - [`AccessPattern::Adaptive`] -> `ColMajor` for tall matrices
942    ///   (`rows > cols`), `RowMajor` otherwise
943    pub fn optimize_layout(
944        &self,
945        matrix: &Matrix,
946        access_pattern: &AccessPattern,
947    ) -> Result<Matrix, LinearAlgebraError> {
948        let target = match access_pattern {
949            AccessPattern::Sequential => MatrixLayout::RowMajor,
950            AccessPattern::Strided => MatrixLayout::ColMajor,
951            AccessPattern::Blocked => {
952                // Pick a cache-friendly tile size bounded by the matrix's
953                // smaller dimension and a 16-element cap.
954                let block_size = matrix.rows.min(matrix.cols).max(1).min(16);
955                MatrixLayout::Blocked(Box::new(MatrixLayout::RowMajor), block_size)
956            }
957            AccessPattern::Random => MatrixLayout::RowMajor,
958            AccessPattern::Adaptive => {
959                if matrix.rows > matrix.cols {
960                    MatrixLayout::ColMajor
961                } else {
962                    MatrixLayout::RowMajor
963                }
964            }
965        };
966        self.transform_matrix(matrix, target)
967    }
968}
969
970/// Padded row stride for the [`MatrixLayout::Packed`] layout: the column count
971/// rounded up to the next multiple of [`SIMD_WIDTH`].
972fn padded_stride(cols: usize) -> usize {
973    ((cols + SIMD_WIDTH - 1) / SIMD_WIDTH) * SIMD_WIDTH
974}
975
976/// Read logical element `(i, j)` from `matrix` according to its
977/// `storage_format`. Returns an error for unsupported source layouts
978/// (`Blocked`, sparse formats) or out-of-bounds indices.
979fn read_element(matrix: &Matrix, i: usize, j: usize) -> Result<f64, LinearAlgebraError> {
980    let rows = matrix.rows;
981    let cols = matrix.cols;
982    if i >= rows || j >= cols {
983        return Err(LinearAlgebraError::OptimizationError(format!(
984            "element index ({}, {}) out of bounds for matrix of shape {}x{}",
985            i, j, rows, cols
986        )));
987    }
988    let idx = match matrix.storage_format {
989        StorageFormat::RowMajor => i * cols + j,
990        StorageFormat::ColumnMajor => j * rows + i,
991        StorageFormat::Packed => {
992            let stride = padded_stride(cols);
993            i * stride + j
994        }
995        StorageFormat::Blocked => {
996            return Err(LinearAlgebraError::OptimizationError(
997                "cannot read from Blocked source: block size is not stored in Matrix metadata"
998                    .to_string(),
999            ));
1000        }
1001        StorageFormat::CompressedSparseRow | StorageFormat::CompressedSparseColumn => {
1002            return Err(LinearAlgebraError::OptimizationError(
1003                "sparse source layouts are not supported by the layout transformer".to_string(),
1004            ));
1005        }
1006    };
1007    if idx >= matrix.data.len() {
1008        return Err(LinearAlgebraError::OptimizationError(format!(
1009            "linear index {} out of bounds for data buffer of length {} (layout {:?})",
1010            idx,
1011            matrix.data.len(),
1012            matrix.storage_format
1013        )));
1014    }
1015    Ok(matrix.data[idx])
1016}
1017
1018/// Validate that the source matrix's `data` buffer length is consistent with
1019/// its declared `storage_format` and dimensions.
1020fn validate_source_buffer(matrix: &Matrix) -> Result<(), LinearAlgebraError> {
1021    let expected = match matrix.storage_format {
1022        StorageFormat::RowMajor | StorageFormat::ColumnMajor | StorageFormat::Blocked => {
1023            matrix.rows * matrix.cols
1024        }
1025        StorageFormat::Packed => matrix.rows * padded_stride(matrix.cols),
1026        StorageFormat::CompressedSparseRow | StorageFormat::CompressedSparseColumn => {
1027            // Sparse formats carry their own structure; skip strict length
1028            // validation rather than guess the expected buffer size.
1029            return Ok(());
1030        }
1031    };
1032    if matrix.data.len() != expected {
1033        return Err(LinearAlgebraError::OptimizationError(format!(
1034            "source data length {} does not match expected {} for {:?} layout ({}x{})",
1035            matrix.data.len(),
1036            expected,
1037            matrix.storage_format,
1038            matrix.rows,
1039            matrix.cols
1040        )));
1041    }
1042    Ok(())
1043}
1044
1045#[derive(Debug, Clone)]
1046pub struct MatrixAnalysis {
1047    pub matrix_id: String,
1048    pub sparsity: f64,
1049    pub structure: MatrixPattern,
1050    /// All detected structural patterns
1051    pub detected_patterns: Vec<MatrixPattern>,
1052    pub access_pattern: AccessPattern,
1053    pub optimization_hints: Vec<String>,
1054    /// Recommended algorithms derived from optimization hints
1055    pub recommended_algorithms: Vec<String>,
1056}
1057
1058/// Result of a full matrix analysis (alias for MatrixAnalysis for API clarity)
1059pub type MatrixAnalysisResult = MatrixAnalysis;
1060
1061#[cfg(test)]
1062mod tests {
1063    use super::*;
1064
1065    fn make_matrix(id: &str, rows: usize, cols: usize, data: Vec<f64>) -> Matrix {
1066        let metadata = MatrixMetadata {
1067            matrix_id: id.to_string(),
1068            rows,
1069            cols,
1070            data_type: DataType::Float64,
1071            storage_format: StorageFormat::RowMajor,
1072            compression: CompressionType::None,
1073            created_at: 0,
1074            last_accessed: 0,
1075            access_count: 0,
1076        };
1077        Matrix {
1078            matrix_id: id.to_string(),
1079            rows,
1080            cols,
1081            data_type: DataType::Float64,
1082            data,
1083            storage_format: StorageFormat::RowMajor,
1084            metadata,
1085        }
1086    }
1087
1088    fn make_analyzer() -> MatrixAnalyzer {
1089        let mut analyzer = MatrixAnalyzer::new();
1090        analyzer.initialize().unwrap();
1091        analyzer
1092    }
1093
1094    #[test]
1095    fn test_detect_diagonal() {
1096        let analyzer = make_analyzer();
1097        // Diagonal matrix
1098        let m = make_matrix("d", 3, 3, vec![1.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 3.0]);
1099        let patterns = analyzer.detect_structure(&m);
1100        assert!(patterns.contains(&MatrixPattern::Diagonal));
1101    }
1102
1103    #[test]
1104    fn test_detect_upper_triangular() {
1105        let analyzer = make_analyzer();
1106        let m = make_matrix("u", 3, 3, vec![1.0, 2.0, 3.0, 0.0, 4.0, 5.0, 0.0, 0.0, 6.0]);
1107        let patterns = analyzer.detect_structure(&m);
1108        assert!(patterns.contains(&MatrixPattern::Triangular));
1109    }
1110
1111    #[test]
1112    fn test_detect_lower_triangular() {
1113        let analyzer = make_analyzer();
1114        let m = make_matrix("l", 3, 3, vec![1.0, 0.0, 0.0, 2.0, 3.0, 0.0, 4.0, 5.0, 6.0]);
1115        let patterns = analyzer.detect_structure(&m);
1116        assert!(patterns.contains(&MatrixPattern::Triangular));
1117    }
1118
1119    #[test]
1120    fn test_detect_symmetric() {
1121        let analyzer = make_analyzer();
1122        let m = make_matrix("s", 3, 3, vec![1.0, 2.0, 3.0, 2.0, 4.0, 5.0, 3.0, 5.0, 6.0]);
1123        let patterns = analyzer.detect_structure(&m);
1124        assert!(patterns.contains(&MatrixPattern::Symmetric));
1125    }
1126
1127    #[test]
1128    fn test_detect_not_symmetric() {
1129        let analyzer = make_analyzer();
1130        let m = make_matrix("ns", 2, 2, vec![1.0, 2.0, 3.0, 4.0]);
1131        let patterns = analyzer.detect_structure(&m);
1132        assert!(!patterns.contains(&MatrixPattern::Symmetric));
1133    }
1134
1135    #[test]
1136    fn test_detect_positive_definite() {
1137        let analyzer = make_analyzer();
1138        // [[2,1],[1,2]] is symmetric positive definite (eigenvalues 1, 3)
1139        let m = make_matrix("pd", 2, 2, vec![2.0, 1.0, 1.0, 2.0]);
1140        let patterns = analyzer.detect_structure(&m);
1141        assert!(patterns.contains(&MatrixPattern::PositiveDefinite));
1142    }
1143
1144    #[test]
1145    fn test_detect_not_positive_definite() {
1146        let analyzer = make_analyzer();
1147        // [[1,2],[2,1]] is symmetric but not positive definite (eigenvalues -1, 3)
1148        let m = make_matrix("npd", 2, 2, vec![1.0, 2.0, 2.0, 1.0]);
1149        let patterns = analyzer.detect_structure(&m);
1150        assert!(!patterns.contains(&MatrixPattern::PositiveDefinite));
1151    }
1152
1153    #[test]
1154    fn test_detect_sparse() {
1155        let analyzer = make_analyzer();
1156        // 4x4 matrix with mostly zeros
1157        let m = make_matrix(
1158            "sp",
1159            4,
1160            4,
1161            vec![
1162                1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0,
1163            ],
1164        );
1165        let patterns = analyzer.detect_structure(&m);
1166        assert!(patterns.contains(&MatrixPattern::Sparse));
1167    }
1168
1169    #[test]
1170    fn test_detect_dense() {
1171        let analyzer = make_analyzer();
1172        let m = make_matrix("d", 2, 2, vec![1.0, 2.0, 3.0, 4.0]);
1173        let patterns = analyzer.detect_structure(&m);
1174        assert!(patterns.contains(&MatrixPattern::Dense));
1175    }
1176
1177    #[test]
1178    fn test_detect_banded() {
1179        let analyzer = make_analyzer();
1180        // Tridiagonal 5x5 (bandwidth 1, which is < 5/3 = 1.66)
1181        let m = make_matrix(
1182            "b",
1183            5,
1184            5,
1185            vec![
1186                1.0, 2.0, 0.0, 0.0, 0.0, 3.0, 4.0, 5.0, 0.0, 0.0, 0.0, 6.0, 7.0, 8.0, 0.0, 0.0,
1187                0.0, 9.0, 10.0, 11.0, 0.0, 0.0, 0.0, 12.0, 13.0,
1188            ],
1189        );
1190        let patterns = analyzer.detect_structure(&m);
1191        assert!(patterns.contains(&MatrixPattern::Banded));
1192    }
1193
1194    #[test]
1195    fn test_detect_toeplitz() {
1196        let analyzer = make_analyzer();
1197        // Toeplitz: each diagonal constant
1198        // [[1,2,3],[4,1,2],[5,4,1]]
1199        let m = make_matrix("t", 3, 3, vec![1.0, 2.0, 3.0, 4.0, 1.0, 2.0, 5.0, 4.0, 1.0]);
1200        let patterns = analyzer.detect_structure(&m);
1201        assert!(patterns.contains(&MatrixPattern::Toeplitz));
1202    }
1203
1204    #[test]
1205    fn test_detect_orthogonal() {
1206        let analyzer = make_analyzer();
1207        // 2x2 rotation matrix (orthogonal)
1208        // [[0, -1], [1, 0]] — A*A^T = I
1209        let m = make_matrix("o", 2, 2, vec![0.0, -1.0, 1.0, 0.0]);
1210        let patterns = analyzer.detect_structure(&m);
1211        assert!(patterns.contains(&MatrixPattern::Orthogonal));
1212    }
1213
1214    #[test]
1215    fn test_detect_circulant() {
1216        let analyzer = make_analyzer();
1217        // Circulant 3x3: each row is cyclic shift of previous
1218        // [[1,2,3],[3,1,2],[2,3,1]]
1219        let m = make_matrix("c", 3, 3, vec![1.0, 2.0, 3.0, 3.0, 1.0, 2.0, 2.0, 3.0, 1.0]);
1220        let patterns = analyzer.detect_structure(&m);
1221        assert!(patterns.contains(&MatrixPattern::Circulant));
1222    }
1223
1224    #[test]
1225    fn test_detect_hankel() {
1226        let analyzer = make_analyzer();
1227        // Hankel: constant along anti-diagonals
1228        // [[1,2,3],[2,3,4],[3,4,5]]
1229        let m = make_matrix("h", 3, 3, vec![1.0, 2.0, 3.0, 2.0, 3.0, 4.0, 3.0, 4.0, 5.0]);
1230        let patterns = analyzer.detect_structure(&m);
1231        assert!(patterns.contains(&MatrixPattern::Hankel));
1232    }
1233
1234    #[test]
1235    fn test_detect_block_diagonal() {
1236        let analyzer = make_analyzer();
1237        // 4x4 block diagonal with 2x2 blocks
1238        let m = make_matrix(
1239            "bd",
1240            4,
1241            4,
1242            vec![
1243                1.0, 2.0, 0.0, 0.0, 3.0, 4.0, 0.0, 0.0, 0.0, 0.0, 5.0, 6.0, 0.0, 0.0, 7.0, 8.0,
1244            ],
1245        );
1246        let patterns = analyzer.detect_structure(&m);
1247        assert!(patterns.contains(&MatrixPattern::BlockDiagonal));
1248    }
1249
1250    #[test]
1251    fn test_pattern_library_initialize() {
1252        let mut lib = PatternLibrary::new();
1253        lib.initialize().unwrap();
1254
1255        // All 12 patterns should be registered
1256        assert_eq!(lib.patterns.len(), 12);
1257        assert_eq!(lib.optimization_hints.len(), 12);
1258    }
1259
1260    #[test]
1261    fn test_get_optimization_hint() {
1262        let mut lib = PatternLibrary::new();
1263        lib.initialize().unwrap();
1264
1265        let hint = lib.get_optimization_hint(&MatrixPattern::Diagonal);
1266        assert!(hint.is_some());
1267        let h = hint.unwrap();
1268        assert_eq!(h.preferred_algorithm, "diagonal_scale");
1269        assert!(h.estimated_speedup > 0.0);
1270
1271        let hint2 = lib.get_optimization_hint(&MatrixPattern::Sparse);
1272        assert!(hint2.is_some());
1273        assert_eq!(hint2.unwrap().preferred_algorithm, "sparse_gemm");
1274    }
1275
1276    #[test]
1277    fn test_analyze_matrix() {
1278        let mut analyzer = make_analyzer();
1279        let m = make_matrix("s", 2, 2, vec![2.0, 1.0, 1.0, 2.0]);
1280        let result = analyzer.analyze_matrix(&m).unwrap();
1281
1282        assert_eq!(result.matrix_id, "s");
1283        assert!(result.detected_patterns.contains(&MatrixPattern::Symmetric));
1284        assert!(result
1285            .detected_patterns
1286            .contains(&MatrixPattern::PositiveDefinite));
1287        assert!(!result.recommended_algorithms.is_empty());
1288    }
1289
1290    #[test]
1291    fn test_analyze_matrix_sparsity() {
1292        let mut analyzer = make_analyzer();
1293        // 3x3 with 5 zeros out of 9 → sparsity = 5/9
1294        let m = make_matrix(
1295            "sp",
1296            3,
1297            3,
1298            vec![1.0, 0.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 3.0],
1299        );
1300        let result = analyzer.analyze_matrix(&m).unwrap();
1301        assert!((result.sparsity - 6.0 / 9.0).abs() < 1e-10); // 6 zeros, 3 non-zeros
1302    }
1303
1304    // ---- MatrixTransformer layout-conversion tests ----
1305
1306    fn make_transformer() -> MatrixTransformer {
1307        let mut t = MatrixTransformer::new();
1308        t.initialize().unwrap();
1309        t
1310    }
1311
1312    /// Build a column-major matrix from a row-major `data` description.
1313    /// `data` is given in row-major order (row by row); the returned matrix
1314    /// stores it in column-major order with `storage_format = ColumnMajor`.
1315    fn make_col_matrix(id: &str, rows: usize, cols: usize, row_major_data: Vec<f64>) -> Matrix {
1316        // Reorganise row-major input into column-major storage.
1317        let mut col_major = Vec::with_capacity(rows * cols);
1318        for j in 0..cols {
1319            for i in 0..rows {
1320                col_major.push(row_major_data[i * cols + j]);
1321            }
1322        }
1323        let metadata = MatrixMetadata {
1324            matrix_id: id.to_string(),
1325            rows,
1326            cols,
1327            data_type: DataType::Float64,
1328            storage_format: StorageFormat::ColumnMajor,
1329            compression: CompressionType::None,
1330            created_at: 0,
1331            last_accessed: 0,
1332            access_count: 0,
1333        };
1334        Matrix {
1335            matrix_id: id.to_string(),
1336            rows,
1337            cols,
1338            data_type: DataType::Float64,
1339            data: col_major,
1340            storage_format: StorageFormat::ColumnMajor,
1341            metadata,
1342        }
1343    }
1344
1345    /// Read element (i, j) from a blocked-layout matrix with a known block size
1346    /// (row-major within blocks, row-major block order). Used to verify the
1347    /// blocked transform reorganised the data correctly.
1348    fn read_blocked(m: &Matrix, block_size: usize, i: usize, j: usize) -> f64 {
1349        let rows = m.rows;
1350        let cols = m.cols;
1351        let block_rows = (rows + block_size - 1) / block_size;
1352        let block_cols = (cols + block_size - 1) / block_size;
1353        let bi = i / block_size;
1354        let bj = j / block_size;
1355        let li = i % block_size; // local row within block
1356        let lj = j % block_size; // local col within block
1357                                 // Count elements in blocks preceding (bi, bj) in row-major block order.
1358        let mut offset = 0usize;
1359        'outer: for bbi in 0..block_rows {
1360            for bbj in 0..block_cols {
1361                if bbi == bi && bbj == bj {
1362                    break 'outer;
1363                }
1364                let i_end = rows.min((bbi + 1) * block_size);
1365                let j_end = cols.min((bbj + 1) * block_size);
1366                offset += (i_end - bbi * block_size) * (j_end - bbj * block_size);
1367            }
1368        }
1369        // Local block dimensions for block (bi, bj).
1370        let i_start = bi * block_size;
1371        let j_start = bj * block_size;
1372        let _local_rows = rows.min((bi + 1) * block_size) - i_start;
1373        let local_cols = cols.min((bj + 1) * block_size) - j_start;
1374        // Row-major within block.
1375        offset += li * local_cols + lj;
1376        m.data[offset]
1377    }
1378
1379    #[test]
1380    fn test_row_to_col_major() {
1381        let transformer = make_transformer();
1382        // 2x3 matrix:
1383        //   [[1, 2, 3],
1384        //    [4, 5, 6]]
1385        let m = make_matrix("rm", 2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
1386        assert_eq!(m.storage_format, StorageFormat::RowMajor);
1387
1388        let result = transformer
1389            .transform_matrix(&m, MatrixLayout::ColMajor)
1390            .unwrap();
1391
1392        assert_eq!(result.storage_format, StorageFormat::ColumnMajor);
1393        assert_eq!(result.metadata.storage_format, StorageFormat::ColumnMajor);
1394        assert_eq!(result.rows, 2);
1395        assert_eq!(result.cols, 3);
1396        // Column-major: column 0 = [1, 4], column 1 = [2, 5], column 2 = [3, 6]
1397        assert_eq!(result.data, vec![1.0, 4.0, 2.0, 5.0, 3.0, 6.0]);
1398        // Verify every logical element is preserved under col-major addressing.
1399        for i in 0..2 {
1400            for j in 0..3 {
1401                let expected = m.data[i * 3 + j];
1402                let got = result.data[j * 2 + i];
1403                assert_eq!(got, expected, "element ({},{}) mismatch", i, j);
1404            }
1405        }
1406    }
1407
1408    #[test]
1409    fn test_col_to_row_major() {
1410        let transformer = make_transformer();
1411        // Build a column-major matrix representing [[1,2,3],[4,5,6]].
1412        let m = make_col_matrix("cm", 2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
1413        assert_eq!(m.storage_format, StorageFormat::ColumnMajor);
1414        assert_eq!(m.data, vec![1.0, 4.0, 2.0, 5.0, 3.0, 6.0]);
1415
1416        let result = transformer
1417            .transform_matrix(&m, MatrixLayout::RowMajor)
1418            .unwrap();
1419
1420        assert_eq!(result.storage_format, StorageFormat::RowMajor);
1421        assert_eq!(result.rows, 2);
1422        assert_eq!(result.cols, 3);
1423        // Row-major: [[1,2,3],[4,5,6]] flattened row by row.
1424        assert_eq!(result.data, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
1425        // Verify every logical element is preserved under row-major addressing.
1426        for i in 0..2 {
1427            for j in 0..3 {
1428                let expected = m.data[j * 2 + i]; // col-major source read
1429                let got = result.data[i * 3 + j]; // row-major result read
1430                assert_eq!(got, expected, "element ({},{}) mismatch", i, j);
1431            }
1432        }
1433    }
1434
1435    #[test]
1436    fn test_blocked_layout() {
1437        let transformer = make_transformer();
1438        // 4x4 matrix 1..16 (row-major):
1439        //   [[ 1,  2,  3,  4],
1440        //    [ 5,  6,  7,  8],
1441        //    [ 9, 10, 11, 12],
1442        //    [13, 14, 15, 16]]
1443        let m = make_matrix(
1444            "blk",
1445            4,
1446            4,
1447            vec![
1448                1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0,
1449                16.0,
1450            ],
1451        );
1452
1453        let result = transformer
1454            .transform_matrix(
1455                &m,
1456                MatrixLayout::Blocked(Box::new(MatrixLayout::RowMajor), 2),
1457            )
1458            .unwrap();
1459
1460        assert_eq!(result.storage_format, StorageFormat::Blocked);
1461        assert_eq!(result.rows, 4);
1462        assert_eq!(result.cols, 4);
1463        assert_eq!(result.data.len(), 16);
1464        // Expected block order (2x2 blocks, row-major within each block):
1465        //   block(0,0) = [1, 2, 5, 6]
1466        //   block(0,1) = [3, 4, 7, 8]
1467        //   block(1,0) = [9, 10, 13, 14]
1468        //   block(1,1) = [11, 12, 15, 16]
1469        assert_eq!(
1470            result.data,
1471            vec![
1472                1.0, 2.0, 5.0, 6.0, 3.0, 4.0, 7.0, 8.0, 9.0, 10.0, 13.0, 14.0, 11.0, 12.0, 15.0,
1473                16.0,
1474            ]
1475        );
1476        // Verify every logical element is recoverable from the blocked buffer.
1477        for i in 0..4 {
1478            for j in 0..4 {
1479                let expected = m.data[i * 4 + j];
1480                let got = read_blocked(&result, 2, i, j);
1481                assert_eq!(got, expected, "blocked element ({},{}) mismatch", i, j);
1482            }
1483        }
1484    }
1485
1486    #[test]
1487    fn test_optimize_layout_row_access() {
1488        let transformer = make_transformer();
1489        let m = make_matrix("r", 2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
1490        // Sequential access = row-heavy traversal -> RowMajor.
1491        let result = transformer
1492            .optimize_layout(&m, &AccessPattern::Sequential)
1493            .unwrap();
1494        assert_eq!(result.storage_format, StorageFormat::RowMajor);
1495        // Row-major data is unchanged from the row-major source.
1496        assert_eq!(result.data, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
1497    }
1498
1499    #[test]
1500    fn test_optimize_layout_col_access() {
1501        let transformer = make_transformer();
1502        let m = make_matrix("c", 2, 3, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
1503        // Strided access = column-heavy traversal -> ColMajor.
1504        let result = transformer
1505            .optimize_layout(&m, &AccessPattern::Strided)
1506            .unwrap();
1507        assert_eq!(result.storage_format, StorageFormat::ColumnMajor);
1508        // Column-major reorganisation: [1, 4, 2, 5, 3, 6].
1509        assert_eq!(result.data, vec![1.0, 4.0, 2.0, 5.0, 3.0, 6.0]);
1510    }
1511}