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