1use crate::matrix::Matrix;
34#[allow(unused_imports)]
35use crate::prelude::*;
36use num_rational::Rational64;
37use num_traits::{One, Zero};
38
39#[derive(Debug, Clone)]
44pub struct BlasConfig {
45 pub gemm_threshold: usize,
48
49 pub gemv_threshold: usize,
52
53 pub trsm_threshold: usize,
56
57 pub block_size: usize,
59}
60
61impl Default for BlasConfig {
62 fn default() -> Self {
63 Self {
64 gemm_threshold: 64 * 64 * 64, gemv_threshold: 256 * 256, trsm_threshold: 128 * 128, block_size: 64,
68 }
69 }
70}
71
72impl BlasConfig {
73 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 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 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106pub enum Transpose {
107 NoTrans,
109 Trans,
111}
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115pub enum Side {
116 Left,
118 Right,
120}
121
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
124pub enum UpLo {
125 Upper,
127 Lower,
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, Eq)]
133pub enum Diag {
134 NonUnit,
136 Unit,
138}
139
140pub trait BlasOps {
149 #[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 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 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 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 #[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 fn trsv(&self, uplo: UpLo, trans: Transpose, diag: Diag, a: &Matrix, b: &mut [Rational64]);
266
267 fn config(&self) -> &BlasConfig;
269
270 fn should_use_blas_gemm(&self, m: usize, n: usize, k: usize) -> bool {
272 m * n * k >= self.config().gemm_threshold
273 }
274
275 fn should_use_blas_gemv(&self, m: usize, n: usize) -> bool {
277 m * n >= self.config().gemv_threshold
278 }
279
280 fn should_use_blas_trsm(&self, n: usize, nrhs: usize) -> bool {
282 n * nrhs >= self.config().trsm_threshold
283 }
284}
285
286#[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 pub fn new() -> Self {
305 Self {
306 config: BlasConfig::default(),
307 }
308 }
309
310 pub fn with_config(config: BlasConfig) -> Self {
312 Self { config }
313 }
314
315 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 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 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 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 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 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 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 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 for col in 0..nrhs {
493 match (uplo, trans) {
494 (UpLo::Lower, Transpose::NoTrans) | (UpLo::Upper, Transpose::Trans) => {
495 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 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 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 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 for row in 0..m {
555 match (uplo, trans) {
556 (UpLo::Upper, Transpose::NoTrans) | (UpLo::Lower, Transpose::Trans) => {
557 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 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
593pub fn get_blas() -> RustBlas {
598 RustBlas::new()
599}
600
601pub fn get_blas_with_config(config: BlasConfig) -> RustBlas {
603 RustBlas::with_config(config)
604}
605
606#[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 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 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 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 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 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 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 blas.gemv_into(Transpose::Trans, &rat(1), &a, &x, &rat(0), &mut y);
729
730 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 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 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 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 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 let l = Matrix::from_rows(vec![
790 vec![rat(999), rat(0)], vec![rat(2), rat(888)], ]);
793
794 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 let l = Matrix::from_rows(vec![vec![rat(2), rat(0)], vec![rat(1), rat(3)]]);
810
811 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 assert_eq!(b.get(0, 0), rat(2));
828 assert_eq!(b.get(1, 0), Rational64::new(5, 3));
829
830 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 assert!(!blas.should_use_blas_gemm(3, 3, 3)); assert!(blas.should_use_blas_gemm(5, 5, 5)); assert!(!blas.should_use_blas_gemv(5, 5)); assert!(blas.should_use_blas_gemv(8, 8)); assert!(!blas.should_use_blas_trsm(4, 4)); assert!(blas.should_use_blas_trsm(5, 5)); }
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 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 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 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 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}