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 }