use crate::diff::diff;
use crate::kernel::{ExprId, ExprPool};
use crate::simplify::engine::simplify;
use std::fmt;
pub mod eigen;
pub mod normal_form;
mod smith;
mod smith_poly;
pub use eigen::{
characteristic_polynomial_lambda_minus_m, diagonalize, eigenvalues, eigenvectors, EigenError,
};
pub use normal_form::{
hermite_form, hermite_form_poly, smith_form, smith_form_poly, IntegerMatrix, NormalFormError,
PolyMatrixQ, RatUniPoly,
};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Matrix {
data: Vec<ExprId>,
pub rows: usize,
pub cols: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum MatrixError {
DimensionMismatch { msg: String },
NotSquare,
SingularMatrix,
}
impl fmt::Display for MatrixError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
MatrixError::DimensionMismatch { msg } => write!(f, "dimension mismatch: {msg}"),
MatrixError::NotSquare => write!(f, "matrix is not square"),
MatrixError::SingularMatrix => write!(f, "matrix is singular"),
}
}
}
impl std::error::Error for MatrixError {}
impl crate::errors::AlkahestError for MatrixError {
fn code(&self) -> &'static str {
match self {
MatrixError::DimensionMismatch { .. } => "E-MAT-001",
MatrixError::NotSquare => "E-MAT-002",
MatrixError::SingularMatrix => "E-MAT-003",
}
}
fn remediation(&self) -> Option<&'static str> {
match self {
MatrixError::DimensionMismatch { .. } => Some(
"ensure all rows have the same column count and operand dimensions match",
),
MatrixError::NotSquare => Some(
"determinant and inverse require a square matrix; use the pseudo-inverse for rectangular matrices",
),
MatrixError::SingularMatrix => Some(
"the matrix has a zero determinant; check your system of equations for linear dependence",
),
}
}
}
impl Matrix {
pub fn new(rows: Vec<Vec<ExprId>>) -> Result<Self, MatrixError> {
if rows.is_empty() {
return Ok(Matrix {
data: vec![],
rows: 0,
cols: 0,
});
}
let cols = rows[0].len();
for r in &rows {
if r.len() != cols {
return Err(MatrixError::DimensionMismatch {
msg: format!("expected {cols} columns, got {}", r.len()),
});
}
}
let nrows = rows.len();
let data: Vec<ExprId> = rows.into_iter().flatten().collect();
Ok(Matrix {
data,
rows: nrows,
cols,
})
}
pub fn zeros(rows: usize, cols: usize, pool: &ExprPool) -> Self {
let zero = pool.integer(0_i32);
Matrix {
data: vec![zero; rows * cols],
rows,
cols,
}
}
pub fn identity(n: usize, pool: &ExprPool) -> Self {
let zero = pool.integer(0_i32);
let one = pool.integer(1_i32);
let mut data = vec![zero; n * n];
for i in 0..n {
data[i * n + i] = one;
}
Matrix {
data,
rows: n,
cols: n,
}
}
pub fn get(&self, r: usize, c: usize) -> ExprId {
self.data[r * self.cols + c]
}
pub fn set(&mut self, r: usize, c: usize, val: ExprId) {
self.data[r * self.cols + c] = val;
}
pub fn row(&self, r: usize) -> Vec<ExprId> {
self.data[r * self.cols..(r + 1) * self.cols].to_vec()
}
pub fn col(&self, c: usize) -> Vec<ExprId> {
(0..self.rows).map(|r| self.get(r, c)).collect()
}
pub fn transpose(&self) -> Self {
let mut data = Vec::with_capacity(self.rows * self.cols);
for c in 0..self.cols {
for r in 0..self.rows {
data.push(self.get(r, c));
}
}
Matrix {
data,
rows: self.cols,
cols: self.rows,
}
}
pub fn add(&self, other: &Matrix, pool: &ExprPool) -> Result<Matrix, MatrixError> {
self.check_same_shape(other)?;
let data = self
.data
.iter()
.zip(other.data.iter())
.map(|(&a, &b)| pool.add(vec![a, b]))
.collect();
Ok(Matrix {
data,
rows: self.rows,
cols: self.cols,
})
}
pub fn sub(&self, other: &Matrix, pool: &ExprPool) -> Result<Matrix, MatrixError> {
self.check_same_shape(other)?;
let neg_one = pool.integer(-1_i32);
let data = self
.data
.iter()
.zip(other.data.iter())
.map(|(&a, &b)| {
let neg_b = pool.mul(vec![neg_one, b]);
pool.add(vec![a, neg_b])
})
.collect();
Ok(Matrix {
data,
rows: self.rows,
cols: self.cols,
})
}
pub fn mul(&self, other: &Matrix, pool: &ExprPool) -> Result<Matrix, MatrixError> {
if self.cols != other.rows {
return Err(MatrixError::DimensionMismatch {
msg: format!(
"cannot multiply {}×{} by {}×{}",
self.rows, self.cols, other.rows, other.cols
),
});
}
let m = self.rows;
let n = other.cols;
let k = self.cols;
let mut data = Vec::with_capacity(m * n);
for r in 0..m {
for c in 0..n {
let terms: Vec<ExprId> = (0..k)
.map(|i| pool.mul(vec![self.get(r, i), other.get(i, c)]))
.collect();
let entry = if terms.is_empty() {
pool.integer(0_i32)
} else if terms.len() == 1 {
terms[0]
} else {
pool.add(terms)
};
data.push(entry);
}
}
Ok(Matrix {
data,
rows: m,
cols: n,
})
}
pub fn scale(&self, scalar: ExprId, pool: &ExprPool) -> Matrix {
let data = self
.data
.iter()
.map(|&e| pool.mul(vec![scalar, e]))
.collect();
Matrix {
data,
rows: self.rows,
cols: self.cols,
}
}
pub fn simplify_entries(&self, pool: &ExprPool) -> Matrix {
let data = self.data.iter().map(|&e| simplify(e, pool).value).collect();
Matrix {
data,
rows: self.rows,
cols: self.cols,
}
}
pub fn det(&self, pool: &ExprPool) -> Result<ExprId, MatrixError> {
if self.rows != self.cols {
return Err(MatrixError::NotSquare);
}
let n = self.rows;
if n == 0 {
return Ok(pool.integer(1_i32));
}
if n == 1 {
return Ok(self.get(0, 0));
}
if n == 2 {
let ad = pool.mul(vec![self.get(0, 0), self.get(1, 1)]);
let bc = pool.mul(vec![self.get(0, 1), self.get(1, 0)]);
let neg_bc = pool.mul(vec![pool.integer(-1_i32), bc]);
return Ok(simplify(pool.add(vec![ad, neg_bc]), pool).value);
}
let mut terms: Vec<ExprId> = Vec::new();
for j in 0..n {
let minor = self.minor(0, j);
let minor_det = minor.det(pool)?;
let sign = if j % 2 == 0 {
pool.integer(1_i32)
} else {
pool.integer(-1_i32)
};
terms.push(pool.mul(vec![sign, self.get(0, j), minor_det]));
}
Ok(simplify(pool.add(terms), pool).value)
}
fn minor(&self, skip_row: usize, skip_col: usize) -> Matrix {
let n = self.rows;
let mut data = Vec::with_capacity((n - 1) * (n - 1));
for r in 0..n {
if r == skip_row {
continue;
}
for c in 0..n {
if c == skip_col {
continue;
}
data.push(self.get(r, c));
}
}
Matrix {
data,
rows: n - 1,
cols: n - 1,
}
}
fn check_same_shape(&self, other: &Matrix) -> Result<(), MatrixError> {
if self.rows != other.rows || self.cols != other.cols {
Err(MatrixError::DimensionMismatch {
msg: format!(
"{}×{} vs {}×{}",
self.rows, self.cols, other.rows, other.cols
),
})
} else {
Ok(())
}
}
pub fn entries(&self) -> &[ExprId] {
&self.data
}
pub fn to_nested(&self) -> Vec<Vec<ExprId>> {
(0..self.rows).map(|r| self.row(r)).collect()
}
pub fn characteristic_polynomial_lambda_minus_m(
&self,
pool: &ExprPool,
) -> Result<(ExprId, ExprId), EigenError> {
eigen::characteristic_polynomial_lambda_minus_m(self, pool)
}
pub fn eigenvalues(&self, pool: &ExprPool) -> Result<Vec<(ExprId, usize)>, EigenError> {
eigen::eigenvalues(self, pool)
}
pub fn eigenvectors(
&self,
pool: &ExprPool,
) -> Result<Vec<(ExprId, usize, Vec<Matrix>)>, EigenError> {
eigen::eigenvectors(self, pool)
}
pub fn diagonalize(&self, pool: &ExprPool) -> Result<(Matrix, Matrix), EigenError> {
eigen::diagonalize(self, pool)
}
}
pub fn jacobian(
f_vec: &[ExprId],
x_vec: &[ExprId],
pool: &ExprPool,
) -> Result<Matrix, crate::diff::diff_impl::DiffError> {
let m = f_vec.len();
let n = x_vec.len();
let mut data = Vec::with_capacity(m * n);
for &f in f_vec {
for &x in x_vec {
let df = diff(f, x, pool)?.value;
data.push(df);
}
}
Ok(Matrix {
data,
rows: m,
cols: n,
})
}
impl Matrix {
pub fn display(&self, pool: &ExprPool) -> String {
let rows: Vec<String> = (0..self.rows)
.map(|r| {
let entries: Vec<String> = self
.row(r)
.into_iter()
.map(|e| pool.display(e).to_string())
.collect();
format!("[{}]", entries.join(", "))
})
.collect();
format!("[{}]", rows.join(", "))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kernel::{Domain, ExprPool};
fn p() -> ExprPool {
ExprPool::new()
}
#[test]
fn identity_2x2() {
let pool = p();
let id = Matrix::identity(2, &pool);
assert_eq!(id.rows, 2);
assert_eq!(id.cols, 2);
assert_eq!(id.get(0, 0), pool.integer(1_i32));
assert_eq!(id.get(0, 1), pool.integer(0_i32));
assert_eq!(id.get(1, 0), pool.integer(0_i32));
assert_eq!(id.get(1, 1), pool.integer(1_i32));
}
#[test]
fn transpose_2x3() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let y = pool.symbol("y", Domain::Real);
let z = pool.symbol("z", Domain::Real);
let a = pool.integer(1_i32);
let b = pool.integer(2_i32);
let c = pool.integer(3_i32);
let m = Matrix::new(vec![vec![x, y, z], vec![a, b, c]]).unwrap();
let t = m.transpose();
assert_eq!(t.rows, 3);
assert_eq!(t.cols, 2);
assert_eq!(t.get(0, 0), x);
assert_eq!(t.get(1, 1), b);
}
#[test]
fn add_matrices() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let one = pool.integer(1_i32);
let m1 = Matrix::new(vec![vec![x, one]]).unwrap();
let m2 = Matrix::new(vec![vec![one, x]]).unwrap();
let result = m1.add(&m2, &pool).unwrap();
let r00_str = pool.display(result.get(0, 0)).to_string();
assert!(
r00_str.contains("x") && r00_str.contains("1"),
"got: {r00_str}"
);
}
#[test]
fn mul_2x2() {
let pool = p();
let id = Matrix::identity(2, &pool);
let x = pool.symbol("x", Domain::Real);
let y = pool.symbol("y", Domain::Real);
let m = Matrix::new(vec![vec![x, y], vec![y, x]]).unwrap();
let result = id.mul(&m, &pool).unwrap().simplify_entries(&pool);
assert_eq!(result.get(0, 0), x);
assert_eq!(result.get(0, 1), y);
}
#[test]
fn det_2x2() {
let pool = p();
let a = pool.symbol("a", Domain::Real);
let b = pool.symbol("b", Domain::Real);
let c = pool.symbol("c", Domain::Real);
let d = pool.symbol("d", Domain::Real);
let m = Matrix::new(vec![vec![a, b], vec![c, d]]).unwrap();
let det = m.det(&pool).unwrap();
let s = pool.display(det).to_string();
assert!(s.contains("a") && s.contains("d"), "got: {s}");
}
#[test]
fn det_3x3_identity_is_one() {
let pool = p();
let id = Matrix::identity(3, &pool);
let det = id.det(&pool).unwrap();
assert_eq!(det, pool.integer(1_i32));
}
#[test]
fn jacobian_linear() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let y = pool.symbol("y", Domain::Real);
let neg_y = pool.mul(vec![pool.integer(-1_i32), y]);
let f1 = pool.add(vec![x, y]);
let f2 = pool.add(vec![x, neg_y]);
let j = jacobian(&[f1, f2], &[x, y], &pool).unwrap();
assert_eq!(j.rows, 2);
assert_eq!(j.cols, 2);
assert_eq!(j.get(0, 0), pool.integer(1_i32)); assert_eq!(j.get(0, 1), pool.integer(1_i32)); assert_eq!(j.get(1, 0), pool.integer(1_i32)); assert_eq!(j.get(1, 1), pool.integer(-1_i32)); }
#[test]
fn jacobian_quadratic() {
let pool = p();
let x = pool.symbol("x", Domain::Real);
let y = pool.symbol("y", Domain::Real);
let f1 = pool.pow(x, pool.integer(2_i32));
let f2 = pool.pow(y, pool.integer(2_i32));
let j = jacobian(&[f1, f2], &[x, y], &pool).unwrap();
assert_eq!(j.get(0, 1), pool.integer(0_i32));
assert_eq!(j.get(1, 0), pool.integer(0_i32));
}
}