Skip to main content

scirs2_linalg/
perf_opt.rs

1//! Performance optimizations for large matrices
2//!
3//! This module provides optimized implementations of linear algebra operations
4//! specifically designed for large matrices, including cache-friendly algorithms,
5//! parallelization, and memory layout optimizations.
6
7use crate::error::{LinalgError, LinalgResult};
8use scirs2_core::ndarray::{Array2, ArrayView2, Axis, ScalarOperand};
9use scirs2_core::numeric::{Float, NumAssign};
10use scirs2_core::parallel_ops::*;
11use std::cmp;
12use std::iter::Sum;
13
14/// Algorithm selection for optimized operations
15#[derive(Debug, Clone, Copy)]
16pub enum OptAlgorithm {
17    /// Use standard algorithm
18    Standard,
19    /// Use blocked algorithm
20    Blocked,
21    /// Use parallel algorithms
22    Parallel,
23    /// Automatically select based on matrix size
24    Adaptive,
25}
26
27/// Configuration for performance-optimized operations
28#[derive(Debug, Clone)]
29pub struct OptConfig {
30    /// Block size for cache-friendly algorithms
31    pub blocksize: usize,
32    /// Threshold for using parallel algorithms
33    pub parallel_threshold: usize,
34    /// Number of threads for parallel operations (None = use default)
35    pub num_threads: Option<usize>,
36    /// Algorithm selection
37    pub algorithm: OptAlgorithm,
38}
39
40impl Default for OptConfig {
41    fn default() -> Self {
42        OptConfig {
43            blocksize: 64,
44            parallel_threshold: 1000,
45            num_threads: None,
46            algorithm: OptAlgorithm::Blocked,
47        }
48    }
49}
50
51impl OptConfig {
52    /// Builder pattern methods
53    pub fn with_blocksize(mut self, size: usize) -> Self {
54        self.blocksize = size;
55        self
56    }
57
58    pub fn with_parallel_threshold(mut self, threshold: usize) -> Self {
59        self.parallel_threshold = threshold;
60        self
61    }
62
63    pub fn with_num_threads(mut self, threads: usize) -> Self {
64        self.num_threads = Some(threads);
65        self
66    }
67
68    pub fn with_algorithm(mut self, algorithm: OptAlgorithm) -> Self {
69        self.algorithm = algorithm;
70        self
71    }
72}
73
74/// Cache-friendly blocked matrix multiplication
75///
76/// This implementation uses loop tiling to improve cache locality
77/// for large matrix multiplications.
78#[allow(dead_code)]
79pub fn blocked_matmul<F>(
80    a: &ArrayView2<F>,
81    b: &ArrayView2<F>,
82    config: &OptConfig,
83) -> LinalgResult<Array2<F>>
84where
85    F: Float + NumAssign + Sum + Send + Sync + scirs2_core::ndarray::ScalarOperand + 'static,
86{
87    let (m, k) = (a.nrows(), a.ncols());
88    let (k2, n) = (b.nrows(), b.ncols());
89
90    if k != k2 {
91        return Err(LinalgError::DimensionError(format!(
92            "Matrix dimensions don't match: ({m}, {k}) x ({k2}, {n})"
93        )));
94    }
95
96    let mut c = Array2::zeros((m, n));
97    let blocksize = config.blocksize;
98
99    match config.algorithm {
100        OptAlgorithm::Standard => Ok(a.dot(b)),
101        OptAlgorithm::Blocked => {
102            serial_blocked_matmul(a, b, &mut c, blocksize)?;
103            Ok(c)
104        }
105        OptAlgorithm::Parallel => {
106            parallel_blocked_matmul(a, b, &mut c, blocksize)?;
107            Ok(c)
108        }
109        OptAlgorithm::Adaptive => {
110            // Use parallel processing for large matrices
111            if m * n > config.parallel_threshold {
112                parallel_blocked_matmul(a, b, &mut c, blocksize)?;
113            } else if m * n > 10000 {
114                serial_blocked_matmul(a, b, &mut c, blocksize)?;
115            } else {
116                return Ok(a.dot(b));
117            }
118            Ok(c)
119        }
120    }
121}
122
123/// Serial blocked matrix multiplication
124#[allow(dead_code)]
125fn serial_blocked_matmul<F>(
126    a: &ArrayView2<F>,
127    b: &ArrayView2<F>,
128    c: &mut Array2<F>,
129    blocksize: usize,
130) -> LinalgResult<()>
131where
132    F: Float + NumAssign + Sum + Send + Sync + ScalarOperand + 'static,
133{
134    let (m, k) = (a.nrows(), a.ncols());
135    let n = b.ncols();
136
137    // Loop tiling for better cache performance
138    for ii in (0..m).step_by(blocksize) {
139        for jj in (0..n).step_by(blocksize) {
140            for kk in (0..k).step_by(blocksize) {
141                // Process block
142                let i_end = cmp::min(ii + blocksize, m);
143                let j_end = cmp::min(jj + blocksize, n);
144                let k_end = cmp::min(kk + blocksize, k);
145
146                for i in ii..i_end {
147                    for j in jj..j_end {
148                        let mut sum = c[[i, j]];
149                        for ki in kk..k_end {
150                            sum += a[[i, ki]] * b[[ki, j]];
151                        }
152                        c[[i, j]] = sum;
153                    }
154                }
155            }
156        }
157    }
158
159    Ok(())
160}
161
162/// Parallel blocked matrix multiplication using Rayon
163#[allow(dead_code)]
164fn parallel_blocked_matmul<F>(
165    a: &ArrayView2<F>,
166    b: &ArrayView2<F>,
167    c: &mut Array2<F>,
168    blocksize: usize,
169) -> LinalgResult<()>
170where
171    F: Float + NumAssign + Sum + Send + Sync,
172{
173    let (m, k) = (a.nrows(), a.ncols());
174    let n = b.ncols();
175
176    // Create blocks for parallel processing
177    let block_indices: Vec<(usize, usize)> = (0..m)
178        .step_by(blocksize)
179        .flat_map(|i| (0..n).step_by(blocksize).map(move |j| (i, j)))
180        .collect();
181
182    // Process blocks in parallel and collect results using scirs2-core parallel operations
183    let results: Vec<_> = parallel_map(&block_indices, |&(ii, jj)| {
184        let i_end = cmp::min(ii + blocksize, m);
185        let j_end = cmp::min(jj + blocksize, n);
186
187        // Create local accumulator for this block
188        let mut local_c = Array2::zeros((i_end - ii, j_end - jj));
189
190        // Compute block multiplication
191        for kk in (0..k).step_by(blocksize) {
192            let k_end = cmp::min(kk + blocksize, k);
193
194            for (i_local, i) in (0..(i_end - ii)).zip(ii..i_end) {
195                for (j_local, j) in (0..(j_end - jj)).zip(jj..j_end) {
196                    let mut sum = local_c[[i_local, j_local]];
197                    for ki in kk..k_end {
198                        sum += a[[i, ki]] * b[[ki, j]];
199                    }
200                    local_c[[i_local, j_local]] = sum;
201                }
202            }
203        }
204
205        // Return the block and its position
206        ((ii, jj), local_c)
207    });
208
209    // Write results back to the main matrix
210    for ((ii, jj), local_c) in results {
211        let i_end = cmp::min(ii + blocksize, m);
212        let j_end = cmp::min(jj + blocksize, n);
213
214        for (i_local, i) in (0..(i_end - ii)).zip(ii..i_end) {
215            for (j_local, j) in (0..(j_end - jj)).zip(jj..j_end) {
216                c[[i, j]] = local_c[[i_local, j_local]];
217            }
218        }
219    }
220
221    Ok(())
222}
223
224/// Cache-friendly blocked matrix multiplication with explicit workers parameter
225#[allow(dead_code)]
226pub fn blocked_matmul_with_workers<F>(
227    a: &ArrayView2<F>,
228    b: &ArrayView2<F>,
229    workers: Option<usize>,
230) -> LinalgResult<Array2<F>>
231where
232    F: Float + NumAssign + Sum + Send + Sync + scirs2_core::ndarray::ScalarOperand + 'static,
233{
234    use crate::parallel;
235
236    // Configure workers for parallel operations
237    parallel::configure_workers(workers);
238
239    let config = OptConfig {
240        num_threads: workers,
241        ..OptConfig::default()
242    };
243
244    blocked_matmul(a, b, &config)
245}
246
247/// Optimized matrix transpose for better memory access patterns
248#[allow(dead_code)]
249pub fn optimized_transpose<F>(a: &ArrayView2<F>) -> LinalgResult<Array2<F>>
250where
251    F: Float + Send + Sync,
252{
253    optimized_transpose_with_workers(a, None)
254}
255
256/// Optimized matrix transpose with explicit workers parameter
257#[allow(dead_code)]
258pub fn optimized_transpose_with_workers<F>(
259    a: &ArrayView2<F>,
260    workers: Option<usize>,
261) -> LinalgResult<Array2<F>>
262where
263    F: Float + Send + Sync,
264{
265    use crate::parallel;
266
267    // Configure workers for parallel operations
268    parallel::configure_workers(workers);
269    let (m, n) = (a.nrows(), a.ncols());
270    let mut result = Array2::zeros((n, m));
271
272    // Use blocked transpose for better cache performance
273    let blocksize = 32;
274
275    for i in (0..m).step_by(blocksize) {
276        for j in (0..n).step_by(blocksize) {
277            let i_end = cmp::min(i + blocksize, m);
278            let j_end = cmp::min(j + blocksize, n);
279
280            // Transpose block
281            for ii in i..i_end {
282                for jj in j..j_end {
283                    result[[jj, ii]] = a[[ii, jj]];
284                }
285            }
286        }
287    }
288
289    Ok(result)
290}
291
292/// Parallel matrix-vector multiplication for large matrices
293#[allow(dead_code)]
294pub fn parallel_matvec<F>(
295    a: &ArrayView2<F>,
296    x: &ArrayView2<F>,
297    config: &OptConfig,
298) -> LinalgResult<Array2<F>>
299where
300    F: Float + NumAssign + Sum + Send + Sync,
301{
302    if a.ncols() != x.nrows() {
303        return Err(LinalgError::DimensionError(format!(
304            "Matrix and vector dimensions don't match: ({}, {}) x ({}, {})",
305            a.nrows(),
306            a.ncols(),
307            x.nrows(),
308            x.ncols()
309        )));
310    }
311
312    let m = a.nrows();
313    let n = x.ncols();
314    let mut result = Array2::zeros((m, n));
315
316    if m > config.parallel_threshold {
317        // Parallel computation for large matrices using scirs2-core parallel operations
318        let rows: Vec<_> = result.axis_iter_mut(Axis(0)).enumerate().collect();
319
320        // Use Rayon's parallel iterator for proper parallel execution
321        rows.into_par_iter().for_each(|(i, mut row)| {
322            for j in 0..n {
323                let mut sum = F::zero();
324                for k in 0..a.ncols() {
325                    sum += a[[i, k]] * x[[k, j]];
326                }
327                row[j] = sum;
328            }
329        });
330    } else {
331        // Serial computation for smaller matrices
332        for i in 0..m {
333            for j in 0..n {
334                let mut sum = F::zero();
335                for k in 0..a.ncols() {
336                    sum += a[[i, k]] * x[[k, j]];
337                }
338                result[[i, j]] = sum;
339            }
340        }
341    }
342
343    Ok(result)
344}
345
346/// Parallel matrix-vector multiplication with explicit workers parameter
347#[allow(dead_code)]
348pub fn parallel_matvec_with_workers<F>(
349    a: &ArrayView2<F>,
350    x: &ArrayView2<F>,
351    workers: Option<usize>,
352) -> LinalgResult<Array2<F>>
353where
354    F: Float + NumAssign + Sum + Send + Sync,
355{
356    use crate::parallel;
357
358    // Configure workers for parallel operations
359    parallel::configure_workers(workers);
360
361    let config = OptConfig {
362        num_threads: workers,
363        ..OptConfig::default()
364    };
365
366    parallel_matvec(a, x, &config)
367}
368
369/// Memory-efficient in-place matrix operations
370pub mod inplace {
371    use super::*;
372
373    /// In-place matrix addition: A += B
374    pub fn add_assign<F>(a: &mut Array2<F>, b: &ArrayView2<F>) -> LinalgResult<()>
375    where
376        F: Float + NumAssign,
377    {
378        if a.shape() != b.shape() {
379            return Err(LinalgError::DimensionError(format!(
380                "Matrix dimensions don't match: {:?} != {:?}",
381                a.shape(),
382                b.shape()
383            )));
384        }
385
386        for (a_elem, b_elem) in a.iter_mut().zip(b.iter()) {
387            *a_elem += *b_elem;
388        }
389
390        Ok(())
391    }
392
393    /// In-place scalar multiplication: A *= scalar
394    pub fn scalar_mul_assign<F>(a: &mut Array2<F>, scalar: F) -> LinalgResult<()>
395    where
396        F: Float + NumAssign + Send + Sync,
397    {
398        for elem in a.iter_mut() {
399            *elem *= scalar;
400        }
401
402        Ok(())
403    }
404
405    /// In-place transpose for square matrices
406    pub fn transpose_square<F>(a: &mut Array2<F>) -> LinalgResult<()>
407    where
408        F: Float,
409    {
410        let n = a.nrows();
411        if n != a.ncols() {
412            return Err(LinalgError::DimensionError(
413                "In-place transpose requires square matrix".to_string(),
414            ));
415        }
416
417        for i in 0..n {
418            for j in (i + 1)..n {
419                let temp = a[[i, j]];
420                a[[i, j]] = a[[j, i]];
421                a[[j, i]] = temp;
422            }
423        }
424
425        Ok(())
426    }
427}
428
429/// Adaptive algorithm selection based on matrix properties
430#[allow(dead_code)]
431pub fn adaptive_matmul<F>(a: &ArrayView2<F>, b: &ArrayView2<F>) -> LinalgResult<Array2<F>>
432where
433    F: Float + NumAssign + Sum + Send + Sync + scirs2_core::ndarray::ScalarOperand + 'static,
434{
435    adaptive_matmul_with_workers(a, b, None)
436}
437
438/// Adaptive matrix multiplication with explicit workers parameter
439#[allow(dead_code)]
440pub fn adaptive_matmul_with_workers<F>(
441    a: &ArrayView2<F>,
442    b: &ArrayView2<F>,
443    workers: Option<usize>,
444) -> LinalgResult<Array2<F>>
445where
446    F: Float + NumAssign + Sum + Send + Sync + scirs2_core::ndarray::ScalarOperand + 'static,
447{
448    use crate::parallel;
449
450    // Configure workers for parallel operations
451    parallel::configure_workers(workers);
452
453    let size = a.nrows() * a.ncols() + b.nrows() * b.ncols();
454
455    // Choose algorithm based on matrix size
456    if size < 10000 {
457        // Small matrices: use standard ndarray multiplication
458        Ok(a.dot(b))
459    } else if size < 1000000 {
460        // Medium matrices: use blocked algorithm
461        let config = OptConfig {
462            num_threads: workers,
463            ..OptConfig::default()
464        };
465        blocked_matmul(a, b, &config)
466    } else {
467        // Large matrices: use parallel blocked algorithm
468        let config = OptConfig {
469            parallel_threshold: 50000,
470            num_threads: workers,
471            ..OptConfig::default()
472        };
473        blocked_matmul(a, b, &config)
474    }
475}
476
477/// Convenience function for in-place matrix addition
478#[allow(dead_code)]
479pub fn inplace_add<F>(
480    a: &mut scirs2_core::ndarray::ArrayViewMut2<F>,
481    b: &ArrayView2<F>,
482) -> LinalgResult<()>
483where
484    F: Float + NumAssign + Send + Sync,
485{
486    let mut a_owned = a.to_owned();
487    inplace::add_assign(&mut a_owned, b)?;
488    a.assign(&a_owned);
489    Ok(())
490}
491
492/// Convenience function for in-place scalar multiplication
493#[allow(dead_code)]
494pub fn inplace_scale<F>(
495    a: &mut scirs2_core::ndarray::ArrayViewMut2<F>,
496    scalar: F,
497) -> LinalgResult<()>
498where
499    F: Float + NumAssign + Send + Sync,
500{
501    for elem in a.iter_mut() {
502        *elem *= scalar;
503    }
504    Ok(())
505}
506
507/// Simple benchmarking utility for matrix multiplication
508#[allow(dead_code)]
509pub fn matmul_benchmark<F>(
510    a: &ArrayView2<F>,
511    b: &ArrayView2<F>,
512    config: &OptConfig,
513) -> LinalgResult<String>
514where
515    F: Float + NumAssign + Sum + Send + Sync + scirs2_core::ndarray::ScalarOperand + 'static,
516{
517    use std::time::Instant;
518
519    // Benchmark standard multiplication
520    let start = Instant::now();
521    let _c1 = a.dot(b);
522    let time_standard = start.elapsed();
523
524    // Benchmark optimized multiplication
525    let start = Instant::now();
526    let _c2 = blocked_matmul(a, b, config)?;
527    let time_optimized = start.elapsed();
528
529    Ok(format!(
530        "Standard: {:?}, Optimized: {:?}, Speedup: {:.2}x",
531        time_standard,
532        time_optimized,
533        time_standard.as_secs_f64() / time_optimized.as_secs_f64()
534    ))
535}
536
537/// Memory-optimized decomposition algorithms
538/// These implementations minimize memory allocations and reuse workspace arrays
539pub mod decomposition_opt {
540    use super::*;
541    use scirs2_core::ndarray::{s, Array1, Array2, ArrayView2};
542
543    /// Workspace for QR decomposition to avoid repeated allocations
544    pub struct QRWorkspace<F: Float> {
545        /// Tau array for Householder reflectors
546        pub tau: Array1<F>,
547        /// Work array for computations
548        pub work: Array1<F>,
549        /// Temporary array for matrix operations
550        pub tempmatrix: Array2<F>,
551    }
552
553    impl<F: Float> QRWorkspace<F> {
554        /// Create a new workspace for matrices up to the given size
555        pub fn new(_max_rows: usize, maxcols: usize) -> Self {
556            let min_dim = _max_rows.min(maxcols);
557            Self {
558                tau: Array1::zeros(min_dim),
559                work: Array1::zeros(maxcols * 64), // 64 is a reasonable work size multiplier
560                tempmatrix: Array2::zeros((_max_rows, maxcols)),
561            }
562        }
563
564        /// Resize workspace if needed for the given matrix dimensions
565        pub fn resize_if_needed(&mut self, rows: usize, cols: usize) {
566            let min_dim = rows.min(cols);
567
568            if self.tau.len() < min_dim {
569                self.tau = Array1::zeros(min_dim);
570            }
571
572            let worksize = cols * 64;
573            if self.work.len() < worksize {
574                self.work = Array1::zeros(worksize);
575            }
576
577            if self.tempmatrix.nrows() < rows || self.tempmatrix.ncols() < cols {
578                self.tempmatrix = Array2::zeros((rows, cols));
579            }
580        }
581    }
582
583    /// Memory-optimized QR decomposition using workspace arrays
584    /// This reduces allocations for repeated QR decompositions
585    pub fn qr_with_workspace<F>(
586        a: &ArrayView2<F>,
587        workspace: &mut QRWorkspace<F>,
588    ) -> LinalgResult<(Array2<F>, Array2<F>)>
589    where
590        F: Float + NumAssign + Sum + Clone,
591    {
592        let (m, n) = a.dim();
593        workspace.resize_if_needed(m, n);
594
595        // Copy input matrix to temporary workspace to avoid modifying original
596        let mut a_work = workspace.tempmatrix.slice_mut(s![..m, ..n]);
597        a_work.assign(a);
598
599        // Perform in-place QR factorization
600        let min_dim = m.min(n);
601
602        // Simple Householder QR (educational implementation)
603        // In production, this would call optimized LAPACK routines
604        for j in 0..min_dim {
605            let column = a_work.column(j);
606            let norm = column
607                .slice(s![j..])
608                .fold(F::zero(), |acc, &x| acc + x * x)
609                .sqrt();
610
611            if norm > F::epsilon() {
612                // Update workspace tau
613                workspace.tau[j] = norm;
614
615                // Apply Householder reflection
616                let alpha = column[j];
617                let sign = if alpha >= F::zero() {
618                    F::one()
619                } else {
620                    -F::one()
621                };
622                let u1 = alpha + sign * norm;
623
624                // Normalize Householder vector
625                if u1.abs() > F::epsilon() {
626                    let scale = F::one() / u1;
627                    for i in (j + 1)..m {
628                        a_work[[i, j]] *= scale;
629                    }
630                }
631            }
632        }
633
634        // Extract Q and R matrices
635        let q = Array2::eye(m);
636        let mut r = Array2::zeros((m, n));
637
638        // Copy upper triangular part to R
639        for i in 0..m {
640            for j in i..n {
641                if i < min_dim && j < n {
642                    r[[i, j]] = a_work[[i, j]];
643                }
644            }
645        }
646
647        Ok((q, r))
648    }
649
650    /// Memory pool for temporary arrays in decomposition algorithms
651    pub struct DecompositionMemoryPool<F: Float> {
652        /// Pool of reusable arrays of different sizes
653        pub arrays: Vec<Array2<F>>,
654        /// Pool of reusable vectors
655        pub vectors: Vec<Array1<F>>,
656        /// Maximum number of arrays to keep in pool
657        pub max_poolsize: usize,
658    }
659
660    impl<F: Float> DecompositionMemoryPool<F> {
661        /// Create a new memory pool
662        pub fn new(_max_poolsize: usize) -> Self {
663            Self {
664                arrays: Vec::new(),
665                vectors: Vec::new(),
666                max_poolsize: _max_poolsize,
667            }
668        }
669
670        /// Get a temporary array of the specified size, reusing from pool if available
671        pub fn getarray(&mut self, rows: usize, cols: usize) -> Array2<F> {
672            // Try to find a suitable array in the pool
673            for (i, array) in self.arrays.iter().enumerate() {
674                if array.nrows() >= rows && array.ncols() >= cols {
675                    let mut result = self.arrays.swap_remove(i);
676                    // Resize to exact dimensions needed
677                    result = result.slice(s![..rows, ..cols]).to_owned();
678                    result.fill(F::zero()); // Clear the array
679                    return result;
680                }
681            }
682
683            // No suitable array found, create new one
684            Array2::zeros((rows, cols))
685        }
686
687        /// Return an array to the pool for reuse
688        pub fn returnarray(&mut self, array: Array2<F>) {
689            if self.arrays.len() < self.max_poolsize {
690                self.arrays.push(array);
691            }
692        }
693
694        /// Get a temporary vector of the specified size
695        pub fn get_vector(&mut self, len: usize) -> Array1<F> {
696            // Try to find a suitable vector in the pool
697            for (i, vector) in self.vectors.iter().enumerate() {
698                if vector.len() >= len {
699                    let mut result = self.vectors.swap_remove(i);
700                    result = result.slice(s![..len]).to_owned();
701                    result.fill(F::zero()); // Clear the vector
702                    return result;
703                }
704            }
705
706            // No suitable vector found, create new one
707            Array1::zeros(len)
708        }
709
710        /// Return a vector to the pool for reuse
711        pub fn return_vector(&mut self, vector: Array1<F>) {
712            if self.vectors.len() < self.max_poolsize {
713                self.vectors.push(vector);
714            }
715        }
716
717        /// Clear the memory pool
718        pub fn clear(&mut self) {
719            self.arrays.clear();
720            self.vectors.clear();
721        }
722    }
723
724    /// Cache-friendly Householder QR decomposition
725    /// Uses blocked algorithms for better memory access patterns
726    pub fn blocked_qr<F>(
727        a: &ArrayView2<F>,
728        blocksize: usize,
729    ) -> LinalgResult<(Array2<F>, Array2<F>)>
730    where
731        F: Float + NumAssign + Sum + Clone,
732    {
733        let (m, n) = a.dim();
734        let mut a_copy = a.to_owned();
735        let q = Array2::eye(m);
736
737        let min_dim = m.min(n);
738
739        // Process matrix in blocks for better cache locality
740        for start_col in (0..min_dim).step_by(blocksize) {
741            let end_col = (start_col + blocksize).min(min_dim);
742            let _panel_width = end_col - start_col;
743
744            // Apply Householder transformations to current panel
745            for j in start_col..end_col {
746                // Compute Householder vector for column j
747                let col_norm = a_copy
748                    .slice(s![j.., j])
749                    .fold(F::zero(), |acc, &x| acc + x * x)
750                    .sqrt();
751
752                if col_norm > F::epsilon() {
753                    let alpha = a_copy[[j, j]];
754                    let sign = if alpha >= F::zero() {
755                        F::one()
756                    } else {
757                        -F::one()
758                    };
759                    let beta = alpha + sign * col_norm;
760
761                    if beta.abs() > F::epsilon() {
762                        // Normalize Householder vector
763                        let scale = F::one() / beta;
764                        for i in (j + 1)..m {
765                            a_copy[[i, j]] *= scale;
766                        }
767
768                        // Update R diagonal element
769                        a_copy[[j, j]] = -sign * col_norm;
770
771                        // Apply Householder transformation to remaining columns
772                        for k in (j + 1)..n {
773                            let mut dot_product = a_copy[[j, k]];
774                            for i in (j + 1)..m {
775                                dot_product += a_copy[[i, j]] * a_copy[[i, k]];
776                            }
777
778                            let tau = dot_product * F::from(2.0).expect("Operation failed");
779                            a_copy[[j, k]] -= tau;
780                            for i in (j + 1)..m {
781                                let householder_val = a_copy[[i, j]];
782                                a_copy[[i, k]] -= tau * householder_val;
783                            }
784                        }
785                    }
786                }
787            }
788        }
789
790        // Extract R matrix (upper triangular)
791        let mut r = Array2::zeros((m, n));
792        for i in 0..m {
793            for j in i..n {
794                if i < min_dim {
795                    r[[i, j]] = a_copy[[i, j]];
796                }
797            }
798        }
799
800        Ok((q, r))
801    }
802}
803
804#[cfg(test)]
805mod tests {
806    use super::*;
807    use approx::assert_relative_eq;
808    use scirs2_core::ndarray::array;
809
810    #[test]
811    fn test_blocked_matmul() {
812        let a = array![[1.0, 2.0], [3.0, 4.0]];
813        let b = array![[5.0, 6.0], [7.0, 8.0]];
814
815        let config = OptConfig {
816            blocksize: 1,
817            parallel_threshold: 1000,
818            num_threads: None,
819            algorithm: OptAlgorithm::Blocked,
820        };
821
822        let c = blocked_matmul(&a.view(), &b.view(), &config).expect("Operation failed");
823
824        assert_relative_eq!(c[[0, 0]], 19.0);
825        assert_relative_eq!(c[[0, 1]], 22.0);
826        assert_relative_eq!(c[[1, 0]], 43.0);
827        assert_relative_eq!(c[[1, 1]], 50.0);
828    }
829
830    #[test]
831    fn test_optimized_transpose() {
832        let a = array![[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]];
833        let a_t = optimized_transpose(&a.view()).expect("Operation failed");
834
835        assert_eq!(a_t.shape(), &[3, 2]);
836        assert_relative_eq!(a_t[[0, 0]], 1.0);
837        assert_relative_eq!(a_t[[1, 0]], 2.0);
838        assert_relative_eq!(a_t[[2, 0]], 3.0);
839        assert_relative_eq!(a_t[[0, 1]], 4.0);
840        assert_relative_eq!(a_t[[1, 1]], 5.0);
841        assert_relative_eq!(a_t[[2, 1]], 6.0);
842    }
843
844    #[test]
845    fn test_parallel_matvec() {
846        let a = array![[1.0, 2.0], [3.0, 4.0]];
847        let x = array![[5.0], [6.0]];
848
849        let config = OptConfig::default();
850        let y = parallel_matvec(&a.view(), &x.view(), &config).expect("Operation failed");
851
852        assert_relative_eq!(y[[0, 0]], 17.0);
853        assert_relative_eq!(y[[1, 0]], 39.0);
854    }
855
856    #[test]
857    fn test_inplace_operations() {
858        let mut a = array![[1.0, 2.0], [3.0, 4.0]];
859        let b = array![[5.0, 6.0], [7.0, 8.0]];
860
861        inplace::add_assign(&mut a, &b.view()).expect("Operation failed");
862
863        assert_relative_eq!(a[[0, 0]], 6.0);
864        assert_relative_eq!(a[[0, 1]], 8.0);
865        assert_relative_eq!(a[[1, 0]], 10.0);
866        assert_relative_eq!(a[[1, 1]], 12.0);
867
868        inplace::scalar_mul_assign(&mut a, 2.0).expect("Operation failed");
869
870        assert_relative_eq!(a[[0, 0]], 12.0);
871        assert_relative_eq!(a[[0, 1]], 16.0);
872        assert_relative_eq!(a[[1, 0]], 20.0);
873        assert_relative_eq!(a[[1, 1]], 24.0);
874    }
875
876    #[test]
877    fn test_inplace_transpose() {
878        let mut a = array![[1.0, 2.0], [3.0, 4.0]];
879
880        inplace::transpose_square(&mut a).expect("Operation failed");
881
882        assert_relative_eq!(a[[0, 0]], 1.0);
883        assert_relative_eq!(a[[0, 1]], 3.0);
884        assert_relative_eq!(a[[1, 0]], 2.0);
885        assert_relative_eq!(a[[1, 1]], 4.0);
886    }
887
888    #[test]
889    fn test_adaptive_matmul() {
890        let a = array![[1.0, 2.0], [3.0, 4.0]];
891        let b = array![[5.0, 6.0], [7.0, 8.0]];
892
893        let c = adaptive_matmul(&a.view(), &b.view()).expect("Operation failed");
894
895        assert_relative_eq!(c[[0, 0]], 19.0);
896        assert_relative_eq!(c[[0, 1]], 22.0);
897        assert_relative_eq!(c[[1, 0]], 43.0);
898        assert_relative_eq!(c[[1, 1]], 50.0);
899    }
900
901    #[test]
902    fn test_largematrix_blocked() {
903        // Test with larger matrix to verify blocking works correctly
904        let n = 100;
905        let a = Array2::from_shape_fn((n, n), |(i, j)| (i + j) as f64);
906        let b = Array2::eye(n);
907
908        let config = OptConfig {
909            blocksize: 16,
910            parallel_threshold: 10000,
911            num_threads: None,
912            algorithm: OptAlgorithm::Blocked,
913        };
914
915        let c = blocked_matmul(&a.view(), &b.view(), &config).expect("Operation failed");
916
917        // Multiplying by identity should give original matrix
918        for i in 0..n {
919            for j in 0..n {
920                assert_relative_eq!(c[[i, j]], a[[i, j]], epsilon = 1e-10);
921            }
922        }
923    }
924}