#[doc(hidden)]
pub mod dynamic {
use std::marker::PhantomData;
use crate::{Array, DynArray, ZeroDimArray};
pub fn build<Item, const NDIM: usize, ArrayImpl>(
shape: [usize; NDIM],
allocate: impl FnOnce(Request<Item, NDIM>) -> Array<ArrayImpl, NDIM>,
) -> Array<ArrayImpl, NDIM> {
allocate(Request::new(shape))
}
pub fn aligned_shape<const NDIM: usize>(shape: [usize; NDIM]) -> [usize; NDIM] {
assert!(
NDIM > 0,
"Aligned zero-dimensional arrays are not supported; use the unaligned macro form"
);
shape
}
pub struct Request<Item, const NDIM: usize> {
shape: [usize; NDIM],
item: PhantomData<Item>,
}
impl<Item, const NDIM: usize> Request<Item, NDIM> {
pub fn new(shape: [usize; NDIM]) -> Self {
Self {
shape,
item: PhantomData,
}
}
}
pub trait Allocate {
type Output;
fn allocate(self) -> Self::Output;
}
impl<Item: Copy + Default, const NDIM: usize> Allocate for &Request<Item, NDIM> {
type Output = DynArray<Item, NDIM>;
fn allocate(self) -> Self::Output {
DynArray::from_shape(self.shape)
}
}
impl<Item: Copy + Default> Allocate for &&Request<Item, 0> {
type Output = ZeroDimArray<Item>;
fn allocate(self) -> Self::Output {
ZeroDimArray::from_scalar(Item::default())
}
}
}
#[doc(hidden)]
pub mod direct {
use std::ops::{Add, AddAssign, Mul};
use num::{One, Zero};
use crate::{
Array, BaseItem, DynArray, Shape, UnsafeRandomAccessByValue, UnsafeRandomAccessMut,
ZeroDimArray, operator::element::Element, sparse::csr_mat::CsrMatrix,
traits::abstract_operator::OperatorBase,
};
pub trait AllocateOutput<const R: usize>: Output<R> + Sized {
fn allocate_output(shape: [usize; R]) -> Self;
}
impl<Item: Copy + Default, const R: usize> AllocateOutput<R> for DynArray<Item, R> {
fn allocate_output(shape: [usize; R]) -> Self {
Self::from_shape(shape)
}
}
impl<Item: Copy + Default> AllocateOutput<0> for ZeroDimArray<Item> {
fn allocate_output(_shape: [usize; 0]) -> Self {
Self::from_scalar(Item::default())
}
}
pub trait Product<'a, Rhs, Result> {
fn __rlst_dot_product(&'a self, rhs: &Rhs) -> Result;
}
impl<'a, A, B, C, Item, const M: usize, const N: usize, const R: usize>
Product<'a, Array<B, N>, Array<C, R>> for Array<A, M>
where
A: Shape<M> + UnsafeRandomAccessByValue<M>,
B: Shape<N> + UnsafeRandomAccessByValue<N>,
A::Item: Mul<B::Item, Output = Item>,
C: Shape<R> + BaseItem<Item = Item>,
Array<C, R>: AllocateOutput<R> + BaseItem<Item = Item>,
Item: Copy + Default + Zero + AddAssign,
{
fn __rlst_dot_product(&'a self, rhs: &Array<B, N>) -> Array<C, R> {
let (shape, _, _, _) = shape(self.shape(), rhs.shape());
let mut output = Array::<C, R>::allocate_output(shape);
crate::dot_into!(output, self, rhs);
output
}
}
impl<'a, B, Item, const N: usize> Product<'a, Array<B, N>, DynArray<Item, N>> for CsrMatrix<Item>
where
B: Shape<N> + UnsafeRandomAccessByValue<N, Item = Item>,
Item: Copy + Default + One + Mul<Output = Item> + AddAssign + Add<Output = Item>,
{
fn __rlst_dot_product(&'a self, rhs: &Array<B, N>) -> DynArray<Item, N> {
self.dot(rhs)
}
}
impl<'a, 'b, Op: OperatorBase + ?Sized + 'a>
Product<'a, Element<'b, Op::Domain>, Element<'a, Op::Range>> for Op
where
<Op::Range as crate::LinearSpace>::F: One + Zero,
{
fn __rlst_dot_product(&'a self, rhs: &Element<'b, Op::Domain>) -> Element<'a, Op::Range> {
self.dot(rhs)
}
}
pub trait Output<const R: usize>: BaseItem {
unsafe fn output_mut(&mut self, index: [usize; R]) -> &mut Self::Item;
}
impl<Impl: UnsafeRandomAccessMut<R>, const R: usize> Output<R> for Array<Impl, R> {
unsafe fn output_mut(&mut self, index: [usize; R]) -> &mut Self::Item {
unsafe { self.get_unchecked_mut(index) }
}
}
impl<Item: Copy + Default> Output<0> for ZeroDimArray<Item> {
unsafe fn output_mut(&mut self, _index: [usize; 0]) -> &mut Item {
self.scalar_mut()
}
}
pub fn shape<const M: usize, const N: usize, const R: usize>(
a: [usize; M],
b: [usize; N],
) -> ([usize; R], usize, usize, usize) {
assert!(
M > 0 && N > 0,
"direct multiplication requires positive ranks"
);
let rank = (M - 1)
.checked_add(N - 1)
.expect("direct multiplication rank overflow");
assert_eq!(R, rank, "direct multiplication output rank mismatch");
assert_eq!(a[M - 1], b[0], "direct multiplication contraction mismatch");
fn size(shape: &[usize]) -> usize {
shape.iter().fold(1usize, |size, &dim| {
size.checked_mul(dim)
.expect("direct multiplication size overflow")
})
}
size(&a);
size(&b);
let rows = size(&a[..M - 1]);
let cols = size(&b[1..]);
rows.checked_mul(cols)
.expect("direct multiplication output size overflow");
let mut output = [1; R];
output[..M - 1].copy_from_slice(&a[..M - 1]);
output[M - 1..M + N - 2].copy_from_slice(&b[1..]);
size(&output);
(output, rows, a[M - 1], cols)
}
pub fn zero<Item: num::Zero>() -> Item {
Item::zero()
}
}
#[macro_export]
macro_rules! dot_into {
($c:expr, $a:expr, $b:expr $(,)?) => {{
use $crate::dense::macros::direct::Output as _;
let c = &mut $c;
let a = &$a;
let b = &$b;
let a_shape = a.shape();
let b_shape = b.shape();
let c_shape = c.shape();
let (expected, rows, inner, cols) = $crate::dense::macros::direct::shape(a_shape, b_shape);
assert_eq!(
c_shape, expected,
"direct multiplication output shape mismatch"
);
let mut ai = a_shape.map(|_| 0);
let mut bi = b_shape.map(|_| 0);
let mut ci = c_shape.map(|_| 0);
let last = a_shape.len() - 1;
if rows != 0 && cols != 0 {
for _ in 0..cols {
for _ in 0..rows {
unsafe {
*c.output_mut(ci) = $crate::dense::macros::direct::zero();
}
for axis in 0..last {
ci[axis] += 1;
if ci[axis] < a_shape[axis] {
break;
}
ci[axis] = 0;
}
}
for k in 0..inner {
ai[last] = k;
bi[0] = k;
let right = unsafe { b.get_value_unchecked(bi) };
for _ in 0..rows {
unsafe {
*c.output_mut(ci) += a.get_value_unchecked(ai) * right;
}
for axis in 0..last {
ai[axis] += 1;
ci[axis] = ai[axis];
if ai[axis] < a_shape[axis] {
break;
}
ai[axis] = 0;
ci[axis] = 0;
}
}
}
for axis in 1..b_shape.len() {
bi[axis] += 1;
ci[last + axis - 1] = bi[axis];
if bi[axis] < b_shape[axis] {
break;
}
bi[axis] = 0;
ci[last + axis - 1] = 0;
}
}
}
}};
}
#[macro_export]
macro_rules! dot {
($a:expr, $b:expr $(,)?) => {{
use $crate::dense::macros::direct::Product as _;
($a).__rlst_dot_product(&$b)
}};
($a:expr, $b:expr, $($c:expr),+ $(,)?) => {{
$crate::dot!($a, $crate::dot!($b, $($c),+))
}};
}
#[macro_export]
macro_rules! dot_blas {
($a:expr, $b:expr $(,)?) => {
$a.dot(&$b)
};
($a:expr, $b:expr, $($c:expr),+ $(,)?) => {{
$crate::dot_blas!($a, $crate::dot_blas!($b, $($c),+))
}};
}
#[macro_export]
macro_rules! linspace {
($dtype:ty, $a:expr, $b:expr, $n:expr) => {{
use $crate::traits::base_operations::Linspace;
let mut arr = $crate::dense::array::DynArray::<$dtype, 1>::from_shape([$n]);
arr.linspace($a, $b);
arr
}};
}
#[macro_export]
macro_rules! diag {
($d:expr) => {
$crate::diag!($d, 2)
};
($d:expr, $ndim:literal) => {{
use itertools::izip;
let mut diag_array =
$crate::dense::array::DynArray::<_, $ndim>::from_shape([$d.len(); $ndim]);
izip!(diag_array.diag_iter_mut(), $d.iter_value()).for_each(|(diag_entry, entry)| {
*diag_entry = entry;
});
diag_array
}};
}
#[cfg(test)]
mod test {
use rand::SeedableRng;
use crate::{MultIntoResize, dense::array::DynArray, empty_array};
#[test]
fn test_dot_blas() {
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0);
let mut arr1 = DynArray::<f64, 2>::from_shape([2, 3]);
let mut arr2 = DynArray::<f64, 2>::from_shape([3, 4]);
let mut arr3 = DynArray::<f64, 2>::from_shape([4, 5]);
arr1.fill_from_standard_normal(&mut rng);
arr2.fill_from_standard_normal(&mut rng);
arr3.fill_from_standard_normal(&mut rng);
let expected = empty_array().simple_mult_into_resize(
empty_array().simple_mult_into_resize(arr1.r(), arr2.r()),
arr3.r(),
);
let actual = crate::dot_blas!(arr1.r(), arr2.r(), arr3.r());
crate::assert_array_relative_eq!(expected, actual, 1E-10);
}
#[test]
fn test_diag() {
let mut rng = rand_chacha::ChaCha8Rng::seed_from_u64(0);
let mut d = DynArray::<f64, 1>::from_shape([2]);
d.fill_from_equally_distributed(&mut rng);
let actual = diag!(d);
let mut expected = DynArray::<f64, 2>::from_shape([2, 2]);
expected[[0, 0]] = d[[0]];
expected[[1, 1]] = d[[1]];
crate::assert_array_relative_eq!(actual, expected, 1E-10);
}
}