1 /++ 2 $(SCRIPT inhibitQuickIndex = 1;) 3 4 This is a submodule of $(MREF mir, ndslice). 5 6 Operators only change strides and lengths of a slice. 7 The range of a slice remains unmodified. 8 All operators return slice as the type of the argument, maybe except slice kind. 9 10 $(BOOKTABLE $(H2 Transpose operators), 11 12 $(TR $(TH Function Name) $(TH Description)) 13 $(T2 transposed, Permutes dimensions. $(BR) 14 `iota(3, 4, 5, 6, 7).transposed!(4, 0, 1).shape` returns `[7, 3, 4, 5, 6]`.) 15 $(T2 swapped, Swaps dimensions $(BR) 16 `iota(3, 4, 5).swapped!(1, 2).shape` returns `[3, 5, 4]`.) 17 $(T2 everted, Reverses the order of dimensions $(BR) 18 `iota(3, 4, 5).everted.shape` returns `[5, 4, 3]`.) 19 ) 20 See also $(SUBREF topology, evertPack). 21 22 $(BOOKTABLE $(H2 Iteration operators), 23 24 $(TR $(TH Function Name) $(TH Description)) 25 $(T2 strided, Multiplies the stride of a selected dimension by a factor.$(BR) 26 `iota(13, 40).strided!(0, 1)(2, 5).shape` equals to `[7, 8]`.) 27 $(T2 reversed, Reverses the direction of iteration for selected dimensions. $(BR) 28 `slice.reversed!0` returns the slice with reversed direction of iteration for top level dimension.) 29 $(T2 allReversed, Reverses the direction of iteration for all dimensions. $(BR) 30 `iota(4, 5).allReversed` equals to `20.iota.retro.sliced(4, 5)`.) 31 ) 32 33 $(BOOKTABLE $(H2 Other operators), 34 $(TR $(TH Function Name) $(TH Description)) 35 36 $(T2 rotated, Rotates two selected dimensions by `k*90` degrees. $(BR) 37 `iota(2, 3).rotated` equals to `[[2, 5], [1, 4], [0, 3]]`.) 38 $(T2 dropToHypercube, Returns maximal multidimensional cube of a slice.) 39 $(T2 normalizeStructure, Reverses iteration order for dimensions with negative strides, they become not negative; 40 and sorts dimensions according to the strides, dimensions with larger strides are going first.) 41 ) 42 43 $(H2 Bifacial operators) 44 45 Some operators are bifacial, 46 i.e. they have two versions: one with template parameters, and another one 47 with function parameters. Versions with template parameters are preferable 48 because they allow compile time checks and can be optimized better. 49 50 $(BOOKTABLE , 51 52 $(TR $(TH Function Name) $(TH Variadic) $(TH Template) $(TH Function)) 53 $(T4 swapped, No, `slice.swapped!(2, 3)`, `slice.swapped(2, 3)`) 54 $(T4 rotated, No, `slice.rotated!(2, 3)(-1)`, `slice.rotated(2, 3, -1)`) 55 $(T4 strided, Yes/No, `slice.strided!(1, 2)(20, 40)`, `slice.strided(1, 20).strided(2, 40)`) 56 $(T4 transposed, Yes, `slice.transposed!(1, 4, 3)`, `slice.transposed(1, 4, 3)`) 57 $(T4 reversed, Yes, `slice.reversed!(0, 2)`, `slice.reversed(0, 2)`) 58 ) 59 60 Bifacial interface of $(LREF drop), $(LREF dropBack) 61 $(LREF dropExactly), and $(LREF dropBackExactly) 62 is identical to that of $(LREF strided). 63 64 Bifacial interface of $(LREF dropOne) and $(LREF dropBackOne) 65 is identical to that of $(LREF reversed). 66 67 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) 68 69 Copyright: 2020 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments 70 71 Authors: Ilya Yaroshenko 72 73 Macros: 74 SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) 75 T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) 76 T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4)) 77 +/ 78 module mir.ndslice.dynamic; 79 80 81 import std.traits; 82 import std.meta; 83 84 import mir.math.common: optmath; 85 import mir.internal.utility: Iota; 86 import mir.ndslice.internal; 87 import mir.ndslice.slice; 88 import mir.utility; 89 90 @optmath: 91 92 /++ 93 Reverses iteration order for dimensions with negative strides, they become not negative; 94 and sorts dimensions according to the strides, dimensions with larger strides are going first. 95 96 Params: 97 slice = a slice to normalize dimension 98 Returns: 99 `true` if the slice can be safely casted to $(SUBREF slice, Contiguous) kind using $(SUBREF topology, assumeContiguous) and false otherwise. 100 +/ 101 bool normalizeStructure(Iterator, size_t N, SliceKind kind)(ref Slice!(Iterator, N, kind) slice) 102 { 103 static if (kind == Contiguous) 104 { 105 return true; 106 } 107 else 108 { 109 import mir.utility: min; 110 enum Y = min(slice.S, N); 111 foreach(i; Iota!Y) 112 if (slice._stride!i < 0) 113 slice = slice.reversed!i; 114 static if (N == 1) 115 return slice._stride!0 == 1; 116 else 117 static if (N == 2 && kind == Canonical) 118 { 119 return slice._stride!0 == slice.length!1; 120 } 121 else 122 { 123 import mir.series: series, sort; 124 import mir.ndslice.topology: zip, iota; 125 auto l = slice._lengths[0 .. Y]; 126 auto s = slice._strides[0 .. Y]; 127 s.series(l).sort!"a > b"; 128 return slice.shape.iota.strides == slice.strides; 129 } 130 } 131 } 132 133 /// 134 version(mir_test) unittest 135 { 136 import mir.ndslice.topology: iota; 137 138 auto g = iota(2, 3); //contiguous 139 auto c = g.reversed!0; //canonical 140 auto u = g.transposed.allReversed; //universal 141 142 assert(g.normalizeStructure); 143 assert(c.normalizeStructure); 144 assert(u.normalizeStructure); 145 146 assert(c == g); 147 assert(u == g); 148 149 c.popFront!1; 150 u.popFront!1; 151 152 assert(!c.normalizeStructure); 153 assert(!u.normalizeStructure); 154 } 155 156 private enum _swappedCode = q{ 157 with (slice) 158 { 159 auto tl = _lengths[dimensionA]; 160 auto ts = _strides[dimensionA]; 161 _lengths[dimensionA] = _lengths[dimensionB]; 162 _strides[dimensionA] = _strides[dimensionB]; 163 _lengths[dimensionB] = tl; 164 _strides[dimensionB] = ts; 165 } 166 return slice; 167 }; 168 169 /++ 170 Swaps two dimensions. 171 172 Params: 173 slice = input slice 174 dimensionA = first dimension 175 dimensionB = second dimension 176 Returns: 177 n-dimensional slice 178 See_also: $(LREF everted), $(LREF transposed) 179 +/ 180 template swapped(size_t dimensionA, size_t dimensionB) 181 { 182 /// 183 @optmath auto swapped(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice) 184 { 185 static if (kind == Universal || kind == Canonical && dimensionA + 1 < N && dimensionB + 1 < N) 186 { 187 alias slice = _slice; 188 } 189 else static if (dimensionA + 1 < N && dimensionB + 1 < N) 190 { 191 import mir.ndslice.topology: canonical; 192 auto slice = _slice.canonical; 193 } 194 else 195 { 196 import mir.ndslice.topology: universal; 197 auto slice = _slice.universal; 198 } 199 { 200 enum i = 0; 201 alias dimension = dimensionA; 202 mixin DimensionCTError; 203 } 204 { 205 enum i = 1; 206 alias dimension = dimensionB; 207 mixin DimensionCTError; 208 } 209 mixin (_swappedCode); 210 } 211 } 212 213 /// ditto 214 auto swapped(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice, size_t dimensionA, size_t dimensionB) 215 { 216 import mir.ndslice.topology: universal; 217 auto slice = _slice.universal; 218 { 219 alias dimension = dimensionA; 220 mixin (DimensionRTError); 221 } 222 { 223 alias dimension = dimensionB; 224 mixin (DimensionRTError); 225 } 226 mixin (_swappedCode); 227 } 228 229 /// ditto 230 Slice!(Iterator, 2, Universal) swapped(Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) slice) 231 { 232 return slice.swapped!(0, 1); 233 } 234 235 /// Template 236 @safe @nogc pure nothrow version(mir_test) unittest 237 { 238 import mir.ndslice.slice; 239 import mir.ndslice.topology: iota; 240 241 assert(iota(3, 4, 5, 6) 242 .swapped!(2, 1) 243 .shape == cast(size_t[4])[3, 5, 4, 6]); 244 245 assert(iota(3, 4, 5, 6) 246 .swapped!(3, 1) 247 .shape == cast(size_t[4])[3, 6, 5, 4]); 248 } 249 250 /// Function 251 @safe @nogc pure nothrow version(mir_test) unittest 252 { 253 import mir.ndslice.slice; 254 import mir.ndslice.topology: iota; 255 256 assert(iota(3, 4, 5, 6) 257 .swapped(1, 2) 258 .shape == cast(size_t[4])[3, 5, 4, 6]); 259 260 assert(iota(3, 4, 5, 6) 261 .swapped(1, 3) 262 .shape == cast(size_t[4])[3, 6, 5, 4]); 263 } 264 265 /// 2D 266 @safe @nogc pure nothrow version(mir_test) unittest 267 { 268 import mir.ndslice.slice; 269 import mir.ndslice.topology: iota; 270 assert(iota(3, 4) 271 .swapped 272 .shape == cast(size_t[2])[4, 3]); 273 } 274 275 private enum _rotatedCode = q{ 276 k &= 0b11; 277 if (k == 0) 278 return slice; 279 if (k == 2) 280 return slice.allReversed; 281 static if (__traits(compiles, { enum _enum = dimensionA + dimensionB; })) 282 { 283 slice = slice.swapped!(dimensionA, dimensionB); 284 if (k == 1) 285 return slice.reversed!dimensionA; 286 else 287 return slice.reversed!dimensionB; 288 } 289 else 290 { 291 slice = slice.swapped (dimensionA, dimensionB); 292 if (k == 1) 293 return slice.reversed(dimensionA); 294 else 295 return slice.reversed(dimensionB); 296 } 297 }; 298 299 /++ 300 Rotates two selected dimensions by `k*90` degrees. 301 The order of dimensions is important. 302 If the slice has two dimensions, the default direction is counterclockwise. 303 304 Params: 305 slice = input slice 306 dimensionA = first dimension 307 dimensionB = second dimension 308 k = rotation counter, can be negative 309 Returns: 310 n-dimensional slice 311 +/ 312 template rotated(size_t dimensionA, size_t dimensionB) 313 { 314 /// 315 @optmath auto rotated(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice, sizediff_t k = 1) 316 { 317 static if (kind == Universal || kind == Canonical && dimensionA + 1 < N && dimensionB + 1 < N) 318 { 319 alias slice = _slice; 320 } 321 else static if (dimensionA + 1 < N && dimensionB + 1 < N) 322 { 323 import mir.ndslice.topology: canonical; 324 auto slice = _slice.canonical; 325 } 326 else 327 { 328 import mir.ndslice.topology: universal; 329 auto slice = _slice.universal; 330 } 331 { 332 enum i = 0; 333 alias dimension = dimensionA; 334 mixin DimensionCTError; 335 } 336 { 337 enum i = 1; 338 alias dimension = dimensionB; 339 mixin DimensionCTError; 340 } 341 mixin (_rotatedCode); 342 } 343 } 344 345 /// ditto 346 auto rotated(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice, size_t dimensionA, size_t dimensionB, sizediff_t k = 1) 347 { 348 import mir.ndslice.topology: universal; 349 auto slice = _slice.universal; 350 { 351 alias dimension = dimensionA; 352 mixin (DimensionRTError); 353 } 354 { 355 alias dimension = dimensionB; 356 mixin (DimensionRTError); 357 } 358 mixin (_rotatedCode); 359 } 360 361 /// ditto 362 Slice!(Iterator, 2, Universal) rotated(Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) slice, sizediff_t k = 1) 363 { 364 return .rotated!(0, 1)(slice, k); 365 } 366 367 /// 368 @safe pure nothrow version(mir_test) unittest 369 { 370 import mir.ndslice.slice; 371 import mir.ndslice.topology: iota; 372 auto slice = iota(2, 3); 373 374 auto a = [[0, 1, 2], 375 [3, 4, 5]]; 376 377 auto b = [[2, 5], 378 [1, 4], 379 [0, 3]]; 380 381 auto c = [[5, 4, 3], 382 [2, 1, 0]]; 383 384 auto d = [[3, 0], 385 [4, 1], 386 [5, 2]]; 387 388 assert(slice.rotated ( 4) == a); 389 assert(slice.rotated!(0, 1)(-4) == a); 390 assert(slice.rotated (1, 0, 8) == a); 391 392 assert(slice.rotated == b); 393 assert(slice.rotated!(0, 1)(-3) == b); 394 assert(slice.rotated (1, 0, 3) == b); 395 396 assert(slice.rotated ( 6) == c); 397 assert(slice.rotated!(0, 1)( 2) == c); 398 assert(slice.rotated (0, 1, -2) == c); 399 400 assert(slice.rotated ( 7) == d); 401 assert(slice.rotated!(0, 1)( 3) == d); 402 assert(slice.rotated (1, 0, ) == d); 403 } 404 405 /++ 406 Reverses the order of dimensions. 407 408 Params: 409 _slice = input slice 410 Returns: 411 n-dimensional slice 412 See_also: $(LREF swapped), $(LREF transposed) 413 +/ 414 auto everted(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice) 415 { 416 static if (kind == Universal) 417 { 418 alias slice = _slice; 419 } 420 else 421 { 422 import mir.ndslice.topology: universal; 423 auto slice = _slice.universal; 424 } 425 with(slice) foreach (i; Iota!(N / 2)) 426 { 427 swap(_lengths[i], _lengths[N - i - 1]); 428 swap(_strides[i], _strides[N - i - 1]); 429 } 430 return slice; 431 } 432 433 /// 434 @safe @nogc pure nothrow version(mir_test) unittest 435 { 436 import mir.ndslice.slice; 437 import mir.ndslice.topology: iota; 438 assert(iota(3, 4, 5) 439 .everted 440 .shape == cast(size_t[3])[5, 4, 3]); 441 } 442 443 private enum _transposedCode = q{ 444 size_t[typeof(slice).N] lengths_; 445 ptrdiff_t[max(typeof(slice).S, size_t(1))] strides_; 446 with(slice) foreach (i; Iota!N) 447 { 448 lengths_[i] = _lengths[perm[i]]; 449 static if (i < typeof(slice).S) 450 strides_[i] = _strides[perm[i]]; 451 } 452 with(slice) foreach (i; Iota!(N, slice.N)) 453 { 454 lengths_[i] = _lengths[i]; 455 static if (i < typeof(slice).S) 456 strides_[i] = _strides[i]; 457 } 458 return typeof(slice)(lengths_, strides_[0 .. typeof(slice).S], slice._iterator); 459 }; 460 461 package size_t[N] completeTranspose(size_t N)(size_t[] dimensions) 462 { 463 assert(dimensions.length <= N); 464 size_t[N] ctr; 465 uint[N] mask; 466 foreach (i, ref dimension; dimensions) 467 { 468 mask[dimension] = true; 469 ctr[i] = dimension; 470 } 471 size_t j = dimensions.length; 472 foreach (i, e; mask) 473 if (e == false) 474 ctr[j++] = i; 475 return ctr; 476 } 477 478 /++ 479 N-dimensional transpose operator. 480 Brings selected dimensions to the first position. 481 Params: 482 slice = input slice 483 Dimensions = indices of dimensions to be brought to the first position 484 dimensions = indices of dimensions to be brought to the first position 485 Returns: 486 n-dimensional slice 487 See_also: $(LREF swapped), $(LREF everted) 488 +/ 489 template transposed(Dimensions...) 490 if (Dimensions.length) 491 { 492 static if (allSatisfy!(isSize_t, Dimensions)) 493 /// 494 @optmath auto transposed(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice) 495 { 496 import mir.algorithm.iteration: any; 497 enum s = N; 498 static if ([Dimensions] == [Iota!(Dimensions.length)]) 499 { 500 return _slice; 501 } 502 else 503 { 504 import core.lifetime: move; 505 enum hasRowStride = [Dimensions].any!(a => a + 1 == s); 506 static if (kind == Universal || kind == Canonical && !hasRowStride) 507 { 508 alias slice = _slice; 509 } 510 else 511 static if (hasRowStride) 512 { 513 import mir.ndslice.topology: universal; 514 auto slice = _slice.move.universal; 515 } 516 else 517 { 518 import mir.ndslice.topology: canonical; 519 auto slice = _slice.move.canonical; 520 } 521 mixin DimensionsCountCTError; 522 foreach (i, dimension; Dimensions) 523 mixin DimensionCTError; 524 static assert(isValidPartialPermutation!(N)([Dimensions]), 525 "Failed to complete permutation of dimensions " ~ Dimensions.stringof 526 ~ tailErrorMessage!()); 527 enum perm = completeTranspose!(N)([Dimensions]); 528 static assert(perm.isPermutation, __PRETTY_FUNCTION__ ~ ": internal error."); 529 mixin (_transposedCode); 530 } 531 } 532 else 533 alias transposed = .transposed!(staticMap!(toSize_t, Dimensions)); 534 } 535 536 ///ditto 537 auto transposed(Iterator, size_t N, SliceKind kind, size_t M)(Slice!(Iterator, N, kind) _slice, size_t[M] dimensions...) 538 { 539 import core.lifetime: move; 540 import mir.ndslice.topology: universal; 541 auto slice = _slice.move.universal; 542 543 mixin (DimensionsCountRTError); 544 foreach (dimension; dimensions) 545 mixin (DimensionRTError); 546 assert(dimensions.isValidPartialPermutation!(N), 547 "Failed to complete permutation of dimensions." 548 ~ tailErrorMessage!()); 549 immutable perm = completeTranspose!(N)(dimensions); 550 assert(perm.isPermutation, __PRETTY_FUNCTION__ ~ ": internal error."); 551 mixin (_transposedCode); 552 } 553 554 ///ditto 555 Slice!(Iterator, 2, Universal) transposed(Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) slice) 556 { 557 return .transposed!(1, 0)(slice); 558 } 559 560 /// Template 561 @safe @nogc pure nothrow version(mir_test) unittest 562 { 563 import mir.ndslice.slice; 564 import mir.ndslice.topology: iota; 565 566 assert(iota(3, 4, 5, 6, 7) 567 .transposed!(3, 1, 0) 568 .shape == cast(size_t[5])[6, 4, 3, 5, 7]); 569 570 assert(iota(3, 4, 5, 6, 7) 571 .transposed!(4, 1, 0) 572 .shape == cast(size_t[5])[7, 4, 3, 5, 6]); 573 } 574 575 /// Function 576 @safe @nogc pure nothrow version(mir_test) unittest 577 { 578 import mir.ndslice.slice; 579 import mir.ndslice.topology: iota; 580 581 assert(iota(3, 4, 5, 6, 7) 582 .transposed(3, 1, 0) 583 .shape == cast(size_t[5])[6, 4, 3, 5, 7]); 584 585 assert(iota(3, 4, 5, 6, 7) 586 .transposed(4, 1, 0) 587 .shape == cast(size_t[5])[7, 4, 3, 5, 6]); 588 } 589 590 /// Single-argument function 591 @safe @nogc pure nothrow version(mir_test) unittest 592 { 593 import mir.ndslice.slice; 594 import mir.ndslice.topology: iota; 595 596 assert(iota(3, 4, 5, 6, 7) 597 .transposed(3) 598 .shape == cast(size_t[5])[6, 3, 4, 5, 7]); 599 600 assert(iota(3, 4, 5, 6, 7) 601 .transposed(4) 602 .shape == cast(size_t[5])[7, 3, 4, 5, 6]); 603 } 604 605 /// _2-dimensional transpose 606 @safe @nogc pure nothrow version(mir_test) unittest 607 { 608 import mir.ndslice.slice; 609 import mir.ndslice.topology: iota; 610 assert(iota(3, 4) 611 .transposed 612 .shape == cast(size_t[2])[4, 3]); 613 } 614 615 private enum _reversedCode = q{ 616 with (slice) 617 { 618 if (_lengths[dimension]) 619 _iterator += _strides[dimension] * (_lengths[dimension] - 1); 620 _strides[dimension] = -_strides[dimension]; 621 } 622 }; 623 624 /++ 625 Reverses the direction of iteration for all dimensions. 626 Params: 627 _slice = input slice 628 Returns: 629 n-dimensional slice 630 +/ 631 Slice!(Iterator, N, Universal) allReversed(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice) 632 { 633 import core.lifetime: move; 634 import mir.ndslice.topology: universal; 635 auto slice = _slice.move.universal; 636 foreach (dimension; Iota!N) 637 { 638 mixin (_reversedCode); 639 } 640 return slice; 641 } 642 643 /// 644 @safe @nogc pure nothrow 645 version(mir_test) unittest 646 { 647 import mir.ndslice.slice; 648 import mir.ndslice.topology: iota, retro; 649 assert(iota(4, 5).allReversed == iota(4, 5).retro); 650 } 651 652 /++ 653 Reverses the direction of iteration for selected dimensions. 654 655 Params: 656 _slice = input slice 657 Dimensions = indices of dimensions to reverse order of iteration 658 dimensions = indices of dimensions to reverse order of iteration 659 Returns: 660 n-dimensional slice 661 +/ 662 template reversed(Dimensions...) 663 if (Dimensions.length) 664 { 665 static if (allSatisfy!(isSize_t, Dimensions)) 666 /// 667 @optmath auto reversed(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice) @trusted 668 { 669 import mir.algorithm.iteration: any; 670 enum s = N; 671 enum hasRowStride = [Dimensions].sliced.any!(a => a + 1 == s); 672 static if (kind == Universal || kind == Canonical && !hasRowStride) 673 { 674 alias slice = _slice; 675 } 676 else 677 static if (hasRowStride) 678 { 679 import mir.ndslice.topology: universal; 680 auto slice = _slice.universal; 681 } 682 else 683 { 684 import mir.ndslice.topology: canonical; 685 auto slice = _slice.canonical; 686 } 687 foreach (i, dimension; Dimensions) 688 { 689 mixin DimensionCTError; 690 mixin (_reversedCode); 691 } 692 return slice; 693 } 694 else 695 alias reversed = .reversed!(staticMap!(toSize_t, Dimensions)); 696 } 697 698 ///ditto 699 Slice!(Iterator, N, Universal) reversed(Iterator, size_t N, SliceKind kind, size_t M)(Slice!(Iterator, N, kind) _slice, size_t[M] dimensions...) 700 @trusted 701 if (M) 702 { 703 import mir.ndslice.topology: universal; 704 auto slice = _slice.universal; 705 foreach (dimension; dimensions) 706 mixin (DimensionRTError); 707 foreach (i; Iota!(0, M)) 708 { 709 auto dimension = dimensions[i]; 710 mixin (_reversedCode); 711 } 712 return slice; 713 } 714 715 /// ditto 716 auto reversed(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) 717 { 718 return .reversed!0(slice); 719 } 720 721 /// 722 @safe pure nothrow version(mir_test) unittest 723 { 724 import mir.ndslice.topology: iota; 725 726 auto slice = iota([2, 2], 1); 727 assert(slice == [[1, 2], [3, 4]]); 728 729 // Default 730 assert(slice.reversed == [[3, 4], [1, 2]]); 731 732 // Template 733 assert(slice.reversed! 0 == [[3, 4], [1, 2]]); 734 assert(slice.reversed! 1 == [[2, 1], [4, 3]]); 735 assert(slice.reversed!(0, 1) == [[4, 3], [2, 1]]); 736 assert(slice.reversed!(1, 0) == [[4, 3], [2, 1]]); 737 assert(slice.reversed!(1, 1) == [[1, 2], [3, 4]]); 738 assert(slice.reversed!(0, 0, 0) == [[3, 4], [1, 2]]); 739 740 // Function 741 assert(slice.reversed (0) == [[3, 4], [1, 2]]); 742 assert(slice.reversed (1) == [[2, 1], [4, 3]]); 743 assert(slice.reversed (0, 1) == [[4, 3], [2, 1]]); 744 assert(slice.reversed (1, 0) == [[4, 3], [2, 1]]); 745 assert(slice.reversed (1, 1) == [[1, 2], [3, 4]]); 746 assert(slice.reversed (0, 0, 0) == [[3, 4], [1, 2]]); 747 } 748 749 /// 750 @safe pure nothrow version(mir_test) unittest 751 { 752 import mir.ndslice.topology: iota, canonical; 753 auto slice = iota([2, 2], 1).canonical; 754 assert(slice == [[1, 2], [3, 4]]); 755 756 // Template 757 assert(slice.reversed! 0 == [[3, 4], [1, 2]]); 758 assert(slice.reversed!(0, 0, 0) == [[3, 4], [1, 2]]); 759 760 // Function 761 assert(slice.reversed (0) == [[3, 4], [1, 2]]); 762 assert(slice.reversed (0, 0, 0) == [[3, 4], [1, 2]]); 763 } 764 765 @safe @nogc pure nothrow version(mir_test) unittest 766 { 767 import mir.algorithm.iteration : equal; 768 import mir.ndslice.concatenation : concatenation; 769 import mir.ndslice.slice; 770 import mir.ndslice.topology; 771 auto i0 = iota([4], 0); auto r0 = i0.retro; 772 auto i1 = iota([4], 4); auto r1 = i1.retro; 773 auto i2 = iota([4], 8); auto r2 = i2.retro; 774 auto slice = iota(3, 4).universal; 775 assert(slice .flattened.equal(concatenation(i0, i1, i2))); 776 // Template 777 assert(slice.reversed!(0) .flattened.equal(concatenation(i2, i1, i0))); 778 assert(slice.reversed!(1) .flattened.equal(concatenation(r0, r1, r2))); 779 assert(slice.reversed!(0, 1) .flattened.equal(concatenation(r2, r1, r0))); 780 assert(slice.reversed!(1, 0) .flattened.equal(concatenation(r2, r1, r0))); 781 assert(slice.reversed!(1, 1) .flattened.equal(concatenation(i0, i1, i2))); 782 assert(slice.reversed!(0, 0, 0).flattened.equal(concatenation(i2, i1, i0))); 783 // Function 784 assert(slice.reversed (0) .flattened.equal(concatenation(i2, i1, i0))); 785 assert(slice.reversed (1) .flattened.equal(concatenation(r0, r1, r2))); 786 assert(slice.reversed (0, 1) .flattened.equal(concatenation(r2, r1, r0))); 787 assert(slice.reversed (1, 0) .flattened.equal(concatenation(r2, r1, r0))); 788 assert(slice.reversed (1, 1) .flattened.equal(concatenation(i0, i1, i2))); 789 assert(slice.reversed (0, 0, 0).flattened.equal(concatenation(i2, i1, i0))); 790 } 791 792 private enum _stridedCode = q{ 793 assert(factor > 0, "factor must be positive" 794 ~ tailErrorMessage!()); 795 immutable rem = slice._lengths[dimension] % factor; 796 slice._lengths[dimension] /= factor; 797 if (slice._lengths[dimension]) //do not remove `if (...)` 798 slice._strides[dimension] *= factor; 799 if (rem) 800 slice._lengths[dimension]++; 801 }; 802 803 /++ 804 Multiplies the stride of the selected dimension by a factor. 805 806 Params: 807 Dimensions = indices of dimensions to be strided 808 dimension = indexe of a dimension to be strided 809 factor = step extension factors 810 Returns: 811 n-dimensional slice 812 +/ 813 auto strided(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice, ptrdiff_t factor) 814 { 815 import core.lifetime: move; 816 import std.meta: Repeat; 817 return move(slice).strided!(Iota!N)(Repeat!(N, factor)); 818 } 819 820 /// 821 @safe pure nothrow version(mir_test) unittest 822 { 823 import mir.ndslice.topology: iota; 824 // 0 1 2 3 825 // 4 5 6 7 826 // 8 9 10 11 827 assert(iota(3, 4).strided(2) == [[0, 2], [8, 10]]); 828 } 829 830 /// ditto 831 template strided(Dimensions...) 832 if (Dimensions.length) 833 { 834 static if (allSatisfy!(isSize_t, Dimensions)) 835 /++ 836 Params: 837 _slice = input slice 838 factors = list of step extension factors 839 +/ 840 @optmath auto strided(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice, Repeat!(Dimensions.length, ptrdiff_t) factors) 841 { 842 import mir.algorithm.iteration: any; 843 enum s = N; 844 enum hasRowStride = [Dimensions].sliced.any!(a => a + 1 == s); 845 static if (kind == Universal || kind == Canonical && !hasRowStride) 846 { 847 alias slice = _slice; 848 } 849 else 850 static if (hasRowStride) 851 { 852 import mir.ndslice.topology: universal; 853 auto slice = _slice.universal; 854 } 855 else 856 { 857 import mir.ndslice.topology: canonical; 858 auto slice = _slice.canonical; 859 } 860 foreach (i, dimension; Dimensions) 861 { 862 mixin DimensionCTError; 863 immutable factor = factors[i]; 864 mixin (_stridedCode); 865 } 866 return slice; 867 } 868 else 869 alias strided = .strided!(staticMap!(toSize_t, Dimensions)); 870 } 871 872 ///ditto 873 Slice!(Iterator, N, Universal) strided(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) _slice, size_t dimension, ptrdiff_t factor) 874 { 875 import mir.ndslice.topology: universal; 876 auto slice = _slice.universal; 877 mixin (DimensionRTError); 878 mixin (_stridedCode); 879 return slice; 880 } 881 882 /// 883 pure nothrow version(mir_test) unittest 884 { 885 import mir.ndslice.topology: iota; 886 auto slice = iota(3, 4); 887 888 assert(slice 889 == [[0,1,2,3], [4,5,6,7], [8,9,10,11]]); 890 891 // Template 892 assert(slice.strided!0(2) 893 == [[0,1,2,3], [8,9,10,11]]); 894 895 assert(slice.strided!1(3) 896 == [[0, 3], [4, 7], [8, 11]]); 897 898 assert(slice.strided!(0, 1)(2, 3) 899 == [[0, 3], [8, 11]]); 900 901 // Function 902 assert(slice.strided(0, 2) 903 == [[0,1,2,3], [8,9,10,11]]); 904 905 assert(slice.strided(1, 3) 906 == [[0, 3], [4, 7], [8, 11]]); 907 908 assert(slice.strided(0, 2).strided(1, 3) 909 == [[0, 3], [8, 11]]); 910 } 911 912 /// 913 @safe @nogc pure nothrow version(mir_test) unittest 914 { 915 import mir.ndslice.topology: iota, universal; 916 static assert(iota(13, 40).universal.strided!(0, 1)(2, 5).shape == [7, 8]); 917 static assert(iota(93).universal.strided!(0, 0)(7, 3).shape == [5]); 918 } 919 920 /// 921 pure nothrow version(mir_test) unittest 922 { 923 import mir.ndslice.topology: iota, canonical; 924 auto slice = iota(3, 4).canonical; 925 926 assert(slice 927 == [[0,1,2,3], [4,5,6,7], [8,9,10,11]]); 928 929 // Template 930 assert(slice.strided!0(2) 931 == [[0,1,2,3], [8,9,10,11]]); 932 933 // Function 934 assert(slice.strided(0, 2) 935 == [[0,1,2,3], [8,9,10,11]]); 936 } 937 938 @safe @nogc pure nothrow version(mir_test) unittest 939 { 940 import mir.ndslice; 941 import mir.algorithm.iteration : equal; 942 943 import std.range : chain; 944 auto i0 = iota([4], 0); auto s0 = stride(i0, 3); 945 auto i1 = iota([4], 4); auto s1 = stride(i1, 3); 946 auto i2 = iota([4], 8); auto s2 = stride(i2, 3); 947 auto slice = iota(3, 4).universal; 948 assert(slice .flattened.equal(concatenation(i0, i1, i2))); 949 // Template 950 assert(slice.strided!0(2) .flattened.equal(concatenation(i0, i2))); 951 assert(slice.strided!1(3) .flattened.equal(concatenation(s0, s1, s2))); 952 assert(slice.strided!(0, 1)(2, 3).flattened.equal(concatenation(s0, s2))); 953 // Function 954 assert(slice.strided(0, 2).flattened.equal(concatenation(i0, i2))); 955 assert(slice.strided(1, 3).flattened.equal(concatenation(s0, s1, s2))); 956 assert(slice.strided(0, 2).strided(1, 3).flattened.equal(concatenation(s0, s2))); 957 } 958 959 /++ 960 Returns maximal multidimensional cube. 961 962 Params: 963 slice = input slice 964 Returns: 965 n-dimensional slice 966 +/ 967 Slice!(Iterator, N, kind) dropToHypercube(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) 968 if (kind == Canonical || kind == Universal || N == 1) 969 do 970 { 971 size_t length = slice._lengths[0]; 972 foreach (i; Iota!(1, N)) 973 if (length > slice._lengths[i]) 974 length = slice._lengths[i]; 975 foreach (i; Iota!N) 976 slice._lengths[i] = length; 977 return slice; 978 } 979 980 /// ditto 981 Slice!(Iterator, N, Canonical) dropToHypercube(Iterator, size_t N)(Slice!(Iterator, N) slice) 982 if (N > 1) 983 { 984 import mir.ndslice.topology: canonical; 985 return slice.canonical.dropToHypercube; 986 } 987 988 /// 989 @safe @nogc pure nothrow version(mir_test) unittest 990 { 991 import mir.ndslice.topology: iota, canonical, universal; 992 993 assert(iota(5, 3, 6, 7) 994 .dropToHypercube 995 .shape == cast(size_t[4])[3, 3, 3, 3]); 996 997 assert(iota(5, 3, 6, 7) 998 .universal 999 .dropToHypercube 1000 .shape == cast(size_t[4])[3, 3, 3, 3]); 1001 1002 assert(4.iota.dropToHypercube == 4.iota); 1003 }