Skip to main content

oxiz_math/
blas_ops.rs

1//! BLAS abstraction layer for large-scale linear programming.
2//!
3//! This module provides a trait-based abstraction for matrix operations commonly
4//! used in linear programming solvers. It includes:
5//!
6//! - A `BlasOps` trait defining core matrix operations (gemm, gemv, trsm)
7//! - A pure Rust implementation (`RustBlas`) for correctness with exact arithmetic
8//! - Configurable thresholds for choosing between inline and BLAS operations
9//! - Feature-flagged support for external BLAS libraries (placeholder for future)
10//!
11//! # Example
12//!
13//! ```
14//! use oxiz_math::blas_ops::{BlasOps, RustBlas, BlasConfig};
15//! use oxiz_math::matrix::Matrix;
16//! use num_rational::Rational64;
17//!
18//! // Create matrices
19//! let a = Matrix::from_rows(vec![
20//!     vec![Rational64::from(1), Rational64::from(2)],
21//!     vec![Rational64::from(3), Rational64::from(4)],
22//! ]);
23//! let b = Matrix::from_rows(vec![
24//!     vec![Rational64::from(5), Rational64::from(6)],
25//!     vec![Rational64::from(7), Rational64::from(8)],
26//! ]);
27//!
28//! // Use BLAS operations
29//! let blas = RustBlas::new();
30//! let c = blas.gemm(&a, &b);
31//! ```
32
33use crate::matrix::Matrix;
34#[allow(unused_imports)]
35use crate::prelude::*;
36use num_rational::Rational64;
37use num_traits::{One, Zero};
38
39/// Configuration for BLAS operation thresholds.
40///
41/// Controls when to use optimized BLAS routines vs. inline operations.
42/// For small matrices, inline operations may be faster due to reduced overhead.
43#[derive(Debug, Clone)]
44pub struct BlasConfig {
45    /// Threshold for matrix-matrix multiplication (gemm).
46    /// Use BLAS when m * n * k > this threshold.
47    pub gemm_threshold: usize,
48
49    /// Threshold for matrix-vector multiplication (gemv).
50    /// Use BLAS when m * n > this threshold.
51    pub gemv_threshold: usize,
52
53    /// Threshold for triangular solve (trsm).
54    /// Use BLAS when n * nrhs > this threshold.
55    pub trsm_threshold: usize,
56
57    /// Block size for blocked operations.
58    pub block_size: usize,
59}
60
61impl Default for BlasConfig {
62    fn default() -> Self {
63        Self {
64            gemm_threshold: 64 * 64 * 64, // 262144
65            gemv_threshold: 256 * 256,    // 65536
66            trsm_threshold: 128 * 128,    // 16384
67            block_size: 64,
68        }
69    }
70}
71
72impl BlasConfig {
73    /// Creates a new configuration with custom thresholds.
74    pub fn new(gemm_threshold: usize, gemv_threshold: usize, trsm_threshold: usize) -> Self {
75        Self {
76            gemm_threshold,
77            gemv_threshold,
78            trsm_threshold,
79            block_size: 64,
80        }
81    }
82
83    /// Configuration optimized for small matrices (always use inline).
84    pub fn small_matrix() -> Self {
85        Self {
86            gemm_threshold: usize::MAX,
87            gemv_threshold: usize::MAX,
88            trsm_threshold: usize::MAX,
89            block_size: 32,
90        }
91    }
92
93    /// Configuration optimized for large matrices (always use BLAS when available).
94    pub fn large_matrix() -> Self {
95        Self {
96            gemm_threshold: 0,
97            gemv_threshold: 0,
98            trsm_threshold: 0,
99            block_size: 128,
100        }
101    }
102}
103
104/// Transpose option for BLAS operations.
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub enum Transpose {
107    /// No transpose.
108    NoTrans,
109    /// Transpose the matrix.
110    Trans,
111}
112
113/// Side option for triangular solve.
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum Side {
116    /// Solve AX = B (A is on the left).
117    Left,
118    /// Solve XA = B (A is on the right).
119    Right,
120}
121
122/// Upper/Lower triangular indicator.
123#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub enum UpLo {
125    /// Upper triangular matrix.
126    Upper,
127    /// Lower triangular matrix.
128    Lower,
129}
130
131/// Diagonal type indicator.
132#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133pub enum Diag {
134    /// Non-unit diagonal (use actual diagonal elements).
135    NonUnit,
136    /// Unit diagonal (diagonal elements are assumed to be 1).
137    Unit,
138}
139
140/// Trait for BLAS-like operations on rational matrices.
141///
142/// This trait abstracts matrix operations commonly needed in linear programming:
143/// - `gemm`: General matrix-matrix multiplication (C = alpha * A * B + beta * C)
144/// - `gemv`: General matrix-vector multiplication (y = alpha * A * x + beta * y)
145/// - `trsm`: Triangular solve with multiple right-hand sides
146///
147/// Implementations maintain exact rational arithmetic for correctness in LP solvers.
148pub trait BlasOps {
149    /// General matrix-matrix multiplication.
150    ///
151    /// Computes C = alpha * op(A) * op(B) + beta * C
152    ///
153    /// # Arguments
154    /// * `trans_a` - Whether to transpose A
155    /// * `trans_b` - Whether to transpose B
156    /// * `alpha` - Scalar multiplier for A*B
157    /// * `a` - Matrix A
158    /// * `b` - Matrix B
159    /// * `beta` - Scalar multiplier for C
160    /// * `c` - Output matrix C (modified in place)
161    #[allow(clippy::too_many_arguments)]
162    fn gemm_into(
163        &self,
164        trans_a: Transpose,
165        trans_b: Transpose,
166        alpha: &Rational64,
167        a: &Matrix,
168        b: &Matrix,
169        beta: &Rational64,
170        c: &mut Matrix,
171    );
172
173    /// General matrix-matrix multiplication (convenience method).
174    ///
175    /// Computes C = A * B
176    fn gemm(&self, a: &Matrix, b: &Matrix) -> Matrix {
177        let m = a.nrows();
178        let n = b.ncols();
179        let mut c = Matrix::zeros(m, n);
180        self.gemm_into(
181            Transpose::NoTrans,
182            Transpose::NoTrans,
183            &Rational64::from(1),
184            a,
185            b,
186            &Rational64::zero(),
187            &mut c,
188        );
189        c
190    }
191
192    /// General matrix-vector multiplication.
193    ///
194    /// Computes y = alpha * op(A) * x + beta * y
195    ///
196    /// # Arguments
197    /// * `trans` - Whether to transpose A
198    /// * `alpha` - Scalar multiplier for A*x
199    /// * `a` - Matrix A
200    /// * `x` - Input vector x
201    /// * `beta` - Scalar multiplier for y
202    /// * `y` - Output vector y (modified in place)
203    fn gemv_into(
204        &self,
205        trans: Transpose,
206        alpha: &Rational64,
207        a: &Matrix,
208        x: &[Rational64],
209        beta: &Rational64,
210        y: &mut [Rational64],
211    );
212
213    /// General matrix-vector multiplication (convenience method).
214    ///
215    /// Computes y = A * x
216    fn gemv(&self, a: &Matrix, x: &[Rational64]) -> Vec<Rational64> {
217        let m = a.nrows();
218        let mut y = vec![Rational64::zero(); m];
219        self.gemv_into(
220            Transpose::NoTrans,
221            &Rational64::from(1),
222            a,
223            x,
224            &Rational64::zero(),
225            &mut y,
226        );
227        y
228    }
229
230    /// Triangular solve with multiple right-hand sides.
231    ///
232    /// Solves op(A) * X = alpha * B (Side::Left) or X * op(A) = alpha * B (Side::Right)
233    /// where A is triangular.
234    ///
235    /// # Arguments
236    /// * `side` - Whether A is on the left or right
237    /// * `uplo` - Whether A is upper or lower triangular
238    /// * `trans` - Whether to transpose A
239    /// * `diag` - Whether A has unit diagonal
240    /// * `alpha` - Scalar multiplier
241    /// * `a` - Triangular matrix A
242    /// * `b` - Right-hand side B (modified in place to contain solution X)
243    #[allow(clippy::too_many_arguments)]
244    fn trsm(
245        &self,
246        side: Side,
247        uplo: UpLo,
248        trans: Transpose,
249        diag: Diag,
250        alpha: &Rational64,
251        a: &Matrix,
252        b: &mut Matrix,
253    );
254
255    /// Triangular solve for a single vector.
256    ///
257    /// Solves op(A) * x = b where A is triangular.
258    ///
259    /// # Arguments
260    /// * `uplo` - Whether A is upper or lower triangular
261    /// * `trans` - Whether to transpose A
262    /// * `diag` - Whether A has unit diagonal
263    /// * `a` - Triangular matrix A
264    /// * `b` - Right-hand side b (modified in place to contain solution x)
265    fn trsv(&self, uplo: UpLo, trans: Transpose, diag: Diag, a: &Matrix, b: &mut [Rational64]);
266
267    /// Returns the configuration for this BLAS implementation.
268    fn config(&self) -> &BlasConfig;
269
270    /// Checks if BLAS should be used for gemm based on matrix dimensions.
271    fn should_use_blas_gemm(&self, m: usize, n: usize, k: usize) -> bool {
272        m * n * k >= self.config().gemm_threshold
273    }
274
275    /// Checks if BLAS should be used for gemv based on matrix dimensions.
276    fn should_use_blas_gemv(&self, m: usize, n: usize) -> bool {
277        m * n >= self.config().gemv_threshold
278    }
279
280    /// Checks if BLAS should be used for trsm based on matrix dimensions.
281    fn should_use_blas_trsm(&self, n: usize, nrhs: usize) -> bool {
282        n * nrhs >= self.config().trsm_threshold
283    }
284}
285
286/// Pure Rust implementation of BLAS operations.
287///
288/// This implementation provides exact rational arithmetic operations,
289/// ensuring correctness for linear programming solvers that require
290/// exact solutions.
291#[derive(Debug, Clone)]
292pub struct RustBlas {
293    config: BlasConfig,
294}
295
296impl Default for RustBlas {
297    fn default() -> Self {
298        Self::new()
299    }
300}
301
302impl RustBlas {
303    /// Creates a new RustBlas instance with default configuration.
304    pub fn new() -> Self {
305        Self {
306            config: BlasConfig::default(),
307        }
308    }
309
310    /// Creates a new RustBlas instance with custom configuration.
311    pub fn with_config(config: BlasConfig) -> Self {
312        Self { config }
313    }
314
315    /// Internal helper for matrix dimensions after transpose.
316    fn get_dims(m: &Matrix, trans: Transpose) -> (usize, usize) {
317        match trans {
318            Transpose::NoTrans => (m.nrows(), m.ncols()),
319            Transpose::Trans => (m.ncols(), m.nrows()),
320        }
321    }
322
323    /// Internal helper for accessing matrix element with transpose.
324    fn get_elem(m: &Matrix, i: usize, j: usize, trans: Transpose) -> Rational64 {
325        match trans {
326            Transpose::NoTrans => m.get(i, j),
327            Transpose::Trans => m.get(j, i),
328        }
329    }
330}
331
332#[allow(clippy::needless_range_loop)]
333impl BlasOps for RustBlas {
334    fn gemm_into(
335        &self,
336        trans_a: Transpose,
337        trans_b: Transpose,
338        alpha: &Rational64,
339        a: &Matrix,
340        b: &Matrix,
341        beta: &Rational64,
342        c: &mut Matrix,
343    ) {
344        let (m, k_a) = Self::get_dims(a, trans_a);
345        let (k_b, n) = Self::get_dims(b, trans_b);
346
347        assert_eq!(
348            k_a, k_b,
349            "Inner dimensions must match for matrix multiplication"
350        );
351        assert_eq!(c.nrows(), m, "Output matrix row dimension mismatch");
352        assert_eq!(c.ncols(), n, "Output matrix column dimension mismatch");
353
354        let k = k_a;
355
356        // C = alpha * op(A) * op(B) + beta * C
357        for i in 0..m {
358            for j in 0..n {
359                let mut sum = Rational64::zero();
360                for l in 0..k {
361                    let a_il = Self::get_elem(a, i, l, trans_a);
362                    let b_lj = Self::get_elem(b, l, j, trans_b);
363                    sum += a_il * b_lj;
364                }
365                let c_ij = c.get(i, j);
366                c.set(i, j, alpha * sum + beta * c_ij);
367            }
368        }
369    }
370
371    fn gemv_into(
372        &self,
373        trans: Transpose,
374        alpha: &Rational64,
375        a: &Matrix,
376        x: &[Rational64],
377        beta: &Rational64,
378        y: &mut [Rational64],
379    ) {
380        let (m, n) = Self::get_dims(a, trans);
381
382        assert_eq!(x.len(), n, "Vector x dimension must match matrix columns");
383        assert_eq!(y.len(), m, "Vector y dimension must match matrix rows");
384
385        // y = alpha * op(A) * x + beta * y
386        for i in 0..m {
387            let mut sum = Rational64::zero();
388            for j in 0..n {
389                let a_ij = Self::get_elem(a, i, j, trans);
390                sum += a_ij * x[j];
391            }
392            y[i] = alpha * sum + beta * y[i];
393        }
394    }
395
396    fn trsm(
397        &self,
398        side: Side,
399        uplo: UpLo,
400        trans: Transpose,
401        diag: Diag,
402        alpha: &Rational64,
403        a: &Matrix,
404        b: &mut Matrix,
405    ) {
406        let n = a.nrows();
407        assert_eq!(a.nrows(), a.ncols(), "Triangular matrix must be square");
408
409        match side {
410            Side::Left => {
411                assert_eq!(b.nrows(), n, "B row dimension must match A");
412                self.trsm_left(uplo, trans, diag, alpha, a, b);
413            }
414            Side::Right => {
415                assert_eq!(b.ncols(), n, "B column dimension must match A");
416                self.trsm_right(uplo, trans, diag, alpha, a, b);
417            }
418        }
419    }
420
421    fn trsv(&self, uplo: UpLo, trans: Transpose, diag: Diag, a: &Matrix, b: &mut [Rational64]) {
422        let n = a.nrows();
423        assert_eq!(a.nrows(), a.ncols(), "Triangular matrix must be square");
424        assert_eq!(b.len(), n, "Vector dimension must match matrix");
425
426        match (uplo, trans) {
427            (UpLo::Lower, Transpose::NoTrans) | (UpLo::Upper, Transpose::Trans) => {
428                // Forward substitution
429                for i in 0..n {
430                    let mut sum = b[i];
431                    for j in 0..i {
432                        let a_ij = Self::get_elem(a, i, j, trans);
433                        sum -= a_ij * b[j];
434                    }
435                    if diag == Diag::NonUnit {
436                        let a_ii = Self::get_elem(a, i, i, trans);
437                        b[i] = sum / a_ii;
438                    } else {
439                        b[i] = sum;
440                    }
441                }
442            }
443            (UpLo::Upper, Transpose::NoTrans) | (UpLo::Lower, Transpose::Trans) => {
444                // Backward substitution
445                for i in (0..n).rev() {
446                    let mut sum = b[i];
447                    for j in (i + 1)..n {
448                        let a_ij = Self::get_elem(a, i, j, trans);
449                        sum -= a_ij * b[j];
450                    }
451                    if diag == Diag::NonUnit {
452                        let a_ii = Self::get_elem(a, i, i, trans);
453                        b[i] = sum / a_ii;
454                    } else {
455                        b[i] = sum;
456                    }
457                }
458            }
459        }
460    }
461
462    fn config(&self) -> &BlasConfig {
463        &self.config
464    }
465}
466
467impl RustBlas {
468    /// Triangular solve with B on the left: op(A) * X = alpha * B
469    fn trsm_left(
470        &self,
471        uplo: UpLo,
472        trans: Transpose,
473        diag: Diag,
474        alpha: &Rational64,
475        a: &Matrix,
476        b: &mut Matrix,
477    ) {
478        let n = a.nrows();
479        let nrhs = b.ncols();
480
481        // Scale B by alpha first
482        if !alpha.is_one() {
483            for i in 0..n {
484                for j in 0..nrhs {
485                    let val = b.get(i, j) * alpha;
486                    b.set(i, j, val);
487                }
488            }
489        }
490
491        // Solve column by column
492        for col in 0..nrhs {
493            match (uplo, trans) {
494                (UpLo::Lower, Transpose::NoTrans) | (UpLo::Upper, Transpose::Trans) => {
495                    // Forward substitution
496                    for i in 0..n {
497                        let mut sum = b.get(i, col);
498                        for j in 0..i {
499                            let a_ij = Self::get_elem(a, i, j, trans);
500                            sum -= a_ij * b.get(j, col);
501                        }
502                        if diag == Diag::NonUnit {
503                            let a_ii = Self::get_elem(a, i, i, trans);
504                            b.set(i, col, sum / a_ii);
505                        } else {
506                            b.set(i, col, sum);
507                        }
508                    }
509                }
510                (UpLo::Upper, Transpose::NoTrans) | (UpLo::Lower, Transpose::Trans) => {
511                    // Backward substitution
512                    for i in (0..n).rev() {
513                        let mut sum = b.get(i, col);
514                        for j in (i + 1)..n {
515                            let a_ij = Self::get_elem(a, i, j, trans);
516                            sum -= a_ij * b.get(j, col);
517                        }
518                        if diag == Diag::NonUnit {
519                            let a_ii = Self::get_elem(a, i, i, trans);
520                            b.set(i, col, sum / a_ii);
521                        } else {
522                            b.set(i, col, sum);
523                        }
524                    }
525                }
526            }
527        }
528    }
529
530    /// Triangular solve with B on the right: X * op(A) = alpha * B
531    fn trsm_right(
532        &self,
533        uplo: UpLo,
534        trans: Transpose,
535        diag: Diag,
536        alpha: &Rational64,
537        a: &Matrix,
538        b: &mut Matrix,
539    ) {
540        let n = a.nrows();
541        let m = b.nrows();
542
543        // Scale B by alpha first
544        if !alpha.is_one() {
545            for i in 0..m {
546                for j in 0..n {
547                    let val = b.get(i, j) * alpha;
548                    b.set(i, j, val);
549                }
550            }
551        }
552
553        // Solve row by row
554        for row in 0..m {
555            match (uplo, trans) {
556                (UpLo::Upper, Transpose::NoTrans) | (UpLo::Lower, Transpose::Trans) => {
557                    // Forward substitution on columns
558                    for j in 0..n {
559                        let mut sum = b.get(row, j);
560                        for k in 0..j {
561                            let a_kj = Self::get_elem(a, k, j, trans);
562                            sum -= b.get(row, k) * a_kj;
563                        }
564                        if diag == Diag::NonUnit {
565                            let a_jj = Self::get_elem(a, j, j, trans);
566                            b.set(row, j, sum / a_jj);
567                        } else {
568                            b.set(row, j, sum);
569                        }
570                    }
571                }
572                (UpLo::Lower, Transpose::NoTrans) | (UpLo::Upper, Transpose::Trans) => {
573                    // Backward substitution on columns
574                    for j in (0..n).rev() {
575                        let mut sum = b.get(row, j);
576                        for k in (j + 1)..n {
577                            let a_kj = Self::get_elem(a, k, j, trans);
578                            sum -= b.get(row, k) * a_kj;
579                        }
580                        if diag == Diag::NonUnit {
581                            let a_jj = Self::get_elem(a, j, j, trans);
582                            b.set(row, j, sum / a_jj);
583                        } else {
584                            b.set(row, j, sum);
585                        }
586                    }
587                }
588            }
589        }
590    }
591}
592
593/// Global BLAS instance for convenient access.
594///
595/// This provides a thread-local instance of `RustBlas` for cases where
596/// you don't want to manage the BLAS instance explicitly.
597pub fn get_blas() -> RustBlas {
598    RustBlas::new()
599}
600
601/// Configurable BLAS instance with custom settings.
602pub fn get_blas_with_config(config: BlasConfig) -> RustBlas {
603    RustBlas::with_config(config)
604}
605
606// Placeholder for feature-gated external BLAS support
607// #[cfg(feature = "external-blas")]
608// pub struct ExternalBlas {
609//     config: BlasConfig,
610// }
611//
612// #[cfg(feature = "external-blas")]
613// impl BlasOps for ExternalBlas {
614//     // Implementation would delegate to external BLAS library
615//     // while converting between Rational64 and f64 for the BLAS calls
616// }
617
618#[cfg(test)]
619mod tests {
620    use super::*;
621
622    fn rat(n: i64) -> Rational64 {
623        Rational64::from(n)
624    }
625
626    #[test]
627    fn test_gemm_basic() {
628        let blas = RustBlas::new();
629
630        let a = Matrix::from_rows(vec![vec![rat(1), rat(2)], vec![rat(3), rat(4)]]);
631        let b = Matrix::from_rows(vec![vec![rat(5), rat(6)], vec![rat(7), rat(8)]]);
632
633        let c = blas.gemm(&a, &b);
634
635        // C = A * B
636        // [1 2] * [5 6] = [1*5+2*7  1*6+2*8] = [19 22]
637        // [3 4]   [7 8]   [3*5+4*7  3*6+4*8]   [43 50]
638        assert_eq!(c.get(0, 0), rat(19));
639        assert_eq!(c.get(0, 1), rat(22));
640        assert_eq!(c.get(1, 0), rat(43));
641        assert_eq!(c.get(1, 1), rat(50));
642    }
643
644    #[test]
645    fn test_gemm_with_transpose() {
646        let blas = RustBlas::new();
647
648        let a = Matrix::from_rows(vec![vec![rat(1), rat(3)], vec![rat(2), rat(4)]]);
649        let b = Matrix::from_rows(vec![vec![rat(5), rat(7)], vec![rat(6), rat(8)]]);
650
651        // C = A^T * B^T
652        let mut c = Matrix::zeros(2, 2);
653        blas.gemm_into(
654            Transpose::Trans,
655            Transpose::Trans,
656            &rat(1),
657            &a,
658            &b,
659            &rat(0),
660            &mut c,
661        );
662
663        // A^T = [1 2]    B^T = [5 6]
664        //       [3 4]          [7 8]
665        // Same result as test_gemm_basic
666        assert_eq!(c.get(0, 0), rat(19));
667        assert_eq!(c.get(0, 1), rat(22));
668        assert_eq!(c.get(1, 0), rat(43));
669        assert_eq!(c.get(1, 1), rat(50));
670    }
671
672    #[test]
673    fn test_gemm_with_alpha_beta() {
674        let blas = RustBlas::new();
675
676        let a = Matrix::from_rows(vec![vec![rat(1), rat(2)], vec![rat(3), rat(4)]]);
677        let b = Matrix::from_rows(vec![vec![rat(1), rat(0)], vec![rat(0), rat(1)]]);
678        let mut c = Matrix::from_rows(vec![vec![rat(10), rat(20)], vec![rat(30), rat(40)]]);
679
680        // C = 2 * A * B + 3 * C
681        blas.gemm_into(
682            Transpose::NoTrans,
683            Transpose::NoTrans,
684            &rat(2),
685            &a,
686            &b,
687            &rat(3),
688            &mut c,
689        );
690
691        // A * B = A (identity)
692        // C = 2 * A + 3 * C_old
693        // c[0,0] = 2*1 + 3*10 = 32
694        // c[0,1] = 2*2 + 3*20 = 64
695        // c[1,0] = 2*3 + 3*30 = 96
696        // c[1,1] = 2*4 + 3*40 = 128
697        assert_eq!(c.get(0, 0), rat(32));
698        assert_eq!(c.get(0, 1), rat(64));
699        assert_eq!(c.get(1, 0), rat(96));
700        assert_eq!(c.get(1, 1), rat(128));
701    }
702
703    #[test]
704    fn test_gemv_basic() {
705        let blas = RustBlas::new();
706
707        let a = Matrix::from_rows(vec![vec![rat(1), rat(2)], vec![rat(3), rat(4)]]);
708        let x = vec![rat(5), rat(6)];
709
710        let y = blas.gemv(&a, &x);
711
712        // y = A * x
713        // [1 2] * [5] = [1*5+2*6] = [17]
714        // [3 4]   [6]   [3*5+4*6]   [39]
715        assert_eq!(y[0], rat(17));
716        assert_eq!(y[1], rat(39));
717    }
718
719    #[test]
720    fn test_gemv_with_transpose() {
721        let blas = RustBlas::new();
722
723        let a = Matrix::from_rows(vec![vec![rat(1), rat(3)], vec![rat(2), rat(4)]]);
724        let x = vec![rat(5), rat(6)];
725        let mut y = vec![rat(0), rat(0)];
726
727        // y = A^T * x
728        blas.gemv_into(Transpose::Trans, &rat(1), &a, &x, &rat(0), &mut y);
729
730        // A^T = [1 2]
731        //       [3 4]
732        // Same as test_gemv_basic
733        assert_eq!(y[0], rat(17));
734        assert_eq!(y[1], rat(39));
735    }
736
737    #[test]
738    fn test_trsv_lower() {
739        let blas = RustBlas::new();
740
741        // Lower triangular matrix
742        let l = Matrix::from_rows(vec![
743            vec![rat(2), rat(0), rat(0)],
744            vec![rat(1), rat(3), rat(0)],
745            vec![rat(1), rat(2), rat(4)],
746        ]);
747
748        // Solve L * x = b where b = [4, 10, 21]
749        // Expected: x = [2, 2.67, 3] approximately, but let's compute exactly
750        // x[0] = 4/2 = 2
751        // x[1] = (10 - 1*2)/3 = 8/3
752        // x[2] = (21 - 1*2 - 2*(8/3))/4 = (21 - 2 - 16/3)/4 = (57/3 - 16/3)/4 = (41/3)/4 = 41/12
753        let mut b = vec![rat(4), rat(10), rat(21)];
754        blas.trsv(UpLo::Lower, Transpose::NoTrans, Diag::NonUnit, &l, &mut b);
755
756        assert_eq!(b[0], rat(2));
757        assert_eq!(b[1], Rational64::new(8, 3));
758        assert_eq!(b[2], Rational64::new(41, 12));
759    }
760
761    #[test]
762    fn test_trsv_upper() {
763        let blas = RustBlas::new();
764
765        // Upper triangular matrix
766        let u = Matrix::from_rows(vec![
767            vec![rat(2), rat(1), rat(1)],
768            vec![rat(0), rat(3), rat(2)],
769            vec![rat(0), rat(0), rat(4)],
770        ]);
771
772        // Solve U * x = b where b = [8, 14, 12]
773        // x[2] = 12/4 = 3
774        // x[1] = (14 - 2*3)/3 = 8/3
775        // x[0] = (8 - 1*(8/3) - 1*3)/2 = (8 - 8/3 - 3)/2 = (24/3 - 8/3 - 9/3)/2 = (7/3)/2 = 7/6
776        let mut b = vec![rat(8), rat(14), rat(12)];
777        blas.trsv(UpLo::Upper, Transpose::NoTrans, Diag::NonUnit, &u, &mut b);
778
779        assert_eq!(b[2], rat(3));
780        assert_eq!(b[1], Rational64::new(8, 3));
781        assert_eq!(b[0], Rational64::new(7, 6));
782    }
783
784    #[test]
785    fn test_trsv_unit_diagonal() {
786        let blas = RustBlas::new();
787
788        // Lower triangular with unit diagonal (diagonal values ignored)
789        let l = Matrix::from_rows(vec![
790            vec![rat(999), rat(0)], // 999 should be ignored
791            vec![rat(2), rat(888)], // 888 should be ignored
792        ]);
793
794        // Solve L * x = b where L has unit diagonal
795        // x[0] = b[0] = 4
796        // x[1] = b[1] - 2*x[0] = 10 - 8 = 2
797        let mut b = vec![rat(4), rat(10)];
798        blas.trsv(UpLo::Lower, Transpose::NoTrans, Diag::Unit, &l, &mut b);
799
800        assert_eq!(b[0], rat(4));
801        assert_eq!(b[1], rat(2));
802    }
803
804    #[test]
805    fn test_trsm_left_lower() {
806        let blas = RustBlas::new();
807
808        // Lower triangular matrix
809        let l = Matrix::from_rows(vec![vec![rat(2), rat(0)], vec![rat(1), rat(3)]]);
810
811        // Solve L * X = B
812        let mut b = Matrix::from_rows(vec![vec![rat(4), rat(6)], vec![rat(7), rat(12)]]);
813
814        blas.trsm(
815            Side::Left,
816            UpLo::Lower,
817            Transpose::NoTrans,
818            Diag::NonUnit,
819            &rat(1),
820            &l,
821            &mut b,
822        );
823
824        // Column 0: L * x = [4, 7]^T
825        // x[0] = 4/2 = 2
826        // x[1] = (7 - 1*2)/3 = 5/3
827        assert_eq!(b.get(0, 0), rat(2));
828        assert_eq!(b.get(1, 0), Rational64::new(5, 3));
829
830        // Column 1: L * x = [6, 12]^T
831        // x[0] = 6/2 = 3
832        // x[1] = (12 - 1*3)/3 = 3
833        assert_eq!(b.get(0, 1), rat(3));
834        assert_eq!(b.get(1, 1), rat(3));
835    }
836
837    #[test]
838    fn test_config_thresholds() {
839        let config = BlasConfig::new(100, 50, 25);
840        let blas = RustBlas::with_config(config);
841
842        // Test threshold checks
843        assert!(!blas.should_use_blas_gemm(3, 3, 3)); // 27 < 100
844        assert!(blas.should_use_blas_gemm(5, 5, 5)); // 125 >= 100
845
846        assert!(!blas.should_use_blas_gemv(5, 5)); // 25 < 50
847        assert!(blas.should_use_blas_gemv(8, 8)); // 64 >= 50
848
849        assert!(!blas.should_use_blas_trsm(4, 4)); // 16 < 25
850        assert!(blas.should_use_blas_trsm(5, 5)); // 25 >= 25
851    }
852
853    #[test]
854    fn test_config_presets() {
855        let small = BlasConfig::small_matrix();
856        assert_eq!(small.gemm_threshold, usize::MAX);
857
858        let large = BlasConfig::large_matrix();
859        assert_eq!(large.gemm_threshold, 0);
860    }
861
862    #[test]
863    fn test_identity_multiplication() {
864        let blas = RustBlas::new();
865
866        let a = Matrix::from_rows(vec![
867            vec![rat(1), rat(2), rat(3)],
868            vec![rat(4), rat(5), rat(6)],
869            vec![rat(7), rat(8), rat(9)],
870        ]);
871        let i = Matrix::identity(3);
872
873        // A * I = A
874        let result = blas.gemm(&a, &i);
875        for row in 0..3 {
876            for col in 0..3 {
877                assert_eq!(result.get(row, col), a.get(row, col));
878            }
879        }
880
881        // I * A = A
882        let result2 = blas.gemm(&i, &a);
883        for row in 0..3 {
884            for col in 0..3 {
885                assert_eq!(result2.get(row, col), a.get(row, col));
886            }
887        }
888    }
889
890    #[test]
891    fn test_gemm_non_square() {
892        let blas = RustBlas::new();
893
894        // 2x3 * 3x2 = 2x2
895        let a = Matrix::from_rows(vec![
896            vec![rat(1), rat(2), rat(3)],
897            vec![rat(4), rat(5), rat(6)],
898        ]);
899        let b = Matrix::from_rows(vec![
900            vec![rat(7), rat(8)],
901            vec![rat(9), rat(10)],
902            vec![rat(11), rat(12)],
903        ]);
904
905        let c = blas.gemm(&a, &b);
906
907        assert_eq!(c.nrows(), 2);
908        assert_eq!(c.ncols(), 2);
909
910        // c[0,0] = 1*7 + 2*9 + 3*11 = 7 + 18 + 33 = 58
911        // c[0,1] = 1*8 + 2*10 + 3*12 = 8 + 20 + 36 = 64
912        // c[1,0] = 4*7 + 5*9 + 6*11 = 28 + 45 + 66 = 139
913        // c[1,1] = 4*8 + 5*10 + 6*12 = 32 + 50 + 72 = 154
914        assert_eq!(c.get(0, 0), rat(58));
915        assert_eq!(c.get(0, 1), rat(64));
916        assert_eq!(c.get(1, 0), rat(139));
917        assert_eq!(c.get(1, 1), rat(154));
918    }
919
920    #[test]
921    fn test_get_blas_functions() {
922        let blas1 = get_blas();
923        assert_eq!(
924            blas1.config().gemm_threshold,
925            BlasConfig::default().gemm_threshold
926        );
927
928        let config = BlasConfig::small_matrix();
929        let blas2 = get_blas_with_config(config);
930        assert_eq!(blas2.config().gemm_threshold, usize::MAX);
931    }
932}