The OpenD Programming Language

1 /++
2 This is a submodule of $(MREF mir,ndslice).
3 
4 Selectors create new views and iteration patterns over the same data, without copying.
5 
6 $(BOOKTABLE $(H2 Sequence Selectors),
7 $(TR $(TH Function Name) $(TH Description))
8 
9 $(T2 cycle, Cycle repeates 1-dimensional field/range/array/slice in a fixed length 1-dimensional slice)
10 $(T2 iota, Contiguous Slice with initial flattened (contiguous) index.)
11 $(T2 linspace, Evenly spaced numbers over a specified interval.)
12 $(T2 magic, Magic square.)
13 $(T2 ndiota, Contiguous Slice with initial multidimensional index.)
14 $(T2 repeat, Slice with identical values)
15 )
16 
17 $(BOOKTABLE $(H2 Shape Selectors),
18 $(TR $(TH Function Name) $(TH Description))
19 
20 $(T2 blocks, n-dimensional slice composed of n-dimensional non-overlapping blocks. If the slice has two dimensions, it is a block matrix.)
21 $(T2 diagonal, 1-dimensional slice composed of diagonal elements)
22 $(T2 dropBorders, Drops borders for all dimensions.)
23 $(T2 reshape, New slice view with changed dimensions)
24 $(T2 squeeze, New slice view of an n-dimensional slice with dimension removed)
25 $(T2 unsqueeze, New slice view of an n-dimensional slice with a dimension added)
26 $(T2 windows, n-dimensional slice of n-dimensional overlapping windows. If the slice has two dimensions, it is a sliding window.)
27 
28 )
29 
30 
31 $(BOOKTABLE $(H2 Subspace Selectors),
32 $(TR $(TH Function Name) $(TH Description))
33 
34 $(T2 alongDim , Returns a slice that can be iterated along dimension.)
35 $(T2 byDim    , Returns a slice that can be iterated by dimension.)
36 $(T2 pack     , Returns slice of slices.)
37 $(T2 ipack    , Returns slice of slices.)
38 $(T2 unpack   , Merges two hight dimension packs. See also $(SUBREF fuse, fuse).)
39 $(T2 evertPack, Reverses dimension packs.)
40 
41 )
42 
43 $(BOOKTABLE $(H2 SliceKind Selectors),
44 $(TR $(TH Function Name) $(TH Description))
45 
46 $(T2 asKindOf, Converts a slice to a user provied kind $(SUBREF slice, SliceKind).)
47 $(T2 universal, Converts a slice to universal $(SUBREF slice, SliceKind).)
48 $(T2 canonical, Converts a slice to canonical $(SUBREF slice, SliceKind).)
49 $(T2 assumeCanonical, Converts a slice to canonical $(SUBREF slice, SliceKind). Does only `assert` checks.)
50 $(T2 assumeContiguous, Converts a slice to contiguous $(SUBREF slice, SliceKind). Does only `assert` checks.)
51 $(T2 assumeHypercube, Helps the compiler to use optimisations related to the shape form. Does only `assert` checks.)
52 $(T2 assumeSameShape, Helps the compiler to use optimisations related to the shape form. Does only `assert` checks.)
53 
54 )
55 
56 $(BOOKTABLE $(H2 Products),
57 $(TR $(TH Function Name) $(TH Description))
58 
59 $(T2 cartesian, Cartesian product.)
60 $(T2 kronecker, Kronecker product.)
61 
62 )
63 
64 $(BOOKTABLE $(H2 Representation Selectors),
65 $(TR $(TH Function Name) $(TH Description))
66 
67 $(T2 as, Convenience function that creates a lazy view,
68 where each element of the original slice is converted to a type `T`.)
69 $(T2 bitpack, Bitpack slice over an unsigned integral slice.)
70 $(T2 bitwise, Bitwise slice over an unsigned integral slice.)
71 $(T2 bytegroup, Groups existing slice into fixed length chunks and uses them as data store for destination type.)
72 $(T2 cached, Random access cache. It is usefull in combiation with $(LREF map) and $(LREF vmap).)
73 $(T2 cachedGC, Random access cache auto-allocated in GC heap. It is usefull in combiation with $(LREF map) and $(LREF vmap).)
74 $(T2 diff, Differences between vector elements.)
75 $(T2 flattened, Contiguous 1-dimensional slice of all elements of a slice.)
76 $(T2 map, Multidimensional functional map.)
77 $(T2 member, Field (element's member) projection.)
78 $(T2 orthogonalReduceField, Functional deep-element wise reduce of a slice composed of fields or iterators.)
79 $(T2 pairwise, Pairwise map for vectors.)
80 $(T2 pairwiseMapSubSlices, Maps pairwise index pairs to subslices.)
81 $(T2 retro, Reverses order of iteration for all dimensions.)
82 $(T2 slide, Lazy convolution for tensors.)
83 $(T2 slideAlong, Lazy convolution for tensors.)
84 $(T2 stairs, Two functions to pack, unpack, and iterate triangular and symmetric matrix storage.)
85 $(T2 stride, Strides 1-dimensional slice.)
86 $(T2 subSlices, Maps index pairs to subslices.)
87 $(T2 triplets, Constructs a lazy view of triplets with `left`, `center`, and `right` members. The topology is usefull for Math and Physics.)
88 $(T2 unzip, Selects a slice from a zipped slice.)
89 $(T2 withNeighboursSum, Zip view of elements packed with sum of their neighbours.)
90 $(T2 zip, Zips slices into a slice of refTuples.)
91 )
92 
93 Subspace selectors serve to generalize and combine other selectors easily.
94 For a slice of `Slice!(Iterator, N, kind)` type `slice.pack!K` creates a slice of
95 slices of `Slice!(kind, [N - K, K], Iterator)` type by packing
96 the last `K` dimensions of the top dimension pack,
97 and the type of element of $(LREF flattened) is `Slice!(Iterator, K)`.
98 Another way to use $(LREF pack) is transposition of dimension packs using
99 $(LREF evertPack).
100 Examples of use of subspace selectors are available for selectors,
101 $(SUBREF slice, Slice.shape), and $(SUBREF slice, Slice.elementCount).
102 
103 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
104 Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments
105 Authors: Ilia Ki, John Michael Hall, Shigeki Karita (original numir code)
106 
107 Sponsors: Part of this work has been sponsored by $(LINK2 http://symmetryinvestments.com, Symmetry Investments) and Kaleidic Associates.
108 
109 Macros:
110 SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP)
111 T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
112 T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4))
113 +/
114 module mir.ndslice.topology;
115 
116 import mir.internal.utility;
117 import mir.math.common: fmamath;
118 import mir.ndslice.field;
119 import mir.ndslice.internal;
120 import mir.ndslice.iterator;
121 import mir.ndslice.ndfield;
122 import mir.ndslice.slice;
123 import mir.primitives;
124 import mir.qualifier;
125 import mir.utility: min;
126 import std.meta: AliasSeq, allSatisfy, staticMap, templateOr, Repeat;
127 
128 private immutable choppedExceptionMsg = "bounds passed to chopped are out of sliceable bounds.";
129 version (D_Exceptions) private immutable choppedException = new Exception(choppedExceptionMsg);
130 
131 @fmamath:
132 
133 /++
134 Converts a slice to user provided kind.
135 
136 Contiguous slices can be converted to any kind.
137 Canonical slices can't be converted to contiguous slices.
138 Universal slices can be converted only to the same kind.
139 
140 See_also:
141     $(LREF canonical),
142     $(LREF universal),
143     $(LREF assumeCanonical),
144     $(LREF assumeContiguous).
145 +/
146 template asKindOf(SliceKind kind)
147 {
148     static if (kind == Contiguous)
149     {
150         auto asKindOf(Iterator, size_t N, Labels...)(Slice!(Iterator, N, Contiguous, Labels) slice)
151         {
152             return slice;
153         }
154     }
155     else
156     static if (kind == Canonical)
157     {
158         alias asKindOf = canonical;
159     }
160     else
161     {
162         alias asKindOf = universal;
163     }
164 }
165 
166 /// Universal
167 @safe pure nothrow
168 version(mir_ndslice_test) unittest
169 {
170     import mir.ndslice.slice: Universal;
171     auto slice = iota(2, 3).asKindOf!Universal;
172     assert(slice == [[0, 1, 2], [3, 4, 5]]);
173     assert(slice._lengths == [2, 3]);
174     assert(slice._strides == [3, 1]);
175 }
176 
177 /// Canonical
178 @safe pure nothrow
179 version(mir_ndslice_test) unittest
180 {
181     import mir.ndslice.slice: Canonical;
182     auto slice = iota(2, 3).asKindOf!Canonical;
183     assert(slice == [[0, 1, 2], [3, 4, 5]]);
184     assert(slice._lengths == [2, 3]);
185     assert(slice._strides == [3]);
186 }
187 
188 /// Contiguous
189 @safe pure nothrow
190 version(mir_ndslice_test) unittest
191 {
192     import mir.ndslice.slice: Contiguous;
193     auto slice = iota(2, 3).asKindOf!Contiguous;
194     assert(slice == [[0, 1, 2], [3, 4, 5]]);
195     assert(slice._lengths == [2, 3]);
196     assert(slice._strides == []);
197 }
198 
199 /++
200 Converts a slice to universal kind.
201 
202 Params:
203     slice = a slice
204 Returns:
205     universal slice
206 See_also:
207     $(LREF canonical),
208     $(LREF assumeCanonical),
209     $(LREF assumeContiguous).
210 +/
211 auto universal(Iterator, size_t N, SliceKind kind, Labels...)(Slice!(Iterator, N, kind, Labels) slice)
212 {
213     import core.lifetime: move;
214 
215     static if (kind == Universal)
216     {
217         return slice;
218     }
219     else
220     static if (is(Iterator : RetroIterator!It, It))
221     {
222         return slice.move.retro.universal.retro;
223     }
224     else
225     {
226         alias Ret = Slice!(Iterator, N, Universal, Labels);
227         size_t[Ret.N] lengths;
228         auto strides = sizediff_t[Ret.S].init;
229         foreach (i; Iota!(slice.N))
230             lengths[i] = slice._lengths[i];
231         static if (kind == Canonical)
232         {
233             foreach (i; Iota!(slice.S))
234                 strides[i] = slice._strides[i];
235             strides[$-1] = 1;
236         }
237         else
238         {
239             ptrdiff_t ball = 1;
240             foreach_reverse (i; Iota!(Ret.S))
241             {
242                 strides[i] = ball;
243                 static if (i)
244                     ball *= slice._lengths[i];
245             }
246         }
247         return Ret(lengths, strides, slice._iterator.move, slice._labels);
248     }
249 }
250 
251 ///
252 @safe pure nothrow
253 version(mir_ndslice_test) unittest
254 {
255     auto slice = iota(2, 3).universal;
256     assert(slice == [[0, 1, 2], [3, 4, 5]]);
257     assert(slice._lengths == [2, 3]);
258     assert(slice._strides == [3, 1]);
259 }
260 
261 @safe pure nothrow
262 version(mir_ndslice_test) unittest
263 {
264     auto slice = iota(2, 3).canonical.universal;
265     assert(slice == [[0, 1, 2], [3, 4, 5]]);
266     assert(slice._lengths == [2, 3]);
267     assert(slice._strides == [3, 1]);
268 }
269 
270 ///
271 @safe pure nothrow
272 version(mir_ndslice_test) unittest
273 {
274     import mir.ndslice.slice;
275     import mir.ndslice.allocation: slice;
276 
277     auto dataframe = slice!(double, int, string)(2, 3);
278     dataframe.label[] = [1, 2];
279     dataframe.label!1[] = ["Label1", "Label2", "Label3"];
280 
281     auto universaldf = dataframe.universal;
282     assert(universaldf._lengths == [2, 3]);
283     assert(universaldf._strides == [3, 1]);
284 
285     assert(is(typeof(universaldf) ==
286         Slice!(double*, 2, Universal, int*, string*)));
287     assert(universaldf.label!0[0] == 1);
288     assert(universaldf.label!1[1] == "Label2");
289 }
290 
291 /++
292 Converts a slice to canonical kind.
293 
294 Params:
295     slice = contiguous or canonical slice
296 Returns:
297     canonical slice
298 See_also:
299     $(LREF universal),
300     $(LREF assumeCanonical),
301     $(LREF assumeContiguous).
302 +/
303 Slice!(Iterator, N, N == 1 ? Contiguous : Canonical, Labels)
304     canonical
305     (Iterator, size_t N, SliceKind kind, Labels...)
306     (Slice!(Iterator, N, kind, Labels) slice)
307     if (kind == Contiguous || kind == Canonical)
308 {
309     import core.lifetime: move;
310 
311     static if (kind == Canonical || N == 1)
312         return slice;
313     else
314     {
315         alias Ret = typeof(return);
316         size_t[Ret.N] lengths;
317         auto strides = sizediff_t[Ret.S].init;
318         foreach (i; Iota!(slice.N))
319             lengths[i] = slice._lengths[i];
320         ptrdiff_t ball = 1;
321         foreach_reverse (i; Iota!(Ret.S))
322         {
323             ball *= slice._lengths[i + 1];
324             strides[i] = ball;
325         }
326         return Ret(lengths, strides, slice._iterator.move, slice._labels);
327     }
328 }
329 
330 ///
331 @safe pure nothrow
332 version(mir_ndslice_test) unittest
333 {
334     auto slice = iota(2, 3).canonical;
335     assert(slice == [[0, 1, 2], [3, 4, 5]]);
336     assert(slice._lengths == [2, 3]);
337     assert(slice._strides == [3]);
338 }
339 
340 ///
341 @safe pure nothrow
342 version(mir_ndslice_test) unittest
343 {
344     import mir.ndslice.slice;
345     import mir.ndslice.allocation: slice;
346 
347     auto dataframe = slice!(double, int, string)(2, 3);
348     dataframe.label[] = [1, 2];
349     dataframe.label!1[] = ["Label1", "Label2", "Label3"];
350 
351     auto canonicaldf = dataframe.canonical;
352     assert(canonicaldf._lengths == [2, 3]);
353     assert(canonicaldf._strides == [3]);
354 
355     assert(is(typeof(canonicaldf) ==
356         Slice!(double*, 2, Canonical, int*, string*)));
357     assert(canonicaldf.label!0[0] == 1);
358     assert(canonicaldf.label!1[1] == "Label2");
359 }
360 
361 /++
362 Converts a slice to canonical kind (unsafe).
363 
364 Params:
365     slice = a slice
366 Returns:
367     canonical slice
368 See_also:
369     $(LREF universal),
370     $(LREF canonical),
371     $(LREF assumeContiguous).
372 +/
373 Slice!(Iterator, N, Canonical, Labels)
374     assumeCanonical
375     (Iterator, size_t N, SliceKind kind, Labels...)
376     (Slice!(Iterator, N, kind, Labels) slice)
377 {
378     static if (kind == Contiguous)
379         return slice.canonical;
380     else
381     static if (kind == Canonical)
382         return slice;
383     else
384     {
385         import mir.utility: swap;
386         assert(slice._lengths[N - 1] <= 1 || slice._strides[N - 1] == 1);
387         typeof(return) ret;
388         ret._lengths = slice._lengths;
389         ret._strides = slice._strides[0 .. $ - 1];
390         swap(ret._iterator, slice._iterator);
391         foreach(i, _; Labels)
392             swap(ret._labels[i], slice._labels[i]);
393         return ret;
394     }
395 }
396 
397 ///
398 @safe pure nothrow
399 version(mir_ndslice_test) unittest
400 {
401     auto slice = iota(2, 3).universal.assumeCanonical;
402     assert(slice == [[0, 1, 2], [3, 4, 5]]);
403     assert(slice._lengths == [2, 3]);
404     assert(slice._strides == [3]);
405 }
406 
407 ///
408 @safe pure nothrow
409 version(mir_ndslice_test) unittest
410 {
411     import mir.ndslice.slice;
412     import mir.ndslice.allocation: slice;
413 
414     auto dataframe = slice!(double, int, string)(2, 3);
415     dataframe.label[] = [1, 2];
416     dataframe.label!1[] = ["Label1", "Label2", "Label3"];
417 
418     auto assmcanonicaldf = dataframe.assumeCanonical;
419     assert(assmcanonicaldf._lengths == [2, 3]);
420     assert(assmcanonicaldf._strides == [3]);
421 
422     assert(is(typeof(assmcanonicaldf) ==
423         Slice!(double*, 2, Canonical, int*, string*)));
424     assert(assmcanonicaldf.label!0[0] == 1);
425     assert(assmcanonicaldf.label!1[1] == "Label2");
426 }
427 
428 /++
429 Converts a slice to contiguous kind (unsafe).
430 
431 Params:
432     slice = a slice
433 Returns:
434     canonical slice
435 See_also:
436     $(LREF universal),
437     $(LREF canonical),
438     $(LREF assumeCanonical).
439 +/
440 Slice!(Iterator, N, Contiguous, Labels)
441     assumeContiguous
442     (Iterator, size_t N, SliceKind kind, Labels...)
443     (Slice!(Iterator, N, kind, Labels) slice)
444 {
445     static if (kind == Contiguous)
446         return slice;
447     else
448     {
449         import mir.utility: swap;
450         typeof(return) ret;
451         ret._lengths = slice._lengths;
452         swap(ret._iterator, slice._iterator);
453         foreach(i, _; Labels)
454             swap(ret._labels[i], slice._labels[i]);
455         return ret;
456     }
457 }
458 
459 ///
460 @safe pure nothrow
461 version(mir_ndslice_test) unittest
462 {
463     auto slice = iota(2, 3).universal.assumeContiguous;
464     assert(slice == [[0, 1, 2], [3, 4, 5]]);
465     assert(slice._lengths == [2, 3]);
466     static assert(slice._strides.length == 0);
467 }
468 
469 ///
470 @safe pure nothrow
471 version(mir_ndslice_test) unittest
472 {
473     import mir.ndslice.slice;
474     import mir.ndslice.allocation: slice;
475 
476     auto dataframe = slice!(double, int, string)(2, 3);
477     dataframe.label[] = [1, 2];
478     dataframe.label!1[] = ["Label1", "Label2", "Label3"];
479 
480     auto assmcontdf = dataframe.canonical.assumeContiguous;
481     assert(assmcontdf._lengths == [2, 3]);
482     static assert(assmcontdf._strides.length == 0);
483 
484     assert(is(typeof(assmcontdf) ==
485         Slice!(double*, 2, Contiguous, int*, string*)));
486     assert(assmcontdf.label!0[0] == 1);
487     assert(assmcontdf.label!1[1] == "Label2");
488 }
489 
490 /++
491 Helps the compiler to use optimisations related to the shape form
492 +/
493 void assumeHypercube
494     (Iterator, size_t N, SliceKind kind, Labels...)
495     (ref scope Slice!(Iterator, N, kind, Labels) slice)
496 {
497     foreach (i; Iota!(1, N))
498     {
499         assert(slice._lengths[i] == slice._lengths[0]);
500         slice._lengths[i] = slice._lengths[0];
501     }
502 }
503 
504 ///
505 @safe @nogc pure nothrow version(mir_ndslice_test) unittest
506 {
507     auto b = iota(5, 5);
508     
509     assumeHypercube(b);
510 
511     assert(b == iota(5, 5));
512 }
513 
514 /++
515 Helps the compiler to use optimisations related to the shape form
516 +/
517 void assumeSameShape(T...)
518     (ref scope T slices)
519     if (allSatisfy!(isSlice, T))
520 {
521     foreach (i; Iota!(1, T.length))
522     {
523         assert(slices[i]._lengths == slices[0]._lengths);
524         slices[i]._lengths = slices[0]._lengths;
525     }
526 }
527 
528 ///
529 @safe @nogc pure nothrow version(mir_ndslice_test) unittest
530 {
531     auto a = iota(5, 5);
532     auto b = iota(5, 5);
533     
534     assumeHypercube(a); // first use this one, if applicable
535     assumeSameShape(a, b); //
536 
537     assert(a == iota(5, 5));
538     assert(b == iota(5, 5));
539 }
540 
541 /++
542 +/
543 auto assumeFieldsHaveZeroShift(Iterator, size_t N, SliceKind kind)
544     (Slice!(Iterator, N, kind) slice)
545     if (__traits(hasMember, Iterator, "assumeFieldsHaveZeroShift"))
546 {
547     return slice._iterator.assumeFieldsHaveZeroShift.slicedField(slice._lengths);
548 }
549 
550 /++
551 Creates a packed slice, i.e. slice of slices.
552 Packs the last `P` dimensions.
553 The function does not allocate any data.
554 
555 Params:
556     P = size of dimension pack
557     slice = a slice to pack
558 Returns:
559     `slice.pack!p` returns `Slice!(kind, [N - p, p], Iterator)`
560 See_also: $(LREF ipack)
561 +/
562 Slice!(SliceIterator!(Iterator, P, P == 1 && kind == Canonical ? Contiguous : kind), N - P, Universal)
563 pack(size_t P, Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice)
564     if (P && P < N)
565 {
566     import core.lifetime: move;
567     return slice.move.ipack!(N - P);
568 }
569 
570 ///
571 @safe @nogc pure nothrow version(mir_ndslice_test) unittest
572 {
573     import mir.ndslice.slice: sliced, Slice;
574 
575     auto a = iota(3, 4, 5, 6);
576     auto b = a.pack!2;
577 
578     static immutable res1 = [3, 4];
579     static immutable res2 = [5, 6];
580     assert(b.shape == res1);
581     assert(b[0, 0].shape == res2);
582     assert(a == b.unpack);
583     assert(a.pack!2 == b);
584     static assert(is(typeof(b) == typeof(a.pack!2)));
585 }
586 
587 /++
588 Creates a packed slice, i.e. slice of slices.
589 Packs the last `N - P` dimensions.
590 The function does not allocate any data.
591 
592 Params:
593     + = size of dimension pack
594     slice = a slice to pack
595 See_also: $(LREF pack)
596 +/
597 Slice!(SliceIterator!(Iterator, N - P, N - P == 1 && kind == Canonical ? Contiguous : kind), P, Universal)
598 ipack(size_t P, Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice)
599     if (P && P < N)
600 {
601     import core.lifetime: move;
602     alias Ret = typeof(return);
603     alias It = Ret.Iterator;
604     alias EN = It.Element.N;
605     alias ES = It.Element.S;
606     auto sl = slice.move.universal;
607     static if (It.Element.kind == Contiguous)
608         return Ret(
609             cast(   size_t[P]) sl._lengths[0 .. P],
610             cast(ptrdiff_t[P]) sl._strides[0 .. P],
611             It(
612                 cast(size_t[EN]) sl._lengths[P .. $],
613                 sl._iterator.move));
614     else
615         return Ret(
616             cast(   size_t[P]) sl._lengths[0 .. P],
617             cast(ptrdiff_t[P]) sl._strides[0 .. P],
618             It(
619                 cast(   size_t[EN]) sl._lengths[P .. $],
620                 cast(ptrdiff_t[ES]) sl._strides[P .. $ - (It.Element.kind == Canonical)],
621                 sl._iterator.move));
622 }
623 
624 ///
625 @safe @nogc pure nothrow version(mir_ndslice_test) unittest
626 {
627     import mir.ndslice.slice: sliced, Slice;
628 
629     auto a = iota(3, 4, 5, 6);
630     auto b = a.ipack!2;
631 
632     static immutable res1 = [3, 4];
633     static immutable res2 = [5, 6];
634     assert(b.shape == res1);
635     assert(b[0, 0].shape == res2);
636     assert(a.ipack!2 == b);
637     static assert(is(typeof(b) == typeof(a.ipack!2)));
638 }
639 
640 /++
641 Unpacks a packed slice.
642 
643 The functions does not allocate any data.
644 
645 Params:
646     slice = packed slice
647 Returns:
648     unpacked slice, that is a view on the same data.
649 
650 See_also: $(LREF pack), $(LREF evertPack)
651 +/
652 Slice!(Iterator, N + M, min(innerKind, Canonical))
653     unpack(Iterator, size_t M, SliceKind innerKind, size_t N, SliceKind outerKind)
654     (Slice!(SliceIterator!(Iterator, M, innerKind), N, outerKind) slice)
655 {
656     alias Ret = typeof(return);
657     size_t[N + M] lengths;
658     auto strides = sizediff_t[Ret.S].init;
659     auto outerStrides = slice.strides;
660     auto innerStrides = Slice!(Iterator, M, innerKind)(
661         slice._iterator._structure,
662         slice._iterator._iterator,
663         ).strides;
664     foreach(i; Iota!N)
665         lengths[i] = slice._lengths[i];
666     foreach(i; Iota!N)
667         strides[i] = outerStrides[i];
668     foreach(i; Iota!M)
669         lengths[N + i] = slice._iterator._structure[0][i];
670     foreach(i; Iota!(Ret.S - N))
671         strides[N + i] = innerStrides[i];
672     return Ret(lengths, strides, slice._iterator._iterator);
673 }
674 
675 /++
676 Reverses the order of dimension packs.
677 This function is used in a functional pipeline with other selectors.
678 
679 Params:
680     slice = packed slice
681 Returns:
682     packed slice
683 
684 See_also: $(LREF pack), $(LREF unpack)
685 +/
686 Slice!(SliceIterator!(Iterator, N, outerKind), M, innerKind)
687 evertPack(Iterator, size_t M, SliceKind innerKind, size_t N, SliceKind outerKind)
688     (Slice!(SliceIterator!(Iterator, M, innerKind), N, outerKind) slice)
689 {
690     import core.lifetime: move;
691     return typeof(return)(
692         slice._iterator._structure,
693         typeof(return).Iterator(
694             slice._structure,
695             slice._iterator._iterator.move));
696 }
697 
698 ///
699 @safe @nogc pure nothrow version(mir_ndslice_test) unittest
700 {
701     import mir.ndslice.dynamic : transposed;
702     auto slice = iota(3, 4, 5, 6, 7, 8, 9, 10, 11).universal;
703     assert(slice
704         .pack!2
705         .evertPack
706         .unpack
707              == slice.transposed!(
708                 slice.shape.length-2,
709                 slice.shape.length-1));
710 }
711 
712 ///
713 @safe pure nothrow version(mir_ndslice_test) unittest
714 {
715     import mir.ndslice.iterator: SliceIterator;
716     import mir.ndslice.slice: sliced, Slice, Universal;
717     import mir.ndslice.allocation: slice;
718     static assert(is(typeof(
719         slice!int(6)
720             .sliced(1,2,3)
721             .pack!1
722             .evertPack
723         )
724          == Slice!(SliceIterator!(int*, 2, Universal), 1)));
725 }
726 
727 ///
728 @safe pure nothrow @nogc
729 version(mir_ndslice_test) unittest
730 {
731     auto a = iota(3, 4, 5, 6, 7, 8, 9, 10, 11);
732     auto b = a.pack!2.unpack;
733     static assert(is(typeof(a.canonical) == typeof(b)));
734     assert(a == b);
735 }
736 
737 /++
738 Returns a slice, the elements of which are equal to the initial flattened index value.
739 
740 Params:
741     N = dimension count
742     lengths = list of dimension lengths
743     start = value of the first element in a slice (optional for integer `I`)
744     stride = value of the stride between elements (optional)
745 Returns:
746     n-dimensional slice composed of indices
747 See_also: $(LREF ndiota)
748 +/
749 Slice!(IotaIterator!I, N)
750 iota
751     (I = sizediff_t, size_t N)(size_t[N] lengths...)
752     if (__traits(isIntegral, I))
753 {
754     import mir.ndslice.slice: sliced;
755     return IotaIterator!I(I.init).sliced(lengths);
756 }
757 
758 ///ditto
759 Slice!(IotaIterator!sizediff_t, N)
760 iota
761     (size_t N)(size_t[N] lengths, sizediff_t start)
762 {
763     import mir.ndslice.slice: sliced;
764     return IotaIterator!sizediff_t(start).sliced(lengths);
765 }
766 
767 ///ditto
768 Slice!(StrideIterator!(IotaIterator!sizediff_t), N)
769 iota
770     (size_t N)(size_t[N] lengths, sizediff_t start, size_t stride)
771 {
772     import mir.ndslice.slice: sliced;
773     return StrideIterator!(IotaIterator!sizediff_t)(stride, IotaIterator!sizediff_t(start)).sliced(lengths);
774 }
775 
776 ///ditto
777 template iota(I)
778     if (__traits(isIntegral, I))
779 {
780     ///
781     Slice!(IotaIterator!I, N)
782     iota
783         (size_t N)(size_t[N] lengths, I start)
784         if (__traits(isIntegral, I))
785     {
786         import mir.ndslice.slice: sliced;
787         return IotaIterator!I(start).sliced(lengths);
788     }
789 
790     ///ditto
791     Slice!(StrideIterator!(IotaIterator!I), N)
792     iota
793         (size_t N)(size_t[N] lengths, I start, size_t stride)
794         if (__traits(isIntegral, I))
795     {
796         import mir.ndslice.slice: sliced;
797         return StrideIterator!(IotaIterator!I)(stride, IotaIterator!I(start)).sliced(lengths);
798     }
799 }
800 
801 ///ditto
802 Slice!(IotaIterator!I, N)
803 iota
804     (I, size_t N)(size_t[N] lengths, I start)
805     if (is(I P : P*))
806 {
807     import mir.ndslice.slice: sliced;
808     return IotaIterator!I(start).sliced(lengths);
809 }
810 
811 ///ditto
812 Slice!(StrideIterator!(IotaIterator!I), N)
813 iota
814     (I, size_t N)(size_t[N] lengths, I start, size_t stride)
815     if (is(I P : P*))
816 {
817     import mir.ndslice.slice: sliced;
818     return StrideIterator!(IotaIterator!I)(stride, IotaIterator!I(start)).sliced(lengths);
819 }
820 
821 ///
822 @safe pure nothrow @nogc version(mir_ndslice_test) unittest
823 {
824     import mir.primitives: DeepElementType;
825     auto slice = iota(2, 3);
826     static immutable array =
827         [[0, 1, 2],
828          [3, 4, 5]];
829 
830     assert(slice == array);
831 
832     static assert(is(DeepElementType!(typeof(slice)) == sizediff_t));
833 }
834 
835 ///
836 pure nothrow @nogc
837 version(mir_ndslice_test) unittest
838 {
839     int[6] data;
840     auto slice = iota([2, 3], data.ptr);
841     assert(slice[0, 0] == data.ptr);
842     assert(slice[0, 1] == data.ptr + 1);
843     assert(slice[1, 0] == data.ptr + 3);
844 }
845 
846 ///
847 @safe pure nothrow @nogc
848 version(mir_ndslice_test) unittest
849 {
850     auto im = iota([10, 5], 100);
851     assert(im[2, 1] == 111); // 100 + 2 * 5 + 1
852 
853     //slicing works correctly
854     auto cm = im[1 .. $, 3 .. $];
855     assert(cm[2, 1] == 119); // 119 = 100 + (1 + 2) * 5 + (3 + 1)
856 }
857 
858 /// `iota` with step
859 @safe pure nothrow version(mir_ndslice_test) unittest
860 {
861     auto sl = iota([2, 3], 10, 10);
862 
863     assert(sl == [[10, 20, 30],
864                   [40, 50, 60]]);
865 }
866 
867 /++
868 Returns a 1-dimensional slice over the main diagonal of an n-dimensional slice.
869 `diagonal` can be generalized with other selectors such as
870 $(LREF blocks) (diagonal blocks) and $(LREF windows) (multi-diagonal slice).
871 
872 Params:
873     slice = input slice
874 Returns:
875     1-dimensional slice composed of diagonal elements
876 See_also: $(LREF antidiagonal)
877 +/
878 Slice!(Iterator, 1, N == 1 ? kind : Universal)
879     diagonal
880     (Iterator, size_t N, SliceKind kind)
881     (Slice!(Iterator, N, kind) slice)
882 {
883     static if (N == 1)
884     {
885         return slice;
886     }
887     else
888     {
889         alias Ret = typeof(return);
890         size_t[Ret.N] lengths;
891         auto strides = sizediff_t[Ret.S].init;
892         lengths[0] = slice._lengths[0];
893         foreach (i; Iota!(1, N))
894             if (lengths[0] > slice._lengths[i])
895                 lengths[0] = slice._lengths[i];
896         foreach (i; Iota!(1, Ret.N))
897             lengths[i] = slice._lengths[i + N - 1];
898         auto rstrides = slice.strides;
899         strides[0] = rstrides[0];
900         foreach (i; Iota!(1, N))
901             strides[0] += rstrides[i];
902         foreach (i; Iota!(1, Ret.S))
903             strides[i] = rstrides[i + N - 1];
904         return Ret(lengths, strides, slice._iterator);
905     }
906 }
907 
908 /// Matrix, main diagonal
909 @safe @nogc pure nothrow version(mir_ndslice_test) unittest
910 {
911     //  -------
912     // | 0 1 2 |
913     // | 3 4 5 |
914     //  -------
915     //->
916     // | 0 4 |
917     static immutable d = [0, 4];
918     assert(iota(2, 3).diagonal == d);
919 }
920 
921 /// Non-square matrix
922 @safe pure nothrow version(mir_ndslice_test) unittest
923 {
924     //  -------
925     // | 0 1 |
926     // | 2 3 |
927     // | 4 5 |
928     //  -------
929     //->
930     // | 0 3 |
931 
932     assert(iota(3, 2).diagonal == iota([2], 0, 3));
933 }
934 
935 /// Loop through diagonal
936 @safe pure nothrow version(mir_ndslice_test) unittest
937 {
938     import mir.ndslice.slice;
939     import mir.ndslice.allocation;
940 
941     auto slice = slice!int(3, 3);
942     int i;
943     foreach (ref e; slice.diagonal)
944         e = ++i;
945     assert(slice == [
946         [1, 0, 0],
947         [0, 2, 0],
948         [0, 0, 3]]);
949 }
950 
951 /// Matrix, subdiagonal
952 @safe @nogc pure nothrow
953 version(mir_ndslice_test) unittest
954 {
955     //  -------
956     // | 0 1 2 |
957     // | 3 4 5 |
958     //  -------
959     //->
960     // | 1 5 |
961     static immutable d = [1, 5];
962     auto a = iota(2, 3).canonical;
963     a.popFront!1;
964     assert(a.diagonal == d);
965 }
966 
967 /// 3D, main diagonal
968 @safe @nogc pure nothrow version(mir_ndslice_test) unittest
969 {
970     //  -----------
971     // |  0   1  2 |
972     // |  3   4  5 |
973     //  - - - - - -
974     // |  6   7  8 |
975     // |  9  10 11 |
976     //  -----------
977     //->
978     // | 0 10 |
979     static immutable d = [0, 10];
980     assert(iota(2, 2, 3).diagonal == d);
981 }
982 
983 /// 3D, subdiagonal
984 @safe @nogc pure nothrow version(mir_ndslice_test) unittest
985 {
986     //  -----------
987     // |  0   1  2 |
988     // |  3   4  5 |
989     //  - - - - - -
990     // |  6   7  8 |
991     // |  9  10 11 |
992     //  -----------
993     //->
994     // | 1 11 |
995     static immutable d = [1, 11];
996     auto a = iota(2, 2, 3).canonical;
997     a.popFront!2;
998     assert(a.diagonal == d);
999 }
1000 
1001 /// 3D, diagonal plain
1002 @nogc @safe pure nothrow
1003 version(mir_ndslice_test) unittest
1004 {
1005     //  -----------
1006     // |  0   1  2 |
1007     // |  3   4  5 |
1008     // |  6   7  8 |
1009     //  - - - - - -
1010     // |  9  10 11 |
1011     // | 12  13 14 |
1012     // | 15  16 17 |
1013     //  - - - - - -
1014     // | 18  20 21 |
1015     // | 22  23 24 |
1016     // | 24  25 26 |
1017     //  -----------
1018     //->
1019     //  -----------
1020     // |  0   4  8 |
1021     // |  9  13 17 |
1022     // | 18  23 26 |
1023     //  -----------
1024 
1025     static immutable d =
1026         [[ 0,  4,  8],
1027          [ 9, 13, 17],
1028          [18, 22, 26]];
1029 
1030     auto slice = iota(3, 3, 3)
1031         .pack!2
1032         .evertPack
1033         .diagonal
1034         .evertPack;
1035 
1036     assert(slice == d);
1037 }
1038 
1039 /++
1040 Returns a 1-dimensional slice over the main antidiagonal of an 2D-dimensional slice.
1041 `antidiagonal` can be generalized with other selectors such as
1042 $(LREF blocks) (diagonal blocks) and $(LREF windows) (multi-diagonal slice).
1043 
1044 It runs from the top right corner to the bottom left corner.
1045 
1046 Pseudo_code:
1047 ------
1048 auto antidiagonal = slice.dropToHypercube.reversed!1.diagonal;
1049 ------
1050 
1051 Params:
1052     slice = input slice
1053 Returns:
1054     1-dimensional slice composed of antidiagonal elements.
1055 See_also: $(LREF diagonal)
1056 +/
1057 Slice!(Iterator, 1, Universal)
1058     antidiagonal
1059     (Iterator, size_t N, SliceKind kind)
1060     (Slice!(Iterator, N, kind) slice)
1061     if (N == 2)
1062 {
1063     import mir.ndslice.dynamic : dropToHypercube, reversed;
1064     return slice.dropToHypercube.reversed!1.diagonal;
1065 }
1066 
1067 ///
1068 @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1069 {
1070     //  -----
1071     // | 0 1 |
1072     // | 2 3 |
1073     //  -----
1074     //->
1075     // | 1 2 |
1076     static immutable c = [1, 2];
1077     assert(iota(2, 2).antidiagonal == c);
1078 }
1079 
1080 ///
1081 @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1082 {
1083     //  -------
1084     // | 0 1 2 |
1085     // | 3 4 5 |
1086     //  -------
1087     //->
1088     // | 1 3 |
1089     static immutable d = [1, 3];
1090     assert(iota(2, 3).antidiagonal == d);
1091 }
1092 
1093 /++
1094 Returns an n-dimensional slice of n-dimensional non-overlapping blocks.
1095 `blocks` can be generalized with other selectors.
1096 For example, `blocks` in combination with $(LREF diagonal) can be used to get a slice of diagonal blocks.
1097 For overlapped blocks, combine $(LREF windows) with $(SUBREF dynamic, strided).
1098 
1099 Params:
1100     N = dimension count
1101     slice = slice to be split into blocks
1102     rlengths_ = dimensions of block, residual blocks are ignored
1103 Returns:
1104     packed `N`-dimensional slice composed of `N`-dimensional slices
1105 
1106 See_also: $(SUBREF chunks, ._chunks)
1107 +/
1108 Slice!(SliceIterator!(Iterator, N, N == 1 ? Universal : min(kind, Canonical)), N, Universal)
1109     blocks
1110     (Iterator, size_t N, SliceKind kind)
1111     (Slice!(Iterator, N, kind) slice, size_t[N] rlengths_...)
1112 in
1113 {
1114     foreach (i, length; rlengths_)
1115         assert(length > 0, "length of dimension = " ~ i.stringof ~ " must be positive"
1116             ~ tailErrorMessage!());
1117 }
1118 do
1119 {
1120     size_t[N] lengths;
1121     size_t[N] rlengths = rlengths_;
1122     sizediff_t[N] strides;
1123     foreach (dimension; Iota!N)
1124         lengths[dimension] = slice._lengths[dimension] / rlengths[dimension];
1125     auto rstrides = slice.strides;
1126     foreach (i; Iota!N)
1127     {
1128         strides[i] = rstrides[i];
1129         if (lengths[i]) //do not remove `if (...)`
1130             strides[i] *= rlengths[i];
1131     }
1132     return typeof(return)(
1133         lengths,
1134         strides,
1135         typeof(return).Iterator(
1136             rlengths,
1137             rstrides[0 .. typeof(return).DeepElement.S],
1138             slice._iterator));
1139 }
1140 
1141 ///
1142 pure nothrow version(mir_ndslice_test) unittest
1143 {
1144     import mir.ndslice.slice;
1145     import mir.ndslice.allocation;
1146     auto slice = slice!int(5, 8);
1147     auto blocks = slice.blocks(2, 3);
1148     int i;
1149     foreach (blocksRaw; blocks)
1150         foreach (block; blocksRaw)
1151             block[] = ++i;
1152 
1153     assert(blocks ==
1154         [[[[1, 1, 1], [1, 1, 1]],
1155           [[2, 2, 2], [2, 2, 2]]],
1156          [[[3, 3, 3], [3, 3, 3]],
1157           [[4, 4, 4], [4, 4, 4]]]]);
1158 
1159     assert(    slice ==
1160         [[1, 1, 1,  2, 2, 2,  0, 0],
1161          [1, 1, 1,  2, 2, 2,  0, 0],
1162 
1163          [3, 3, 3,  4, 4, 4,  0, 0],
1164          [3, 3, 3,  4, 4, 4,  0, 0],
1165 
1166          [0, 0, 0,  0, 0, 0,  0, 0]]);
1167 }
1168 
1169 /// Diagonal blocks
1170 @safe pure nothrow version(mir_ndslice_test) unittest
1171 {
1172     import mir.ndslice.slice;
1173     import mir.ndslice.allocation;
1174     auto slice = slice!int(5, 8);
1175     auto blocks = slice.blocks(2, 3);
1176     auto diagonalBlocks = blocks.diagonal.unpack;
1177 
1178     diagonalBlocks[0][] = 1;
1179     diagonalBlocks[1][] = 2;
1180 
1181     assert(diagonalBlocks ==
1182         [[[1, 1, 1], [1, 1, 1]],
1183          [[2, 2, 2], [2, 2, 2]]]);
1184 
1185     assert(blocks ==
1186         [[[[1, 1, 1], [1, 1, 1]],
1187           [[0, 0, 0], [0, 0, 0]]],
1188          [[[0, 0, 0], [0, 0, 0]],
1189           [[2, 2, 2], [2, 2, 2]]]]);
1190 
1191     assert(slice ==
1192         [[1, 1, 1,  0, 0, 0,  0, 0],
1193          [1, 1, 1,  0, 0, 0,  0, 0],
1194 
1195          [0, 0, 0,  2, 2, 2,  0, 0],
1196          [0, 0, 0,  2, 2, 2,  0, 0],
1197 
1198          [0, 0, 0, 0, 0, 0, 0, 0]]);
1199 }
1200 
1201 /// Matrix divided into vertical blocks
1202 @safe pure version(mir_ndslice_test) unittest
1203 {
1204     import mir.ndslice.allocation;
1205     import mir.ndslice.slice;
1206     auto slice = slice!int(5, 13);
1207     auto blocks = slice
1208         .pack!1
1209         .evertPack
1210         .blocks(3)
1211         .unpack;
1212 
1213     int i;
1214     foreach (block; blocks)
1215         block[] = ++i;
1216 
1217     assert(slice ==
1218         [[1, 1, 1,  2, 2, 2,  3, 3, 3,  4, 4, 4,  0],
1219          [1, 1, 1,  2, 2, 2,  3, 3, 3,  4, 4, 4,  0],
1220          [1, 1, 1,  2, 2, 2,  3, 3, 3,  4, 4, 4,  0],
1221          [1, 1, 1,  2, 2, 2,  3, 3, 3,  4, 4, 4,  0],
1222          [1, 1, 1,  2, 2, 2,  3, 3, 3,  4, 4, 4,  0]]);
1223 }
1224 
1225 /++
1226 Returns an n-dimensional slice of n-dimensional overlapping windows.
1227 `windows` can be generalized with other selectors.
1228 For example, `windows` in combination with $(LREF diagonal) can be used to get a multi-diagonal slice.
1229 
1230 Params:
1231     N = dimension count
1232     slice = slice to be iterated
1233     rlengths = dimensions of windows
1234 Returns:
1235     packed `N`-dimensional slice composed of `N`-dimensional slices
1236 +/
1237 Slice!(SliceIterator!(Iterator, N, N == 1 ? kind : min(kind, Canonical)), N, Universal)
1238     windows
1239     (Iterator, size_t N, SliceKind kind)
1240     (Slice!(Iterator, N, kind) slice, size_t[N] rlengths...)
1241 in
1242 {
1243     foreach (i, length; rlengths)
1244         assert(length > 0, "length of dimension = " ~ i.stringof ~ " must be positive"
1245             ~ tailErrorMessage!());
1246 }
1247 do
1248 {
1249     size_t[N] rls = rlengths;
1250     size_t[N] lengths;
1251     foreach (dimension; Iota!N)
1252         lengths[dimension] = slice._lengths[dimension] >= rls[dimension] ?
1253             slice._lengths[dimension] - rls[dimension] + 1 : 0;
1254     auto rstrides = slice.strides;
1255     static if (typeof(return).DeepElement.S)
1256         return typeof(return)(
1257             lengths,
1258             rstrides,
1259             typeof(return).Iterator(
1260                 rls,
1261                 rstrides[0 .. typeof(return).DeepElement.S],
1262                 slice._iterator));
1263     else
1264         return typeof(return)(
1265             lengths,
1266             rstrides,
1267             typeof(return).Iterator(
1268                 rls,
1269                 slice._iterator));
1270 }
1271 
1272 ///
1273 @safe pure nothrow
1274 version(mir_ndslice_test) unittest
1275 {
1276     import mir.ndslice.allocation;
1277     import mir.ndslice.slice;
1278     auto slice = slice!int(5, 8);
1279     auto windows = slice.windows(2, 3);
1280 
1281     int i;
1282     foreach (windowsRaw; windows)
1283         foreach (window; windowsRaw)
1284             ++window[];
1285 
1286     assert(slice ==
1287         [[1,  2,  3, 3, 3, 3,  2,  1],
1288 
1289          [2,  4,  6, 6, 6, 6,  4,  2],
1290          [2,  4,  6, 6, 6, 6,  4,  2],
1291          [2,  4,  6, 6, 6, 6,  4,  2],
1292 
1293          [1,  2,  3, 3, 3, 3,  2,  1]]);
1294 }
1295 
1296 ///
1297 @safe pure nothrow version(mir_ndslice_test) unittest
1298 {
1299     import mir.ndslice.allocation;
1300     import mir.ndslice.slice;
1301     auto slice = slice!int(5, 8);
1302     auto windows = slice.windows(2, 3);
1303     windows[1, 2][] = 1;
1304     windows[1, 2][0, 1] += 1;
1305     windows.unpack[1, 2, 0, 1] += 1;
1306 
1307     assert(slice ==
1308         [[0, 0,  0, 0, 0,  0, 0, 0],
1309 
1310          [0, 0,  1, 3, 1,  0, 0, 0],
1311          [0, 0,  1, 1, 1,  0, 0, 0],
1312 
1313          [0, 0,  0, 0, 0,  0, 0, 0],
1314          [0, 0,  0, 0, 0,  0, 0, 0]]);
1315 }
1316 
1317 /// Multi-diagonal matrix
1318 @safe pure nothrow version(mir_ndslice_test) unittest
1319 {
1320     import mir.ndslice.allocation;
1321     import mir.ndslice.slice;
1322     auto slice = slice!int(8, 8);
1323     auto windows = slice.windows(3, 3);
1324 
1325     auto multidiagonal = windows
1326         .diagonal
1327         .unpack;
1328     foreach (window; multidiagonal)
1329         window[] += 1;
1330 
1331     assert(slice ==
1332         [[ 1, 1, 1,  0, 0, 0, 0, 0],
1333          [ 1, 2, 2, 1,  0, 0, 0, 0],
1334          [ 1, 2, 3, 2, 1,  0, 0, 0],
1335          [0,  1, 2, 3, 2, 1,  0, 0],
1336          [0, 0,  1, 2, 3, 2, 1,  0],
1337          [0, 0, 0,  1, 2, 3, 2, 1],
1338          [0, 0, 0, 0,  1, 2, 2, 1],
1339          [0, 0, 0, 0, 0,  1, 1, 1]]);
1340 }
1341 
1342 /// Sliding window over matrix columns
1343 @safe pure nothrow version(mir_ndslice_test) unittest
1344 {
1345     import mir.ndslice.allocation;
1346     import mir.ndslice.slice;
1347     auto slice = slice!int(5, 8);
1348     auto windows = slice
1349         .pack!1
1350         .evertPack
1351         .windows(3)
1352         .unpack;
1353 
1354     foreach (window; windows)
1355         window[] += 1;
1356 
1357     assert(slice ==
1358         [[1,  2,  3, 3, 3, 3,  2,  1],
1359          [1,  2,  3, 3, 3, 3,  2,  1],
1360          [1,  2,  3, 3, 3, 3,  2,  1],
1361          [1,  2,  3, 3, 3, 3,  2,  1],
1362          [1,  2,  3, 3, 3, 3,  2,  1]]);
1363 }
1364 
1365 /// Overlapping blocks using windows
1366 @safe pure nothrow version(mir_ndslice_test) unittest
1367 {
1368     //  ----------------
1369     // |  0  1  2  3  4 |
1370     // |  5  6  7  8  9 |
1371     // | 10 11 12 13 14 |
1372     // | 15 16 17 18 19 |
1373     // | 20 21 22 23 24 |
1374     //  ----------------
1375     //->
1376     //  ---------------------
1377     // |  0  1  2 |  2  3  4 |
1378     // |  5  6  7 |  7  8  9 |
1379     // | 10 11 12 | 12 13 14 |
1380     // | - - - - - - - - - - |
1381     // | 10 11 13 | 12 13 14 |
1382     // | 15 16 17 | 17 18 19 |
1383     // | 20 21 22 | 22 23 24 |
1384     //  ---------------------
1385 
1386     import mir.ndslice.slice;
1387     import mir.ndslice.dynamic : strided;
1388 
1389     auto overlappingBlocks = iota(5, 5)
1390         .windows(3, 3)
1391         .universal
1392         .strided!(0, 1)(2, 2);
1393 
1394     assert(overlappingBlocks ==
1395             [[[[ 0,  1,  2], [ 5,  6,  7], [10, 11, 12]],
1396               [[ 2,  3,  4], [ 7,  8,  9], [12, 13, 14]]],
1397              [[[10, 11, 12], [15, 16, 17], [20, 21, 22]],
1398               [[12, 13, 14], [17, 18, 19], [22, 23, 24]]]]);
1399 }
1400 
1401 version(mir_ndslice_test) unittest
1402 {
1403     auto w = iota(9, 9).windows(3, 3);
1404     assert(w.front == w[0]);
1405 }
1406 
1407 /++
1408 Error codes for $(LREF reshape).
1409 +/
1410 enum ReshapeError
1411 {
1412     /// No error
1413     none,
1414     /// Slice should be not empty
1415     empty,
1416     /// Total element count should be the same
1417     total,
1418     /// Structure is incompatible with new shape
1419     incompatible,
1420 }
1421 
1422 /++
1423 Returns a new slice for the same data with different dimensions.
1424 
1425 Params:
1426     slice = slice to be reshaped
1427     rlengths = list of new dimensions. One of the lengths can be set to `-1`.
1428         In this case, the corresponding dimension is inferable.
1429     err = $(LREF ReshapeError) code
1430 Returns:
1431     reshaped slice
1432 +/
1433 Slice!(Iterator, M, kind) reshape
1434         (Iterator, size_t N, SliceKind kind, size_t M)
1435         (Slice!(Iterator, N, kind) slice, ptrdiff_t[M] rlengths, ref int err)
1436 {
1437     static if (kind == Canonical)
1438     {
1439         auto r = slice.universal.reshape(rlengths, err);
1440         assert(err || r._strides[$-1] == 1);
1441         r._strides[$-1] = 1;
1442         return r.assumeCanonical;
1443     }
1444     else
1445     {
1446         alias Ret = typeof(return);
1447         auto structure = Ret._Structure.init;
1448         alias lengths = structure[0];
1449         foreach (i; Iota!M)
1450             lengths[i] = rlengths[i];
1451 
1452         /// Code size optimization
1453         immutable size_t eco = slice.elementCount;
1454         size_t ecn = lengths[0 .. rlengths.length].iota.elementCount;
1455         if (eco == 0)
1456         {
1457             err = ReshapeError.empty;
1458             goto R;
1459         }
1460         foreach (i; Iota!M)
1461             if (lengths[i] == -1)
1462             {
1463                 ecn = -ecn;
1464                 lengths[i] = eco / ecn;
1465                 ecn *= lengths[i];
1466                 break;
1467             }
1468         if (eco != ecn)
1469         {
1470             err = ReshapeError.total;
1471             goto R;
1472         }
1473         static if (kind == Universal)
1474         {
1475             for (size_t oi, ni, oj, nj; oi < N && ni < M; oi = oj, ni = nj)
1476             {
1477                 size_t op = slice._lengths[oj++];
1478                 size_t np =        lengths[nj++];
1479 
1480                 for (;;)
1481                 {
1482                     if (op < np)
1483                         op *= slice._lengths[oj++];
1484                     if (op > np)
1485                         np *=        lengths[nj++];
1486                     if (op == np)
1487                         break;
1488                 }
1489                 while (oj < N && slice._lengths[oj] == 1) oj++;
1490                 while (nj < M        &&        lengths[nj] == 1) nj++;
1491 
1492                 for (size_t l = oi, r = oi + 1; r < oj; r++)
1493                     if (slice._lengths[r] != 1)
1494                     {
1495                         if (slice._strides[l] != slice._lengths[r] * slice._strides[r])
1496                         {
1497                             err = ReshapeError.incompatible;
1498                             goto R;
1499                         }
1500                         l = r;
1501                     }
1502                 assert((oi == N) == (ni == M));
1503 
1504                 structure[1][nj - 1] = slice._strides[oj - 1];
1505                 foreach_reverse (i; ni .. nj - 1)
1506                     structure[1][i] = lengths[i + 1] * structure[1][i + 1];
1507             }
1508         }
1509         foreach (i; Iota!(M, Ret.N))
1510             lengths[i] = slice._lengths[i + N - M];
1511         static if (M < Ret.S)
1512         foreach (i; Iota!(M, Ret.S))
1513             structure[1][i] = slice._strides[i + N - M];
1514         err = 0;
1515         return Ret(structure, slice._iterator);
1516     R:
1517         return Ret(structure, slice._iterator.init);
1518     }
1519 }
1520 
1521 ///
1522 @safe nothrow pure
1523 version(mir_ndslice_test) unittest
1524 {
1525     import mir.ndslice.dynamic : allReversed;
1526     int err;
1527     auto slice = iota(3, 4)
1528         .universal
1529         .allReversed
1530         .reshape([-1, 3], err);
1531     assert(err == 0);
1532     assert(slice ==
1533         [[11, 10, 9],
1534          [ 8,  7, 6],
1535          [ 5,  4, 3],
1536          [ 2,  1, 0]]);
1537 }
1538 
1539 /// Reshaping with memory allocation
1540 @safe pure version(mir_ndslice_test) unittest
1541 {
1542     import mir.ndslice.slice: sliced;
1543     import mir.ndslice.allocation: slice;
1544     import mir.ndslice.dynamic : reversed;
1545 
1546     auto reshape2(S, size_t M)(S sl, ptrdiff_t[M] lengths)
1547     {
1548         int err;
1549         // Tries to reshape without allocation
1550         auto ret = sl.reshape(lengths, err);
1551         if (!err)
1552             return ret;
1553         if (err == ReshapeError.incompatible)
1554             // allocates, flattens, reshapes with `sliced`, converts to universal kind
1555             return sl.slice.flattened.sliced(cast(size_t[M])lengths).universal;
1556         throw new Exception("total elements count is different or equals to zero");
1557     }
1558 
1559     auto sl = iota!int(3, 4)
1560         .slice
1561         .universal
1562         .reversed!0;
1563 
1564     assert(reshape2(sl, [4, 3]) ==
1565         [[ 8, 9, 10],
1566          [11, 4,  5],
1567          [ 6, 7,  0],
1568          [ 1, 2,  3]]);
1569 }
1570 
1571 nothrow @safe pure version(mir_ndslice_test) unittest
1572 {
1573     import mir.ndslice.dynamic : allReversed;
1574     auto slice = iota(1, 1, 3, 2, 1, 2, 1).universal.allReversed;
1575     int err;
1576     assert(slice.reshape([1, -1, 1, 1, 3, 1], err) ==
1577         [[[[[[11], [10], [9]]]],
1578           [[[[ 8], [ 7], [6]]]],
1579           [[[[ 5], [ 4], [3]]]],
1580           [[[[ 2], [ 1], [0]]]]]]);
1581     assert(err == 0);
1582 }
1583 
1584 // Issue 15919
1585 nothrow @nogc @safe pure
1586 version(mir_ndslice_test) unittest
1587 {
1588     int err;
1589     assert(iota(3, 4, 5, 6, 7).pack!2.reshape([4, 3, 5], err)[0, 0, 0].shape == cast(size_t[2])[6, 7]);
1590     assert(err == 0);
1591 }
1592 
1593 nothrow @nogc @safe pure version(mir_ndslice_test) unittest
1594 {
1595     import mir.ndslice.slice;
1596 
1597     int err;
1598     auto e = iota(1);
1599     // resize to the wrong dimension
1600     auto s = e.reshape([2], err);
1601     assert(err == ReshapeError.total);
1602     e.popFront;
1603     // test with an empty slice
1604     e.reshape([1], err);
1605     assert(err == ReshapeError.empty);
1606 }
1607 
1608 nothrow @nogc @safe pure
1609 version(mir_ndslice_test) unittest
1610 {
1611     auto pElements = iota(3, 4, 5, 6, 7)
1612         .pack!2
1613         .flattened;
1614     assert(pElements[0][0] == iota(7));
1615     assert(pElements[$-1][$-1] == iota([7], 2513));
1616 }
1617 
1618 /++
1619 A contiguous 1-dimensional slice of all elements of a slice.
1620 `flattened` iterates existing data.
1621 The order of elements is preserved.
1622 
1623 `flattened` can be generalized with other selectors.
1624 
1625 Params:
1626     slice = slice to be iterated
1627 Returns:
1628     contiguous 1-dimensional slice of elements of the `slice`
1629 +/
1630 Slice!(FlattenedIterator!(Iterator, N, kind))
1631     flattened
1632     (Iterator, size_t N, SliceKind kind)
1633     (Slice!(Iterator, N, kind) slice)
1634     if (N != 1 && kind != Contiguous)
1635 {
1636     import core.lifetime: move;
1637     size_t[typeof(return).N] lengths;
1638     sizediff_t[typeof(return)._iterator._indices.length] indices;
1639     lengths[0] = slice.elementCount;
1640     return typeof(return)(lengths, FlattenedIterator!(Iterator, N, kind)(indices, slice.move));
1641 }
1642 
1643 /// ditto
1644 Slice!Iterator
1645     flattened
1646     (Iterator, size_t N)
1647     (Slice!(Iterator, N) slice)
1648 {
1649     static if (N == 1)
1650     {
1651         return slice;
1652     }
1653     else
1654     {
1655         import core.lifetime: move;
1656         size_t[typeof(return).N] lengths;
1657         lengths[0] = slice.elementCount;
1658         return typeof(return)(lengths, slice._iterator.move);
1659     }
1660 }
1661 
1662 /// ditto
1663 Slice!(StrideIterator!Iterator)
1664     flattened
1665     (Iterator)
1666     (Slice!(Iterator,  1, Universal) slice)
1667 {
1668     import core.lifetime: move;
1669     return slice.move.hideStride;
1670 }
1671 
1672 version(mir_ndslice_test) unittest
1673 {
1674     import mir.ndslice.allocation: slice;
1675     auto sl1 = iota(2, 3).slice.universal.pack!1.flattened;
1676     auto sl2 = iota(2, 3).slice.canonical.pack!1.flattened;
1677     auto sl3 = iota(2, 3).slice.pack!1.flattened;
1678 }
1679 
1680 /// Regular slice
1681 @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1682 {
1683     assert(iota(4, 5).flattened == iota(20));
1684     assert(iota(4, 5).canonical.flattened == iota(20));
1685     assert(iota(4, 5).universal.flattened == iota(20));
1686 }
1687 
1688 @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1689 {
1690     assert(iota(4).flattened == iota(4));
1691     assert(iota(4).canonical.flattened == iota(4));
1692     assert(iota(4).universal.flattened == iota(4));
1693 }
1694 
1695 /// Packed slice
1696 @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1697 {
1698     import mir.ndslice.slice;
1699     import mir.ndslice.dynamic;
1700     assert(iota(3, 4, 5, 6, 7).pack!2.flattened[1] == iota([6, 7], 6 * 7));
1701 }
1702 
1703 /// Properties
1704 @safe pure nothrow version(mir_ndslice_test) unittest
1705 {
1706     auto elems = iota(3, 4).universal.flattened;
1707 
1708     elems.popFrontExactly(2);
1709     assert(elems.front == 2);
1710     /// `_index` is available only for canonical and universal ndslices.
1711     assert(elems._iterator._indices == [0, 2]);
1712 
1713     elems.popBackExactly(2);
1714     assert(elems.back == 9);
1715     assert(elems.length == 8);
1716 }
1717 
1718 /// Index property
1719 @safe pure nothrow version(mir_ndslice_test) unittest
1720 {
1721     import mir.ndslice.slice;
1722     auto slice = new long[20].sliced(5, 4);
1723 
1724     for (auto elems = slice.universal.flattened; !elems.empty; elems.popFront)
1725     {
1726         ptrdiff_t[2] index = elems._iterator._indices;
1727         elems.front = index[0] * 10 + index[1] * 3;
1728     }
1729     assert(slice ==
1730         [[ 0,  3,  6,  9],
1731          [10, 13, 16, 19],
1732          [20, 23, 26, 29],
1733          [30, 33, 36, 39],
1734          [40, 43, 46, 49]]);
1735 }
1736 
1737 @safe pure nothrow version(mir_ndslice_test) unittest
1738 {
1739     auto elems = iota(3, 4).universal.flattened;
1740     assert(elems.front == 0);
1741     assert(elems.save[1] == 1);
1742 }
1743 
1744 /++
1745 Random access and slicing
1746 +/
1747 nothrow version(mir_ndslice_test) unittest
1748 {
1749     import mir.ndslice.allocation: slice;
1750     import mir.ndslice.slice: sliced;
1751 
1752     auto elems = iota(4, 5).slice.flattened;
1753 
1754     elems = elems[11 .. $ - 2];
1755 
1756     assert(elems.length == 7);
1757     assert(elems.front == 11);
1758     assert(elems.back == 17);
1759 
1760     foreach (i; 0 .. 7)
1761         assert(elems[i] == i + 11);
1762 
1763     // assign an element
1764     elems[2 .. 6] = -1;
1765     assert(elems[2 .. 6] == repeat(-1, 4));
1766 
1767     // assign an array
1768     static ar = [-1, -2, -3, -4];
1769     elems[2 .. 6] = ar;
1770     assert(elems[2 .. 6] == ar);
1771 
1772     // assign a slice
1773     ar[] *= 2;
1774     auto sl = ar.sliced(ar.length);
1775     elems[2 .. 6] = sl;
1776     assert(elems[2 .. 6] == sl);
1777 }
1778 
1779 @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1780 {
1781     import mir.ndslice.dynamic : allReversed;
1782 
1783     auto slice = iota(3, 4, 5);
1784 
1785     foreach (ref e; slice.universal.flattened.retro)
1786     {
1787         //...
1788     }
1789 
1790     foreach_reverse (ref e; slice.universal.flattened)
1791     {
1792         //...
1793     }
1794 
1795     foreach (ref e; slice.universal.allReversed.flattened)
1796     {
1797         //...
1798     }
1799 }
1800 
1801 @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1802 {
1803     import std.range.primitives : isRandomAccessRange, hasSlicing;
1804     auto elems = iota(4, 5).flattened;
1805     static assert(isRandomAccessRange!(typeof(elems)));
1806     static assert(hasSlicing!(typeof(elems)));
1807 }
1808 
1809 // Checks strides
1810 @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1811 {
1812     import mir.ndslice.dynamic;
1813     import std.range.primitives : isRandomAccessRange;
1814     auto elems = iota(4, 5).universal.everted.flattened;
1815     static assert(isRandomAccessRange!(typeof(elems)));
1816 
1817     elems = elems[11 .. $ - 2];
1818     auto elems2 = elems;
1819     foreach (i; 0 .. 7)
1820     {
1821         assert(elems[i] == elems2.front);
1822         elems2.popFront;
1823     }
1824 }
1825 
1826 @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1827 {
1828     import mir.ndslice.slice;
1829     import mir.ndslice.dynamic;
1830     import std.range.primitives : isRandomAccessRange, hasLength;
1831 
1832     auto range = (3 * 4 * 5 * 6 * 7).iota;
1833     auto slice0 = range.sliced(3, 4, 5, 6, 7).universal;
1834     auto slice1 = slice0.transposed!(2, 1).pack!2;
1835     auto elems0 = slice0.flattened;
1836     auto elems1 = slice1.flattened;
1837 
1838     foreach (S; AliasSeq!(typeof(elems0), typeof(elems1)))
1839     {
1840         static assert(isRandomAccessRange!S);
1841         static assert(hasLength!S);
1842     }
1843 
1844     assert(elems0.length == slice0.elementCount);
1845     assert(elems1.length == 5 * 4 * 3);
1846 
1847     auto elems2 = elems1;
1848     foreach (q; slice1)
1849         foreach (w; q)
1850             foreach (e; w)
1851             {
1852                 assert(!elems2.empty);
1853                 assert(e == elems2.front);
1854                 elems2.popFront;
1855             }
1856     assert(elems2.empty);
1857 
1858     elems0.popFront();
1859     elems0.popFrontExactly(slice0.elementCount - 14);
1860     assert(elems0.length == 13);
1861     assert(elems0 == range[slice0.elementCount - 13 .. slice0.elementCount]);
1862 
1863     foreach (elem; elems0) {}
1864 }
1865 
1866 // Issue 15549
1867 version(mir_ndslice_test) unittest
1868 {
1869     import std.range.primitives;
1870     import mir.ndslice.allocation;
1871     alias A = typeof(iota(1, 2, 3, 4).pack!1);
1872     static assert(isRandomAccessRange!A);
1873     static assert(hasLength!A);
1874     static assert(hasSlicing!A);
1875     alias B = typeof(slice!int(1, 2, 3, 4).pack!3);
1876     static assert(isRandomAccessRange!B);
1877     static assert(hasLength!B);
1878     static assert(hasSlicing!B);
1879 }
1880 
1881 // Issue 16010
1882 version(mir_ndslice_test) unittest
1883 {
1884     auto s = iota(3, 4).flattened;
1885     foreach (_; 0 .. s.length)
1886         s = s[1 .. $];
1887 }
1888 
1889 /++
1890 Returns a slice, the elements of which are equal to the initial multidimensional index value.
1891 For a flattened (contiguous) index, see $(LREF iota).
1892 
1893 Params:
1894     N = dimension count
1895     lengths = list of dimension lengths
1896 Returns:
1897     `N`-dimensional slice composed of indices
1898 See_also: $(LREF iota)
1899 +/
1900 Slice!(FieldIterator!(ndIotaField!N), N)
1901     ndiota
1902     (size_t N)
1903     (size_t[N] lengths...)
1904     if (N)
1905 {
1906     return FieldIterator!(ndIotaField!N)(0, ndIotaField!N(lengths[1 .. $])).sliced(lengths);
1907 }
1908 
1909 ///
1910 @safe pure nothrow @nogc version(mir_ndslice_test) unittest
1911 {
1912     auto slice = ndiota(2, 3);
1913     static immutable array =
1914         [[[0, 0], [0, 1], [0, 2]],
1915          [[1, 0], [1, 1], [1, 2]]];
1916 
1917     assert(slice == array);
1918 }
1919 
1920 ///
1921 @safe pure nothrow version(mir_ndslice_test) unittest
1922 {
1923     auto im = ndiota(7, 9);
1924 
1925     assert(im[2, 1] == [2, 1]);
1926 
1927     //slicing works correctly
1928     auto cm = im[1 .. $, 4 .. $];
1929     assert(cm[2, 1] == [3, 5]);
1930 }
1931 
1932 version(mir_ndslice_test) unittest
1933 {
1934     auto r = ndiota(1);
1935     auto d = r.front;
1936     r.popFront;
1937     import std.range.primitives;
1938     static assert(isRandomAccessRange!(typeof(r)));
1939 }
1940 
1941 /++
1942 Evenly spaced numbers over a specified interval.
1943 
1944 Params:
1945     T = floating point or complex numbers type
1946     lengths = list of dimension lengths. Each length must be greater then 1.
1947     intervals = list of [start, end] pairs.
1948 Returns:
1949     `n`-dimensional grid of evenly spaced numbers over specified intervals.
1950 See_also: $(LREF)
1951 +/
1952 auto linspace(T, size_t N)(size_t[N] lengths, T[2][N] intervals...)
1953     if (N && (isFloatingPoint!T || isComplex!T))
1954 {
1955     Repeat!(N, LinspaceField!T) fields;
1956     foreach(i; Iota!N)
1957     {
1958         assert(lengths[i] > 1, "linspace: all lengths must be greater then 1.");
1959         fields[i] = LinspaceField!T(lengths[i], intervals[i][0], intervals[i][1]);
1960     }
1961     static if (N == 1)
1962         return slicedField(fields);
1963     else
1964         return cartesian(fields);
1965 }
1966 
1967 // example from readme
1968 version(mir_ndslice_test) unittest
1969 {
1970     import mir.ndslice;
1971 
1972     enum fmt = "%(%(%.2f %)\n%)\n";
1973 
1974     auto a = magic(5).as!float;
1975     // writefln(fmt, a);
1976 
1977     auto b = linspace!float([5, 5], [1f, 2f], [0f, 1f]).map!"a * a + b";
1978     // writefln(fmt, b);
1979 
1980     auto c = slice!float(5, 5);
1981     c[] = transposed(a + b / 2);
1982 }
1983 
1984 /// 1D
1985 @safe pure nothrow
1986 version(mir_ndslice_test) unittest
1987 {
1988     auto s = linspace!double([5], [1.0, 2.0]);
1989     assert(s == [1.0, 1.25, 1.5, 1.75, 2.0]);
1990 
1991     // reverse order
1992     assert(linspace!double([5], [2.0, 1.0]) == s.retro);
1993 
1994     // remove endpoint
1995     s.popBack;
1996     assert(s == [1.0, 1.25, 1.5, 1.75]);
1997 }
1998 
1999 /// 2D
2000 @safe pure nothrow
2001 version(mir_ndslice_test) unittest
2002 {
2003     import mir.functional: tuple;
2004 
2005     auto s = linspace!double([5, 3], [1.0, 2.0], [0.0, 1.0]);
2006 
2007     assert(s == [
2008         [tuple(1.00, 0.00), tuple(1.00, 0.5), tuple(1.00, 1.0)],
2009         [tuple(1.25, 0.00), tuple(1.25, 0.5), tuple(1.25, 1.0)],
2010         [tuple(1.50, 0.00), tuple(1.50, 0.5), tuple(1.50, 1.0)],
2011         [tuple(1.75, 0.00), tuple(1.75, 0.5), tuple(1.75, 1.0)],
2012         [tuple(2.00, 0.00), tuple(2.00, 0.5), tuple(2.00, 1.0)],
2013         ]);
2014 
2015     assert(s.map!"a * b" == [
2016         [0.0, 0.500, 1.00],
2017         [0.0, 0.625, 1.25],
2018         [0.0, 0.750, 1.50],
2019         [0.0, 0.875, 1.75],
2020         [0.0, 1.000, 2.00],
2021         ]);
2022 }
2023 
2024 /// Complex numbers
2025 @safe pure nothrow
2026 version(mir_ndslice_test) unittest
2027 {
2028     import mir.complex;
2029     alias C = Complex!double;
2030     auto s = linspace!C([3], [C(1.0, 0), C(2.0, 4)]);
2031     assert(s == [C(1.0, 0), C(1.5, 2), C(2.0, 4)]);
2032 }
2033 
2034 /++
2035 Returns a slice with identical elements.
2036 `RepeatSlice` stores only single value.
2037 Params:
2038     lengths = list of dimension lengths
2039 Returns:
2040     `n`-dimensional slice composed of identical values, where `n` is dimension count.
2041 +/
2042 Slice!(FieldIterator!(RepeatField!T), M, Universal)
2043     repeat(T, size_t M)(T value, size_t[M] lengths...) @trusted
2044     if (M && !isSlice!T)
2045 {
2046     size_t[M] ls = lengths;
2047     return typeof(return)(
2048         ls,
2049         sizediff_t[M].init,
2050         typeof(return).Iterator(0, RepeatField!T(cast(RepeatField!T.UT) value)));
2051 }
2052 
2053 /// ditto
2054 Slice!(SliceIterator!(Iterator, N, kind), M, Universal)
2055     repeat
2056     (SliceKind kind, size_t N, Iterator, size_t M)
2057     (Slice!(Iterator, N, kind) slice, size_t[M] lengths...)
2058     if (M)
2059 {
2060     import core.lifetime: move;
2061     size_t[M] ls = lengths;
2062     return typeof(return)(
2063         ls,
2064         sizediff_t[M].init,
2065         typeof(return).Iterator(
2066             slice._structure,
2067             move(slice._iterator)));
2068 }
2069 
2070 ///
2071 @safe pure nothrow
2072 version(mir_ndslice_test) unittest
2073 {
2074     auto sl = iota(3).repeat(4);
2075     assert(sl == [[0, 1, 2],
2076                   [0, 1, 2],
2077                   [0, 1, 2],
2078                   [0, 1, 2]]);
2079 }
2080 
2081 ///
2082 @safe pure nothrow version(mir_ndslice_test) unittest
2083 {
2084     import mir.ndslice.dynamic : transposed;
2085 
2086     auto sl = iota(3)
2087         .repeat(4)
2088         .unpack
2089         .universal
2090         .transposed;
2091 
2092     assert(sl == [[0, 0, 0, 0],
2093                   [1, 1, 1, 1],
2094                   [2, 2, 2, 2]]);
2095 }
2096 
2097 ///
2098 @safe pure nothrow version(mir_ndslice_test) unittest
2099 {
2100     import mir.ndslice.allocation;
2101 
2102     auto sl = iota([3], 6).slice;
2103     auto slC = sl.repeat(2, 3);
2104     sl[1] = 4;
2105     assert(slC == [[[6, 4, 8],
2106                     [6, 4, 8],
2107                     [6, 4, 8]],
2108                    [[6, 4, 8],
2109                     [6, 4, 8],
2110                     [6, 4, 8]]]);
2111 }
2112 
2113 ///
2114 @safe pure nothrow version(mir_ndslice_test) unittest
2115 {
2116     import mir.primitives: DeepElementType;
2117 
2118     auto sl = repeat(4.0, 2, 3);
2119     assert(sl == [[4.0, 4.0, 4.0],
2120                   [4.0, 4.0, 4.0]]);
2121 
2122     static assert(is(DeepElementType!(typeof(sl)) == double));
2123 
2124     sl[1, 1] = 3;
2125     assert(sl == [[3.0, 3.0, 3.0],
2126                   [3.0, 3.0, 3.0]]);
2127 }
2128 
2129 /++
2130 Cycle repeates 1-dimensional field/range/array/slice in a fixed length 1-dimensional slice.
2131 +/
2132 auto cycle(Field)(Field field, size_t loopLength, size_t length)
2133     if (!isSlice!Field && !is(Field : T[], T))
2134 {
2135     return CycleField!Field(loopLength, field).slicedField(length);
2136 }
2137 
2138 /// ditto
2139 auto cycle(size_t loopLength, Field)(Field field, size_t length)
2140     if (!isSlice!Field && !is(Field : T[], T))
2141 {
2142     static assert(loopLength);
2143     return CycleField!(Field, loopLength)(field).slicedField(length);
2144 }
2145 
2146 /// ditto
2147 auto cycle(Iterator, SliceKind kind)(Slice!(Iterator, 1, kind) slice, size_t length)
2148 {
2149     assert(slice.length);
2150     static if (kind == Universal)
2151         return slice.hideStride.cycle(length);
2152     else
2153         return CycleField!Iterator(slice._lengths[0], slice._iterator).slicedField(length);
2154 }
2155 
2156 /// ditto
2157 auto cycle(size_t loopLength, Iterator, SliceKind kind)(Slice!(Iterator, 1, kind) slice, size_t length)
2158 {
2159     static assert(loopLength);
2160     assert(loopLength <= slice.length);
2161     static if (kind == Universal)
2162         return slice.hideStride.cycle!loopLength(length);
2163     else
2164         return CycleField!(Iterator, loopLength)(slice._iterator).slicedField(length);
2165 }
2166 
2167 /// ditto
2168 auto cycle(T)(T[] array, size_t length)
2169 {
2170     return cycle(array.sliced, length);
2171 }
2172 
2173 /// ditto
2174 auto cycle(size_t loopLength, T)(T[] array, size_t length)
2175 {
2176     return cycle!loopLength(array.sliced, length);
2177 }
2178 
2179 /// ditto
2180 auto cycle(size_t loopLength, T)(T withAsSlice, size_t length)
2181     if (hasAsSlice!T)
2182 {
2183     return cycle!loopLength(withAsSlice.asSlice, length);
2184 }
2185 
2186 ///
2187 @safe pure nothrow version(mir_ndslice_test) unittest
2188 {
2189     auto slice = iota(3);
2190     assert(slice.cycle(7) == [0, 1, 2, 0, 1, 2, 0]);
2191     assert(slice.cycle!2(7) == [0, 1, 0, 1, 0, 1, 0]);
2192     assert([0, 1, 2].cycle(7) == [0, 1, 2, 0, 1, 2, 0]);
2193     assert([4, 3, 2, 1].cycle!4(7) == [4, 3, 2, 1, 4, 3, 2]);
2194 }
2195 
2196 /++
2197 Strides 1-dimensional slice.
2198 Params:
2199     slice = 1-dimensional unpacked slice.
2200     factor = positive stride size.
2201 Returns:
2202     Contiguous slice with strided iterator.
2203 See_also: $(SUBREF dynamic, strided)
2204 +/
2205 auto stride
2206     (Iterator, size_t N, SliceKind kind)
2207     (Slice!(Iterator, N, kind) slice, ptrdiff_t factor)
2208     if (N == 1)
2209 in
2210 {
2211     assert (factor > 0, "factor must be positive.");
2212 }
2213 do
2214 {
2215     static if (kind == Contiguous)
2216         return slice.universal.stride(factor);
2217     else
2218     {
2219         import mir.ndslice.dynamic: strided;
2220         return slice.strided!0(factor).hideStride;
2221     }
2222 }
2223 
2224 ///ditto
2225 template stride(size_t factor = 2)
2226     if (factor > 1)
2227 {
2228     auto stride
2229         (Iterator, size_t N, SliceKind kind)
2230         (Slice!(Iterator, N, kind) slice)
2231     {
2232         import core.lifetime: move;
2233         static if (N > 1)
2234         {
2235             return stride(slice.move.ipack!1.map!(.stride!factor));
2236         }
2237         else
2238         static if (kind == Contiguous)
2239         {
2240             immutable rem = slice._lengths[0] % factor;
2241             slice._lengths[0] /= factor;
2242             if (rem)
2243                 slice._lengths[0]++;
2244             return Slice!(StrideIterator!(Iterator, factor), 1, kind)(slice._structure, StrideIterator!(Iterator, factor)(move(slice._iterator)));
2245         }
2246         else
2247         {
2248             return .stride(slice.move, factor);
2249         }
2250     }
2251 
2252     /// ditto
2253     auto stride(T)(T[] array)
2254     {
2255         return stride(array.sliced);
2256     }
2257 
2258     /// ditto
2259     auto stride(T)(T withAsSlice)
2260         if (hasAsSlice!T)
2261     {
2262         return stride(withAsSlice.asSlice);
2263     }
2264 }
2265 
2266 /// ditto
2267 auto stride(T)(T[] array, ptrdiff_t factor)
2268 {
2269     return stride(array.sliced, factor);
2270 }
2271 
2272 /// ditto
2273 auto stride(T)(T withAsSlice, ptrdiff_t factor)
2274     if (hasAsSlice!T)
2275 {
2276     return stride(withAsSlice.asSlice, factor);
2277 }
2278 
2279 ///
2280 @safe pure nothrow @nogc version(mir_ndslice_test) unittest
2281 {
2282     auto slice = iota(6);
2283     static immutable str = [0, 2, 4];
2284     assert(slice.stride(2) == str); // runtime factor
2285     assert(slice.stride!2 == str); // compile time factor
2286     assert(slice.stride == str); // default compile time factor is 2
2287     assert(slice.universal.stride(2) == str);
2288 }
2289 
2290 /// ND-compile time
2291 @safe pure nothrow @nogc version(mir_ndslice_test) unittest
2292 {
2293     auto slice = iota(4, 6);
2294     static immutable str = [[0, 2, 4], [12, 14, 16]];
2295     assert(slice.stride!2 == str); // compile time factor
2296     assert(slice.stride == str); // default compile time factor is 2
2297 }
2298 
2299 /++
2300 Reverses order of iteration for all dimensions.
2301 Params:
2302     slice = slice, range, or array.
2303 Returns:
2304     Slice/range with reversed order of iteration for all dimensions.
2305 See_also: $(SUBREF dynamic, reversed), $(SUBREF dynamic, allReversed).
2306 +/
2307 auto retro
2308     (Iterator, size_t N, SliceKind kind)
2309     (Slice!(Iterator, N, kind) slice)
2310     @trusted
2311 {
2312     import core.lifetime: move;
2313     static if (kind == Contiguous || kind == Canonical)
2314     {
2315         size_t[slice.N] lengths;
2316         foreach (i; Iota!(slice.N))
2317             lengths[i] = slice._lengths[i];
2318         static if (slice.S)
2319         {
2320             sizediff_t[slice.S] strides;
2321             foreach (i; Iota!(slice.S))
2322                 strides[i] = slice._strides[i];
2323             alias structure = AliasSeq!(lengths, strides);
2324         }
2325         else
2326         {
2327             alias structure = lengths;
2328         }
2329         static if (is(Iterator : RetroIterator!It, It))
2330         {
2331             alias Ret = Slice!(It, N, kind);
2332             slice._iterator._iterator -= slice.lastIndex;
2333             return Ret(structure, slice._iterator._iterator.move);
2334         }
2335         else
2336         {
2337             alias Ret = Slice!(RetroIterator!Iterator, N, kind);
2338             slice._iterator += slice.lastIndex;
2339             return Ret(structure, RetroIterator!Iterator(slice._iterator.move));
2340         }
2341     }
2342     else
2343     {
2344         import mir.ndslice.dynamic: allReversed;
2345         return slice.move.allReversed;
2346     }
2347 }
2348 
2349 /// ditto
2350 auto retro(T)(T[] array)
2351 {
2352     return retro(array.sliced);
2353 }
2354 
2355 /// ditto
2356 auto retro(T)(T withAsSlice)
2357     if (hasAsSlice!T)
2358 {
2359     return retro(withAsSlice.asSlice);
2360 }
2361 
2362 /// ditto
2363 auto retro(Range)(Range r)
2364     if (!hasAsSlice!Range && !isSlice!Range && !is(Range : T[], T))
2365 {
2366     import std.traits: Unqual;
2367 
2368     static if (is(Unqual!Range == Range))
2369     {
2370         import core.lifetime: move;
2371         static if (is(Range : RetroRange!R, R))
2372         {
2373             return move(r._source);
2374         }
2375         else
2376         {
2377             return RetroRange!Range(move(r));
2378         }
2379     }
2380     else
2381     {
2382         return .retro!(Unqual!Range)(r);
2383     }
2384 }
2385 
2386 /// ditto
2387 struct RetroRange(Range)
2388 {
2389     import mir.primitives: hasLength;
2390 
2391     ///
2392     Range _source;
2393 
2394     private enum hasAccessByRef = __traits(compiles, &_source.front);
2395 
2396     @property 
2397     {
2398         bool empty()() const { return _source.empty; }
2399         static if (hasLength!Range)
2400             auto length()() const { return _source.length; }
2401         auto ref front()() { return _source.back; }
2402         auto ref back()() { return _source.front; }
2403         static if (__traits(hasMember, Range, "save"))
2404             auto save()() { return RetroRange(_source.save); }
2405         alias opDollar = length;
2406 
2407         static if (!hasAccessByRef)
2408         {
2409             import std.traits: ForeachType;
2410 
2411             void front()(ForeachType!R val)
2412             {
2413                 import mir.functional: forward;
2414                 _source.back = forward!val;
2415             }
2416 
2417             void back()(ForeachType!R val)
2418             {
2419                 import mir.functional: forward;
2420                 _source.front = forward!val;
2421             }
2422         }
2423     }
2424 
2425     void popFront()() { _source.popBack(); }
2426     void popBack()() { _source.popFront(); }
2427 
2428     static if (is(typeof(_source.moveBack())))
2429     auto moveFront()() { return _source.moveBack(); }
2430 
2431     static if (is(typeof(_source.moveFront())))
2432     auto moveBack()() { return _source.moveFront(); }
2433 }
2434 
2435 ///
2436 @safe pure nothrow @nogc version(mir_ndslice_test) unittest
2437 {
2438     auto slice = iota(2, 3);
2439     static immutable reversed = [[5, 4, 3], [2, 1, 0]];
2440     assert(slice.retro == reversed);
2441     assert(slice.canonical.retro == reversed);
2442     assert(slice.universal.retro == reversed);
2443 
2444     static assert(is(typeof(slice.retro.retro) == typeof(slice)));
2445     static assert(is(typeof(slice.canonical.retro.retro) == typeof(slice.canonical)));
2446     static assert(is(typeof(slice.universal.retro) == typeof(slice.universal)));
2447 }
2448 
2449 /// Ranges
2450 @safe pure nothrow @nogc version(mir_ndslice_test) unittest
2451 {
2452     import mir.algorithm.iteration: equal;
2453     import std.range: std_iota = iota;
2454 
2455     assert(std_iota(4).retro.equal(iota(4).retro));
2456     static assert(is(typeof(std_iota(4).retro.retro) == typeof(std_iota(4))));
2457 }
2458 
2459 /++
2460 Bitwise slice over an integral slice.
2461 Params:
2462     slice = a contiguous or canonical slice on top of integral iterator.
2463 Returns: A bitwise slice.
2464 +/
2465 auto bitwise
2466     (Iterator, size_t N, SliceKind kind, I = typeof(Iterator.init[size_t.init]))
2467     (Slice!(Iterator, N, kind) slice)
2468     if (__traits(isIntegral, I) && (kind != Universal || N == 1))
2469 {
2470     import core.lifetime: move;
2471     static if (kind == Universal)
2472     {
2473         return slice.move.flattened.bitwise;
2474     }
2475     else
2476     {
2477         static if (is(Iterator : FieldIterator!Field, Field))
2478         {
2479             enum simplified = true;
2480             alias It = FieldIterator!(BitField!Field);
2481         }
2482         else
2483         {
2484             enum simplified = false;
2485             alias It = FieldIterator!(BitField!Iterator);
2486         }
2487         alias Ret = Slice!(It, N, kind);
2488         auto structure_ = Ret._Structure.init;
2489         foreach(i; Iota!(Ret.N))
2490             structure_[0][i] = slice._lengths[i];
2491         structure_[0][$ - 1] *= I.sizeof * 8;
2492         foreach(i; Iota!(Ret.S))
2493             structure_[1][i] = slice._strides[i];
2494         static if (simplified)
2495             return Ret(structure_, It(slice._iterator._index * I.sizeof * 8, BitField!Field(slice._iterator._field.move)));
2496         else
2497             return Ret(structure_, It(0, BitField!Iterator(slice._iterator.move)));
2498     }
2499 }
2500 
2501 /// ditto
2502 auto bitwise(T)(T[] array)
2503 {
2504     return bitwise(array.sliced);
2505 }
2506 
2507 /// ditto
2508 auto bitwise(T)(T withAsSlice)
2509     if (hasAsSlice!T)
2510 {
2511     return bitwise(withAsSlice.asSlice);
2512 }
2513 
2514 ///
2515 @safe pure nothrow @nogc
2516 version(mir_ndslice_test) unittest
2517 {
2518     size_t[10] data;
2519     auto bits = data[].bitwise;
2520     assert(bits.length == data.length * size_t.sizeof * 8);
2521     bits[111] = true;
2522     assert(bits[111]);
2523 
2524     bits.popFront;
2525     assert(bits[110]);
2526     bits[] = true;
2527     bits[110] = false;
2528     bits = bits[10 .. $];
2529     assert(bits[100] == false);
2530 }
2531 
2532 @safe pure nothrow @nogc
2533 version(mir_ndslice_test) unittest
2534 {
2535     size_t[10] data;
2536     auto slice = FieldIterator!(size_t[])(0, data[]).sliced(10);
2537     slice.popFrontExactly(2);
2538     auto bits_normal = data[].sliced.bitwise;
2539     auto bits = slice.bitwise;
2540     assert(bits.length == (data.length - 2) * size_t.sizeof * 8);
2541     bits[111] = true;
2542     assert(bits[111]);
2543     assert(bits_normal[111 + size_t.sizeof * 2 * 8]);
2544     auto ubits = slice.universal.bitwise;
2545     assert(bits.map!"~a" == bits.map!"!a");
2546     static assert (is(typeof(bits.map!"~a") == typeof(bits.map!"!a")));
2547     assert(bits.map!"~a" == bits.map!"!!!a");
2548     static assert (!is(typeof(bits.map!"~a") == typeof(bits.map!"!!!a")));
2549     assert(bits == ubits);
2550 
2551     bits.popFront;
2552     assert(bits[110]);
2553     bits[] = true;
2554     bits[110] = false;
2555     bits = bits[10 .. $];
2556     assert(bits[100] == false);
2557 }
2558 
2559 /++
2560 Bitwise field over an integral field.
2561 Params:
2562     field = an integral field.
2563 Returns: A bitwise field.
2564 +/
2565 auto bitwiseField(Field, I = typeof(Field.init[size_t.init]))(Field field)
2566     if (__traits(isUnsigned, I))
2567 {
2568     import core.lifetime: move;
2569     return BitField!(Field, I)(field.move);
2570 }
2571 
2572 /++
2573 Bitpack slice over an integral slice.
2574 
2575 Bitpack is used to represent unsigned integer slice with fewer number of bits in integer binary representation.
2576 
2577 Params:
2578     pack = counts of bits in the integer.
2579     slice = a contiguous or canonical slice on top of integral iterator.
2580 Returns: A bitpack slice.
2581 +/
2582 auto bitpack
2583     (size_t pack, Iterator, size_t N, SliceKind kind, I = typeof(Iterator.init[size_t.init]))
2584     (Slice!(Iterator, N, kind) slice)
2585     if (__traits(isIntegral, I) && (kind == Contiguous || kind == Canonical) && pack > 1)
2586 {
2587     import core.lifetime: move;
2588     static if (is(Iterator : FieldIterator!Field, Field) && I.sizeof * 8 % pack == 0)
2589     {
2590         enum simplified = true;
2591         alias It = FieldIterator!(BitpackField!(Field, pack));
2592     }
2593     else
2594     {
2595         enum simplified = false;
2596         alias It = FieldIterator!(BitpackField!(Iterator, pack));
2597     }
2598     alias Ret = Slice!(It, N, kind);
2599     auto structure = Ret._Structure.init;
2600     foreach(i; Iota!(Ret.N))
2601         structure[0][i] = slice._lengths[i];
2602     structure[0][$ - 1] *= I.sizeof * 8;
2603     structure[0][$ - 1] /= pack;
2604     foreach(i; Iota!(Ret.S))
2605         structure[1][i] = slice._strides[i];
2606     static if (simplified)
2607         return Ret(structure, It(slice._iterator._index * I.sizeof * 8 / pack, BitpackField!(Field, pack)(slice._iterator._field.move)));
2608     else
2609         return Ret(structure, It(0, BitpackField!(Iterator, pack)(slice._iterator.move)));
2610 }
2611 
2612 /// ditto
2613 auto bitpack(size_t pack, T)(T[] array)
2614 {
2615     return bitpack!pack(array.sliced);
2616 }
2617 
2618 /// ditto
2619 auto bitpack(size_t pack, T)(T withAsSlice)
2620     if (hasAsSlice!T)
2621 {
2622     return bitpack!pack(withAsSlice.asSlice);
2623 }
2624 
2625 ///
2626 @safe pure nothrow @nogc
2627 version(mir_ndslice_test) unittest
2628 {
2629     size_t[10] data;
2630     // creates a packed unsigned integer slice with max allowed value equal to `2^^6 - 1 == 63`.
2631     auto packs = data[].bitpack!6;
2632     assert(packs.length == data.length * size_t.sizeof * 8 / 6);
2633     packs[$ - 1] = 24;
2634     assert(packs[$ - 1] == 24);
2635 
2636     packs.popFront;
2637     assert(packs[$ - 1] == 24);
2638 }
2639 
2640 /++
2641 Bytegroup slice over an integral slice.
2642 
2643 Groups existing slice into fixed length chunks and uses them as data store for destination type.
2644 
2645 Correctly handles scalar types on both little-endian and big-endian platforms.
2646 
2647 Params:
2648     group = count of iterator items used to store the destination type.
2649     DestinationType = deep element type of the result slice.
2650     slice = a contiguous or canonical slice.
2651 Returns: A bytegroup slice.
2652 +/
2653 Slice!(BytegroupIterator!(Iterator, group, DestinationType), N, kind)
2654 bytegroup
2655     (size_t group, DestinationType, Iterator, size_t N, SliceKind kind)
2656     (Slice!(Iterator, N, kind) slice)
2657     if ((kind == Contiguous || kind == Canonical) && group)
2658 {
2659     import core.lifetime: move;
2660     auto structure = slice._structure;
2661     structure[0][$ - 1] /= group;
2662     return typeof(return)(structure, BytegroupIterator!(Iterator, group, DestinationType)(slice._iterator.move));
2663 }
2664 
2665 /// ditto
2666 auto bytegroup(size_t pack, DestinationType, T)(T[] array)
2667 {
2668     return bytegroup!(pack, DestinationType)(array.sliced);
2669 }
2670 
2671 /// ditto
2672 auto bytegroup(size_t pack, DestinationType, T)(T withAsSlice)
2673     if (hasAsSlice!T)
2674 {
2675     return bytegroup!(pack, DestinationType)(withAsSlice.asSlice);
2676 }
2677 
2678 /// 24 bit integers
2679 @safe pure nothrow @nogc
2680 version(mir_ndslice_test) unittest
2681 {
2682     import mir.ndslice.slice: DeepElementType, sliced;
2683 
2684     ubyte[20] data;
2685     // creates a packed unsigned integer slice with max allowed value equal to `2^^6 - 1 == 63`.
2686     auto int24ar = data[].bytegroup!(3, int); // 24 bit integers
2687     assert(int24ar.length == data.length / 3);
2688 
2689     enum checkInt = ((1 << 20) - 1);
2690 
2691     int24ar[3] = checkInt;
2692     assert(int24ar[3] == checkInt);
2693 
2694     int24ar.popFront;
2695     assert(int24ar[2] == checkInt);
2696 
2697     static assert(is(DeepElementType!(typeof(int24ar)) == int));
2698 }
2699 
2700 /// 48 bit integers
2701 @safe pure nothrow @nogc
2702 version(mir_ndslice_test) unittest
2703 {
2704     import mir.ndslice.slice: DeepElementType, sliced;
2705     ushort[20] data;
2706     // creates a packed unsigned integer slice with max allowed value equal to `2^^6 - 1 == 63`.
2707     auto int48ar = data[].sliced.bytegroup!(3, long); // 48 bit integers
2708     assert(int48ar.length == data.length / 3);
2709 
2710     enum checkInt = ((1L << 44) - 1);
2711 
2712     int48ar[3] = checkInt;
2713     assert(int48ar[3] == checkInt);
2714 
2715     int48ar.popFront;
2716     assert(int48ar[2] == checkInt);
2717 
2718     static assert(is(DeepElementType!(typeof(int48ar)) == long));
2719 }
2720 
2721 /++
2722 Implements the homonym function (also known as `transform`) present
2723 in many languages of functional flavor. The call `map!(fun)(slice)`
2724 returns a slice of which elements are obtained by applying `fun`
2725 for all elements in `slice`. The original slices are
2726 not changed. Evaluation is done lazily.
2727 
2728 Note:
2729     $(SUBREF dynamic, transposed) and
2730     $(SUBREF topology, pack) can be used to specify dimensions.
2731 Params:
2732     fun = One or more functions.
2733 See_Also:
2734     $(LREF cached), $(LREF vmap),  $(LREF rcmap), $(LREF indexed),
2735     $(LREF pairwise), $(LREF subSlices), $(LREF slide), $(LREF zip),
2736     $(HTTP en.wikipedia.org/wiki/Map_(higher-order_function), Map (higher-order function))
2737 +/
2738 template map(fun...)
2739     if (fun.length)
2740 {
2741     import mir.functional: adjoin, naryFun, pipe;
2742     static if (fun.length == 1)
2743     {
2744         static if (__traits(isSame, naryFun!(fun[0]), fun[0]))
2745         {
2746             alias f = fun[0];
2747         @fmamath:
2748             /++
2749             Params:
2750                 slice = An ndslice, array, or an input range.
2751             Returns:
2752                 ndslice or an input range with each fun applied to all the elements. If there is more than one
2753                 fun, the element type will be `Tuple` containing one element for each fun.
2754             +/
2755             auto map(Iterator, size_t N, SliceKind kind)
2756                 (Slice!(Iterator, N, kind) slice)
2757             {
2758                 import core.lifetime: move;
2759                 alias MIterator = typeof(_mapIterator!f(slice._iterator));
2760                 import mir.ndslice.traits: isIterator;
2761                 alias testIter = typeof(MIterator.init[0]);
2762                 static assert(isIterator!MIterator, "mir.ndslice.map: probably the lambda function contains a compile time bug.");
2763                 return Slice!(MIterator, N, kind)(slice._structure, _mapIterator!f(slice._iterator.move));
2764             }
2765 
2766             /// ditto
2767             auto map(T)(T[] array)
2768             {
2769                 return map(array.sliced);
2770             }
2771 
2772             /// ditto
2773             auto map(T)(T withAsSlice)
2774                 if (hasAsSlice!T)
2775             {
2776                 return map(withAsSlice.asSlice);
2777             }
2778 
2779             /// ditto
2780             auto map(Range)(Range r)
2781                 if (!hasAsSlice!Range && !isSlice!Range && !is(Range : T[], T))
2782             {
2783                 import core.lifetime: forward;
2784                 import mir.primitives: isInputRange;
2785                 static assert (isInputRange!Range, "map can work with ndslice, array, or an input range.");
2786                 return MapRange!(f, ImplicitlyUnqual!Range)(forward!r);
2787             }
2788         }
2789         else alias map = .map!(staticMap!(naryFun, fun));
2790     }
2791     else alias map = .map!(adjoin!fun);
2792 }
2793 
2794 /// ditto
2795 struct MapRange(alias fun, Range)
2796 {
2797     import std.range.primitives;
2798 
2799     Range _input;
2800 
2801     static if (isInfinite!Range)
2802     {
2803         enum bool empty = false;
2804     }
2805     else
2806     {
2807         bool empty() @property
2808         {
2809             return _input.empty;
2810         }
2811     }
2812 
2813     void popFront()
2814     {
2815         assert(!empty, "Attempting to popFront an empty map.");
2816         _input.popFront();
2817     }
2818 
2819     auto ref front() @property
2820     {
2821         assert(!empty, "Attempting to fetch the front of an empty map.");
2822         return fun(_input.front);
2823     }
2824 
2825     static if (isBidirectionalRange!Range)
2826     auto ref back()() @property
2827     {
2828         assert(!empty, "Attempting to fetch the back of an empty map.");
2829         return fun(_input.back);
2830     }
2831 
2832     static if (isBidirectionalRange!Range)
2833     void popBack()()
2834     {
2835         assert(!empty, "Attempting to popBack an empty map.");
2836         _input.popBack();
2837     }
2838 
2839     static if (hasLength!Range)
2840     auto length() @property
2841     {
2842         return _input.length;
2843     }
2844 
2845     static if (isForwardRange!Range)
2846     auto save()() @property
2847     {
2848         return typeof(this)(_input.save);
2849     }
2850 }
2851 
2852 ///
2853 @safe pure nothrow
2854 version(mir_ndslice_test) unittest
2855 {
2856     import mir.ndslice.topology : iota;
2857     auto s = iota(2, 3).map!(a => a * 3);
2858     assert(s == [[ 0,  3,  6],
2859                  [ 9, 12, 15]]);
2860 }
2861 
2862 /// String lambdas
2863 @safe pure nothrow
2864 version(mir_ndslice_test) unittest
2865 {
2866     import mir.ndslice.topology : iota;
2867     assert(iota(2, 3).map!"a * 2" == [[0, 2, 4], [6, 8, 10]]);
2868 }
2869 
2870 /// Input ranges
2871 @safe pure nothrow
2872 version(mir_ndslice_test) unittest
2873 {
2874     import mir.algorithm.iteration: filter, equal;
2875     assert (6.iota.filter!"a % 2".map!"a * 10".equal([10, 30, 50])); 
2876 }
2877 
2878 /// Packed tensors
2879 @safe pure nothrow
2880 version(mir_ndslice_test) unittest
2881 {
2882     import mir.ndslice.topology : iota, windows;
2883     import mir.math.sum: sum;
2884 
2885     //  iota        windows     map  sums ( reduce!"a + b" )
2886     //                --------------
2887     //  -------      |  ---    ---  |      ------
2888     // | 0 1 2 |  => || 0 1 || 1 2 ||  => | 8 12 |
2889     // | 3 4 5 |     || 3 4 || 4 5 ||      ------
2890     //  -------      |  ---    ---  |
2891     //                --------------
2892     auto s = iota(2, 3)
2893         .windows(2, 2)
2894         .map!sum;
2895 
2896     assert(s == [[8, 12]]);
2897 }
2898 
2899 /// Zipped tensors
2900 @safe pure nothrow
2901 version(mir_ndslice_test) unittest
2902 {
2903     import mir.ndslice.topology : iota, zip;
2904 
2905     // 0 1 2
2906     // 3 4 5
2907     auto sl1 = iota(2, 3);
2908     // 1 2 3
2909     // 4 5 6
2910     auto sl2 = iota([2, 3], 1);
2911 
2912     auto z = zip(sl1, sl2);
2913 
2914     assert(zip(sl1, sl2).map!"a + b" == sl1 + sl2);
2915     assert(zip(sl1, sl2).map!((a, b) => a + b) == sl1 + sl2);
2916 }
2917 
2918 /++
2919 Multiple functions can be passed to `map`.
2920 In that case, the element type of `map` is a tuple containing
2921 one element for each function.
2922 +/
2923 @safe pure nothrow
2924 version(mir_ndslice_test) unittest
2925 {
2926     import mir.ndslice.topology : iota;
2927 
2928     auto sl = iota(2, 3);
2929     auto s = sl.map!("a + a", "a * a");
2930 
2931     auto sums     = [[0, 2, 4], [6,  8, 10]];
2932     auto products = [[0, 1, 4], [9, 16, 25]];
2933 
2934     assert(s.map!"a[0]" == sl + sl);
2935     assert(s.map!"a[1]" == sl * sl);
2936 }
2937 
2938 /++
2939 `map` can be aliased to a symbol and be used separately:
2940 +/
2941 pure nothrow version(mir_ndslice_test) unittest
2942 {
2943     import mir.ndslice.topology : iota;
2944 
2945     alias halfs = map!"double(a) / 2";
2946     assert(halfs(iota(2, 3)) == [[0.0, 0.5, 1], [1.5, 2, 2.5]]);
2947 }
2948 
2949 /++
2950 Type normalization
2951 +/
2952 version(mir_ndslice_test) unittest
2953 {
2954     import mir.functional : pipe;
2955     import mir.ndslice.topology : iota;
2956     auto a = iota(2, 3).map!"a + 10".map!(pipe!("a * 2", "a + 1"));
2957     auto b = iota(2, 3).map!(pipe!("a + 10", "a * 2", "a + 1"));
2958     assert(a == b);
2959     static assert(is(typeof(a) == typeof(b)));
2960 }
2961 
2962 /// Use map with byDim/alongDim to apply functions to each dimension
2963 version(mir_ndslice_test)
2964 @safe pure
2965 unittest
2966 {
2967     import mir.ndslice.topology: byDim, alongDim;
2968     import mir.ndslice.fuse: fuse;
2969     import mir.math.stat: mean;
2970     import mir.algorithm.iteration: all;
2971     import mir.math.common: approxEqual;
2972 
2973     auto x = [
2974         [0.0, 1.0, 1.5, 2.0, 3.5, 4.25],
2975         [2.0, 7.5, 5.0, 1.0, 1.5, 0.0]
2976     ].fuse;
2977 
2978     // Use byDim/alongDim with map to compute mean of row/column.
2979     assert(x.byDim!0.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6]));
2980     assert(x.byDim!1.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125]));
2981     assert(x.alongDim!1.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6]));
2982     assert(x.alongDim!0.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125]));
2983 }
2984 
2985 /++
2986 Use map with a lambda and with byDim/alongDim, but may need to allocate result. 
2987 This example uses fuse, which allocates. Note: fuse!1 will transpose the result. 
2988 +/
2989 version(mir_ndslice_test)
2990 @safe pure
2991 unittest {
2992     import mir.ndslice.topology: iota, byDim, alongDim, map;
2993     import mir.ndslice.fuse: fuse;
2994     import mir.ndslice.slice: sliced;
2995     
2996     auto x = [1, 2, 3].sliced;
2997     auto y = [1, 2].sliced;
2998 
2999     auto s1 = iota(2, 3).byDim!0.map!(a => a * x).fuse;
3000     assert(s1 == [[ 0, 2,  6],
3001                   [ 3, 8, 15]]);
3002     auto s2 = iota(2, 3).byDim!1.map!(a => a * y).fuse!1;
3003     assert(s2 == [[ 0, 1,  2],
3004                   [ 6, 8, 10]]);
3005     auto s3 = iota(2, 3).alongDim!1.map!(a => a * x).fuse;
3006     assert(s1 == [[ 0, 2,  6],
3007                   [ 3, 8, 15]]);
3008     auto s4 = iota(2, 3).alongDim!0.map!(a => a * y).fuse!1;
3009     assert(s2 == [[ 0, 1,  2],
3010                   [ 6, 8, 10]]);
3011 }
3012 
3013 ///
3014 pure version(mir_ndslice_test) unittest
3015 {
3016     import mir.algorithm.iteration: reduce;
3017     import mir.math.common: fmax;
3018     import mir.math.stat: mean;
3019     import mir.math.sum;
3020     /// Returns maximal column average.
3021     auto maxAvg(S)(S matrix) {
3022         return reduce!fmax(0.0, matrix.alongDim!1.map!mean);
3023     }
3024     // 1 2
3025     // 3 4
3026     auto matrix = iota([2, 2], 1);
3027     assert(maxAvg(matrix) == 3.5);
3028 }
3029 
3030 /++
3031 Implements the homonym function (also known as `transform`) present
3032 in many languages of functional flavor. The call `slice.vmap(fun)`
3033 returns a slice of which elements are obtained by applying `fun`
3034 for all elements in `slice`. The original slices are
3035 not changed. Evaluation is done lazily.
3036 
3037 Note:
3038     $(SUBREF dynamic, transposed) and
3039     $(SUBREF topology, pack) can be used to specify dimensions.
3040 Params:
3041     slice = ndslice
3042     callable = callable object, structure, delegate, or function pointer.
3043 See_Also:
3044     $(LREF cached), $(LREF map),  $(LREF rcmap), $(LREF indexed),
3045     $(LREF pairwise), $(LREF subSlices), $(LREF slide), $(LREF zip),
3046     $(HTTP en.wikipedia.org/wiki/Map_(higher-order_function), Map (higher-order function))
3047 +/
3048 @fmamath auto vmap(Iterator, size_t N, SliceKind kind, Callable)
3049     (
3050         Slice!(Iterator, N, kind) slice,
3051         Callable callable,
3052     )
3053 {
3054     import core.lifetime: move;
3055     alias It = VmapIterator!(Iterator, Callable);
3056     return Slice!(It, N, kind)(slice._structure, It(slice._iterator.move, callable.move));
3057 }
3058 
3059 /// ditto
3060 auto vmap(T, Callable)(T[] array, Callable callable)
3061 {
3062     import core.lifetime: move;
3063     return vmap(array.sliced, callable.move);
3064 }
3065 
3066 /// ditto
3067 auto vmap(T, Callable)(T withAsSlice, Callable callable)
3068     if (hasAsSlice!T)
3069 {
3070     import core.lifetime: move;
3071     return vmap(withAsSlice.asSlice, callable.move);
3072 }
3073 
3074 ///
3075 @safe pure nothrow
3076 version(mir_ndslice_test) unittest
3077 {
3078     import mir.ndslice.topology : iota;
3079 
3080     static struct Mul {
3081         double factor; this(double f) { factor = f; }
3082         auto opCall(long x) const {return x * factor; }
3083         auto lightConst()() const @property { return Mul(factor); }
3084     }
3085 
3086     auto callable = Mul(3);
3087     auto s = iota(2, 3).vmap(callable);
3088 
3089     assert(s == [[ 0,  3,  6],
3090                  [ 9, 12, 15]]);
3091 }
3092 
3093 /// Packed tensors.
3094 @safe pure nothrow
3095 version(mir_ndslice_test) unittest
3096 {
3097     import mir.math.sum: sum;
3098     import mir.ndslice.topology : iota, windows;
3099 
3100     //  iota        windows     vmap  scaled sums
3101     //                --------------
3102     //  -------      |  ---    ---  |      -----
3103     // | 0 1 2 |  => || 0 1 || 1 2 ||  => | 4 6 |
3104     // | 3 4 5 |     || 3 4 || 4 5 ||      -----
3105     //  -------      |  ---    ---  |
3106     //                --------------
3107 
3108     struct Callable
3109     {
3110         double factor;
3111         this(double f) {factor = f;}
3112         auto opCall(S)(S x) { return x.sum * factor; }
3113 
3114         auto lightConst()() const @property { return Callable(factor); }
3115         auto lightImmutable()() immutable @property { return Callable(factor); }
3116     }
3117 
3118     auto callable = Callable(0.5);
3119 
3120     auto s = iota(2, 3)
3121         .windows(2, 2)
3122         .vmap(callable);
3123 
3124     assert(s == [[4, 6]]);
3125 }
3126 
3127 /// Zipped tensors
3128 @safe pure nothrow
3129 version(mir_ndslice_test) unittest
3130 {
3131     import mir.ndslice.topology : iota, zip;
3132 
3133     struct Callable
3134     {
3135         double factor;
3136         this(double f) {factor = f;}
3137         auto opCall(S, T)(S x, T y) { return x + y * factor; }
3138 
3139         auto lightConst()() const { return Callable(factor); }
3140         auto lightImmutable()() immutable { return Callable(factor); }
3141     }
3142 
3143     auto callable = Callable(10);
3144 
3145     // 0 1 2
3146     // 3 4 5
3147     auto sl1 = iota(2, 3);
3148     // 1 2 3
3149     // 4 5 6
3150     auto sl2 = iota([2, 3], 1);
3151 
3152     auto z = zip(sl1, sl2);
3153 
3154     assert(zip(sl1, sl2).vmap(callable) ==
3155             [[10,  21,  32],
3156              [43,  54,  65]]);
3157 }
3158 
3159 // TODO
3160 /+
3161 Multiple functions can be passed to `vmap`.
3162 In that case, the element type of `vmap` is a tuple containing
3163 one element for each function.
3164 +/
3165 @safe pure nothrow
3166 version(none) version(mir_ndslice_test) unittest
3167 {
3168     import mir.ndslice.topology : iota;
3169 
3170     auto s = iota(2, 3).vmap!("a + a", "a * a");
3171 
3172     auto sums     = [[0, 2, 4], [6,  8, 10]];
3173     auto products = [[0, 1, 4], [9, 16, 25]];
3174 
3175     foreach (i; 0..s.length!0)
3176     foreach (j; 0..s.length!1)
3177     {
3178         auto values = s[i, j];
3179         assert(values.a == sums[i][j]);
3180         assert(values.b == products[i][j]);
3181     }
3182 }
3183 
3184 /// Use vmap with byDim/alongDim to apply functions to each dimension
3185 version(mir_ndslice_test)
3186 @safe pure
3187 unittest
3188 {
3189     import mir.ndslice.fuse: fuse;
3190     import mir.math.stat: mean;
3191     import mir.algorithm.iteration: all;
3192     import mir.math.common: approxEqual;
3193 
3194     auto x = [
3195         [0.0, 1.0, 1.5, 2.0, 3.5, 4.25],
3196         [2.0, 7.5, 5.0, 1.0, 1.5, 0.0]
3197     ].fuse;
3198 
3199     static struct Callable
3200     {
3201         double factor;
3202         this(double f) {factor = f;}
3203         auto opCall(U)(U x) const {return x.mean + factor; }
3204         auto lightConst()() const @property { return Callable(factor); }
3205     }
3206 
3207     auto callable = Callable(0.0);
3208 
3209     // Use byDim/alongDim with map to compute callable of row/column.
3210     assert(x.byDim!0.vmap(callable).all!approxEqual([12.25 / 6, 17.0 / 6]));
3211     assert(x.byDim!1.vmap(callable).all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125]));
3212     assert(x.alongDim!1.vmap(callable).all!approxEqual([12.25 / 6, 17.0 / 6]));
3213     assert(x.alongDim!0.vmap(callable).all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125]));
3214 }
3215 
3216 /++
3217 Use vmap with a lambda and with byDim/alongDim, but may need to allocate result. 
3218 This example uses fuse, which allocates. Note: fuse!1 will transpose the result. 
3219 +/
3220 version(mir_ndslice_test)
3221 @safe pure
3222 unittest {
3223     import mir.ndslice.topology: iota, alongDim, map;
3224     import mir.ndslice.fuse: fuse;
3225     import mir.ndslice.slice: sliced;
3226 
3227     static struct Mul(T)
3228     {
3229         T factor;
3230         this(T f) { factor = f; }
3231         auto opCall(U)(U x) {return x * factor; }
3232         auto lightConst()() const @property { return Mul!(typeof(factor.lightConst))(factor.lightConst); }
3233     }
3234 
3235     auto a = [1, 2, 3].sliced;
3236     auto b = [1, 2].sliced;
3237     auto A = Mul!(typeof(a))(a);
3238     auto B = Mul!(typeof(b))(b);
3239 
3240     auto x = [
3241         [0, 1, 2],
3242         [3, 4, 5]
3243     ].fuse;
3244 
3245     auto s1 = x.byDim!0.vmap(A).fuse;
3246     assert(s1 == [[ 0, 2,  6],
3247                   [ 3, 8, 15]]);
3248     auto s2 = x.byDim!1.vmap(B).fuse!1;
3249     assert(s2 == [[ 0, 1,  2],
3250                   [ 6, 8, 10]]);
3251     auto s3 = x.alongDim!1.vmap(A).fuse;
3252     assert(s1 == [[ 0, 2,  6],
3253                   [ 3, 8, 15]]);
3254     auto s4 = x.alongDim!0.vmap(B).fuse!1;
3255     assert(s2 == [[ 0, 1,  2],
3256                   [ 6, 8, 10]]);
3257 }
3258 
3259 private auto hideStride
3260     (Iterator, SliceKind kind)
3261     (Slice!(Iterator, 1, kind) slice)
3262 {
3263     import core.lifetime: move;
3264     static if (kind == Universal)
3265         return Slice!(StrideIterator!Iterator)(
3266             slice._lengths,
3267             StrideIterator!Iterator(slice._strides[0], move(slice._iterator)));
3268     else
3269         return slice;
3270 }
3271 
3272 private auto unhideStride
3273     (Iterator, size_t N, SliceKind kind)
3274     (Slice!(Iterator, N, kind) slice)
3275 {
3276     static if (is(Iterator : StrideIterator!It, It))
3277     {
3278         import core.lifetime: move;
3279         static if (kind == Universal)
3280         {
3281             alias Ret = SliceKind!(It, N, Universal);
3282             auto strides = slice._strides;
3283             foreach(i; Iota!(Ret.S))
3284                 strides[i] = slice._strides[i] * slice._iterator._stride;
3285             return Slice!(It, N, Universal)(slice._lengths, strides, slice._iterator._iterator.move);
3286         }
3287         else
3288             return slice.move.universal.unhideStride;
3289     }
3290     else
3291         return slice;
3292 }
3293 
3294 /++
3295 Implements the homonym function (also known as `transform`) present
3296 in many languages of functional flavor. The call `rmap!(fun)(slice)`
3297 returns an RC array (1D) or  RC slice (ND) of which elements are obtained by applying `fun`
3298 for all elements in `slice`. The original slices are
3299 not changed. Evaluation is done eagerly.
3300 
3301 Note:
3302     $(SUBREF dynamic, transposed) and
3303     $(SUBREF topology, pack) can be used to specify dimensions.
3304 Params:
3305     fun = One or more functions.
3306 See_Also:
3307     $(LREF cached), $(LREF map), $(LREF vmap), $(LREF indexed),
3308     $(LREF pairwise), $(LREF subSlices), $(LREF slide), $(LREF zip),
3309     $(HTTP en.wikipedia.org/wiki/Map_(higher-order_function), Map (higher-order function))
3310 +/
3311 template rcmap(fun...)
3312     if (fun.length)
3313 {
3314     import mir.functional: adjoin, naryFun, pipe;
3315     static if (fun.length == 1)
3316     {
3317         static if (__traits(isSame, naryFun!(fun[0]), fun[0]))
3318         {
3319             alias f = fun[0];
3320         @fmamath:
3321             /++
3322             Params:
3323                 slice = An ndslice, array, or an input range.
3324             Returns:
3325                 ndslice or an input range with each fun applied to all the elements. If there is more than one
3326                 fun, the element type will be `Tuple` containing one element for each fun.
3327             +/
3328             auto rcmap(Iterator, size_t N, SliceKind kind)
3329                 (Slice!(Iterator, N, kind) slice)
3330             {
3331                 import core.lifetime: move;
3332                 auto shape = slice.shape;
3333                 auto r = slice.move.flattened;
3334                 if (false)
3335                 {
3336                     auto e = f(r.front);
3337                     r.popFront;
3338                     auto d = r.empty;
3339                 }
3340                 return () @trusted
3341                 {
3342                     import mir.rc.array: RCArray;
3343                     import std.traits: Unqual;
3344                     import mir.conv: emplaceRef;
3345 
3346                     alias T = typeof(f(r.front));
3347                     auto ret = RCArray!T(r.length);
3348                     auto next = ret.ptr;
3349                     while (!r.empty)
3350                     {
3351                         emplaceRef(*cast(Unqual!T*)next++, f(r.front));
3352                         r.popFront;
3353                     }
3354                     static if (N == 1)
3355                     {
3356                         return ret;
3357                     }
3358                     else
3359                     {
3360                         return ret.moveToSlice.sliced(shape);
3361                     }
3362                 } ();
3363             }
3364 
3365             /// ditto
3366             auto rcmap(T)(T[] array)
3367             {
3368                 return rcmap(array.sliced);
3369             }
3370 
3371             /// ditto
3372             auto rcmap(T)(T withAsSlice)
3373                 if (hasAsSlice!T)
3374             {
3375                 static if (__traits(hasMember, T, "moveToSlice"))
3376                     return rcmap(withAsSlice.moveToSlice);
3377                 else
3378                     return rcmap(withAsSlice.asSlice);
3379             }
3380 
3381             /// ditto
3382             auto rcmap(Range)(Range r)
3383                 if (!hasAsSlice!Range && !isSlice!Range && !is(Range : T[], T))
3384             {
3385                 import core.lifetime: forward;
3386                 import mir.appender: scopedBuffer;
3387                 import mir.primitives: isInputRange;
3388                 import mir.rc.array: RCArray;
3389                 alias T = typeof(f(r.front));
3390                 auto buffer = scopedBuffer!T;
3391                 while (!r.empty)
3392                 {
3393                     buffer.put(f(r.front));
3394                     r.popFront;
3395                 }
3396                 return () @trusted
3397                 {
3398                     auto ret = RCArray!T(buffer.length, false);
3399                     buffer.moveDataAndEmplaceTo(ret[]);
3400                     return ret;
3401                 } ();
3402             }
3403         }
3404         else alias rcmap = .rcmap!(staticMap!(naryFun, fun));
3405     }
3406     else alias rcmap = .rcmap!(adjoin!fun);
3407 }
3408 
3409 /// Returns RCArray for input ranges and one-dimensional slices.
3410 @safe pure nothrow @nogc
3411 version(mir_ndslice_test) unittest
3412 {
3413     import mir.algorithm.iteration: filter, equal;
3414     auto factor = 10;
3415     auto step = 20;
3416     assert (3.iota.rcmap!(a => a * factor).moveToSlice.equal(3.iota * factor)); 
3417     assert (6.iota.filter!"a % 2".rcmap!(a => a * factor).moveToSlice.equal([3].iota(factor, step))); 
3418 }
3419 
3420 /// For multidimensional case returns `Slice!(RCI!T, N)`.
3421 @safe pure nothrow @nogc
3422 version(mir_ndslice_test) unittest
3423 {
3424     import mir.ndslice.topology : iota;
3425     auto factor = 3;
3426     auto s = iota(2, 3).rcmap!(a => a * factor);
3427     assert(s == iota(2, 3) * factor);
3428 }
3429 
3430 /// String lambdas
3431 @safe pure nothrow
3432 version(mir_ndslice_test) unittest
3433 {
3434     import mir.ndslice.topology : iota;
3435     assert(iota(2, 3).rcmap!"a * 2" == iota(2, 3) * 2);
3436 }
3437 
3438 @safe pure nothrow @nogc
3439 version(mir_ndslice_test) unittest
3440 {
3441     import mir.algorithm.iteration: filter, equal;
3442     auto factor = 10;
3443     auto step = 20;
3444     assert (3.iota.as!(const int).rcmap!(a => a * factor).moveToSlice.equal(3.iota * factor)); 
3445     assert (6.iota.filter!"a % 2".as!(immutable int).rcmap!(a => a * factor).moveToSlice.equal([3].iota(factor, step))); 
3446 }
3447 
3448 /++
3449 Creates a random access cache for lazyly computed elements.
3450 Params:
3451     original = original ndslice
3452     caches = cached values
3453     flags = array composed of flags that indicates if values are already computed
3454 Returns:
3455     ndslice, which is internally composed of three ndslices: `original`, allocated cache and allocated bit-ndslice.
3456 See_also: $(LREF cachedGC), $(LREF map), $(LREF vmap), $(LREF indexed)
3457 +/
3458 Slice!(CachedIterator!(Iterator, CacheIterator, FlagIterator), N, kind)
3459     cached(Iterator, SliceKind kind, size_t N, CacheIterator, FlagIterator)(
3460         Slice!(Iterator, N, kind) original,
3461         Slice!(CacheIterator, N, kind) caches,
3462         Slice!(FlagIterator, N, kind) flags,
3463     )
3464 {
3465     assert(original.shape == caches.shape, "caches.shape should be equal to original.shape");
3466     assert(original.shape == flags.shape, "flags.shape should be equal to original.shape");
3467     return typeof(return)(
3468         original._structure,
3469         IteratorOf!(typeof(return))(
3470             original._iterator,
3471             caches._iterator,
3472             flags._iterator,
3473         ));
3474 }
3475 
3476 ///
3477 @safe pure nothrow
3478 version(mir_ndslice_test) unittest
3479 {
3480     import mir.ndslice.topology: cached, iota, map;
3481     import mir.ndslice.allocation: bitSlice, uninitSlice;
3482 
3483     int[] funCalls;
3484 
3485     auto v = 5.iota!int
3486         .map!((i) {
3487             funCalls ~= i;
3488             return 2 ^^ i;
3489         });
3490     auto flags = v.length.bitSlice;
3491     auto cache = v.length.uninitSlice!int;
3492     // cached lazy slice: 1 2 4 8 16
3493     auto sl = v.cached(cache, flags);
3494 
3495     assert(funCalls == []);
3496     assert(sl[1] == 2); // remember result
3497     assert(funCalls == [1]);
3498     assert(sl[1] == 2); // reuse result
3499     assert(funCalls == [1]);
3500 
3501     assert(sl[0] == 1);
3502     assert(funCalls == [1, 0]);
3503     funCalls = [];
3504 
3505     // set values directly
3506     sl[1 .. 3] = 5;
3507     assert(sl[1] == 5);
3508     assert(sl[2] == 5);
3509     // no function calls
3510     assert(funCalls == []);
3511 }
3512 
3513 /// Cache of immutable elements
3514 @safe pure nothrow
3515 version(mir_ndslice_test) unittest
3516 {
3517     import mir.ndslice.slice: DeepElementType;
3518     import mir.ndslice.topology: cached, iota, map, as;
3519     import mir.ndslice.allocation: bitSlice, uninitSlice;
3520 
3521     int[] funCalls;
3522 
3523     auto v = 5.iota!int
3524         .map!((i) {
3525             funCalls ~= i;
3526             return 2 ^^ i;
3527         })
3528         .as!(immutable int);
3529     auto flags = v.length.bitSlice;
3530     auto cache = v.length.uninitSlice!(immutable int);
3531 
3532     // cached lazy slice: 1 2 4 8 16
3533     auto sl = v.cached(cache, flags);
3534 
3535     static assert(is(DeepElementType!(typeof(sl)) == immutable int));
3536 
3537     assert(funCalls == []);
3538     assert(sl[1] == 2); // remember result
3539     assert(funCalls == [1]);
3540     assert(sl[1] == 2); // reuse result
3541     assert(funCalls == [1]);
3542 
3543     assert(sl[0] == 1);
3544     assert(funCalls == [1, 0]);
3545 }
3546 
3547 /++
3548 Creates a random access cache for lazyly computed elements.
3549 Params:
3550     original = ND Contiguous or 1D Universal ndslice.
3551 Returns:
3552     ndslice, which is internally composed of three ndslices: `original`, allocated cache and allocated bit-ndslice.
3553 See_also: $(LREF cached), $(LREF map), $(LREF vmap), $(LREF indexed)
3554 +/
3555 Slice!(CachedIterator!(Iterator, typeof(Iterator.init[0])*, FieldIterator!(BitField!(size_t*))), N)
3556     cachedGC(Iterator, size_t N)(Slice!(Iterator, N) original) @trusted
3557 {
3558     import std.traits: hasElaborateAssign, Unqual;
3559     import mir.ndslice.allocation: bitSlice, slice, uninitSlice;
3560     alias C = typeof(Iterator.init[0]);
3561     alias UC = Unqual!C;
3562     static if (hasElaborateAssign!UC)
3563         alias newSlice = slice;
3564     else
3565         alias newSlice = uninitSlice;
3566     return typeof(return)(
3567         original._structure,
3568         IteratorOf!(typeof(return))(
3569             original._iterator,
3570             newSlice!C(original._lengths)._iterator,
3571             original._lengths.bitSlice._iterator,
3572             ));
3573 }
3574 
3575 /// ditto
3576 auto cachedGC(Iterator)(Slice!(Iterator,  1, Universal) from)
3577 {
3578     return from.flattened.cachedGC;
3579 }
3580 
3581 /// ditto
3582 auto cachedGC(T)(T withAsSlice)
3583     if (hasAsSlice!T)
3584 {
3585     return cachedGC(withAsSlice.asSlice);
3586 }
3587 
3588 ///
3589 @safe pure nothrow
3590 version(mir_ndslice_test) unittest
3591 {
3592     import mir.ndslice.topology: cachedGC, iota, map;
3593 
3594     int[] funCalls;
3595 
3596     // cached lazy slice: 1 2 4 8 16
3597     auto sl = 5.iota!int
3598         .map!((i) {
3599             funCalls ~= i;
3600             return 2 ^^ i;
3601         })
3602         .cachedGC;
3603 
3604     assert(funCalls == []);
3605     assert(sl[1] == 2); // remember result
3606     assert(funCalls == [1]);
3607     assert(sl[1] == 2); // reuse result
3608     assert(funCalls == [1]);
3609 
3610     assert(sl[0] == 1);
3611     assert(funCalls == [1, 0]);
3612     funCalls = [];
3613 
3614     // set values directly
3615     sl[1 .. 3] = 5;
3616     assert(sl[1] == 5);
3617     assert(sl[2] == 5);
3618     // no function calls
3619     assert(funCalls == []);
3620 }
3621 
3622 /// Cache of immutable elements
3623 @safe pure nothrow
3624 version(mir_ndslice_test) unittest
3625 {
3626     import mir.ndslice.slice: DeepElementType;
3627     import mir.ndslice.topology: cachedGC, iota, map, as;
3628 
3629     int[] funCalls;
3630 
3631     // cached lazy slice: 1 2 4 8 16
3632     auto sl = 5.iota!int
3633         .map!((i) {
3634             funCalls ~= i;
3635             return 2 ^^ i;
3636         })
3637         .as!(immutable int)
3638         .cachedGC;
3639 
3640     static assert(is(DeepElementType!(typeof(sl)) == immutable int));
3641 
3642     assert(funCalls == []);
3643     assert(sl[1] == 2); // remember result
3644     assert(funCalls == [1]);
3645     assert(sl[1] == 2); // reuse result
3646     assert(funCalls == [1]);
3647 
3648     assert(sl[0] == 1);
3649     assert(funCalls == [1, 0]);
3650 }
3651 
3652 /++
3653 Convenience function that creates a lazy view,
3654 where each element of the original slice is converted to the type `T`.
3655 It uses $(LREF  map) and $(REF_ALTTEXT $(TT to), to, mir,conv)$(NBSP)
3656 composition under the hood.
3657 Params:
3658     slice = a slice to create a view on.
3659 Returns:
3660     A lazy slice with elements converted to the type `T`.
3661 See_also: $(LREF map), $(LREF vmap)
3662 +/
3663 template as(T)
3664 {
3665     ///
3666     @fmamath auto as(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice)
3667     {
3668         static if (is(slice.DeepElement == T))
3669             return slice;
3670         else
3671         static if (is(Iterator : T*))
3672             return slice.toConst;
3673         else
3674         {
3675             import core.lifetime: move;
3676             import mir.conv: to;
3677             return map!(to!T)(slice.move);
3678         }
3679     }
3680 
3681     /// ditto
3682     auto as(S)(S[] array)
3683     {
3684         return as(array.sliced);
3685     }
3686 
3687     /// ditto
3688     auto as(S)(S withAsSlice)
3689         if (hasAsSlice!S)
3690     {
3691         return as(withAsSlice.asSlice);
3692     }
3693 
3694     /// ditto
3695     auto as(Range)(Range r)
3696         if (!hasAsSlice!Range && !isSlice!Range && !is(Range : T[], T))
3697     {
3698         static if (is(ForeachType!Range == T))
3699             return r;
3700         else
3701         {
3702             import core.lifetime: move;
3703             import mir.conv: to;
3704             return map!(to!T)(r.move);
3705         }
3706     }
3707 }
3708 
3709 ///
3710 @safe pure nothrow version(mir_ndslice_test) unittest
3711 {
3712     import mir.ndslice.slice: Slice;
3713     import mir.ndslice.allocation : slice;
3714     import mir.ndslice.topology : diagonal, as;
3715 
3716     auto matrix = slice!double([2, 2], 0);
3717     auto stringMatrixView = matrix.as!int;
3718     assert(stringMatrixView ==
3719             [[0, 0],
3720              [0, 0]]);
3721 
3722     matrix.diagonal[] = 1;
3723     assert(stringMatrixView ==
3724             [[1, 0],
3725              [0, 1]]);
3726 
3727     /// allocate new slice composed of strings
3728     Slice!(int*, 2) stringMatrix = stringMatrixView.slice;
3729 }
3730 
3731 /// Special behavior for pointers to a constant data.
3732 @safe pure nothrow version(mir_ndslice_test) unittest
3733 {
3734     import mir.ndslice.allocation : slice;
3735     import mir.ndslice.slice: Contiguous, Slice;
3736 
3737     Slice!(double*, 2)              matrix = slice!double([2, 2], 0);
3738     Slice!(const(double)*, 2) const_matrix = matrix.as!(const double);
3739 }
3740 
3741 /// Ranges
3742 @safe pure nothrow version(mir_ndslice_test) unittest
3743 {
3744     import mir.algorithm.iteration: filter, equal;
3745     assert(5.iota.filter!"a % 2".as!double.map!"a / 2".equal([0.5, 1.5]));
3746 }
3747 
3748 /++
3749 Takes a field `source` and a slice `indices`, and creates a view of source as if its elements were reordered according to indices.
3750 `indices` may include only a subset of the elements of `source` and may also repeat elements.
3751 
3752 Params:
3753     source = a filed, source of data. `source` must be an array or a pointer, or have `opIndex` primitive. Full random access range API is not required.
3754     indices = a slice, source of indices.
3755 Returns:
3756     n-dimensional slice with the same kind, shape and strides.
3757 
3758 See_also: `indexed` is similar to $(LREF vmap), but a field (`[]`) is used instead of a function (`()`), and order of arguments is reversed.
3759 +/
3760 Slice!(IndexIterator!(Iterator, Field), N, kind)
3761     indexed(Field, Iterator, size_t N, SliceKind kind)
3762     (Field source, Slice!(Iterator, N, kind) indices)
3763 {
3764     import core.lifetime: move;
3765     return typeof(return)(
3766             indices._structure,
3767             IndexIterator!(Iterator, Field)(
3768                 indices._iterator.move,
3769                 source));
3770 }
3771 
3772 /// ditto
3773 auto indexed(Field, S)(Field source, S[] indices)
3774 {
3775     return indexed(source, indices.sliced);
3776 }
3777 
3778 /// ditto
3779 auto indexed(Field, S)(Field source, S indices)
3780     if (hasAsSlice!S)
3781 {
3782     return indexed(source, indices.asSlice);
3783 }
3784 
3785 ///
3786 @safe pure nothrow version(mir_ndslice_test) unittest
3787 {
3788     auto source = [1, 2, 3, 4, 5];
3789     auto indices = [4, 3, 1, 2, 0, 4];
3790     auto ind = source.indexed(indices);
3791     assert(ind == [5, 4, 2, 3, 1, 5]);
3792 
3793     assert(ind.retro == source.indexed(indices.retro));
3794 
3795     ind[3] += 10; // for index 2
3796     //                0  1   2  3  4
3797     assert(source == [1, 2, 13, 4, 5]);
3798 }
3799 
3800 /++
3801 Maps index pairs to subslices.
3802 Params:
3803     sliceable = pointer, array, ndslice, series, or something sliceable with `[a .. b]`.
3804     slices = ndslice composed of index pairs.
3805 Returns:
3806     ndslice composed of subslices.
3807 See_also: $(LREF chopped), $(LREF pairwise).
3808 +/
3809 Slice!(SubSliceIterator!(Iterator, Sliceable), N, kind)
3810     subSlices(Iterator, size_t N, SliceKind kind, Sliceable)(
3811         Sliceable sliceable,
3812         Slice!(Iterator, N, kind) slices,
3813     )
3814 {
3815     import core.lifetime: move;
3816     return typeof(return)(
3817         slices._structure,
3818         SubSliceIterator!(Iterator, Sliceable)(slices._iterator.move, sliceable.move)
3819     );
3820 }
3821 
3822 /// ditto
3823 auto subSlices(S, Sliceable)(Sliceable sliceable, S[] slices)
3824 {
3825     return subSlices(sliceable, slices.sliced);
3826 }
3827 
3828 /// ditto
3829 auto subSlices(S, Sliceable)(Sliceable sliceable, S slices)
3830     if (hasAsSlice!S)
3831 {
3832     return subSlices(sliceable, slices.asSlice);
3833 }
3834 
3835 ///
3836 @safe pure version(mir_ndslice_test) unittest
3837 {
3838     import mir.functional: staticArray;
3839     auto subs =[
3840             staticArray(2, 4),
3841             staticArray(2, 10),
3842         ];
3843     auto sliceable = 10.iota;
3844 
3845     auto r = sliceable.subSlices(subs);
3846     assert(r == [
3847         iota([4 - 2], 2),
3848         iota([10 - 2], 2),
3849         ]);
3850 }
3851 
3852 /++
3853 Maps index pairs to subslices.
3854 Params:
3855     bounds = ndslice composed of consequent (`a_i <= a_(i+1)`) pairwise index bounds.
3856     sliceable = pointer, array, ndslice, series, or something sliceable with `[a_i .. a_(i+1)]`.
3857 Returns:
3858     ndslice composed of subslices.
3859 See_also: $(LREF pairwise), $(LREF subSlices).
3860 +/
3861 Slice!(ChopIterator!(Iterator, Sliceable)) chopped(Iterator, Sliceable)(
3862         Sliceable sliceable,
3863         Slice!Iterator bounds,
3864     )
3865 in
3866 {
3867     debug(mir)
3868         foreach(b; bounds.pairwise!"a <= b")
3869             assert(b);
3870 }
3871 do {
3872     import core.lifetime: move;
3873     sizediff_t length = bounds._lengths[0] <= 1 ? 0 : bounds._lengths[0] - 1;
3874     static if (hasLength!Sliceable)
3875     {
3876         if (length && bounds[length - 1] > sliceable.length)
3877         {
3878             version (D_Exceptions)
3879                 { import mir.exception : toMutable; throw choppedException.toMutable; }
3880             else
3881                assert(0, choppedExceptionMsg);
3882         }
3883     }
3884 
3885     return typeof(return)([size_t(length)], ChopIterator!(Iterator, Sliceable)(bounds._iterator.move, sliceable.move));
3886 }
3887 
3888 /// ditto
3889 auto chopped(S, Sliceable)(Sliceable sliceable, S[] bounds)
3890 {
3891     return chopped(sliceable, bounds.sliced);
3892 }
3893 
3894 /// ditto
3895 auto chopped(S, Sliceable)(Sliceable sliceable, S bounds)
3896     if (hasAsSlice!S)
3897 {
3898     return chopped(sliceable, bounds.asSlice);
3899 }
3900 
3901 ///
3902 @safe pure version(mir_ndslice_test) unittest
3903 {
3904     import mir.functional: staticArray;
3905     import mir.ndslice.slice: sliced;
3906     auto pairwiseIndexes = [2, 4, 10].sliced;
3907     auto sliceable = 10.iota;
3908 
3909     auto r = sliceable.chopped(pairwiseIndexes);
3910     assert(r == [
3911         iota([4 - 2], 2),
3912         iota([10 - 4], 4),
3913         ]);
3914 }
3915 
3916 /++
3917 Groups slices into a slice of refTuples. The slices must have identical strides or be 1-dimensional.
3918 Params:
3919     sameStrides = if `true` assumes that all slices has the same strides.
3920     slices = list of slices
3921 Returns:
3922     n-dimensional slice of elements tuple
3923 See_also: $(SUBREF slice, Slice.strides).
3924 +/
3925 template zip(bool sameStrides = false)
3926 {
3927     /++
3928     Groups slices into a slice of refTuples. The slices must have identical strides or be 1-dimensional.
3929     Params:
3930         slices = list of slices
3931     Returns:
3932         n-dimensional slice of elements tuple
3933     See_also: $(SUBREF slice, Slice.strides).
3934     +/
3935     @fmamath
3936     auto zip(Slices...)(Slices slices)
3937         if (Slices.length > 1 && allSatisfy!(isConvertibleToSlice, Slices))
3938     {
3939         static if (allSatisfy!(isSlice, Slices))
3940         {
3941             enum N = Slices[0].N;
3942             foreach(i, S; Slices[1 .. $])
3943             {
3944                 static assert(S.N == N, "zip: all Slices must have the same dimension count");
3945                 assert(slices[i + 1]._lengths == slices[0]._lengths, "zip: all slices must have the same lengths");
3946                 static if (sameStrides)
3947                     assert(slices[i + 1].strides == slices[0].strides, "zip: all slices must have the same strides when unpacked");
3948             }
3949             static if (!sameStrides && minElem(staticMap!(kindOf, Slices)) != Contiguous)
3950             {
3951                 static assert(N == 1, "zip: cannot zip canonical and universal multidimensional slices if `sameStrides` is false");
3952                 mixin(`return .zip(` ~ _iotaArgs!(Slices.length, "slices[", "].hideStride, ") ~`);`);
3953             }
3954             else
3955             {
3956                 enum kind = maxElem(staticMap!(kindOf, Slices));
3957                 alias Iterator = ZipIterator!(staticMap!(_IteratorOf, Slices));
3958                 alias Ret = Slice!(Iterator, N, kind);
3959                 auto structure = Ret._Structure.init;
3960                 structure[0] = slices[0]._lengths;
3961                 foreach (i; Iota!(Ret.S))
3962                     structure[1][i] = slices[0]._strides[i];
3963                 return Ret(structure, mixin("Iterator(" ~ _iotaArgs!(Slices.length, "slices[", "]._iterator, ") ~ ")"));
3964             }
3965         }
3966         else
3967         {
3968             return .zip(toSlices!slices);
3969         }
3970     }
3971 }
3972 
3973 ///
3974 @safe pure nothrow version(mir_ndslice_test) unittest
3975 {
3976     import mir.ndslice.allocation : slice;
3977     import mir.ndslice.topology : flattened, iota;
3978 
3979     auto alpha = iota!int(4, 3);
3980     auto beta = slice!int(4, 3).universal;
3981 
3982     auto m = zip!true(alpha, beta);
3983     foreach (r; m)
3984         foreach (e; r)
3985             e.b = e.a;
3986     assert(alpha == beta);
3987 
3988     beta[] = 0;
3989     foreach (e; m.flattened)
3990         e.b = cast(int)e.a;
3991     assert(alpha == beta);
3992 }
3993 
3994 @safe pure nothrow version(mir_ndslice_test) unittest
3995 {
3996     import mir.ndslice.allocation : slice;
3997     import mir.ndslice.topology : flattened, iota;
3998 
3999     auto alpha = iota!int(4).universal;
4000     auto beta = new int[4];
4001 
4002     auto m = zip(alpha, beta);
4003     foreach (e; m)
4004         e.b = e.a;
4005     assert(alpha == beta);
4006 }
4007 
4008 /++
4009 Selects a slice from a zipped slice.
4010 Params:
4011     name = name of a slice to unzip.
4012     slice = zipped slice
4013 Returns:
4014     unzipped slice
4015 +/
4016 auto unzip
4017     (char name, size_t N, SliceKind kind, Iterators...)
4018     (Slice!(ZipIterator!Iterators, N, kind) slice)
4019 {
4020     import core.lifetime: move;
4021     enum size_t i = name - 'a';
4022     static assert(i < Iterators.length, `unzip: constraint: size_t(name - 'a') < Iterators.length`);
4023     return Slice!(Iterators[i], N, kind)(slice._structure, slice._iterator._iterators[i].move).unhideStride;
4024 }
4025 
4026 /// ditto
4027 auto unzip
4028     (char name, size_t N, SliceKind kind, Iterators...)
4029     (ref Slice!(ZipIterator!Iterators, N, kind) slice)
4030 {
4031     enum size_t i = name - 'a';
4032     static assert(i < Iterators.length, `unzip: constraint: size_t(name - 'a') < Iterators.length`);
4033     return Slice!(Iterators[i], N, kind)(slice._structure, slice._iterator._iterators[i]).unhideStride;
4034 }
4035 
4036 ///
4037 pure nothrow version(mir_ndslice_test) unittest
4038 {
4039     import mir.ndslice.allocation : slice;
4040     import mir.ndslice.topology : iota;
4041 
4042     auto alpha = iota!int(4, 3);
4043     auto beta = iota!int([4, 3], 1).slice;
4044 
4045     auto m = zip(alpha, beta);
4046 
4047     static assert(is(typeof(unzip!'a'(m)) == typeof(alpha)));
4048     static assert(is(typeof(unzip!'b'(m)) == typeof(beta)));
4049 
4050     assert(m.unzip!'a' == alpha);
4051     assert(m.unzip!'b' == beta);
4052 }
4053 
4054 private enum TotalDim(NdFields...) = [staticMap!(DimensionCount, NdFields)].sum;
4055 
4056 private template applyInner(alias fun, size_t N)
4057 {
4058     static if (N == 0)
4059         alias applyInner = fun;
4060     else
4061     {
4062         import mir.functional: pipe;
4063         alias applyInner = pipe!(zip!true, map!(.applyInner!(fun, N - 1)));
4064     }
4065 }
4066 
4067 /++
4068 Lazy convolution for tensors.
4069 
4070 Suitable for advanced convolution algorithms.
4071 
4072 Params:
4073     params = convolution windows length.
4074     fun = one dimensional convolution function with `params` arity.
4075     SDimensions = dimensions to perform lazy convolution along. Negative dimensions are supported.
4076 See_also: $(LREF slide), $(LREF pairwise), $(LREF diff).
4077 +/
4078 template slideAlong(size_t params, alias fun, SDimensions...)
4079     if (params <= 'z' - 'a' + 1 && SDimensions.length > 0)
4080 {
4081     import mir.functional: naryFun;
4082 
4083     static if (allSatisfy!(isSizediff_t, SDimensions) && params > 1 && __traits(isSame, naryFun!fun, fun))
4084     {
4085     @fmamath:
4086         /++
4087         Params: slice = ndslice or array
4088         Returns: lazy convolution result
4089         +/
4090         auto slideAlong(Iterator, size_t N, SliceKind kind)
4091             (Slice!(Iterator, N, kind) slice)
4092         {
4093             import core.lifetime: move;
4094             static if (N > 1 && kind == Contiguous)
4095             {
4096                 return slideAlong(slice.move.canonical);
4097             }
4098             else
4099             static if (N == 1 && kind == Universal)
4100             {
4101                 return slideAlong(slice.move.flattened);
4102             }
4103             else
4104             {
4105                 alias Dimensions = staticMap!(ShiftNegativeWith!N, SDimensions);
4106                 enum dimension = Dimensions[$ - 1];
4107                 size_t len = slice._lengths[dimension] - (params - 1);
4108                 if (sizediff_t(len) <= 0) // overfow
4109                     len = 0;
4110                 slice._lengths[dimension] = len;
4111                 static if (dimension + 1 == N || kind == Universal)
4112                 {
4113                     alias I = SlideIterator!(Iterator, params, fun);
4114                     auto ret = Slice!(I, N, kind)(slice._structure, I(move(slice._iterator)));
4115                 }
4116                 else
4117                 {
4118                     alias Z = ZipIterator!(Repeat!(params, Iterator));
4119                     Z z;
4120                     foreach_reverse (p; Iota!(1, params))
4121                         z._iterators[p] = slice._iterator + slice._strides[dimension] * p;
4122                     z._iterators[0] = move(slice._iterator);
4123                     alias M = MapIterator!(Z, fun);
4124                     auto ret = Slice!(M, N, kind)(slice._structure, M(move(z)));
4125                 }
4126                 static if (Dimensions.length == 1)
4127                 {
4128                     return ret;
4129                 }
4130                 else
4131                 {
4132                     return .slideAlong!(params, fun, Dimensions[0 .. $ - 1])(ret);
4133                 }
4134             }
4135         }
4136 
4137         /// ditto
4138         auto slideAlong(S)(S[] slice)
4139         {
4140             return slideAlong(slice.sliced);
4141         }
4142 
4143         /// ditto
4144         auto slideAlong(S)(S slice)
4145             if (hasAsSlice!S)
4146         {
4147             return slideAlong(slice.asSlice);
4148         }
4149     }
4150     else
4151     static if (params == 1)
4152         alias slideAlong = .map!(naryFun!fun);
4153     else alias slideAlong = .slideAlong!(params, naryFun!fun, staticMap!(toSizediff_t, SDimensions));
4154 }
4155 
4156 ///
4157 @safe pure nothrow @nogc version(mir_ndslice_test) unittest
4158 {
4159     auto data = [4, 5].iota;
4160 
4161     alias scaled = a => a * 0.25;
4162 
4163     auto v = data.slideAlong!(3, "a + 2 * b + c", 0).map!scaled;
4164     auto h = data.slideAlong!(3, "a + 2 * b + c", 1).map!scaled;
4165 
4166     assert(v == [4, 5].iota[1 .. $ - 1, 0 .. $]);
4167     assert(h == [4, 5].iota[0 .. $, 1 .. $ - 1]);
4168 }
4169 
4170 /++
4171 Lazy convolution for tensors.
4172 
4173 Suitable for simple convolution algorithms.
4174 
4175 Params:
4176     params = windows length.
4177     fun = one dimensional convolution function with `params` arity.
4178 See_also: $(LREF slideAlong), $(LREF withNeighboursSum), $(LREF pairwise), $(LREF diff).
4179 +/
4180 template slide(size_t params, alias fun)
4181     if (params <= 'z' - 'a' + 1)
4182 {
4183     import mir.functional: naryFun;
4184 
4185     static if (params > 1 && __traits(isSame, naryFun!fun, fun))
4186     {
4187     @fmamath:
4188         /++
4189         Params: slice = ndslice or array
4190         Returns: lazy convolution result
4191         +/
4192         auto slide(Iterator, size_t N, SliceKind kind)
4193             (Slice!(Iterator, N, kind) slice)
4194         {
4195             import core.lifetime: move;
4196             return slice.move.slideAlong!(params, fun, Iota!N);
4197         }
4198 
4199         /// ditto
4200         auto slide(S)(S[] slice)
4201         {
4202             return slide(slice.sliced);
4203         }
4204 
4205         /// ditto
4206         auto slide(S)(S slice)
4207             if (hasAsSlice!S)
4208         {
4209             return slide(slice.asSlice);
4210         }
4211     }
4212     else
4213     static if (params == 1)
4214         alias slide = .map!(naryFun!fun);
4215     else alias slide = .slide!(params, naryFun!fun);
4216 }
4217 
4218 ///
4219 version(mir_ndslice_test) unittest
4220 {
4221     auto data = 10.iota;
4222     auto sw = data.slide!(3, "a + 2 * b + c");
4223 
4224     import mir.utility: max;
4225     assert(sw.length == max(0, cast(ptrdiff_t)data.length - 3 + 1));
4226     assert(sw == sw.length.iota.map!"(a + 1) * 4");
4227     assert(sw == [4, 8, 12, 16, 20, 24, 28, 32]);
4228 }
4229 
4230 /++
4231 ND-use case
4232 +/
4233 @safe pure nothrow @nogc version(mir_ndslice_test) unittest
4234 {
4235     auto data = [4, 5].iota;
4236 
4237     enum factor = 1.0 / 4 ^^ data.shape.length;
4238     alias scaled = a => a * factor;
4239 
4240     auto sw = data.slide!(3, "a + 2 * b + c").map!scaled;
4241 
4242     assert(sw == [4, 5].iota[1 .. $ - 1, 1 .. $ - 1]);
4243 }
4244 
4245 /++
4246 Pairwise map for tensors.
4247 
4248 The computation is performed on request, when the element is accessed.
4249 
4250 Params:
4251     fun = function to accumulate
4252     lag = an integer indicating which lag to use
4253 Returns: lazy ndslice composed of `fun(a_n, a_n+1)` values.
4254 
4255 See_also: $(LREF slide), $(LREF slideAlong), $(LREF subSlices).
4256 +/
4257 alias pairwise(alias fun, size_t lag = 1) = slide!(lag + 1, fun);
4258 
4259 ///
4260 @safe pure nothrow version(mir_ndslice_test) unittest
4261 {
4262     import mir.ndslice.slice: sliced;
4263     assert([2, 4, 3, -1].sliced.pairwise!"a + b" == [6, 7, 2]);
4264 }
4265 
4266 /// N-dimensional
4267 @safe pure nothrow
4268 version(mir_ndslice_test) unittest
4269 {
4270     // performs pairwise along each dimension
4271     // 0 1 2 3
4272     // 4 5 6 7
4273     // 8 9 10 11
4274     assert([3, 4].iota.pairwise!"a + b" == [[10, 14, 18], [26, 30, 34]]);
4275 }
4276 
4277 /++
4278 Differences between tensor elements.
4279 
4280 The computation is performed on request, when the element is accessed.
4281 
4282 Params:
4283     lag = an integer indicating which lag to use
4284 Returns: lazy differences.
4285 
4286 See_also: $(LREF slide), $(LREF slide).
4287 +/
4288 alias diff(size_t lag = 1) = pairwise!(('a' + lag) ~ " - a", lag);
4289 
4290 ///
4291 version(mir_ndslice_test) unittest
4292 {
4293     import mir.ndslice.slice: sliced;
4294     assert([2, 4, 3, -1].sliced.diff == [2, -1, -4]);
4295 }
4296 
4297 /// N-dimensional
4298 @safe pure nothrow @nogc
4299 version(mir_ndslice_test) unittest
4300 {
4301     // 0 1 2 3
4302     // 4 5 6 7     =>
4303     // 8 9 10 11
4304     
4305     // 1 1 1
4306     // 1 1 1      =>
4307     // 1 1 1
4308 
4309     // 0 0 0
4310     // 0 0 0
4311 
4312     assert([3, 4].iota.diff == repeat(0, [2, 3]));
4313 }
4314 
4315 /// packed slices
4316 version(mir_ndslice_test) unittest
4317 {
4318     // 0 1  2  3
4319     // 4 5  6  7
4320     // 8 9 10 11
4321     auto s = iota(3, 4);
4322     assert(iota(3, 4).byDim!0.diff == [
4323         [4, 4, 4, 4],
4324         [4, 4, 4, 4]]);
4325     assert(iota(3, 4).byDim!1.diff == [
4326         [1, 1, 1],
4327         [1, 1, 1],
4328         [1, 1, 1]]);
4329 }
4330 
4331 /++
4332 Drops borders for all dimensions.
4333 
4334 Params:
4335     slice = ndslice
4336 Returns:
4337     Tensors with striped borders
4338 See_also:
4339     $(LREF universal),
4340     $(LREF assumeCanonical),
4341     $(LREF assumeContiguous).
4342 +/
4343 Slice!(Iterator, N, N > 1 && kind == Contiguous ? Canonical : kind, Labels)
4344     dropBorders
4345     (Iterator, size_t N, SliceKind kind, Labels...)
4346     (Slice!(Iterator, N, kind, Labels) slice)
4347 {
4348     static if (N > 1 && kind == Contiguous)
4349     {
4350         import core.lifetime: move;
4351         auto ret = slice.move.canonical;
4352     }
4353     else
4354     {
4355         alias ret = slice;
4356     }
4357     ret.popFrontAll;
4358     ret.popBackAll;
4359     return ret;
4360 }
4361 
4362 ///
4363 version(mir_ndslice_test) unittest
4364 {
4365     assert([4, 5].iota.dropBorders == [[6, 7, 8], [11, 12, 13]]);
4366 }
4367 
4368 /++
4369 Lazy zip view of elements packed with sum of their neighbours.
4370 
4371 Params:
4372     fun = neighbours accumulation function.
4373 See_also: $(LREF slide), $(LREF slideAlong).
4374 +/
4375 template withNeighboursSum(alias fun = "a + b")
4376 {
4377     import mir.functional: naryFun;
4378 
4379     static if (__traits(isSame, naryFun!fun, fun))
4380     {
4381     @fmamath:
4382         /++
4383         Params:
4384             slice = ndslice or array
4385         Returns:
4386             Lazy zip view of elements packed with sum of their neighbours.
4387         +/
4388         auto withNeighboursSum(Iterator, size_t N, SliceKind kind)
4389             (Slice!(Iterator, N, kind) slice)
4390         {
4391             import core.lifetime: move;
4392             static if (N > 1 && kind == Contiguous)
4393             {
4394                 return withNeighboursSum(slice.move.canonical);
4395             }
4396             else
4397             static if (N == 1 && kind == Universal)
4398             {
4399                 return withNeighboursSum(slice.move.flattened);
4400             }
4401             else
4402             {
4403                 enum around = kind != Universal;
4404                 alias Z = NeighboursIterator!(Iterator, N - around, fun, around);
4405 
4406                 size_t shift;
4407                 foreach (dimension; Iota!N)
4408                 {
4409                     slice._lengths[dimension] -= 2;
4410                     if (sizediff_t(slice._lengths[dimension]) <= 0) // overfow
4411                         slice._lengths[dimension] = 0;
4412                     shift += slice._stride!dimension;
4413                 }                
4414 
4415                 Z z;
4416                 z._iterator = move(slice._iterator);
4417                 z._iterator += shift;
4418                 foreach (dimension; Iota!(N - around))
4419                 {
4420                     z._neighbours[dimension][0] = z._iterator - slice._strides[dimension];
4421                     z._neighbours[dimension][1] = z._iterator + slice._strides[dimension];
4422                 }
4423                 return Slice!(Z, N, kind)(slice._structure, move(z));
4424             }
4425         }
4426 
4427         /// ditto
4428         auto withNeighboursSum(S)(S[] slice)
4429         {
4430             return withNeighboursSum(slice.sliced);
4431         }
4432 
4433         /// ditto
4434         auto withNeighboursSum(S)(S slice)
4435             if (hasAsSlice!S)
4436         {
4437             return withNeighboursSum(slice.asSlice);
4438         }
4439     }
4440     else alias withNeighboursSum = .withNeighboursSum!(naryFun!fun);
4441 }
4442 
4443 ///
4444 @safe pure nothrow @nogc version(mir_ndslice_test) unittest
4445 {
4446     import mir.ndslice.allocation: slice;
4447     import mir.algorithm.iteration: all;
4448 
4449     auto wn = [4, 5].iota.withNeighboursSum;
4450     assert(wn.all!"a[0] == a[1] * 0.25");
4451     assert(wn.map!"a" == wn.map!"b * 0.25");
4452 }
4453 
4454 @safe pure nothrow @nogc version(mir_ndslice_test) unittest
4455 {
4456     import mir.ndslice.allocation: slice;
4457     import mir.algorithm.iteration: all;
4458 
4459     auto wn = [4, 5].iota.withNeighboursSum.universal;
4460     assert(wn.all!"a[0] == a[1] * 0.25");
4461     assert(wn.map!"a" == wn.map!"b * 0.25");
4462 }
4463 
4464 /++
4465 Cartesian product.
4466 
4467 Constructs lazy cartesian product $(SUBREF slice, Slice) without memory allocation.
4468 
4469 Params:
4470     fields = list of fields with lengths or ndFields with shapes
4471 Returns: $(SUBREF ndfield, Cartesian)`!NdFields(fields).`$(SUBREF slice, slicedNdField)`;`
4472 +/
4473 auto cartesian(NdFields...)(NdFields fields)
4474     if (NdFields.length > 1 && allSatisfy!(templateOr!(hasShape, hasLength), NdFields))
4475 {
4476     return Cartesian!NdFields(fields).slicedNdField;
4477 }
4478 
4479 /// 1D x 1D
4480 version(mir_ndslice_test) unittest
4481 {
4482     auto a = [10, 20, 30];
4483     auto b = [ 1,  2,  3];
4484 
4485     auto c = cartesian(a, b)
4486         .map!"a + b";
4487 
4488     assert(c == [
4489         [11, 12, 13],
4490         [21, 22, 23],
4491         [31, 32, 33]]);
4492 }
4493 
4494 /// 1D x 2D
4495 version(mir_ndslice_test) unittest
4496 {
4497     auto a = [10, 20, 30];
4498     auto b = iota([2, 3], 1);
4499 
4500     auto c = cartesian(a, b)
4501         .map!"a + b";
4502 
4503     assert(c.shape == [3, 2, 3]);
4504 
4505     assert(c == [
4506         [
4507             [11, 12, 13],
4508             [14, 15, 16],
4509         ],
4510         [
4511             [21, 22, 23],
4512             [24, 25, 26],
4513         ],
4514         [
4515             [31, 32, 33],
4516             [34, 35, 36],
4517         ]]);
4518 }
4519 
4520 /// 1D x 1D x 1D
4521 version(mir_ndslice_test) unittest
4522 {
4523     auto u = [100, 200];
4524     auto v = [10, 20, 30];
4525     auto w = [1, 2];
4526 
4527     auto c = cartesian(u, v, w)
4528         .map!"a + b + c";
4529 
4530     assert(c.shape == [2, 3, 2]);
4531 
4532     assert(c == [
4533         [
4534             [111, 112],
4535             [121, 122],
4536             [131, 132],
4537         ],
4538         [
4539             [211, 212],
4540             [221, 222],
4541             [231, 232],
4542         ]]);
4543 }
4544 
4545 /++
4546 $(LINK2 https://en.wikipedia.org/wiki/Kronecker_product,  Kronecker product).
4547 
4548 Constructs lazy kronecker product $(SUBREF slice, Slice) without memory allocation.
4549 +/
4550 template kronecker(alias fun = product)
4551 {
4552     import mir.functional: naryFun;
4553     static if (__traits(isSame, naryFun!fun, fun))
4554 
4555     /++
4556     Params:
4557         fields = list of either fields with lengths or ndFields with shapes.
4558             All ndFields must have the same dimension count.
4559     Returns:
4560         $(SUBREF ndfield, Kronecker)`!(fun, NdFields)(fields).`$(SUBREF slice, slicedNdField)
4561     +/
4562     @fmamath auto kronecker(NdFields...)(NdFields fields)
4563         if (allSatisfy!(hasShape, NdFields) || allSatisfy!(hasLength, NdFields))
4564     {
4565         return Kronecker!(fun, NdFields)(fields).slicedNdField;
4566     }
4567     else
4568         alias kronecker = .kronecker!(naryFun!fun);
4569 }
4570 
4571 /// 2D
4572 version(mir_ndslice_test) unittest
4573 {
4574     import mir.ndslice.allocation: slice;
4575     import mir.ndslice.slice: sliced;
4576 
4577     // eye
4578     auto a = slice!double([4, 4], 0);
4579     a.diagonal[] = 1;
4580 
4581     auto b = [ 1, -1,
4582               -1,  1].sliced(2, 2);
4583 
4584     auto c = kronecker(a, b);
4585 
4586     assert(c == [
4587         [ 1, -1,  0,  0,  0,  0,  0,  0],
4588         [-1,  1,  0,  0,  0,  0,  0,  0],
4589         [ 0,  0,  1, -1,  0,  0,  0,  0],
4590         [ 0,  0, -1,  1,  0,  0,  0,  0],
4591         [ 0,  0,  0,  0,  1, -1,  0,  0],
4592         [ 0,  0,  0,  0, -1,  1,  0,  0],
4593         [ 0,  0,  0,  0,  0,  0,  1, -1],
4594         [ 0,  0,  0,  0,  0,  0, -1,  1]]);
4595 }
4596 
4597 /// 1D
4598 version(mir_ndslice_test) unittest
4599 {
4600     auto a = iota([3], 1);
4601 
4602     auto b = [ 1, -1];
4603 
4604     auto c = kronecker(a, b);
4605 
4606     assert(c == [1, -1, 2, -2, 3, -3]);
4607 }
4608 
4609 /// 2D with 3 arguments
4610 version(mir_ndslice_test) unittest
4611 {
4612     import mir.ndslice.allocation: slice;
4613     import mir.ndslice.slice: sliced;
4614 
4615     auto a = [ 1,  2,
4616                3,  4].sliced(2, 2);
4617 
4618     auto b = [ 1,  0,
4619                0,  1].sliced(2, 2);
4620 
4621     auto c = [ 1, -1,
4622               -1,  1].sliced(2, 2);
4623 
4624     auto d = kronecker(a, b, c);
4625 
4626     assert(d == [
4627         [ 1, -1,  0,  0,  2, -2,  0,  0],
4628         [-1,  1,  0,  0, -2,  2,  0,  0],
4629         [ 0,  0,  1, -1,  0,  0,  2, -2],
4630         [ 0,  0, -1,  1,  0,  0, -2,  2],
4631         [ 3, -3,  0,  0,  4, -4,  0,  0],
4632         [-3,  3,  0,  0, -4,  4,  0,  0],
4633         [ 0,  0,  3, -3,  0,  0,  4, -4],
4634         [ 0,  0, -3,  3,  0,  0, -4,  4]]);
4635 }
4636 
4637 /++
4638 $(HTTPS en.wikipedia.org/wiki/Magic_square, Magic square).
4639 Params:
4640     length = square matrix length.
4641 Returns:
4642     Lazy magic matrix.
4643 +/
4644 auto magic(size_t length)
4645 {
4646     assert(length > 0);
4647     static if (is(size_t == ulong))
4648         assert(length <= uint.max);
4649     else
4650         assert(length <= ushort.max);
4651     import mir.ndslice.field: MagicField;
4652     return MagicField(length).slicedField(length, length);
4653 }
4654 
4655 ///
4656 @safe pure nothrow
4657 version(mir_ndslice_test) unittest
4658 {
4659     import mir.math.sum;
4660     import mir.ndslice: slice, magic, byDim, map, as, repeat, diagonal, antidiagonal;
4661 
4662     bool isMagic(S)(S matrix)
4663     {
4664         auto n = matrix.length;
4665         auto c = n * (n * n + 1) / 2; // magic number
4666         return // check shape
4667             matrix.length!0 > 0 && matrix.length!0 == matrix.length!1
4668             && // each row sum should equal magic number
4669             matrix.byDim!0.map!sum == c.repeat(n)
4670             && // each columns sum should equal magic number
4671             matrix.byDim!1.map!sum == c.repeat(n)
4672             && // diagonal sum should equal magic number
4673             matrix.diagonal.sum == c
4674             && // antidiagonal sum should equal magic number
4675             matrix.antidiagonal.sum == c;
4676     }
4677 
4678     assert(isMagic(magic(1)));
4679     assert(!isMagic(magic(2))); // 2x2 magic square does not exist
4680     foreach(n; 3 .. 24)
4681         assert(isMagic(magic(n)));
4682     assert(isMagic(magic(3).as!double.slice));
4683 }
4684 
4685 /++
4686 Chops 1D input slice into n chunks with ascending or descending lengths.
4687 
4688 `stairs` can be used to pack and unpack symmetric and triangular matrix storage.
4689 
4690 Note: `stairs` is defined for 1D (packet) input and 2D (general) input.
4691     This part of documentation is for 1D input.
4692 
4693 Params:
4694     type = $(UL
4695         $(LI `"-"` for stairs with lengths `n, n-1, ..., 1`.)
4696         $(LI `"+"` for stairs with lengths `1, 2, ..., n`;)
4697         )
4698     slice = input slice with length equal to `n * (n + 1) / 2`
4699     n = stairs count
4700 Returns:
4701     1D contiguous slice composed of 1D contiguous slices.
4702 
4703 See_also: $(LREF triplets) $(LREF ._stairs.2)
4704 +/
4705 Slice!(StairsIterator!(Iterator, type)) stairs(string type, Iterator)(Slice!Iterator slice, size_t n)
4706     if (type == "+" || type == "-")
4707 {
4708     assert(slice.length == (n + 1) * n / 2, "stairs: slice length must be equal to n * (n + 1) / 2, where n is stairs count.");
4709     static if (type == "+")
4710         size_t length = 1;
4711     else
4712         size_t length = n;
4713     return StairsIterator!(Iterator, type)(length, slice._iterator).sliced(n);
4714 }
4715 
4716 /// ditto
4717 Slice!(StairsIterator!(S*, type))  stairs(string type, S)(S[] slice, size_t n)
4718     if (type == "+" || type == "-")
4719 {
4720     return stairs!type(slice.sliced, n);
4721 }
4722 
4723 /// ditto
4724 auto stairs(string type, S)(S slice, size_t n)
4725     if (hasAsSlice!S && (type == "+" || type == "-"))
4726 {
4727     return stairs!type(slice.asSlice, n);
4728 }
4729 
4730 ///
4731 version(mir_ndslice_test) unittest
4732 {
4733     import mir.ndslice.topology: iota, stairs;
4734 
4735     auto pck = 15.iota;
4736     auto inc = pck.stairs!"+"(5);
4737     auto dec = pck.stairs!"-"(5);
4738 
4739     assert(inc == [
4740         [0],
4741         [1, 2],
4742         [3, 4, 5],
4743         [6, 7, 8, 9],
4744         [10, 11, 12, 13, 14]]);
4745     assert(inc[1 .. $][2] == [6, 7, 8, 9]);
4746 
4747     assert(dec == [
4748         [0, 1, 2, 3, 4],
4749            [5, 6, 7, 8],
4750             [9, 10, 11],
4751                [12, 13],
4752                    [14]]);
4753     assert(dec[1 .. $][2] == [12, 13]);
4754 
4755     static assert(is(typeof(inc.front) == typeof(pck)));
4756     static assert(is(typeof(dec.front) == typeof(pck)));
4757 }
4758 
4759 /++
4760 Slice composed of rows of lower or upper triangular matrix.
4761 
4762 `stairs` can be used to pack and unpack symmetric and triangular matrix storage.
4763 
4764 Note: `stairs` is defined for 1D (packet) input and 2D (general) input.
4765     This part of documentation is for 2D input.
4766 
4767 Params:
4768     type = $(UL
4769         $(LI `"+"` for stairs with lengths `1, 2, ..., n`, lower matrix;)
4770         $(LI `"-"` for stairs with lengths `n, n-1, ..., 1`, upper matrix.)
4771         )
4772     slice = input slice with length equal to `n * (n + 1) / 2`
4773 Returns:
4774     1D slice composed of 1D contiguous slices.
4775 
4776 See_also: $(LREF _stairs) $(SUBREF dynamic, transposed), $(LREF universal)
4777 +/
4778 auto stairs(string type, Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) slice)
4779     if (type == "+" || type == "-")
4780 {
4781     assert(slice.length!0 == slice.length!1, "stairs: input slice must be a square matrix.");
4782     static if (type == "+")
4783     {
4784         return slice
4785             .pack!1
4786             .map!"a"
4787             .zip([slice.length].iota!size_t(1))
4788             .map!"a[0 .. b]";
4789     }
4790     else
4791     {
4792         return slice
4793             .pack!1
4794             .map!"a"
4795             .zip([slice.length].iota!size_t)
4796             .map!"a[b .. $]";
4797     }
4798 }
4799 
4800 ///
4801 version(mir_ndslice_test) unittest
4802 {
4803     import mir.ndslice.topology: iota, as, stairs;
4804 
4805     auto gen = [3, 3].iota.as!double;
4806     auto inc = gen.stairs!"+";
4807     auto dec = gen.stairs!"-";
4808 
4809     assert(inc == [
4810         [0],
4811         [3, 4],
4812         [6, 7, 8]]);
4813 
4814     assert(dec == [
4815         [0, 1, 2],
4816            [4, 5],
4817               [8]]);
4818 
4819     static assert(is(typeof(inc.front) == typeof(gen.front)));
4820     static assert(is(typeof(dec.front) == typeof(gen.front)));
4821 
4822     /////////////////////////////////////////
4823     // Pack lower and upper matrix parts
4824     auto n = gen.length;
4825     auto m = n * (n + 1) / 2;
4826     // allocate memory
4827     import mir.ndslice.allocation: uninitSlice;
4828     auto lowerData = m.uninitSlice!double;
4829     auto upperData = m.uninitSlice!double;
4830     // construct packed stairs
4831     auto lower = lowerData.stairs!"+"(n);
4832     auto upper = upperData.stairs!"-"(n);
4833     // copy data
4834     import mir.algorithm.iteration: each;
4835     each!"a[] = b"(lower, inc);
4836     each!"a[] = b"(upper, dec);
4837 
4838     assert(&lower[0][0] is &lowerData[0]);
4839     assert(&upper[0][0] is &upperData[0]);
4840 
4841     assert(lowerData == [0, 3, 4, 6, 7, 8]);
4842     assert(upperData == [0, 1, 2, 4, 5, 8]);
4843 }
4844 
4845 /++
4846 Returns a slice that can be iterated along dimension. Transposes other dimensions on top and then packs them.
4847 
4848 Combines $(LREF byDim) and $(LREF evertPack).
4849 
4850 Params:
4851     SDimensions = dimensions to iterate along, length of d, `1 <= d < n`. Negative dimensions are supported.
4852 Returns:
4853     `(n-d)`-dimensional slice composed of d-dimensional slices
4854 See_also:
4855     $(LREF byDim),
4856     $(LREF iota),
4857     $(SUBREF allocation, slice),
4858     $(LREF ipack),
4859     $(SUBREF dynamic, transposed).
4860 +/
4861 template alongDim(SDimensions...)
4862     if (SDimensions.length > 0)
4863 {
4864     static if (allSatisfy!(isSizediff_t, SDimensions))
4865     {
4866         /++
4867         Params:
4868             slice = input n-dimensional slice, n > d
4869         Returns:
4870             `(n-d)`-dimensional slice composed of d-dimensional slices
4871         +/
4872         @fmamath auto alongDim(Iterator, size_t N, SliceKind kind)
4873             (Slice!(Iterator, N, kind) slice)
4874             if (N > SDimensions.length)
4875         {
4876             import core.lifetime: move;
4877             return slice.move.byDim!SDimensions.evertPack;
4878         }
4879     }
4880     else
4881     {
4882         alias alongDim = .alongDim!(staticMap!(toSizediff_t, SDimensions));
4883     }
4884 }
4885 
4886 /// 2-dimensional slice support
4887 @safe @nogc pure nothrow
4888 version(mir_ndslice_test) unittest
4889 {
4890     import mir.ndslice;
4891 
4892     //  ------------
4893     // | 0  1  2  3 |
4894     // | 4  5  6  7 |
4895     // | 8  9 10 11 |
4896     //  ------------
4897     auto slice = iota(3, 4);
4898     //->
4899     // | 3 |
4900     //->
4901     // | 4 |
4902     size_t[1] shape3 = [3];
4903     size_t[1] shape4 = [4];
4904 
4905     //  ------------
4906     // | 0  1  2  3 |
4907     // | 4  5  6  7 |
4908     // | 8  9 10 11 |
4909     //  ------------
4910     auto x = slice.alongDim!(-1); // -1 is the last dimension index, the same as 1 for this case.
4911     static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t), 1, Universal)));
4912 
4913     assert(x.shape == shape3);
4914     assert(x.front.shape == shape4);
4915     assert(x.front == iota(4));
4916     x.popFront;
4917     assert(x.front == iota([4], 4));
4918 
4919     //  ---------
4920     // | 0  4  8 |
4921     // | 1  5  9 |
4922     // | 2  6 10 |
4923     // | 3  7 11 |
4924     //  ---------
4925     auto y = slice.alongDim!0; // alongDim!(-2) is the same for matrices.
4926     static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal))));
4927 
4928     assert(y.shape == shape4);
4929     assert(y.front.shape == shape3);
4930     assert(y.front == iota([3], 0, 4));
4931     y.popFront;
4932     assert(y.front == iota([3], 1, 4));
4933 }
4934 
4935 /// 3-dimensional slice support, N-dimensional also supported
4936 @safe @nogc pure nothrow
4937 version(mir_ndslice_test) unittest
4938 {
4939     import mir.ndslice;
4940 
4941     //  ----------------
4942     // | 0   1  2  3  4 |
4943     // | 5   6  7  8  9 |
4944     // | 10 11 12 13 14 |
4945     // | 15 16 17 18 19 |
4946     //  - - - - - - - -
4947     // | 20 21 22 23 24 |
4948     // | 25 26 27 28 29 |
4949     // | 30 31 32 33 34 |
4950     // | 35 36 37 38 39 |
4951     //  - - - - - - - -
4952     // | 40 41 42 43 44 |
4953     // | 45 46 47 48 49 |
4954     // | 50 51 52 53 54 |
4955     // | 55 56 57 58 59 |
4956     //  ----------------
4957     auto slice = iota(3, 4, 5);
4958 
4959     size_t[2] shape45 = [4, 5];
4960     size_t[2] shape35 = [3, 5];
4961     size_t[2] shape34 = [3, 4];
4962     size_t[2] shape54 = [5, 4];
4963     size_t[1] shape3 = [3];
4964     size_t[1] shape4 = [4];
4965     size_t[1] shape5 = [5];
4966 
4967     //  ----------
4968     // |  0 20 40 |
4969     // |  5 25 45 |
4970     // | 10 30 50 |
4971     // | 15 35 55 |
4972     //  - - - - -
4973     // |  1 21 41 |
4974     // |  6 26 46 |
4975     // | 11 31 51 |
4976     // | 16 36 56 |
4977     //  - - - - -
4978     // |  2 22 42 |
4979     // |  7 27 47 |
4980     // | 12 32 52 |
4981     // | 17 37 57 |
4982     //  - - - - -
4983     // |  3 23 43 |
4984     // |  8 28 48 |
4985     // | 13 33 53 |
4986     // | 18 38 58 |
4987     //  - - - - -
4988     // |  4 24 44 |
4989     // |  9 29 49 |
4990     // | 14 34 54 |
4991     // | 19 39 59 |
4992     //  ----------
4993     auto a = slice.alongDim!0.transposed;
4994     static assert(is(typeof(a) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal), 2, Universal)));
4995 
4996     assert(a.shape == shape54);
4997     assert(a.front.shape == shape4);
4998     assert(a.front.unpack == iota([3, 4], 0, 5).universal.transposed);
4999     a.popFront;
5000     assert(a.front.front == iota([3], 1, 20));
5001 
5002     //  ----------------
5003     // |  0  1  2  3  4 |
5004     // |  5  6  7  8  9 |
5005     // | 10 11 12 13 14 |
5006     // | 15 16 17 18 19 |
5007     //  - - - - - - - -
5008     // | 20 21 22 23 24 |
5009     // | 25 26 27 28 29 |
5010     // | 30 31 32 33 34 |
5011     // | 35 36 37 38 39 |
5012     //  - - - - - - - -
5013     // | 40 41 42 43 44 |
5014     // | 45 46 47 48 49 |
5015     // | 50 51 52 53 54 |
5016     // | 55 56 57 58 59 |
5017     //  ----------------
5018     auto x = slice.alongDim!(1, 2);
5019     static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2), 1, Universal)));
5020 
5021     assert(x.shape == shape3);
5022     assert(x.front.shape == shape45);
5023     assert(x.front == iota([4, 5]));
5024     x.popFront;
5025     assert(x.front == iota([4, 5], (4 * 5)));
5026 
5027     //  ----------------
5028     // |  0  1  2  3  4 |
5029     // | 20 21 22 23 24 |
5030     // | 40 41 42 43 44 |
5031     //  - - - - - - - -
5032     // |  5  6  7  8  9 |
5033     // | 25 26 27 28 29 |
5034     // | 45 46 47 48 49 |
5035     //  - - - - - - - -
5036     // | 10 11 12 13 14 |
5037     // | 30 31 32 33 34 |
5038     // | 50 51 52 53 54 |
5039     //  - - - - - - - -
5040     // | 15 16 17 18 19 |
5041     // | 35 36 37 38 39 |
5042     // | 55 56 57 58 59 |
5043     //  ----------------
5044     auto y = slice.alongDim!(0, 2);
5045     static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2, Canonical), 1, Universal)));
5046 
5047     assert(y.shape == shape4);
5048     assert(y.front.shape == shape35);
5049     int err;
5050     assert(y.front == slice.universal.strided!1(4).reshape([3, -1], err));
5051     y.popFront;
5052     assert(y.front.front == iota([5], 5));
5053 
5054     //  -------------
5055     // |  0  5 10 15 |
5056     // | 20 25 30 35 |
5057     // | 40 45 50 55 |
5058     //  - - - - - - -
5059     // |  1  6 11 16 |
5060     // | 21 26 31 36 |
5061     // | 41 46 51 56 |
5062     //  - - - - - - -
5063     // |  2  7 12 17 |
5064     // | 22 27 32 37 |
5065     // | 42 47 52 57 |
5066     //  - - - - - - -
5067     // |  3  8 13 18 |
5068     // | 23 28 33 38 |
5069     // | 43 48 53 58 |
5070     //  - - - - - - -
5071     // |  4  9 14 19 |
5072     // | 24 29 34 39 |
5073     // | 44 49 54 59 |
5074     //  -------------
5075     auto z = slice.alongDim!(0, 1);
5076     static assert(is(typeof(z) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2, Universal))));
5077 
5078     assert(z.shape == shape5);
5079     assert(z.front.shape == shape34);
5080     assert(z.front == iota([3, 4], 0, 5));
5081     z.popFront;
5082     assert(z.front.front == iota([4], 1, 5));
5083 }
5084 
5085 /// Use alongDim to calculate column mean/row mean of 2-dimensional slice
5086 version(mir_ndslice_test)
5087 @safe pure
5088 unittest
5089 {
5090     import mir.ndslice.topology: alongDim;
5091     import mir.ndslice.fuse: fuse;
5092     import mir.math.stat: mean;
5093     import mir.algorithm.iteration: all;
5094     import mir.math.common: approxEqual;
5095 
5096     auto x = [
5097         [0.0, 1.0, 1.5, 2.0, 3.5, 4.25],
5098         [2.0, 7.5, 5.0, 1.0, 1.5, 0.0]
5099     ].fuse;
5100 
5101     // Use alongDim with map to compute mean of row/column.
5102     assert(x.alongDim!1.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6]));
5103     assert(x.alongDim!0.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125]));
5104 
5105     // FIXME
5106     // Without using map, computes the mean of the whole slice
5107     // assert(x.alongDim!1.mean == x.sliced.mean);
5108     // assert(x.alongDim!0.mean == x.sliced.mean);
5109 }
5110 
5111 /++
5112 Use alongDim and map with a lambda, but may need to allocate result. This example
5113 uses fuse, which allocates. Note: fuse!1 will transpose the result. 
5114 +/
5115 version(mir_ndslice_test)
5116 @safe pure
5117 unittest {
5118     import mir.ndslice.topology: iota, alongDim, map;
5119     import mir.ndslice.fuse: fuse;
5120     import mir.ndslice.slice: sliced;
5121     
5122     auto x = [1, 2, 3].sliced;
5123     auto y = [1, 2].sliced;
5124 
5125     auto s1 = iota(2, 3).alongDim!1.map!(a => a * x).fuse;
5126     assert(s1 == [[ 0, 2,  6],
5127                   [ 3, 8, 15]]);
5128     auto s2 = iota(2, 3).alongDim!0.map!(a => a * y).fuse!1;
5129     assert(s2 == [[ 0, 1,  2],
5130                   [ 6, 8, 10]]);
5131 }
5132 
5133 /++
5134 Returns a slice that can be iterated by dimension. Transposes dimensions on top and then packs them.
5135 
5136 Combines $(SUBREF dynamic, transposed), $(LREF ipack), and SliceKind Selectors.
5137 
5138 Params:
5139     SDimensions = dimensions to perform iteration on, length of d, `1 <= d <= n`. Negative dimensions are supported.
5140 Returns:
5141     d-dimensional slice composed of `(n-d)`-dimensional slices
5142 See_also:
5143     $(LREF alongDim),
5144     $(SUBREF allocation, slice),
5145     $(LREF ipack),
5146     $(SUBREF dynamic, transposed).
5147 +/
5148 template byDim(SDimensions...)
5149     if (SDimensions.length > 0)
5150 {
5151     static if (allSatisfy!(isSizediff_t, SDimensions))
5152     {
5153         /++
5154         Params:
5155             slice = input n-dimensional slice, n >= d
5156         Returns:
5157             d-dimensional slice composed of `(n-d)`-dimensional slices
5158         +/
5159         @fmamath auto byDim(Iterator, size_t N, SliceKind kind)
5160             (Slice!(Iterator, N, kind) slice)
5161             if (N >= SDimensions.length)
5162         {
5163 
5164             alias Dimensions = staticMap!(ShiftNegativeWith!N, SDimensions);
5165 
5166             mixin DimensionsCountCTError;
5167 
5168             static if (N == 1)
5169             {
5170                 return slice;
5171             }
5172             else
5173             {
5174                 import core.lifetime: move;
5175                 import mir.ndslice.dynamic: transposed;
5176                 import mir.algorithm.iteration: all;
5177 
5178                 auto trans = slice
5179                     .move
5180                     .transposed!Dimensions;
5181                 static if (Dimensions.length == N)
5182                 {
5183                     return trans;
5184                 }
5185                 else
5186                 {
5187                     auto ret = trans.move.ipack!(Dimensions.length);
5188                     static if ((kind == Contiguous || kind == Canonical && N - Dimensions.length == 1) && [Dimensions].all!(a => a < Dimensions.length))
5189                     {
5190                         return ret
5191                             .move
5192                             .evertPack
5193                             .assumeContiguous
5194                             .evertPack;
5195                     }
5196                     else
5197                     static if (kind == Canonical && [Dimensions].all!(a => a < N - 1))
5198                     {
5199                         return ret
5200                             .move
5201                             .evertPack
5202                             .assumeCanonical
5203                             .evertPack;
5204                     }
5205                     else
5206                     static if ((kind == Contiguous || kind == Canonical && Dimensions.length == 1) && [Dimensions] == [Iota!(N - Dimensions.length, N)])
5207                     {
5208                         return ret.assumeContiguous;
5209                     }
5210                     else
5211                     static if ((kind == Contiguous || kind == Canonical) && Dimensions[$-1] == N - 1)
5212                     {
5213                         return ret.assumeCanonical;
5214                     }
5215                     else
5216                     {
5217                         return ret;
5218                     }
5219                 }
5220             }
5221         }
5222     }
5223     else
5224     {
5225         alias byDim = .byDim!(staticMap!(toSizediff_t, SDimensions));
5226     }
5227 }
5228 
5229 /// 2-dimensional slice support
5230 @safe @nogc pure nothrow
5231 version(mir_ndslice_test) unittest
5232 {
5233     import mir.ndslice;
5234 
5235     //  ------------
5236     // | 0  1  2  3 |
5237     // | 4  5  6  7 |
5238     // | 8  9 10 11 |
5239     //  ------------
5240     auto slice = iota(3, 4);
5241     //->
5242     // | 3 |
5243     //->
5244     // | 4 |
5245     size_t[1] shape3 = [3];
5246     size_t[1] shape4 = [4];
5247 
5248     //  ------------
5249     // | 0  1  2  3 |
5250     // | 4  5  6  7 |
5251     // | 8  9 10 11 |
5252     //  ------------
5253     auto x = slice.byDim!0; // byDim!(-2) is the same for matrices.
5254     static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t), 1, Universal)));
5255 
5256     assert(x.shape == shape3);
5257     assert(x.front.shape == shape4);
5258     assert(x.front == iota(4));
5259     x.popFront;
5260     assert(x.front == iota([4], 4));
5261 
5262     //  ---------
5263     // | 0  4  8 |
5264     // | 1  5  9 |
5265     // | 2  6 10 |
5266     // | 3  7 11 |
5267     //  ---------
5268     auto y = slice.byDim!(-1); // -1 is the last dimension index, the same as 1 for this case.
5269     static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal))));
5270 
5271     assert(y.shape == shape4);
5272     assert(y.front.shape == shape3);
5273     assert(y.front == iota([3], 0, 4));
5274     y.popFront;
5275     assert(y.front == iota([3], 1, 4));
5276 }
5277 
5278 /// 3-dimensional slice support, N-dimensional also supported
5279 @safe @nogc pure nothrow
5280 version(mir_ndslice_test) unittest
5281 {
5282     import mir.ndslice;
5283 
5284     //  ----------------
5285     // | 0   1  2  3  4 |
5286     // | 5   6  7  8  9 |
5287     // | 10 11 12 13 14 |
5288     // | 15 16 17 18 19 |
5289     //  - - - - - - - -
5290     // | 20 21 22 23 24 |
5291     // | 25 26 27 28 29 |
5292     // | 30 31 32 33 34 |
5293     // | 35 36 37 38 39 |
5294     //  - - - - - - - -
5295     // | 40 41 42 43 44 |
5296     // | 45 46 47 48 49 |
5297     // | 50 51 52 53 54 |
5298     // | 55 56 57 58 59 |
5299     //  ----------------
5300     auto slice = iota(3, 4, 5);
5301 
5302     size_t[2] shape45 = [4, 5];
5303     size_t[2] shape35 = [3, 5];
5304     size_t[2] shape34 = [3, 4];
5305     size_t[2] shape54 = [5, 4];
5306     size_t[1] shape3 = [3];
5307     size_t[1] shape4 = [4];
5308     size_t[1] shape5 = [5];
5309 
5310     //  ----------------
5311     // |  0  1  2  3  4 |
5312     // |  5  6  7  8  9 |
5313     // | 10 11 12 13 14 |
5314     // | 15 16 17 18 19 |
5315     //  - - - - - - - -
5316     // | 20 21 22 23 24 |
5317     // | 25 26 27 28 29 |
5318     // | 30 31 32 33 34 |
5319     // | 35 36 37 38 39 |
5320     //  - - - - - - - -
5321     // | 40 41 42 43 44 |
5322     // | 45 46 47 48 49 |
5323     // | 50 51 52 53 54 |
5324     // | 55 56 57 58 59 |
5325     //  ----------------
5326     auto x = slice.byDim!0;
5327     static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2), 1, Universal)));
5328 
5329     assert(x.shape == shape3);
5330     assert(x.front.shape == shape45);
5331     assert(x.front == iota([4, 5]));
5332     x.popFront;
5333     assert(x.front == iota([4, 5], (4 * 5)));
5334 
5335     //  ----------------
5336     // |  0  1  2  3  4 |
5337     // | 20 21 22 23 24 |
5338     // | 40 41 42 43 44 |
5339     //  - - - - - - - -
5340     // |  5  6  7  8  9 |
5341     // | 25 26 27 28 29 |
5342     // | 45 46 47 48 49 |
5343     //  - - - - - - - -
5344     // | 10 11 12 13 14 |
5345     // | 30 31 32 33 34 |
5346     // | 50 51 52 53 54 |
5347     //  - - - - - - - -
5348     // | 15 16 17 18 19 |
5349     // | 35 36 37 38 39 |
5350     // | 55 56 57 58 59 |
5351     //  ----------------
5352     auto y = slice.byDim!1;
5353     static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2, Canonical), 1, Universal)));
5354 
5355     assert(y.shape == shape4);
5356     assert(y.front.shape == shape35);
5357     int err;
5358     assert(y.front == slice.universal.strided!1(4).reshape([3, -1], err));
5359     y.popFront;
5360     assert(y.front.front == iota([5], 5));
5361 
5362     //  -------------
5363     // |  0  5 10 15 |
5364     // | 20 25 30 35 |
5365     // | 40 45 50 55 |
5366     //  - - - - - - -
5367     // |  1  6 11 16 |
5368     // | 21 26 31 36 |
5369     // | 41 46 51 56 |
5370     //  - - - - - - -
5371     // |  2  7 12 17 |
5372     // | 22 27 32 37 |
5373     // | 42 47 52 57 |
5374     //  - - - - - - -
5375     // |  3  8 13 18 |
5376     // | 23 28 33 38 |
5377     // | 43 48 53 58 |
5378     //  - - - - - - -
5379     // |  4  9 14 19 |
5380     // | 24 29 34 39 |
5381     // | 44 49 54 59 |
5382     //  -------------
5383     auto z = slice.byDim!2;
5384     static assert(is(typeof(z) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2, Universal))));
5385 
5386     assert(z.shape == shape5);
5387     assert(z.front.shape == shape34);
5388     assert(z.front == iota([3, 4], 0, 5));
5389     z.popFront;
5390     assert(z.front.front == iota([4], 1, 5));
5391 
5392     //  ----------
5393     // |  0 20 40 |
5394     // |  5 25 45 |
5395     // | 10 30 50 |
5396     // | 15 35 55 |
5397     //  - - - - -
5398     // |  1 21 41 |
5399     // |  6 26 46 |
5400     // | 11 31 51 |
5401     // | 16 36 56 |
5402     //  - - - - -
5403     // |  2 22 42 |
5404     // |  7 27 47 |
5405     // | 12 32 52 |
5406     // | 17 37 57 |
5407     //  - - - - -
5408     // |  3 23 43 |
5409     // |  8 28 48 |
5410     // | 13 33 53 |
5411     // | 18 38 58 |
5412     //  - - - - -
5413     // |  4 24 44 |
5414     // |  9 29 49 |
5415     // | 14 34 54 |
5416     // | 19 39 59 |
5417     //  ----------
5418     auto a = slice.byDim!(2, 1);
5419     static assert(is(typeof(a) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal), 2, Universal)));
5420 
5421     assert(a.shape == shape54);
5422     assert(a.front.shape == shape4);
5423     assert(a.front.unpack == iota([3, 4], 0, 5).universal.transposed);
5424     a.popFront;
5425     assert(a.front.front == iota([3], 1, 20));
5426 }
5427 
5428 /// Use byDim to calculate column mean/row mean of 2-dimensional slice
5429 version(mir_ndslice_test)
5430 @safe pure
5431 unittest
5432 {
5433     import mir.ndslice.topology: byDim;
5434     import mir.ndslice.fuse: fuse;
5435     import mir.math.stat: mean;
5436     import mir.algorithm.iteration: all;
5437     import mir.math.common: approxEqual;
5438 
5439     auto x = [
5440         [0.0, 1.0, 1.5, 2.0, 3.5, 4.25],
5441         [2.0, 7.5, 5.0, 1.0, 1.5, 0.0]
5442     ].fuse;
5443 
5444     // Use byDim with map to compute mean of row/column.
5445     assert(x.byDim!0.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6]));
5446     assert(x.byDim!1.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125]));
5447 
5448     // FIXME
5449     // Without using map, computes the mean of the whole slice
5450     // assert(x.byDim!0.mean == x.sliced.mean);
5451     // assert(x.byDim!1.mean == x.sliced.mean);
5452 }
5453 
5454 /++
5455 Use byDim and map with a lambda, but may need to allocate result. This example
5456 uses fuse, which allocates. Note: fuse!1 will transpose the result. 
5457 +/
5458 version(mir_ndslice_test)
5459 @safe pure
5460 unittest {
5461     import mir.ndslice.topology: iota, byDim, map;
5462     import mir.ndslice.fuse: fuse;
5463     import mir.ndslice.slice: sliced;
5464     
5465     auto x = [1, 2, 3].sliced;
5466     auto y = [1, 2].sliced;
5467 
5468     auto s1 = iota(2, 3).byDim!0.map!(a => a * x).fuse;
5469     assert(s1 == [[ 0, 2,  6],
5470                   [ 3, 8, 15]]);
5471     auto s2 = iota(2, 3).byDim!1.map!(a => a * y).fuse!1;
5472     assert(s2 == [[ 0, 1,  2],
5473                   [ 6, 8, 10]]);
5474 }
5475 
5476 // Ensure works on canonical
5477 @safe @nogc pure nothrow
5478 version(mir_ndslice_test) unittest
5479 {
5480     import mir.ndslice.topology : iota, canonical;
5481     //  ------------
5482     // | 0  1  2  3 |
5483     // | 4  5  6  7 |
5484     // | 8  9 10 11 |
5485     //  ------------
5486     auto slice = iota(3, 4).canonical;
5487     //->
5488     // | 3 |
5489     //->
5490     // | 4 |
5491     size_t[1] shape3 = [3];
5492     size_t[1] shape4 = [4];
5493 
5494     //  ------------
5495     // | 0  1  2  3 |
5496     // | 4  5  6  7 |
5497     // | 8  9 10 11 |
5498     //  ------------
5499     auto x = slice.byDim!0;
5500     static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t), 1, Universal)));
5501 
5502     assert(x.shape == shape3);
5503     assert(x.front.shape == shape4);
5504     assert(x.front == iota(4));
5505     x.popFront;
5506     assert(x.front == iota([4], 4));
5507 
5508     //  ---------
5509     // | 0  4  8 |
5510     // | 1  5  9 |
5511     // | 2  6 10 |
5512     // | 3  7 11 |
5513     //  ---------
5514     auto y = slice.byDim!1;
5515     static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal))));
5516 
5517     assert(y.shape == shape4);
5518     assert(y.front.shape == shape3);
5519     assert(y.front == iota([3], 0, 4));
5520     y.popFront;
5521     assert(y.front == iota([3], 1, 4));
5522 }
5523 
5524 // Ensure works on universal
5525 @safe @nogc pure nothrow
5526 version(mir_ndslice_test) unittest
5527 {
5528     import mir.ndslice.topology : iota, universal;
5529     //  ------------
5530     // | 0  1  2  3 |
5531     // | 4  5  6  7 |
5532     // | 8  9 10 11 |
5533     //  ------------
5534     auto slice = iota(3, 4).universal;
5535     //->
5536     // | 3 |
5537     //->
5538     // | 4 |
5539     size_t[1] shape3 = [3];
5540     size_t[1] shape4 = [4];
5541 
5542     //  ------------
5543     // | 0  1  2  3 |
5544     // | 4  5  6  7 |
5545     // | 8  9 10 11 |
5546     //  ------------
5547     auto x = slice.byDim!0;
5548     static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal), 1, Universal)));
5549 
5550     assert(x.shape == shape3);
5551     assert(x.front.shape == shape4);
5552     assert(x.front == iota(4));
5553     x.popFront;
5554     assert(x.front == iota([4], 4));
5555 
5556     //  ---------
5557     // | 0  4  8 |
5558     // | 1  5  9 |
5559     // | 2  6 10 |
5560     // | 3  7 11 |
5561     //  ---------
5562     auto y = slice.byDim!1;
5563     static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal), 1, Universal)));
5564 
5565     assert(y.shape == shape4);
5566     assert(y.front.shape == shape3);
5567     assert(y.front == iota([3], 0, 4));
5568     y.popFront;
5569     assert(y.front == iota([3], 1, 4));
5570 }
5571 
5572 // 1-dimensional slice support
5573 @safe @nogc pure nothrow
5574 version(mir_ndslice_test) unittest
5575 {
5576     import mir.ndslice.topology : iota;
5577     //  -------
5578     // | 0 1 2 |
5579     //  -------
5580     auto slice = iota(3);
5581     auto x = slice.byDim!0;
5582     static assert (is(typeof(x) == typeof(slice)));
5583     assert(x == slice);
5584 }
5585 
5586 /++
5587 Constructs a new view of an n-dimensional slice with dimension `axis` removed.
5588 
5589 Throws:
5590     `AssertError` if the length of the corresponding dimension doesn' equal 1.
5591 Params:
5592     axis = dimension to remove, if it is single-dimensional
5593     slice = n-dimensional slice
5594 Returns:
5595     new view of a slice with dimension removed
5596 See_also: $(LREF unsqueeze), $(LREF iota).
5597 +/
5598 template squeeze(sizediff_t axis = 0)
5599 {
5600     Slice!(Iterator, N - 1, kind != Canonical ? kind : ((axis == N - 1 || axis == -1) ? Universal : (N == 2 ? Contiguous : kind)))
5601         squeeze(Iterator, size_t N, SliceKind kind)
5602         (Slice!(Iterator, N, kind) slice)
5603         if (-sizediff_t(N) <= axis && axis < sizediff_t(N) && N > 1)
5604     in {
5605         assert(slice._lengths[axis < 0 ? N + axis : axis] == 1);
5606     }
5607     do {
5608         import mir.utility: swap;
5609         enum sizediff_t a = axis < 0 ? N + axis : axis;
5610         typeof(return) ret;
5611         foreach (i; Iota!(0, a))
5612             ret._lengths[i] = slice._lengths[i];
5613         foreach (i; Iota!(a + 1, N))
5614             ret._lengths[i - 1] = slice._lengths[i];
5615         static if (kind == Universal)
5616         {
5617             foreach (i; Iota!(0, a))
5618                 ret._strides[i] = slice._strides[i];
5619             foreach (i; Iota!(a + 1, N))
5620                 ret._strides[i - 1] = slice._strides[i];
5621         }
5622         else
5623         static if (kind == Canonical)
5624         {
5625             static if (a == N - 1)
5626             {
5627                 foreach (i; Iota!(0, N - 1))
5628                     ret._strides[i] = slice._strides[i];
5629             }
5630             else
5631             {
5632                 foreach (i; Iota!(0, a))
5633                     ret._strides[i] = slice._strides[i];
5634                 foreach (i; Iota!(a + 1, N - 1))
5635                     ret._strides[i - 1] = slice._strides[i];
5636             }
5637         }
5638         swap(ret._iterator, slice._iterator);
5639         return ret;
5640     }
5641 }
5642 
5643 ///
5644 version(mir_ndslice_test)
5645 unittest
5646 {
5647     import mir.ndslice.topology : iota;
5648     import mir.ndslice.allocation : slice;
5649 
5650     // [[0, 1, 2]] -> [0, 1, 2]
5651     assert([1, 3].iota.squeeze == [3].iota);
5652     // [[0], [1], [2]] -> [0, 1, 2]
5653     assert([3, 1].iota.squeeze!1 == [3].iota);
5654     assert([3, 1].iota.squeeze!(-1) == [3].iota);
5655 
5656     assert([1, 3].iota.canonical.squeeze == [3].iota);
5657     assert([3, 1].iota.canonical.squeeze!1 == [3].iota);
5658     assert([3, 1].iota.canonical.squeeze!(-1) == [3].iota);
5659 
5660     assert([1, 3].iota.universal.squeeze == [3].iota);
5661     assert([3, 1].iota.universal.squeeze!1 == [3].iota);
5662     assert([3, 1].iota.universal.squeeze!(-1) == [3].iota);
5663 
5664     assert([1, 3, 4].iota.squeeze == [3, 4].iota);
5665     assert([3, 1, 4].iota.squeeze!1 == [3, 4].iota);
5666     assert([3, 4, 1].iota.squeeze!(-1) == [3, 4].iota);
5667 
5668     assert([1, 3, 4].iota.canonical.squeeze == [3, 4].iota);
5669     assert([3, 1, 4].iota.canonical.squeeze!1 == [3, 4].iota);
5670     assert([3, 4, 1].iota.canonical.squeeze!(-1) == [3, 4].iota);
5671 
5672     assert([1, 3, 4].iota.universal.squeeze == [3, 4].iota);
5673     assert([3, 1, 4].iota.universal.squeeze!1 == [3, 4].iota);
5674     assert([3, 4, 1].iota.universal.squeeze!(-1) == [3, 4].iota);
5675 }
5676 
5677 /++
5678 Constructs a view of an n-dimensional slice with a dimension added at `axis`. Used
5679 to unsqueeze a squeezed slice.
5680 
5681 Params:
5682     slice = n-dimensional slice
5683     axis = dimension to be unsqueezed (add new dimension), default values is 0, the first dimension
5684 Returns:
5685     unsqueezed n+1-dimensional slice of the same slice kind
5686 See_also: $(LREF squeeze), $(LREF iota).
5687 +/
5688 Slice!(Iterator, N + 1, kind) unsqueeze(Iterator, size_t N, SliceKind kind)
5689     (Slice!(Iterator, N, kind) slice, sizediff_t axis)
5690 in {
5691     assert(-sizediff_t(N + 1) <= axis && axis <= sizediff_t(N));
5692 }
5693 do {
5694     import mir.utility: swap;
5695     typeof(return) ret;
5696     auto a = axis < 0 ? axis + N + 1 : axis;
5697     foreach (i; 0 .. a)
5698         ret._lengths[i] = slice._lengths[i];
5699     ret._lengths[a] = 1;
5700     foreach (i; a .. N)
5701         ret._lengths[i + 1] = slice._lengths[i];
5702     static if (kind == Universal)
5703     {
5704         foreach (i; 0 .. a)
5705             ret._strides[i] = slice._strides[i];
5706         foreach (i; a .. N)
5707             ret._strides[i + 1] = slice._strides[i];
5708     }
5709     else
5710     static if (kind == Canonical)
5711     {
5712         if (a == N)
5713         {
5714             foreach (i; Iota!(0, N - 1))
5715                 ret._strides[i] = slice._strides[i];
5716             ret._strides[N - 1] = 1;
5717         }
5718         else
5719         {
5720             foreach (i; 0 .. a)
5721                 ret._strides[i] = slice._strides[i];
5722             foreach (i; a .. N - 1)
5723                 ret._strides[i + 1] = slice._strides[i];
5724         }
5725     }
5726     swap(ret._iterator, slice._iterator);
5727     return ret;
5728 }
5729 
5730 /// ditto
5731 template unsqueeze(sizediff_t axis = 0)
5732 {
5733     Slice!(Iterator, N + 1, kind) unsqueeze(Iterator, size_t N, SliceKind kind)
5734         (Slice!(Iterator, N, kind) slice)
5735     in {
5736         assert(-sizediff_t(N + 1) <= axis && axis <= sizediff_t(N));
5737     }
5738     do {
5739         import mir.utility: swap;
5740         typeof(return) ret;
5741         enum a = axis < 0 ? axis + N + 1 : axis;
5742         foreach (i; Iota!a)
5743             ret._lengths[i] = slice._lengths[i];
5744         ret._lengths[a] = 1;
5745         foreach (i; Iota!(a, N))
5746             ret._lengths[i + 1] = slice._lengths[i];
5747         static if (kind == Universal)
5748         {
5749             foreach (i; Iota!a)
5750                 ret._strides[i] = slice._strides[i];
5751             foreach (i; Iota!(a, N))
5752                 ret._strides[i + 1] = slice._strides[i];
5753         }
5754         else
5755         static if (kind == Canonical)
5756         {
5757             static if (a == N)
5758             {
5759                 foreach (i; Iota!(0, N - 1))
5760                     ret._strides[i] = slice._strides[i];
5761                 ret._strides[N - 1] = 1;
5762             }
5763             else
5764             {
5765                 foreach (i; Iota!(0, a))
5766                     ret._strides[i] = slice._strides[i];
5767                 foreach (i; Iota!(a, N - 1))
5768                     ret._strides[i + 1] = slice._strides[i];
5769             }
5770         }
5771         swap(ret._iterator, slice._iterator);
5772         return ret;
5773     }
5774 }
5775 
5776 ///
5777 version (mir_ndslice_test) 
5778 @safe pure nothrow @nogc
5779 unittest
5780 {
5781     // [0, 1, 2] -> [[0, 1, 2]]
5782     assert([3].iota.unsqueeze == [1, 3].iota);
5783 
5784     assert([3].iota.universal.unsqueeze == [1, 3].iota);
5785     assert([3, 4].iota.unsqueeze == [1, 3, 4].iota);
5786     assert([3, 4].iota.canonical.unsqueeze == [1, 3, 4].iota);
5787     assert([3, 4].iota.universal.unsqueeze == [1, 3, 4].iota);
5788 
5789     // [0, 1, 2] -> [[0], [1], [2]]
5790     assert([3].iota.unsqueeze(-1) == [3, 1].iota);
5791     assert([3].iota.unsqueeze!(-1) == [3, 1].iota);
5792 
5793     assert([3].iota.universal.unsqueeze(-1) == [3, 1].iota);
5794     assert([3].iota.universal.unsqueeze!(-1) == [3, 1].iota);
5795     assert([3, 4].iota.unsqueeze(-1) == [3, 4, 1].iota);
5796     assert([3, 4].iota.unsqueeze!(-1) == [3, 4, 1].iota);
5797     assert([3, 4].iota.canonical.unsqueeze(-1) == [3, 4, 1].iota);
5798     assert([3, 4].iota.canonical.unsqueeze!(-1) == [3, 4, 1].iota);
5799     assert([3, 4].iota.universal.unsqueeze(-1) == [3, 4, 1].iota);
5800     assert([3, 4].iota.universal.unsqueeze!(-1) == [3, 4, 1].iota);
5801 }
5802 
5803 /++
5804 Field (element's member) projection.
5805 
5806 Params:
5807     name = element's member name
5808 Returns:
5809     lazy n-dimensional slice of the same shape
5810 See_also:
5811     $(LREF map)
5812 +/
5813 
5814 template member(string name)
5815     if (name.length)
5816 {
5817     /++
5818     Params:
5819         slice = n-dimensional slice composed of structs, classes or unions
5820     Returns:
5821         lazy n-dimensional slice of the same shape
5822     +/
5823     Slice!(MemberIterator!(Iterator, name), N, kind) member(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice)
5824     {
5825         return typeof(return)(slice._structure, MemberIterator!(Iterator, name)(slice._iterator));
5826     }
5827 
5828     /// ditto
5829     Slice!(MemberIterator!(T*, name)) member(T)(T[] array)
5830     {
5831         return member(array.sliced);
5832     }
5833 
5834     /// ditto
5835     auto member(T)(T withAsSlice)
5836         if (hasAsSlice!T)
5837     {
5838         return member(withAsSlice.asSlice);
5839     }
5840 }
5841 
5842 ///
5843 version(mir_ndslice_test)
5844 @safe pure unittest
5845 {
5846     // struct, union or class
5847     struct S
5848     {
5849         // Property support
5850         // Getter always must be defined.
5851         double _x;
5852         double x() @property
5853         {
5854             return x;
5855         }
5856         void x(double x) @property
5857         {
5858             _x = x;
5859         }
5860 
5861         /// Field support
5862         double y;
5863 
5864         /// Zero argument function support
5865         double f()
5866         {
5867             return _x * 2;
5868         }
5869     }
5870 
5871     import mir.ndslice.allocation: slice;
5872     import mir.ndslice.topology: iota;
5873 
5874     auto matrix = slice!S(2, 3);
5875     matrix.member!"x"[] = [2, 3].iota;
5876     matrix.member!"y"[] = matrix.member!"f";
5877     assert(matrix.member!"y" == [2, 3].iota * 2);
5878 }
5879 
5880 /++
5881 Functional deep-element wise reduce of a slice composed of fields or iterators.
5882 +/
5883 template orthogonalReduceField(alias fun)
5884 {
5885     import mir.functional: naryFun;
5886     static if (__traits(isSame, naryFun!fun, fun))
5887     {
5888     @fmamath:
5889         /++
5890         Params:
5891             slice = Non empty input slice composed of fields or iterators.
5892         Returns:
5893             a lazy field with each element of which is reduced value of element of the same index of all iterators.
5894         +/
5895         OrthogonalReduceField!(Iterator, fun, I) orthogonalReduceField(I, Iterator)(I initialValue, Slice!Iterator slice)
5896         {
5897             return typeof(return)(slice, initialValue);
5898         }
5899 
5900         /// ditto
5901         OrthogonalReduceField!(T*, fun, I) orthogonalReduceField(I, T)(I initialValue, T[] array)
5902         {
5903             return orthogonalReduceField(initialValue, array.sliced);
5904         }
5905 
5906         /// ditto
5907         auto orthogonalReduceField(I, T)(I initialValue, T withAsSlice)
5908             if (hasAsSlice!T)
5909         {
5910             return orthogonalReduceField(initialValue, withAsSlice.asSlice);
5911         }
5912     }
5913     else alias orthogonalReduceField = .orthogonalReduceField!(naryFun!fun);
5914 }
5915 
5916 /// bit array operations
5917 version(mir_ndslice_test)
5918 unittest
5919 {
5920     import mir.ndslice.slice: slicedField;
5921     import mir.ndslice.allocation: bitSlice;
5922     import mir.ndslice.dynamic: strided;
5923     import mir.ndslice.topology: iota, orthogonalReduceField;
5924     auto len = 100;
5925     auto a = len.bitSlice;
5926     auto b = len.bitSlice;
5927     auto c = len.bitSlice;
5928     a[len.iota.strided!0(7)][] = true;
5929     b[len.iota.strided!0(11)][] = true;
5930     c[len.iota.strided!0(13)][] = true;
5931 
5932     // this is valid since bitslices above are oroginal slices of allocated memory.
5933     auto and =
5934         orthogonalReduceField!"a & b"(size_t.max, [
5935             a.iterator._field._field, // get raw data pointers
5936             b.iterator._field._field,
5937             c.iterator._field._field,
5938         ]) // operation on size_t
5939         .bitwiseField
5940         .slicedField(len);
5941 
5942     assert(and == (a & b & c));
5943 }
5944 
5945 /++
5946 Constructs a lazy view of triplets with `left`, `center`, and `right` members.
5947 
5948 Returns: Slice of the same length composed of $(SUBREF iterator, Triplet) triplets.
5949 The `center` member is type of a slice element.
5950 The `left` and `right` members has the same type as slice.
5951 
5952 The module contains special function $(LREF collapse) to handle
5953 left and right side of triplets in one expression.
5954 
5955 Params:
5956     slice = a slice or an array to iterate over
5957 
5958 Example:
5959 ------
5960 triplets(eeeeee) =>
5961 
5962 ||c|lllll|
5963 |r|c|llll|
5964 |rr|c|lll|
5965 |rrr|c|ll|
5966 |rrrr|c|l|
5967 |rrrrr|c||
5968 ------
5969 
5970 See_also: $(LREF stairs).
5971 +/
5972 Slice!(TripletIterator!(Iterator, kind)) triplets(Iterator, SliceKind kind)(Slice!(Iterator, 1, kind) slice)
5973 {
5974     return typeof(return)(slice.length, typeof(return).Iterator(0, slice));
5975 }
5976 
5977 /// ditto
5978 Slice!(TripletIterator!(T*)) triplets(T)(scope return T[] slice)
5979 {
5980     return .triplets(slice.sliced);
5981 }
5982 
5983 /// ditto
5984 auto triplets(string type, S)(S slice, size_t n)
5985     if (hasAsSlice!S)
5986 {
5987     return .triplets(slice.asSlice);
5988 }
5989 
5990 ///
5991 version(mir_ndslice_test) unittest
5992 {
5993     import mir.ndslice.slice: sliced;
5994     import mir.ndslice.topology: triplets, member, iota;
5995 
5996     auto a = [4, 5, 2, 8];
5997     auto h = a.triplets;
5998 
5999     assert(h[1].center == 5);
6000     assert(h[1].left == [4]);
6001     assert(h[1].right == [2, 8]);
6002 
6003     h[1].center = 9;
6004     assert(a[1] == 9);
6005 
6006     assert(h.member!"center" == a);
6007 
6008     // `triplets` topology can be used with iota to index a slice
6009     auto s = a.sliced;
6010     auto w = s.length.iota.triplets[1];
6011 
6012     assert(&s[w.center] == &a[1]);
6013     assert(s[w.left].field is a[0 .. 1]);
6014     assert(s[w.right].field is a[2 .. $]);
6015 }