1#[cfg(all(feature = "blas", feature = "blas-inject"))]
25compile_error!("Features `blas` and `blas-inject` are mutually exclusive.");
26
27#[cfg(any(
28 all(feature = "blas-accelerate", feature = "blas-openblas"),
29 all(feature = "blas-accelerate", feature = "blas-mkl"),
30 all(feature = "blas-openblas", feature = "blas-mkl")
31))]
32compile_error!("Select at most one explicit BLAS provider feature.");
33
34#[cfg(all(feature = "blas-inject", not(feature = "blas")))]
35extern crate cblas_inject as cblas_sys;
36#[cfg(all(feature = "blas", not(feature = "blas-inject")))]
37extern crate cblas_sys;
38
39#[cfg(any(
40 all(feature = "blas", not(feature = "blas-inject")),
41 all(feature = "blas-inject", not(feature = "blas"))
42))]
43pub mod bgemm_blas;
44
45#[cfg(feature = "faer")]
46pub mod bgemm_faer;
48pub mod bgemm_naive;
50pub mod contiguous;
52pub mod dot_general;
54pub mod plan;
56pub mod raw_bgemm;
58pub mod trace;
60pub mod uninit;
62pub mod util;
64
65pub mod backend;
67
68use std::any::TypeId;
69use std::fmt::Debug;
70use std::hash::Hash;
71
72use strided_kernel::zip_map2_into;
73#[cfg(any(feature = "faer", feature = "blas", feature = "blas-inject"))]
74use strided_view::StridedArray;
75use strided_view::{Adjoint, Conj, ElementOp, ElementOpApply};
76
77pub use strided_traits::ScalarBase;
78pub use strided_view::{
79 col_major_strides, RawStridedMut, RawStridedRef, StridedView, StridedViewMut,
80};
81
82pub use backend::Backend;
83pub use dot_general::{
84 dot_general_into, dot_general_into_uninit, dot_general_with_backend_into, DotGeneralConfig,
85};
86pub use plan::Einsum2Plan;
87pub use raw_bgemm::{
88 bgemm_raw_strided_into, bgemm_raw_strided_into_unchecked, bgemm_raw_with_backend_into,
89 bgemm_raw_with_backend_into_unchecked,
90};
91pub use uninit::{bgemm_raw_strided_into_uninit, einsum2_into_owned_uninit, einsum2_into_uninit};
92
93pub trait AxisId: Clone + Eq + Hash + Debug {}
95impl<T: Clone + Eq + Hash + Debug> AxisId for T {}
96
97#[cfg(any(
106 all(feature = "blas", not(feature = "blas-inject")),
107 all(feature = "blas-inject", not(feature = "blas"))
108))]
109pub trait Scalar: ScalarBase + ElementOpApply + bgemm_blas::BlasGemm {}
110
111#[cfg(any(
112 all(feature = "blas", not(feature = "blas-inject")),
113 all(feature = "blas-inject", not(feature = "blas"))
114))]
115impl<T> Scalar for T where T: ScalarBase + ElementOpApply + bgemm_blas::BlasGemm {}
116
117#[cfg(all(feature = "faer", not(any(feature = "blas", feature = "blas-inject"))))]
122pub trait Scalar: ScalarBase + ElementOpApply + faer_traits::ComplexField {}
123
124#[cfg(all(feature = "faer", not(any(feature = "blas", feature = "blas-inject"))))]
125impl<T> Scalar for T where T: ScalarBase + ElementOpApply + faer_traits::ComplexField {}
126
127#[cfg(not(any(feature = "faer", feature = "blas", feature = "blas-inject")))]
129pub trait Scalar: ScalarBase + ElementOpApply {}
130
131#[cfg(not(any(feature = "faer", feature = "blas", feature = "blas-inject")))]
132impl<T> Scalar for T where T: ScalarBase + ElementOpApply {}
133
134#[cfg(all(feature = "blas", feature = "blas-inject"))]
139pub trait Scalar: ScalarBase + ElementOpApply {}
140
141#[cfg(all(feature = "blas", feature = "blas-inject"))]
142impl<T> Scalar for T where T: ScalarBase + ElementOpApply {}
143
144#[derive(Debug, thiserror::Error)]
146pub enum EinsumError {
147 #[error("duplicate axis label: {0}")]
148 DuplicateAxis(String),
149 #[error("output axis {0} not found in any input")]
150 OrphanOutputAxis(String),
151 #[error("dimension mismatch for axis {axis:?}: {dim_a} vs {dim_b}")]
152 DimensionMismatch {
153 axis: String,
154 dim_a: usize,
155 dim_b: usize,
156 },
157 #[error("invalid dot-general config: {0}")]
158 InvalidDotGeneralConfig(String),
159 #[error("output shape mismatch: expected {expected:?}, got {got:?}")]
160 OutputShapeMismatch {
161 expected: Vec<usize>,
162 got: Vec<usize>,
163 },
164 #[error("unsupported einsum operation: {0}")]
165 Unsupported(String),
166 #[error(transparent)]
167 Strided(#[from] strided_view::StridedError),
168}
169
170pub type Result<T> = std::result::Result<T, EinsumError>;
172
173fn op_is_conj<Op: 'static>() -> bool {
182 TypeId::of::<Op>() == TypeId::of::<Conj>() || TypeId::of::<Op>() == TypeId::of::<Adjoint>()
183}
184
185pub fn einsum2_into<T: Scalar, OpA, OpB, ID: AxisId>(
196 c: StridedViewMut<T>,
197 a: &StridedView<T, OpA>,
198 b: &StridedView<T, OpB>,
199 ic: &[ID],
200 ia: &[ID],
201 ib: &[ID],
202 alpha: T,
203 beta: T,
204) -> Result<()>
205where
206 OpA: ElementOp<T> + 'static,
207 OpB: ElementOp<T> + 'static,
208{
209 let plan = Einsum2Plan::new(ia, ib, ic)?;
211
212 validate_dimensions::<ID>(&plan, a.dims(), b.dims(), c.dims(), ia, ib, ic)?;
214
215 let left_trace = trace::find_trace_indices(ia, ib, ic);
220 let (a_buf, conj_a) = if !left_trace.is_empty() {
221 (Some(trace::reduce_trace_axes(a, &left_trace)?), false)
222 } else {
223 (None, op_is_conj::<OpA>())
224 };
225
226 let a_view: StridedView<T> = match a_buf.as_ref() {
227 Some(buf) => buf.view(),
228 None => StridedView::new(a.data(), a.dims(), a.strides(), a.offset())
229 .expect("strip_op_view: metadata already validated"),
230 };
231
232 let right_trace = trace::find_trace_indices(ib, ia, ic);
233 let (b_buf, conj_b) = if !right_trace.is_empty() {
234 (Some(trace::reduce_trace_axes(b, &right_trace)?), false)
235 } else {
236 (None, op_is_conj::<OpB>())
237 };
238
239 let b_view: StridedView<T> = match b_buf.as_ref() {
240 Some(buf) => buf.view(),
241 None => StridedView::new(b.data(), b.dims(), b.strides(), b.offset())
242 .expect("strip_op_view: metadata already validated"),
243 };
244
245 #[cfg(any(feature = "faer", feature = "blas", feature = "blas-inject"))]
247 {
248 let conj_fn = make_conj_fn::<T>();
249 einsum2_dispatch::<T, backend::ActiveBackend, _>(
250 c, &a_view, &b_view, &plan, alpha, beta, conj_a, conj_b, conj_fn,
251 )?;
252 }
253
254 #[cfg(not(any(feature = "faer", feature = "blas", feature = "blas-inject")))]
255 {
256 let a_perm = a_view.permute(&plan.left_perm)?;
257 let b_perm = b_view.permute(&plan.right_perm)?;
258 let mut c_perm = c.permute(&plan.c_to_internal_perm)?;
259
260 if plan.sum.is_empty() && plan.lo.is_empty() && plan.ro.is_empty() && beta == T::zero() {
261 let mul_fn = move |a_val: T, b_val: T| -> T {
262 let a_c = if conj_a { Conj::apply(a_val) } else { a_val };
263 let b_c = if conj_b { Conj::apply(b_val) } else { b_val };
264 alpha * a_c * b_c
265 };
266 zip_map2_into(&mut c_perm, &a_perm, &b_perm, mul_fn)?;
267 return Ok(());
268 }
269
270 bgemm_naive::bgemm_strided_into(
271 &mut c_perm,
272 &a_perm,
273 &b_perm,
274 plan.batch.len(),
275 plan.lo.len(),
276 plan.ro.len(),
277 plan.sum.len(),
278 alpha,
279 beta,
280 conj_a,
281 conj_b,
282 )?;
283 }
284
285 Ok(())
286}
287
288pub fn einsum2_naive_into<T, ID, MapA, MapB>(
296 c: StridedViewMut<T>,
297 a: &StridedView<T>,
298 b: &StridedView<T>,
299 ic: &[ID],
300 ia: &[ID],
301 ib: &[ID],
302 alpha: T,
303 beta: T,
304 map_a: MapA,
305 map_b: MapB,
306) -> Result<()>
307where
308 T: ScalarBase,
309 ID: AxisId,
310 MapA: Fn(T) -> T + strided_kernel::MaybeSync,
311 MapB: Fn(T) -> T + strided_kernel::MaybeSync,
312{
313 let plan = Einsum2Plan::new(ia, ib, ic)?;
314 validate_dimensions::<ID>(&plan, a.dims(), b.dims(), c.dims(), ia, ib, ic)?;
315
316 let left_trace = trace::find_trace_indices(ia, ib, ic);
320 let (a_buf, use_map_a) = if !left_trace.is_empty() {
321 let mut mapped = unsafe { strided_view::StridedArray::<T>::col_major_uninit(a.dims()) };
322 strided_kernel::map_into(&mut mapped.view_mut(), a, &map_a)?;
323 let reduced = trace::reduce_trace_axes(&mapped.view(), &left_trace)?;
324 (Some(reduced), false)
325 } else {
326 (None, true)
327 };
328 let a_view: StridedView<T> = match a_buf.as_ref() {
329 Some(buf) => buf.view(),
330 None => a.clone(),
331 };
332
333 let right_trace = trace::find_trace_indices(ib, ia, ic);
334 let (b_buf, use_map_b) = if !right_trace.is_empty() {
335 let mut mapped = unsafe { strided_view::StridedArray::<T>::col_major_uninit(b.dims()) };
336 strided_kernel::map_into(&mut mapped.view_mut(), b, &map_b)?;
337 let reduced = trace::reduce_trace_axes(&mapped.view(), &right_trace)?;
338 (Some(reduced), false)
339 } else {
340 (None, true)
341 };
342 let b_view: StridedView<T> = match b_buf.as_ref() {
343 Some(buf) => buf.view(),
344 None => b.clone(),
345 };
346
347 let a_perm = a_view.permute(&plan.left_perm)?;
348 let b_perm = b_view.permute(&plan.right_perm)?;
349 let mut c_perm = c.permute(&plan.c_to_internal_perm)?;
350
351 if plan.sum.is_empty() && plan.lo.is_empty() && plan.ro.is_empty() && beta == T::zero() {
353 let mul_fn = move |a_val: T, b_val: T| -> T {
354 let a_c = if use_map_a { map_a(a_val) } else { a_val };
355 let b_c = if use_map_b { map_b(b_val) } else { b_val };
356 alpha * a_c * b_c
357 };
358 zip_map2_into(&mut c_perm, &a_perm, &b_perm, mul_fn)?;
359 return Ok(());
360 }
361
362 let final_map_a: Box<dyn Fn(T) -> T> = if use_map_a {
363 Box::new(map_a)
364 } else {
365 Box::new(|x| x)
366 };
367 let final_map_b: Box<dyn Fn(T) -> T> = if use_map_b {
368 Box::new(map_b)
369 } else {
370 Box::new(|x| x)
371 };
372
373 bgemm_naive::bgemm_strided_into_with_map(
374 &mut c_perm,
375 &a_perm,
376 &b_perm,
377 plan.batch.len(),
378 plan.lo.len(),
379 plan.ro.len(),
380 plan.sum.len(),
381 alpha,
382 beta,
383 final_map_a,
384 final_map_b,
385 )?;
386
387 Ok(())
388}
389
390pub fn einsum2_with_backend_into<T, B, ID>(
399 c: StridedViewMut<T>,
400 a: &StridedView<T>,
401 b: &StridedView<T>,
402 ic: &[ID],
403 ia: &[ID],
404 ib: &[ID],
405 alpha: T,
406 beta: T,
407) -> Result<()>
408where
409 T: ScalarBase,
410 B: Backend<T>,
411 ID: AxisId,
412{
413 let plan = Einsum2Plan::new(ia, ib, ic)?;
414 validate_dimensions::<ID>(&plan, a.dims(), b.dims(), c.dims(), ia, ib, ic)?;
415
416 let left_trace = trace::find_trace_indices(ia, ib, ic);
418 let a_buf = if !left_trace.is_empty() {
419 Some(trace::reduce_trace_axes(a, &left_trace)?)
420 } else {
421 None
422 };
423 let a_view: StridedView<T> = match a_buf.as_ref() {
424 Some(buf) => buf.view(),
425 None => a.clone(),
426 };
427
428 let right_trace = trace::find_trace_indices(ib, ia, ic);
429 let b_buf = if !right_trace.is_empty() {
430 Some(trace::reduce_trace_axes(b, &right_trace)?)
431 } else {
432 None
433 };
434 let b_view: StridedView<T> = match b_buf.as_ref() {
435 Some(buf) => buf.view(),
436 None => b.clone(),
437 };
438
439 einsum2_dispatch::<T, B, _>(c, &a_view, &b_view, &plan, alpha, beta, false, false, None)
441}
442
443#[cfg(any(feature = "faer", feature = "blas", feature = "blas-inject"))]
449fn make_conj_fn<T: Scalar>() -> Option<fn(T) -> T> {
450 if <backend::ActiveBackend as Backend<T>>::MATERIALIZES_CONJ {
451 Some(|x| Conj::apply(x))
452 } else {
453 None
454 }
455}
456
457fn scale_or_zero_strided_mut<T: ScalarBase>(c: &mut StridedViewMut<T>, beta: T) {
458 if c.is_empty() {
459 return;
460 }
461
462 let dims = c.dims().to_vec();
463 let strides = c.strides().to_vec();
464 let ptr = c.as_mut_ptr();
465 let zero = T::zero();
466
467 fn visit<T: ScalarBase>(
468 ptr: *mut T,
469 dims: &[usize],
470 strides: &[isize],
471 axis: usize,
472 offset: isize,
473 beta: T,
474 zero: T,
475 ) {
476 if axis == dims.len() {
477 unsafe {
478 let dst = ptr.offset(offset);
479 if beta == zero {
480 *dst = zero;
481 } else {
482 *dst = beta * *dst;
483 }
484 }
485 return;
486 }
487
488 for i in 0..dims[axis] {
489 visit(
490 ptr,
491 dims,
492 strides,
493 axis + 1,
494 offset + i as isize * strides[axis],
495 beta,
496 zero,
497 );
498 }
499 }
500
501 visit(ptr, &dims, &strides, 0, 0, beta, zero);
502}
503
504pub(crate) fn einsum2_dispatch<T, B, ID>(
517 c: StridedViewMut<T>,
518 a: &StridedView<T>,
519 b: &StridedView<T>,
520 plan: &Einsum2Plan<ID>,
521 alpha: T,
522 beta: T,
523 conj_a: bool,
524 conj_b: bool,
525 conj_fn: Option<fn(T) -> T>,
526) -> Result<()>
527where
528 T: ScalarBase,
529 B: Backend<T>,
530 ID: AxisId,
531{
532 let a_perm = a.permute(&plan.left_perm)?;
534 let b_perm = b.permute(&plan.right_perm)?;
535 let mut c_perm = c.permute(&plan.c_to_internal_perm)?;
536
537 if c_perm.is_empty() {
538 return Ok(());
539 }
540
541 if plan.sum.is_empty() && plan.lo.is_empty() && plan.ro.is_empty() && beta == T::zero() {
543 if !conj_a && !conj_b && alpha == T::one() {
544 zip_map2_into(&mut c_perm, &a_perm, &b_perm, |a_val, b_val| a_val * b_val)?;
545 } else if !conj_a && !conj_b {
546 let mul_fn = move |a_val: T, b_val: T| -> T { alpha * a_val * b_val };
547 zip_map2_into(&mut c_perm, &a_perm, &b_perm, mul_fn)?;
548 } else {
549 let conj_fn = conj_fn.unwrap_or(|x| x);
550 let mul_fn = move |a_val: T, b_val: T| -> T {
551 let a_c = if conj_a { conj_fn(a_val) } else { a_val };
552 let b_c = if conj_b { conj_fn(b_val) } else { b_val };
553 alpha * a_c * b_c
554 };
555 zip_map2_into(&mut c_perm, &a_perm, &b_perm, mul_fn)?;
556 }
557 return Ok(());
558 }
559
560 let n_lo = plan.lo.len();
562 let n_ro = plan.ro.len();
563 let n_sum = plan.sum.len();
564 let use_pool = true;
565 let materialize = if B::MATERIALIZES_CONJ { conj_fn } else { None };
566
567 let a_op = contiguous::prepare_input_view(
568 &a_perm,
569 n_lo,
570 n_sum,
571 conj_a,
572 B::REQUIRES_UNIT_STRIDE,
573 use_pool,
574 materialize,
575 )?;
576 let b_op = contiguous::prepare_input_view(
577 &b_perm,
578 n_sum,
579 n_ro,
580 conj_b,
581 B::REQUIRES_UNIT_STRIDE,
582 use_pool,
583 materialize,
584 )?;
585 let mut c_op = contiguous::prepare_output_view(
586 &mut c_perm,
587 n_lo,
588 n_ro,
589 beta,
590 B::REQUIRES_UNIT_STRIDE,
591 use_pool,
592 )?;
593
594 let lo_dims = &a_perm.dims()[..n_lo];
596 let sum_dims = &a_perm.dims()[n_lo..n_lo + n_sum];
597 let batch_dims = &a_perm.dims()[n_lo + n_sum..];
598 let ro_dims = &b_perm.dims()[n_sum..n_sum + n_ro];
599 if sum_dims.iter().any(|&dim| dim == 0) {
600 scale_or_zero_strided_mut(&mut c_perm, beta);
601 return Ok(());
602 }
603 let m: usize = lo_dims.iter().product::<usize>().max(1);
604 let k: usize = sum_dims.iter().product::<usize>().max(1);
605 let n: usize = ro_dims.iter().product::<usize>().max(1);
606
607 B::bgemm_contiguous_into(&mut c_op, &a_op, &b_op, batch_dims, m, n, k, alpha, beta)?;
609
610 c_op.finalize_into(&mut c_perm)?;
612
613 Ok(())
614}
615
616#[cfg(any(feature = "faer", feature = "blas", feature = "blas-inject"))]
625pub fn einsum2_into_owned<T: Scalar, ID: AxisId>(
626 c: StridedViewMut<T>,
627 a: StridedArray<T>,
628 b: StridedArray<T>,
629 ic: &[ID],
630 ia: &[ID],
631 ib: &[ID],
632 alpha: T,
633 beta: T,
634 conj_a: bool,
635 conj_b: bool,
636) -> Result<()>
637where
638 backend::ActiveBackend: Backend<T>,
639{
640 let plan = Einsum2Plan::new(ia, ib, ic)?;
642
643 validate_dimensions::<ID>(&plan, a.dims(), b.dims(), c.dims(), ia, ib, ic)?;
645
646 let left_trace = trace::find_trace_indices(ia, ib, ic);
650 let (a_for_gemm, conj_a_final) = if !left_trace.is_empty() {
651 (trace::reduce_trace_axes(&a.view(), &left_trace)?, false)
652 } else {
653 (a, conj_a)
654 };
655
656 let right_trace = trace::find_trace_indices(ib, ia, ic);
657 let (b_for_gemm, conj_b_final) = if !right_trace.is_empty() {
658 (trace::reduce_trace_axes(&b.view(), &right_trace)?, false)
659 } else {
660 (b, conj_b)
661 };
662
663 let a_perm = a_for_gemm.permuted(&plan.left_perm)?;
665 let b_perm = b_for_gemm.permuted(&plan.right_perm)?;
666 let mut c_perm = c.permute(&plan.c_to_internal_perm)?;
667
668 let n_lo = plan.lo.len();
669 let n_ro = plan.ro.len();
670 let n_sum = plan.sum.len();
671
672 if plan.sum.is_empty() && plan.lo.is_empty() && plan.ro.is_empty() && beta == T::zero() {
674 let mul_fn = move |a_val: T, b_val: T| -> T {
675 let a_c = if conj_a_final {
676 Conj::apply(a_val)
677 } else {
678 a_val
679 };
680 let b_c = if conj_b_final {
681 Conj::apply(b_val)
682 } else {
683 b_val
684 };
685 alpha * a_c * b_c
686 };
687 zip_map2_into(&mut c_perm, &a_perm.view(), &b_perm.view(), mul_fn)?;
688 return Ok(());
689 }
690
691 let a_dims_perm = a_perm.dims().to_vec();
693 let b_dims_perm = b_perm.dims().to_vec();
694
695 let lo_dims = &a_dims_perm[..n_lo];
696 let sum_dims = &a_dims_perm[n_lo..n_lo + n_sum];
697 let batch_dims = a_dims_perm[n_lo + n_sum..].to_vec();
698 let ro_dims = &b_dims_perm[n_sum..n_sum + n_ro];
699 let m: usize = lo_dims.iter().product::<usize>().max(1);
700 let k: usize = sum_dims.iter().product::<usize>().max(1);
701 let n: usize = ro_dims.iter().product::<usize>().max(1);
702
703 let conj_fn = make_conj_fn::<T>();
705 let materialize = if <backend::ActiveBackend as Backend<T>>::MATERIALIZES_CONJ {
706 conj_fn
707 } else {
708 None
709 };
710 let use_pool = true;
711 let unit_stride = <backend::ActiveBackend as Backend<T>>::REQUIRES_UNIT_STRIDE;
712 let a_op = contiguous::prepare_input_owned(
713 a_perm,
714 n_lo,
715 n_sum,
716 conj_a_final,
717 unit_stride,
718 use_pool,
719 materialize,
720 )?;
721 let b_op = contiguous::prepare_input_owned(
722 b_perm,
723 n_sum,
724 n_ro,
725 conj_b_final,
726 unit_stride,
727 use_pool,
728 materialize,
729 )?;
730 let mut c_op =
731 contiguous::prepare_output_view(&mut c_perm, n_lo, n_ro, beta, unit_stride, use_pool)?;
732
733 backend::ActiveBackend::bgemm_contiguous_into(
735 &mut c_op,
736 &a_op,
737 &b_op,
738 &batch_dims,
739 m,
740 n,
741 k,
742 alpha,
743 beta,
744 )?;
745
746 c_op.finalize_into(&mut c_perm)?;
748
749 Ok(())
750}
751
752fn validate_dimensions<ID: AxisId>(
754 plan: &Einsum2Plan<ID>,
755 a_dims: &[usize],
756 b_dims: &[usize],
757 c_dims: &[usize],
758 ia: &[ID],
759 ib: &[ID],
760 ic: &[ID],
761) -> Result<()> {
762 let find_dim = |labels: &[ID], dims: &[usize], id: &ID| -> usize {
763 labels
764 .iter()
765 .position(|x| x == id)
766 .map(|i| dims[i])
767 .unwrap()
768 };
769
770 for id in &plan.batch {
772 let da = find_dim(ia, a_dims, id);
773 let db = find_dim(ib, b_dims, id);
774 let dc = find_dim(ic, c_dims, id);
775 if da != db || da != dc {
776 return Err(EinsumError::DimensionMismatch {
777 axis: format!("{:?}", id),
778 dim_a: da,
779 dim_b: db,
780 });
781 }
782 }
783
784 for id in &plan.sum {
786 let da = find_dim(ia, a_dims, id);
787 let db = find_dim(ib, b_dims, id);
788 if da != db {
789 return Err(EinsumError::DimensionMismatch {
790 axis: format!("{:?}", id),
791 dim_a: da,
792 dim_b: db,
793 });
794 }
795 }
796
797 for id in &plan.lo {
799 let da = find_dim(ia, a_dims, id);
800 let dc = find_dim(ic, c_dims, id);
801 if da != dc {
802 return Err(EinsumError::DimensionMismatch {
803 axis: format!("{:?}", id),
804 dim_a: da,
805 dim_b: dc,
806 });
807 }
808 }
809
810 for id in &plan.ro {
812 let db = find_dim(ib, b_dims, id);
813 let dc = find_dim(ic, c_dims, id);
814 if db != dc {
815 return Err(EinsumError::DimensionMismatch {
816 axis: format!("{:?}", id),
817 dim_a: db,
818 dim_b: dc,
819 });
820 }
821 }
822
823 Ok(())
824}
825
826#[cfg(test)]
827mod tests {
828 use super::*;
829 use strided_view::StridedArray;
830
831 #[test]
832 fn test_matmul_ij_jk_ik() {
833 let a = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
835 [[1.0, 2.0], [3.0, 4.0]][idx[0]][idx[1]]
836 });
837 let b = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
838 [[5.0, 6.0], [7.0, 8.0]][idx[0]][idx[1]]
839 });
840 let mut c = StridedArray::<f64>::row_major(&[2, 2]);
841
842 einsum2_into(
843 c.view_mut(),
844 &a.view(),
845 &b.view(),
846 &['i', 'k'],
847 &['i', 'j'],
848 &['j', 'k'],
849 1.0,
850 0.0,
851 )
852 .unwrap();
853
854 assert_eq!(c.get(&[0, 0]), 19.0);
855 assert_eq!(c.get(&[0, 1]), 22.0);
856 assert_eq!(c.get(&[1, 0]), 43.0);
857 assert_eq!(c.get(&[1, 1]), 50.0);
858 }
859
860 #[test]
861 fn test_matmul_rect() {
862 let a =
864 StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
865 let b =
866 StridedArray::<f64>::from_fn_row_major(&[3, 4], |idx| (idx[0] * 4 + idx[1] + 1) as f64);
867 let mut c = StridedArray::<f64>::row_major(&[2, 4]);
868
869 einsum2_into(
870 c.view_mut(),
871 &a.view(),
872 &b.view(),
873 &['i', 'k'],
874 &['i', 'j'],
875 &['j', 'k'],
876 1.0,
877 0.0,
878 )
879 .unwrap();
880
881 assert_eq!(c.get(&[0, 0]), 38.0);
883 assert_eq!(c.get(&[1, 3]), 128.0);
884 }
885
886 #[test]
887 fn test_batched_matmul() {
888 let a = StridedArray::<f64>::from_fn_row_major(&[2, 2, 3], |idx| {
890 (idx[0] * 6 + idx[1] * 3 + idx[2] + 1) as f64
891 });
892 let b = StridedArray::<f64>::from_fn_row_major(&[2, 3, 2], |idx| {
893 (idx[0] * 6 + idx[1] * 2 + idx[2] + 1) as f64
894 });
895 let mut c = StridedArray::<f64>::row_major(&[2, 2, 2]);
896
897 einsum2_into(
898 c.view_mut(),
899 &a.view(),
900 &b.view(),
901 &['b', 'i', 'k'],
902 &['b', 'i', 'j'],
903 &['b', 'j', 'k'],
904 1.0,
905 0.0,
906 )
907 .unwrap();
908
909 assert_eq!(c.get(&[0, 0, 0]), 22.0);
912 }
913
914 #[test]
915 fn test_batched_matmul_col_major_output() {
916 let a_data = vec![1.0, 0.0, 0.0, 1.0, 2.0, 0.0, 0.0, 2.0];
918 let b_data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
919 let a = StridedArray::<f64>::from_parts(a_data, &[2, 2, 2], &[4, 2, 1], 0).unwrap();
920 let b = StridedArray::<f64>::from_parts(b_data, &[2, 2, 2], &[4, 2, 1], 0).unwrap();
921 let mut c = StridedArray::<f64>::col_major(&[2, 2, 2]);
922
923 einsum2_into(
924 c.view_mut(),
925 &a.view(),
926 &b.view(),
927 &['b', 'i', 'k'],
928 &['b', 'i', 'j'],
929 &['b', 'j', 'k'],
930 1.0,
931 0.0,
932 )
933 .unwrap();
934
935 assert_eq!(c.get(&[0, 0, 0]), 1.0);
937 assert_eq!(c.get(&[0, 0, 1]), 2.0);
938 assert_eq!(c.get(&[0, 1, 0]), 3.0);
939 assert_eq!(c.get(&[0, 1, 1]), 4.0);
940 assert_eq!(c.get(&[1, 0, 0]), 10.0);
942 assert_eq!(c.get(&[1, 1, 1]), 16.0);
943 }
944
945 #[test]
946 fn test_outer_product() {
947 let a = StridedArray::<f64>::from_fn_row_major(&[3], |idx| (idx[0] + 1) as f64);
949 let b = StridedArray::<f64>::from_fn_row_major(&[4], |idx| (idx[0] + 1) as f64);
950 let mut c = StridedArray::<f64>::row_major(&[3, 4]);
951
952 einsum2_into(
953 c.view_mut(),
954 &a.view(),
955 &b.view(),
956 &['i', 'j'],
957 &['i'],
958 &['j'],
959 1.0,
960 0.0,
961 )
962 .unwrap();
963
964 assert_eq!(c.get(&[0, 0]), 1.0);
965 assert_eq!(c.get(&[2, 3]), 12.0);
966 }
967
968 #[test]
969 fn test_dot_product() {
970 let a = StridedArray::<f64>::from_fn_row_major(&[3], |idx| (idx[0] + 1) as f64);
972 let b = StridedArray::<f64>::from_fn_row_major(&[3], |idx| (idx[0] + 1) as f64);
973 let mut c = StridedArray::<f64>::row_major(&[]);
974
975 einsum2_into(
976 c.view_mut(),
977 &a.view(),
978 &b.view(),
979 &[] as &[char],
980 &['i'],
981 &['i'],
982 1.0,
983 0.0,
984 )
985 .unwrap();
986
987 assert_eq!(c.get(&[]), 14.0);
989 }
990
991 #[test]
992 fn test_alpha_beta() {
993 let a = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
995 [[1.0, 0.0], [0.0, 1.0]][idx[0]][idx[1]] });
997 let b = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
998 [[1.0, 2.0], [3.0, 4.0]][idx[0]][idx[1]]
999 });
1000 let mut c = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
1001 [[10.0, 20.0], [30.0, 40.0]][idx[0]][idx[1]]
1002 });
1003
1004 einsum2_into(
1005 c.view_mut(),
1006 &a.view(),
1007 &b.view(),
1008 &['i', 'k'],
1009 &['i', 'j'],
1010 &['j', 'k'],
1011 2.0,
1012 3.0,
1013 )
1014 .unwrap();
1015
1016 assert_eq!(c.get(&[0, 0]), 32.0); assert_eq!(c.get(&[1, 1]), 128.0); }
1020
1021 #[test]
1022 fn test_transposed_output() {
1023 let a = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
1025 [[1.0, 2.0], [3.0, 4.0]][idx[0]][idx[1]]
1026 });
1027 let b = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
1028 [[5.0, 6.0], [7.0, 8.0]][idx[0]][idx[1]]
1029 });
1030 let mut c = StridedArray::<f64>::row_major(&[2, 2]);
1031
1032 einsum2_into(
1033 c.view_mut(),
1034 &a.view(),
1035 &b.view(),
1036 &['k', 'i'], &['i', 'j'],
1038 &['j', 'k'],
1039 1.0,
1040 0.0,
1041 )
1042 .unwrap();
1043
1044 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); }
1051
1052 #[test]
1053 fn test_left_trace() {
1054 let a =
1057 StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
1058 let b =
1061 StridedArray::<f64>::from_fn_row_major(&[3, 2], |idx| (idx[0] * 2 + idx[1] + 1) as f64);
1062 let mut c = StridedArray::<f64>::row_major(&[2]);
1064
1065 einsum2_into(
1066 c.view_mut(),
1067 &a.view(),
1068 &b.view(),
1069 &['k'],
1070 &['i', 'j'],
1071 &['j', 'k'],
1072 1.0,
1073 0.0,
1074 )
1075 .unwrap();
1076
1077 assert_eq!(c.get(&[0]), 71.0);
1081 assert_eq!(c.get(&[1]), 92.0);
1082 }
1083
1084 #[test]
1085 fn test_u32_labels() {
1086 let a = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
1088 [[1.0, 2.0], [3.0, 4.0]][idx[0]][idx[1]]
1089 });
1090 let b = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
1091 [[5.0, 6.0], [7.0, 8.0]][idx[0]][idx[1]]
1092 });
1093 let mut c = StridedArray::<f64>::row_major(&[2, 2]);
1094
1095 einsum2_into(
1096 c.view_mut(),
1097 &a.view(),
1098 &b.view(),
1099 &[0u32, 2],
1100 &[0u32, 1],
1101 &[1u32, 2],
1102 1.0,
1103 0.0,
1104 )
1105 .unwrap();
1106
1107 assert_eq!(c.get(&[0, 0]), 19.0);
1108 assert_eq!(c.get(&[1, 1]), 50.0);
1109 }
1110
1111 #[test]
1112 fn test_complex_matmul() {
1113 use num_complex::Complex64;
1114 let i = Complex64::i();
1115
1116 let a_vals = [
1118 [1.0 + i, Complex64::new(2.0, 0.0)],
1119 [Complex64::new(3.0, 0.0), 4.0 - i],
1120 ];
1121 let a = StridedArray::<Complex64>::from_fn_row_major(&[2, 2], |idx| a_vals[idx[0]][idx[1]]);
1122
1123 let b_vals = [
1125 [Complex64::new(1.0, 0.0), i],
1126 [Complex64::new(0.0, 0.0), Complex64::new(1.0, 0.0)],
1127 ];
1128 let b = StridedArray::<Complex64>::from_fn_row_major(&[2, 2], |idx| b_vals[idx[0]][idx[1]]);
1129
1130 let mut c = StridedArray::<Complex64>::row_major(&[2, 2]);
1131
1132 einsum2_into(
1133 c.view_mut(),
1134 &a.view(),
1135 &b.view(),
1136 &['i', 'k'],
1137 &['i', 'j'],
1138 &['j', 'k'],
1139 Complex64::new(1.0, 0.0),
1140 Complex64::new(0.0, 0.0),
1141 )
1142 .unwrap();
1143
1144 assert_eq!(c.get(&[0, 0]), 1.0 + i);
1150 assert_eq!(c.get(&[0, 1]), 1.0 + i);
1151 assert_eq!(c.get(&[1, 0]), Complex64::new(3.0, 0.0));
1152 assert_eq!(c.get(&[1, 1]), 4.0 + 2.0 * i);
1153 }
1154
1155 #[test]
1156 fn test_complex_matmul_with_conj() {
1157 use num_complex::Complex64;
1158 let i = Complex64::i();
1159
1160 let a_vals = [[1.0 + i, 2.0 * i], [Complex64::new(3.0, 0.0), 4.0 - i]];
1162 let a = StridedArray::<Complex64>::from_fn_row_major(&[2, 2], |idx| a_vals[idx[0]][idx[1]]);
1163
1164 let b = StridedArray::<Complex64>::from_fn_row_major(&[2, 2], |idx| {
1166 if idx[0] == idx[1] {
1167 Complex64::new(1.0, 0.0)
1168 } else {
1169 Complex64::new(0.0, 0.0)
1170 }
1171 });
1172
1173 let mut c = StridedArray::<Complex64>::row_major(&[2, 2]);
1174
1175 let a_conj = a.view().conj();
1177 einsum2_into(
1178 c.view_mut(),
1179 &a_conj,
1180 &b.view(),
1181 &['i', 'k'],
1182 &['i', 'j'],
1183 &['j', 'k'],
1184 Complex64::new(1.0, 0.0),
1185 Complex64::new(0.0, 0.0),
1186 )
1187 .unwrap();
1188
1189 assert_eq!(c.get(&[0, 0]), 1.0 - i);
1191 assert_eq!(c.get(&[0, 1]), -2.0 * i);
1192 assert_eq!(c.get(&[1, 0]), Complex64::new(3.0, 0.0));
1193 assert_eq!(c.get(&[1, 1]), 4.0 + i);
1194 }
1195
1196 #[test]
1197 fn test_complex_matmul_with_conj_both() {
1198 use num_complex::Complex64;
1199 let i = Complex64::i();
1200
1201 let a_vals = [
1203 [1.0 + i, Complex64::new(0.0, 0.0)],
1204 [Complex64::new(0.0, 0.0), 2.0 - i],
1205 ];
1206 let a = StridedArray::<Complex64>::from_fn_row_major(&[2, 2], |idx| a_vals[idx[0]][idx[1]]);
1207
1208 let b_vals = [
1210 [Complex64::new(1.0, 0.0), i],
1211 [Complex64::new(0.0, 0.0), 1.0 + i],
1212 ];
1213 let b = StridedArray::<Complex64>::from_fn_row_major(&[2, 2], |idx| b_vals[idx[0]][idx[1]]);
1214
1215 let mut c = StridedArray::<Complex64>::row_major(&[2, 2]);
1216
1217 let a_conj = a.view().conj();
1219 let b_conj = b.view().conj();
1220 einsum2_into(
1221 c.view_mut(),
1222 &a_conj,
1223 &b_conj,
1224 &['i', 'k'],
1225 &['i', 'j'],
1226 &['j', 'k'],
1227 Complex64::new(1.0, 0.0),
1228 Complex64::new(0.0, 0.0),
1229 )
1230 .unwrap();
1231
1232 assert_eq!(c.get(&[0, 0]), 1.0 - i);
1240 assert_eq!(c.get(&[0, 1]), -(1.0 + i));
1241 assert_eq!(c.get(&[1, 0]), Complex64::new(0.0, 0.0));
1242 assert_eq!(c.get(&[1, 1]), 3.0 - i);
1243 }
1244
1245 #[test]
1246 fn test_elementwise_hadamard() {
1247 let a = StridedArray::<f64>::from_fn_row_major(&[3, 4, 5], |idx| {
1249 (idx[0] * 20 + idx[1] * 5 + idx[2] + 1) as f64
1250 });
1251 let b = StridedArray::<f64>::from_fn_row_major(&[3, 4, 5], |idx| {
1252 (idx[0] * 20 + idx[1] * 5 + idx[2] + 1) as f64 * 0.1
1253 });
1254 let mut c = StridedArray::<f64>::row_major(&[3, 4, 5]);
1255
1256 einsum2_into(
1257 c.view_mut(),
1258 &a.view(),
1259 &b.view(),
1260 &['i', 'j', 'k'],
1261 &['i', 'j', 'k'],
1262 &['i', 'j', 'k'],
1263 1.0,
1264 0.0,
1265 )
1266 .unwrap();
1267
1268 assert!((c.get(&[0, 0, 0]) - 0.1).abs() < 1e-12);
1270 assert!((c.get(&[2, 3, 4]) - 360.0).abs() < 1e-10);
1272 }
1273
1274 #[test]
1275 fn test_elementwise_hadamard_with_alpha() {
1276 let a =
1277 StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
1278 let b =
1279 StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
1280 let mut c = StridedArray::<f64>::row_major(&[2, 3]);
1281
1282 einsum2_into(
1283 c.view_mut(),
1284 &a.view(),
1285 &b.view(),
1286 &['i', 'j'],
1287 &['i', 'j'],
1288 &['i', 'j'],
1289 2.0,
1290 0.0,
1291 )
1292 .unwrap();
1293
1294 assert_eq!(c.get(&[0, 0]), 2.0);
1296 assert_eq!(c.get(&[1, 2]), 72.0);
1298 }
1299
1300 #[cfg(any(feature = "faer", feature = "blas", feature = "blas-inject"))]
1301 #[test]
1302 fn test_einsum2_owned_matmul() {
1303 let a = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
1304 [[1.0, 2.0], [3.0, 4.0]][idx[0]][idx[1]]
1305 });
1306 let b = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
1307 [[5.0, 6.0], [7.0, 8.0]][idx[0]][idx[1]]
1308 });
1309 let mut c = StridedArray::<f64>::row_major(&[2, 2]);
1310
1311 einsum2_into_owned(
1312 c.view_mut(),
1313 a,
1314 b,
1315 &['i', 'k'],
1316 &['i', 'j'],
1317 &['j', 'k'],
1318 1.0,
1319 0.0,
1320 false,
1321 false,
1322 )
1323 .unwrap();
1324
1325 assert_eq!(c.get(&[0, 0]), 19.0);
1326 assert_eq!(c.get(&[0, 1]), 22.0);
1327 assert_eq!(c.get(&[1, 0]), 43.0);
1328 assert_eq!(c.get(&[1, 1]), 50.0);
1329 }
1330
1331 #[cfg(any(feature = "faer", feature = "blas", feature = "blas-inject"))]
1332 #[test]
1333 fn test_einsum2_owned_batched() {
1334 let a = StridedArray::<f64>::from_fn_row_major(&[2, 2, 3], |idx| {
1335 (idx[0] * 6 + idx[1] * 3 + idx[2] + 1) as f64
1336 });
1337 let b = StridedArray::<f64>::from_fn_row_major(&[2, 3, 2], |idx| {
1338 (idx[0] * 6 + idx[1] * 2 + idx[2] + 1) as f64
1339 });
1340 let mut c = StridedArray::<f64>::row_major(&[2, 2, 2]);
1341
1342 einsum2_into_owned(
1343 c.view_mut(),
1344 a,
1345 b,
1346 &['b', 'i', 'k'],
1347 &['b', 'i', 'j'],
1348 &['b', 'j', 'k'],
1349 1.0,
1350 0.0,
1351 false,
1352 false,
1353 )
1354 .unwrap();
1355
1356 assert_eq!(c.get(&[0, 0, 0]), 22.0);
1359 }
1360
1361 #[cfg(any(feature = "faer", feature = "blas", feature = "blas-inject"))]
1362 #[test]
1363 fn test_einsum2_owned_alpha_beta() {
1364 let a = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
1365 [[1.0, 0.0], [0.0, 1.0]][idx[0]][idx[1]]
1366 });
1367 let b = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
1368 [[1.0, 2.0], [3.0, 4.0]][idx[0]][idx[1]]
1369 });
1370 let mut c = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
1371 [[10.0, 20.0], [30.0, 40.0]][idx[0]][idx[1]]
1372 });
1373
1374 einsum2_into_owned(
1375 c.view_mut(),
1376 a,
1377 b,
1378 &['i', 'k'],
1379 &['i', 'j'],
1380 &['j', 'k'],
1381 2.0,
1382 3.0,
1383 false,
1384 false,
1385 )
1386 .unwrap();
1387
1388 assert_eq!(c.get(&[0, 0]), 32.0); assert_eq!(c.get(&[1, 1]), 128.0); }
1392
1393 #[cfg(any(feature = "faer", feature = "blas", feature = "blas-inject"))]
1394 #[test]
1395 fn test_einsum2_owned_elementwise() {
1396 let a =
1398 StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
1399 let b =
1400 StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
1401 let mut c = StridedArray::<f64>::row_major(&[2, 3]);
1402
1403 einsum2_into_owned(
1404 c.view_mut(),
1405 a,
1406 b,
1407 &['i', 'j'],
1408 &['i', 'j'],
1409 &['i', 'j'],
1410 2.0,
1411 0.0,
1412 false,
1413 false,
1414 )
1415 .unwrap();
1416
1417 assert_eq!(c.get(&[0, 0]), 2.0); assert_eq!(c.get(&[1, 2]), 72.0); }
1420
1421 #[cfg(any(feature = "faer", feature = "blas", feature = "blas-inject"))]
1422 #[test]
1423 fn test_einsum2_owned_left_trace() {
1424 let a =
1427 StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
1428 let b =
1431 StridedArray::<f64>::from_fn_row_major(&[3, 2], |idx| (idx[0] * 2 + idx[1] + 1) as f64);
1432 let mut c = StridedArray::<f64>::row_major(&[2]);
1434
1435 einsum2_into_owned(
1436 c.view_mut(),
1437 a,
1438 b,
1439 &['k'],
1440 &['i', 'j'],
1441 &['j', 'k'],
1442 1.0,
1443 0.0,
1444 false,
1445 false,
1446 )
1447 .unwrap();
1448
1449 assert_eq!(c.get(&[0]), 71.0);
1452 assert_eq!(c.get(&[1]), 92.0);
1453 }
1454
1455 #[test]
1456 fn test_einsum2_naive_matmul() {
1457 let a = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
1459 [[1.0, 2.0], [3.0, 4.0]][idx[0]][idx[1]]
1460 });
1461 let b = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
1462 [[5.0, 6.0], [7.0, 8.0]][idx[0]][idx[1]]
1463 });
1464 let mut c = StridedArray::<f64>::row_major(&[2, 2]);
1465
1466 einsum2_naive_into(
1467 c.view_mut(),
1468 &a.view(),
1469 &b.view(),
1470 &['i', 'k'],
1471 &['i', 'j'],
1472 &['j', 'k'],
1473 1.0,
1474 0.0,
1475 |x| x,
1476 |x| x,
1477 )
1478 .unwrap();
1479
1480 assert_eq!(c.get(&[0, 0]), 19.0);
1481 assert_eq!(c.get(&[0, 1]), 22.0);
1482 assert_eq!(c.get(&[1, 0]), 43.0);
1483 assert_eq!(c.get(&[1, 1]), 50.0);
1484 }
1485
1486 #[test]
1487 fn test_einsum2_naive_elementwise() {
1488 let a =
1490 StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
1491 let b =
1492 StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
1493 let mut c = StridedArray::<f64>::row_major(&[2, 3]);
1494
1495 einsum2_naive_into(
1496 c.view_mut(),
1497 &a.view(),
1498 &b.view(),
1499 &['i', 'j'],
1500 &['i', 'j'],
1501 &['i', 'j'],
1502 2.0,
1503 0.0,
1504 |x| x,
1505 |x| x,
1506 )
1507 .unwrap();
1508
1509 assert_eq!(c.get(&[0, 0]), 2.0); assert_eq!(c.get(&[1, 2]), 72.0); }
1512
1513 #[test]
1514 fn test_einsum2_naive_custom_type() {
1515 use num_traits::{One, Zero};
1518
1519 #[derive(Debug, Clone, Copy, PartialEq)]
1520 struct MyVal(f64);
1521
1522 impl Default for MyVal {
1523 fn default() -> Self {
1524 MyVal(0.0)
1525 }
1526 }
1527
1528 impl std::ops::Add for MyVal {
1529 type Output = Self;
1530 fn add(self, rhs: Self) -> Self {
1531 MyVal(self.0 + rhs.0)
1532 }
1533 }
1534
1535 impl std::ops::Mul for MyVal {
1536 type Output = Self;
1537 fn mul(self, rhs: Self) -> Self {
1538 MyVal(self.0 * rhs.0)
1539 }
1540 }
1541
1542 impl Zero for MyVal {
1543 fn zero() -> Self {
1544 MyVal(0.0)
1545 }
1546 fn is_zero(&self) -> bool {
1547 self.0 == 0.0
1548 }
1549 }
1550
1551 impl One for MyVal {
1552 fn one() -> Self {
1553 MyVal(1.0)
1554 }
1555 }
1556
1557 let a = StridedArray::from_parts(
1559 vec![MyVal(1.0), MyVal(2.0), MyVal(3.0), MyVal(4.0)],
1560 &[2, 2],
1561 &[2, 1],
1562 0,
1563 )
1564 .unwrap();
1565 let b = StridedArray::from_parts(
1566 vec![MyVal(5.0), MyVal(6.0), MyVal(7.0), MyVal(8.0)],
1567 &[2, 2],
1568 &[2, 1],
1569 0,
1570 )
1571 .unwrap();
1572 let mut c = StridedArray::<MyVal>::col_major(&[2, 2]);
1573
1574 einsum2_naive_into(
1575 c.view_mut(),
1576 &a.view(),
1577 &b.view(),
1578 &['i', 'k'],
1579 &['i', 'j'],
1580 &['j', 'k'],
1581 MyVal(1.0),
1582 MyVal(0.0),
1583 |x| x,
1584 |x| x,
1585 )
1586 .unwrap();
1587
1588 assert_eq!(c.get(&[0, 0]), MyVal(19.0));
1591 assert_eq!(c.get(&[0, 1]), MyVal(22.0));
1592 assert_eq!(c.get(&[1, 0]), MyVal(43.0));
1593 assert_eq!(c.get(&[1, 1]), MyVal(50.0));
1594 }
1595
1596 #[test]
1597 fn test_einsum2_naive_left_trace() {
1598 let a =
1600 StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
1601 let b =
1602 StridedArray::<f64>::from_fn_row_major(&[3, 2], |idx| (idx[0] * 2 + idx[1] + 1) as f64);
1603 let mut c = StridedArray::<f64>::row_major(&[2]);
1604
1605 einsum2_naive_into(
1606 c.view_mut(),
1607 &a.view(),
1608 &b.view(),
1609 &['k'],
1610 &['i', 'j'],
1611 &['j', 'k'],
1612 1.0,
1613 0.0,
1614 |x| x,
1615 |x| x,
1616 )
1617 .unwrap();
1618
1619 assert_eq!(c.get(&[0]), 71.0);
1620 assert_eq!(c.get(&[1]), 92.0);
1621 }
1622
1623 struct TestNaiveBackend;
1625
1626 impl Backend<f64> for TestNaiveBackend {
1627 const MATERIALIZES_CONJ: bool = false;
1628 const REQUIRES_UNIT_STRIDE: bool = false;
1629
1630 fn bgemm_contiguous_into(
1631 c: &mut contiguous::ContiguousOperandMut<f64>,
1632 a: &contiguous::ContiguousOperand<f64>,
1633 b: &contiguous::ContiguousOperand<f64>,
1634 batch_dims: &[usize],
1635 m: usize,
1636 n: usize,
1637 k: usize,
1638 alpha: f64,
1639 beta: f64,
1640 ) -> strided_view::Result<()> {
1641 let a_ptr = a.ptr();
1643 let b_ptr = b.ptr();
1644 let c_ptr = c.ptr();
1645 let a_rs = a.row_stride();
1646 let a_cs = a.col_stride();
1647 let b_rs = b.row_stride();
1648 let b_cs = b.col_stride();
1649 let c_rs = c.row_stride();
1650 let c_cs = c.col_stride();
1651
1652 let mut batch_idx = crate::util::MultiIndex::new(batch_dims);
1653 while batch_idx.next().is_some() {
1654 let a_base = batch_idx.offset(a.batch_strides());
1655 let b_base = batch_idx.offset(b.batch_strides());
1656 let c_base = batch_idx.offset(c.batch_strides());
1657
1658 for i in 0..m {
1659 for j in 0..n {
1660 let mut acc = 0.0f64;
1661 for l in 0..k {
1662 let a_val = unsafe {
1663 *a_ptr.offset(a_base + i as isize * a_rs + l as isize * a_cs)
1664 };
1665 let b_val = unsafe {
1666 *b_ptr.offset(b_base + l as isize * b_rs + j as isize * b_cs)
1667 };
1668 acc += a_val * b_val;
1669 }
1670 unsafe {
1671 let c_elem =
1672 c_ptr.offset(c_base + i as isize * c_rs + j as isize * c_cs);
1673 if beta == 0.0 {
1674 *c_elem = alpha * acc;
1675 } else {
1676 *c_elem = alpha * acc + beta * (*c_elem);
1677 }
1678 }
1679 }
1680 }
1681 }
1682 Ok(())
1683 }
1684 }
1685
1686 #[test]
1687 fn test_einsum2_with_backend_matmul() {
1688 let a = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
1689 [[1.0, 2.0], [3.0, 4.0]][idx[0]][idx[1]]
1690 });
1691 let b = StridedArray::<f64>::from_fn_row_major(&[2, 2], |idx| {
1692 [[5.0, 6.0], [7.0, 8.0]][idx[0]][idx[1]]
1693 });
1694 let mut c = StridedArray::<f64>::row_major(&[2, 2]);
1695
1696 einsum2_with_backend_into::<_, TestNaiveBackend, _>(
1697 c.view_mut(),
1698 &a.view(),
1699 &b.view(),
1700 &['i', 'k'],
1701 &['i', 'j'],
1702 &['j', 'k'],
1703 1.0,
1704 0.0,
1705 )
1706 .unwrap();
1707
1708 assert_eq!(c.get(&[0, 0]), 19.0);
1709 assert_eq!(c.get(&[0, 1]), 22.0);
1710 assert_eq!(c.get(&[1, 0]), 43.0);
1711 assert_eq!(c.get(&[1, 1]), 50.0);
1712 }
1713
1714 #[test]
1715 fn test_einsum2_with_backend_elementwise() {
1716 let a =
1717 StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
1718 let b =
1719 StridedArray::<f64>::from_fn_row_major(&[2, 3], |idx| (idx[0] * 3 + idx[1] + 1) as f64);
1720 let mut c = StridedArray::<f64>::row_major(&[2, 3]);
1721
1722 einsum2_with_backend_into::<_, TestNaiveBackend, _>(
1723 c.view_mut(),
1724 &a.view(),
1725 &b.view(),
1726 &['i', 'j'],
1727 &['i', 'j'],
1728 &['i', 'j'],
1729 2.0,
1730 0.0,
1731 )
1732 .unwrap();
1733
1734 assert_eq!(c.get(&[0, 0]), 2.0); assert_eq!(c.get(&[1, 2]), 72.0); }
1737
1738 #[test]
1739 fn test_einsum2_with_backend_custom_type() {
1740 use num_traits::{One, Zero};
1741
1742 #[derive(Debug, Clone, Copy, PartialEq)]
1743 struct Tropical(f64);
1744
1745 impl Default for Tropical {
1746 fn default() -> Self {
1747 Tropical(0.0)
1748 }
1749 }
1750
1751 impl std::ops::Add for Tropical {
1752 type Output = Self;
1753 fn add(self, rhs: Self) -> Self {
1754 Tropical(self.0 + rhs.0)
1755 }
1756 }
1757
1758 impl std::ops::Mul for Tropical {
1759 type Output = Self;
1760 fn mul(self, rhs: Self) -> Self {
1761 Tropical(self.0 * rhs.0)
1762 }
1763 }
1764
1765 impl Zero for Tropical {
1766 fn zero() -> Self {
1767 Tropical(0.0)
1768 }
1769 fn is_zero(&self) -> bool {
1770 self.0 == 0.0
1771 }
1772 }
1773
1774 impl One for Tropical {
1775 fn one() -> Self {
1776 Tropical(1.0)
1777 }
1778 }
1779
1780 struct TropicalBackend;
1781
1782 impl Backend<Tropical> for TropicalBackend {
1783 const MATERIALIZES_CONJ: bool = false;
1784 const REQUIRES_UNIT_STRIDE: bool = false;
1785
1786 fn bgemm_contiguous_into(
1787 c: &mut contiguous::ContiguousOperandMut<Tropical>,
1788 a: &contiguous::ContiguousOperand<Tropical>,
1789 b: &contiguous::ContiguousOperand<Tropical>,
1790 batch_dims: &[usize],
1791 m: usize,
1792 n: usize,
1793 k: usize,
1794 alpha: Tropical,
1795 beta: Tropical,
1796 ) -> strided_view::Result<()> {
1797 let a_ptr = a.ptr();
1799 let b_ptr = b.ptr();
1800 let c_ptr = c.ptr();
1801 let a_rs = a.row_stride();
1802 let a_cs = a.col_stride();
1803 let b_rs = b.row_stride();
1804 let b_cs = b.col_stride();
1805 let c_rs = c.row_stride();
1806 let c_cs = c.col_stride();
1807
1808 let mut batch_idx = crate::util::MultiIndex::new(batch_dims);
1809 while batch_idx.next().is_some() {
1810 let a_base = batch_idx.offset(a.batch_strides());
1811 let b_base = batch_idx.offset(b.batch_strides());
1812 let c_base = batch_idx.offset(c.batch_strides());
1813
1814 for i in 0..m {
1815 for j in 0..n {
1816 let mut acc = Tropical::zero();
1817 for l in 0..k {
1818 let a_val = unsafe {
1819 *a_ptr.offset(a_base + i as isize * a_rs + l as isize * a_cs)
1820 };
1821 let b_val = unsafe {
1822 *b_ptr.offset(b_base + l as isize * b_rs + j as isize * b_cs)
1823 };
1824 acc = acc + a_val * b_val;
1825 }
1826 unsafe {
1827 let c_elem =
1828 c_ptr.offset(c_base + i as isize * c_rs + j as isize * c_cs);
1829 if beta == Tropical::zero() {
1830 *c_elem = alpha * acc;
1831 } else {
1832 *c_elem = alpha * acc + beta * (*c_elem);
1833 }
1834 }
1835 }
1836 }
1837 }
1838 Ok(())
1839 }
1840 }
1841
1842 let a = StridedArray::from_parts(
1843 vec![Tropical(1.0), Tropical(2.0), Tropical(3.0), Tropical(4.0)],
1844 &[2, 2],
1845 &[2, 1],
1846 0,
1847 )
1848 .unwrap();
1849 let b = StridedArray::from_parts(
1850 vec![Tropical(5.0), Tropical(6.0), Tropical(7.0), Tropical(8.0)],
1851 &[2, 2],
1852 &[2, 1],
1853 0,
1854 )
1855 .unwrap();
1856 let mut c = StridedArray::<Tropical>::col_major(&[2, 2]);
1857
1858 einsum2_with_backend_into::<_, TropicalBackend, _>(
1859 c.view_mut(),
1860 &a.view(),
1861 &b.view(),
1862 &['i', 'k'],
1863 &['i', 'j'],
1864 &['j', 'k'],
1865 Tropical(1.0),
1866 Tropical(0.0),
1867 )
1868 .unwrap();
1869
1870 assert_eq!(c.get(&[0, 0]), Tropical(19.0));
1873 assert_eq!(c.get(&[0, 1]), Tropical(22.0));
1874 assert_eq!(c.get(&[1, 0]), Tropical(43.0));
1875 assert_eq!(c.get(&[1, 1]), Tropical(50.0));
1876 }
1877}