use crate::ring::{Field, Ring};
use alloc::vec::Vec;
use core::fmt;
#[derive(Clone, PartialEq, Eq, Hash, Debug)]
pub struct Matrix<T> {
rows: usize,
cols: usize,
data: Vec<T>,
}
impl<T> Matrix<T> {
pub fn new(rows: usize, cols: usize, data: Vec<T>) -> Matrix<T> {
assert_eq!(rows * cols, data.len(), "Matrix::new: data length mismatch");
Matrix { rows, cols, data }
}
pub fn from_rows(rows: Vec<Vec<T>>) -> Matrix<T> {
let r = rows.len();
let c = rows.first().map_or(0, |row| row.len());
let mut data = Vec::with_capacity(r * c);
for row in rows {
assert_eq!(row.len(), c, "Matrix::from_rows: ragged rows");
data.extend(row);
}
Matrix {
rows: r,
cols: c,
data,
}
}
#[inline]
pub fn rows(&self) -> usize {
self.rows
}
#[inline]
pub fn cols(&self) -> usize {
self.cols
}
#[inline]
pub fn is_square(&self) -> bool {
self.rows == self.cols
}
#[inline]
pub fn get(&self, row: usize, col: usize) -> &T {
&self.data[row * self.cols + col]
}
#[inline]
pub fn set(&mut self, row: usize, col: usize, value: T) {
self.data[row * self.cols + col] = value;
}
#[inline]
pub fn as_slice(&self) -> &[T] {
&self.data
}
}
impl<T: Clone> Matrix<T> {
pub fn filled(value: T, rows: usize, cols: usize) -> Matrix<T> {
Matrix {
rows,
cols,
data: alloc::vec![value; rows * cols],
}
}
pub fn transpose(&self) -> Matrix<T> {
let mut data = self.data.clone();
for i in 0..self.rows {
for j in 0..self.cols {
data[j * self.rows + i] = self.data[i * self.cols + j].clone();
}
}
Matrix {
rows: self.cols,
cols: self.rows,
data,
}
}
}
impl<T: Clone + Default> Matrix<T> {
pub fn zeros(rows: usize, cols: usize) -> Matrix<T> {
Matrix {
rows,
cols,
data: alloc::vec![T::default(); rows * cols],
}
}
}
impl<T: Ring> Matrix<T> {
pub fn zeros_like(sample: &T, rows: usize, cols: usize) -> Matrix<T> {
Matrix::filled(sample.zero(), rows, cols)
}
pub fn identity_like(sample: &T, n: usize) -> Matrix<T> {
let mut m = Matrix::zeros_like(sample, n, n);
let one = sample.one();
for i in 0..n {
m.set(i, i, one.clone());
}
m
}
}
impl<T: Ring> Matrix<T> {
pub fn add(&self, rhs: &Matrix<T>) -> Matrix<T> {
assert!(
self.rows == rhs.rows && self.cols == rhs.cols,
"Matrix::add: shape mismatch"
);
Matrix {
rows: self.rows,
cols: self.cols,
data: self
.data
.iter()
.zip(&rhs.data)
.map(|(a, b)| a.clone() + b.clone())
.collect(),
}
}
pub fn sub(&self, rhs: &Matrix<T>) -> Matrix<T> {
assert!(
self.rows == rhs.rows && self.cols == rhs.cols,
"Matrix::sub: shape mismatch"
);
Matrix {
rows: self.rows,
cols: self.cols,
data: self
.data
.iter()
.zip(&rhs.data)
.map(|(a, b)| a.clone() - b.clone())
.collect(),
}
}
pub fn mul(&self, rhs: &Matrix<T>) -> Matrix<T> {
assert_eq!(self.cols, rhs.rows, "Matrix::mul: inner dimension mismatch");
let out_len = self.rows * rhs.cols;
let data: Vec<T> = match self.data.first().or_else(|| rhs.data.first()) {
Some(sample) => alloc::vec![sample.zero(); out_len],
None => {
assert_eq!(
out_len, 0,
"Matrix::mul: cannot infer the ring's zero from empty operands"
);
Vec::new()
}
};
let mut out = Matrix {
rows: self.rows,
cols: rhs.cols,
data,
};
for i in 0..self.rows {
for k in 0..self.cols {
let a = self.data[i * self.cols + k].clone();
for j in 0..rhs.cols {
let prod = a.clone() * rhs.data[k * rhs.cols + j].clone();
let slot = &mut out.data[i * rhs.cols + j];
*slot = slot.clone() + prod;
}
}
}
out
}
pub fn scalar_mul(&self, scalar: &T) -> Matrix<T> {
Matrix {
rows: self.rows,
cols: self.cols,
data: self
.data
.iter()
.map(|a| a.clone() * scalar.clone())
.collect(),
}
}
}
impl<T: Ring> Matrix<T> {
pub fn neg(&self) -> Matrix<T> {
Matrix {
rows: self.rows,
cols: self.cols,
data: self.data.iter().map(|a| -a.clone()).collect(),
}
}
}
impl<T: fmt::Display> fmt::Display for Matrix<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for i in 0..self.rows {
f.write_str("[")?;
for j in 0..self.cols {
if j > 0 {
f.write_str(", ")?;
}
write!(f, "{}", self.get(i, j))?;
}
f.write_str("]")?;
if i + 1 < self.rows {
f.write_str("\n")?;
}
}
Ok(())
}
}
#[cfg(feature = "int")]
impl Matrix<crate::int::Int> {
pub fn identity(n: usize) -> Matrix<crate::int::Int> {
use crate::int::Int;
let mut m = Matrix::zeros(n, n);
for i in 0..n {
m.set(i, i, Int::ONE);
}
m
}
pub fn determinant(&self) -> crate::int::Int {
use crate::int::Int;
assert!(self.is_square(), "determinant: matrix must be square");
let n = self.rows;
if n == 0 {
return Int::ONE;
}
let idx = |i: usize, j: usize| i * n + j;
let mut a = self.data.clone();
let mut prev = Int::ONE;
let mut sign = false; for k in 0..n - 1 {
if a[idx(k, k)].is_zero() {
match (k + 1..n).find(|&r| !a[idx(r, k)].is_zero()) {
Some(r) => {
for c in 0..n {
a.swap(idx(k, c), idx(r, c));
}
sign = !sign;
}
None => return Int::ZERO,
}
}
for i in k + 1..n {
for j in k + 1..n {
let num = Int::sub(
&Int::mul(&a[idx(i, j)], &a[idx(k, k)]),
&Int::mul(&a[idx(i, k)], &a[idx(k, j)]),
);
a[idx(i, j)] = Int::div_exact(&num, &prev); }
}
prev = a[idx(k, k)].clone();
}
let det = a[idx(n - 1, n - 1)].clone();
if sign { det.neg() } else { det }
}
}
#[cfg(feature = "rational")]
impl Matrix<crate::rational::Rational> {
pub fn identity(n: usize) -> Matrix<crate::rational::Rational> {
use crate::rational::Rational;
let mut m = Matrix::zeros(n, n);
for i in 0..n {
m.set(i, i, Rational::ONE);
}
m
}
fn eliminate(
data: &mut [crate::rational::Rational],
n: usize,
width: usize,
) -> (usize, crate::rational::Rational) {
use crate::rational::Rational;
let mut det = Rational::ONE;
let mut pivots = 0;
for col in 0..n {
let piv = (pivots..n).find(|&r| !data[r * width + col].is_zero());
let piv = match piv {
Some(p) => p,
None => {
det = Rational::ZERO;
continue;
}
};
if piv != pivots {
for c in 0..width {
data.swap(pivots * width + c, piv * width + c);
}
det = Rational::neg(&det);
}
let pivot = data[pivots * width + col].clone();
det = Rational::mul(&det, &pivot);
for c in 0..width {
data[pivots * width + c] = Rational::div(&data[pivots * width + c], &pivot);
}
for r in 0..n {
if r == pivots {
continue;
}
let factor = data[r * width + col].clone();
if factor.is_zero() {
continue;
}
for c in 0..width {
let prod = Rational::mul(&factor, &data[pivots * width + c]);
data[r * width + c] = Rational::sub(&data[r * width + c], &prod);
}
}
pivots += 1;
}
(pivots, det)
}
pub fn determinant(&self) -> crate::rational::Rational {
use crate::int::Int;
use crate::rational::Rational;
assert!(self.is_square(), "determinant: matrix must be square");
let n = self.rows;
if n == 0 {
return Rational::ONE;
}
let mut int_data = alloc::vec::Vec::with_capacity(n * n);
let mut scale = Int::ONE;
for i in 0..n {
let mut s = Int::ONE;
for j in 0..n {
s = s.lcm(self.get(i, j).denominator());
}
for j in 0..n {
let e = self.get(i, j);
let factor = s.div_exact(e.denominator()); int_data.push(e.numerator().mul(&factor));
}
scale = scale.mul(&s);
}
let int_det = Matrix::<Int>::new(n, n, int_data).determinant();
Rational::new(int_det, scale)
}
pub fn rank(&self) -> usize {
use crate::rational::Rational;
if self.rows == 0 || self.cols == 0 {
return 0;
}
let n = self.rows;
let width = self.cols;
let mut data = self.data.clone();
let mut pivots = 0;
for col in 0..self.cols {
if pivots == n {
break;
}
let piv = (pivots..n).find(|&r| !data[r * width + col].is_zero());
let piv = match piv {
Some(p) => p,
None => continue,
};
if piv != pivots {
for c in 0..width {
data.swap(pivots * width + c, piv * width + c);
}
}
let pivot = data[pivots * width + col].clone();
for r in pivots + 1..n {
let factor = Rational::div(&data[r * width + col], &pivot);
for c in col..width {
let prod = Rational::mul(&factor, &data[pivots * width + c]);
data[r * width + c] = Rational::sub(&data[r * width + c], &prod);
}
}
pivots += 1;
}
pivots
}
pub fn inverse(&self) -> Option<Matrix<crate::rational::Rational>> {
use crate::rational::Rational;
assert!(self.is_square(), "inverse: matrix must be square");
let n = self.rows;
let width = 2 * n;
let mut data = alloc::vec![Rational::ZERO; n * width];
for i in 0..n {
for j in 0..n {
data[i * width + j] = self.data[i * n + j].clone();
}
data[i * width + n + i] = Rational::ONE;
}
if let Some(sol) = fraction_free_solve(&data, n, n) {
let mut inv = Matrix::zeros(n, n);
for i in 0..n {
for j in 0..n {
inv.set(i, j, sol[i * n + j].clone());
}
}
return Some(inv);
}
let (pivots, _) = Self::eliminate(&mut data, n, width);
if pivots < n {
return None; }
let mut inv = Matrix::zeros(n, n);
for i in 0..n {
for j in 0..n {
inv.set(i, j, data[i * width + n + j].clone());
}
}
Some(inv)
}
pub fn solve(&self, b: &[crate::rational::Rational]) -> Option<Vec<crate::rational::Rational>> {
use crate::rational::Rational;
assert!(self.is_square(), "solve: matrix must be square");
let n = self.rows;
assert_eq!(b.len(), n, "solve: right-hand side length mismatch");
let width = n + 1;
let mut data = alloc::vec![Rational::ZERO; n * width];
for i in 0..n {
for j in 0..n {
data[i * width + j] = self.data[i * n + j].clone();
}
data[i * width + n] = b[i].clone();
}
if let Some(sol) = fraction_free_solve(&data, n, 1) {
return Some(sol); }
let (pivots, _) = Self::eliminate(&mut data, n, width);
if pivots < n {
return None;
}
Some((0..n).map(|i| data[i * width + n].clone()).collect())
}
}
#[cfg(feature = "rational")]
fn fraction_free_solve(
aug: &[crate::rational::Rational],
n: usize,
extra: usize,
) -> Option<alloc::vec::Vec<crate::rational::Rational>> {
use crate::int::Int;
use crate::rational::Rational;
let width = n + extra;
let mut m: alloc::vec::Vec<Int> = alloc::vec::Vec::with_capacity(n * width);
for i in 0..n {
let mut s = Int::ONE;
for j in 0..width {
s = s.lcm(aug[i * width + j].denominator());
}
for j in 0..width {
let e = &aug[i * width + j];
m.push(e.numerator().mul(&s.div_exact(e.denominator())));
}
}
let mut prev = Int::ONE;
for k in 0..n {
if m[k * width + k].is_zero() {
return None;
}
let mkk = m[k * width + k].clone();
for i in k + 1..n {
let mik = m[i * width + k].clone();
for j in k + 1..width {
let num = Int::sub(
&Int::mul(&mkk, &m[i * width + j]),
&Int::mul(&mik, &m[k * width + j]),
);
m[i * width + j] = num.div_exact(&prev); }
m[i * width + k] = Int::ZERO;
}
prev = mkk;
}
let mut x = alloc::vec![Rational::ZERO; n * extra];
for c in 0..extra {
for i in (0..n).rev() {
let mut acc = Rational::from_integer(m[i * width + n + c].clone());
for j in i + 1..n {
let term = Rational::mul(
&Rational::from_integer(m[i * width + j].clone()),
&x[j * extra + c],
);
acc = Rational::sub(&acc, &term);
}
x[i * extra + c] =
Rational::div(&acc, &Rational::from_integer(m[i * width + i].clone()));
}
}
Some(x)
}
pub trait FieldMatrix<T: Field> {
fn determinant(&self) -> T;
fn inverse(&self) -> Option<Matrix<T>>;
fn solve(&self, b: &[T]) -> Option<Vec<T>>;
fn rank(&self) -> usize;
}
impl<T: Field> FieldMatrix<T> for Matrix<T> {
fn determinant(&self) -> T {
assert!(self.is_square(), "determinant: matrix must be square");
let n = self.rows;
let sample = self
.data
.first()
.expect("determinant: empty matrix has no sample element for the ring's one");
let zero = sample.zero();
let mut det = sample.one();
let mut a = self.data.clone();
let idx = |i: usize, j: usize| i * n + j;
let mut negated = false;
for col in 0..n {
let piv = match (col..n).find(|&r| !a[idx(r, col)].is_zero()) {
Some(p) => p,
None => return zero, };
if piv != col {
for c in 0..n {
a.swap(idx(col, c), idx(piv, c));
}
negated = !negated;
}
let pivot = a[idx(col, col)].clone();
det = det * pivot.clone();
for r in col + 1..n {
let factor = a[idx(r, col)].clone() / pivot.clone();
if factor.is_zero() {
continue;
}
for c in col..n {
let prod = factor.clone() * a[idx(col, c)].clone();
a[idx(r, c)] = a[idx(r, c)].clone() - prod;
}
}
}
if negated { -det } else { det }
}
fn inverse(&self) -> Option<Matrix<T>> {
assert!(self.is_square(), "inverse: matrix must be square");
let n = self.rows;
if n == 0 {
return Some(self.clone()); }
let sample = &self.data[0];
let zero = sample.zero();
let one = sample.one();
let width = 2 * n;
let mut data = alloc::vec![zero.clone(); n * width];
for i in 0..n {
for j in 0..n {
data[i * width + j] = self.data[i * n + j].clone();
}
data[i * width + n + i] = one.clone();
}
for col in 0..n {
let piv = (col..n).find(|&r| !data[r * width + col].is_zero())?; if piv != col {
for c in 0..width {
data.swap(col * width + c, piv * width + c);
}
}
let pivot = data[col * width + col].clone();
for c in 0..width {
data[col * width + c] = data[col * width + c].clone() / pivot.clone();
}
for r in 0..n {
if r == col {
continue;
}
let factor = data[r * width + col].clone();
if factor.is_zero() {
continue;
}
for c in 0..width {
let prod = factor.clone() * data[col * width + c].clone();
data[r * width + c] = data[r * width + c].clone() - prod;
}
}
}
let mut inv = Matrix::filled(zero, n, n);
for i in 0..n {
for j in 0..n {
inv.set(i, j, data[i * width + n + j].clone());
}
}
Some(inv)
}
fn solve(&self, b: &[T]) -> Option<Vec<T>> {
assert!(self.is_square(), "solve: matrix must be square");
let n = self.rows;
assert_eq!(b.len(), n, "solve: right-hand side length mismatch");
if n == 0 {
return Some(Vec::new());
}
let zero = self.data[0].zero();
let width = n + 1;
let mut data = alloc::vec![zero.clone(); n * width];
for i in 0..n {
for j in 0..n {
data[i * width + j] = self.data[i * n + j].clone();
}
data[i * width + n] = b[i].clone();
}
for col in 0..n {
let piv = (col..n).find(|&r| !data[r * width + col].is_zero())?; if piv != col {
for c in 0..width {
data.swap(col * width + c, piv * width + c);
}
}
let pivot = data[col * width + col].clone();
for r in col + 1..n {
let factor = data[r * width + col].clone() / pivot.clone();
if factor.is_zero() {
continue;
}
for c in col..width {
let prod = factor.clone() * data[col * width + c].clone();
data[r * width + c] = data[r * width + c].clone() - prod;
}
}
}
let mut x = alloc::vec![zero; n];
for i in (0..n).rev() {
let mut acc = data[i * width + n].clone();
for j in i + 1..n {
acc = acc - data[i * width + j].clone() * x[j].clone();
}
x[i] = acc / data[i * width + i].clone();
}
Some(x)
}
fn rank(&self) -> usize {
if self.rows == 0 || self.cols == 0 {
return 0;
}
let n = self.rows;
let width = self.cols;
let mut data = self.data.clone();
let mut pivots = 0;
for col in 0..self.cols {
if pivots == n {
break;
}
let piv = match (pivots..n).find(|&r| !data[r * width + col].is_zero()) {
Some(p) => p,
None => continue,
};
if piv != pivots {
for c in 0..width {
data.swap(pivots * width + c, piv * width + c);
}
}
let pivot = data[pivots * width + col].clone();
for r in pivots + 1..n {
let factor = data[r * width + col].clone() / pivot.clone();
if factor.is_zero() {
continue;
}
for c in col..width {
let prod = factor.clone() * data[pivots * width + c].clone();
data[r * width + c] = data[r * width + c].clone() - prod;
}
}
pivots += 1;
}
pivots
}
}
macro_rules! matrix_binop {
($tr:ident, $m:ident, $atr:ident, $am:ident) => {
impl<T: Ring> core::ops::$tr for Matrix<T> {
type Output = Matrix<T>;
#[inline]
fn $m(self, rhs: Matrix<T>) -> Matrix<T> {
Matrix::$m(&self, &rhs)
}
}
impl<T: Ring> core::ops::$tr<&Matrix<T>> for &Matrix<T> {
type Output = Matrix<T>;
#[inline]
fn $m(self, rhs: &Matrix<T>) -> Matrix<T> {
Matrix::$m(self, rhs)
}
}
};
}
matrix_binop!(Add, add, AddAssign, add_assign);
matrix_binop!(Sub, sub, SubAssign, sub_assign);
matrix_binop!(Mul, mul, MulAssign, mul_assign);
pub trait RingMatrix<T: Ring> {
fn charpoly(&self) -> alloc::vec::Vec<T>;
fn det(&self) -> T;
}
impl<T: Ring> RingMatrix<T> for Matrix<T> {
#[allow(clippy::needless_range_loop)] fn charpoly(&self) -> alloc::vec::Vec<T> {
assert!(
self.is_square(),
"RingMatrix::charpoly: matrix must be square"
);
let n = self.rows();
assert!(
n > 0,
"RingMatrix::charpoly: 0×0 matrix has no ring context"
);
let one = self.get(0, 0).one();
let zero = self.get(0, 0).zero();
let mut v = alloc::vec![one.clone()];
for r in 1..=n {
let a = self.get(r - 1, r - 1).clone();
let mut t = alloc::vec![zero.clone(); r + 1];
t[0] = one.clone();
t[1] = -a.clone();
if r >= 2 {
let mut w: alloc::vec::Vec<T> =
(0..r - 1).map(|i| self.get(i, r - 1).clone()).collect();
for j in 2..=r {
let mut s = zero.clone();
for (c, wc) in w.iter().enumerate() {
s = s + self.get(r - 1, c).clone() * wc.clone();
}
t[j] = -s;
if j < r {
let mut wn = alloc::vec![zero.clone(); r - 1];
for (i, wni) in wn.iter_mut().enumerate() {
let mut acc = zero.clone();
for (k, wk) in w.iter().enumerate() {
acc = acc + self.get(i, k).clone() * wk.clone();
}
*wni = acc;
}
w = wn;
}
}
}
let mut vn = alloc::vec![zero.clone(); r + 1];
for (i, vni) in vn.iter_mut().enumerate() {
let mut acc = zero.clone();
for (k, vk) in v.iter().enumerate() {
if i >= k {
acc = acc + t[i - k].clone() * vk.clone();
}
}
*vni = acc;
}
v = vn;
}
v.reverse(); v
}
fn det(&self) -> T {
let c = self.charpoly();
if self.rows().is_multiple_of(2) {
c[0].clone()
} else {
-c[0].clone()
}
}
}
#[cfg(feature = "algebraic")]
impl Matrix<crate::rational::Rational> {
pub fn characteristic_polynomial(&self) -> crate::poly::Poly<crate::rational::Rational> {
crate::poly::Poly::new(RingMatrix::charpoly(self))
}
pub fn real_eigenvalues(&self) -> Vec<crate::algebraic::Algebraic> {
crate::algebraic::Algebraic::real_roots_of(&self.characteristic_polynomial())
}
pub fn real_eigenvalues_with_multiplicity(&self) -> Vec<(crate::algebraic::Algebraic, usize)> {
use crate::algebraic::Algebraic;
let cp = self.characteristic_polynomial();
let mut out = Vec::new();
for (i, factor) in squarefree_decomposition(&cp).into_iter().enumerate() {
let mult = i + 1; for root in Algebraic::real_roots_of(&factor) {
out.push((root, mult));
}
}
out.sort_by(|a, b| a.0.cmp(&b.0));
out
}
}
#[cfg(feature = "algebraic")]
fn squarefree_decomposition(
p: &crate::poly::Poly<crate::rational::Rational>,
) -> Vec<crate::poly::Poly<crate::rational::Rational>> {
let p = p.monic();
if p.degree().unwrap_or(0) < 1 {
return Vec::new();
}
let d = p.derivative();
let a0 = p.subresultant_gcd(&d);
let mut b = p.div_rem(&a0).0; let mut c = d.div_rem(&a0).0; let mut result = Vec::new();
loop {
let dd = c.sub(&b.derivative()); let g = b.subresultant_gcd(&dd); result.push(g.monic());
b = b.div_rem(&g).0; c = dd.div_rem(&g).0; if b.degree().unwrap_or(0) < 1 {
break;
}
}
result
}