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);