1 /++
2 This is a submodule of $(MREF mir, ndslice).
3 
4 The module contains $(LREF ._concatenation) routine.
5 It construct $(LREF Concatenation) structure that can be
6 assigned to an ndslice of the same shape with `[] = ` or `[] op= `.
7 
8 $(SUBREF slice, slicedNdField) can be used to construct ndslice view on top of $(LREF Concatenation).
9 
10 $(SUBREF allocation, slice) has special overload for $(LREF Concatenation) that can be used to allocate new ndslice.
11 
12 $(BOOKTABLE $(H2 Concatenation constructors),
13 $(TR $(TH Function Name) $(TH Description))
14 $(T2 ._concatenation, Creates a $(LREF Concatenation) view of multiple slices.)
15 $(T2 pad, Pads with a constant value.)
16 $(T2 padEdge, Pads with the edge values of slice.)
17 $(T2 padSymmetric, Pads with the reflection of the slice mirrored along the edge of the slice.)
18 $(T2 padWrap, Pads with the wrap of the slice along the axis. The first values are used to pad the end and the end values are used to pad the beginning.)
19 )
20 
21 
22 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
23 Copyright: 2020 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments
24 Authors: Ilya Yaroshenko
25 
26 See_also: $(SUBMODULE fuse) submodule.
27 
28 Macros:
29 SUBMODULE = $(MREF_ALTTEXT $1, mir, ndslice, $1)
30 SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP)
31 T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
32 +/
33 module mir.ndslice.concatenation;
34 
35 import std.traits;
36 import std.meta;
37 
38 import mir.internal.utility;
39 import mir.math.common: optmath;
40 import mir.ndslice.internal;
41 import mir.ndslice.slice;
42 import mir.primitives;
43 
44 @optmath:
45 
46 private template _expose(size_t maxN, size_t dim)
47 {
48     static @optmath auto _expose(S)(S s)
49     {
50         static if (s.N == maxN)
51         {
52             return s;
53         }
54         else
55         {
56             static assert(s.shape.length == s.N, "Cannot create concatenation for packed slice of smaller dimension.");
57             import mir.ndslice.topology: repeat, unpack;
58             auto r = s.repeat(1).unpack;
59             static if (dim)
60             {
61                 import mir.ndslice.dynamic: transposed;
62                 return r.transposed!(Iota!(1, dim + 1));
63             }
64             else
65             {
66                 return r;
67             }
68         }
69     }
70 }
71 
72 private template _Expose(size_t maxN, size_t dim)
73 {
74     alias _expose = ._expose!(maxN, dim);
75     alias _Expose(S) = ReturnType!(_expose!S);
76 }
77 
78 
79 /++
80 Creates a $(LREF Concatenation) view of multiple slices.
81 
82 Can be used in combination with itself, $(LREF until), $(SUBREF allocation, slice),
83 and $(SUBREF slice, Slice) assignment.
84 
85 Params:
86     slices = tuple of slices and/or concatenations.
87 
88 Returns: $(LREF Concatenation).
89 +/
90 auto concatenation(size_t dim = 0, Slices...)(Slices slices)
91 {
92     static if (allSatisfy!(templateOr!(isSlice, isConcatenation), Slices))
93     {
94         import mir.algorithm.iteration: reduce;
95         import mir.utility: min, max; 
96         enum NOf(S) = S.N;
97         enum NArray = [staticMap!(NOf, Slices)];
98         enum minN = size_t.max.reduce!min(NArray);
99         enum maxN = size_t.min.reduce!max(NArray);
100         static if (minN == maxN)
101         {
102             import core.lifetime: forward;
103             return Concatenation!(dim, Slices)(forward!slices);
104         }
105         else
106         {
107             import core.lifetime: move;
108             static assert(minN + 1 == maxN);
109             alias S = staticMap!(_Expose!(maxN, dim), Slices);
110             Concatenation!(dim, S) ret;
111             foreach (i, ref e; ret._slices)
112                 e = _expose!(maxN, dim)(move(slices[i]));
113             return ret;
114         }
115     }
116     else
117     {
118         import core.lifetime: forward;
119         return .concatenation(toSlices!(forward!slices));
120     }
121 }
122 
123 /// Concatenation of slices with different dimmensions. 
124 version(mir_test) unittest
125 {
126     import mir.ndslice.allocation: slice;
127     import mir.ndslice.topology: repeat, iota;
128 
129     // 0 0 0
130     auto vector = size_t.init.repeat([3]);
131 
132     // 1 2 3
133     // 4 5 6
134     auto matrix = iota([2, 3], 1);
135 
136     assert(concatenation(vector, matrix).slice == [
137         [0, 0, 0],
138         [1, 2, 3],
139         [4, 5, 6],
140     ]);
141 
142     vector.popFront;
143     assert(concatenation!1(vector, matrix).slice == [
144         [0, 1, 2, 3],
145         [0, 4, 5, 6],
146     ]);
147 }
148 
149 /// Multidimensional
150 version(mir_test) unittest
151 {
152     import mir.ndslice.allocation: slice;
153     import mir.ndslice.topology: iota;
154     import mir.ndslice.slice : slicedNdField;
155 
156     // 0, 1, 2
157     // 3, 4, 5
158     auto a = iota(2, 3);
159     // 0, 1
160     // 2, 3
161     auto b = iota(2, 2);
162     // 0, 1, 2, 3, 4
163     auto c = iota(1, 5);
164 
165     // 0, 1, 2,   0, 1
166     // 3, 4, 5,   2, 3
167     // 
168     // 0, 1, 2, 3, 4
169     // construction phase
170     auto s = concatenation(concatenation!1(a, b), c);
171 
172     // allocation phase
173     auto d = s.slice;
174     assert(d == [
175         [0, 1, 2, 0, 1],
176         [3, 4, 5, 2, 3],
177         [0, 1, 2, 3, 4],
178         ]);
179 
180     // optimal fragmentation for output/writing/buffering
181     auto testData = [
182         [0, 1, 2], [0, 1],
183         [3, 4, 5], [2, 3],
184         [0, 1, 2, 3, 4],
185     ];
186     size_t i;
187     s.forEachFragment!((fragment) {
188         pragma(inline, false); //reduces template bloat
189         assert(fragment == testData[i++]);
190         });
191     assert(i == testData.length);
192 
193     // lazy ndslice view
194     assert(s.slicedNdField == d);
195 }
196 
197 /// 1D
198 version(mir_test) unittest
199 {
200     import mir.ndslice.allocation: slice;
201     import mir.ndslice.topology: iota;
202     import mir.ndslice.slice : slicedNdField;
203 
204     size_t i;
205     auto a = 3.iota;
206     auto b = iota([6], a.length);
207     auto s = concatenation(a, b);
208     assert(s.length == a.length + b.length);
209     // fast iteration with until
210     s.until!((elem){ assert(elem == i++); return false; });
211     // allocation with slice
212     assert(s.slice == s.length.iota);
213     // 1D or multidimensional assignment
214     auto d = slice!double(s.length);
215     d[] = s;
216     assert(d == s.length.iota);
217     d.opIndexOpAssign!"+"(s);
218     assert(d == iota([s.length], 0, 2));
219 
220     // lazy ndslice view
221     assert(s.slicedNdField == s.length.iota);
222 }
223 
224 ///
225 enum bool isConcatenation(T) = is(T : Concatenation!(dim, Slices), size_t dim, Slices...);
226 ///
227 enum size_t concatenationDimension(T : Concatenation!(dim, Slices), size_t dim, Slices...) = dim; 
228 
229 ///
230 struct Concatenation(size_t dim, Slices...)
231     if (Slices.length > 1)
232 {
233     @optmath:
234 
235 
236     /// Slices and sub-concatenations
237     Slices _slices;
238 
239     package enum N = typeof(Slices[0].shape).length;
240 
241     static assert(dim < N);
242 
243     alias DeepElement = CommonType!(staticMap!(DeepElementType, Slices));
244 
245     ///
246     auto lightConst()() const @property
247     {
248         import std.format;
249         import mir.qualifier;
250         import mir.ndslice.topology: iota;
251         return mixin("Concatenation!(dim, staticMap!(LightConstOf, Slices))(%(_slices[%s].lightConst,%)].lightConst)".format(_slices.length.iota));
252     }
253 
254     ///
255     auto lightImmutable()() immutable @property
256     {
257         import std.format;
258         import mir.ndslice.topology: iota;
259         import mir.qualifier;
260         return mixin("Concatenation!(dim, staticMap!(LightImmutableOf, Slices))(%(_slices[%s].lightImmutable,%)].lightImmutable)".format(_slices.length.iota));
261     }
262 
263     /// Length primitive
264     size_t length(size_t d = 0)() const @property
265     {
266         static if (d == dim)
267         {
268             size_t length;
269             foreach(ref slice; _slices)
270                 length += slice.length!d;
271             return length;
272         }
273         else
274         {
275             return _slices[0].length!d;
276         }
277     }
278 
279     /// Total elements count in the concatenation.
280     size_t elementCount()() const @property
281     {
282         size_t count = 1;
283         foreach(i; Iota!N)
284             count *= length!i;
285         return count;
286     }
287 
288     /// Shape of the concatenation.
289     size_t[N] shape()() const @property
290     {
291         typeof(return) ret;
292         foreach(i; Iota!N)
293             ret[i] = length!i;
294         return ret;
295     }
296 
297     /// Multidimensional input range primitives
298     bool empty(size_t d = 0)() const @property
299     {
300         static if (d == dim)
301         {
302             foreach(ref slice; _slices)
303                 if (!slice.empty!d)
304                     return false;
305             return true;
306         }
307         else
308         {
309             return _slices[0].empty!d;
310         }
311     }
312 
313     /// ditto
314     void popFront(size_t d = 0)()
315     {
316         static if (d == dim)
317         {
318             foreach(i, ref slice; _slices)
319             {
320                 static if (i != Slices.length - 1)
321                     if (slice.empty!d)
322                         continue;
323                 return slice.popFront!d;
324             }
325         }
326         else
327         {
328             foreach_reverse (ref slice; _slices)
329                 slice.popFront!d;
330         }
331     }
332 
333     /// ditto
334     auto front(size_t d = 0)()
335     {
336         static if (d == dim)
337         {
338             foreach(i, ref slice; _slices)
339             {
340                 static if (i != Slices.length - 1)
341                     if (slice.empty!d)
342                         continue;
343                 return slice.front!d;
344             }
345         }
346         else
347         {
348             import mir.ndslice.internal: frontOfDim;
349             enum elemDim = d < dim ? dim - 1 : dim;
350             return concatenation!elemDim(frontOfDim!(d, _slices));
351         }
352     }
353 
354     /// Simplest multidimensional random access primitive
355     auto opIndex()(size_t[N] indices...)
356     {
357         foreach(i, ref slice; _slices[0 .. $-1])
358         {
359             ptrdiff_t diff = indices[dim] - slice.length!dim;
360             if (diff < 0)
361                 return slice[indices];
362             indices[dim] = diff;
363         }
364         assert(indices[dim] < _slices[$-1].length!dim);
365         return _slices[$-1][indices];
366     }
367 }
368 
369 
370 /++
371 Performs `fun(st.front!d)`.
372 
373 This functions is useful when `st.front!d` has not a common type and fails to compile.
374 
375 Can be used instead of $(LREF .Concatenation.front)
376 +/
377 auto applyFront(size_t d = 0, alias fun, size_t dim, Slices...)(Concatenation!(dim, Slices) st)
378 {
379     static if (d == dim)
380     {
381         foreach(i, ref slice; st._slices)
382         {
383             static if (i != Slices.length - 1)
384                 if (slice.empty!d)
385                     continue;
386             return fun(slice.front!d);
387         }
388     }
389     else
390     {
391         import mir.ndslice.internal: frontOfDim;
392         enum elemDim = d < dim ? dim - 1 : dim;
393         auto slices = st._slices;
394         return fun(concatenation!elemDim(frontOfDim!(d, slices)));
395     }
396 }
397 
398 /++
399 Pads with a constant value.
400 
401 Params:
402     direction = padding direction.
403         Direction can be one of the following values: `"both"`, `"pre"`, and `"post"`.
404     s = $(SUBREF slice, Slice) or ndField
405     value = initial value for padding
406     lengths = list of lengths
407 
408 Returns: $(LREF Concatenation)
409 
410 See_also: $(LREF ._concatenation) examples.
411 +/
412 auto pad(string direction = "both", S, T, size_t N)(S s, T value, size_t[N] lengths...)
413     if (hasShape!S && N == typeof(S.shape).length)
414 {
415     return .pad!([Iota!N], [Repeat!(N, direction)])(s, value, lengths);
416 }
417 
418 ///
419 version(mir_test) unittest
420 {
421     import mir.ndslice.allocation: slice;
422     import mir.ndslice.topology: iota;
423 
424     auto pad = iota([3], 1)
425         .pad(0, [2])
426         .slice;
427 
428     assert(pad == [0, 0,  1, 2, 3,  0, 0]);
429 }
430 
431 ///
432 version(mir_test) unittest
433 {
434     import mir.ndslice.allocation: slice;
435     import mir.ndslice.topology: iota;
436 
437     auto pad = iota([2, 2], 1)
438         .pad(0, [2, 1])
439         .slice;
440 
441     assert(pad == [
442         [0,  0, 0,  0],
443         [0,  0, 0,  0],
444 
445         [0,  1, 2,  0],
446         [0,  3, 4,  0],
447         
448         [0,  0, 0,  0],
449         [0,  0, 0,  0]]);
450 }
451 
452 /++
453 Pads with a constant value.
454 
455 Params:
456     dimensions = dimensions to pad.
457     directions = padding directions.
458         Direction can be one of the following values: `"both"`, `"pre"`, and `"post"`.
459 
460 Returns: $(LREF Concatenation)
461 
462 See_also: $(LREF ._concatenation) examples.
463 +/
464 template pad(size_t[] dimensions, string[] directions)
465     if (dimensions.length && dimensions.length == directions.length)
466 {
467     @optmath:
468 
469     /++
470     Params:
471         s = $(SUBREF slice, Slice) or ndField
472         value = initial value for padding
473         lengths = list of lengths
474     Returns: $(LREF Concatenation)
475     See_also: $(LREF ._concatenation) examples.
476     +/
477     auto pad(S, T)(S s, T value, size_t[dimensions.length] lengths...)
478     {
479         import mir.ndslice.topology: repeat;
480 
481         enum d = dimensions[$ - 1];
482         enum q = directions[$ - 1];
483         enum N = typeof(S.shape).length;
484 
485         size_t[N] len;
486         auto _len = s.shape;
487         foreach(i; Iota!(len.length))
488             static if (i != d)
489                 len[i] = _len[i];
490             else
491                 len[i] = lengths[$ - 1];
492 
493         auto p = repeat(value, len);
494         static if (q == "both")
495             auto r = concatenation!d(p, s, p);
496         else
497         static if (q == "pre")
498             auto r = concatenation!d(p, s);
499         else
500         static if (q == "post")
501             auto r = concatenation!d(s, p);
502         else
503         static assert(0, `allowed directions are "both", "pre", and "post"`);
504 
505         static if (dimensions.length == 1)
506             return r;
507         else
508             return .pad!(dimensions[0 .. $ - 1], directions[0 .. $ - 1])(r, value, lengths[0 .. $ -1]);
509     }
510 }
511 
512 ///
513 version(mir_test) unittest
514 {
515     import mir.ndslice.allocation: slice;
516     import mir.ndslice.topology: iota;
517 
518     auto pad = iota([2, 2], 1)
519         .pad!([1], ["pre"])(0, [2])
520         .slice;
521 
522     assert(pad == [
523         [0, 0,  1, 2],
524         [0, 0,  3, 4]]);
525 }
526 
527 ///
528 version(mir_test) unittest
529 {
530     import mir.ndslice.allocation: slice;
531     import mir.ndslice.topology: iota;
532 
533     auto pad = iota([2, 2], 1)
534         .pad!([0, 1], ["both", "post"])(0, [2, 1])
535         .slice;
536 
537     assert(pad == [
538         [0, 0,  0],
539         [0, 0,  0],
540 
541         [1, 2,  0],
542         [3, 4,  0],
543         
544         [0, 0,  0],
545         [0, 0,  0]]);
546 }
547 
548 /++
549 Pads with the wrap of the slice along the axis. The first values are used to pad the end and the end values are used to pad the beginning.
550 
551 Params:
552     direction = padding direction.
553         Direction can be one of the following values: `"both"`, `"pre"`, and `"post"`.
554     s = $(SUBREF slice, Slice)
555     lengths = list of lengths for each dimension. Each length must be less or equal to the corresponding slice length.
556 Returns: $(LREF Concatenation)
557 See_also: $(LREF ._concatenation) examples.
558 +/
559 auto padWrap(string direction = "both", Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) s, size_t[N] lengths...)
560 {
561     return .padWrap!([Iota!N], [Repeat!(N, direction)])(s, lengths);
562 }
563 
564 ///
565 version(mir_test) unittest
566 {
567     import mir.ndslice.allocation: slice;
568     import mir.ndslice.topology: iota;
569 
570     auto pad = iota([3], 1)
571         .padWrap([2])
572         .slice;
573 
574     assert(pad == [2, 3,  1, 2, 3,  1, 2]);
575 }
576 
577 ///
578 version(mir_test) unittest
579 {
580     import mir.ndslice.allocation: slice;
581     import mir.ndslice.topology: iota;
582 
583     auto pad = iota([2, 2], 1)
584         .padWrap([2, 1])
585         .slice;
586 
587     assert(pad == [
588         [2,  1, 2,  1],
589         [4,  3, 4,  3],
590 
591         [2,  1, 2,  1],
592         [4,  3, 4,  3],
593 
594         [2,  1, 2,  1],
595         [4,  3, 4,  3]]);
596 }
597 
598 /++
599 Pads with the wrap of the slice along the axis. The first values are used to pad the end and the end values are used to pad the beginning.
600 
601 Params:
602     dimensions = dimensions to pad.
603     directions = padding directions.
604         Direction can be one of the following values: `"both"`, `"pre"`, and `"post"`.
605 
606 Returns: $(LREF Concatenation)
607 
608 See_also: $(LREF ._concatenation) examples.
609 +/
610 template padWrap(size_t[] dimensions, string[] directions)
611     if (dimensions.length && dimensions.length == directions.length)
612 {
613     @optmath:
614 
615     /++
616     Params:
617         s = $(SUBREF slice, Slice)
618         lengths = list of lengths for each dimension. Each length must be less or equal to the corresponding slice length.
619     Returns: $(LREF Concatenation)
620     See_also: $(LREF ._concatenation) examples.
621     +/
622     auto padWrap(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) s, size_t[dimensions.length] lengths...)
623     {
624         enum d = dimensions[$ - 1];
625         enum q = directions[$ - 1];
626 
627         static if (d == 0 || kind != Contiguous)
628         {
629             alias _s = s;
630         }
631         else
632         {
633             import mir.ndslice.topology: canonical;
634             auto _s = s.canonical;
635         }
636 
637         assert(lengths[$ - 1] <= s.length!d);
638 
639         static if (dimensions.length != 1)
640             alias next = .padWrap!(dimensions[0 .. $ - 1], directions[0 .. $ - 1]);
641 
642         static if (q == "pre" || q == "both")
643         {
644             auto _pre = _s;
645             _pre.popFrontExactly!d(s.length!d - lengths[$ - 1]);
646             static if (dimensions.length == 1)
647                 alias pre = _pre;
648             else
649                 auto pre = next(_pre, lengths[0 .. $ - 1]);
650         }
651 
652         static if (q == "post" || q == "both")
653         {
654             auto _post = _s;
655             _post.popBackExactly!d(s.length!d - lengths[$ - 1]);
656             static if (dimensions.length == 1)
657                 alias post = _post;
658             else
659                 auto post = next(_post, lengths[0 .. $ - 1]);
660         }
661 
662         static if (dimensions.length == 1)
663             alias r = s;
664         else
665             auto r = next(s, lengths[0 .. $ - 1]);
666 
667         static if (q == "both")
668             return concatenation!d(pre, r, post);
669         else
670         static if (q == "pre")
671             return concatenation!d(pre, r);
672         else
673         static if (q == "post")
674             return concatenation!d(r, post);
675         else
676         static assert(0, `allowed directions are "both", "pre", and "post"`);
677     }
678 }
679 
680 ///
681 version(mir_test) unittest
682 {
683     import mir.ndslice.allocation: slice;
684     import mir.ndslice.topology: iota;
685 
686     auto pad = iota([2, 3], 1)
687         .padWrap!([1], ["pre"])([1])
688         .slice;
689 
690     assert(pad == [
691         [3,  1, 2, 3],
692         [6,  4, 5, 6]]);
693 }
694 
695 ///
696 version(mir_test) unittest
697 {
698     import mir.ndslice.allocation: slice;
699     import mir.ndslice.topology: iota;
700 
701     auto pad = iota([2, 2], 1)
702         .padWrap!([0, 1], ["both", "post"])([2, 1])
703         .slice;
704 
705     assert(pad == [
706         [1, 2,  1],
707         [3, 4,  3],
708 
709         [1, 2,  1],
710         [3, 4,  3],
711         
712         [1, 2,  1],
713         [3, 4,  3]]);
714 }
715 
716 /++
717 Pads with the reflection of the slice mirrored along the edge of the slice.
718 
719 Params:
720     direction = padding direction.
721         Direction can be one of the following values: `"both"`, `"pre"`, and `"post"`.
722     s = $(SUBREF slice, Slice)
723     lengths = list of lengths for each dimension. Each length must be less or equal to the corresponding slice length.
724 Returns: $(LREF Concatenation)
725 See_also: $(LREF ._concatenation) examples.
726 +/
727 auto padSymmetric(string direction = "both", Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) s, size_t[N] lengths...)
728 {
729     return .padSymmetric!([Iota!N], [Repeat!(N, direction)])(s, lengths);
730 }
731 
732 ///
733 version(mir_test) unittest
734 {
735     import mir.ndslice.allocation: slice;
736     import mir.ndslice.topology: iota;
737 
738     auto pad = iota([3], 1)
739         .padSymmetric([2])
740         .slice;
741 
742     assert(pad == [2, 1,  1, 2, 3,  3, 2]);
743 }
744 
745 ///
746 version(mir_test) unittest
747 {
748     import mir.ndslice.allocation: slice;
749     import mir.ndslice.topology: iota;
750 
751     auto pad = iota([2, 2], 1)
752         .padSymmetric([2, 1])
753         .slice;
754 
755     assert(pad == [
756         [3,  3, 4,  4],
757         [1,  1, 2,  2],
758 
759         [1,  1, 2,  2],
760         [3,  3, 4,  4],
761 
762         [3,  3, 4,  4],
763         [1,  1, 2,  2]]);
764 }
765 
766 /++
767 Pads with the reflection of the slice mirrored along the edge of the slice.
768 
769 Params:
770     dimensions = dimensions to pad.
771     directions = padding directions.
772         Direction can be one of the following values: `"both"`, `"pre"`, and `"post"`.
773 
774 Returns: $(LREF Concatenation)
775 
776 See_also: $(LREF ._concatenation) examples.
777 +/
778 template padSymmetric(size_t[] dimensions, string[] directions)
779     if (dimensions.length && dimensions.length == directions.length)
780 {
781     @optmath:
782 
783     /++
784     Params:
785         s = $(SUBREF slice, Slice)
786         lengths = list of lengths for each dimension. Each length must be less or equal to the corresponding slice length.
787     Returns: $(LREF Concatenation)
788     See_also: $(LREF ._concatenation) examples.
789     +/
790     auto padSymmetric(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) s, size_t[dimensions.length] lengths...)
791     {
792         enum d = dimensions[$ - 1];
793         enum q = directions[$ - 1];
794         import mir.ndslice.dynamic: reversed;
795 
796 
797         static if (kind == Contiguous)
798         {
799             import mir.ndslice.topology: canonical;
800             auto __s = s.canonical;
801         }
802         else
803         {
804             alias __s = s;
805         }
806 
807         static if (kind == Universal || d != N - 1)
808         {
809             auto _s = __s.reversed!d;
810         }
811         else
812         static if (N == 1)
813         {
814             import mir.ndslice.topology: retro;
815             auto _s = s.retro;
816         }
817         else
818         {
819             import mir.ndslice.topology: retro;
820             auto _s = __s.retro.reversed!(Iota!d, Iota!(d + 1, N));
821         }
822 
823         assert(lengths[$ - 1] <= s.length!d);
824 
825         static if (dimensions.length != 1)
826             alias next = .padSymmetric!(dimensions[0 .. $ - 1], directions[0 .. $ - 1]);
827 
828         static if (q == "pre" || q == "both")
829         {
830             auto _pre = _s;
831             _pre.popFrontExactly!d(s.length!d - lengths[$ - 1]);
832             static if (dimensions.length == 1)
833                 alias pre = _pre;
834             else
835                 auto pre = next(_pre, lengths[0 .. $ - 1]);
836         }
837 
838         static if (q == "post" || q == "both")
839         {
840             auto _post = _s;
841             _post.popBackExactly!d(s.length!d - lengths[$ - 1]);
842             static if (dimensions.length == 1)
843                 alias post = _post;
844             else
845                 auto post = next(_post, lengths[0 .. $ - 1]);
846         }
847 
848         static if (dimensions.length == 1)
849             alias r = s;
850         else
851             auto r = next(s, lengths[0 .. $ - 1]);
852 
853         static if (q == "both")
854             return concatenation!d(pre, r, post);
855         else
856         static if (q == "pre")
857             return concatenation!d(pre, r);
858         else
859         static if (q == "post")
860             return concatenation!d(r, post);
861         else
862         static assert(0, `allowed directions are "both", "pre", and "post"`);
863     }
864 }
865 
866 ///
867 version(mir_test) unittest
868 {
869     import mir.ndslice.allocation: slice;
870     import mir.ndslice.topology: iota;
871 
872     auto pad = iota([2, 3], 1)
873         .padSymmetric!([1], ["pre"])([2])
874         .slice;
875 
876     assert(pad == [
877         [2, 1,  1, 2, 3],
878         [5, 4,  4, 5, 6]]);
879 }
880 
881 ///
882 version(mir_test) unittest
883 {
884     import mir.ndslice.allocation: slice;
885     import mir.ndslice.topology: iota;
886 
887     auto pad = iota([2, 2], 1)
888         .padSymmetric!([0, 1], ["both", "post"])([2, 1])
889         .slice;
890 
891     assert(pad == [
892         [3, 4,  4],
893         [1, 2,  2],
894 
895         [1, 2,  2],
896         [3, 4,  4],
897         
898         [3, 4,  4],
899         [1, 2,  2]]);
900 }
901 
902 /++
903 Pads with the edge values of slice.
904 
905 Params:
906     direction = padding direction.
907         Direction can be one of the following values: `"both"`, `"pre"`, and `"post"`.
908     s = $(SUBREF slice, Slice)
909     lengths = list of lengths for each dimension.
910 Returns: $(LREF Concatenation)
911 See_also: $(LREF ._concatenation) examples.
912 +/
913 auto padEdge(string direction = "both", Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) s, size_t[N] lengths...)
914 {
915     return .padEdge!([Iota!N], [Repeat!(N, direction)])(s, lengths);
916 }
917 
918 ///
919 version(mir_test) unittest
920 {
921     import mir.ndslice.allocation: slice;
922     import mir.ndslice.topology: iota;
923 
924     auto pad = iota([3], 1)
925         .padEdge([2])
926         .slice;
927 
928     assert(pad == [1, 1,  1, 2, 3,  3, 3]);
929 }
930 
931 ///
932 version(mir_test) unittest
933 {
934     import mir.ndslice.allocation: slice;
935     import mir.ndslice.topology: iota;
936 
937     auto pad = iota([2, 2], 1)
938         .padEdge([2, 1])
939         .slice;
940 
941     assert(pad == [
942         [1,  1, 2,  2],
943         [1,  1, 2,  2],
944 
945         [1,  1, 2,  2],
946         [3,  3, 4,  4],
947 
948         [3,  3, 4,  4],
949         [3,  3, 4,  4]]);
950 }
951 
952 /++
953 Pads with the edge values of slice.
954 
955 Params:
956     dimensions = dimensions to pad.
957     directions = padding directions.
958         Direction can be one of the following values: `"both"`, `"pre"`, and `"post"`.
959 
960 Returns: $(LREF Concatenation)
961 
962 See_also: $(LREF ._concatenation) examples.
963 +/
964 template padEdge(size_t[] dimensions, string[] directions)
965     if (dimensions.length && dimensions.length == directions.length)
966 {
967     @optmath:
968 
969     /++
970     Params:
971         s = $(SUBREF slice, Slice)
972         lengths = list of lengths for each dimension.
973     Returns: $(LREF Concatenation)
974     See_also: $(LREF ._concatenation) examples.
975     +/
976     auto padEdge(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) s, size_t[dimensions.length] lengths...)
977     {
978         enum d = dimensions[$ - 1];
979         enum q = directions[$ - 1];
980 
981         static if (kind == Universal)
982         {
983             alias _s = s;
984         }
985         else
986         static if (d != N - 1)
987         {
988             import mir.ndslice.topology: canonical;
989             auto _s = s.canonical;
990         }
991         else
992         {
993             import mir.ndslice.topology: universal;
994             auto _s = s.universal;
995         }
996 
997         static if (dimensions.length != 1)
998             alias next = .padEdge!(dimensions[0 .. $ - 1], directions[0 .. $ - 1]);
999 
1000         static if (q == "pre" || q == "both")
1001         {
1002             auto _pre = _s;
1003             _pre._strides[d] = 0;
1004             _pre._lengths[d] = lengths[$ - 1];
1005             static if (dimensions.length == 1)
1006                 alias pre = _pre;
1007             else
1008                 auto pre = next(_pre, lengths[0 .. $ - 1]);
1009 
1010         }
1011 
1012         static if (q == "post" || q == "both")
1013         {
1014             auto _post = _s;
1015             _post._iterator += _post.backIndex!d;
1016             _post._strides[d] = 0;
1017             _post._lengths[d] = lengths[$ - 1];
1018             static if (dimensions.length == 1)
1019                 alias post = _post;
1020             else
1021                 auto post = next(_post, lengths[0 .. $ - 1]);
1022         }
1023 
1024         static if (dimensions.length == 1)
1025             alias r = s;
1026         else
1027             auto r = next( s, lengths[0 .. $ - 1]);
1028 
1029         static if (q == "both")
1030             return concatenation!d(pre, r, post);
1031         else
1032         static if (q == "pre")
1033             return concatenation!d(pre, r);
1034         else
1035         static if (q == "post")
1036             return concatenation!d(r, post);
1037         else
1038         static assert(0, `allowed directions are "both", "pre", and "post"`);
1039     }
1040 }
1041 
1042 ///
1043 version(mir_test) unittest
1044 {
1045     import mir.ndslice.allocation: slice;
1046     import mir.ndslice.topology: iota;
1047 
1048     auto pad = iota([2, 3], 1)
1049         .padEdge!([0], ["pre"])([2])
1050         .slice;
1051 
1052     assert(pad == [
1053         [1, 2, 3],
1054         [1, 2, 3],
1055         
1056         [1, 2, 3],
1057         [4, 5, 6]]);
1058 }
1059 
1060 ///
1061 version(mir_test) unittest
1062 {
1063     import mir.ndslice.allocation: slice;
1064     import mir.ndslice.topology: iota;
1065 
1066     auto pad = iota([2, 2], 1)
1067         .padEdge!([0, 1], ["both", "post"])([2, 1])
1068         .slice;
1069 
1070     assert(pad == [
1071         [1, 2,  2],
1072         [1, 2,  2],
1073 
1074         [1, 2,  2],
1075         [3, 4,  4],
1076 
1077         [3, 4,  4],
1078         [3, 4,  4]]);
1079 }
1080 
1081 /++
1082 Iterates 1D fragments in $(SUBREF slice, Slice) or $(LREF Concatenation) in optimal for buffering way.
1083 
1084 See_also: $(LREF ._concatenation) examples.
1085 +/
1086 template forEachFragment(alias pred)
1087 {
1088     @optmath:
1089 
1090     import mir.functional: naryFun;
1091     static if (__traits(isSame, naryFun!pred, pred))
1092     {
1093         /++
1094         Specialization for slices
1095         Params:
1096             sl = $(SUBREF slice, Slice)
1097         +/
1098         void forEachFragment(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) sl)
1099         {
1100             static if (N == 1)
1101             {
1102                 pred(sl);
1103             }
1104             else
1105             static if (kind == Contiguous)
1106             {
1107                 import mir.ndslice.topology: flattened;
1108                 pred(sl.flattened);
1109             }
1110             else
1111             {
1112                 if (!sl.empty) do
1113                 {
1114                     .forEachFragment!pred(sl.front);
1115                     sl.popFront;
1116                 }
1117                 while(!sl.empty);
1118             }
1119         }
1120 
1121         /++
1122         Specialization for concatenations
1123         Params:
1124             st = $(LREF Concatenation)
1125         +/
1126         void forEachFragment(size_t dim, Slices...)(Concatenation!(dim, Slices) st)
1127         {
1128             static if (dim == 0)
1129             {
1130                foreach (i, ref slice; st._slices)
1131                     .forEachFragment!pred(slice);
1132             }
1133             else
1134             {
1135                 if (!st.empty) do
1136                 {
1137                     st.applyFront!(0, .forEachFragment!pred);
1138                     st.popFront;
1139                 }
1140                 while(!st.empty);
1141             }
1142         }
1143     }
1144     else
1145         alias forEachFragment = .forEachFragment!(naryFun!pred);
1146 }
1147 
1148 /++
1149 Iterates elements in $(SUBREF slice, Slice) or $(LREF Concatenation)
1150 until pred returns true.
1151 
1152 Returns: false if pred returned false for all elements and true otherwise.
1153 
1154 See_also: $(LREF ._concatenation) examples.
1155 +/
1156 template until(alias pred)
1157 {
1158     @optmath:
1159 
1160     import mir.functional: naryFun;
1161     static if (__traits(isSame, naryFun!pred, pred))
1162     {
1163         /++
1164         Specialization for slices
1165         Params:
1166             sl = $(SUBREF slice, Slice)
1167         +/
1168         bool until(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) sl)
1169         {
1170             static if (N == 1)
1171             {
1172                 pragma(inline, false);
1173                 alias f = pred;
1174             }
1175             else
1176                 alias f = .until!pred;
1177             if (!sl.empty) do
1178             {
1179                 if (f(sl.front))
1180                     return true;
1181                 sl.popFront;
1182             }
1183             while(!sl.empty);
1184             return false;
1185         }
1186 
1187         /++
1188         Specialization for concatenations
1189         Params:
1190             st = $(LREF Concatenation)
1191         +/
1192         bool until(size_t dim, Slices...)(Concatenation!(dim, Slices) st)
1193         {
1194             static if (dim == 0)
1195             {
1196                foreach (i, ref slice; st._slices)
1197                {
1198                     if (.until!pred(slice))
1199                         return true;
1200                }
1201             }
1202             else
1203             {
1204                 if (!st.empty) do
1205                 {
1206                     if (st.applyFront!(0, .until!pred))
1207                         return true;
1208                     st.popFront;
1209                 }
1210                 while(!st.empty);
1211             }
1212             return false;
1213         }
1214     }
1215     else
1216         alias until = .until!(naryFun!pred);
1217 }