@@ -157,6 +157,22 @@ unsafe fn blas_1d_params<A>(ptr: *const A, len: usize, stride: isize) -> (*const
157157///
158158/// For two-dimensional arrays, the dot method computes the matrix
159159/// multiplication.
160+ ///
161+ /// For higher-dimensional arrays (3-D through 6-D, and dynamic-dimensional),
162+ /// `Dot<Ix2>` contracts the last axis of the left-hand side with the first
163+ /// axis of the right-hand side, following NumPy semantics. For example, if
164+ /// `self` has shape *I* × *J* × *K* and `rhs` has shape *K* × *N*, the
165+ /// result has shape *I* × *J* × *N*.
166+ ///
167+ /// ```
168+ /// use ndarray::{Array3, Array2};
169+ /// use ndarray::linalg::Dot;
170+ ///
171+ /// let a = Array3::<f64>::zeros((3, 4, 5));
172+ /// let b = Array2::<f64>::zeros((5, 6));
173+ /// let c = a.dot(&b);
174+ /// assert_eq!(c.shape(), &[3, 4, 6]);
175+ /// ```
160176pub trait Dot < Rhs : ?Sized >
161177{
162178 /// The result of the operation.
@@ -222,13 +238,58 @@ impl_dots!(Ix1, Ix2);
222238impl_dots ! ( Ix2 , Ix1 ) ;
223239impl_dots ! ( Ix2 , Ix2 ) ;
224240
225- fn nd_dot_non_contiguous < A , S1 , S2 , S3 > (
226- lhs : & ArrayBase < S1 , IxDyn > , rhs : & ArrayBase < S2 , Ix2 > , out : & mut ArrayBase < S3 , IxDyn > ,
241+ /// Compute the dot product of an N-dimensional LHS array with a 2-D RHS matrix.
242+ ///
243+ /// Contracts the last axis of `lhs` with the first axis of `rhs`.
244+ /// If `lhs` has shape *d₀ × d₁ × … × dₙ₋₁ × K* and `rhs` has shape
245+ /// *K × N*, the result has shape *d₀ × d₁ × … × dₙ₋₁ × N*.
246+ ///
247+ /// Two paths are used internally:
248+ /// - **C-contiguous LHS**: the leading axes are flattened into a single
249+ /// 2-D view (zero-copy) and delegated to the optimised 2-D `dot`.
250+ /// - **Non-contiguous LHS**: a result array is pre-allocated and filled
251+ /// in-place via recursive `general_mat_mul` calls (no intermediate copies).
252+ #[ track_caller]
253+ fn nd_dot < A : LinalgScalar > ( lhs : & ArrayRef < A , IxDyn > , rhs : & ArrayRef < A , Ix2 > ) -> Array < A , IxDyn >
254+ {
255+ let ndim = lhs. ndim ( ) ;
256+ let k = lhs. shape ( ) [ ndim - 1 ] ;
257+ let k2 = rhs. shape ( ) [ 0 ] ;
258+ let n = rhs. shape ( ) [ 1 ] ;
259+ if k != k2 {
260+ panic ! (
261+ "shapes {:?} and {:?} are not compatible for nd dot \
262+ (last axis of lhs must equal first axis of rhs)",
263+ lhs. shape( ) ,
264+ rhs. shape( )
265+ ) ;
266+ }
267+
268+ let mut out_shape = lhs. shape ( ) . to_vec ( ) ;
269+ * out_shape. last_mut ( ) . unwrap ( ) = n;
270+
271+ if lhs. is_standard_layout ( ) {
272+ // C-contiguous: to_shape returns a *view* (no copy of LHS data).
273+ let rows = lhs. len ( ) / k;
274+ // unwrap: rows * k == lhs.len() by construction, so reshape always succeeds.
275+ let lhs_2d = lhs. to_shape ( ( rows, k) ) . unwrap ( ) ;
276+ let result_2d = lhs_2d. dot ( rhs) ;
277+ // unwrap: result_2d is a fresh C-contiguous owned array of the correct size.
278+ result_2d. into_shape_with_order ( IxDyn ( & out_shape) ) . unwrap ( )
279+ } else {
280+ // Non-contiguous: pre-allocate and fill in-place to avoid whole-array copying.
281+ let mut out = Array :: zeros ( IxDyn ( & out_shape) ) ;
282+ nd_dot_non_contiguous ( & lhs. view ( ) , & rhs. view ( ) , & mut out. view_mut ( ) ) ;
283+ out
284+ }
285+ }
286+
287+ /// Recursive helper: writes the N-D × 2-D product directly into `out`
288+ /// using `general_mat_mul` at the 2-D base case.
289+ fn nd_dot_non_contiguous < A > (
290+ lhs : & ArrayRef < A , IxDyn > , rhs : & ArrayRef < A , Ix2 > , out : & mut ArrayRef < A , IxDyn > ,
227291) where
228292 A : LinalgScalar ,
229- S1 : Data < Elem = A > ,
230- S2 : Data < Elem = A > ,
231- S3 : DataMut < Elem = A > ,
232293{
233294 let ndim = lhs. ndim ( ) ;
234295 if ndim == 2 {
@@ -237,8 +298,8 @@ fn nd_dot_non_contiguous<A, S1, S2, S3>(
237298 let mut out_2d = out. view_mut ( ) . into_dimensionality :: < Ix2 > ( ) . unwrap ( ) ;
238299 general_mat_mul ( A :: one ( ) , & lhs_2d, rhs, A :: zero ( ) , & mut out_2d) ;
239300 } else {
240- Zip :: from ( lhs. axis_iter ( Axis ( 0 ) ) )
241- . and ( out. axis_iter_mut ( Axis ( 0 ) ) )
301+ Zip :: from ( lhs. view ( ) . axis_iter ( Axis ( 0 ) ) )
302+ . and ( out. view_mut ( ) . axis_iter_mut ( Axis ( 0 ) ) )
242303 . for_each ( |lhs_slice, mut out_slice| {
243304 nd_dot_non_contiguous ( & lhs_slice, rhs, & mut out_slice) ;
244305 } ) ;
@@ -255,46 +316,9 @@ macro_rules! impl_dot_nd_ix2 {
255316 #[ track_caller]
256317 fn dot( & self , rhs: & ArrayRef <A , Ix2 >) -> Array <A , $dim>
257318 {
258- let ndim = self . ndim( ) ;
259- let k = self . shape( ) [ ndim - 1 ] ;
260- let k2 = rhs. shape( ) [ 0 ] ;
261- let n = rhs. shape( ) [ 1 ] ;
262- if k != k2 {
263- panic!(
264- "shapes {:?} and {:?} are not compatible for nd dot \
265- (last axis of lhs must equal first axis of rhs)",
266- self . shape( ) ,
267- rhs. shape( )
268- ) ;
269- }
270-
271- if self . is_standard_layout( ) {
272- // C-contiguous: to_shape returns a *view* (no copy of LHS data).
273- let rows = self . len( ) / k;
274- // unwrap: rows * k == self.len() by construction, so reshape always succeeds.
275- let lhs_2d = self . to_shape( ( rows, k) ) . unwrap( ) ;
276- let result_2d = lhs_2d. dot( rhs) ;
277-
278- let mut out_dim = <$dim>:: zeros( ndim) ;
279- for i in 0 ..ndim - 1 {
280- out_dim[ i] = self . shape( ) [ i] ;
281- }
282- out_dim[ ndim - 1 ] = n;
283-
284- // unwrap: result_2d is a fresh C-contiguous owned array of the correct size.
285- result_2d. into_shape_with_order( out_dim) . unwrap( )
286- } else {
287- // Non-contiguous: iterate over the first axis and write results directly
288- // into the pre-allocated output array to avoid whole-array copying.
289- let mut out_dim = <$dim>:: zeros( ndim) ;
290- for i in 0 ..ndim - 1 {
291- out_dim[ i] = self . shape( ) [ i] ;
292- }
293- out_dim[ ndim - 1 ] = n;
294- let mut out = Array :: zeros( out_dim) ;
295- nd_dot_non_contiguous( & self . view( ) . into_dyn( ) , & rhs. view( ) , & mut out. view_mut( ) . into_dyn( ) ) ;
296- out
297- }
319+ let result_dyn = nd_dot( & self . view( ) . into_dyn( ) , rhs) ;
320+ // unwrap: nd_dot preserves the number of dimensions.
321+ result_dyn. into_dimensionality:: <$dim>( ) . unwrap( )
298322 }
299323 }
300324
@@ -315,40 +339,7 @@ where A: LinalgScalar
315339 #[ track_caller]
316340 fn dot ( & self , rhs : & ArrayRef < A , Ix2 > ) -> Array < A , IxDyn >
317341 {
318- let ndim = self . ndim ( ) ;
319- let k = self . shape ( ) [ ndim - 1 ] ;
320- let k2 = rhs. shape ( ) [ 0 ] ;
321- let n = rhs. shape ( ) [ 1 ] ;
322- if k != k2 {
323- panic ! (
324- "shapes {:?} and {:?} are not compatible for nd dot \
325- (last axis of lhs must equal first axis of rhs)",
326- self . shape( ) ,
327- rhs. shape( )
328- ) ;
329- }
330-
331- if self . is_standard_layout ( ) {
332- // C-contiguous: to_shape returns a *view* (no copy of LHS data).
333- let rows = self . len ( ) / k;
334- // unwrap: rows * k == self.len() by construction, so reshape always succeeds.
335- let lhs_2d = self . to_shape ( ( rows, k) ) . unwrap ( ) ;
336- let result_2d = lhs_2d. dot ( rhs) ;
337-
338- let mut out_shape = self . shape ( ) . to_vec ( ) ;
339- * out_shape. last_mut ( ) . unwrap ( ) = n;
340-
341- // unwrap: result_2d is a fresh C-contiguous owned array of the correct size.
342- result_2d. into_shape_with_order ( IxDyn ( & out_shape) ) . unwrap ( )
343- } else {
344- // Non-contiguous: iterate recursively over the first axis and write results
345- // directly into the pre-allocated output array to avoid whole-array copying.
346- let mut out_shape = self . shape ( ) . to_vec ( ) ;
347- * out_shape. last_mut ( ) . unwrap ( ) = n;
348- let mut out = Array :: zeros ( IxDyn ( & out_shape) ) ;
349- nd_dot_non_contiguous ( & self . view ( ) , & rhs. view ( ) , & mut out. view_mut ( ) ) ;
350- out
351- }
342+ nd_dot ( & self . view ( ) . into_dyn ( ) , rhs)
352343 }
353344}
354345
0 commit comments