#[cfg(feature = "scirs2")]
use crate::core::error::{Error, Result};
#[cfg(feature = "scirs2")]
use crate::dataframe::DataFrame;
#[cfg(feature = "scirs2")]
use crate::scirs2_integration::conversion::{array2_to_dataframe, dataframe_to_array2};
#[cfg(feature = "scirs2")]
use crate::series::Series;
#[cfg(feature = "scirs2")]
#[derive(Debug, Clone)]
pub struct EigResult {
pub values: Vec<f64>,
pub vectors: DataFrame,
}
#[cfg(feature = "scirs2")]
#[derive(Debug, Clone)]
pub struct SvdResult {
pub u: DataFrame,
pub s: Vec<f64>,
pub vt: DataFrame,
}
#[cfg(feature = "scirs2")]
#[derive(Debug, Clone)]
pub struct QrResult {
pub q: DataFrame,
pub r: DataFrame,
}
#[cfg(feature = "scirs2")]
#[derive(Debug, Clone)]
pub struct LuResult {
pub l: DataFrame,
pub u: DataFrame,
}
#[cfg(feature = "scirs2")]
#[derive(Debug, Clone)]
pub struct LstsqDataFrameResult {
pub solution: DataFrame,
pub residuals: Vec<f64>,
pub rank: usize,
}
#[cfg(feature = "scirs2")]
pub struct SciRS2LinAlg;
#[cfg(feature = "scirs2")]
impl SciRS2LinAlg {
pub fn matmul(a: &DataFrame, b: &DataFrame) -> Result<DataFrame> {
let a_col_names = a.column_names();
let a_cols: Vec<&str> = a_col_names.iter().map(|s| s.as_str()).collect();
let b_col_names = b.column_names();
let b_cols: Vec<&str> = b_col_names.iter().map(|s| s.as_str()).collect();
let arr_a = dataframe_to_array2(a, &a_cols)?;
let arr_b = dataframe_to_array2(b, &b_cols)?;
let (m, k_a) = arr_a.dim();
let (k_b, n) = arr_b.dim();
if k_a != k_b {
return Err(Error::InvalidInput(format!(
"Matrix dimensions incompatible for multiplication: ({}, {}) x ({}, {})",
m, k_a, k_b, n
)));
}
let result = arr_a.dot(&arr_b);
let col_names: Vec<String> = (0..n).map(|i| format!("c{}", i)).collect();
array2_to_dataframe(&result, col_names)
}
pub fn eig(df: &DataFrame) -> Result<EigResult> {
use scirs2_linalg::eigh;
let cols: Vec<String> = df.column_names();
let col_refs: Vec<&str> = cols.iter().map(|s| s.as_str()).collect();
let arr = dataframe_to_array2(df, &col_refs)?;
let (n_rows, n_cols) = arr.dim();
if n_rows != n_cols {
return Err(Error::InvalidInput(format!(
"Eigendecomposition requires a square matrix, got ({}, {})",
n_rows, n_cols
)));
}
let (eigenvalues, eigenvectors) = eigh(&arr.view(), None)
.map_err(|e| Error::OperationFailed(format!("SciRS2 eigh failed: {}", e)))?;
let values: Vec<f64> = eigenvalues.iter().copied().collect();
let ev_col_names: Vec<String> = (0..n_cols).map(|i| format!("ev{}", i)).collect();
let vectors_df = array2_to_dataframe(&eigenvectors, ev_col_names)?;
Ok(EigResult {
values,
vectors: vectors_df,
})
}
pub fn svd(df: &DataFrame) -> Result<SvdResult> {
use scirs2_linalg::svd;
let cols: Vec<String> = df.column_names();
let col_refs: Vec<&str> = cols.iter().map(|s| s.as_str()).collect();
let arr = dataframe_to_array2(df, &col_refs)?;
let (m, n) = arr.dim();
let (u, s, vt) = svd(&arr.view(), false, None)
.map_err(|e| Error::OperationFailed(format!("SciRS2 svd failed: {}", e)))?;
let k = s.len();
let singular_values: Vec<f64> = s.iter().copied().collect();
let u_col_names: Vec<String> = (0..u.ncols()).map(|i| format!("u{}", i)).collect();
let vt_col_names: Vec<String> = (0..vt.ncols()).map(|i| format!("v{}", i)).collect();
let u_df = array2_to_dataframe(&u, u_col_names)?;
let vt_df = array2_to_dataframe(&vt, vt_col_names)?;
Ok(SvdResult {
u: u_df,
s: singular_values,
vt: vt_df,
})
}
pub fn solve(a: &DataFrame, b: &DataFrame) -> Result<DataFrame> {
use scirs2_linalg::solve_multiple;
let a_cols: Vec<String> = a.column_names();
let b_cols: Vec<String> = b.column_names();
let a_col_refs: Vec<&str> = a_cols.iter().map(|s| s.as_str()).collect();
let b_col_refs: Vec<&str> = b_cols.iter().map(|s| s.as_str()).collect();
let arr_a = dataframe_to_array2(a, &a_col_refs)?;
let arr_b = dataframe_to_array2(b, &b_col_refs)?;
let (n_rows, n_cols) = arr_a.dim();
if n_rows != n_cols {
return Err(Error::InvalidInput(format!(
"Coefficient matrix must be square, got ({}, {})",
n_rows, n_cols
)));
}
let x = solve_multiple(&arr_a.view(), &arr_b.view(), None)
.map_err(|e| Error::OperationFailed(format!("SciRS2 solve failed: {}", e)))?;
let k = x.ncols();
let x_col_names: Vec<String> = (0..k).map(|i| format!("x{}", i)).collect();
array2_to_dataframe(&x, x_col_names)
}
pub fn inv(df: &DataFrame) -> Result<DataFrame> {
use scirs2_linalg::inv;
let cols: Vec<String> = df.column_names();
let col_refs: Vec<&str> = cols.iter().map(|s| s.as_str()).collect();
let arr = dataframe_to_array2(df, &col_refs)?;
let (n_rows, n_cols) = arr.dim();
if n_rows != n_cols {
return Err(Error::InvalidInput(format!(
"Matrix inverse requires a square matrix, got ({}, {})",
n_rows, n_cols
)));
}
let inv_arr = inv(&arr.view(), None)
.map_err(|e| Error::OperationFailed(format!("SciRS2 inv failed: {}", e)))?;
array2_to_dataframe(&inv_arr, cols)
}
pub fn det(df: &DataFrame) -> Result<f64> {
use scirs2_linalg::det;
let cols: Vec<String> = df.column_names();
let col_refs: Vec<&str> = cols.iter().map(|s| s.as_str()).collect();
let arr = dataframe_to_array2(df, &col_refs)?;
let (n_rows, n_cols) = arr.dim();
if n_rows != n_cols {
return Err(Error::InvalidInput(format!(
"Determinant requires a square matrix, got ({}, {})",
n_rows, n_cols
)));
}
det(&arr.view(), None)
.map_err(|e| Error::OperationFailed(format!("SciRS2 det failed: {}", e)))
}
pub fn qr(df: &DataFrame) -> Result<QrResult> {
use scirs2_linalg::qr;
let cols: Vec<String> = df.column_names();
let col_refs: Vec<&str> = cols.iter().map(|s| s.as_str()).collect();
let arr = dataframe_to_array2(df, &col_refs)?;
let (q_arr, r_arr) = qr(&arr.view(), None)
.map_err(|e| Error::OperationFailed(format!("SciRS2 qr failed: {}", e)))?;
let q_col_names: Vec<String> = (0..q_arr.ncols()).map(|i| format!("q{}", i)).collect();
let r_col_names: Vec<String> = (0..r_arr.ncols()).map(|i| format!("r{}", i)).collect();
let q_df = array2_to_dataframe(&q_arr, q_col_names)?;
let r_df = array2_to_dataframe(&r_arr, r_col_names)?;
Ok(QrResult { q: q_df, r: r_df })
}
pub fn cholesky(df: &DataFrame) -> Result<DataFrame> {
use scirs2_linalg::cholesky;
let cols: Vec<String> = df.column_names();
let col_refs: Vec<&str> = cols.iter().map(|s| s.as_str()).collect();
let arr = dataframe_to_array2(df, &col_refs)?;
let (n_rows, n_cols) = arr.dim();
if n_rows != n_cols {
return Err(Error::InvalidInput(format!(
"Cholesky decomposition requires a square matrix, got ({}, {})",
n_rows, n_cols
)));
}
let l = cholesky(&arr.view(), None)
.map_err(|e| Error::OperationFailed(format!("SciRS2 cholesky failed: {}", e)))?;
array2_to_dataframe(&l, cols)
}
pub fn lu(df: &DataFrame) -> Result<LuResult> {
use scirs2_linalg::lu;
let cols: Vec<String> = df.column_names();
let col_refs: Vec<&str> = cols.iter().map(|s| s.as_str()).collect();
let arr = dataframe_to_array2(df, &col_refs)?;
let (_p_arr, l_arr, u_arr) = lu(&arr.view(), None)
.map_err(|e| Error::OperationFailed(format!("SciRS2 lu failed: {}", e)))?;
let l_col_names: Vec<String> = (0..l_arr.ncols()).map(|i| format!("l{}", i)).collect();
let u_col_names: Vec<String> = (0..u_arr.ncols()).map(|i| format!("u{}", i)).collect();
let l_df = array2_to_dataframe(&l_arr, l_col_names)?;
let u_df = array2_to_dataframe(&u_arr, u_col_names)?;
Ok(LuResult { l: l_df, u: u_df })
}
pub fn lstsq(a: &DataFrame, b: &DataFrame) -> Result<LstsqDataFrameResult> {
use scirs2_linalg::lstsq;
let a_cols: Vec<String> = a.column_names();
let b_cols: Vec<String> = b.column_names();
let a_col_refs: Vec<&str> = a_cols.iter().map(|s| s.as_str()).collect();
let b_col_refs: Vec<&str> = b_cols.iter().map(|s| s.as_str()).collect();
let arr_a = dataframe_to_array2(a, &a_col_refs)?;
let arr_b = dataframe_to_array2(b, &b_col_refs)?;
let (m_a, _n) = arr_a.dim();
let (m_b, k) = arr_b.dim();
if m_a != m_b {
return Err(Error::InvalidInput(format!(
"lstsq: A has {} rows but b has {} rows",
m_a, m_b
)));
}
let mut solution_cols: Vec<Vec<f64>> = Vec::with_capacity(k);
let mut residuals: Vec<f64> = Vec::with_capacity(k);
let mut final_rank = 0usize;
for j in 0..k {
let col_b: scirs2_core::ndarray::Array1<f64> = arr_b.column(j).to_owned();
let res = lstsq(&arr_a.view(), &col_b.view(), None)
.map_err(|e| Error::OperationFailed(format!("SciRS2 lstsq failed: {}", e)))?;
solution_cols.push(res.x.iter().copied().collect());
residuals.push(res.residuals);
final_rank = res.rank;
}
let n_sol = if solution_cols.is_empty() {
0
} else {
solution_cols[0].len()
};
let col_names: Vec<String> = (0..k).map(|i| format!("x{}", i)).collect();
let mut sol_df = DataFrame::new();
for (j, col_name) in col_names.iter().enumerate() {
let vals = solution_cols[j].clone();
let series = Series::new(vals, Some(col_name.clone()))?;
sol_df.add_column(col_name.clone(), series)?;
}
let _ = n_sol;
Ok(LstsqDataFrameResult {
solution: sol_df,
residuals,
rank: final_rank,
})
}
pub fn matrix_norm(df: &DataFrame, ord: &str) -> Result<f64> {
use scirs2_linalg::matrix_norm;
let cols: Vec<String> = df.column_names();
let col_refs: Vec<&str> = cols.iter().map(|s| s.as_str()).collect();
let arr = dataframe_to_array2(df, &col_refs)?;
matrix_norm(&arr.view(), ord, None)
.map_err(|e| Error::OperationFailed(format!("SciRS2 matrix_norm failed: {}", e)))
}
pub fn matrix_rank(df: &DataFrame) -> Result<usize> {
use scirs2_linalg::matrix_rank;
let cols: Vec<String> = df.column_names();
let col_refs: Vec<&str> = cols.iter().map(|s| s.as_str()).collect();
let arr = dataframe_to_array2(df, &col_refs)?;
matrix_rank(&arr.view(), None, None)
.map_err(|e| Error::OperationFailed(format!("SciRS2 matrix_rank failed: {}", e)))
}
pub fn condition_number(df: &DataFrame) -> Result<f64> {
use scirs2_linalg::cond;
let cols: Vec<String> = df.column_names();
let col_refs: Vec<&str> = cols.iter().map(|s| s.as_str()).collect();
let arr = dataframe_to_array2(df, &col_refs)?;
let (n_rows, n_cols) = arr.dim();
if n_rows != n_cols {
return Err(Error::InvalidInput(format!(
"Condition number requires a square matrix, got ({}, {})",
n_rows, n_cols
)));
}
cond(&arr.view(), Some("2"), None)
.map_err(|e| Error::OperationFailed(format!("SciRS2 cond failed: {}", e)))
}
pub fn pinv(df: &DataFrame) -> Result<DataFrame> {
use scirs2_core::ndarray::Array2;
use scirs2_linalg::lstsq;
let cols: Vec<String> = df.column_names();
let col_refs: Vec<&str> = cols.iter().map(|s| s.as_str()).collect();
let arr_a = dataframe_to_array2(df, &col_refs)?;
let (m, n) = arr_a.dim();
if m == 0 || n == 0 {
return Err(Error::EmptyData("pinv: input matrix is empty".to_string()));
}
let eye = Array2::<f64>::eye(m);
let mut pinv_cols: Vec<Vec<f64>> = Vec::with_capacity(m);
for j in 0..m {
let col_b: scirs2_core::ndarray::Array1<f64> = eye.column(j).to_owned();
let res = lstsq(&arr_a.view(), &col_b.view(), None).map_err(|e| {
Error::OperationFailed(format!("SciRS2 lstsq (pinv) failed: {}", e))
})?;
pinv_cols.push(res.x.iter().copied().collect());
}
let col_names: Vec<String> = (0..m).map(|i| format!("pi{}", i)).collect();
let mut result_df = DataFrame::new();
for (j, col_name) in col_names.iter().enumerate() {
let vals = pinv_cols[j].clone();
let series = Series::new(vals, Some(col_name.clone()))?;
result_df.add_column(col_name.clone(), series)?;
}
Ok(result_df)
}
}