The OpenD Programming Language

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