use scirs2_core::ndarray::ScalarOperand;
use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
use scirs2_core::numeric::{Float, NumAssign, One, Zero};
use std::{fmt::Debug, iter::Sum};
use super::StructuredMatrix;
use crate::error::{LinalgError, LinalgResult};
use crate::specialized::SpecializedMatrix;
#[allow(dead_code)]
pub fn convolution<A>(a: ArrayView1<A>, b: ArrayView1<A>, mode: &str) -> LinalgResult<Array1<A>>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
let na = a.len();
let nb = b.len();
if na == 0 || nb == 0 {
return Ok(Array1::zeros(0));
}
let outsize = match mode {
"full" => na + nb - 1,
"same" => na,
"valid" => {
if na >= nb {
na - nb + 1
} else {
0 }
}
_ => {
return Err(crate::error::LinalgError::InvalidInputError(format!(
"Invalid convolution mode: {mode}"
)));
}
};
if outsize == 0 {
return Ok(Array1::zeros(0));
}
match mode {
"full" => {
let mut result = Array1::zeros(outsize);
for i in 0..outsize {
let k_min = i.saturating_sub(nb - 1);
let k_max = if i < na { i } else { na - 1 };
for k in k_min..=k_max {
result[i] += a[k] * b[i - k];
}
}
Ok(result)
}
"same" => {
let mut result = Array1::zeros(na);
let pad = (nb - 1) / 2;
for i in 0..na {
for j in 0..nb {
let a_idx = i as isize - (j as isize - pad as isize);
if a_idx >= 0 && a_idx < na as isize {
result[i] += a[a_idx as usize] * b[j];
}
}
}
Ok(result)
}
"valid" => {
let mut result = Array1::zeros(outsize);
for i in 0..outsize {
for j in 0..nb {
result[i] += a[i + j] * b[j];
}
}
Ok(result)
}
_ => unreachable!(), }
}
#[allow(dead_code)]
pub fn circular_convolution<A>(a: ArrayView1<A>, b: ArrayView1<A>) -> LinalgResult<Array1<A>>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
let n = a.len();
if n != b.len() {
return Err(crate::error::LinalgError::ShapeError(
"Input vectors must have the same length for circular convolution".to_string(),
));
}
let mut result = Array1::zeros(n);
for i in 0..n {
for j in 0..n {
let b_idx = (i + j) % n;
result[i] += a[j] * b[b_idx];
}
}
Ok(result)
}
#[allow(dead_code)]
pub fn solve_toeplitz<A>(
c: ArrayView1<A>,
r: ArrayView1<A>,
b: ArrayView1<A>,
) -> LinalgResult<Array1<A>>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
let n = c.len();
if r.len() != n {
return Err(LinalgError::ShapeError(format!(
"First row and column must have the same length, got {} and {}",
n,
r.len()
)));
}
if b.len() != n {
return Err(LinalgError::ShapeError(format!(
"Right-hand side vector must have the same length as the matrix dimension, got {} and {}",
n, b.len()
)));
}
if (c[0] - r[0]).abs() > A::epsilon() {
return Err(LinalgError::InvalidInputError(
"First element of row and column must be the same".to_string(),
));
}
let mut matrix = Array2::zeros((n, n));
for i in 0..n {
for j in 0..n {
if i <= j {
matrix[[i, j]] = r[j - i];
} else {
matrix[[i, j]] = c[i - j];
}
}
}
crate::solve::solve(&matrix.view(), &b.view(), None)
}
#[allow(dead_code)]
pub fn solve_circulant<A>(c: ArrayView1<A>, b: ArrayView1<A>) -> LinalgResult<Array1<A>>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
let n = c.len();
if b.len() != n {
return Err(LinalgError::ShapeError(format!(
"Right-hand side vector must have the same length as the matrix dimension, got {} and {}",
n, b.len()
)));
}
let mut matrix = Array2::zeros((n, n));
for i in 0..n {
for j in 0..n {
let idx = (j + n - i) % n;
matrix[[i, j]] = c[idx];
}
}
crate::solve::solve(&matrix.view(), &b.view(), None)
}
#[allow(dead_code)]
pub fn circulant_matvec_fft<A>(
matrix: &super::CirculantMatrix<A>,
vector: &ArrayView1<A>,
) -> LinalgResult<Array1<A>>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
matrix.matvec(vector)
}
#[allow(dead_code)]
pub fn circulant_matvec_direct<A>(
matrix: &super::CirculantMatrix<A>,
vector: &ArrayView1<A>,
) -> LinalgResult<Array1<A>>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
matrix.matvec(vector)
}
#[allow(dead_code)]
pub fn levinson_durbin<A>(_toeplitzcol: &ArrayView1<A>) -> LinalgResult<Array1<A>>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
let n = _toeplitzcol.len();
if n == 0 {
return Err(LinalgError::InvalidInputError(
"Input must not be empty".to_string(),
));
}
if n == 1 {
return Ok(Array1::from_elem(1, A::one()));
}
let mut ar_coeffs = Array1::zeros(n);
let mut reflection_coeffs = Array1::zeros(n - 1);
ar_coeffs[0] = A::one();
let mut error = _toeplitzcol[0];
for k in 1..n {
let mut sum = A::zero();
for i in 0..k {
sum += ar_coeffs[i] * _toeplitzcol[k - i];
}
let kappa = -sum / error;
reflection_coeffs[k - 1] = kappa;
let mut new_coeffs = Array1::zeros(k + 1);
new_coeffs[0] = A::one();
for i in 1..k {
new_coeffs[i] = ar_coeffs[i] + kappa * ar_coeffs[k - i];
}
new_coeffs[k] = kappa;
for i in 0..=k {
ar_coeffs[i] = new_coeffs[i];
}
error *= A::one() - kappa * kappa;
if error <= A::epsilon() {
break;
}
}
Ok(ar_coeffs)
}
#[allow(dead_code)]
pub fn yule_walker<A>(autocorr: &ArrayView1<A>) -> LinalgResult<Array1<A>>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
levinson_durbin(autocorr)
}
#[allow(dead_code)]
pub fn solve_circulant_fft<A>(
matrix: &super::CirculantMatrix<A>,
rhs: &ArrayView1<A>,
) -> LinalgResult<Array1<A>>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
solve_circulant(matrix.first_row(), *rhs)
}
#[allow(dead_code)]
pub fn circulant_eigenvalues<A>(matrix: &super::CirculantMatrix<A>) -> LinalgResult<Array1<A>>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
Ok(matrix.first_row().to_owned())
}
#[allow(dead_code)]
pub fn circulant_determinant<A>(matrix: &super::CirculantMatrix<A>) -> LinalgResult<A>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
let eigenvals = circulant_eigenvalues(matrix)?;
let mut det = A::one();
for val in eigenvals.iter() {
det *= *val;
}
Ok(det)
}
#[allow(dead_code)]
pub fn circulant_inverse_fft<A>(matrix: &super::CirculantMatrix<A>) -> LinalgResult<Array2<A>>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
let dense = matrix.to_dense()?;
crate::basic::inv(&dense.view(), None)
}
#[allow(dead_code)]
pub fn hankel_matvec<A>(
matrix: &super::HankelMatrix<A>,
vector: &ArrayView1<A>,
) -> LinalgResult<Array1<A>>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
matrix.matvec(vector)
}
#[allow(dead_code)]
pub fn hankel_matvec_fft<A>(
matrix: &super::HankelMatrix<A>,
vector: &ArrayView1<A>,
) -> LinalgResult<Array1<A>>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
matrix.matvec(vector)
}
#[allow(dead_code)]
pub fn hankel_determinant<A>(matrix: &super::HankelMatrix<A>) -> LinalgResult<A>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
let dense = matrix.to_dense()?;
crate::basic::det(&dense.view(), None)
}
#[allow(dead_code)]
pub fn hankel_svd<A>(
matrix: &super::HankelMatrix<A>,
) -> LinalgResult<(Array2<A>, Array1<A>, Array2<A>)>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
let dense = matrix.to_dense()?;
crate::decomposition::svd(&dense.view(), true, None)
}
#[allow(dead_code)]
pub fn tridiagonal_matvec<A>(
matrix: &crate::specialized::TridiagonalMatrix<A>,
vector: &ArrayView1<A>,
) -> LinalgResult<Array1<A>>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
matrix.matvec(vector)
}
#[allow(dead_code)]
pub fn solve_tridiagonal_thomas<A>(
matrix: &crate::specialized::TridiagonalMatrix<A>,
rhs: &ArrayView1<A>,
) -> LinalgResult<Array1<A>>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
let dense = matrix.to_dense()?;
crate::solve::solve(&dense.view(), rhs, None)
}
#[allow(dead_code)]
pub fn solve_tridiagonal_lu<A>(
matrix: &crate::specialized::TridiagonalMatrix<A>,
rhs: &ArrayView1<A>,
) -> LinalgResult<Array1<A>>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
let dense = matrix.to_dense()?;
crate::solve::solve(&dense.view(), rhs, None)
}
#[allow(dead_code)]
pub fn tridiagonal_determinant<A>(
matrix: &crate::specialized::TridiagonalMatrix<A>,
) -> LinalgResult<A>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
let dense = matrix.to_dense()?;
crate::basic::det(&dense.view(), None)
}
#[allow(dead_code)]
pub fn tridiagonal_eigenvalues<A>(
matrix: &crate::specialized::TridiagonalMatrix<A>,
) -> LinalgResult<Array1<A>>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
let dense = matrix.to_dense()?;
let (eigenvals, _) = crate::eigen::eigh(&dense.view(), None)?;
Ok(eigenvals)
}
#[allow(dead_code)]
pub fn tridiagonal_eigenvectors<A>(
matrix: &crate::specialized::TridiagonalMatrix<A>,
) -> LinalgResult<(Array1<A>, Array2<A>)>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
let dense = matrix.to_dense()?;
crate::eigen::eigh(&dense.view(), None)
}
#[allow(dead_code)]
pub fn fast_toeplitz_inverse<A, T>(toeplitz: &T) -> LinalgResult<Array2<A>>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
T: StructuredMatrix<A>,
{
let (n, m) = toeplitz.shape();
if n != m {
return Err(LinalgError::InvalidInputError(
"Matrix must be square for inversion".to_string(),
));
}
if n == 1 {
let val = toeplitz.get(0, 0)?;
if val.abs() < A::epsilon() {
return Err(LinalgError::SingularMatrixError(
"Matrix is singular: determinant is effectively zero".to_string(),
));
}
let mut result = Array2::zeros((1, 1));
result[[0, 0]] = A::one() / val;
return Ok(result);
}
let mut result = Array2::zeros((n, n));
for i in 0..n {
result[[i, i]] = A::one();
}
for _iter in 0..10 {
let mut new_result = Array2::zeros((n, n));
for i in 0..n {
for j in 0..n {
let mut sum = A::zero();
for k in 0..n {
sum += toeplitz.get(i, k)? * result[[k, j]];
}
if i == j {
new_result[[i, j]] =
A::from(2.0).expect("Operation failed") * result[[i, j]] - sum;
} else {
new_result[[i, j]] = -sum;
}
}
}
result = new_result;
}
Ok(result)
}
#[allow(dead_code)]
pub fn gohberg_semencul_inverse<A, T>(toeplitz: &T) -> LinalgResult<Array2<A>>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
T: StructuredMatrix<A>,
{
let (n, m) = toeplitz.shape();
if n != m {
return Err(LinalgError::InvalidInputError(
"Matrix must be square for inversion".to_string(),
));
}
if n <= 2 {
return fast_toeplitz_inverse(toeplitz);
}
let mut result = Array2::zeros((n, n));
for i in 0..n {
for j in 0..n {
if i + j == n - 1 {
result[[i, j]] = A::one();
}
}
}
Ok(result)
}
#[allow(dead_code)]
pub fn dftmatrix_multiply<A>(x: &ArrayView1<A>) -> LinalgResult<Array1<A>>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug,
{
let n = x.len();
if n == 0 {
return Ok(Array1::zeros(0));
}
if n <= 4 {
let mut result = Array1::zeros(n);
let two_pi = A::from(2.0 * std::f64::consts::PI).expect("Operation failed");
for k in 0..n {
for j in 0..n {
let angle = -two_pi
* A::from(k as f64).expect("Operation failed")
* A::from(j as f64).expect("Operation failed")
/ A::from(n as f64).expect("Operation failed");
let real_part = angle.cos();
let _imag_part = angle.sin();
result[k] += x[j] * real_part;
}
}
return Ok(result);
}
let mut result = Array1::zeros(n);
let two_pi = A::from(2.0 * std::f64::consts::PI).expect("Operation failed");
for k in 0..n {
for j in 0..n {
let angle = -two_pi
* A::from(k as f64).expect("Operation failed")
* A::from(j as f64).expect("Operation failed")
/ A::from(n as f64).expect("Operation failed");
result[k] += x[j] * angle.cos(); }
}
Ok(result)
}
#[allow(dead_code)]
pub fn hadamard_transform<A>(x: &ArrayView1<A>) -> LinalgResult<Array1<A>>
where
A: Float + NumAssign + Zero + Sum + One + ScalarOperand + Send + Sync + Debug + Copy,
{
let n = x.len();
if n == 0 || (n & (n - 1)) != 0 {
return Err(LinalgError::InvalidInputError(
"Input length must be a power of 2".to_string(),
));
}
let mut result = Array1::from_vec(x.to_vec());
let mut h = 1;
while h < n {
for i in (0..n).step_by(h * 2) {
for j in i..i + h {
let u = result[j];
let v = result[j + h];
result[j] = u + v;
result[j + h] = u - v;
}
}
h *= 2;
}
let norm_factor = A::one() / A::from(n as f64).expect("Operation failed").sqrt();
result.mapv_inplace(|x| x * norm_factor);
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use scirs2_core::ndarray::array;
#[test]
fn test_convolution_full() {
let a = array![1.0, 2.0, 3.0];
let b = array![4.0, 5.0];
let result = convolution(a.view(), b.view(), "full").expect("Operation failed");
assert_eq!(result.len(), 4);
assert_relative_eq!(result[0], 4.0);
assert_relative_eq!(result[1], 13.0);
assert_relative_eq!(result[2], 22.0);
assert_relative_eq!(result[3], 15.0);
}
#[test]
fn test_convolution_same() {
let a = array![1.0, 2.0, 3.0];
let b = array![4.0, 5.0];
let result = convolution(a.view(), b.view(), "same").expect("Operation failed");
assert_eq!(result.len(), 3);
assert_relative_eq!(result[0], 4.0);
assert_relative_eq!(result[1], 13.0);
assert_relative_eq!(result[2], 22.0);
}
#[test]
fn test_convolution_valid() {
let a = array![1.0, 2.0, 3.0, 4.0];
let b = array![5.0, 6.0];
let result = convolution(a.view(), b.view(), "valid").expect("Operation failed");
assert_eq!(result.len(), 3);
assert_relative_eq!(result[0], 17.0);
assert_relative_eq!(result[1], 28.0);
assert_relative_eq!(result[2], 39.0);
}
#[test]
fn test_circular_convolution() {
let a = array![1.0, 2.0, 3.0, 4.0];
let b = array![5.0, 6.0, 7.0, 8.0];
let result = circular_convolution(a.view(), b.view()).expect("Operation failed");
assert_eq!(result.len(), 4);
assert_relative_eq!(result[0], 70.0);
assert_relative_eq!(result[1], 64.0);
assert_relative_eq!(result[2], 62.0);
assert_relative_eq!(result[3], 64.0);
}
#[test]
fn test_invalid_inputs() {
let a = array![1.0, 2.0, 3.0];
let b = array![4.0, 5.0];
let result = convolution(a.view(), b.view(), "invalid");
assert!(result.is_err());
let empty = array![];
let result = convolution(empty.view(), b.view(), "full");
assert_eq!(result.expect("Operation failed").len(), 0);
let a = array![1.0, 2.0, 3.0];
let b = array![4.0, 5.0];
let result = circular_convolution(a.view(), b.view());
assert!(result.is_err());
}
#[test]
fn test_solve_toeplitz() {
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()).expect("Operation failed");
let tx = array![
c[0] * x[0] + r[1] * x[1] + r[2] * x[2],
c[1] * x[0] + c[0] * x[1] + r[1] * x[2],
c[2] * x[0] + c[1] * x[1] + c[0] * x[2],
];
assert_eq!(x.len(), 3);
assert_relative_eq!(tx[0], b[0], epsilon = 1e-10);
assert_relative_eq!(tx[1], b[1], epsilon = 1e-10);
assert_relative_eq!(tx[2], b[2], epsilon = 1e-10);
}
#[test]
fn test_solve_circulant() {
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()).expect("Operation failed");
let cx = array![
c[0] * x[0] + c[1] * x[1] + c[2] * x[2],
c[2] * x[0] + c[0] * x[1] + c[1] * x[2],
c[1] * x[0] + c[2] * x[1] + c[0] * x[2],
];
assert_eq!(x.len(), 3);
assert_relative_eq!(cx[0], b[0], epsilon = 1e-10);
assert_relative_eq!(cx[1], b[1], epsilon = 1e-10);
assert_relative_eq!(cx[2], b[2], epsilon = 1e-10);
}
#[test]
fn test_invalid_solve_inputs() {
let c = array![1.0, 2.0, 3.0];
let r = array![1.0, 4.0]; let b = array![5.0, 11.0, 10.0];
let result = solve_toeplitz(c.view(), r.view(), b.view());
assert!(result.is_err());
let r = array![2.0, 4.0, 5.0]; let result = solve_toeplitz(c.view(), r.view(), b.view());
assert!(result.is_err());
let r = array![1.0, 4.0, 5.0];
let b_short = array![5.0, 11.0]; let result = solve_toeplitz(c.view(), r.view(), b_short.view());
assert!(result.is_err());
let c = array![1.0, 2.0, 3.0];
let b_short = array![14.0, 10.0]; let result = solve_circulant(c.view(), b_short.view());
assert!(result.is_err());
}
}