use std::{
fmt::{self, Debug, Display, Formatter},
ops::{Add, AddAssign, Index, IndexMut, Mul, Sub},
};
use ndarray::Array2;
use crate::{
matrix::{MatrixMul, ScalarMul},
wrappers::PP,
Complex, Polynomial, Zero,
};
#[derive(Clone, Debug)]
pub(crate) struct PolyMatrix<T> {
matrix_coeffs: Vec<Array2<T>>,
}
impl<T> PolyMatrix<T> {
fn degree(&self) -> usize {
assert!(
!self.matrix_coeffs.is_empty(),
"Degree is not defined on empty polynomial matrix"
);
self.matrix_coeffs.len() - 1
}
}
impl<T> PolyMatrix<T>
where
T: Clone + PartialEq + Zero,
{
pub(crate) fn new_from_coeffs(matrix_coeffs: &[Array2<T>]) -> Self {
let shape = matrix_coeffs[0].shape();
assert!(matrix_coeffs.iter().all(|c| c.shape() == shape));
let mut pm = Self {
matrix_coeffs: matrix_coeffs.into(),
};
pm.trim();
debug_assert!(!pm.matrix_coeffs.is_empty());
pm
}
pub(crate) fn new_from_iter<II>(matrix_iter: II) -> Self
where
II: IntoIterator<Item = Array2<T>>,
{
let mut pm = Self {
matrix_coeffs: matrix_iter.into_iter().collect(),
};
debug_assert!(!pm.matrix_coeffs.is_empty());
let shape = pm.matrix_coeffs[0].shape();
assert!(pm.matrix_coeffs.iter().all(|c| c.shape() == shape));
pm.trim();
pm
}
fn trim(&mut self) {
let rows = self.matrix_coeffs[0].nrows();
let cols = self.matrix_coeffs[0].ncols();
let zero = Array2::from_elem((rows, cols), T::zero());
if let Some(p) = self.matrix_coeffs.iter().rposition(|c| c != zero) {
self.matrix_coeffs.truncate(p + 1);
} else {
self.matrix_coeffs.resize(1, zero);
}
}
}
impl<T> PolyMatrix<T>
where
T: Clone + Mul<Output = T> + PartialEq + Zero,
{
pub(crate) fn multiply(poly: &PP<T>, matrix: &Array2<T>) -> Self {
let result = poly.0.as_slice().iter().map(|c| matrix.smul(c.0.clone()));
Self::new_from_iter(result)
}
}
impl<T> PolyMatrix<T>
where
T: Add<Output = T> + Clone + Mul<Output = T> + PartialEq + Zero,
{
pub(crate) fn right_mul(&self, rhs: &Array2<T>) -> Self {
let result: Vec<_> = self.matrix_coeffs.iter().map(|l| l.mmul(rhs)).collect();
Self::new_from_coeffs(&result)
}
pub(crate) fn left_mul(&self, lhs: &Array2<T>) -> Self {
let res: Vec<_> = self.matrix_coeffs.iter().map(|r| lhs.mmul(r)).collect();
Self::new_from_coeffs(&res)
}
}
impl<T> PolyMatrix<T>
where
T: Add<Output = T> + Clone + Mul<Output = T> + Sub<Output = T> + Zero,
{
#[allow(dead_code)]
pub(crate) fn eval(&self, s: &Array2<Complex<T>>) -> Array2<Complex<T>> {
let rows = self.matrix_coeffs[0].nrows();
let cols = self.matrix_coeffs[0].ncols();
let mut result = Array2::from_elem((rows, cols), Complex::<T>::zero());
for mc in self.matrix_coeffs.iter().rev() {
let mcplx = mc.map(|x| Complex::new(x.clone(), T::zero()));
result = result * s + mcplx;
}
result
}
}
impl<T> Add for PolyMatrix<T>
where
T: Add<Output = T> + AddAssign + Clone + PartialEq + Zero,
{
type Output = Self;
fn add(mut self, mut rhs: Self) -> Self {
let mut result = if self.degree() < rhs.degree() {
for (i, c) in self.matrix_coeffs.iter().enumerate() {
rhs[i] += c;
}
rhs
} else {
for (i, c) in rhs.matrix_coeffs.iter().enumerate() {
self[i] += c;
}
self
};
result.trim();
result
}
}
impl<T> Index<usize> for PolyMatrix<T> {
type Output = Array2<T>;
fn index(&self, i: usize) -> &Self::Output {
&self.matrix_coeffs[i]
}
}
impl<T> IndexMut<usize> for PolyMatrix<T> {
fn index_mut(&mut self, i: usize) -> &mut Self::Output {
&mut self.matrix_coeffs[i]
}
}
impl<T: Display + PartialEq + Zero> Display for PolyMatrix<T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
if self.degree() == 0 {
return write!(f, "{}", self.matrix_coeffs[0]);
}
let mut s = String::new();
let mut sep = "";
for (i, c) in self.matrix_coeffs.iter().enumerate() {
if c.iter().all(|x| x == &T::zero()) {
continue;
}
s.push_str(sep);
if i == 0 {
s.push_str(&format!("{}", c));
} else if i == 1 {
s.push_str(&format!("+{}*s", c));
} else {
s.push_str(&format!("+{}*s^{}", c, i));
}
sep = " ";
}
write!(f, "{}", s)
}
}
#[derive(Debug)]
pub struct MatrixOfPoly<T> {
matrix: Array2<PP<T>>,
}
impl<T> MatrixOfPoly<T> {
fn new(rows: usize, cols: usize, data: Vec<PP<T>>) -> Self {
Self {
matrix: Array2::from_shape_vec((rows, cols), data)
.expect("Input data do not allow to create the matrix"),
}
}
pub(crate) fn matrix(&self) -> &Array2<PP<T>> {
&self.matrix
}
pub(crate) fn single(&self) -> Option<&PP<T>> {
if self.matrix.shape() == [1, 1] {
self.matrix.first()
} else {
None
}
}
}
impl<T> Index<[usize; 2]> for MatrixOfPoly<T> {
type Output = PP<T>;
fn index(&self, i: [usize; 2]) -> &Self::Output {
&self.matrix[i]
}
}
impl<T> MatrixOfPoly<T>
where
T: Clone,
{
#[must_use]
pub fn get_unchecked(&self, i: [usize; 2]) -> impl Polynomial<T> {
self.matrix[i].to_unwrapped_vec()
}
}
impl<T> MatrixOfPoly<T>
where
T: Clone + PartialEq + Zero,
{
pub fn set_unchecked(&mut self, i: [usize; 2], value: &impl Polynomial<T>) {
self.matrix[i] = PP::new_from_coeffs(value.as_slice());
}
}
impl<T: Clone + PartialEq + Zero> From<PolyMatrix<T>> for MatrixOfPoly<T> {
fn from(pm: PolyMatrix<T>) -> Self {
let coeffs = pm.matrix_coeffs; let rows = coeffs[0].nrows();
let cols = coeffs[0].ncols();
let vectorized_coeffs: Vec<Vec<_>> = coeffs
.iter()
.map(|c| c.iter().cloned().collect::<Vec<_>>())
.collect();
let mut tmp: Vec<Vec<T>> = vec![vec![]; rows * cols];
for order in vectorized_coeffs {
for (i, value) in order.into_iter().enumerate() {
tmp[i].push(value);
}
}
let polys: Vec<_> = tmp.iter().map(|p| PP::new_from_coeffs(p)).collect();
Self::new(rows, cols, polys)
}
}
impl<T> Display for MatrixOfPoly<T>
where
T: Display + PartialOrd + Zero,
{
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
write!(f, "{}", self.matrix)
}
}
#[cfg(test)]
mod tests {
use ndarray::arr2;
use crate::wrappers::PWrapper;
use super::*;
#[test]
fn trim_empty() {
let v = vec![Array2::<f32>::zeros((1, 2)), Array2::zeros((1, 2))];
let pm = PolyMatrix::new_from_coeffs(&v);
assert_eq!(Array2::<f32>::zeros((1, 2)), pm.matrix_coeffs[0]);
}
#[test]
fn right_mul() {
let v = vec![
arr2(&[[1.0_f32, 2.], [3., 4.]]),
arr2(&[[1., 0.], [0., 1.]]),
];
let pm = PolyMatrix::new_from_coeffs(&v);
let res = pm.right_mul(&arr2(&[[5., 0.], [0., 5.]]));
assert_eq!(arr2(&[[5., 10.], [15., 20.]]), res.matrix_coeffs[0]);
dbg!(&res);
}
#[test]
fn left_mul() {
let v = vec![
arr2(&[[1.0_f32, 2.], [3., 4.]]),
arr2(&[[1., 0.], [0., 1.]]),
];
let pm = PolyMatrix::new_from_coeffs(&v);
let res = pm.left_mul(&arr2(&[[5., 0.], [0., 5.]]));
assert_eq!(arr2(&[[5., 10.], [15., 20.]]), res[0]);
}
#[test]
fn eval() {
let v = vec![
arr2(&[[1.0_f32, 2.], [3., 4.]]),
arr2(&[[1., 0.], [0., 1.]]),
];
let pm = PolyMatrix::new_from_coeffs(&v);
let res = pm.eval(&arr2(&[
[Complex::new(5., 0.), Complex::zero()],
[Complex::zero(), Complex::new(0., 5.)],
]));
assert_eq!(
arr2(&[
[Complex::new(6., 0.), Complex::new(2., 0.)],
[Complex::new(3., 0.), Complex::new(4., 5.)]
]),
res
);
}
#[test]
fn add() {
let v = vec![
arr2(&[[1.0_f64, 2.], [3., 4.]]),
arr2(&[[1., 0.], [0., 1.]]),
];
let pm = PolyMatrix::new_from_coeffs(&v);
let res = pm.clone() + pm;
assert_eq!(arr2(&[[2., 4.], [6., 8.]]), res[0]);
assert_eq!(arr2(&[[2., 0.], [0., 2.]]), res[1]);
}
#[test]
fn format_polymatrix_zero_degree() {
let v = vec![arr2(&[[1., 0.]])];
let pm = PolyMatrix::new_from_coeffs(&v);
let expected = pm.matrix_coeffs[0].to_string();
assert_eq!(expected, format!("{}", &pm));
}
#[test]
fn format_polymatrix_zero_coefficient() {
let v = vec![Array2::<f32>::zeros((1, 2)), arr2(&[[1., 0.]])];
let pm = PolyMatrix::new_from_coeffs(&v);
assert!(format!("{}", &pm).starts_with('+'));
}
#[test]
fn mp_creation() {
let c = [4.3, 5.32];
let p = PP::new_from_coeffs(&c);
let v = vec![p.clone(), p.clone(), p.clone(), p];
let mp = MatrixOfPoly::new(2, 2, v);
let expected = "[[4.3 +5.32s, 4.3 +5.32s],\n [4.3 +5.32s, 4.3 +5.32s]]";
assert_eq!(expected, format!("{}", &mp));
}
#[test]
#[allow(clippy::float_cmp)]
fn indexing() {
let c = [4.3, 5.32];
let p = PP::new_from_coeffs(&c);
let v = vec![p.clone(), p.clone(), p.clone(), p];
let mut mp = MatrixOfPoly::new(2, 2, v);
assert_eq!(c, mp.get_unchecked([1, 1]).as_slice());
let p2 = vec![1., 2.];
mp.set_unchecked([1, 1], &p2);
assert_eq!(p2, mp.get_unchecked([1, 1]).as_slice());
}
#[test]
fn single() {
let v = vec![PP::new_from_coeffs(&[4.3, 5.32])];
let mp = MatrixOfPoly::new(1, 1, v);
assert_eq!(1, mp.matrix().len());
let res = mp.single();
assert!(res.is_some());
assert_relative_eq!(
14.94,
res.unwrap().0.eval(&PWrapper(2.)).0,
max_relative = 1e-10
);
}
#[test]
fn single_fail() {
let c = [4.3, 5.32];
let p = PP::new_from_coeffs(&c);
let v = vec![p.clone(), p.clone(), p.clone(), p];
let mp = MatrixOfPoly::new(2, 2, v);
let res = mp.single();
assert!(res.is_none());
}
#[test]
fn matrixofpoly_index() {
let c = [4.3, 5.32];
let p = PP::new_from_coeffs(&c);
let v = vec![p.clone(), p.clone(), p.clone(), p.clone()];
let mp = MatrixOfPoly::new(2, 2, v);
assert_eq!(p, mp[[1, 0]]);
}
}