use oxiblas_core::scalar::Field;
use oxiblas_matrix::{Mat, MatRef};
use crate::lu::{Lu, LuError};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SolveError {
NotSquare,
DimensionMismatch,
SingularMatrix,
}
impl core::fmt::Display for SolveError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::NotSquare => write!(f, "Matrix must be square"),
Self::DimensionMismatch => write!(f, "Matrix and vector dimensions do not match"),
Self::SingularMatrix => write!(f, "Matrix is singular"),
}
}
}
impl std::error::Error for SolveError {}
impl From<LuError> for SolveError {
fn from(e: LuError) -> Self {
match e {
LuError::Singular { .. } => Self::SingularMatrix,
LuError::NotSquare { .. } => Self::NotSquare,
LuError::DimensionMismatch { .. } => Self::DimensionMismatch,
}
}
}
pub fn solve<T: Field + bytemuck::Zeroable>(
a: MatRef<'_, T>,
b: MatRef<'_, T>,
) -> Result<Mat<T>, SolveError> {
let n = a.nrows();
if a.ncols() != n {
return Err(SolveError::NotSquare);
}
if b.nrows() != n {
return Err(SolveError::DimensionMismatch);
}
let lu = Lu::compute(a)?;
let x = lu.solve(b)?;
Ok(x)
}
pub fn solve_multiple<T: Field + bytemuck::Zeroable>(
a: MatRef<'_, T>,
b: MatRef<'_, T>,
) -> Result<Mat<T>, SolveError> {
let n = a.nrows();
let m = b.ncols();
if a.ncols() != n {
return Err(SolveError::NotSquare);
}
if b.nrows() != n {
return Err(SolveError::DimensionMismatch);
}
let lu = Lu::compute(a)?;
let mut x = Mat::zeros(n, m);
for col in 0..m {
let mut b_col = Mat::zeros(n, 1);
for i in 0..n {
b_col[(i, 0)] = b[(i, col)];
}
let x_col = lu.solve(b_col.as_ref())?;
for i in 0..n {
x[(i, col)] = x_col[(i, 0)];
}
}
Ok(x)
}
#[cfg(test)]
mod tests {
use super::*;
fn approx_eq(a: f64, b: f64, tol: f64) -> bool {
(a - b).abs() < tol
}
#[test]
fn test_solve_2x2() {
let a = Mat::from_rows(&[&[2.0f64, 1.0], &[1.0, 3.0]]);
let b = Mat::from_rows(&[&[5.0], &[7.0]]);
let x = solve(a.as_ref(), b.as_ref()).unwrap();
let ax0 = a[(0, 0)] * x[(0, 0)] + a[(0, 1)] * x[(1, 0)];
let ax1 = a[(1, 0)] * x[(0, 0)] + a[(1, 1)] * x[(1, 0)];
assert!(approx_eq(ax0, b[(0, 0)], 1e-10));
assert!(approx_eq(ax1, b[(1, 0)], 1e-10));
}
#[test]
fn test_solve_3x3() {
let a = Mat::from_rows(&[
&[1.0f64, 2.0, 3.0],
&[4.0, 5.0, 6.0],
&[7.0, 8.0, 10.0], ]);
let b = Mat::from_rows(&[&[14.0], &[32.0], &[50.0]]);
let x = solve(a.as_ref(), b.as_ref()).unwrap();
for i in 0..3 {
let mut ax_i = 0.0;
for j in 0..3 {
ax_i += a[(i, j)] * x[(j, 0)];
}
assert!(approx_eq(ax_i, b[(i, 0)], 1e-8));
}
}
#[test]
fn test_solve_identity() {
let a = Mat::from_rows(&[&[1.0f64, 0.0, 0.0], &[0.0, 1.0, 0.0], &[0.0, 0.0, 1.0]]);
let b = Mat::from_rows(&[&[1.0], &[2.0], &[3.0]]);
let x = solve(a.as_ref(), b.as_ref()).unwrap();
for i in 0..3 {
assert!(approx_eq(x[(i, 0)], b[(i, 0)], 1e-10));
}
}
#[test]
fn test_solve_multiple_rhs() {
let a = Mat::from_rows(&[&[2.0f64, 1.0], &[1.0, 3.0]]);
let b = Mat::from_rows(&[&[5.0, 3.0], &[7.0, 5.0]]);
let x = solve_multiple(a.as_ref(), b.as_ref()).unwrap();
for col in 0..2 {
for i in 0..2 {
let mut ax_i = 0.0;
for j in 0..2 {
ax_i += a[(i, j)] * x[(j, col)];
}
assert!(approx_eq(ax_i, b[(i, col)], 1e-10));
}
}
}
#[test]
fn test_solve_singular() {
let a = Mat::from_rows(&[
&[1.0f64, 2.0],
&[2.0, 4.0], ]);
let b = Mat::from_rows(&[&[1.0], &[2.0]]);
let result = solve(a.as_ref(), b.as_ref());
assert!(matches!(result, Err(SolveError::SingularMatrix)));
}
#[test]
fn test_solve_dimension_mismatch() {
let a = Mat::from_rows(&[&[1.0f64, 2.0], &[3.0, 4.0]]);
let b = Mat::from_rows(&[&[1.0], &[2.0], &[3.0]]);
let result = solve(a.as_ref(), b.as_ref());
assert!(matches!(result, Err(SolveError::DimensionMismatch)));
}
#[test]
fn test_solve_f32() {
let a = Mat::from_rows(&[&[2.0f32, 1.0], &[1.0, 3.0]]);
let b = Mat::from_rows(&[&[5.0f32], &[7.0]]);
let x = solve(a.as_ref(), b.as_ref()).unwrap();
let ax0 = a[(0, 0)] * x[(0, 0)] + a[(0, 1)] * x[(1, 0)];
let ax1 = a[(1, 0)] * x[(0, 0)] + a[(1, 1)] * x[(1, 0)];
assert!((ax0 - b[(0, 0)]).abs() < 1e-5);
assert!((ax1 - b[(1, 0)]).abs() < 1e-5);
}
}