1#[allow(unused_imports)]
54use crate::prelude::*;
55
56const BLOCK_SIZE: usize = 64;
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60pub enum Transpose {
61 NoTrans,
63 Trans,
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum Side {
70 Left,
72 Right,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78pub enum UpLo {
79 Upper,
81 Lower,
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum Diag {
88 NonUnit,
90 Unit,
92}
93
94#[inline]
117pub fn ddot(x: &[f64], y: &[f64]) -> f64 {
118 assert_eq!(
119 x.len(),
120 y.len(),
121 "Vector lengths must match for dot product"
122 );
123
124 let n = x.len();
125 let mut sum = 0.0;
126
127 let chunks = n / 4;
129 let remainder = n % 4;
130
131 for i in 0..chunks {
132 let idx = i * 4;
133 sum += x[idx] * y[idx];
134 sum += x[idx + 1] * y[idx + 1];
135 sum += x[idx + 2] * y[idx + 2];
136 sum += x[idx + 3] * y[idx + 3];
137 }
138
139 for i in (chunks * 4)..n {
141 sum += x[i] * y[i];
142 }
143
144 let _ = remainder;
146
147 sum
148}
149
150#[inline]
163pub fn dnrm2(x: &[f64]) -> f64 {
164 if x.is_empty() {
165 return 0.0;
166 }
167
168 let n = x.len();
169
170 let mut scale = 0.0f64;
172 for &xi in x {
173 let abs_xi = xi.abs();
174 if abs_xi > scale {
175 scale = abs_xi;
176 }
177 }
178
179 if scale == 0.0 {
180 return 0.0;
181 }
182
183 let mut sum = 0.0;
185 let inv_scale = 1.0 / scale;
186
187 let chunks = n / 4;
189
190 for i in 0..chunks {
191 let idx = i * 4;
192 let s0 = x[idx] * inv_scale;
193 let s1 = x[idx + 1] * inv_scale;
194 let s2 = x[idx + 2] * inv_scale;
195 let s3 = x[idx + 3] * inv_scale;
196 sum += s0 * s0 + s1 * s1 + s2 * s2 + s3 * s3;
197 }
198
199 for s in x.iter().skip(chunks * 4).take(n - chunks * 4) {
200 let s = s * inv_scale;
201 sum += s * s;
202 }
203
204 scale * sum.sqrt()
205}
206
207#[inline]
223pub fn dscal(alpha: f64, x: &mut [f64]) {
224 if alpha == 1.0 {
225 return;
226 }
227
228 if alpha == 0.0 {
229 x.fill(0.0);
230 return;
231 }
232
233 let n = x.len();
234 let chunks = n / 4;
235
236 for i in 0..chunks {
237 let idx = i * 4;
238 x[idx] *= alpha;
239 x[idx + 1] *= alpha;
240 x[idx + 2] *= alpha;
241 x[idx + 3] *= alpha;
242 }
243
244 for x_val in x.iter_mut().skip(chunks * 4).take(n - chunks * 4) {
245 *x_val *= alpha;
246 }
247}
248
249#[inline]
268pub fn daxpy(alpha: f64, x: &[f64], y: &mut [f64]) {
269 assert_eq!(x.len(), y.len(), "Vector lengths must match for DAXPY");
270
271 if alpha == 0.0 {
272 return;
273 }
274
275 let n = x.len();
276 let chunks = n / 4;
277
278 for i in 0..chunks {
279 let idx = i * 4;
280 y[idx] += alpha * x[idx];
281 y[idx + 1] += alpha * x[idx + 1];
282 y[idx + 2] += alpha * x[idx + 2];
283 y[idx + 3] += alpha * x[idx + 3];
284 }
285
286 for i in (chunks * 4)..n {
287 y[i] += alpha * x[i];
288 }
289}
290
291#[inline]
309pub fn dcopy(x: &[f64], y: &mut [f64]) {
310 assert_eq!(x.len(), y.len(), "Vector lengths must match for DCOPY");
311 y.copy_from_slice(x);
312}
313
314#[inline]
333pub fn dswap(x: &mut [f64], y: &mut [f64]) {
334 assert_eq!(x.len(), y.len(), "Vector lengths must match for DSWAP");
335 x.swap_with_slice(y);
336}
337
338#[inline]
350pub fn idamax(x: &[f64]) -> usize {
351 if x.is_empty() {
352 return 0;
353 }
354
355 let mut max_idx = 0;
356 let mut max_val = x[0].abs();
357
358 for (i, &xi) in x.iter().enumerate().skip(1) {
359 let abs_xi = xi.abs();
360 if abs_xi > max_val {
361 max_val = abs_xi;
362 max_idx = i;
363 }
364 }
365
366 max_idx
367}
368
369#[inline]
380pub fn dasum(x: &[f64]) -> f64 {
381 let n = x.len();
382 let mut sum = 0.0;
383
384 let chunks = n / 4;
385
386 for i in 0..chunks {
387 let idx = i * 4;
388 sum += x[idx].abs();
389 sum += x[idx + 1].abs();
390 sum += x[idx + 2].abs();
391 sum += x[idx + 3].abs();
392 }
393
394 for x_val in x.iter().skip(chunks * 4).take(n - chunks * 4) {
395 sum += x_val.abs();
396 }
397
398 sum
399}
400
401#[allow(clippy::too_many_arguments)]
434pub fn dgemv(
435 trans: Transpose,
436 m: usize,
437 n: usize,
438 alpha: f64,
439 a: &[f64],
440 x: &[f64],
441 beta: f64,
442 y: &mut [f64],
443) {
444 assert_eq!(a.len(), m * n, "Matrix A size must be m * n");
445
446 match trans {
447 Transpose::NoTrans => {
448 assert_eq!(x.len(), n, "Vector x length must be n for NoTrans");
449 assert_eq!(y.len(), m, "Vector y length must be m for NoTrans");
450
451 if beta == 0.0 {
453 y.fill(0.0);
454 } else if beta != 1.0 {
455 dscal(beta, y);
456 }
457
458 if alpha == 0.0 {
459 return;
460 }
461
462 for (i, y_val) in y.iter_mut().enumerate().take(m) {
464 let row_start = i * n;
465 let mut sum = 0.0;
466
467 let chunks = n / 4;
469 for j in 0..chunks {
470 let idx = j * 4;
471 sum += a[row_start + idx] * x[idx];
472 sum += a[row_start + idx + 1] * x[idx + 1];
473 sum += a[row_start + idx + 2] * x[idx + 2];
474 sum += a[row_start + idx + 3] * x[idx + 3];
475 }
476 for j in (chunks * 4)..n {
477 sum += a[row_start + j] * x[j];
478 }
479
480 *y_val += alpha * sum;
481 }
482 }
483 Transpose::Trans => {
484 assert_eq!(x.len(), m, "Vector x length must be m for Trans");
485 assert_eq!(y.len(), n, "Vector y length must be n for Trans");
486
487 if beta == 0.0 {
489 y.fill(0.0);
490 } else if beta != 1.0 {
491 dscal(beta, y);
492 }
493
494 if alpha == 0.0 {
495 return;
496 }
497
498 for (i, x_val) in x.iter().enumerate().take(m) {
500 let row_start = i * n;
501 let alpha_xi = alpha * x_val;
502
503 let chunks = n / 4;
505 for j in 0..chunks {
506 let idx = j * 4;
507 y[idx] += alpha_xi * a[row_start + idx];
508 y[idx + 1] += alpha_xi * a[row_start + idx + 1];
509 y[idx + 2] += alpha_xi * a[row_start + idx + 2];
510 y[idx + 3] += alpha_xi * a[row_start + idx + 3];
511 }
512 for j in (chunks * 4)..n {
513 y[j] += alpha_xi * a[row_start + j];
514 }
515 }
516 }
517 }
518}
519
520#[allow(clippy::too_many_arguments)]
535pub fn dtrsv(uplo: UpLo, trans: Transpose, diag: Diag, n: usize, a: &[f64], b: &mut [f64]) {
536 assert_eq!(a.len(), n * n, "Matrix A size must be n * n");
537 assert_eq!(b.len(), n, "Vector b length must be n");
538
539 if n == 0 {
540 return;
541 }
542
543 match (uplo, trans) {
544 (UpLo::Lower, Transpose::NoTrans) | (UpLo::Upper, Transpose::Trans) => {
545 for i in 0..n {
547 let mut sum = b[i];
548 for j in 0..i {
549 let a_ij = if trans == Transpose::Trans {
550 a[j * n + i]
551 } else {
552 a[i * n + j]
553 };
554 sum -= a_ij * b[j];
555 }
556 if diag == Diag::NonUnit {
557 let a_ii = a[i * n + i];
559 b[i] = sum / a_ii;
560 } else {
561 b[i] = sum;
562 }
563 }
564 }
565 (UpLo::Upper, Transpose::NoTrans) | (UpLo::Lower, Transpose::Trans) => {
566 for i in (0..n).rev() {
568 let mut sum = b[i];
569 for j in (i + 1)..n {
570 let a_ij = if trans == Transpose::Trans {
571 a[j * n + i]
572 } else {
573 a[i * n + j]
574 };
575 sum -= a_ij * b[j];
576 }
577 if diag == Diag::NonUnit {
578 let a_ii = a[i * n + i];
580 b[i] = sum / a_ii;
581 } else {
582 b[i] = sum;
583 }
584 }
585 }
586 }
587}
588
589#[allow(clippy::too_many_arguments)]
626pub fn dgemm(
627 trans_a: Transpose,
628 trans_b: Transpose,
629 m: usize,
630 n: usize,
631 k: usize,
632 alpha: f64,
633 a: &[f64],
634 b: &[f64],
635 beta: f64,
636 c: &mut [f64],
637) {
638 let (a_rows, a_cols) = match trans_a {
640 Transpose::NoTrans => (m, k),
641 Transpose::Trans => (k, m),
642 };
643 let (b_rows, b_cols) = match trans_b {
644 Transpose::NoTrans => (k, n),
645 Transpose::Trans => (n, k),
646 };
647
648 assert_eq!(a.len(), a_rows * a_cols, "Matrix A size mismatch");
649 assert_eq!(b.len(), b_rows * b_cols, "Matrix B size mismatch");
650 assert_eq!(c.len(), m * n, "Matrix C size must be m * n");
651
652 if beta == 0.0 {
654 c.fill(0.0);
655 } else if beta != 1.0 {
656 for ci in c.iter_mut() {
657 *ci *= beta;
658 }
659 }
660
661 if alpha == 0.0 {
662 return;
663 }
664
665 if m * n * k > BLOCK_SIZE * BLOCK_SIZE * BLOCK_SIZE {
667 dgemm_blocked(trans_a, trans_b, m, n, k, alpha, a, b, c);
668 } else {
669 dgemm_simple(trans_a, trans_b, m, n, k, alpha, a, b, c);
670 }
671}
672
673#[allow(clippy::too_many_arguments)]
675fn dgemm_simple(
676 trans_a: Transpose,
677 trans_b: Transpose,
678 m: usize,
679 n: usize,
680 k: usize,
681 alpha: f64,
682 a: &[f64],
683 b: &[f64],
684 c: &mut [f64],
685) {
686 let (a_cols, b_cols) = match (trans_a, trans_b) {
687 (Transpose::NoTrans, Transpose::NoTrans) => (k, n),
688 (Transpose::NoTrans, Transpose::Trans) => (k, k),
689 (Transpose::Trans, Transpose::NoTrans) => (m, n),
690 (Transpose::Trans, Transpose::Trans) => (m, k),
691 };
692
693 for i in 0..m {
694 for j in 0..n {
695 let mut sum = 0.0;
696 for l in 0..k {
697 let a_il = match trans_a {
698 Transpose::NoTrans => a[i * a_cols + l],
699 Transpose::Trans => a[l * a_cols + i],
700 };
701 let b_lj = match trans_b {
702 Transpose::NoTrans => b[l * b_cols + j],
703 Transpose::Trans => b[j * b_cols + l],
704 };
705 sum += a_il * b_lj;
706 }
707 c[i * n + j] += alpha * sum;
708 }
709 }
710}
711
712#[allow(clippy::too_many_arguments)]
714fn dgemm_blocked(
715 trans_a: Transpose,
716 trans_b: Transpose,
717 m: usize,
718 n: usize,
719 k: usize,
720 alpha: f64,
721 a: &[f64],
722 b: &[f64],
723 c: &mut [f64],
724) {
725 let (a_cols, b_cols) = match (trans_a, trans_b) {
726 (Transpose::NoTrans, Transpose::NoTrans) => (k, n),
727 (Transpose::NoTrans, Transpose::Trans) => (k, k),
728 (Transpose::Trans, Transpose::NoTrans) => (m, n),
729 (Transpose::Trans, Transpose::Trans) => (m, k),
730 };
731
732 for i0 in (0..m).step_by(BLOCK_SIZE) {
734 let i1 = (i0 + BLOCK_SIZE).min(m);
735
736 for j0 in (0..n).step_by(BLOCK_SIZE) {
737 let j1 = (j0 + BLOCK_SIZE).min(n);
738
739 for l0 in (0..k).step_by(BLOCK_SIZE) {
740 let l1 = (l0 + BLOCK_SIZE).min(k);
741
742 for i in i0..i1 {
744 for j in j0..j1 {
745 let mut sum = 0.0;
746 for l in l0..l1 {
747 let a_il = match trans_a {
748 Transpose::NoTrans => a[i * a_cols + l],
749 Transpose::Trans => a[l * a_cols + i],
750 };
751 let b_lj = match trans_b {
752 Transpose::NoTrans => b[l * b_cols + j],
753 Transpose::Trans => b[j * b_cols + l],
754 };
755 sum += a_il * b_lj;
756 }
757 c[i * n + j] += alpha * sum;
758 }
759 }
760 }
761 }
762 }
763}
764
765#[allow(clippy::too_many_arguments)]
784pub fn dtrsm(
785 side: Side,
786 uplo: UpLo,
787 trans: Transpose,
788 diag: Diag,
789 m: usize,
790 n: usize,
791 alpha: f64,
792 a: &[f64],
793 b: &mut [f64],
794) {
795 let a_size = match side {
796 Side::Left => m,
797 Side::Right => n,
798 };
799
800 assert_eq!(a.len(), a_size * a_size, "Matrix A must be square");
801 assert_eq!(b.len(), m * n, "Matrix B size must be m * n");
802
803 if alpha != 1.0 {
805 for bi in b.iter_mut() {
806 *bi *= alpha;
807 }
808 }
809
810 match side {
811 Side::Left => dtrsm_left(uplo, trans, diag, m, n, a, b),
812 Side::Right => dtrsm_right(uplo, trans, diag, m, n, a, b),
813 }
814}
815
816fn dtrsm_left(
818 uplo: UpLo,
819 trans: Transpose,
820 diag: Diag,
821 m: usize,
822 n: usize,
823 a: &[f64],
824 b: &mut [f64],
825) {
826 for col in 0..n {
827 match (uplo, trans) {
828 (UpLo::Lower, Transpose::NoTrans) | (UpLo::Upper, Transpose::Trans) => {
829 for i in 0..m {
831 let mut sum = b[i * n + col];
832 for j in 0..i {
833 let a_ij = if trans == Transpose::Trans {
834 a[j * m + i]
835 } else {
836 a[i * m + j]
837 };
838 sum -= a_ij * b[j * n + col];
839 }
840 if diag == Diag::NonUnit {
841 let a_ii = a[i * m + i];
842 b[i * n + col] = sum / a_ii;
843 } else {
844 b[i * n + col] = sum;
845 }
846 }
847 }
848 (UpLo::Upper, Transpose::NoTrans) | (UpLo::Lower, Transpose::Trans) => {
849 for i in (0..m).rev() {
851 let mut sum = b[i * n + col];
852 for j in (i + 1)..m {
853 let a_ij = if trans == Transpose::Trans {
854 a[j * m + i]
855 } else {
856 a[i * m + j]
857 };
858 sum -= a_ij * b[j * n + col];
859 }
860 if diag == Diag::NonUnit {
861 let a_ii = a[i * m + i];
862 b[i * n + col] = sum / a_ii;
863 } else {
864 b[i * n + col] = sum;
865 }
866 }
867 }
868 }
869 }
870}
871
872fn dtrsm_right(
874 uplo: UpLo,
875 trans: Transpose,
876 diag: Diag,
877 m: usize,
878 n: usize,
879 a: &[f64],
880 b: &mut [f64],
881) {
882 for row in 0..m {
883 match (uplo, trans) {
884 (UpLo::Upper, Transpose::NoTrans) | (UpLo::Lower, Transpose::Trans) => {
885 for j in 0..n {
887 let mut sum = b[row * n + j];
888 for k in 0..j {
889 let a_kj = if trans == Transpose::Trans {
890 a[j * n + k]
891 } else {
892 a[k * n + j]
893 };
894 sum -= b[row * n + k] * a_kj;
895 }
896 if diag == Diag::NonUnit {
897 let a_jj = a[j * n + j];
898 b[row * n + j] = sum / a_jj;
899 } else {
900 b[row * n + j] = sum;
901 }
902 }
903 }
904 (UpLo::Lower, Transpose::NoTrans) | (UpLo::Upper, Transpose::Trans) => {
905 for j in (0..n).rev() {
907 let mut sum = b[row * n + j];
908 for k in (j + 1)..n {
909 let a_kj = if trans == Transpose::Trans {
910 a[j * n + k]
911 } else {
912 a[k * n + j]
913 };
914 sum -= b[row * n + k] * a_kj;
915 }
916 if diag == Diag::NonUnit {
917 let a_jj = a[j * n + j];
918 b[row * n + j] = sum / a_jj;
919 } else {
920 b[row * n + j] = sum;
921 }
922 }
923 }
924 }
925 }
926}
927
928#[derive(Debug, Clone)]
934pub struct BlasLPConfig {
935 pub block_size: usize,
937 pub zero_tolerance: f64,
939 pub use_pivoting: bool,
941}
942
943impl Default for BlasLPConfig {
944 fn default() -> Self {
945 Self {
946 block_size: BLOCK_SIZE,
947 zero_tolerance: 1e-12,
948 use_pivoting: true,
949 }
950 }
951}
952
953#[allow(dead_code)]
960pub fn compute_reduced_cost(
961 c: &[f64],
962 c_b: &[f64],
963 b_inv_a: &[f64],
964 m: usize,
965 n: usize,
966) -> Vec<f64> {
967 let mut reduced = c.to_vec();
968
969 for j in 0..n {
972 for i in 0..m {
973 reduced[j] -= c_b[i] * b_inv_a[i * n + j];
974 }
975 }
976
977 reduced
978}
979
980#[allow(dead_code)]
984pub fn solve_basis(b: &[f64], n: usize, rhs: &mut [f64], config: &BlasLPConfig) -> bool {
985 let mut lu = b.to_vec();
987 let mut perm: Vec<usize> = (0..n).collect();
988
989 for k in 0..n - 1 {
991 if config.use_pivoting {
993 let mut max_idx = k;
994 let mut max_val = lu[k * n + k].abs();
995 for i in (k + 1)..n {
996 let val = lu[i * n + k].abs();
997 if val > max_val {
998 max_val = val;
999 max_idx = i;
1000 }
1001 }
1002
1003 if max_val < config.zero_tolerance {
1004 return false; }
1006
1007 if max_idx != k {
1008 for j in 0..n {
1010 lu.swap(k * n + j, max_idx * n + j);
1011 }
1012 perm.swap(k, max_idx);
1013 }
1014 }
1015
1016 let pivot = lu[k * n + k];
1017 if pivot.abs() < config.zero_tolerance {
1018 return false; }
1020
1021 for i in (k + 1)..n {
1023 let factor = lu[i * n + k] / pivot;
1024 lu[i * n + k] = factor;
1025 for j in (k + 1)..n {
1026 lu[i * n + j] -= factor * lu[k * n + j];
1027 }
1028 }
1029 }
1030
1031 let mut tmp = vec![0.0; n];
1033 for i in 0..n {
1034 tmp[i] = rhs[perm[i]];
1035 }
1036 rhs.copy_from_slice(&tmp);
1037
1038 for i in 1..n {
1040 for j in 0..i {
1041 rhs[i] -= lu[i * n + j] * rhs[j];
1042 }
1043 }
1044
1045 for i in (0..n).rev() {
1047 for j in (i + 1)..n {
1048 rhs[i] -= lu[i * n + j] * rhs[j];
1049 }
1050 rhs[i] /= lu[i * n + i];
1051 }
1052
1053 true
1054}
1055
1056#[cfg(test)]
1057mod tests {
1058 use super::*;
1059
1060 const EPSILON: f64 = 1e-10;
1061
1062 fn approx_eq(a: f64, b: f64) -> bool {
1063 (a - b).abs() < EPSILON
1064 }
1065
1066 fn approx_eq_vec(a: &[f64], b: &[f64]) -> bool {
1067 a.len() == b.len() && a.iter().zip(b.iter()).all(|(&ai, &bi)| approx_eq(ai, bi))
1068 }
1069
1070 #[test]
1075 fn test_ddot() {
1076 let x = vec![1.0, 2.0, 3.0, 4.0];
1077 let y = vec![5.0, 6.0, 7.0, 8.0];
1078 let result = ddot(&x, &y);
1079 assert!(approx_eq(result, 70.0)); }
1081
1082 #[test]
1083 fn test_ddot_empty() {
1084 let x: Vec<f64> = vec![];
1085 let y: Vec<f64> = vec![];
1086 assert!(approx_eq(ddot(&x, &y), 0.0));
1087 }
1088
1089 #[test]
1090 fn test_dnrm2() {
1091 let x = vec![3.0, 4.0];
1092 assert!(approx_eq(dnrm2(&x), 5.0));
1093 }
1094
1095 #[test]
1096 fn test_dnrm2_large_values() {
1097 let scale = 1e150;
1099 let x = vec![3.0 * scale, 4.0 * scale];
1100 assert!(approx_eq(dnrm2(&x), 5.0 * scale));
1101 }
1102
1103 #[test]
1104 fn test_dnrm2_small_values() {
1105 let scale = 1e-150;
1107 let x = vec![3.0 * scale, 4.0 * scale];
1108 assert!(approx_eq(dnrm2(&x), 5.0 * scale));
1109 }
1110
1111 #[test]
1112 fn test_dscal() {
1113 let mut x = vec![1.0, 2.0, 3.0, 4.0, 5.0];
1114 dscal(2.0, &mut x);
1115 assert!(approx_eq_vec(&x, &[2.0, 4.0, 6.0, 8.0, 10.0]));
1116 }
1117
1118 #[test]
1119 fn test_dscal_zero() {
1120 let mut x = vec![1.0, 2.0, 3.0];
1121 dscal(0.0, &mut x);
1122 assert!(approx_eq_vec(&x, &[0.0, 0.0, 0.0]));
1123 }
1124
1125 #[test]
1126 fn test_dscal_one() {
1127 let mut x = vec![1.0, 2.0, 3.0];
1128 let original = x.clone();
1129 dscal(1.0, &mut x);
1130 assert!(approx_eq_vec(&x, &original));
1131 }
1132
1133 #[test]
1134 fn test_daxpy() {
1135 let x = vec![1.0, 2.0, 3.0, 4.0, 5.0];
1136 let mut y = vec![10.0, 20.0, 30.0, 40.0, 50.0];
1137 daxpy(2.0, &x, &mut y);
1138 assert!(approx_eq_vec(&y, &[12.0, 24.0, 36.0, 48.0, 60.0]));
1139 }
1140
1141 #[test]
1142 fn test_daxpy_zero_alpha() {
1143 let x = vec![1.0, 2.0, 3.0];
1144 let mut y = vec![10.0, 20.0, 30.0];
1145 let original_y = y.clone();
1146 daxpy(0.0, &x, &mut y);
1147 assert!(approx_eq_vec(&y, &original_y));
1148 }
1149
1150 #[test]
1151 fn test_dcopy() {
1152 let x = vec![1.0, 2.0, 3.0];
1153 let mut y = vec![0.0, 0.0, 0.0];
1154 dcopy(&x, &mut y);
1155 assert!(approx_eq_vec(&y, &x));
1156 }
1157
1158 #[test]
1159 fn test_dswap() {
1160 let mut x = vec![1.0, 2.0, 3.0];
1161 let mut y = vec![4.0, 5.0, 6.0];
1162 dswap(&mut x, &mut y);
1163 assert!(approx_eq_vec(&x, &[4.0, 5.0, 6.0]));
1164 assert!(approx_eq_vec(&y, &[1.0, 2.0, 3.0]));
1165 }
1166
1167 #[test]
1168 fn test_idamax() {
1169 let x = vec![1.0, -5.0, 3.0, -2.0];
1170 assert_eq!(idamax(&x), 1);
1171 }
1172
1173 #[test]
1174 fn test_idamax_empty() {
1175 let x: Vec<f64> = vec![];
1176 assert_eq!(idamax(&x), 0);
1177 }
1178
1179 #[test]
1180 fn test_dasum() {
1181 let x = vec![1.0, -2.0, 3.0, -4.0, 5.0];
1182 assert!(approx_eq(dasum(&x), 15.0));
1183 }
1184
1185 #[test]
1190 fn test_dgemv_notrans() {
1191 let a = vec![1.0, 2.0, 3.0, 4.0];
1194 let x = vec![5.0, 6.0];
1195 let mut y = vec![0.0, 0.0];
1196 dgemv(Transpose::NoTrans, 2, 2, 1.0, &a, &x, 0.0, &mut y);
1197 assert!(approx_eq_vec(&y, &[17.0, 39.0]));
1198 }
1199
1200 #[test]
1201 fn test_dgemv_trans() {
1202 let a = vec![1.0, 2.0, 3.0, 4.0];
1205 let x = vec![5.0, 6.0];
1206 let mut y = vec![0.0, 0.0];
1207 dgemv(Transpose::Trans, 2, 2, 1.0, &a, &x, 0.0, &mut y);
1208 assert!(approx_eq_vec(&y, &[23.0, 34.0]));
1209 }
1210
1211 #[test]
1212 fn test_dgemv_with_beta() {
1213 let a = vec![1.0, 2.0, 3.0, 4.0];
1214 let x = vec![1.0, 1.0];
1215 let mut y = vec![10.0, 10.0];
1216 dgemv(Transpose::NoTrans, 2, 2, 2.0, &a, &x, 3.0, &mut y);
1218 assert!(approx_eq_vec(&y, &[36.0, 44.0]));
1219 }
1220
1221 #[test]
1222 fn test_dtrsv_lower() {
1223 let l = vec![2.0, 0.0, 1.0, 3.0];
1228 let mut b = vec![4.0, 5.0];
1229 dtrsv(
1230 UpLo::Lower,
1231 Transpose::NoTrans,
1232 Diag::NonUnit,
1233 2,
1234 &l,
1235 &mut b,
1236 );
1237 assert!(approx_eq_vec(&b, &[2.0, 1.0]));
1238 }
1239
1240 #[test]
1241 fn test_dtrsv_upper() {
1242 let u = vec![2.0, 1.0, 0.0, 3.0];
1247 let mut b = vec![5.0, 6.0];
1248 dtrsv(
1249 UpLo::Upper,
1250 Transpose::NoTrans,
1251 Diag::NonUnit,
1252 2,
1253 &u,
1254 &mut b,
1255 );
1256 assert!(approx_eq_vec(&b, &[1.5, 2.0]));
1257 }
1258
1259 #[test]
1260 fn test_dtrsv_unit_diagonal() {
1261 let l = vec![1.0, 0.0, 2.0, 1.0];
1266 let mut b = vec![3.0, 8.0];
1267 dtrsv(UpLo::Lower, Transpose::NoTrans, Diag::Unit, 2, &l, &mut b);
1268 assert!(approx_eq_vec(&b, &[3.0, 2.0]));
1269 }
1270
1271 #[test]
1276 fn test_dgemm_basic() {
1277 let a = vec![1.0, 2.0, 3.0, 4.0];
1280 let b = vec![5.0, 6.0, 7.0, 8.0];
1281 let mut c = vec![0.0; 4];
1282 dgemm(
1283 Transpose::NoTrans,
1284 Transpose::NoTrans,
1285 2,
1286 2,
1287 2,
1288 1.0,
1289 &a,
1290 &b,
1291 0.0,
1292 &mut c,
1293 );
1294 assert!(approx_eq_vec(&c, &[19.0, 22.0, 43.0, 50.0]));
1295 }
1296
1297 #[test]
1298 fn test_dgemm_with_transpose_a() {
1299 let a = vec![1.0, 3.0, 2.0, 4.0];
1303 let b = vec![5.0, 6.0, 7.0, 8.0];
1304 let mut c = vec![0.0; 4];
1305 dgemm(
1306 Transpose::Trans,
1307 Transpose::NoTrans,
1308 2,
1309 2,
1310 2,
1311 1.0,
1312 &a,
1313 &b,
1314 0.0,
1315 &mut c,
1316 );
1317 assert!(approx_eq_vec(&c, &[19.0, 22.0, 43.0, 50.0]));
1318 }
1319
1320 #[test]
1321 fn test_dgemm_with_transpose_b() {
1322 let a = vec![1.0, 2.0, 3.0, 4.0];
1326 let b = vec![5.0, 7.0, 6.0, 8.0];
1327 let mut c = vec![0.0; 4];
1328 dgemm(
1329 Transpose::NoTrans,
1330 Transpose::Trans,
1331 2,
1332 2,
1333 2,
1334 1.0,
1335 &a,
1336 &b,
1337 0.0,
1338 &mut c,
1339 );
1340 assert!(approx_eq_vec(&c, &[19.0, 22.0, 43.0, 50.0]));
1341 }
1342
1343 #[test]
1344 fn test_dgemm_with_alpha_beta() {
1345 let a = vec![1.0, 0.0, 0.0, 1.0]; let b = vec![1.0, 2.0, 3.0, 4.0];
1347 let mut c = vec![10.0, 20.0, 30.0, 40.0];
1348 dgemm(
1351 Transpose::NoTrans,
1352 Transpose::NoTrans,
1353 2,
1354 2,
1355 2,
1356 2.0,
1357 &a,
1358 &b,
1359 3.0,
1360 &mut c,
1361 );
1362 assert!(approx_eq_vec(&c, &[32.0, 64.0, 96.0, 128.0]));
1363 }
1364
1365 #[test]
1366 fn test_dgemm_non_square() {
1367 let a = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0];
1371 let b = vec![7.0, 8.0, 9.0, 10.0, 11.0, 12.0];
1372 let mut c = vec![0.0; 4];
1373 dgemm(
1374 Transpose::NoTrans,
1375 Transpose::NoTrans,
1376 2,
1377 2,
1378 3,
1379 1.0,
1380 &a,
1381 &b,
1382 0.0,
1383 &mut c,
1384 );
1385 assert!(approx_eq_vec(&c, &[58.0, 64.0, 139.0, 154.0]));
1390 }
1391
1392 #[test]
1393 fn test_dtrsm_left_lower() {
1394 let l = vec![2.0, 0.0, 1.0, 3.0];
1399 let mut b = vec![4.0, 6.0, 5.0, 9.0];
1400 dtrsm(
1401 Side::Left,
1402 UpLo::Lower,
1403 Transpose::NoTrans,
1404 Diag::NonUnit,
1405 2,
1406 2,
1407 1.0,
1408 &l,
1409 &mut b,
1410 );
1411 assert!(approx_eq_vec(&b, &[2.0, 3.0, 1.0, 2.0]));
1412 }
1413
1414 #[test]
1415 fn test_dtrsm_with_alpha() {
1416 let l = vec![2.0, 0.0, 1.0, 3.0];
1419 let mut b = vec![4.0, 6.0, 5.0, 9.0];
1420 dtrsm(
1421 Side::Left,
1422 UpLo::Lower,
1423 Transpose::NoTrans,
1424 Diag::NonUnit,
1425 2,
1426 2,
1427 2.0,
1428 &l,
1429 &mut b,
1430 );
1431 assert!(approx_eq_vec(&b, &[4.0, 6.0, 2.0, 4.0]));
1432 }
1433
1434 #[test]
1439 fn test_blas_lp_config() {
1440 let config = BlasLPConfig::default();
1441 assert_eq!(config.block_size, BLOCK_SIZE);
1442 assert!(config.zero_tolerance > 0.0);
1443 assert!(config.use_pivoting);
1444 }
1445
1446 #[test]
1447 fn test_solve_basis_simple() {
1448 let b = vec![2.0, 1.0, 1.0, 3.0];
1452 let mut rhs = vec![5.0, 7.0];
1453 let config = BlasLPConfig::default();
1454 let success = solve_basis(&b, 2, &mut rhs, &config);
1455 assert!(success);
1456 assert!(approx_eq(rhs[0], 1.6));
1457 assert!(approx_eq(rhs[1], 1.8));
1458 }
1459
1460 #[test]
1461 fn test_identity_operations() {
1462 let i = vec![1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0];
1465 let x = vec![1.0, 2.0, 3.0];
1466 let mut y = vec![0.0, 0.0, 0.0];
1467 dgemv(Transpose::NoTrans, 3, 3, 1.0, &i, &x, 0.0, &mut y);
1468 assert!(approx_eq_vec(&y, &x));
1469 }
1470
1471 #[test]
1472 fn test_large_matrix_blocked() {
1473 let n = 100;
1475 let a: Vec<f64> = (0..n * n).map(|i| (i % 7) as f64).collect();
1476 let b: Vec<f64> = (0..n * n).map(|i| ((i + 3) % 5) as f64).collect();
1477 let mut c = vec![0.0; n * n];
1478
1479 dgemm(
1480 Transpose::NoTrans,
1481 Transpose::NoTrans,
1482 n,
1483 n,
1484 n,
1485 1.0,
1486 &a,
1487 &b,
1488 0.0,
1489 &mut c,
1490 );
1491
1492 let mut expected = 0.0;
1494 for k in 0..n {
1495 expected += a[k] * b[k * n];
1496 }
1497 assert!(approx_eq(c[0], expected));
1498 }
1499}