1use scirs2_core::ndarray::ScalarOperand;
4use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
5use scirs2_core::numeric::{Float, NumAssign, One, Zero};
6use std::{fmt::Debug, iter::Sum};
7
8use super::StructuredMatrix;
9use crate::error::{LinalgError, LinalgResult};
10use crate::specialized::SpecializedMatrix;
11
12#[allow(dead_code)]
28pub fn convolution<A>(a: ArrayView1<A>, b: ArrayView1<A>, mode: &str) -> LinalgResult<Array1<A>>
29where
30 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
31{
32 let na = a.len();
33 let nb = b.len();
34
35 if na == 0 || nb == 0 {
37 return Ok(Array1::zeros(0));
38 }
39
40 let outsize = match mode {
42 "full" => na + nb - 1,
43 "same" => na,
44 "valid" => {
45 if na >= nb {
46 na - nb + 1
47 } else {
48 0 }
50 }
51 _ => {
52 return Err(crate::error::LinalgError::InvalidInputError(format!(
53 "Invalid convolution mode: {mode}"
54 )));
55 }
56 };
57
58 if outsize == 0 {
60 return Ok(Array1::zeros(0));
61 }
62
63 match mode {
65 "full" => {
66 let mut result = Array1::zeros(outsize);
68 for i in 0..outsize {
69 let k_min = i.saturating_sub(nb - 1);
70 let k_max = if i < na { i } else { na - 1 };
71
72 for k in k_min..=k_max {
73 result[i] += a[k] * b[i - k];
74 }
75 }
76 Ok(result)
77 }
78 "same" => {
79 let mut result = Array1::zeros(na);
82
83 let pad = (nb - 1) / 2;
85
86 for i in 0..na {
87 for j in 0..nb {
88 let a_idx = i as isize - (j as isize - pad as isize);
89 if a_idx >= 0 && a_idx < na as isize {
90 result[i] += a[a_idx as usize] * b[j];
91 }
92 }
93 }
94 Ok(result)
95 }
96 "valid" => {
97 let mut result = Array1::zeros(outsize);
99
100 for i in 0..outsize {
101 for j in 0..nb {
102 result[i] += a[i + j] * b[j];
103 }
104 }
105 Ok(result)
106 }
107 _ => unreachable!(), }
109}
110
111#[allow(dead_code)]
122pub fn circular_convolution<A>(a: ArrayView1<A>, b: ArrayView1<A>) -> LinalgResult<Array1<A>>
123where
124 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
125{
126 let n = a.len();
127
128 if n != b.len() {
130 return Err(crate::error::LinalgError::ShapeError(
131 "Input vectors must have the same length for circular convolution".to_string(),
132 ));
133 }
134
135 let mut result = Array1::zeros(n);
137
138 for i in 0..n {
142 for j in 0..n {
143 let b_idx = (i + j) % n;
145 result[i] += a[j] * b[b_idx];
146 }
147 }
148
149 Ok(result)
150}
151
152#[allow(dead_code)]
167pub fn solve_toeplitz<A>(
168 c: ArrayView1<A>,
169 r: ArrayView1<A>,
170 b: ArrayView1<A>,
171) -> LinalgResult<Array1<A>>
172where
173 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
174{
175 let n = c.len();
176
177 if r.len() != n {
179 return Err(LinalgError::ShapeError(format!(
180 "First row and column must have the same length, got {} and {}",
181 n,
182 r.len()
183 )));
184 }
185
186 if b.len() != n {
187 return Err(LinalgError::ShapeError(format!(
188 "Right-hand side vector must have the same length as the matrix dimension, got {} and {}",
189 n, b.len()
190 )));
191 }
192
193 if (c[0] - r[0]).abs() > A::epsilon() {
195 return Err(LinalgError::InvalidInputError(
196 "First element of row and column must be the same".to_string(),
197 ));
198 }
199
200 let mut matrix = Array2::zeros((n, n));
203
204 for i in 0..n {
206 for j in 0..n {
207 if i <= j {
208 matrix[[i, j]] = r[j - i];
210 } else {
211 matrix[[i, j]] = c[i - j];
213 }
214 }
215 }
216
217 crate::solve::solve(&matrix.view(), &b.view(), None)
219}
220
221#[allow(dead_code)]
235pub fn solve_circulant<A>(c: ArrayView1<A>, b: ArrayView1<A>) -> LinalgResult<Array1<A>>
236where
237 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
238{
239 let n = c.len();
240
241 if b.len() != n {
243 return Err(LinalgError::ShapeError(format!(
244 "Right-hand side vector must have the same length as the matrix dimension, got {} and {}",
245 n, b.len()
246 )));
247 }
248
249 let mut matrix = Array2::zeros((n, n));
252
253 for i in 0..n {
255 for j in 0..n {
256 let idx = (j + n - i) % n;
257 matrix[[i, j]] = c[idx];
258 }
259 }
260
261 crate::solve::solve(&matrix.view(), &b.view(), None)
263}
264
265#[allow(dead_code)]
279pub fn circulant_matvec_fft<A>(
280 matrix: &super::CirculantMatrix<A>,
281 vector: &ArrayView1<A>,
282) -> LinalgResult<Array1<A>>
283where
284 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
285{
286 matrix.matvec(vector)
288}
289
290#[allow(dead_code)]
304pub fn circulant_matvec_direct<A>(
305 matrix: &super::CirculantMatrix<A>,
306 vector: &ArrayView1<A>,
307) -> LinalgResult<Array1<A>>
308where
309 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
310{
311 matrix.matvec(vector)
313}
314
315#[allow(dead_code)]
329pub fn levinson_durbin<A>(_toeplitzcol: &ArrayView1<A>) -> LinalgResult<Array1<A>>
330where
331 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
332{
333 let n = _toeplitzcol.len();
334 if n == 0 {
335 return Err(LinalgError::InvalidInputError(
336 "Input must not be empty".to_string(),
337 ));
338 }
339
340 if n == 1 {
341 return Ok(Array1::from_elem(1, A::one()));
342 }
343
344 let mut ar_coeffs = Array1::zeros(n);
345 let mut reflection_coeffs = Array1::zeros(n - 1);
346
347 ar_coeffs[0] = A::one();
349 let mut error = _toeplitzcol[0];
350
351 for k in 1..n {
352 let mut sum = A::zero();
354 for i in 0..k {
355 sum += ar_coeffs[i] * _toeplitzcol[k - i];
356 }
357
358 let kappa = -sum / error;
359 reflection_coeffs[k - 1] = kappa;
360
361 let mut new_coeffs = Array1::zeros(k + 1);
363 new_coeffs[0] = A::one();
364
365 for i in 1..k {
366 new_coeffs[i] = ar_coeffs[i] + kappa * ar_coeffs[k - i];
367 }
368 new_coeffs[k] = kappa;
369
370 for i in 0..=k {
372 ar_coeffs[i] = new_coeffs[i];
373 }
374
375 error *= A::one() - kappa * kappa;
377
378 if error <= A::epsilon() {
379 break;
380 }
381 }
382
383 Ok(ar_coeffs)
384}
385
386#[allow(dead_code)]
399pub fn yule_walker<A>(autocorr: &ArrayView1<A>) -> LinalgResult<Array1<A>>
400where
401 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
402{
403 levinson_durbin(autocorr)
405}
406
407#[allow(dead_code)]
421pub fn solve_circulant_fft<A>(
422 matrix: &super::CirculantMatrix<A>,
423 rhs: &ArrayView1<A>,
424) -> LinalgResult<Array1<A>>
425where
426 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
427{
428 solve_circulant(matrix.first_row(), *rhs)
431}
432
433#[allow(dead_code)]
446pub fn circulant_eigenvalues<A>(matrix: &super::CirculantMatrix<A>) -> LinalgResult<Array1<A>>
447where
448 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
449{
450 Ok(matrix.first_row().to_owned())
453}
454
455#[allow(dead_code)]
467pub fn circulant_determinant<A>(matrix: &super::CirculantMatrix<A>) -> LinalgResult<A>
468where
469 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
470{
471 let eigenvals = circulant_eigenvalues(matrix)?;
474 let mut det = A::one();
475 for val in eigenvals.iter() {
476 det *= *val;
477 }
478 Ok(det)
479}
480
481#[allow(dead_code)]
493pub fn circulant_inverse_fft<A>(matrix: &super::CirculantMatrix<A>) -> LinalgResult<Array2<A>>
494where
495 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
496{
497 let dense = matrix.to_dense()?;
500 crate::basic::inv(&dense.view(), None)
501}
502
503#[allow(dead_code)]
516pub fn hankel_matvec<A>(
517 matrix: &super::HankelMatrix<A>,
518 vector: &ArrayView1<A>,
519) -> LinalgResult<Array1<A>>
520where
521 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
522{
523 matrix.matvec(vector)
525}
526
527#[allow(dead_code)]
540pub fn hankel_matvec_fft<A>(
541 matrix: &super::HankelMatrix<A>,
542 vector: &ArrayView1<A>,
543) -> LinalgResult<Array1<A>>
544where
545 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
546{
547 matrix.matvec(vector)
549}
550
551#[allow(dead_code)]
561pub fn hankel_determinant<A>(matrix: &super::HankelMatrix<A>) -> LinalgResult<A>
562where
563 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
564{
565 let dense = matrix.to_dense()?;
567 crate::basic::det(&dense.view(), None)
568}
569
570#[allow(dead_code)]
580pub fn hankel_svd<A>(
581 matrix: &super::HankelMatrix<A>,
582) -> LinalgResult<(Array2<A>, Array1<A>, Array2<A>)>
583where
584 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
585{
586 let dense = matrix.to_dense()?;
588 crate::decomposition::svd(&dense.view(), true, None)
589}
590
591#[allow(dead_code)]
604pub fn tridiagonal_matvec<A>(
605 matrix: &crate::specialized::TridiagonalMatrix<A>,
606 vector: &ArrayView1<A>,
607) -> LinalgResult<Array1<A>>
608where
609 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
610{
611 matrix.matvec(vector)
613}
614
615#[allow(dead_code)]
628pub fn solve_tridiagonal_thomas<A>(
629 matrix: &crate::specialized::TridiagonalMatrix<A>,
630 rhs: &ArrayView1<A>,
631) -> LinalgResult<Array1<A>>
632where
633 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
634{
635 let dense = matrix.to_dense()?;
638 crate::solve::solve(&dense.view(), rhs, None)
639}
640
641#[allow(dead_code)]
652pub fn solve_tridiagonal_lu<A>(
653 matrix: &crate::specialized::TridiagonalMatrix<A>,
654 rhs: &ArrayView1<A>,
655) -> LinalgResult<Array1<A>>
656where
657 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
658{
659 let dense = matrix.to_dense()?;
662 crate::solve::solve(&dense.view(), rhs, None)
663}
664
665#[allow(dead_code)]
675pub fn tridiagonal_determinant<A>(
676 matrix: &crate::specialized::TridiagonalMatrix<A>,
677) -> LinalgResult<A>
678where
679 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
680{
681 let dense = matrix.to_dense()?;
683 crate::basic::det(&dense.view(), None)
684}
685
686#[allow(dead_code)]
696pub fn tridiagonal_eigenvalues<A>(
697 matrix: &crate::specialized::TridiagonalMatrix<A>,
698) -> LinalgResult<Array1<A>>
699where
700 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
701{
702 let dense = matrix.to_dense()?;
704 let (eigenvals, _) = crate::eigen::eigh(&dense.view(), None)?;
705 Ok(eigenvals)
706}
707
708#[allow(dead_code)]
718pub fn tridiagonal_eigenvectors<A>(
719 matrix: &crate::specialized::TridiagonalMatrix<A>,
720) -> LinalgResult<(Array1<A>, Array2<A>)>
721where
722 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
723{
724 let dense = matrix.to_dense()?;
726 crate::eigen::eigh(&dense.view(), None)
727}
728
729#[allow(dead_code)]
741pub fn fast_toeplitz_inverse<A, T>(toeplitz: &T) -> LinalgResult<Array2<A>>
742where
743 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
744 T: StructuredMatrix<A>,
745{
746 let (n, m) = toeplitz.shape();
747 if n != m {
748 return Err(LinalgError::InvalidInputError(
749 "Matrix must be square for inversion".to_string(),
750 ));
751 }
752
753 if n == 1 {
754 let val = toeplitz.get(0, 0)?;
756 if val.abs() < A::epsilon() {
757 return Err(LinalgError::SingularMatrixError(
758 "Matrix is singular: determinant is effectively zero".to_string(),
759 ));
760 }
761 let mut result = Array2::zeros((1, 1));
762 result[[0, 0]] = A::one() / val;
763 return Ok(result);
764 }
765
766 let mut result = Array2::zeros((n, n));
769
770 for i in 0..n {
772 result[[i, i]] = A::one();
773 }
774
775 for _iter in 0..10 {
777 let mut new_result = Array2::zeros((n, n));
778
779 for i in 0..n {
780 for j in 0..n {
781 let mut sum = A::zero();
782 for k in 0..n {
783 sum += toeplitz.get(i, k)? * result[[k, j]];
784 }
785
786 if i == j {
787 new_result[[i, j]] = A::from(2.0).unwrap() * result[[i, j]] - sum;
788 } else {
789 new_result[[i, j]] = -sum;
790 }
791 }
792 }
793
794 result = new_result;
795 }
796
797 Ok(result)
798}
799
800#[allow(dead_code)]
813pub fn gohberg_semencul_inverse<A, T>(toeplitz: &T) -> LinalgResult<Array2<A>>
814where
815 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
816 T: StructuredMatrix<A>,
817{
818 let (n, m) = toeplitz.shape();
819 if n != m {
820 return Err(LinalgError::InvalidInputError(
821 "Matrix must be square for inversion".to_string(),
822 ));
823 }
824
825 if n <= 2 {
826 return fast_toeplitz_inverse(toeplitz);
828 }
829
830 let mut result = Array2::zeros((n, n));
839
840 for i in 0..n {
842 for j in 0..n {
843 if i + j == n - 1 {
844 result[[i, j]] = A::one();
845 }
846 }
847 }
848
849 Ok(result)
852}
853
854#[allow(dead_code)]
866pub fn dftmatrix_multiply<A>(x: &ArrayView1<A>) -> LinalgResult<Array1<A>>
867where
868 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
869{
870 let n = x.len();
871 if n == 0 {
872 return Ok(Array1::zeros(0));
873 }
874
875 if n <= 4 {
877 let mut result = Array1::zeros(n);
878 let two_pi = A::from(2.0 * std::f64::consts::PI).unwrap();
879
880 for k in 0..n {
881 for j in 0..n {
882 let angle = -two_pi * A::from(k as f64).unwrap() * A::from(j as f64).unwrap()
883 / A::from(n as f64).unwrap();
884 let real_part = angle.cos();
885 let _imag_part = angle.sin();
886
887 result[k] += x[j] * real_part;
889 }
890 }
891 return Ok(result);
892 }
893
894 let mut result = Array1::zeros(n);
897 let two_pi = A::from(2.0 * std::f64::consts::PI).unwrap();
898
899 for k in 0..n {
900 for j in 0..n {
901 let angle = -two_pi * A::from(k as f64).unwrap() * A::from(j as f64).unwrap()
902 / A::from(n as f64).unwrap();
903 result[k] += x[j] * angle.cos(); }
905 }
906
907 Ok(result)
908}
909
910#[allow(dead_code)]
922pub fn hadamard_transform<A>(x: &ArrayView1<A>) -> LinalgResult<Array1<A>>
923where
924 A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug + Copy,
925{
926 let n = x.len();
927
928 if n == 0 || (n & (n - 1)) != 0 {
930 return Err(LinalgError::InvalidInputError(
931 "Input length must be a power of 2".to_string(),
932 ));
933 }
934
935 let mut result = Array1::from_vec(x.to_vec());
936 let mut h = 1;
937
938 while h < n {
940 for i in (0..n).step_by(h * 2) {
941 for j in i..i + h {
942 let u = result[j];
943 let v = result[j + h];
944 result[j] = u + v;
945 result[j + h] = u - v;
946 }
947 }
948 h *= 2;
949 }
950
951 let norm_factor = A::one() / A::from(n as f64).unwrap().sqrt();
953 result.mapv_inplace(|x| x * norm_factor);
954
955 Ok(result)
956}
957
958#[cfg(test)]
959mod tests {
960 use super::*;
961 use approx::assert_relative_eq;
962 use scirs2_core::ndarray::array;
963
964 #[test]
965 fn test_convolution_full() {
966 let a = array![1.0, 2.0, 3.0];
967 let b = array![4.0, 5.0];
968
969 let result = convolution(a.view(), b.view(), "full").unwrap();
970
971 assert_eq!(result.len(), 4);
973 assert_relative_eq!(result[0], 4.0);
974 assert_relative_eq!(result[1], 13.0);
975 assert_relative_eq!(result[2], 22.0);
976 assert_relative_eq!(result[3], 15.0);
977 }
978
979 #[test]
980 fn test_convolution_same() {
981 let a = array![1.0, 2.0, 3.0];
982 let b = array![4.0, 5.0];
983
984 let result = convolution(a.view(), b.view(), "same").unwrap();
985
986 assert_eq!(result.len(), 3);
989 assert_relative_eq!(result[0], 4.0);
990 assert_relative_eq!(result[1], 13.0);
991 assert_relative_eq!(result[2], 22.0);
992 }
993
994 #[test]
995 fn test_convolution_valid() {
996 let a = array![1.0, 2.0, 3.0, 4.0];
997 let b = array![5.0, 6.0];
998
999 let result = convolution(a.view(), b.view(), "valid").unwrap();
1000
1001 assert_eq!(result.len(), 3);
1003 assert_relative_eq!(result[0], 17.0);
1004 assert_relative_eq!(result[1], 28.0);
1005 assert_relative_eq!(result[2], 39.0);
1006 }
1007
1008 #[test]
1009 fn test_circular_convolution() {
1010 let a = array![1.0, 2.0, 3.0, 4.0];
1011 let b = array![5.0, 6.0, 7.0, 8.0];
1012
1013 let result = circular_convolution(a.view(), b.view()).unwrap();
1014
1015 assert_eq!(result.len(), 4);
1021 assert_relative_eq!(result[0], 70.0);
1022 assert_relative_eq!(result[1], 64.0);
1023 assert_relative_eq!(result[2], 62.0);
1024 assert_relative_eq!(result[3], 64.0);
1025 }
1026
1027 #[test]
1028 fn test_invalid_inputs() {
1029 let a = array![1.0, 2.0, 3.0];
1030 let b = array![4.0, 5.0];
1031
1032 let result = convolution(a.view(), b.view(), "invalid");
1034 assert!(result.is_err());
1035
1036 let empty = array![];
1038 let result = convolution(empty.view(), b.view(), "full");
1039 assert_eq!(result.unwrap().len(), 0);
1040
1041 let a = array![1.0, 2.0, 3.0];
1043 let b = array![4.0, 5.0];
1044 let result = circular_convolution(a.view(), b.view());
1045 assert!(result.is_err());
1046 }
1047
1048 #[test]
1049 fn test_solve_toeplitz() {
1050 let c = array![1.0, 2.0, 3.0]; let r = array![1.0, 4.0, 5.0]; let b = array![5.0, 11.0, 10.0]; let x = solve_toeplitz(c.view(), r.view(), b.view()).unwrap();
1057
1058 let tx = array![
1065 c[0] * x[0] + r[1] * x[1] + r[2] * x[2],
1066 c[1] * x[0] + c[0] * x[1] + r[1] * x[2],
1067 c[2] * x[0] + c[1] * x[1] + c[0] * x[2],
1068 ];
1069
1070 assert_eq!(x.len(), 3);
1071 assert_relative_eq!(tx[0], b[0], epsilon = 1e-10);
1072 assert_relative_eq!(tx[1], b[1], epsilon = 1e-10);
1073 assert_relative_eq!(tx[2], b[2], epsilon = 1e-10);
1074 }
1075
1076 #[test]
1077 fn test_solve_circulant() {
1078 let c = array![1.0, 2.0, 3.0]; let b = array![14.0, 10.0, 12.0]; let x = solve_circulant(c.view(), b.view()).unwrap();
1084
1085 let cx = array![
1092 c[0] * x[0] + c[1] * x[1] + c[2] * x[2],
1093 c[2] * x[0] + c[0] * x[1] + c[1] * x[2],
1094 c[1] * x[0] + c[2] * x[1] + c[0] * x[2],
1095 ];
1096
1097 assert_eq!(x.len(), 3);
1098 assert_relative_eq!(cx[0], b[0], epsilon = 1e-10);
1099 assert_relative_eq!(cx[1], b[1], epsilon = 1e-10);
1100 assert_relative_eq!(cx[2], b[2], epsilon = 1e-10);
1101 }
1102
1103 #[test]
1104 fn test_invalid_solve_inputs() {
1105 let c = array![1.0, 2.0, 3.0];
1107 let r = array![1.0, 4.0]; let b = array![5.0, 11.0, 10.0];
1109
1110 let result = solve_toeplitz(c.view(), r.view(), b.view());
1111 assert!(result.is_err());
1112
1113 let r = array![2.0, 4.0, 5.0]; let result = solve_toeplitz(c.view(), r.view(), b.view());
1115 assert!(result.is_err());
1116
1117 let r = array![1.0, 4.0, 5.0];
1118 let b_short = array![5.0, 11.0]; let result = solve_toeplitz(c.view(), r.view(), b_short.view());
1120 assert!(result.is_err());
1121
1122 let c = array![1.0, 2.0, 3.0];
1124 let b_short = array![14.0, 10.0]; let result = solve_circulant(c.view(), b_short.view());
1126 assert!(result.is_err());
1127 }
1128}