1 /++
2 $(SCRIPT inhibitQuickIndex = 1;)
3 
4 This is a submodule of $(MREF mir, ndslice).
5 
6 Operators only change strides and lengths of a slice.
7 The range of a slice remains unmodified.
8 All operators return slice as the type of the argument, maybe except slice kind.
9 
10 $(BOOKTABLE $(H2 Transpose operators),
11 
12 $(TR $(TH Function Name) $(TH Description))
13 $(T2 transposed, Permutes dimensions. $(BR)
14     `iota(3, 4, 5, 6, 7).transposed!(4, 0, 1).shape` returns `[7, 3, 4, 5, 6]`.)
15 $(T2 swapped, Swaps dimensions $(BR)
16     `iota(3, 4, 5).swapped!(1, 2).shape` returns `[3, 5, 4]`.)
17 $(T2 everted, Reverses the order of dimensions $(BR)
18     `iota(3, 4, 5).everted.shape` returns `[5, 4, 3]`.)
19 )
20 See also $(SUBREF topology, evertPack).
21 
22 $(BOOKTABLE $(H2 Iteration operators),
23 
24 $(TR $(TH Function Name) $(TH Description))
25 $(T2 strided, Multiplies the stride of a selected dimension by a factor.$(BR)
26     `iota(13, 40).strided!(0, 1)(2, 5).shape` equals to `[7, 8]`.)
27 $(T2 reversed, Reverses the direction of iteration for selected dimensions. $(BR)
28     `slice.reversed!0` returns the slice with reversed direction of iteration for top level dimension.)
29 $(T2 allReversed, Reverses the direction of iteration for all dimensions. $(BR)
30     `iota(4, 5).allReversed` equals to `20.iota.retro.sliced(4, 5)`.)
31 )
32 
33 $(BOOKTABLE $(H2 Other operators),
34 $(TR $(TH Function Name) $(TH Description))
35 
36 $(T2 rotated, Rotates two selected dimensions by `k*90` degrees. $(BR)
37     `iota(2, 3).rotated` equals to `[[2, 5], [1, 4], [0, 3]]`.)
38 $(T2 dropToHypercube, Returns maximal multidimensional cube of a slice.)
39 $(T2 normalizeStructure, Reverses iteration order for dimensions with negative strides, they become not negative;
40 and sorts dimensions according to the strides, dimensions with larger strides are going first.)
41 )
42 
43 $(H2 Bifacial operators)
44 
45 Some operators are bifacial,
46 i.e. they have two versions: one with template parameters, and another one
47 with function parameters. Versions with template parameters are preferable
48 because they allow compile time checks and can be optimized better.
49 
50 $(BOOKTABLE ,
51 
52 $(TR $(TH Function Name) $(TH Variadic) $(TH Template) $(TH Function))
53 $(T4 swapped, No, `slice.swapped!(2, 3)`, `slice.swapped(2, 3)`)
54 $(T4 rotated, No, `slice.rotated!(2, 3)(-1)`, `slice.rotated(2, 3, -1)`)
55 $(T4 strided, Yes/No, `slice.strided!(1, 2)(20, 40)`, `slice.strided(1, 20).strided(2, 40)`)
56 $(T4 transposed, Yes, `slice.transposed!(1, 4, 3)`, `slice.transposed(1, 4, 3)`)
57 $(T4 reversed, Yes, `slice.reversed!(0, 2)`, `slice.reversed(0, 2)`)
58 )
59 
60 Bifacial interface of $(LREF drop), $(LREF dropBack)
61 $(LREF dropExactly), and $(LREF dropBackExactly)
62 is identical to that of $(LREF strided).
63 
64 Bifacial interface of $(LREF dropOne) and $(LREF dropBackOne)
65 is identical to that of $(LREF reversed).
66 
67 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
68 
69 Copyright: 2020 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments
70 
71 Authors: Ilya Yaroshenko
72 
73 Macros:
74 SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP)
75 T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
76 T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4))
77 +/
78 module mir.ndslice.dynamic;
79 
80 
81 import std.traits;
82 import std.meta;
83 
84 import mir.math.common: optmath;
85 import mir.internal.utility: Iota;
86 import mir.ndslice.internal;
87 import mir.ndslice.slice;
88 import mir.utility;
89 
90 @optmath:
91 
92 /++
93 Reverses iteration order for dimensions with negative strides, they become not negative;
94 and sorts dimensions according to the strides, dimensions with larger strides are going first.
95 
96 Params:
97     slice = a slice to normalize dimension
98 Returns:
99     `true` if the slice can be safely casted to $(SUBREF slice, Contiguous) kind using $(SUBREF topology, assumeContiguous) and false otherwise.
100 +/
101 bool normalizeStructure(Iterator, size_t N, SliceKind kind)(ref Slice!(Iterator, N, kind) slice)
102 {
103     static if (kind == Contiguous)
104     {
105         return true;
106     }
107     else
108     {
109         import mir.utility: min;
110         enum Y = min(slice.S, N);
111         foreach(i; Iota!Y)
112             if (slice._stride!i < 0)
113                 slice = slice.reversed!i;
114         static if (N == 1)
115             return slice._stride!0 == 1;
116         else
117         static if (N == 2 && kind == Canonical)
118         {
119             return slice._stride!0 == slice.length!1;
120         }
121         else
122         {
123             import mir.series: series, sort;
124             import mir.ndslice.topology: zip, iota;
125             auto l = slice._lengths[0 .. Y];
126             auto s = slice._strides[0 .. Y];
127             s.series(l).sort!"a > b";
128             return slice.shape.iota.strides == slice.strides;
129         }
130     }
131 }
132 
133 ///
134 version(mir_test) unittest
135 {
136     import mir.ndslice.topology: iota;
137 
138     auto g = iota(2, 3); //contiguous
139     auto c = g.reversed!0; //canonical
140     auto u = g.transposed.allReversed; //universal
141 
142     assert(g.normalizeStructure);
143     assert(c.normalizeStructure);
144     assert(u.normalizeStructure);
145 
146     assert(c == g);
147     assert(u == g);
148 
149     c.popFront!1;
150     u.popFront!1;
151 
152     assert(!c.normalizeStructure);
153     assert(!u.normalizeStructure);
154 }
155 
156 private enum _swappedCode = q{
157     with (slice)
158     {
159         auto tl = _lengths[dimensionA];
160         auto ts = _strides[dimensionA];
161         _lengths[dimensionA] = _lengths[dimensionB];
162         _strides[dimensionA] = _strides[dimensionB];
163         _lengths[dimensionB] = tl;
164         _strides[dimensionB] = ts;
165     }
166     return slice;
167 };
168 
169 /++
170 Swaps two dimensions.
171 
172 Params:
173     slice = input slice
174     dimensionA = first dimension
175     dimensionB = second dimension
176 Returns:
177     n-dimensional slice
178 See_also: $(LREF everted), $(LREF transposed)
179 +/
180 template swapped(size_t dimensionA, size_t dimensionB)
181 {
182     ///
183     @optmath auto swapped(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice)
184     {
185         static if (kind == Universal || kind == Canonical && dimensionA + 1 < N && dimensionB + 1 < N)
186         {
187             alias slice = _slice;
188         }
189         else static if (dimensionA + 1 < N && dimensionB + 1 < N)
190         {
191             import mir.ndslice.topology: canonical;
192             auto slice = _slice.canonical;
193         }
194         else
195         {
196             import mir.ndslice.topology: universal;
197             auto slice = _slice.universal;
198         }
199         {
200             enum i = 0;
201             alias dimension = dimensionA;
202             mixin DimensionCTError;
203         }
204         {
205             enum i = 1;
206             alias dimension = dimensionB;
207             mixin DimensionCTError;
208         }
209         mixin (_swappedCode);
210     }
211 }
212 
213 /// ditto
214 auto swapped(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice, size_t dimensionA, size_t dimensionB)
215 {
216     import mir.ndslice.topology: universal;
217     auto slice = _slice.universal;
218     {
219         alias dimension = dimensionA;
220         mixin (DimensionRTError);
221     }
222     {
223         alias dimension = dimensionB;
224         mixin (DimensionRTError);
225     }
226     mixin (_swappedCode);
227 }
228 
229 /// ditto
230 Slice!(Iterator, 2, Universal) swapped(Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) slice)
231 {
232     return slice.swapped!(0, 1);
233 }
234 
235 /// Template
236 @safe @nogc pure nothrow version(mir_test) unittest
237 {
238     import mir.ndslice.slice;
239     import mir.ndslice.topology: iota;
240 
241     assert(iota(3, 4, 5, 6)
242         .swapped!(2, 1)
243         .shape == cast(size_t[4])[3, 5, 4, 6]);
244 
245     assert(iota(3, 4, 5, 6)
246         .swapped!(3, 1)
247         .shape == cast(size_t[4])[3, 6, 5, 4]);
248 }
249 
250 /// Function
251 @safe @nogc pure nothrow version(mir_test) unittest
252 {
253     import mir.ndslice.slice;
254     import mir.ndslice.topology: iota;
255 
256     assert(iota(3, 4, 5, 6)
257         .swapped(1, 2)
258         .shape == cast(size_t[4])[3, 5, 4, 6]);
259 
260     assert(iota(3, 4, 5, 6)
261         .swapped(1, 3)
262         .shape == cast(size_t[4])[3, 6, 5, 4]);
263 }
264 
265 /// 2D
266 @safe @nogc pure nothrow version(mir_test) unittest
267 {
268     import mir.ndslice.slice;
269     import mir.ndslice.topology: iota;
270     assert(iota(3, 4)
271         .swapped
272         .shape == cast(size_t[2])[4, 3]);
273 }
274 
275 private enum _rotatedCode = q{
276     k &= 0b11;
277     if (k == 0)
278         return slice;
279     if (k == 2)
280         return slice.allReversed;
281     static if (__traits(compiles, { enum _enum = dimensionA + dimensionB; }))
282     {
283         slice = slice.swapped!(dimensionA, dimensionB);
284         if (k == 1)
285             return slice.reversed!dimensionA;
286         else
287             return slice.reversed!dimensionB;
288     }
289     else
290     {
291         slice = slice.swapped (dimensionA, dimensionB);
292         if (k == 1)
293             return slice.reversed(dimensionA);
294         else
295             return slice.reversed(dimensionB);
296     }
297 };
298 
299 /++
300 Rotates two selected dimensions by `k*90` degrees.
301 The order of dimensions is important.
302 If the slice has two dimensions, the default direction is counterclockwise.
303 
304 Params:
305     slice = input slice
306     dimensionA = first dimension
307     dimensionB = second dimension
308     k = rotation counter, can be negative
309 Returns:
310     n-dimensional slice
311 +/
312 template rotated(size_t dimensionA, size_t dimensionB)
313 {
314     ///
315     @optmath auto rotated(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice, sizediff_t k = 1)
316     {
317         static if (kind == Universal || kind == Canonical && dimensionA + 1 < N && dimensionB + 1 < N)
318         {
319             alias slice = _slice;
320         }
321         else static if (dimensionA + 1 < N && dimensionB + 1 < N)
322         {
323             import mir.ndslice.topology: canonical;
324             auto slice = _slice.canonical;
325         }
326         else
327         {
328             import mir.ndslice.topology: universal;
329             auto slice = _slice.universal;
330         }
331         {
332             enum i = 0;
333             alias dimension = dimensionA;
334             mixin DimensionCTError;
335         }
336         {
337             enum i = 1;
338             alias dimension = dimensionB;
339             mixin DimensionCTError;
340         }
341         mixin (_rotatedCode);
342     }
343 }
344 
345 /// ditto
346 auto rotated(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice, size_t dimensionA, size_t dimensionB, sizediff_t k = 1)
347 {
348     import mir.ndslice.topology: universal;
349     auto slice = _slice.universal;
350     {
351         alias dimension = dimensionA;
352         mixin (DimensionRTError);
353     }
354     {
355         alias dimension = dimensionB;
356         mixin (DimensionRTError);
357     }
358     mixin (_rotatedCode);
359 }
360 
361 /// ditto
362 Slice!(Iterator, 2, Universal) rotated(Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) slice, sizediff_t k = 1)
363 {
364     return .rotated!(0, 1)(slice, k);
365 }
366 
367 ///
368 @safe pure nothrow version(mir_test) unittest
369 {
370     import mir.ndslice.slice;
371     import mir.ndslice.topology: iota;
372     auto slice = iota(2, 3);
373 
374     auto a = [[0, 1, 2],
375               [3, 4, 5]];
376 
377     auto b = [[2, 5],
378               [1, 4],
379               [0, 3]];
380 
381     auto c = [[5, 4, 3],
382               [2, 1, 0]];
383 
384     auto d = [[3, 0],
385               [4, 1],
386               [5, 2]];
387 
388     assert(slice.rotated       ( 4) == a);
389     assert(slice.rotated!(0, 1)(-4) == a);
390     assert(slice.rotated (1, 0,  8) == a);
391 
392     assert(slice.rotated            == b);
393     assert(slice.rotated!(0, 1)(-3) == b);
394     assert(slice.rotated (1, 0,  3) == b);
395 
396     assert(slice.rotated       ( 6) == c);
397     assert(slice.rotated!(0, 1)( 2) == c);
398     assert(slice.rotated (0, 1, -2) == c);
399 
400     assert(slice.rotated       ( 7) == d);
401     assert(slice.rotated!(0, 1)( 3) == d);
402     assert(slice.rotated (1, 0,   ) == d);
403 }
404 
405 /++
406 Reverses the order of dimensions.
407 
408 Params:
409     _slice = input slice
410 Returns:
411     n-dimensional slice
412 See_also: $(LREF swapped), $(LREF transposed)
413 +/
414 auto everted(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice)
415 {
416     static if (kind == Universal)
417     {
418         alias slice = _slice;
419     }
420     else
421     {
422         import mir.ndslice.topology: universal;
423         auto slice = _slice.universal;
424     }
425     with(slice) foreach (i; Iota!(N / 2))
426     {
427         swap(_lengths[i], _lengths[N - i - 1]);
428         swap(_strides[i], _strides[N - i - 1]);
429     }
430     return slice;
431 }
432 
433 ///
434 @safe @nogc pure nothrow version(mir_test) unittest
435 {
436     import mir.ndslice.slice;
437     import mir.ndslice.topology: iota;
438     assert(iota(3, 4, 5)
439         .everted
440         .shape == cast(size_t[3])[5, 4, 3]);
441 }
442 
443 private enum _transposedCode = q{
444     size_t[typeof(slice).N] lengths_;
445     ptrdiff_t[max(typeof(slice).S, size_t(1))] strides_;
446     with(slice) foreach (i; Iota!N)
447     {
448         lengths_[i] = _lengths[perm[i]];
449         static if (i < typeof(slice).S)
450             strides_[i] = _strides[perm[i]];
451     }
452     with(slice) foreach (i; Iota!(N, slice.N))
453     {
454         lengths_[i] = _lengths[i];
455         static if (i < typeof(slice).S)
456             strides_[i] = _strides[i];
457     }
458     return typeof(slice)(lengths_, strides_[0 .. typeof(slice).S], slice._iterator);
459 };
460 
461 package size_t[N] completeTranspose(size_t N)(size_t[] dimensions)
462 {
463     assert(dimensions.length <= N);
464     size_t[N] ctr;
465     uint[N] mask;
466     foreach (i, ref dimension; dimensions)
467     {
468         mask[dimension] = true;
469         ctr[i] = dimension;
470     }
471     size_t j = dimensions.length;
472     foreach (i, e; mask)
473         if (e == false)
474             ctr[j++] = i;
475     return ctr;
476 }
477 
478 /++
479 N-dimensional transpose operator.
480 Brings selected dimensions to the first position.
481 Params:
482     slice = input slice
483     Dimensions = indices of dimensions to be brought to the first position
484     dimensions = indices of dimensions to be brought to the first position
485 Returns:
486     n-dimensional slice
487 See_also: $(LREF swapped), $(LREF everted)
488 +/
489 template transposed(Dimensions...)
490     if (Dimensions.length)
491 {
492     static if (allSatisfy!(isSize_t, Dimensions))
493     ///
494     @optmath auto transposed(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice)
495     {
496         import mir.algorithm.iteration: any;
497         enum s = N;
498         static if ([Dimensions] == [Iota!(Dimensions.length)])
499         {
500             return _slice;
501         }
502         else
503         {
504             import core.lifetime: move;
505             enum hasRowStride = [Dimensions].any!(a => a + 1 == s);
506             static if (kind == Universal || kind == Canonical && !hasRowStride)
507             {
508                 alias slice = _slice;
509             }
510             else
511             static if (hasRowStride)
512             {
513                 import mir.ndslice.topology: universal;
514                 auto slice = _slice.move.universal;
515             }
516             else
517             {
518                 import mir.ndslice.topology: canonical;
519                 auto slice = _slice.move.canonical;
520             }
521             mixin DimensionsCountCTError;
522             foreach (i, dimension; Dimensions)
523                 mixin DimensionCTError;
524             static assert(isValidPartialPermutation!(N)([Dimensions]),
525                 "Failed to complete permutation of dimensions " ~ Dimensions.stringof
526                 ~ tailErrorMessage!());
527             enum perm = completeTranspose!(N)([Dimensions]);
528             static assert(perm.isPermutation, __PRETTY_FUNCTION__ ~ ": internal error.");
529             mixin (_transposedCode);
530         }
531     }
532     else
533         alias transposed = .transposed!(staticMap!(toSize_t, Dimensions));
534 }
535 
536 ///ditto
537 auto transposed(Iterator, size_t N, SliceKind kind, size_t M)(Slice!(Iterator, N, kind) _slice, size_t[M] dimensions...)
538 {
539     import core.lifetime: move;
540     import mir.ndslice.topology: universal;
541     auto slice = _slice.move.universal;
542 
543     mixin (DimensionsCountRTError);
544     foreach (dimension; dimensions)
545         mixin (DimensionRTError);
546     assert(dimensions.isValidPartialPermutation!(N),
547         "Failed to complete permutation of dimensions."
548         ~ tailErrorMessage!());
549     immutable perm = completeTranspose!(N)(dimensions);
550     assert(perm.isPermutation, __PRETTY_FUNCTION__ ~ ": internal error.");
551     mixin (_transposedCode);
552 }
553 
554 ///ditto
555 Slice!(Iterator, 2, Universal) transposed(Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) slice)
556 {
557     return .transposed!(1, 0)(slice);
558 }
559 
560 /// Template
561 @safe @nogc pure nothrow version(mir_test) unittest
562 {
563     import mir.ndslice.slice;
564     import mir.ndslice.topology: iota;
565 
566     assert(iota(3, 4, 5, 6, 7)
567         .transposed!(3, 1, 0)
568         .shape == cast(size_t[5])[6, 4, 3, 5, 7]);
569 
570     assert(iota(3, 4, 5, 6, 7)
571         .transposed!(4, 1, 0)
572         .shape == cast(size_t[5])[7, 4, 3, 5, 6]);
573 }
574 
575 /// Function
576 @safe @nogc pure nothrow version(mir_test) unittest
577 {
578     import mir.ndslice.slice;
579     import mir.ndslice.topology: iota;
580 
581     assert(iota(3, 4, 5, 6, 7)
582         .transposed(3, 1, 0)
583         .shape == cast(size_t[5])[6, 4, 3, 5, 7]);
584 
585     assert(iota(3, 4, 5, 6, 7)
586         .transposed(4, 1, 0)
587         .shape == cast(size_t[5])[7, 4, 3, 5, 6]);
588 }
589 
590 /// Single-argument function
591 @safe @nogc pure nothrow version(mir_test) unittest
592 {
593     import mir.ndslice.slice;
594     import mir.ndslice.topology: iota;
595 
596     assert(iota(3, 4, 5, 6, 7)
597         .transposed(3)
598         .shape == cast(size_t[5])[6, 3, 4, 5, 7]);
599 
600     assert(iota(3, 4, 5, 6, 7)
601         .transposed(4)
602         .shape == cast(size_t[5])[7, 3, 4, 5, 6]);
603 }
604 
605 /// _2-dimensional transpose
606 @safe @nogc pure nothrow version(mir_test) unittest
607 {
608     import mir.ndslice.slice;
609     import mir.ndslice.topology: iota;
610     assert(iota(3, 4)
611         .transposed
612         .shape == cast(size_t[2])[4, 3]);
613 }
614 
615 private enum _reversedCode = q{
616     with (slice)
617     {
618         if (_lengths[dimension])
619             _iterator += _strides[dimension] * (_lengths[dimension] - 1);
620         _strides[dimension] = -_strides[dimension];
621     }
622 };
623 
624 /++
625 Reverses the direction of iteration for all dimensions.
626 Params:
627     _slice = input slice
628 Returns:
629     n-dimensional slice
630 +/
631 Slice!(Iterator, N, Universal) allReversed(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice)
632 {
633     import core.lifetime: move;
634     import mir.ndslice.topology: universal;
635     auto slice = _slice.move.universal;
636     foreach (dimension; Iota!N)
637     {
638         mixin (_reversedCode);
639     }
640     return slice;
641 }
642 
643 ///
644 @safe @nogc pure nothrow
645 version(mir_test) unittest
646 {
647     import mir.ndslice.slice;
648     import mir.ndslice.topology: iota, retro;
649     assert(iota(4, 5).allReversed == iota(4, 5).retro);
650 }
651 
652 /++
653 Reverses the direction of iteration for selected dimensions.
654 
655 Params:
656     _slice = input slice
657     Dimensions = indices of dimensions to reverse order of iteration
658     dimensions = indices of dimensions to reverse order of iteration
659 Returns:
660     n-dimensional slice
661 +/
662 template reversed(Dimensions...)
663     if (Dimensions.length)
664 {
665     static if (allSatisfy!(isSize_t, Dimensions))
666     ///
667     @optmath auto reversed(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice) @trusted
668     {
669         import mir.algorithm.iteration: any;
670         enum s = N;
671         enum hasRowStride = [Dimensions].sliced.any!(a => a + 1 == s);
672         static if (kind == Universal || kind == Canonical && !hasRowStride)
673         {
674             alias slice = _slice;
675         }
676         else
677         static if (hasRowStride)
678         {
679             import mir.ndslice.topology: universal;
680             auto slice = _slice.universal;
681         }
682         else
683         {
684             import mir.ndslice.topology: canonical;
685             auto slice = _slice.canonical;
686         }
687         foreach (i, dimension; Dimensions)
688         {
689             mixin DimensionCTError;
690             mixin (_reversedCode);
691         }
692         return slice;
693     }
694     else
695         alias reversed = .reversed!(staticMap!(toSize_t, Dimensions));
696 }
697 
698 ///ditto
699 Slice!(Iterator, N, Universal) reversed(Iterator, size_t N, SliceKind kind, size_t M)(Slice!(Iterator, N, kind) _slice, size_t[M] dimensions...)
700     @trusted
701     if (M)
702 {
703     import mir.ndslice.topology: universal;
704     auto slice = _slice.universal;
705     foreach (dimension; dimensions)
706         mixin (DimensionRTError);
707     foreach (i; Iota!(0, M))
708     {
709         auto dimension = dimensions[i];
710         mixin (_reversedCode);
711     }
712     return slice;
713 }
714 
715 /// ditto
716 auto reversed(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice)
717 {
718     return .reversed!0(slice);
719 }
720 
721 ///
722 @safe pure nothrow version(mir_test) unittest
723 {
724     import mir.ndslice.topology: iota;
725 
726     auto slice = iota([2, 2], 1);
727     assert(slice                    == [[1, 2], [3, 4]]);
728 
729     // Default
730     assert(slice.reversed           == [[3, 4], [1, 2]]);
731 
732     // Template
733     assert(slice.reversed! 0        == [[3, 4], [1, 2]]);
734     assert(slice.reversed! 1        == [[2, 1], [4, 3]]);
735     assert(slice.reversed!(0, 1)    == [[4, 3], [2, 1]]);
736     assert(slice.reversed!(1, 0)    == [[4, 3], [2, 1]]);
737     assert(slice.reversed!(1, 1)    == [[1, 2], [3, 4]]);
738     assert(slice.reversed!(0, 0, 0) == [[3, 4], [1, 2]]);
739 
740     // Function
741     assert(slice.reversed (0)       == [[3, 4], [1, 2]]);
742     assert(slice.reversed (1)       == [[2, 1], [4, 3]]);
743     assert(slice.reversed (0, 1)    == [[4, 3], [2, 1]]);
744     assert(slice.reversed (1, 0)    == [[4, 3], [2, 1]]);
745     assert(slice.reversed (1, 1)    == [[1, 2], [3, 4]]);
746     assert(slice.reversed (0, 0, 0) == [[3, 4], [1, 2]]);
747 }
748 
749 ///
750 @safe pure nothrow version(mir_test) unittest
751 {
752     import mir.ndslice.topology: iota, canonical;
753     auto slice = iota([2, 2], 1).canonical;
754     assert(slice                    == [[1, 2], [3, 4]]);
755 
756     // Template
757     assert(slice.reversed! 0        == [[3, 4], [1, 2]]);
758     assert(slice.reversed!(0, 0, 0) == [[3, 4], [1, 2]]);
759 
760     // Function
761     assert(slice.reversed (0)       == [[3, 4], [1, 2]]);
762     assert(slice.reversed (0, 0, 0) == [[3, 4], [1, 2]]);
763 }
764 
765 @safe @nogc pure nothrow version(mir_test) unittest
766 {
767     import mir.algorithm.iteration : equal;
768     import mir.ndslice.concatenation : concatenation;
769     import mir.ndslice.slice;
770     import mir.ndslice.topology;
771     auto i0 = iota([4], 0); auto r0 = i0.retro;
772     auto i1 = iota([4], 4); auto r1 = i1.retro;
773     auto i2 = iota([4], 8); auto r2 = i2.retro;
774     auto slice = iota(3, 4).universal;
775     assert(slice                   .flattened.equal(concatenation(i0, i1, i2)));
776     // Template
777     assert(slice.reversed!(0)      .flattened.equal(concatenation(i2, i1, i0)));
778     assert(slice.reversed!(1)      .flattened.equal(concatenation(r0, r1, r2)));
779     assert(slice.reversed!(0, 1)   .flattened.equal(concatenation(r2, r1, r0)));
780     assert(slice.reversed!(1, 0)   .flattened.equal(concatenation(r2, r1, r0)));
781     assert(slice.reversed!(1, 1)   .flattened.equal(concatenation(i0, i1, i2)));
782     assert(slice.reversed!(0, 0, 0).flattened.equal(concatenation(i2, i1, i0)));
783     // Function
784     assert(slice.reversed (0)      .flattened.equal(concatenation(i2, i1, i0)));
785     assert(slice.reversed (1)      .flattened.equal(concatenation(r0, r1, r2)));
786     assert(slice.reversed (0, 1)   .flattened.equal(concatenation(r2, r1, r0)));
787     assert(slice.reversed (1, 0)   .flattened.equal(concatenation(r2, r1, r0)));
788     assert(slice.reversed (1, 1)   .flattened.equal(concatenation(i0, i1, i2)));
789     assert(slice.reversed (0, 0, 0).flattened.equal(concatenation(i2, i1, i0)));
790 }
791 
792 private enum _stridedCode = q{
793     assert(factor > 0, "factor must be positive"
794         ~ tailErrorMessage!());
795     immutable rem = slice._lengths[dimension] % factor;
796     slice._lengths[dimension] /= factor;
797     if (slice._lengths[dimension]) //do not remove `if (...)`
798         slice._strides[dimension] *= factor;
799     if (rem)
800         slice._lengths[dimension]++;
801 };
802 
803 /++
804 Multiplies the stride of the selected dimension by a factor.
805 
806 Params:
807     Dimensions = indices of dimensions to be strided
808     dimension = indexe of a dimension to be strided
809     factor = step extension factors
810 Returns:
811     n-dimensional slice
812 +/
813 auto strided(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice, ptrdiff_t factor)
814 {
815     import core.lifetime: move;
816     import std.meta: Repeat;
817     return move(slice).strided!(Iota!N)(Repeat!(N, factor));
818 }
819 
820 ///
821 @safe pure nothrow version(mir_test) unittest
822 {
823     import mir.ndslice.topology: iota;
824     // 0  1   2  3
825     // 4  5   6  7
826     // 8  9  10 11
827     assert(iota(3, 4).strided(2) == [[0, 2], [8, 10]]);
828 }
829 
830 /// ditto
831 template strided(Dimensions...)
832     if (Dimensions.length)
833 {
834     static if (allSatisfy!(isSize_t, Dimensions))
835     /++
836     Params:
837         _slice = input slice
838         factors = list of step extension factors
839     +/
840     @optmath auto strided(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice, Repeat!(Dimensions.length, ptrdiff_t) factors)
841     {
842         import mir.algorithm.iteration: any;
843         enum s = N;
844         enum hasRowStride = [Dimensions].sliced.any!(a => a + 1 == s);
845         static if (kind == Universal || kind == Canonical && !hasRowStride)
846         {
847             alias slice = _slice;
848         }
849         else
850         static if (hasRowStride)
851         {
852             import mir.ndslice.topology: universal;
853             auto slice = _slice.universal;
854         }
855         else
856         {
857             import mir.ndslice.topology: canonical;
858             auto slice = _slice.canonical;
859         }
860         foreach (i, dimension; Dimensions)
861         {
862             mixin DimensionCTError;
863             immutable factor = factors[i];
864             mixin (_stridedCode);
865         }
866         return slice;
867     }
868     else
869         alias strided = .strided!(staticMap!(toSize_t, Dimensions));
870 }
871 
872 ///ditto
873 Slice!(Iterator, N, Universal) strided(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice, size_t dimension, ptrdiff_t factor)
874 {
875     import mir.ndslice.topology: universal;
876     auto slice = _slice.universal;
877     mixin (DimensionRTError);
878     mixin (_stridedCode);
879     return slice;
880 }
881 
882 ///
883 pure nothrow version(mir_test) unittest
884 {
885     import mir.ndslice.topology: iota;
886     auto slice = iota(3, 4);
887 
888     assert(slice
889         == [[0,1,2,3], [4,5,6,7], [8,9,10,11]]);
890 
891     // Template
892     assert(slice.strided!0(2)
893         == [[0,1,2,3],            [8,9,10,11]]);
894 
895     assert(slice.strided!1(3)
896         == [[0,    3], [4,    7], [8,     11]]);
897 
898     assert(slice.strided!(0, 1)(2, 3)
899         == [[0,    3],            [8,     11]]);
900 
901     // Function
902     assert(slice.strided(0, 2)
903         == [[0,1,2,3],            [8,9,10,11]]);
904 
905     assert(slice.strided(1, 3)
906         == [[0,    3], [4,    7], [8,     11]]);
907 
908     assert(slice.strided(0, 2).strided(1, 3)
909         == [[0,    3],            [8,     11]]);
910 }
911 
912 ///
913 @safe @nogc pure nothrow version(mir_test) unittest
914 {
915     import mir.ndslice.topology: iota, universal;
916     static assert(iota(13, 40).universal.strided!(0, 1)(2, 5).shape == [7, 8]);
917     static assert(iota(93).universal.strided!(0, 0)(7, 3).shape == [5]);
918 }
919 
920 ///
921 pure nothrow version(mir_test) unittest
922 {
923     import mir.ndslice.topology: iota, canonical;
924     auto slice = iota(3, 4).canonical;
925 
926     assert(slice
927         == [[0,1,2,3], [4,5,6,7], [8,9,10,11]]);
928 
929     // Template
930     assert(slice.strided!0(2)
931         == [[0,1,2,3],            [8,9,10,11]]);
932 
933     // Function
934     assert(slice.strided(0, 2)
935         == [[0,1,2,3],            [8,9,10,11]]);
936 }
937 
938 @safe @nogc pure nothrow version(mir_test) unittest
939 {
940     import mir.ndslice;
941     import mir.algorithm.iteration : equal;
942 
943     import std.range : chain;
944     auto i0 = iota([4], 0); auto s0 = stride(i0, 3);
945     auto i1 = iota([4], 4); auto s1 = stride(i1, 3);
946     auto i2 = iota([4], 8); auto s2 = stride(i2, 3);
947     auto slice = iota(3, 4).universal;
948     assert(slice              .flattened.equal(concatenation(i0, i1, i2)));
949     // Template
950     assert(slice.strided!0(2) .flattened.equal(concatenation(i0, i2)));
951     assert(slice.strided!1(3) .flattened.equal(concatenation(s0, s1, s2)));
952     assert(slice.strided!(0, 1)(2, 3).flattened.equal(concatenation(s0, s2)));
953     // Function
954     assert(slice.strided(0, 2).flattened.equal(concatenation(i0, i2)));
955     assert(slice.strided(1, 3).flattened.equal(concatenation(s0, s1, s2)));
956     assert(slice.strided(0, 2).strided(1, 3).flattened.equal(concatenation(s0, s2)));
957 }
958 
959 /++
960 Returns maximal multidimensional cube.
961 
962 Params:
963     slice = input slice
964 Returns:
965     n-dimensional slice
966 +/
967 Slice!(Iterator, N, kind) dropToHypercube(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice)
968     if (kind == Canonical || kind == Universal || N == 1)
969 do
970 {
971     size_t length = slice._lengths[0];
972     foreach (i; Iota!(1, N))
973         if (length > slice._lengths[i])
974             length = slice._lengths[i];
975     foreach (i; Iota!N)
976         slice._lengths[i] = length;
977     return slice;
978 }
979 
980 /// ditto
981 Slice!(Iterator, N, Canonical) dropToHypercube(Iterator, size_t N)(Slice!(Iterator, N) slice)
982     if (N > 1)
983 {
984     import mir.ndslice.topology: canonical;
985     return slice.canonical.dropToHypercube;
986 }
987 
988 ///
989 @safe @nogc pure nothrow version(mir_test) unittest
990 {
991     import mir.ndslice.topology: iota, canonical, universal;
992 
993     assert(iota(5, 3, 6, 7)
994         .dropToHypercube
995         .shape == cast(size_t[4])[3, 3, 3, 3]);
996 
997     assert(iota(5, 3, 6, 7)
998         .universal
999         .dropToHypercube
1000         .shape == cast(size_t[4])[3, 3, 3, 3]);
1001 
1002     assert(4.iota.dropToHypercube == 4.iota);
1003 }