#![allow(clippy::ptr_arg)]
use ff::PrimeField;
pub(crate) type Matrix<T> = Vec<Vec<T>>;
pub(crate) fn rows<T>(matrix: &Matrix<T>) -> usize {
matrix.len()
}
fn columns<T>(matrix: &Matrix<T>) -> usize {
if matrix.is_empty() {
0
} else {
let column_length = matrix[0].len();
for row in matrix {
assert!(row.len() == column_length, "not a matrix");
}
column_length
}
}
pub(crate) fn is_invertible<F: PrimeField>(matrix: &Matrix<F>) -> bool {
is_square(matrix) && invert(matrix).is_some()
}
fn scalar_vec_mul<F: PrimeField>(scalar: F, vec: &[F]) -> Vec<F> {
vec
.iter()
.map(|val| {
let mut prod = scalar;
prod.mul_assign(val);
prod
})
.collect::<Vec<_>>()
}
pub(crate) fn mat_mul<F: PrimeField>(a: &Matrix<F>, b: &Matrix<F>) -> Option<Matrix<F>> {
if rows(a) != columns(b) {
return None;
};
let b_t = transpose(b);
let res = a
.iter()
.map(|input_row| {
b_t
.iter()
.map(|transposed_column| vec_mul(input_row, transposed_column))
.collect()
})
.collect();
Some(res)
}
fn vec_mul<F: PrimeField>(a: &[F], b: &[F]) -> F {
a.iter().zip(b).fold(F::ZERO, |mut acc, (v1, v2)| {
let mut tmp = *v1;
tmp.mul_assign(v2);
acc.add_assign(&tmp);
acc
})
}
pub(crate) fn vec_add<F: PrimeField>(a: &[F], b: &[F]) -> Vec<F> {
a.iter()
.zip(b.iter())
.map(|(a, b)| {
let mut res = *a;
res.add_assign(b);
res
})
.collect::<Vec<_>>()
}
pub(crate) fn vec_sub<F: PrimeField>(a: &[F], b: &[F]) -> Vec<F> {
a.iter()
.zip(b.iter())
.map(|(a, b)| {
let mut res = *a;
res.sub_assign(b);
res
})
.collect::<Vec<_>>()
}
pub(crate) fn left_apply_matrix<F: PrimeField>(m: &Matrix<F>, v: &[F]) -> Vec<F> {
assert!(is_square(m), "Only square matrix can be applied to vector.");
assert_eq!(
rows(m),
v.len(),
"Matrix can only be applied to vector of same size."
);
let mut result = vec![F::ZERO; v.len()];
for (result, row) in result.iter_mut().zip(m.iter()) {
for (mat_val, vec_val) in row.iter().zip(v) {
let mut tmp = *mat_val;
tmp.mul_assign(vec_val);
result.add_assign(&tmp);
}
}
result
}
#[allow(clippy::needless_range_loop)]
pub(crate) fn transpose<F: PrimeField>(matrix: &Matrix<F>) -> Matrix<F> {
let size = rows(matrix);
let mut new = Vec::with_capacity(size);
for j in 0..size {
let mut row = Vec::with_capacity(size);
for i in 0..size {
row.push(matrix[i][j])
}
new.push(row);
}
new
}
#[allow(clippy::needless_range_loop)]
pub(crate) fn make_identity<F: PrimeField>(size: usize) -> Matrix<F> {
let mut result = vec![vec![F::ZERO; size]; size];
for i in 0..size {
result[i][i] = F::ONE;
}
result
}
pub(crate) fn kronecker_delta<F: PrimeField>(i: usize, j: usize) -> F {
if i == j {
F::ONE
} else {
F::ZERO
}
}
#[allow(clippy::needless_range_loop)]
pub(crate) fn is_identity<F: PrimeField>(matrix: &Matrix<F>) -> bool {
for i in 0..rows(matrix) {
for j in 0..columns(matrix) {
if matrix[i][j] != kronecker_delta(i, j) {
return false;
}
}
}
true
}
pub(crate) fn is_square<T>(matrix: &Matrix<T>) -> bool {
rows(matrix) == columns(matrix)
}
pub(crate) fn minor<F: PrimeField>(matrix: &Matrix<F>, i: usize, j: usize) -> Matrix<F> {
assert!(is_square(matrix));
let size = rows(matrix);
assert!(size > 0);
let new = matrix
.iter()
.enumerate()
.filter_map(|(ii, row)| {
if ii == i {
None
} else {
let mut new_row = row.clone();
new_row.remove(j);
Some(new_row)
}
})
.collect();
assert!(is_square(&new));
new
}
fn eliminate<F: PrimeField>(
matrix: &Matrix<F>,
column: usize,
shadow: &mut Matrix<F>,
) -> Option<Matrix<F>> {
let zero = F::ZERO;
let pivot_index = (0..rows(matrix))
.find(|&i| matrix[i][column] != zero && (0..column).all(|j| matrix[i][j] == zero))?;
let pivot = &matrix[pivot_index];
let pivot_val = pivot[column];
let inv_pivot = Option::from(pivot_val.invert())?;
let mut result = Vec::with_capacity(matrix.len());
result.push(pivot.clone());
for (i, row) in matrix.iter().enumerate() {
if i == pivot_index {
continue;
};
let val = row[column];
if val == zero {
result.push(row.to_vec());
} else {
let mut factor = val;
factor.mul_assign(&inv_pivot);
let scaled_pivot = scalar_vec_mul(factor, pivot);
let eliminated = vec_sub(row, &scaled_pivot);
result.push(eliminated);
let shadow_pivot = &shadow[pivot_index];
let scaled_shadow_pivot = scalar_vec_mul(factor, shadow_pivot);
let shadow_row = &shadow[i];
shadow[i] = vec_sub(shadow_row, &scaled_shadow_pivot);
}
}
let pivot_row = shadow.remove(pivot_index);
shadow.insert(0, pivot_row);
Some(result)
}
fn upper_triangular<F: PrimeField>(
matrix: &Matrix<F>,
shadow: &mut Matrix<F>,
) -> Option<Matrix<F>> {
assert!(is_square(matrix));
let mut result = Vec::with_capacity(matrix.len());
let mut shadow_result = Vec::with_capacity(matrix.len());
let mut curr = matrix.clone();
let mut column = 0;
while curr.len() > 1 {
let initial_rows = curr.len();
curr = eliminate(&curr, column, shadow)?;
result.push(curr[0].clone());
shadow_result.push(shadow[0].clone());
column += 1;
curr = curr[1..].to_vec();
*shadow = shadow[1..].to_vec();
assert_eq!(curr.len(), initial_rows - 1);
}
result.push(curr[0].clone());
shadow_result.push(shadow[0].clone());
*shadow = shadow_result;
Some(result)
}
fn reduce_to_identity<F: PrimeField>(
matrix: &Matrix<F>,
shadow: &mut Matrix<F>,
) -> Option<Matrix<F>> {
let size = rows(matrix);
let mut result: Matrix<F> = Vec::new();
let mut shadow_result: Matrix<F> = Vec::new();
for i in 0..size {
let idx = size - i - 1;
let row = &matrix[idx];
let shadow_row = &shadow[idx];
let val = row[idx];
let inv = {
let inv = val.invert();
if inv.is_none().into() {
return None;
}
inv.unwrap()
};
let mut normalized = scalar_vec_mul(inv, row);
let mut shadow_normalized = scalar_vec_mul(inv, shadow_row);
for j in 0..i {
let idx = size - j - 1;
let val = normalized[idx];
let subtracted = scalar_vec_mul(val, &result[j]);
let result_subtracted = scalar_vec_mul(val, &shadow_result[j]);
normalized = vec_sub(&normalized, &subtracted);
shadow_normalized = vec_sub(&shadow_normalized, &result_subtracted);
}
result.push(normalized);
shadow_result.push(shadow_normalized);
}
result.reverse();
shadow_result.reverse();
*shadow = shadow_result;
Some(result)
}
pub(crate) fn invert<F: PrimeField>(matrix: &Matrix<F>) -> Option<Matrix<F>> {
let mut shadow = make_identity(columns(matrix));
let ut = upper_triangular(matrix, &mut shadow);
ut.and_then(|x| reduce_to_identity(&x, &mut shadow))
.and(Some(shadow))
}