use crate::array::Array;
use crate::error::{NumRs2Error, Result};
use num_traits::Float;
use std::borrow::Cow;
use std::fmt::Debug;
pub fn multi_dot<T>(arrays: &[&Array<T>]) -> Result<Array<T>>
where
T: Float
+ Clone
+ Debug
+ std::ops::AddAssign
+ std::ops::MulAssign
+ std::ops::DivAssign
+ std::ops::SubAssign
+ std::fmt::Display
+ 'static,
{
if arrays.len() < 2 {
return Err(NumRs2Error::InvalidOperation(
"multi_dot requires at least two arrays".to_string(),
));
}
let last = arrays.len() - 1;
let first_is_vector = arrays[0].ndim() == 1;
let last_is_vector = arrays[last].ndim() == 1;
let mut mats: Vec<Array<T>> = Vec::with_capacity(arrays.len());
for (i, &arr) in arrays.iter().enumerate() {
let shape = arr.shape();
match shape.len() {
2 => mats.push(arr.clone()),
1 if i == 0 => mats.push(arr.try_reshape(&[1, shape[0]])?),
1 if i == last => mats.push(arr.try_reshape(&[shape[0], 1])?),
_ => {
return Err(NumRs2Error::DimensionMismatch(format!(
"multi_dot: array {} must be 2-D (only the first and last arrays may be 1-D), got shape {:?}",
i, shape
)));
}
}
}
for pair in mats.windows(2) {
if pair[0].shape()[1] != pair[1].shape()[0] {
return Err(NumRs2Error::ShapeMismatch {
expected: vec![pair[0].shape()[1]],
actual: vec![pair[1].shape()[0]],
});
}
}
let n = mats.len();
let result = if n == 2 {
mats[0].matmul(&mats[1])?
} else {
multi_dot_chain(&mats)?
};
let mut final_shape = result.shape();
if last_is_vector {
final_shape.pop();
}
if first_is_vector {
final_shape.remove(0);
}
if final_shape.is_empty() {
Ok(Array::from_vec(result.to_vec()))
} else {
result.try_reshape(&final_shape)
}
}
fn multi_dot_chain<T>(mats: &[Array<T>]) -> Result<Array<T>>
where
T: Float
+ Clone
+ Debug
+ std::ops::AddAssign
+ std::ops::MulAssign
+ std::ops::DivAssign
+ std::ops::SubAssign
+ std::fmt::Display
+ 'static,
{
let n = mats.len();
let mut dims = Vec::with_capacity(n + 1);
dims.push(mats[0].shape()[0]);
for m in mats {
dims.push(m.shape()[1]);
}
let mut cost = vec![vec![0u128; n]; n];
let mut split = vec![vec![0usize; n]; n];
for len in 2..=n {
for i in 0..=n - len {
let j = i + len - 1;
let mut best_cost = u128::MAX;
let mut best_k = i;
for k in i..j {
let candidate = cost[i][k]
+ cost[k + 1][j]
+ (dims[i] as u128) * (dims[k + 1] as u128) * (dims[j + 1] as u128);
if candidate < best_cost {
best_cost = candidate;
best_k = k;
}
}
cost[i][j] = best_cost;
split[i][j] = best_k;
}
}
multi_dot_evaluate(mats, &split, 0, n - 1)
}
fn multi_dot_evaluate<T>(
mats: &[Array<T>],
split: &[Vec<usize>],
i: usize,
j: usize,
) -> Result<Array<T>>
where
T: Float
+ Clone
+ Debug
+ std::ops::AddAssign
+ std::ops::MulAssign
+ std::ops::DivAssign
+ std::ops::SubAssign
+ std::fmt::Display
+ 'static,
{
if i == j {
return Ok(mats[i].clone());
}
let k = split[i][j];
let left = multi_dot_evaluate(mats, split, i, k)?;
let right = multi_dot_evaluate(mats, split, k + 1, j)?;
left.matmul(&right)
}
pub fn tensorsolve<T>(a: &Array<T>, b: &Array<T>, axes: Option<&[usize]>) -> Result<Array<T>>
where
T: Float
+ Clone
+ Debug
+ std::ops::AddAssign
+ std::ops::MulAssign
+ std::ops::DivAssign
+ std::ops::SubAssign
+ std::fmt::Display
+ 'static,
{
let an = a.ndim();
let bn = b.ndim();
if bn > an {
return Err(NumRs2Error::DimensionMismatch(format!(
"tensorsolve: b has more dimensions ({}) than a ({})",
bn, an
)));
}
let a_reordered: Cow<'_, Array<T>> = match axes {
None => Cow::Borrowed(a),
Some(axes_to_move) => {
let mut seen = vec![false; an];
for &ax in axes_to_move {
if ax >= an {
return Err(NumRs2Error::DimensionMismatch(format!(
"tensorsolve: axis {} out of bounds for a {}-D array",
ax, an
)));
}
if seen[ax] {
return Err(NumRs2Error::InvalidOperation(format!(
"tensorsolve: axis {} repeated in axes {:?}",
ax, axes_to_move
)));
}
seen[ax] = true;
}
let k = axes_to_move.len();
let destination: Vec<usize> = (an - k..an).collect();
Cow::Owned(crate::array_ops::axis_ops::moveaxis(
a,
axes_to_move,
&destination,
)?)
}
};
let a_shape = a_reordered.shape();
let old_shape: Vec<usize> = a_shape[bn..].to_vec();
let prod: usize = old_shape.iter().product();
if a_reordered.size() != prod * prod {
return Err(NumRs2Error::DimensionMismatch(format!(
"tensorsolve: array with shape {:?} does not satisfy prod(a.shape[b.ndim:]) == prod(a.shape[:b.ndim])",
a_shape
)));
}
let a_mat = a_reordered.try_reshape(&[prod, prod])?;
let b_flat = b.try_reshape(&[prod])?;
let x = a_mat.solve(&b_flat)?;
x.try_reshape(&old_shape)
}
pub fn tensorinv<T>(a: &Array<T>, ind: usize) -> Result<Array<T>>
where
T: Float
+ Clone
+ Debug
+ std::ops::AddAssign
+ std::ops::MulAssign
+ std::ops::DivAssign
+ std::ops::SubAssign
+ std::fmt::Display
+ 'static,
{
if ind == 0 {
return Err(NumRs2Error::InvalidOperation(
"tensorinv: ind must be a positive integer".to_string(),
));
}
let old_shape = a.shape();
if ind > old_shape.len() {
return Err(NumRs2Error::DimensionMismatch(format!(
"tensorinv: ind ({}) must be <= a.ndim ({})",
ind,
old_shape.len()
)));
}
let prod_head: usize = old_shape[..ind].iter().product();
let prod_tail: usize = old_shape[ind..].iter().product();
if prod_head != prod_tail {
return Err(NumRs2Error::DimensionMismatch(format!(
"tensorinv: array with shape {:?} is not 'square' for ind={} (prod(shape[..ind]) = {}, prod(shape[ind..]) = {})",
old_shape, ind, prod_head, prod_tail
)));
}
let reshaped = a.try_reshape(&[prod_tail, prod_head])?;
let inv_mat = reshaped.inv()?;
let mut inv_shape: Vec<usize> = old_shape[ind..].to_vec();
inv_shape.extend_from_slice(&old_shape[..ind]);
inv_mat.try_reshape(&inv_shape)
}