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 }