#[cfg(all(feature = "blas", feature = "blas-inject"))]
compile_error!("Features `blas` and `blas-inject` are mutually exclusive.");
#[cfg(any(
all(feature = "blas-accelerate", feature = "blas-openblas"),
all(feature = "blas-accelerate", feature = "blas-mkl"),
all(feature = "blas-openblas", feature = "blas-mkl")
))]
compile_error!("Select at most one explicit BLAS provider feature.");
#[cfg(all(feature = "blas-inject", not(feature = "blas")))]
extern crate cblas_inject as cblas_sys;
#[cfg(all(feature = "blas", not(feature = "blas-inject")))]
extern crate cblas_sys;
#[cfg(any(
all(feature = "blas", not(feature = "blas-inject")),
all(feature = "blas-inject", not(feature = "blas"))
))]
pub mod bgemm_blas;
#[cfg(feature = "faer")]
pub mod bgemm_faer;
pub mod bgemm_naive;
pub mod contiguous;
pub mod dot_general;
pub mod plan;
pub mod raw_bgemm;
pub mod trace;
pub mod util;
pub mod backend;
use std::any::TypeId;
use std::fmt::Debug;
use std::hash::Hash;
use strided_kernel::zip_map2_into;
#[cfg(any(feature = "faer", feature = "blas", feature = "blas-inject"))]
use strided_view::StridedArray;
use strided_view::{Adjoint, Conj, ElementOp, ElementOpApply};
pub use strided_traits::ScalarBase;
pub use strided_view::{
col_major_strides, RawStridedMut, RawStridedRef, StridedView, StridedViewMut,
};
pub use backend::Backend;
pub use dot_general::{dot_general_into, dot_general_with_backend_into, DotGeneralConfig};
pub use plan::Einsum2Plan;
pub use raw_bgemm::{
bgemm_raw_strided_into, bgemm_raw_strided_into_unchecked, bgemm_raw_with_backend_into,
bgemm_raw_with_backend_into_unchecked,
};
pub trait AxisId: Clone + Eq + Hash + Debug {}
impl<T: Clone + Eq + Hash + Debug> AxisId for T {}
#[cfg(any(
all(feature = "blas", not(feature = "blas-inject")),
all(feature = "blas-inject", not(feature = "blas"))
))]
pub trait Scalar: ScalarBase + ElementOpApply + bgemm_blas::BlasGemm {}
#[cfg(any(
all(feature = "blas", not(feature = "blas-inject")),
all(feature = "blas-inject", not(feature = "blas"))
))]
impl<T> Scalar for T where T: ScalarBase + ElementOpApply + bgemm_blas::BlasGemm {}
#[cfg(all(feature = "faer", not(any(feature = "blas", feature = "blas-inject"))))]
pub trait Scalar: ScalarBase + ElementOpApply + faer_traits::ComplexField {}
#[cfg(all(feature = "faer", not(any(feature = "blas", feature = "blas-inject"))))]
impl<T> Scalar for T where T: ScalarBase + ElementOpApply + faer_traits::ComplexField {}
#[cfg(not(any(feature = "faer", feature = "blas", feature = "blas-inject")))]
pub trait Scalar: ScalarBase + ElementOpApply {}
#[cfg(not(any(feature = "faer", feature = "blas", feature = "blas-inject")))]
impl<T> Scalar for T where T: ScalarBase + ElementOpApply {}
#[cfg(all(feature = "blas", feature = "blas-inject"))]
pub trait Scalar: ScalarBase + ElementOpApply {}
#[cfg(all(feature = "blas", feature = "blas-inject"))]
impl<T> Scalar for T where T: ScalarBase + ElementOpApply {}
#[derive(Debug, thiserror::Error)]
pub enum EinsumError {
#[error("duplicate axis label: {0}")]
DuplicateAxis(String),
#[error("output axis {0} not found in any input")]
OrphanOutputAxis(String),
#[error("dimension mismatch for axis {axis:?}: {dim_a} vs {dim_b}")]
DimensionMismatch {
axis: String,
dim_a: usize,
dim_b: usize,
},
#[error("invalid dot-general config: {0}")]
InvalidDotGeneralConfig(String),
#[error("output shape mismatch: expected {expected:?}, got {got:?}")]
OutputShapeMismatch {
expected: Vec<usize>,
got: Vec<usize>,
},
#[error(transparent)]
Strided(#[from] strided_view::StridedError),
}
pub type Result<T> = std::result::Result<T, EinsumError>;
fn op_is_conj<Op: 'static>() -> bool {
TypeId::of::<Op>() == TypeId::of::<Conj>() || TypeId::of::<Op>() == TypeId::of::<Adjoint>()
}
pub fn einsum2_into<T: Scalar, OpA, OpB, ID: AxisId>(
c: StridedViewMut<T>,
a: &StridedView<T, OpA>,
b: &StridedView<T, OpB>,
ic: &[ID],
ia: &[ID],
ib: &[ID],
alpha: T,
beta: T,
) -> Result<()>
where
OpA: ElementOp<T> + 'static,
OpB: ElementOp<T> + 'static,
{
let plan = Einsum2Plan::new(ia, ib, ic)?;
validate_dimensions::<ID>(&plan, a.dims(), b.dims(), c.dims(), ia, ib, ic)?;
let left_trace = trace::find_trace_indices(ia, ib, ic);
let (a_buf, conj_a) = if !left_trace.is_empty() {
(Some(trace::reduce_trace_axes(a, &left_trace)?), false)
} else {
(None, op_is_conj::<OpA>())
};
let a_view: StridedView<T> = match a_buf.as_ref() {
Some(buf) => buf.view(),
None => StridedView::new(a.data(), a.dims(), a.strides(), a.offset())
.expect("strip_op_view: metadata already validated"),
};
let right_trace = trace::find_trace_indices(ib, ia, ic);
let (b_buf, conj_b) = if !right_trace.is_empty() {
(Some(trace::reduce_trace_axes(b, &right_trace)?), false)
} else {
(None, op_is_conj::<OpB>())
};
let b_view: StridedView<T> = match b_buf.as_ref() {
Some(buf) => buf.view(),
None => StridedView::new(b.data(), b.dims(), b.strides(), b.offset())
.expect("strip_op_view: metadata already validated"),
};
#[cfg(any(feature = "faer", feature = "blas", feature = "blas-inject"))]
{
let conj_fn = make_conj_fn::<T>();
einsum2_dispatch::<T, backend::ActiveBackend, _>(
c, &a_view, &b_view, &plan, alpha, beta, conj_a, conj_b, conj_fn,
)?;
}
#[cfg(not(any(feature = "faer", feature = "blas", feature = "blas-inject")))]
{
let a_perm = a_view.permute(&plan.left_perm)?;
let b_perm = b_view.permute(&plan.right_perm)?;
let mut c_perm = c.permute(&plan.c_to_internal_perm)?;
if plan.sum.is_empty() && plan.lo.is_empty() && plan.ro.is_empty() && beta == T::zero() {
let mul_fn = move |a_val: T, b_val: T| -> T {
let a_c = if conj_a { Conj::apply(a_val) } else { a_val };
let b_c = if conj_b { Conj::apply(b_val) } else { b_val };
alpha * a_c * b_c
};
zip_map2_into(&mut c_perm, &a_perm, &b_perm, mul_fn)?;
return Ok(());
}
bgemm_naive::bgemm_strided_into(
&mut c_perm,
&a_perm,
&b_perm,
plan.batch.len(),
plan.lo.len(),
plan.ro.len(),
plan.sum.len(),
alpha,
beta,
conj_a,
conj_b,
)?;
}
Ok(())
}
pub fn einsum2_naive_into<T, ID, MapA, MapB>(
c: StridedViewMut<T>,
a: &StridedView<T>,
b: &StridedView<T>,
ic: &[ID],
ia: &[ID],
ib: &[ID],
alpha: T,
beta: T,
map_a: MapA,
map_b: MapB,
) -> Result<()>
where
T: ScalarBase,
ID: AxisId,
MapA: Fn(T) -> T + strided_kernel::MaybeSync,
MapB: Fn(T) -> T + strided_kernel::MaybeSync,
{
let plan = Einsum2Plan::new(ia, ib, ic)?;
validate_dimensions::<ID>(&plan, a.dims(), b.dims(), c.dims(), ia, ib, ic)?;
let left_trace = trace::find_trace_indices(ia, ib, ic);
let (a_buf, use_map_a) = if !left_trace.is_empty() {
let mut mapped = unsafe { strided_view::StridedArray::<T>::col_major_uninit(a.dims()) };
strided_kernel::map_into(&mut mapped.view_mut(), a, &map_a)?;
let reduced = trace::reduce_trace_axes(&mapped.view(), &left_trace)?;
(Some(reduced), false)
} else {
(None, true)
};
let a_view: StridedView<T> = match a_buf.as_ref() {
Some(buf) => buf.view(),
None => a.clone(),
};
let right_trace = trace::find_trace_indices(ib, ia, ic);
let (b_buf, use_map_b) = if !right_trace.is_empty() {
let mut mapped = unsafe { strided_view::StridedArray::<T>::col_major_uninit(b.dims()) };
strided_kernel::map_into(&mut mapped.view_mut(), b, &map_b)?;
let reduced = trace::reduce_trace_axes(&mapped.view(), &right_trace)?;
(Some(reduced), false)
} else {
(None, true)
};
let b_view: StridedView<T> = match b_buf.as_ref() {
Some(buf) => buf.view(),
None => b.clone(),
};
let a_perm = a_view.permute(&plan.left_perm)?;
let b_perm = b_view.permute(&plan.right_perm)?;
let mut c_perm = c.permute(&plan.c_to_internal_perm)?;
if plan.sum.is_empty() && plan.lo.is_empty() && plan.ro.is_empty() && beta == T::zero() {
let mul_fn = move |a_val: T, b_val: T| -> T {
let a_c = if use_map_a { map_a(a_val) } else { a_val };
let b_c = if use_map_b { map_b(b_val) } else { b_val };
alpha * a_c * b_c
};
zip_map2_into(&mut c_perm, &a_perm, &b_perm, mul_fn)?;
return Ok(());
}
let final_map_a: Box<dyn Fn(T) -> T> = if use_map_a {
Box::new(map_a)
} else {
Box::new(|x| x)
};
let final_map_b: Box<dyn Fn(T) -> T> = if use_map_b {
Box::new(map_b)
} else {
Box::new(|x| x)
};
bgemm_naive::bgemm_strided_into_with_map(
&mut c_perm,
&a_perm,
&b_perm,
plan.batch.len(),
plan.lo.len(),
plan.ro.len(),
plan.sum.len(),
alpha,
beta,
final_map_a,
final_map_b,
)?;
Ok(())
}
pub fn einsum2_with_backend_into<T, B, ID>(
c: StridedViewMut<T>,
a: &StridedView<T>,
b: &StridedView<T>,
ic: &[ID],
ia: &[ID],
ib: &[ID],
alpha: T,
beta: T,
) -> Result<()>
where
T: ScalarBase,
B: Backend<T>,
ID: AxisId,
{
let plan = Einsum2Plan::new(ia, ib, ic)?;
validate_dimensions::<ID>(&plan, a.dims(), b.dims(), c.dims(), ia, ib, ic)?;
let left_trace = trace::find_trace_indices(ia, ib, ic);
let a_buf = if !left_trace.is_empty() {
Some(trace::reduce_trace_axes(a, &left_trace)?)
} else {
None
};
let a_view: StridedView<T> = match a_buf.as_ref() {
Some(buf) => buf.view(),
None => a.clone(),
};
let right_trace = trace::find_trace_indices(ib, ia, ic);
let b_buf = if !right_trace.is_empty() {
Some(trace::reduce_trace_axes(b, &right_trace)?)
} else {
None
};
let b_view: StridedView<T> = match b_buf.as_ref() {
Some(buf) => buf.view(),
None => b.clone(),
};
einsum2_dispatch::<T, B, _>(c, &a_view, &b_view, &plan, alpha, beta, false, false, None)
}
#[cfg(any(feature = "faer", feature = "blas", feature = "blas-inject"))]
fn make_conj_fn<T: Scalar>() -> Option<fn(T) -> T> {
if <backend::ActiveBackend as Backend<T>>::MATERIALIZES_CONJ {
Some(|x| Conj::apply(x))
} else {
None
}
}
fn scale_or_zero_strided_mut<T: ScalarBase>(c: &mut StridedViewMut<T>, beta: T) {
if c.is_empty() {
return;
}
let dims = c.dims().to_vec();
let strides = c.strides().to_vec();
let ptr = c.as_mut_ptr();
let zero = T::zero();
fn visit<T: ScalarBase>(
ptr: *mut T,
dims: &[usize],
strides: &[isize],
axis: usize,
offset: isize,
beta: T,
zero: T,
) {
if axis == dims.len() {
unsafe {
let dst = ptr.offset(offset);
if beta == zero {
*dst = zero;
} else {
*dst = beta * *dst;
}
}
return;
}
for i in 0..dims[axis] {
visit(
ptr,
dims,
strides,
axis + 1,
offset + i as isize * strides[axis],
beta,
zero,
);
}
}
visit(ptr, &dims, &strides, 0, 0, beta, zero);
}
pub(crate) fn einsum2_dispatch<T, B, ID>(
c: StridedViewMut<T>,
a: &StridedView<T>,
b: &StridedView<T>,
plan: &Einsum2Plan<ID>,
alpha: T,
beta: T,
conj_a: bool,
conj_b: bool,
conj_fn: Option<fn(T) -> T>,
) -> Result<()>
where
T: ScalarBase,
B: Backend<T>,
ID: AxisId,
{
let a_perm = a.permute(&plan.left_perm)?;
let b_perm = b.permute(&plan.right_perm)?;
let mut c_perm = c.permute(&plan.c_to_internal_perm)?;
if c_perm.is_empty() {
return Ok(());
}
if plan.sum.is_empty() && plan.lo.is_empty() && plan.ro.is_empty() && beta == T::zero() {
if !conj_a && !conj_b && alpha == T::one() {
zip_map2_into(&mut c_perm, &a_perm, &b_perm, |a_val, b_val| a_val * b_val)?;
} else if !conj_a && !conj_b {
let mul_fn = move |a_val: T, b_val: T| -> T { alpha * a_val * b_val };
zip_map2_into(&mut c_perm, &a_perm, &b_perm, mul_fn)?;
} else {
let conj_fn = conj_fn.unwrap_or(|x| x);
let mul_fn = move |a_val: T, b_val: T| -> T {
let a_c = if conj_a { conj_fn(a_val) } else { a_val };
let b_c = if conj_b { conj_fn(b_val) } else { b_val };
alpha * a_c * b_c
};
zip_map2_into(&mut c_perm, &a_perm, &b_perm, mul_fn)?;
}
return Ok(());
}
let n_lo = plan.lo.len();
let n_ro = plan.ro.len();
let n_sum = plan.sum.len();
let use_pool = true;
let materialize = if B::MATERIALIZES_CONJ { conj_fn } else { None };
let a_op = contiguous::prepare_input_view(
&a_perm,
n_lo,
n_sum,
conj_a,
B::REQUIRES_UNIT_STRIDE,
use_pool,
materialize,
)?;
let b_op = contiguous::prepare_input_view(
&b_perm,
n_sum,
n_ro,
conj_b,
B::REQUIRES_UNIT_STRIDE,
use_pool,
materialize,
)?;
let mut c_op = contiguous::prepare_output_view(
&mut c_perm,
n_lo,
n_ro,
beta,
B::REQUIRES_UNIT_STRIDE,
use_pool,
)?;
let lo_dims = &a_perm.dims()[..n_lo];
let sum_dims = &a_perm.dims()[n_lo..n_lo + n_sum];
let batch_dims = &a_perm.dims()[n_lo + n_sum..];
let ro_dims = &b_perm.dims()[n_sum..n_sum + n_ro];
if sum_dims.iter().any(|&dim| dim == 0) {
scale_or_zero_strided_mut(&mut c_perm, beta);
return Ok(());
}
let m: usize = lo_dims.iter().product::<usize>().max(1);
let k: usize = sum_dims.iter().product::<usize>().max(1);
let n: usize = ro_dims.iter().product::<usize>().max(1);
B::bgemm_contiguous_into(&mut c_op, &a_op, &b_op, batch_dims, m, n, k, alpha, beta)?;
c_op.finalize_into(&mut c_perm)?;
Ok(())
}
#[cfg(any(feature = "faer", feature = "blas", feature = "blas-inject"))]
pub fn einsum2_into_owned<T: Scalar, ID: AxisId>(
c: StridedViewMut<T>,
a: StridedArray<T>,
b: StridedArray<T>,
ic: &[ID],
ia: &[ID],
ib: &[ID],
alpha: T,
beta: T,
conj_a: bool,
conj_b: bool,
) -> Result<()>
where
backend::ActiveBackend: Backend<T>,
{
let plan = Einsum2Plan::new(ia, ib, ic)?;
validate_dimensions::<ID>(&plan, a.dims(), b.dims(), c.dims(), ia, ib, ic)?;
let left_trace = trace::find_trace_indices(ia, ib, ic);
let (a_for_gemm, conj_a_final) = if !left_trace.is_empty() {
(trace::reduce_trace_axes(&a.view(), &left_trace)?, false)
} else {
(a, conj_a)
};
let right_trace = trace::find_trace_indices(ib, ia, ic);
let (b_for_gemm, conj_b_final) = if !right_trace.is_empty() {
(trace::reduce_trace_axes(&b.view(), &right_trace)?, false)
} else {
(b, conj_b)
};
let a_perm = a_for_gemm.permuted(&plan.left_perm)?;
let b_perm = b_for_gemm.permuted(&plan.right_perm)?;
let mut c_perm = c.permute(&plan.c_to_internal_perm)?;
let n_lo = plan.lo.len();
let n_ro = plan.ro.len();
let n_sum = plan.sum.len();
if plan.sum.is_empty() && plan.lo.is_empty() && plan.ro.is_empty() && beta == T::zero() {
let mul_fn = move |a_val: T, b_val: T| -> T {
let a_c = if conj_a_final {
Conj::apply(a_val)
} else {
a_val
};
let b_c = if conj_b_final {
Conj::apply(b_val)
} else {
b_val
};
alpha * a_c * b_c
};
zip_map2_into(&mut c_perm, &a_perm.view(), &b_perm.view(), mul_fn)?;
return Ok(());
}
let a_dims_perm = a_perm.dims().to_vec();
let b_dims_perm = b_perm.dims().to_vec();
let lo_dims = &a_dims_perm[..n_lo];
let sum_dims = &a_dims_perm[n_lo..n_lo + n_sum];
let batch_dims = a_dims_perm[n_lo + n_sum..].to_vec();
let ro_dims = &b_dims_perm[n_sum..n_sum + n_ro];
let m: usize = lo_dims.iter().product::<usize>().max(1);
let k: usize = sum_dims.iter().product::<usize>().max(1);
let n: usize = ro_dims.iter().product::<usize>().max(1);
let conj_fn = make_conj_fn::<T>();
let materialize = if <backend::ActiveBackend as Backend<T>>::MATERIALIZES_CONJ {
conj_fn
} else {
None
};
let use_pool = true;
let unit_stride = <backend::ActiveBackend as Backend<T>>::REQUIRES_UNIT_STRIDE;
let a_op = contiguous::prepare_input_owned(
a_perm,
n_lo,
n_sum,
conj_a_final,
unit_stride,
use_pool,
materialize,
)?;
let b_op = contiguous::prepare_input_owned(
b_perm,
n_sum,
n_ro,
conj_b_final,
unit_stride,
use_pool,
materialize,
)?;
let mut c_op =
contiguous::prepare_output_view(&mut c_perm, n_lo, n_ro, beta, unit_stride, use_pool)?;
backend::ActiveBackend::bgemm_contiguous_into(
&mut c_op,
&a_op,
&b_op,
&batch_dims,
m,
n,
k,
alpha,
beta,
)?;
c_op.finalize_into(&mut c_perm)?;
Ok(())
}
fn validate_dimensions<ID: AxisId>(
plan: &Einsum2Plan<ID>,
a_dims: &[usize],
b_dims: &[usize],
c_dims: &[usize],
ia: &[ID],
ib: &[ID],
ic: &[ID],
) -> Result<()> {
let find_dim = |labels: &[ID], dims: &[usize], id: &ID| -> usize {
labels
.iter()
.position(|x| x == id)
.map(|i| dims[i])
.unwrap()
};
for id in &plan.batch {
let da = find_dim(ia, a_dims, id);
let db = find_dim(ib, b_dims, id);
let dc = find_dim(ic, c_dims, id);
if da != db || da != dc {
return Err(EinsumError::DimensionMismatch {
axis: format!("{:?}", id),
dim_a: da,
dim_b: db,
});
}
}
for id in &plan.sum {
let da = find_dim(ia, a_dims, id);
let db = find_dim(ib, b_dims, id);
if da != db {
return Err(EinsumError::DimensionMismatch {
axis: format!("{:?}", id),
dim_a: da,
dim_b: db,
});
}
}
for id in &plan.lo {
let da = find_dim(ia, a_dims, id);
let dc = find_dim(ic, c_dims, id);
if da != dc {
return Err(EinsumError::DimensionMismatch {
axis: format!("{:?}", id),
dim_a: da,
dim_b: dc,
});
}
}
for id in &plan.ro {
let db = find_dim(ib, b_dims, id);
let dc = find_dim(ic, c_dims, id);
if db != dc {
return Err(EinsumError::DimensionMismatch {
axis: format!("{:?}", id),
dim_a: db,
dim_b: dc,
});
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use strided_view::StridedArray;
#[test]
fn test_matmul_ij_jk_ik() {
let a = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
[[1.0, 2.0], [3.0, 4.0]][idx[0]][idx[1]]
});
let b = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
[[5.0, 6.0], [7.0, 8.0]][idx[0]][idx[1]]
});
let mut c = StridedArray::<f64>::row_major(&[2, 2]);
einsum2_into(
c.view_mut(),
&a.view(),
&b.view(),
&['i', 'k'],
&['i', 'j'],
&['j', 'k'],
1.0,
0.0,
)
.unwrap();
assert_eq!(c.get(&[0, 0]), 19.0);
assert_eq!(c.get(&[0, 1]), 22.0);
assert_eq!(c.get(&[1, 0]), 43.0);
assert_eq!(c.get(&[1, 1]), 50.0);
}
#[test]
fn test_matmul_rect() {
let a =
StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
let b =
StridedArray::<f64>::from_fn_row_major(&[3, 4], |idx| (idx[0] * 4 + idx[1] + 1) as f64);
let mut c = StridedArray::<f64>::row_major(&[2, 4]);
einsum2_into(
c.view_mut(),
&a.view(),
&b.view(),
&['i', 'k'],
&['i', 'j'],
&['j', 'k'],
1.0,
0.0,
)
.unwrap();
assert_eq!(c.get(&[0, 0]), 38.0);
assert_eq!(c.get(&[1, 3]), 128.0);
}
#[test]
fn test_batched_matmul() {
let a = StridedArray::<f64>::from_fn_row_major(&[2, 2, 3], |idx| {
(idx[0] * 6 + idx[1] * 3 + idx[2] + 1) as f64
});
let b = StridedArray::<f64>::from_fn_row_major(&[2, 3, 2], |idx| {
(idx[0] * 6 + idx[1] * 2 + idx[2] + 1) as f64
});
let mut c = StridedArray::<f64>::row_major(&[2, 2, 2]);
einsum2_into(
c.view_mut(),
&a.view(),
&b.view(),
&['b', 'i', 'k'],
&['b', 'i', 'j'],
&['b', 'j', 'k'],
1.0,
0.0,
)
.unwrap();
assert_eq!(c.get(&[0, 0, 0]), 22.0);
}
#[test]
fn test_batched_matmul_col_major_output() {
let a_data = vec![1.0, 0.0, 0.0, 1.0, 2.0, 0.0, 0.0, 2.0];
let b_data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
let a = StridedArray::<f64>::from_parts(a_data, &[2, 2, 2], &[4, 2, 1], 0).unwrap();
let b = StridedArray::<f64>::from_parts(b_data, &[2, 2, 2], &[4, 2, 1], 0).unwrap();
let mut c = StridedArray::<f64>::col_major(&[2, 2, 2]);
einsum2_into(
c.view_mut(),
&a.view(),
&b.view(),
&['b', 'i', 'k'],
&['b', 'i', 'j'],
&['b', 'j', 'k'],
1.0,
0.0,
)
.unwrap();
assert_eq!(c.get(&[0, 0, 0]), 1.0);
assert_eq!(c.get(&[0, 0, 1]), 2.0);
assert_eq!(c.get(&[0, 1, 0]), 3.0);
assert_eq!(c.get(&[0, 1, 1]), 4.0);
assert_eq!(c.get(&[1, 0, 0]), 10.0);
assert_eq!(c.get(&[1, 1, 1]), 16.0);
}
#[test]
fn test_outer_product() {
let a = StridedArray::<f64>::from_fn_row_major(&[3], |idx| (idx[0] + 1) as f64);
let b = StridedArray::<f64>::from_fn_row_major(&[4], |idx| (idx[0] + 1) as f64);
let mut c = StridedArray::<f64>::row_major(&[3, 4]);
einsum2_into(
c.view_mut(),
&a.view(),
&b.view(),
&['i', 'j'],
&['i'],
&['j'],
1.0,
0.0,
)
.unwrap();
assert_eq!(c.get(&[0, 0]), 1.0);
assert_eq!(c.get(&[2, 3]), 12.0);
}
#[test]
fn test_dot_product() {
let a = StridedArray::<f64>::from_fn_row_major(&[3], |idx| (idx[0] + 1) as f64);
let b = StridedArray::<f64>::from_fn_row_major(&[3], |idx| (idx[0] + 1) as f64);
let mut c = StridedArray::<f64>::row_major(&[]);
einsum2_into(
c.view_mut(),
&a.view(),
&b.view(),
&[] as &[char],
&['i'],
&['i'],
1.0,
0.0,
)
.unwrap();
assert_eq!(c.get(&[]), 14.0);
}
#[test]
fn test_alpha_beta() {
let a = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
[[1.0, 0.0], [0.0, 1.0]][idx[0]][idx[1]] });
let b = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
[[1.0, 2.0], [3.0, 4.0]][idx[0]][idx[1]]
});
let mut c = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
[[10.0, 20.0], [30.0, 40.0]][idx[0]][idx[1]]
});
einsum2_into(
c.view_mut(),
&a.view(),
&b.view(),
&['i', 'k'],
&['i', 'j'],
&['j', 'k'],
2.0,
3.0,
)
.unwrap();
assert_eq!(c.get(&[0, 0]), 32.0); assert_eq!(c.get(&[1, 1]), 128.0); }
#[test]
fn test_transposed_output() {
let a = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
[[1.0, 2.0], [3.0, 4.0]][idx[0]][idx[1]]
});
let b = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
[[5.0, 6.0], [7.0, 8.0]][idx[0]][idx[1]]
});
let mut c = StridedArray::<f64>::row_major(&[2, 2]);
einsum2_into(
c.view_mut(),
&a.view(),
&b.view(),
&['k', 'i'], &['i', 'j'],
&['j', 'k'],
1.0,
0.0,
)
.unwrap();
assert_eq!(c.get(&[0, 0]), 19.0); assert_eq!(c.get(&[0, 1]), 43.0); assert_eq!(c.get(&[1, 0]), 22.0); assert_eq!(c.get(&[1, 1]), 50.0); }
#[test]
fn test_left_trace() {
let a =
StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
let b =
StridedArray::<f64>::from_fn_row_major(&[3, 2], |idx| (idx[0] * 2 + idx[1] + 1) as f64);
let mut c = StridedArray::<f64>::row_major(&[2]);
einsum2_into(
c.view_mut(),
&a.view(),
&b.view(),
&['k'],
&['i', 'j'],
&['j', 'k'],
1.0,
0.0,
)
.unwrap();
assert_eq!(c.get(&[0]), 71.0);
assert_eq!(c.get(&[1]), 92.0);
}
#[test]
fn test_u32_labels() {
let a = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
[[1.0, 2.0], [3.0, 4.0]][idx[0]][idx[1]]
});
let b = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
[[5.0, 6.0], [7.0, 8.0]][idx[0]][idx[1]]
});
let mut c = StridedArray::<f64>::row_major(&[2, 2]);
einsum2_into(
c.view_mut(),
&a.view(),
&b.view(),
&[0u32, 2],
&[0u32, 1],
&[1u32, 2],
1.0,
0.0,
)
.unwrap();
assert_eq!(c.get(&[0, 0]), 19.0);
assert_eq!(c.get(&[1, 1]), 50.0);
}
#[test]
fn test_complex_matmul() {
use num_complex::Complex64;
let i = Complex64::i();
let a_vals = [
[1.0 + i, Complex64::new(2.0, 0.0)],
[Complex64::new(3.0, 0.0), 4.0 - i],
];
let a = StridedArray::<Complex64>::from_fn_row_major(&[2, 2], |idx| a_vals[idx[0]][idx[1]]);
let b_vals = [
[Complex64::new(1.0, 0.0), i],
[Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)],
];
let b = StridedArray::<Complex64>::from_fn_row_major(&[2, 2], |idx| b_vals[idx[0]][idx[1]]);
let mut c = StridedArray::<Complex64>::row_major(&[2, 2]);
einsum2_into(
c.view_mut(),
&a.view(),
&b.view(),
&['i', 'k'],
&['i', 'j'],
&['j', 'k'],
Complex64::new(1.0, 0.0),
Complex64::new(0.0, 0.0),
)
.unwrap();
assert_eq!(c.get(&[0, 0]), 1.0 + i);
assert_eq!(c.get(&[0, 1]), 1.0 + i);
assert_eq!(c.get(&[1, 0]), Complex64::new(3.0, 0.0));
assert_eq!(c.get(&[1, 1]), 4.0 + 2.0 * i);
}
#[test]
fn test_complex_matmul_with_conj() {
use num_complex::Complex64;
let i = Complex64::i();
let a_vals = [[1.0 + i, 2.0 * i], [Complex64::new(3.0, 0.0), 4.0 - i]];
let a = StridedArray::<Complex64>::from_fn_row_major(&[2, 2], |idx| a_vals[idx[0]][idx[1]]);
let b = StridedArray::<Complex64>::from_fn_row_major(&[2, 2], |idx| {
if idx[0] == idx[1] {
Complex64::new(1.0, 0.0)
} else {
Complex64::new(0.0, 0.0)
}
});
let mut c = StridedArray::<Complex64>::row_major(&[2, 2]);
let a_conj = a.view().conj();
einsum2_into(
c.view_mut(),
&a_conj,
&b.view(),
&['i', 'k'],
&['i', 'j'],
&['j', 'k'],
Complex64::new(1.0, 0.0),
Complex64::new(0.0, 0.0),
)
.unwrap();
assert_eq!(c.get(&[0, 0]), 1.0 - i);
assert_eq!(c.get(&[0, 1]), -2.0 * i);
assert_eq!(c.get(&[1, 0]), Complex64::new(3.0, 0.0));
assert_eq!(c.get(&[1, 1]), 4.0 + i);
}
#[test]
fn test_complex_matmul_with_conj_both() {
use num_complex::Complex64;
let i = Complex64::i();
let a_vals = [
[1.0 + i, Complex64::new(0.0, 0.0)],
[Complex64::new(0.0, 0.0), 2.0 - i],
];
let a = StridedArray::<Complex64>::from_fn_row_major(&[2, 2], |idx| a_vals[idx[0]][idx[1]]);
let b_vals = [
[Complex64::new(1.0, 0.0), i],
[Complex64::new(0.0, 0.0), 1.0 + i],
];
let b = StridedArray::<Complex64>::from_fn_row_major(&[2, 2], |idx| b_vals[idx[0]][idx[1]]);
let mut c = StridedArray::<Complex64>::row_major(&[2, 2]);
let a_conj = a.view().conj();
let b_conj = b.view().conj();
einsum2_into(
c.view_mut(),
&a_conj,
&b_conj,
&['i', 'k'],
&['i', 'j'],
&['j', 'k'],
Complex64::new(1.0, 0.0),
Complex64::new(0.0, 0.0),
)
.unwrap();
assert_eq!(c.get(&[0, 0]), 1.0 - i);
assert_eq!(c.get(&[0, 1]), -(1.0 + i));
assert_eq!(c.get(&[1, 0]), Complex64::new(0.0, 0.0));
assert_eq!(c.get(&[1, 1]), 3.0 - i);
}
#[test]
fn test_elementwise_hadamard() {
let a = StridedArray::<f64>::from_fn_row_major(&[3, 4, 5], |idx| {
(idx[0] * 20 + idx[1] * 5 + idx[2] + 1) as f64
});
let b = StridedArray::<f64>::from_fn_row_major(&[3, 4, 5], |idx| {
(idx[0] * 20 + idx[1] * 5 + idx[2] + 1) as f64 * 0.1
});
let mut c = StridedArray::<f64>::row_major(&[3, 4, 5]);
einsum2_into(
c.view_mut(),
&a.view(),
&b.view(),
&['i', 'j', 'k'],
&['i', 'j', 'k'],
&['i', 'j', 'k'],
1.0,
0.0,
)
.unwrap();
assert!((c.get(&[0, 0, 0]) - 0.1).abs() < 1e-12);
assert!((c.get(&[2, 3, 4]) - 360.0).abs() < 1e-10);
}
#[test]
fn test_elementwise_hadamard_with_alpha() {
let a =
StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
let b =
StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
let mut c = StridedArray::<f64>::row_major(&[2, 3]);
einsum2_into(
c.view_mut(),
&a.view(),
&b.view(),
&['i', 'j'],
&['i', 'j'],
&['i', 'j'],
2.0,
0.0,
)
.unwrap();
assert_eq!(c.get(&[0, 0]), 2.0);
assert_eq!(c.get(&[1, 2]), 72.0);
}
#[cfg(any(feature = "faer", feature = "blas", feature = "blas-inject"))]
#[test]
fn test_einsum2_owned_matmul() {
let a = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
[[1.0, 2.0], [3.0, 4.0]][idx[0]][idx[1]]
});
let b = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
[[5.0, 6.0], [7.0, 8.0]][idx[0]][idx[1]]
});
let mut c = StridedArray::<f64>::row_major(&[2, 2]);
einsum2_into_owned(
c.view_mut(),
a,
b,
&['i', 'k'],
&['i', 'j'],
&['j', 'k'],
1.0,
0.0,
false,
false,
)
.unwrap();
assert_eq!(c.get(&[0, 0]), 19.0);
assert_eq!(c.get(&[0, 1]), 22.0);
assert_eq!(c.get(&[1, 0]), 43.0);
assert_eq!(c.get(&[1, 1]), 50.0);
}
#[cfg(any(feature = "faer", feature = "blas", feature = "blas-inject"))]
#[test]
fn test_einsum2_owned_batched() {
let a = StridedArray::<f64>::from_fn_row_major(&[2, 2, 3], |idx| {
(idx[0] * 6 + idx[1] * 3 + idx[2] + 1) as f64
});
let b = StridedArray::<f64>::from_fn_row_major(&[2, 3, 2], |idx| {
(idx[0] * 6 + idx[1] * 2 + idx[2] + 1) as f64
});
let mut c = StridedArray::<f64>::row_major(&[2, 2, 2]);
einsum2_into_owned(
c.view_mut(),
a,
b,
&['b', 'i', 'k'],
&['b', 'i', 'j'],
&['b', 'j', 'k'],
1.0,
0.0,
false,
false,
)
.unwrap();
assert_eq!(c.get(&[0, 0, 0]), 22.0);
}
#[cfg(any(feature = "faer", feature = "blas", feature = "blas-inject"))]
#[test]
fn test_einsum2_owned_alpha_beta() {
let a = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
[[1.0, 0.0], [0.0, 1.0]][idx[0]][idx[1]]
});
let b = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
[[1.0, 2.0], [3.0, 4.0]][idx[0]][idx[1]]
});
let mut c = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
[[10.0, 20.0], [30.0, 40.0]][idx[0]][idx[1]]
});
einsum2_into_owned(
c.view_mut(),
a,
b,
&['i', 'k'],
&['i', 'j'],
&['j', 'k'],
2.0,
3.0,
false,
false,
)
.unwrap();
assert_eq!(c.get(&[0, 0]), 32.0); assert_eq!(c.get(&[1, 1]), 128.0); }
#[cfg(any(feature = "faer", feature = "blas", feature = "blas-inject"))]
#[test]
fn test_einsum2_owned_elementwise() {
let a =
StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
let b =
StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
let mut c = StridedArray::<f64>::row_major(&[2, 3]);
einsum2_into_owned(
c.view_mut(),
a,
b,
&['i', 'j'],
&['i', 'j'],
&['i', 'j'],
2.0,
0.0,
false,
false,
)
.unwrap();
assert_eq!(c.get(&[0, 0]), 2.0); assert_eq!(c.get(&[1, 2]), 72.0); }
#[cfg(any(feature = "faer", feature = "blas", feature = "blas-inject"))]
#[test]
fn test_einsum2_owned_left_trace() {
let a =
StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
let b =
StridedArray::<f64>::from_fn_row_major(&[3, 2], |idx| (idx[0] * 2 + idx[1] + 1) as f64);
let mut c = StridedArray::<f64>::row_major(&[2]);
einsum2_into_owned(
c.view_mut(),
a,
b,
&['k'],
&['i', 'j'],
&['j', 'k'],
1.0,
0.0,
false,
false,
)
.unwrap();
assert_eq!(c.get(&[0]), 71.0);
assert_eq!(c.get(&[1]), 92.0);
}
#[test]
fn test_einsum2_naive_matmul() {
let a = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
[[1.0, 2.0], [3.0, 4.0]][idx[0]][idx[1]]
});
let b = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
[[5.0, 6.0], [7.0, 8.0]][idx[0]][idx[1]]
});
let mut c = StridedArray::<f64>::row_major(&[2, 2]);
einsum2_naive_into(
c.view_mut(),
&a.view(),
&b.view(),
&['i', 'k'],
&['i', 'j'],
&['j', 'k'],
1.0,
0.0,
|x| x,
|x| x,
)
.unwrap();
assert_eq!(c.get(&[0, 0]), 19.0);
assert_eq!(c.get(&[0, 1]), 22.0);
assert_eq!(c.get(&[1, 0]), 43.0);
assert_eq!(c.get(&[1, 1]), 50.0);
}
#[test]
fn test_einsum2_naive_elementwise() {
let a =
StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
let b =
StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
let mut c = StridedArray::<f64>::row_major(&[2, 3]);
einsum2_naive_into(
c.view_mut(),
&a.view(),
&b.view(),
&['i', 'j'],
&['i', 'j'],
&['i', 'j'],
2.0,
0.0,
|x| x,
|x| x,
)
.unwrap();
assert_eq!(c.get(&[0, 0]), 2.0); assert_eq!(c.get(&[1, 2]), 72.0); }
#[test]
fn test_einsum2_naive_custom_type() {
use num_traits::{One, Zero};
#[derive(Debug, Clone, Copy, PartialEq)]
struct MyVal(f64);
impl Default for MyVal {
fn default() -> Self {
MyVal(0.0)
}
}
impl std::ops::Add for MyVal {
type Output = Self;
fn add(self, rhs: Self) -> Self {
MyVal(self.0 + rhs.0)
}
}
impl std::ops::Mul for MyVal {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
MyVal(self.0 * rhs.0)
}
}
impl Zero for MyVal {
fn zero() -> Self {
MyVal(0.0)
}
fn is_zero(&self) -> bool {
self.0 == 0.0
}
}
impl One for MyVal {
fn one() -> Self {
MyVal(1.0)
}
}
let a = StridedArray::from_parts(
vec![MyVal(1.0), MyVal(2.0), MyVal(3.0), MyVal(4.0)],
&[2, 2],
&[2, 1],
0,
)
.unwrap();
let b = StridedArray::from_parts(
vec![MyVal(5.0), MyVal(6.0), MyVal(7.0), MyVal(8.0)],
&[2, 2],
&[2, 1],
0,
)
.unwrap();
let mut c = StridedArray::<MyVal>::col_major(&[2, 2]);
einsum2_naive_into(
c.view_mut(),
&a.view(),
&b.view(),
&['i', 'k'],
&['i', 'j'],
&['j', 'k'],
MyVal(1.0),
MyVal(0.0),
|x| x,
|x| x,
)
.unwrap();
assert_eq!(c.get(&[0, 0]), MyVal(19.0));
assert_eq!(c.get(&[0, 1]), MyVal(22.0));
assert_eq!(c.get(&[1, 0]), MyVal(43.0));
assert_eq!(c.get(&[1, 1]), MyVal(50.0));
}
#[test]
fn test_einsum2_naive_left_trace() {
let a =
StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
let b =
StridedArray::<f64>::from_fn_row_major(&[3, 2], |idx| (idx[0] * 2 + idx[1] + 1) as f64);
let mut c = StridedArray::<f64>::row_major(&[2]);
einsum2_naive_into(
c.view_mut(),
&a.view(),
&b.view(),
&['k'],
&['i', 'j'],
&['j', 'k'],
1.0,
0.0,
|x| x,
|x| x,
)
.unwrap();
assert_eq!(c.get(&[0]), 71.0);
assert_eq!(c.get(&[1]), 92.0);
}
struct TestNaiveBackend;
impl Backend<f64> for TestNaiveBackend {
const MATERIALIZES_CONJ: bool = false;
const REQUIRES_UNIT_STRIDE: bool = false;
fn bgemm_contiguous_into(
c: &mut contiguous::ContiguousOperandMut<f64>,
a: &contiguous::ContiguousOperand<f64>,
b: &contiguous::ContiguousOperand<f64>,
batch_dims: &[usize],
m: usize,
n: usize,
k: usize,
alpha: f64,
beta: f64,
) -> strided_view::Result<()> {
let a_ptr = a.ptr();
let b_ptr = b.ptr();
let c_ptr = c.ptr();
let a_rs = a.row_stride();
let a_cs = a.col_stride();
let b_rs = b.row_stride();
let b_cs = b.col_stride();
let c_rs = c.row_stride();
let c_cs = c.col_stride();
let mut batch_idx = crate::util::MultiIndex::new(batch_dims);
while batch_idx.next().is_some() {
let a_base = batch_idx.offset(a.batch_strides());
let b_base = batch_idx.offset(b.batch_strides());
let c_base = batch_idx.offset(c.batch_strides());
for i in 0..m {
for j in 0..n {
let mut acc = 0.0f64;
for l in 0..k {
let a_val = unsafe {
*a_ptr.offset(a_base + i as isize * a_rs + l as isize * a_cs)
};
let b_val = unsafe {
*b_ptr.offset(b_base + l as isize * b_rs + j as isize * b_cs)
};
acc += a_val * b_val;
}
unsafe {
let c_elem =
c_ptr.offset(c_base + i as isize * c_rs + j as isize * c_cs);
if beta == 0.0 {
*c_elem = alpha * acc;
} else {
*c_elem = alpha * acc + beta * (*c_elem);
}
}
}
}
}
Ok(())
}
}
#[test]
fn test_einsum2_with_backend_matmul() {
let a = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
[[1.0, 2.0], [3.0, 4.0]][idx[0]][idx[1]]
});
let b = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
[[5.0, 6.0], [7.0, 8.0]][idx[0]][idx[1]]
});
let mut c = StridedArray::<f64>::row_major(&[2, 2]);
einsum2_with_backend_into::<_, TestNaiveBackend, _>(
c.view_mut(),
&a.view(),
&b.view(),
&['i', 'k'],
&['i', 'j'],
&['j', 'k'],
1.0,
0.0,
)
.unwrap();
assert_eq!(c.get(&[0, 0]), 19.0);
assert_eq!(c.get(&[0, 1]), 22.0);
assert_eq!(c.get(&[1, 0]), 43.0);
assert_eq!(c.get(&[1, 1]), 50.0);
}
#[test]
fn test_einsum2_with_backend_elementwise() {
let a =
StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
let b =
StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
let mut c = StridedArray::<f64>::row_major(&[2, 3]);
einsum2_with_backend_into::<_, TestNaiveBackend, _>(
c.view_mut(),
&a.view(),
&b.view(),
&['i', 'j'],
&['i', 'j'],
&['i', 'j'],
2.0,
0.0,
)
.unwrap();
assert_eq!(c.get(&[0, 0]), 2.0); assert_eq!(c.get(&[1, 2]), 72.0); }
#[test]
fn test_einsum2_with_backend_custom_type() {
use num_traits::{One, Zero};
#[derive(Debug, Clone, Copy, PartialEq)]
struct Tropical(f64);
impl Default for Tropical {
fn default() -> Self {
Tropical(0.0)
}
}
impl std::ops::Add for Tropical {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Tropical(self.0 + rhs.0)
}
}
impl std::ops::Mul for Tropical {
type Output = Self;
fn mul(self, rhs: Self) -> Self {
Tropical(self.0 * rhs.0)
}
}
impl Zero for Tropical {
fn zero() -> Self {
Tropical(0.0)
}
fn is_zero(&self) -> bool {
self.0 == 0.0
}
}
impl One for Tropical {
fn one() -> Self {
Tropical(1.0)
}
}
struct TropicalBackend;
impl Backend<Tropical> for TropicalBackend {
const MATERIALIZES_CONJ: bool = false;
const REQUIRES_UNIT_STRIDE: bool = false;
fn bgemm_contiguous_into(
c: &mut contiguous::ContiguousOperandMut<Tropical>,
a: &contiguous::ContiguousOperand<Tropical>,
b: &contiguous::ContiguousOperand<Tropical>,
batch_dims: &[usize],
m: usize,
n: usize,
k: usize,
alpha: Tropical,
beta: Tropical,
) -> strided_view::Result<()> {
let a_ptr = a.ptr();
let b_ptr = b.ptr();
let c_ptr = c.ptr();
let a_rs = a.row_stride();
let a_cs = a.col_stride();
let b_rs = b.row_stride();
let b_cs = b.col_stride();
let c_rs = c.row_stride();
let c_cs = c.col_stride();
let mut batch_idx = crate::util::MultiIndex::new(batch_dims);
while batch_idx.next().is_some() {
let a_base = batch_idx.offset(a.batch_strides());
let b_base = batch_idx.offset(b.batch_strides());
let c_base = batch_idx.offset(c.batch_strides());
for i in 0..m {
for j in 0..n {
let mut acc = Tropical::zero();
for l in 0..k {
let a_val = unsafe {
*a_ptr.offset(a_base + i as isize * a_rs + l as isize * a_cs)
};
let b_val = unsafe {
*b_ptr.offset(b_base + l as isize * b_rs + j as isize * b_cs)
};
acc = acc + a_val * b_val;
}
unsafe {
let c_elem =
c_ptr.offset(c_base + i as isize * c_rs + j as isize * c_cs);
if beta == Tropical::zero() {
*c_elem = alpha * acc;
} else {
*c_elem = alpha * acc + beta * (*c_elem);
}
}
}
}
}
Ok(())
}
}
let a = StridedArray::from_parts(
vec![Tropical(1.0), Tropical(2.0), Tropical(3.0), Tropical(4.0)],
&[2, 2],
&[2, 1],
0,
)
.unwrap();
let b = StridedArray::from_parts(
vec![Tropical(5.0), Tropical(6.0), Tropical(7.0), Tropical(8.0)],
&[2, 2],
&[2, 1],
0,
)
.unwrap();
let mut c = StridedArray::<Tropical>::col_major(&[2, 2]);
einsum2_with_backend_into::<_, TropicalBackend, _>(
c.view_mut(),
&a.view(),
&b.view(),
&['i', 'k'],
&['i', 'j'],
&['j', 'k'],
Tropical(1.0),
Tropical(0.0),
)
.unwrap();
assert_eq!(c.get(&[0, 0]), Tropical(19.0));
assert_eq!(c.get(&[0, 1]), Tropical(22.0));
assert_eq!(c.get(&[1, 0]), Tropical(43.0));
assert_eq!(c.get(&[1, 1]), Tropical(50.0));
}
}