use crate::error::{LinalgError, LinalgResult};
use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
use scirs2_core::numeric::{Float, NumAssign, One, Zero};
use std::{fmt::Debug, iter::Sum};
pub fn cauchy_matrix<F>(x: &ArrayView1<F>, y: &ArrayView1<F>) -> LinalgResult<Array2<F>>
where
F: Float + NumAssign + Zero + Sum + One + Send + Sync + Debug,
{
let m = x.len();
let n = y.len();
if m == 0 || n == 0 {
return Err(LinalgError::InvalidInputError(
"Input vectors must be non-empty".to_string(),
));
}
let mut c = Array2::<F>::zeros((m, n));
for i in 0..m {
for j in 0..n {
let diff = x[i] - y[j];
if diff.abs() < F::epsilon() * F::from(100.0).expect("convert") {
return Err(LinalgError::InvalidInputError(format!(
"x[{i}] and y[{j}] are too close; Cauchy matrix is undefined when x[i] = y[j]"
)));
}
c[[i, j]] = F::one() / diff;
}
}
Ok(c)
}
pub fn cauchy_det<F>(x: &ArrayView1<F>, y: &ArrayView1<F>) -> LinalgResult<F>
where
F: Float + NumAssign + Zero + Sum + One + Send + Sync + Debug,
{
let n = x.len();
if n == 0 {
return Err(LinalgError::InvalidInputError(
"Input vectors must be non-empty".to_string(),
));
}
if y.len() != n {
return Err(LinalgError::ShapeError(format!(
"x and y must have the same length, got {} and {}",
n,
y.len()
)));
}
for i in 0..n {
for j in 0..n {
let diff = x[i] - y[j];
if diff.abs() < F::epsilon() * F::from(100.0).expect("convert") {
return Err(LinalgError::InvalidInputError(format!(
"x[{i}] and y[{j}] are too close; Cauchy matrix is singular when x[i] = y[j]"
)));
}
}
}
let mut num = F::one();
for i in 0..n {
for j in (i + 1)..n {
num *= x[j] - x[i];
}
}
for i in 0..n {
for j in (i + 1)..n {
num *= y[i] - y[j];
}
}
let mut den = F::one();
for xi in x.iter() {
for yj in y.iter() {
den *= *xi - *yj;
}
}
if den.abs() < F::epsilon() {
return Err(LinalgError::SingularMatrixError(
"Cauchy matrix denominator is zero; matrix is singular".to_string(),
));
}
Ok(num / den)
}
pub fn solve_cauchy<F>(x: &ArrayView1<F>, y: &ArrayView1<F>, b: &ArrayView1<F>) -> LinalgResult<Array1<F>>
where
F: Float + NumAssign + Zero + Sum + One + Send + Sync + Debug,
{
let n = x.len();
if n == 0 {
return Err(LinalgError::InvalidInputError(
"Input vectors must be non-empty".to_string(),
));
}
if y.len() != n {
return Err(LinalgError::ShapeError(format!(
"x and y must have the same length: {} vs {}",
n,
y.len()
)));
}
if b.len() != n {
return Err(LinalgError::ShapeError(format!(
"b must have length {}, got {}",
n,
b.len()
)));
}
for i in 0..n {
for j in 0..n {
let diff = x[i] - y[j];
if diff.abs() < F::epsilon() * F::from(100.0).expect("convert") {
return Err(LinalgError::InvalidInputError(format!(
"x[{i}] and y[{j}] are too close; cannot form Cauchy matrix"
)));
}
}
}
let c = cauchy_matrix(x, y)?;
let mut mat = c;
let mut rhs = b.to_owned();
let mut perm: Vec<usize> = (0..n).collect();
for col in 0..n {
let mut max_val = F::zero();
let mut max_row = col;
for row in col..n {
let val = mat[[row, col]].abs();
if val > max_val {
max_val = val;
max_row = row;
}
}
if max_val < F::epsilon() {
return Err(LinalgError::SingularMatrixError(
"Cauchy matrix is singular or numerically rank-deficient".to_string(),
));
}
if max_row != col {
for k in 0..n {
let tmp = mat[[col, k]];
mat[[col, k]] = mat[[max_row, k]];
mat[[max_row, k]] = tmp;
}
let tmp_rhs = rhs[col];
rhs[col] = rhs[max_row];
rhs[max_row] = tmp_rhs;
perm.swap(col, max_row);
}
let pivot = mat[[col, col]];
for row in (col + 1)..n {
let factor = mat[[row, col]] / pivot;
for k in col..n {
let sub = factor * mat[[col, k]];
mat[[row, k]] -= sub;
}
let sub_rhs = factor * rhs[col];
rhs[row] -= sub_rhs;
}
}
let mut sol = Array1::<F>::zeros(n);
for i in (0..n).rev() {
let mut sum = rhs[i];
for j in (i + 1)..n {
sum -= mat[[i, j]] * sol[j];
}
let diag = mat[[i, i]];
if diag.abs() < F::epsilon() {
return Err(LinalgError::SingularMatrixError(
"Cauchy system: zero diagonal during back-substitution".to_string(),
));
}
sol[i] = sum / diag;
}
Ok(sol)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use scirs2_core::ndarray::array;
#[test]
fn test_cauchy_matrix_2x2() {
let x = array![1.0_f64, 2.0];
let y = array![0.0_f64, 3.0];
let c = cauchy_matrix(&x.view(), &y.view()).expect("cauchy_matrix failed");
assert_relative_eq!(c[[0, 0]], 1.0, epsilon = 1e-10);
assert_relative_eq!(c[[0, 1]], -0.5, epsilon = 1e-10);
assert_relative_eq!(c[[1, 0]], 0.5, epsilon = 1e-10);
assert_relative_eq!(c[[1, 1]], -1.0, epsilon = 1e-10);
}
#[test]
fn test_cauchy_det_2x2() {
let x = array![1.0_f64, 2.0];
let y = array![0.0_f64, 3.0];
let det = cauchy_det(&x.view(), &y.view()).expect("cauchy_det failed");
assert_relative_eq!(det, -0.75, epsilon = 1e-10);
}
#[test]
fn test_solve_cauchy() {
let x = array![1.0_f64, 2.0];
let y = array![0.0_f64, 3.0];
let b = array![0.0_f64, -1.5];
let sol = solve_cauchy(&x.view(), &y.view(), &b.view()).expect("solve_cauchy failed");
assert_relative_eq!(sol[0], 1.0, epsilon = 1e-8);
assert_relative_eq!(sol[1], 2.0, epsilon = 1e-8);
}
#[test]
fn test_cauchy_singular_when_xi_eq_yj() {
let x = array![1.0_f64, 2.0];
let y = array![1.0_f64, 3.0]; let result = cauchy_det(&x.view(), &y.view());
assert!(result.is_err());
}
}