1 /++ 2 This is a submodule of $(MREF mir,ndslice). 3 4 Selectors create new views and iteration patterns over the same data, without copying. 5 6 $(BOOKTABLE $(H2 Sequence Selectors), 7 $(TR $(TH Function Name) $(TH Description)) 8 9 $(T2 cycle, Cycle repeates 1-dimensional field/range/array/slice in a fixed length 1-dimensional slice) 10 $(T2 iota, Contiguous Slice with initial flattened (contiguous) index.) 11 $(T2 linspace, Evenly spaced numbers over a specified interval.) 12 $(T2 magic, Magic square.) 13 $(T2 ndiota, Contiguous Slice with initial multidimensional index.) 14 $(T2 repeat, Slice with identical values) 15 ) 16 17 $(BOOKTABLE $(H2 Shape Selectors), 18 $(TR $(TH Function Name) $(TH Description)) 19 20 $(T2 blocks, n-dimensional slice composed of n-dimensional non-overlapping blocks. If the slice has two dimensions, it is a block matrix.) 21 $(T2 diagonal, 1-dimensional slice composed of diagonal elements) 22 $(T2 dropBorders, Drops borders for all dimensions.) 23 $(T2 reshape, New slice view with changed dimensions) 24 $(T2 squeeze, New slice view of an n-dimensional slice with dimension removed) 25 $(T2 unsqueeze, New slice view of an n-dimensional slice with a dimension added) 26 $(T2 windows, n-dimensional slice of n-dimensional overlapping windows. If the slice has two dimensions, it is a sliding window.) 27 28 ) 29 30 31 $(BOOKTABLE $(H2 Subspace Selectors), 32 $(TR $(TH Function Name) $(TH Description)) 33 34 $(T2 alongDim , Returns a slice that can be iterated along dimension.) 35 $(T2 byDim , Returns a slice that can be iterated by dimension.) 36 $(T2 pack , Returns slice of slices.) 37 $(T2 ipack , Returns slice of slices.) 38 $(T2 unpack , Merges two hight dimension packs. See also $(SUBREF fuse, fuse).) 39 $(T2 evertPack, Reverses dimension packs.) 40 41 ) 42 43 $(BOOKTABLE $(H2 SliceKind Selectors), 44 $(TR $(TH Function Name) $(TH Description)) 45 46 $(T2 asKindOf, Converts a slice to a user provied kind $(SUBREF slice, SliceKind).) 47 $(T2 universal, Converts a slice to universal $(SUBREF slice, SliceKind).) 48 $(T2 canonical, Converts a slice to canonical $(SUBREF slice, SliceKind).) 49 $(T2 assumeCanonical, Converts a slice to canonical $(SUBREF slice, SliceKind). Does only `assert` checks.) 50 $(T2 assumeContiguous, Converts a slice to contiguous $(SUBREF slice, SliceKind). Does only `assert` checks.) 51 $(T2 assumeHypercube, Helps the compiler to use optimisations related to the shape form. Does only `assert` checks.) 52 $(T2 assumeSameShape, Helps the compiler to use optimisations related to the shape form. Does only `assert` checks.) 53 54 ) 55 56 $(BOOKTABLE $(H2 Products), 57 $(TR $(TH Function Name) $(TH Description)) 58 59 $(T2 cartesian, Cartesian product.) 60 $(T2 kronecker, Kronecker product.) 61 62 ) 63 64 $(BOOKTABLE $(H2 Representation Selectors), 65 $(TR $(TH Function Name) $(TH Description)) 66 67 $(T2 as, Convenience function that creates a lazy view, 68 where each element of the original slice is converted to a type `T`.) 69 $(T2 bitpack, Bitpack slice over an unsigned integral slice.) 70 $(T2 bitwise, Bitwise slice over an unsigned integral slice.) 71 $(T2 bytegroup, Groups existing slice into fixed length chunks and uses them as data store for destination type.) 72 $(T2 cached, Random access cache. It is usefull in combiation with $(LREF map) and $(LREF vmap).) 73 $(T2 cachedGC, Random access cache auto-allocated in GC heap. It is usefull in combiation with $(LREF map) and $(LREF vmap).) 74 $(T2 diff, Differences between vector elements.) 75 $(T2 flattened, Contiguous 1-dimensional slice of all elements of a slice.) 76 $(T2 map, Multidimensional functional map.) 77 $(T2 member, Field (element's member) projection.) 78 $(T2 orthogonalReduceField, Functional deep-element wise reduce of a slice composed of fields or iterators.) 79 $(T2 pairwise, Pairwise map for vectors.) 80 $(T2 pairwiseMapSubSlices, Maps pairwise index pairs to subslices.) 81 $(T2 retro, Reverses order of iteration for all dimensions.) 82 $(T2 slide, Lazy convolution for tensors.) 83 $(T2 slideAlong, Lazy convolution for tensors.) 84 $(T2 stairs, Two functions to pack, unpack, and iterate triangular and symmetric matrix storage.) 85 $(T2 stride, Strides 1-dimensional slice.) 86 $(T2 subSlices, Maps index pairs to subslices.) 87 $(T2 triplets, Constructs a lazy view of triplets with `left`, `center`, and `right` members. The topology is usefull for Math and Physics.) 88 $(T2 unzip, Selects a slice from a zipped slice.) 89 $(T2 withNeighboursSum, Zip view of elements packed with sum of their neighbours.) 90 $(T2 zip, Zips slices into a slice of refTuples.) 91 ) 92 93 Subspace selectors serve to generalize and combine other selectors easily. 94 For a slice of `Slice!(Iterator, N, kind)` type `slice.pack!K` creates a slice of 95 slices of `Slice!(kind, [N - K, K], Iterator)` type by packing 96 the last `K` dimensions of the top dimension pack, 97 and the type of element of $(LREF flattened) is `Slice!(Iterator, K)`. 98 Another way to use $(LREF pack) is transposition of dimension packs using 99 $(LREF evertPack). 100 Examples of use of subspace selectors are available for selectors, 101 $(SUBREF slice, Slice.shape), and $(SUBREF slice, Slice.elementCount). 102 103 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) 104 Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments 105 Authors: Ilia Ki, John Michael Hall, Shigeki Karita (original numir code) 106 107 Sponsors: Part of this work has been sponsored by $(LINK2 http://symmetryinvestments.com, Symmetry Investments) and Kaleidic Associates. 108 109 Macros: 110 SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) 111 T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) 112 T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4)) 113 +/ 114 module mir.ndslice.topology; 115 116 import mir.internal.utility; 117 import mir.math.common: fmamath; 118 import mir.ndslice.field; 119 import mir.ndslice.internal; 120 import mir.ndslice.iterator; 121 import mir.ndslice.ndfield; 122 import mir.ndslice.slice; 123 import mir.primitives; 124 import mir.qualifier; 125 import mir.utility: min; 126 import std.meta: AliasSeq, allSatisfy, staticMap, templateOr, Repeat; 127 128 private immutable choppedExceptionMsg = "bounds passed to chopped are out of sliceable bounds."; 129 version (D_Exceptions) private immutable choppedException = new Exception(choppedExceptionMsg); 130 131 @fmamath: 132 133 /++ 134 Converts a slice to user provided kind. 135 136 Contiguous slices can be converted to any kind. 137 Canonical slices can't be converted to contiguous slices. 138 Universal slices can be converted only to the same kind. 139 140 See_also: 141 $(LREF canonical), 142 $(LREF universal), 143 $(LREF assumeCanonical), 144 $(LREF assumeContiguous). 145 +/ 146 template asKindOf(SliceKind kind) 147 { 148 static if (kind == Contiguous) 149 { 150 auto asKindOf(Iterator, size_t N, Labels...)(Slice!(Iterator, N, Contiguous, Labels) slice) 151 { 152 return slice; 153 } 154 } 155 else 156 static if (kind == Canonical) 157 { 158 alias asKindOf = canonical; 159 } 160 else 161 { 162 alias asKindOf = universal; 163 } 164 } 165 166 /// Universal 167 @safe pure nothrow 168 version(mir_ndslice_test) unittest 169 { 170 import mir.ndslice.slice: Universal; 171 auto slice = iota(2, 3).asKindOf!Universal; 172 assert(slice == [[0, 1, 2], [3, 4, 5]]); 173 assert(slice._lengths == [2, 3]); 174 assert(slice._strides == [3, 1]); 175 } 176 177 /// Canonical 178 @safe pure nothrow 179 version(mir_ndslice_test) unittest 180 { 181 import mir.ndslice.slice: Canonical; 182 auto slice = iota(2, 3).asKindOf!Canonical; 183 assert(slice == [[0, 1, 2], [3, 4, 5]]); 184 assert(slice._lengths == [2, 3]); 185 assert(slice._strides == [3]); 186 } 187 188 /// Contiguous 189 @safe pure nothrow 190 version(mir_ndslice_test) unittest 191 { 192 import mir.ndslice.slice: Contiguous; 193 auto slice = iota(2, 3).asKindOf!Contiguous; 194 assert(slice == [[0, 1, 2], [3, 4, 5]]); 195 assert(slice._lengths == [2, 3]); 196 assert(slice._strides == []); 197 } 198 199 /++ 200 Converts a slice to universal kind. 201 202 Params: 203 slice = a slice 204 Returns: 205 universal slice 206 See_also: 207 $(LREF canonical), 208 $(LREF assumeCanonical), 209 $(LREF assumeContiguous). 210 +/ 211 auto universal(Iterator, size_t N, SliceKind kind, Labels...)(Slice!(Iterator, N, kind, Labels) slice) 212 { 213 import core.lifetime: move; 214 215 static if (kind == Universal) 216 { 217 return slice; 218 } 219 else 220 static if (is(Iterator : RetroIterator!It, It)) 221 { 222 return slice.move.retro.universal.retro; 223 } 224 else 225 { 226 alias Ret = Slice!(Iterator, N, Universal, Labels); 227 size_t[Ret.N] lengths; 228 auto strides = sizediff_t[Ret.S].init; 229 foreach (i; Iota!(slice.N)) 230 lengths[i] = slice._lengths[i]; 231 static if (kind == Canonical) 232 { 233 foreach (i; Iota!(slice.S)) 234 strides[i] = slice._strides[i]; 235 strides[$-1] = 1; 236 } 237 else 238 { 239 ptrdiff_t ball = 1; 240 foreach_reverse (i; Iota!(Ret.S)) 241 { 242 strides[i] = ball; 243 static if (i) 244 ball *= slice._lengths[i]; 245 } 246 } 247 return Ret(lengths, strides, slice._iterator.move, slice._labels); 248 } 249 } 250 251 /// 252 @safe pure nothrow 253 version(mir_ndslice_test) unittest 254 { 255 auto slice = iota(2, 3).universal; 256 assert(slice == [[0, 1, 2], [3, 4, 5]]); 257 assert(slice._lengths == [2, 3]); 258 assert(slice._strides == [3, 1]); 259 } 260 261 @safe pure nothrow 262 version(mir_ndslice_test) unittest 263 { 264 auto slice = iota(2, 3).canonical.universal; 265 assert(slice == [[0, 1, 2], [3, 4, 5]]); 266 assert(slice._lengths == [2, 3]); 267 assert(slice._strides == [3, 1]); 268 } 269 270 /// 271 @safe pure nothrow 272 version(mir_ndslice_test) unittest 273 { 274 import mir.ndslice.slice; 275 import mir.ndslice.allocation: slice; 276 277 auto dataframe = slice!(double, int, string)(2, 3); 278 dataframe.label[] = [1, 2]; 279 dataframe.label!1[] = ["Label1", "Label2", "Label3"]; 280 281 auto universaldf = dataframe.universal; 282 assert(universaldf._lengths == [2, 3]); 283 assert(universaldf._strides == [3, 1]); 284 285 assert(is(typeof(universaldf) == 286 Slice!(double*, 2, Universal, int*, string*))); 287 assert(universaldf.label!0[0] == 1); 288 assert(universaldf.label!1[1] == "Label2"); 289 } 290 291 /++ 292 Converts a slice to canonical kind. 293 294 Params: 295 slice = contiguous or canonical slice 296 Returns: 297 canonical slice 298 See_also: 299 $(LREF universal), 300 $(LREF assumeCanonical), 301 $(LREF assumeContiguous). 302 +/ 303 Slice!(Iterator, N, N == 1 ? Contiguous : Canonical, Labels) 304 canonical 305 (Iterator, size_t N, SliceKind kind, Labels...) 306 (Slice!(Iterator, N, kind, Labels) slice) 307 if (kind == Contiguous || kind == Canonical) 308 { 309 import core.lifetime: move; 310 311 static if (kind == Canonical || N == 1) 312 return slice; 313 else 314 { 315 alias Ret = typeof(return); 316 size_t[Ret.N] lengths; 317 auto strides = sizediff_t[Ret.S].init; 318 foreach (i; Iota!(slice.N)) 319 lengths[i] = slice._lengths[i]; 320 ptrdiff_t ball = 1; 321 foreach_reverse (i; Iota!(Ret.S)) 322 { 323 ball *= slice._lengths[i + 1]; 324 strides[i] = ball; 325 } 326 return Ret(lengths, strides, slice._iterator.move, slice._labels); 327 } 328 } 329 330 /// 331 @safe pure nothrow 332 version(mir_ndslice_test) unittest 333 { 334 auto slice = iota(2, 3).canonical; 335 assert(slice == [[0, 1, 2], [3, 4, 5]]); 336 assert(slice._lengths == [2, 3]); 337 assert(slice._strides == [3]); 338 } 339 340 /// 341 @safe pure nothrow 342 version(mir_ndslice_test) unittest 343 { 344 import mir.ndslice.slice; 345 import mir.ndslice.allocation: slice; 346 347 auto dataframe = slice!(double, int, string)(2, 3); 348 dataframe.label[] = [1, 2]; 349 dataframe.label!1[] = ["Label1", "Label2", "Label3"]; 350 351 auto canonicaldf = dataframe.canonical; 352 assert(canonicaldf._lengths == [2, 3]); 353 assert(canonicaldf._strides == [3]); 354 355 assert(is(typeof(canonicaldf) == 356 Slice!(double*, 2, Canonical, int*, string*))); 357 assert(canonicaldf.label!0[0] == 1); 358 assert(canonicaldf.label!1[1] == "Label2"); 359 } 360 361 /++ 362 Converts a slice to canonical kind (unsafe). 363 364 Params: 365 slice = a slice 366 Returns: 367 canonical slice 368 See_also: 369 $(LREF universal), 370 $(LREF canonical), 371 $(LREF assumeContiguous). 372 +/ 373 Slice!(Iterator, N, Canonical, Labels) 374 assumeCanonical 375 (Iterator, size_t N, SliceKind kind, Labels...) 376 (Slice!(Iterator, N, kind, Labels) slice) 377 { 378 static if (kind == Contiguous) 379 return slice.canonical; 380 else 381 static if (kind == Canonical) 382 return slice; 383 else 384 { 385 import mir.utility: swap; 386 assert(slice._lengths[N - 1] <= 1 || slice._strides[N - 1] == 1); 387 typeof(return) ret; 388 ret._lengths = slice._lengths; 389 ret._strides = slice._strides[0 .. $ - 1]; 390 swap(ret._iterator, slice._iterator); 391 foreach(i, _; Labels) 392 swap(ret._labels[i], slice._labels[i]); 393 return ret; 394 } 395 } 396 397 /// 398 @safe pure nothrow 399 version(mir_ndslice_test) unittest 400 { 401 auto slice = iota(2, 3).universal.assumeCanonical; 402 assert(slice == [[0, 1, 2], [3, 4, 5]]); 403 assert(slice._lengths == [2, 3]); 404 assert(slice._strides == [3]); 405 } 406 407 /// 408 @safe pure nothrow 409 version(mir_ndslice_test) unittest 410 { 411 import mir.ndslice.slice; 412 import mir.ndslice.allocation: slice; 413 414 auto dataframe = slice!(double, int, string)(2, 3); 415 dataframe.label[] = [1, 2]; 416 dataframe.label!1[] = ["Label1", "Label2", "Label3"]; 417 418 auto assmcanonicaldf = dataframe.assumeCanonical; 419 assert(assmcanonicaldf._lengths == [2, 3]); 420 assert(assmcanonicaldf._strides == [3]); 421 422 assert(is(typeof(assmcanonicaldf) == 423 Slice!(double*, 2, Canonical, int*, string*))); 424 assert(assmcanonicaldf.label!0[0] == 1); 425 assert(assmcanonicaldf.label!1[1] == "Label2"); 426 } 427 428 /++ 429 Converts a slice to contiguous kind (unsafe). 430 431 Params: 432 slice = a slice 433 Returns: 434 canonical slice 435 See_also: 436 $(LREF universal), 437 $(LREF canonical), 438 $(LREF assumeCanonical). 439 +/ 440 Slice!(Iterator, N, Contiguous, Labels) 441 assumeContiguous 442 (Iterator, size_t N, SliceKind kind, Labels...) 443 (Slice!(Iterator, N, kind, Labels) slice) 444 { 445 static if (kind == Contiguous) 446 return slice; 447 else 448 { 449 import mir.utility: swap; 450 typeof(return) ret; 451 ret._lengths = slice._lengths; 452 swap(ret._iterator, slice._iterator); 453 foreach(i, _; Labels) 454 swap(ret._labels[i], slice._labels[i]); 455 return ret; 456 } 457 } 458 459 /// 460 @safe pure nothrow 461 version(mir_ndslice_test) unittest 462 { 463 auto slice = iota(2, 3).universal.assumeContiguous; 464 assert(slice == [[0, 1, 2], [3, 4, 5]]); 465 assert(slice._lengths == [2, 3]); 466 static assert(slice._strides.length == 0); 467 } 468 469 /// 470 @safe pure nothrow 471 version(mir_ndslice_test) unittest 472 { 473 import mir.ndslice.slice; 474 import mir.ndslice.allocation: slice; 475 476 auto dataframe = slice!(double, int, string)(2, 3); 477 dataframe.label[] = [1, 2]; 478 dataframe.label!1[] = ["Label1", "Label2", "Label3"]; 479 480 auto assmcontdf = dataframe.canonical.assumeContiguous; 481 assert(assmcontdf._lengths == [2, 3]); 482 static assert(assmcontdf._strides.length == 0); 483 484 assert(is(typeof(assmcontdf) == 485 Slice!(double*, 2, Contiguous, int*, string*))); 486 assert(assmcontdf.label!0[0] == 1); 487 assert(assmcontdf.label!1[1] == "Label2"); 488 } 489 490 /++ 491 Helps the compiler to use optimisations related to the shape form 492 +/ 493 void assumeHypercube 494 (Iterator, size_t N, SliceKind kind, Labels...) 495 (ref scope Slice!(Iterator, N, kind, Labels) slice) 496 { 497 foreach (i; Iota!(1, N)) 498 { 499 assert(slice._lengths[i] == slice._lengths[0]); 500 slice._lengths[i] = slice._lengths[0]; 501 } 502 } 503 504 /// 505 @safe @nogc pure nothrow version(mir_ndslice_test) unittest 506 { 507 auto b = iota(5, 5); 508 509 assumeHypercube(b); 510 511 assert(b == iota(5, 5)); 512 } 513 514 /++ 515 Helps the compiler to use optimisations related to the shape form 516 +/ 517 void assumeSameShape(T...) 518 (ref scope T slices) 519 if (allSatisfy!(isSlice, T)) 520 { 521 foreach (i; Iota!(1, T.length)) 522 { 523 assert(slices[i]._lengths == slices[0]._lengths); 524 slices[i]._lengths = slices[0]._lengths; 525 } 526 } 527 528 /// 529 @safe @nogc pure nothrow version(mir_ndslice_test) unittest 530 { 531 auto a = iota(5, 5); 532 auto b = iota(5, 5); 533 534 assumeHypercube(a); // first use this one, if applicable 535 assumeSameShape(a, b); // 536 537 assert(a == iota(5, 5)); 538 assert(b == iota(5, 5)); 539 } 540 541 /++ 542 +/ 543 auto assumeFieldsHaveZeroShift(Iterator, size_t N, SliceKind kind) 544 (Slice!(Iterator, N, kind) slice) 545 if (__traits(hasMember, Iterator, "assumeFieldsHaveZeroShift")) 546 { 547 return slice._iterator.assumeFieldsHaveZeroShift.slicedField(slice._lengths); 548 } 549 550 /++ 551 Creates a packed slice, i.e. slice of slices. 552 Packs the last `P` dimensions. 553 The function does not allocate any data. 554 555 Params: 556 P = size of dimension pack 557 slice = a slice to pack 558 Returns: 559 `slice.pack!p` returns `Slice!(kind, [N - p, p], Iterator)` 560 See_also: $(LREF ipack) 561 +/ 562 Slice!(SliceIterator!(Iterator, P, P == 1 && kind == Canonical ? Contiguous : kind), N - P, Universal) 563 pack(size_t P, Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) 564 if (P && P < N) 565 { 566 import core.lifetime: move; 567 return slice.move.ipack!(N - P); 568 } 569 570 /// 571 @safe @nogc pure nothrow version(mir_ndslice_test) unittest 572 { 573 import mir.ndslice.slice: sliced, Slice; 574 575 auto a = iota(3, 4, 5, 6); 576 auto b = a.pack!2; 577 578 static immutable res1 = [3, 4]; 579 static immutable res2 = [5, 6]; 580 assert(b.shape == res1); 581 assert(b[0, 0].shape == res2); 582 assert(a == b.unpack); 583 assert(a.pack!2 == b); 584 static assert(is(typeof(b) == typeof(a.pack!2))); 585 } 586 587 /++ 588 Creates a packed slice, i.e. slice of slices. 589 Packs the last `N - P` dimensions. 590 The function does not allocate any data. 591 592 Params: 593 + = size of dimension pack 594 slice = a slice to pack 595 See_also: $(LREF pack) 596 +/ 597 Slice!(SliceIterator!(Iterator, N - P, N - P == 1 && kind == Canonical ? Contiguous : kind), P, Universal) 598 ipack(size_t P, Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) 599 if (P && P < N) 600 { 601 import core.lifetime: move; 602 alias Ret = typeof(return); 603 alias It = Ret.Iterator; 604 alias EN = It.Element.N; 605 alias ES = It.Element.S; 606 auto sl = slice.move.universal; 607 static if (It.Element.kind == Contiguous) 608 return Ret( 609 cast( size_t[P]) sl._lengths[0 .. P], 610 cast(ptrdiff_t[P]) sl._strides[0 .. P], 611 It( 612 cast(size_t[EN]) sl._lengths[P .. $], 613 sl._iterator.move)); 614 else 615 return Ret( 616 cast( size_t[P]) sl._lengths[0 .. P], 617 cast(ptrdiff_t[P]) sl._strides[0 .. P], 618 It( 619 cast( size_t[EN]) sl._lengths[P .. $], 620 cast(ptrdiff_t[ES]) sl._strides[P .. $ - (It.Element.kind == Canonical)], 621 sl._iterator.move)); 622 } 623 624 /// 625 @safe @nogc pure nothrow version(mir_ndslice_test) unittest 626 { 627 import mir.ndslice.slice: sliced, Slice; 628 629 auto a = iota(3, 4, 5, 6); 630 auto b = a.ipack!2; 631 632 static immutable res1 = [3, 4]; 633 static immutable res2 = [5, 6]; 634 assert(b.shape == res1); 635 assert(b[0, 0].shape == res2); 636 assert(a.ipack!2 == b); 637 static assert(is(typeof(b) == typeof(a.ipack!2))); 638 } 639 640 /++ 641 Unpacks a packed slice. 642 643 The functions does not allocate any data. 644 645 Params: 646 slice = packed slice 647 Returns: 648 unpacked slice, that is a view on the same data. 649 650 See_also: $(LREF pack), $(LREF evertPack) 651 +/ 652 Slice!(Iterator, N + M, min(innerKind, Canonical)) 653 unpack(Iterator, size_t M, SliceKind innerKind, size_t N, SliceKind outerKind) 654 (Slice!(SliceIterator!(Iterator, M, innerKind), N, outerKind) slice) 655 { 656 alias Ret = typeof(return); 657 size_t[N + M] lengths; 658 auto strides = sizediff_t[Ret.S].init; 659 auto outerStrides = slice.strides; 660 auto innerStrides = Slice!(Iterator, M, innerKind)( 661 slice._iterator._structure, 662 slice._iterator._iterator, 663 ).strides; 664 foreach(i; Iota!N) 665 lengths[i] = slice._lengths[i]; 666 foreach(i; Iota!N) 667 strides[i] = outerStrides[i]; 668 foreach(i; Iota!M) 669 lengths[N + i] = slice._iterator._structure[0][i]; 670 foreach(i; Iota!(Ret.S - N)) 671 strides[N + i] = innerStrides[i]; 672 return Ret(lengths, strides, slice._iterator._iterator); 673 } 674 675 /++ 676 Reverses the order of dimension packs. 677 This function is used in a functional pipeline with other selectors. 678 679 Params: 680 slice = packed slice 681 Returns: 682 packed slice 683 684 See_also: $(LREF pack), $(LREF unpack) 685 +/ 686 Slice!(SliceIterator!(Iterator, N, outerKind), M, innerKind) 687 evertPack(Iterator, size_t M, SliceKind innerKind, size_t N, SliceKind outerKind) 688 (Slice!(SliceIterator!(Iterator, M, innerKind), N, outerKind) slice) 689 { 690 import core.lifetime: move; 691 return typeof(return)( 692 slice._iterator._structure, 693 typeof(return).Iterator( 694 slice._structure, 695 slice._iterator._iterator.move)); 696 } 697 698 /// 699 @safe @nogc pure nothrow version(mir_ndslice_test) unittest 700 { 701 import mir.ndslice.dynamic : transposed; 702 auto slice = iota(3, 4, 5, 6, 7, 8, 9, 10, 11).universal; 703 assert(slice 704 .pack!2 705 .evertPack 706 .unpack 707 == slice.transposed!( 708 slice.shape.length-2, 709 slice.shape.length-1)); 710 } 711 712 /// 713 @safe pure nothrow version(mir_ndslice_test) unittest 714 { 715 import mir.ndslice.iterator: SliceIterator; 716 import mir.ndslice.slice: sliced, Slice, Universal; 717 import mir.ndslice.allocation: slice; 718 static assert(is(typeof( 719 slice!int(6) 720 .sliced(1,2,3) 721 .pack!1 722 .evertPack 723 ) 724 == Slice!(SliceIterator!(int*, 2, Universal), 1))); 725 } 726 727 /// 728 @safe pure nothrow @nogc 729 version(mir_ndslice_test) unittest 730 { 731 auto a = iota(3, 4, 5, 6, 7, 8, 9, 10, 11); 732 auto b = a.pack!2.unpack; 733 static assert(is(typeof(a.canonical) == typeof(b))); 734 assert(a == b); 735 } 736 737 /++ 738 Returns a slice, the elements of which are equal to the initial flattened index value. 739 740 Params: 741 N = dimension count 742 lengths = list of dimension lengths 743 start = value of the first element in a slice (optional for integer `I`) 744 stride = value of the stride between elements (optional) 745 Returns: 746 n-dimensional slice composed of indices 747 See_also: $(LREF ndiota) 748 +/ 749 Slice!(IotaIterator!I, N) 750 iota 751 (I = sizediff_t, size_t N)(size_t[N] lengths...) 752 if (__traits(isIntegral, I)) 753 { 754 import mir.ndslice.slice: sliced; 755 return IotaIterator!I(I.init).sliced(lengths); 756 } 757 758 ///ditto 759 Slice!(IotaIterator!sizediff_t, N) 760 iota 761 (size_t N)(size_t[N] lengths, sizediff_t start) 762 { 763 import mir.ndslice.slice: sliced; 764 return IotaIterator!sizediff_t(start).sliced(lengths); 765 } 766 767 ///ditto 768 Slice!(StrideIterator!(IotaIterator!sizediff_t), N) 769 iota 770 (size_t N)(size_t[N] lengths, sizediff_t start, size_t stride) 771 { 772 import mir.ndslice.slice: sliced; 773 return StrideIterator!(IotaIterator!sizediff_t)(stride, IotaIterator!sizediff_t(start)).sliced(lengths); 774 } 775 776 ///ditto 777 template iota(I) 778 if (__traits(isIntegral, I)) 779 { 780 /// 781 Slice!(IotaIterator!I, N) 782 iota 783 (size_t N)(size_t[N] lengths, I start) 784 if (__traits(isIntegral, I)) 785 { 786 import mir.ndslice.slice: sliced; 787 return IotaIterator!I(start).sliced(lengths); 788 } 789 790 ///ditto 791 Slice!(StrideIterator!(IotaIterator!I), N) 792 iota 793 (size_t N)(size_t[N] lengths, I start, size_t stride) 794 if (__traits(isIntegral, I)) 795 { 796 import mir.ndslice.slice: sliced; 797 return StrideIterator!(IotaIterator!I)(stride, IotaIterator!I(start)).sliced(lengths); 798 } 799 } 800 801 ///ditto 802 Slice!(IotaIterator!I, N) 803 iota 804 (I, size_t N)(size_t[N] lengths, I start) 805 if (is(I P : P*)) 806 { 807 import mir.ndslice.slice: sliced; 808 return IotaIterator!I(start).sliced(lengths); 809 } 810 811 ///ditto 812 Slice!(StrideIterator!(IotaIterator!I), N) 813 iota 814 (I, size_t N)(size_t[N] lengths, I start, size_t stride) 815 if (is(I P : P*)) 816 { 817 import mir.ndslice.slice: sliced; 818 return StrideIterator!(IotaIterator!I)(stride, IotaIterator!I(start)).sliced(lengths); 819 } 820 821 /// 822 @safe pure nothrow @nogc version(mir_ndslice_test) unittest 823 { 824 import mir.primitives: DeepElementType; 825 auto slice = iota(2, 3); 826 static immutable array = 827 [[0, 1, 2], 828 [3, 4, 5]]; 829 830 assert(slice == array); 831 832 static assert(is(DeepElementType!(typeof(slice)) == sizediff_t)); 833 } 834 835 /// 836 pure nothrow @nogc 837 version(mir_ndslice_test) unittest 838 { 839 int[6] data; 840 auto slice = iota([2, 3], data.ptr); 841 assert(slice[0, 0] == data.ptr); 842 assert(slice[0, 1] == data.ptr + 1); 843 assert(slice[1, 0] == data.ptr + 3); 844 } 845 846 /// 847 @safe pure nothrow @nogc 848 version(mir_ndslice_test) unittest 849 { 850 auto im = iota([10, 5], 100); 851 assert(im[2, 1] == 111); // 100 + 2 * 5 + 1 852 853 //slicing works correctly 854 auto cm = im[1 .. $, 3 .. $]; 855 assert(cm[2, 1] == 119); // 119 = 100 + (1 + 2) * 5 + (3 + 1) 856 } 857 858 /// `iota` with step 859 @safe pure nothrow version(mir_ndslice_test) unittest 860 { 861 auto sl = iota([2, 3], 10, 10); 862 863 assert(sl == [[10, 20, 30], 864 [40, 50, 60]]); 865 } 866 867 /++ 868 Returns a 1-dimensional slice over the main diagonal of an n-dimensional slice. 869 `diagonal` can be generalized with other selectors such as 870 $(LREF blocks) (diagonal blocks) and $(LREF windows) (multi-diagonal slice). 871 872 Params: 873 slice = input slice 874 Returns: 875 1-dimensional slice composed of diagonal elements 876 See_also: $(LREF antidiagonal) 877 +/ 878 Slice!(Iterator, 1, N == 1 ? kind : Universal) 879 diagonal 880 (Iterator, size_t N, SliceKind kind) 881 (Slice!(Iterator, N, kind) slice) 882 { 883 static if (N == 1) 884 { 885 return slice; 886 } 887 else 888 { 889 alias Ret = typeof(return); 890 size_t[Ret.N] lengths; 891 auto strides = sizediff_t[Ret.S].init; 892 lengths[0] = slice._lengths[0]; 893 foreach (i; Iota!(1, N)) 894 if (lengths[0] > slice._lengths[i]) 895 lengths[0] = slice._lengths[i]; 896 foreach (i; Iota!(1, Ret.N)) 897 lengths[i] = slice._lengths[i + N - 1]; 898 auto rstrides = slice.strides; 899 strides[0] = rstrides[0]; 900 foreach (i; Iota!(1, N)) 901 strides[0] += rstrides[i]; 902 foreach (i; Iota!(1, Ret.S)) 903 strides[i] = rstrides[i + N - 1]; 904 return Ret(lengths, strides, slice._iterator); 905 } 906 } 907 908 /// Matrix, main diagonal 909 @safe @nogc pure nothrow version(mir_ndslice_test) unittest 910 { 911 // ------- 912 // | 0 1 2 | 913 // | 3 4 5 | 914 // ------- 915 //-> 916 // | 0 4 | 917 static immutable d = [0, 4]; 918 assert(iota(2, 3).diagonal == d); 919 } 920 921 /// Non-square matrix 922 @safe pure nothrow version(mir_ndslice_test) unittest 923 { 924 // ------- 925 // | 0 1 | 926 // | 2 3 | 927 // | 4 5 | 928 // ------- 929 //-> 930 // | 0 3 | 931 932 assert(iota(3, 2).diagonal == iota([2], 0, 3)); 933 } 934 935 /// Loop through diagonal 936 @safe pure nothrow version(mir_ndslice_test) unittest 937 { 938 import mir.ndslice.slice; 939 import mir.ndslice.allocation; 940 941 auto slice = slice!int(3, 3); 942 int i; 943 foreach (ref e; slice.diagonal) 944 e = ++i; 945 assert(slice == [ 946 [1, 0, 0], 947 [0, 2, 0], 948 [0, 0, 3]]); 949 } 950 951 /// Matrix, subdiagonal 952 @safe @nogc pure nothrow 953 version(mir_ndslice_test) unittest 954 { 955 // ------- 956 // | 0 1 2 | 957 // | 3 4 5 | 958 // ------- 959 //-> 960 // | 1 5 | 961 static immutable d = [1, 5]; 962 auto a = iota(2, 3).canonical; 963 a.popFront!1; 964 assert(a.diagonal == d); 965 } 966 967 /// 3D, main diagonal 968 @safe @nogc pure nothrow version(mir_ndslice_test) unittest 969 { 970 // ----------- 971 // | 0 1 2 | 972 // | 3 4 5 | 973 // - - - - - - 974 // | 6 7 8 | 975 // | 9 10 11 | 976 // ----------- 977 //-> 978 // | 0 10 | 979 static immutable d = [0, 10]; 980 assert(iota(2, 2, 3).diagonal == d); 981 } 982 983 /// 3D, subdiagonal 984 @safe @nogc pure nothrow version(mir_ndslice_test) unittest 985 { 986 // ----------- 987 // | 0 1 2 | 988 // | 3 4 5 | 989 // - - - - - - 990 // | 6 7 8 | 991 // | 9 10 11 | 992 // ----------- 993 //-> 994 // | 1 11 | 995 static immutable d = [1, 11]; 996 auto a = iota(2, 2, 3).canonical; 997 a.popFront!2; 998 assert(a.diagonal == d); 999 } 1000 1001 /// 3D, diagonal plain 1002 @nogc @safe pure nothrow 1003 version(mir_ndslice_test) unittest 1004 { 1005 // ----------- 1006 // | 0 1 2 | 1007 // | 3 4 5 | 1008 // | 6 7 8 | 1009 // - - - - - - 1010 // | 9 10 11 | 1011 // | 12 13 14 | 1012 // | 15 16 17 | 1013 // - - - - - - 1014 // | 18 20 21 | 1015 // | 22 23 24 | 1016 // | 24 25 26 | 1017 // ----------- 1018 //-> 1019 // ----------- 1020 // | 0 4 8 | 1021 // | 9 13 17 | 1022 // | 18 23 26 | 1023 // ----------- 1024 1025 static immutable d = 1026 [[ 0, 4, 8], 1027 [ 9, 13, 17], 1028 [18, 22, 26]]; 1029 1030 auto slice = iota(3, 3, 3) 1031 .pack!2 1032 .evertPack 1033 .diagonal 1034 .evertPack; 1035 1036 assert(slice == d); 1037 } 1038 1039 /++ 1040 Returns a 1-dimensional slice over the main antidiagonal of an 2D-dimensional slice. 1041 `antidiagonal` can be generalized with other selectors such as 1042 $(LREF blocks) (diagonal blocks) and $(LREF windows) (multi-diagonal slice). 1043 1044 It runs from the top right corner to the bottom left corner. 1045 1046 Pseudo_code: 1047 ------ 1048 auto antidiagonal = slice.dropToHypercube.reversed!1.diagonal; 1049 ------ 1050 1051 Params: 1052 slice = input slice 1053 Returns: 1054 1-dimensional slice composed of antidiagonal elements. 1055 See_also: $(LREF diagonal) 1056 +/ 1057 Slice!(Iterator, 1, Universal) 1058 antidiagonal 1059 (Iterator, size_t N, SliceKind kind) 1060 (Slice!(Iterator, N, kind) slice) 1061 if (N == 2) 1062 { 1063 import mir.ndslice.dynamic : dropToHypercube, reversed; 1064 return slice.dropToHypercube.reversed!1.diagonal; 1065 } 1066 1067 /// 1068 @safe @nogc pure nothrow version(mir_ndslice_test) unittest 1069 { 1070 // ----- 1071 // | 0 1 | 1072 // | 2 3 | 1073 // ----- 1074 //-> 1075 // | 1 2 | 1076 static immutable c = [1, 2]; 1077 assert(iota(2, 2).antidiagonal == c); 1078 } 1079 1080 /// 1081 @safe @nogc pure nothrow version(mir_ndslice_test) unittest 1082 { 1083 // ------- 1084 // | 0 1 2 | 1085 // | 3 4 5 | 1086 // ------- 1087 //-> 1088 // | 1 3 | 1089 static immutable d = [1, 3]; 1090 assert(iota(2, 3).antidiagonal == d); 1091 } 1092 1093 /++ 1094 Returns an n-dimensional slice of n-dimensional non-overlapping blocks. 1095 `blocks` can be generalized with other selectors. 1096 For example, `blocks` in combination with $(LREF diagonal) can be used to get a slice of diagonal blocks. 1097 For overlapped blocks, combine $(LREF windows) with $(SUBREF dynamic, strided). 1098 1099 Params: 1100 N = dimension count 1101 slice = slice to be split into blocks 1102 rlengths_ = dimensions of block, residual blocks are ignored 1103 Returns: 1104 packed `N`-dimensional slice composed of `N`-dimensional slices 1105 1106 See_also: $(SUBREF chunks, ._chunks) 1107 +/ 1108 Slice!(SliceIterator!(Iterator, N, N == 1 ? Universal : min(kind, Canonical)), N, Universal) 1109 blocks 1110 (Iterator, size_t N, SliceKind kind) 1111 (Slice!(Iterator, N, kind) slice, size_t[N] rlengths_...) 1112 in 1113 { 1114 foreach (i, length; rlengths_) 1115 assert(length > 0, "length of dimension = " ~ i.stringof ~ " must be positive" 1116 ~ tailErrorMessage!()); 1117 } 1118 do 1119 { 1120 size_t[N] lengths; 1121 size_t[N] rlengths = rlengths_; 1122 sizediff_t[N] strides; 1123 foreach (dimension; Iota!N) 1124 lengths[dimension] = slice._lengths[dimension] / rlengths[dimension]; 1125 auto rstrides = slice.strides; 1126 foreach (i; Iota!N) 1127 { 1128 strides[i] = rstrides[i]; 1129 if (lengths[i]) //do not remove `if (...)` 1130 strides[i] *= rlengths[i]; 1131 } 1132 return typeof(return)( 1133 lengths, 1134 strides, 1135 typeof(return).Iterator( 1136 rlengths, 1137 rstrides[0 .. typeof(return).DeepElement.S], 1138 slice._iterator)); 1139 } 1140 1141 /// 1142 pure nothrow version(mir_ndslice_test) unittest 1143 { 1144 import mir.ndslice.slice; 1145 import mir.ndslice.allocation; 1146 auto slice = slice!int(5, 8); 1147 auto blocks = slice.blocks(2, 3); 1148 int i; 1149 foreach (blocksRaw; blocks) 1150 foreach (block; blocksRaw) 1151 block[] = ++i; 1152 1153 assert(blocks == 1154 [[[[1, 1, 1], [1, 1, 1]], 1155 [[2, 2, 2], [2, 2, 2]]], 1156 [[[3, 3, 3], [3, 3, 3]], 1157 [[4, 4, 4], [4, 4, 4]]]]); 1158 1159 assert( slice == 1160 [[1, 1, 1, 2, 2, 2, 0, 0], 1161 [1, 1, 1, 2, 2, 2, 0, 0], 1162 1163 [3, 3, 3, 4, 4, 4, 0, 0], 1164 [3, 3, 3, 4, 4, 4, 0, 0], 1165 1166 [0, 0, 0, 0, 0, 0, 0, 0]]); 1167 } 1168 1169 /// Diagonal blocks 1170 @safe pure nothrow version(mir_ndslice_test) unittest 1171 { 1172 import mir.ndslice.slice; 1173 import mir.ndslice.allocation; 1174 auto slice = slice!int(5, 8); 1175 auto blocks = slice.blocks(2, 3); 1176 auto diagonalBlocks = blocks.diagonal.unpack; 1177 1178 diagonalBlocks[0][] = 1; 1179 diagonalBlocks[1][] = 2; 1180 1181 assert(diagonalBlocks == 1182 [[[1, 1, 1], [1, 1, 1]], 1183 [[2, 2, 2], [2, 2, 2]]]); 1184 1185 assert(blocks == 1186 [[[[1, 1, 1], [1, 1, 1]], 1187 [[0, 0, 0], [0, 0, 0]]], 1188 [[[0, 0, 0], [0, 0, 0]], 1189 [[2, 2, 2], [2, 2, 2]]]]); 1190 1191 assert(slice == 1192 [[1, 1, 1, 0, 0, 0, 0, 0], 1193 [1, 1, 1, 0, 0, 0, 0, 0], 1194 1195 [0, 0, 0, 2, 2, 2, 0, 0], 1196 [0, 0, 0, 2, 2, 2, 0, 0], 1197 1198 [0, 0, 0, 0, 0, 0, 0, 0]]); 1199 } 1200 1201 /// Matrix divided into vertical blocks 1202 @safe pure version(mir_ndslice_test) unittest 1203 { 1204 import mir.ndslice.allocation; 1205 import mir.ndslice.slice; 1206 auto slice = slice!int(5, 13); 1207 auto blocks = slice 1208 .pack!1 1209 .evertPack 1210 .blocks(3) 1211 .unpack; 1212 1213 int i; 1214 foreach (block; blocks) 1215 block[] = ++i; 1216 1217 assert(slice == 1218 [[1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 0], 1219 [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 0], 1220 [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 0], 1221 [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 0], 1222 [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 0]]); 1223 } 1224 1225 /++ 1226 Returns an n-dimensional slice of n-dimensional overlapping windows. 1227 `windows` can be generalized with other selectors. 1228 For example, `windows` in combination with $(LREF diagonal) can be used to get a multi-diagonal slice. 1229 1230 Params: 1231 N = dimension count 1232 slice = slice to be iterated 1233 rlengths = dimensions of windows 1234 Returns: 1235 packed `N`-dimensional slice composed of `N`-dimensional slices 1236 +/ 1237 Slice!(SliceIterator!(Iterator, N, N == 1 ? kind : min(kind, Canonical)), N, Universal) 1238 windows 1239 (Iterator, size_t N, SliceKind kind) 1240 (Slice!(Iterator, N, kind) slice, size_t[N] rlengths...) 1241 in 1242 { 1243 foreach (i, length; rlengths) 1244 assert(length > 0, "length of dimension = " ~ i.stringof ~ " must be positive" 1245 ~ tailErrorMessage!()); 1246 } 1247 do 1248 { 1249 size_t[N] rls = rlengths; 1250 size_t[N] lengths; 1251 foreach (dimension; Iota!N) 1252 lengths[dimension] = slice._lengths[dimension] >= rls[dimension] ? 1253 slice._lengths[dimension] - rls[dimension] + 1 : 0; 1254 auto rstrides = slice.strides; 1255 static if (typeof(return).DeepElement.S) 1256 return typeof(return)( 1257 lengths, 1258 rstrides, 1259 typeof(return).Iterator( 1260 rls, 1261 rstrides[0 .. typeof(return).DeepElement.S], 1262 slice._iterator)); 1263 else 1264 return typeof(return)( 1265 lengths, 1266 rstrides, 1267 typeof(return).Iterator( 1268 rls, 1269 slice._iterator)); 1270 } 1271 1272 /// 1273 @safe pure nothrow 1274 version(mir_ndslice_test) unittest 1275 { 1276 import mir.ndslice.allocation; 1277 import mir.ndslice.slice; 1278 auto slice = slice!int(5, 8); 1279 auto windows = slice.windows(2, 3); 1280 1281 int i; 1282 foreach (windowsRaw; windows) 1283 foreach (window; windowsRaw) 1284 ++window[]; 1285 1286 assert(slice == 1287 [[1, 2, 3, 3, 3, 3, 2, 1], 1288 1289 [2, 4, 6, 6, 6, 6, 4, 2], 1290 [2, 4, 6, 6, 6, 6, 4, 2], 1291 [2, 4, 6, 6, 6, 6, 4, 2], 1292 1293 [1, 2, 3, 3, 3, 3, 2, 1]]); 1294 } 1295 1296 /// 1297 @safe pure nothrow version(mir_ndslice_test) unittest 1298 { 1299 import mir.ndslice.allocation; 1300 import mir.ndslice.slice; 1301 auto slice = slice!int(5, 8); 1302 auto windows = slice.windows(2, 3); 1303 windows[1, 2][] = 1; 1304 windows[1, 2][0, 1] += 1; 1305 windows.unpack[1, 2, 0, 1] += 1; 1306 1307 assert(slice == 1308 [[0, 0, 0, 0, 0, 0, 0, 0], 1309 1310 [0, 0, 1, 3, 1, 0, 0, 0], 1311 [0, 0, 1, 1, 1, 0, 0, 0], 1312 1313 [0, 0, 0, 0, 0, 0, 0, 0], 1314 [0, 0, 0, 0, 0, 0, 0, 0]]); 1315 } 1316 1317 /// Multi-diagonal matrix 1318 @safe pure nothrow version(mir_ndslice_test) unittest 1319 { 1320 import mir.ndslice.allocation; 1321 import mir.ndslice.slice; 1322 auto slice = slice!int(8, 8); 1323 auto windows = slice.windows(3, 3); 1324 1325 auto multidiagonal = windows 1326 .diagonal 1327 .unpack; 1328 foreach (window; multidiagonal) 1329 window[] += 1; 1330 1331 assert(slice == 1332 [[ 1, 1, 1, 0, 0, 0, 0, 0], 1333 [ 1, 2, 2, 1, 0, 0, 0, 0], 1334 [ 1, 2, 3, 2, 1, 0, 0, 0], 1335 [0, 1, 2, 3, 2, 1, 0, 0], 1336 [0, 0, 1, 2, 3, 2, 1, 0], 1337 [0, 0, 0, 1, 2, 3, 2, 1], 1338 [0, 0, 0, 0, 1, 2, 2, 1], 1339 [0, 0, 0, 0, 0, 1, 1, 1]]); 1340 } 1341 1342 /// Sliding window over matrix columns 1343 @safe pure nothrow version(mir_ndslice_test) unittest 1344 { 1345 import mir.ndslice.allocation; 1346 import mir.ndslice.slice; 1347 auto slice = slice!int(5, 8); 1348 auto windows = slice 1349 .pack!1 1350 .evertPack 1351 .windows(3) 1352 .unpack; 1353 1354 foreach (window; windows) 1355 window[] += 1; 1356 1357 assert(slice == 1358 [[1, 2, 3, 3, 3, 3, 2, 1], 1359 [1, 2, 3, 3, 3, 3, 2, 1], 1360 [1, 2, 3, 3, 3, 3, 2, 1], 1361 [1, 2, 3, 3, 3, 3, 2, 1], 1362 [1, 2, 3, 3, 3, 3, 2, 1]]); 1363 } 1364 1365 /// Overlapping blocks using windows 1366 @safe pure nothrow version(mir_ndslice_test) unittest 1367 { 1368 // ---------------- 1369 // | 0 1 2 3 4 | 1370 // | 5 6 7 8 9 | 1371 // | 10 11 12 13 14 | 1372 // | 15 16 17 18 19 | 1373 // | 20 21 22 23 24 | 1374 // ---------------- 1375 //-> 1376 // --------------------- 1377 // | 0 1 2 | 2 3 4 | 1378 // | 5 6 7 | 7 8 9 | 1379 // | 10 11 12 | 12 13 14 | 1380 // | - - - - - - - - - - | 1381 // | 10 11 13 | 12 13 14 | 1382 // | 15 16 17 | 17 18 19 | 1383 // | 20 21 22 | 22 23 24 | 1384 // --------------------- 1385 1386 import mir.ndslice.slice; 1387 import mir.ndslice.dynamic : strided; 1388 1389 auto overlappingBlocks = iota(5, 5) 1390 .windows(3, 3) 1391 .universal 1392 .strided!(0, 1)(2, 2); 1393 1394 assert(overlappingBlocks == 1395 [[[[ 0, 1, 2], [ 5, 6, 7], [10, 11, 12]], 1396 [[ 2, 3, 4], [ 7, 8, 9], [12, 13, 14]]], 1397 [[[10, 11, 12], [15, 16, 17], [20, 21, 22]], 1398 [[12, 13, 14], [17, 18, 19], [22, 23, 24]]]]); 1399 } 1400 1401 version(mir_ndslice_test) unittest 1402 { 1403 auto w = iota(9, 9).windows(3, 3); 1404 assert(w.front == w[0]); 1405 } 1406 1407 /++ 1408 Error codes for $(LREF reshape). 1409 +/ 1410 enum ReshapeError 1411 { 1412 /// No error 1413 none, 1414 /// Slice should be not empty 1415 empty, 1416 /// Total element count should be the same 1417 total, 1418 /// Structure is incompatible with new shape 1419 incompatible, 1420 } 1421 1422 /++ 1423 Returns a new slice for the same data with different dimensions. 1424 1425 Params: 1426 slice = slice to be reshaped 1427 rlengths = list of new dimensions. One of the lengths can be set to `-1`. 1428 In this case, the corresponding dimension is inferable. 1429 err = $(LREF ReshapeError) code 1430 Returns: 1431 reshaped slice 1432 +/ 1433 Slice!(Iterator, M, kind) reshape 1434 (Iterator, size_t N, SliceKind kind, size_t M) 1435 (Slice!(Iterator, N, kind) slice, ptrdiff_t[M] rlengths, ref int err) 1436 { 1437 static if (kind == Canonical) 1438 { 1439 auto r = slice.universal.reshape(rlengths, err); 1440 assert(err || r._strides[$-1] == 1); 1441 r._strides[$-1] = 1; 1442 return r.assumeCanonical; 1443 } 1444 else 1445 { 1446 alias Ret = typeof(return); 1447 auto structure = Ret._Structure.init; 1448 alias lengths = structure[0]; 1449 foreach (i; Iota!M) 1450 lengths[i] = rlengths[i]; 1451 1452 /// Code size optimization 1453 immutable size_t eco = slice.elementCount; 1454 size_t ecn = lengths[0 .. rlengths.length].iota.elementCount; 1455 if (eco == 0) 1456 { 1457 err = ReshapeError.empty; 1458 goto R; 1459 } 1460 foreach (i; Iota!M) 1461 if (lengths[i] == -1) 1462 { 1463 ecn = -ecn; 1464 lengths[i] = eco / ecn; 1465 ecn *= lengths[i]; 1466 break; 1467 } 1468 if (eco != ecn) 1469 { 1470 err = ReshapeError.total; 1471 goto R; 1472 } 1473 static if (kind == Universal) 1474 { 1475 for (size_t oi, ni, oj, nj; oi < N && ni < M; oi = oj, ni = nj) 1476 { 1477 size_t op = slice._lengths[oj++]; 1478 size_t np = lengths[nj++]; 1479 1480 for (;;) 1481 { 1482 if (op < np) 1483 op *= slice._lengths[oj++]; 1484 if (op > np) 1485 np *= lengths[nj++]; 1486 if (op == np) 1487 break; 1488 } 1489 while (oj < N && slice._lengths[oj] == 1) oj++; 1490 while (nj < M && lengths[nj] == 1) nj++; 1491 1492 for (size_t l = oi, r = oi + 1; r < oj; r++) 1493 if (slice._lengths[r] != 1) 1494 { 1495 if (slice._strides[l] != slice._lengths[r] * slice._strides[r]) 1496 { 1497 err = ReshapeError.incompatible; 1498 goto R; 1499 } 1500 l = r; 1501 } 1502 assert((oi == N) == (ni == M)); 1503 1504 structure[1][nj - 1] = slice._strides[oj - 1]; 1505 foreach_reverse (i; ni .. nj - 1) 1506 structure[1][i] = lengths[i + 1] * structure[1][i + 1]; 1507 } 1508 } 1509 foreach (i; Iota!(M, Ret.N)) 1510 lengths[i] = slice._lengths[i + N - M]; 1511 static if (M < Ret.S) 1512 foreach (i; Iota!(M, Ret.S)) 1513 structure[1][i] = slice._strides[i + N - M]; 1514 err = 0; 1515 return Ret(structure, slice._iterator); 1516 R: 1517 return Ret(structure, slice._iterator.init); 1518 } 1519 } 1520 1521 /// 1522 @safe nothrow pure 1523 version(mir_ndslice_test) unittest 1524 { 1525 import mir.ndslice.dynamic : allReversed; 1526 int err; 1527 auto slice = iota(3, 4) 1528 .universal 1529 .allReversed 1530 .reshape([-1, 3], err); 1531 assert(err == 0); 1532 assert(slice == 1533 [[11, 10, 9], 1534 [ 8, 7, 6], 1535 [ 5, 4, 3], 1536 [ 2, 1, 0]]); 1537 } 1538 1539 /// Reshaping with memory allocation 1540 @safe pure version(mir_ndslice_test) unittest 1541 { 1542 import mir.ndslice.slice: sliced; 1543 import mir.ndslice.allocation: slice; 1544 import mir.ndslice.dynamic : reversed; 1545 1546 auto reshape2(S, size_t M)(S sl, ptrdiff_t[M] lengths) 1547 { 1548 int err; 1549 // Tries to reshape without allocation 1550 auto ret = sl.reshape(lengths, err); 1551 if (!err) 1552 return ret; 1553 if (err == ReshapeError.incompatible) 1554 // allocates, flattens, reshapes with `sliced`, converts to universal kind 1555 return sl.slice.flattened.sliced(cast(size_t[M])lengths).universal; 1556 throw new Exception("total elements count is different or equals to zero"); 1557 } 1558 1559 auto sl = iota!int(3, 4) 1560 .slice 1561 .universal 1562 .reversed!0; 1563 1564 assert(reshape2(sl, [4, 3]) == 1565 [[ 8, 9, 10], 1566 [11, 4, 5], 1567 [ 6, 7, 0], 1568 [ 1, 2, 3]]); 1569 } 1570 1571 nothrow @safe pure version(mir_ndslice_test) unittest 1572 { 1573 import mir.ndslice.dynamic : allReversed; 1574 auto slice = iota(1, 1, 3, 2, 1, 2, 1).universal.allReversed; 1575 int err; 1576 assert(slice.reshape([1, -1, 1, 1, 3, 1], err) == 1577 [[[[[[11], [10], [9]]]], 1578 [[[[ 8], [ 7], [6]]]], 1579 [[[[ 5], [ 4], [3]]]], 1580 [[[[ 2], [ 1], [0]]]]]]); 1581 assert(err == 0); 1582 } 1583 1584 // Issue 15919 1585 nothrow @nogc @safe pure 1586 version(mir_ndslice_test) unittest 1587 { 1588 int err; 1589 assert(iota(3, 4, 5, 6, 7).pack!2.reshape([4, 3, 5], err)[0, 0, 0].shape == cast(size_t[2])[6, 7]); 1590 assert(err == 0); 1591 } 1592 1593 nothrow @nogc @safe pure version(mir_ndslice_test) unittest 1594 { 1595 import mir.ndslice.slice; 1596 1597 int err; 1598 auto e = iota(1); 1599 // resize to the wrong dimension 1600 auto s = e.reshape([2], err); 1601 assert(err == ReshapeError.total); 1602 e.popFront; 1603 // test with an empty slice 1604 e.reshape([1], err); 1605 assert(err == ReshapeError.empty); 1606 } 1607 1608 nothrow @nogc @safe pure 1609 version(mir_ndslice_test) unittest 1610 { 1611 auto pElements = iota(3, 4, 5, 6, 7) 1612 .pack!2 1613 .flattened; 1614 assert(pElements[0][0] == iota(7)); 1615 assert(pElements[$-1][$-1] == iota([7], 2513)); 1616 } 1617 1618 /++ 1619 A contiguous 1-dimensional slice of all elements of a slice. 1620 `flattened` iterates existing data. 1621 The order of elements is preserved. 1622 1623 `flattened` can be generalized with other selectors. 1624 1625 Params: 1626 slice = slice to be iterated 1627 Returns: 1628 contiguous 1-dimensional slice of elements of the `slice` 1629 +/ 1630 Slice!(FlattenedIterator!(Iterator, N, kind)) 1631 flattened 1632 (Iterator, size_t N, SliceKind kind) 1633 (Slice!(Iterator, N, kind) slice) 1634 if (N != 1 && kind != Contiguous) 1635 { 1636 import core.lifetime: move; 1637 size_t[typeof(return).N] lengths; 1638 sizediff_t[typeof(return)._iterator._indices.length] indices; 1639 lengths[0] = slice.elementCount; 1640 return typeof(return)(lengths, FlattenedIterator!(Iterator, N, kind)(indices, slice.move)); 1641 } 1642 1643 /// ditto 1644 Slice!Iterator 1645 flattened 1646 (Iterator, size_t N) 1647 (Slice!(Iterator, N) slice) 1648 { 1649 static if (N == 1) 1650 { 1651 return slice; 1652 } 1653 else 1654 { 1655 import core.lifetime: move; 1656 size_t[typeof(return).N] lengths; 1657 lengths[0] = slice.elementCount; 1658 return typeof(return)(lengths, slice._iterator.move); 1659 } 1660 } 1661 1662 /// ditto 1663 Slice!(StrideIterator!Iterator) 1664 flattened 1665 (Iterator) 1666 (Slice!(Iterator, 1, Universal) slice) 1667 { 1668 import core.lifetime: move; 1669 return slice.move.hideStride; 1670 } 1671 1672 version(mir_ndslice_test) unittest 1673 { 1674 import mir.ndslice.allocation: slice; 1675 auto sl1 = iota(2, 3).slice.universal.pack!1.flattened; 1676 auto sl2 = iota(2, 3).slice.canonical.pack!1.flattened; 1677 auto sl3 = iota(2, 3).slice.pack!1.flattened; 1678 } 1679 1680 /// Regular slice 1681 @safe @nogc pure nothrow version(mir_ndslice_test) unittest 1682 { 1683 assert(iota(4, 5).flattened == iota(20)); 1684 assert(iota(4, 5).canonical.flattened == iota(20)); 1685 assert(iota(4, 5).universal.flattened == iota(20)); 1686 } 1687 1688 @safe @nogc pure nothrow version(mir_ndslice_test) unittest 1689 { 1690 assert(iota(4).flattened == iota(4)); 1691 assert(iota(4).canonical.flattened == iota(4)); 1692 assert(iota(4).universal.flattened == iota(4)); 1693 } 1694 1695 /// Packed slice 1696 @safe @nogc pure nothrow version(mir_ndslice_test) unittest 1697 { 1698 import mir.ndslice.slice; 1699 import mir.ndslice.dynamic; 1700 assert(iota(3, 4, 5, 6, 7).pack!2.flattened[1] == iota([6, 7], 6 * 7)); 1701 } 1702 1703 /// Properties 1704 @safe pure nothrow version(mir_ndslice_test) unittest 1705 { 1706 auto elems = iota(3, 4).universal.flattened; 1707 1708 elems.popFrontExactly(2); 1709 assert(elems.front == 2); 1710 /// `_index` is available only for canonical and universal ndslices. 1711 assert(elems._iterator._indices == [0, 2]); 1712 1713 elems.popBackExactly(2); 1714 assert(elems.back == 9); 1715 assert(elems.length == 8); 1716 } 1717 1718 /// Index property 1719 @safe pure nothrow version(mir_ndslice_test) unittest 1720 { 1721 import mir.ndslice.slice; 1722 auto slice = new long[20].sliced(5, 4); 1723 1724 for (auto elems = slice.universal.flattened; !elems.empty; elems.popFront) 1725 { 1726 ptrdiff_t[2] index = elems._iterator._indices; 1727 elems.front = index[0] * 10 + index[1] * 3; 1728 } 1729 assert(slice == 1730 [[ 0, 3, 6, 9], 1731 [10, 13, 16, 19], 1732 [20, 23, 26, 29], 1733 [30, 33, 36, 39], 1734 [40, 43, 46, 49]]); 1735 } 1736 1737 @safe pure nothrow version(mir_ndslice_test) unittest 1738 { 1739 auto elems = iota(3, 4).universal.flattened; 1740 assert(elems.front == 0); 1741 assert(elems.save[1] == 1); 1742 } 1743 1744 /++ 1745 Random access and slicing 1746 +/ 1747 nothrow version(mir_ndslice_test) unittest 1748 { 1749 import mir.ndslice.allocation: slice; 1750 import mir.ndslice.slice: sliced; 1751 1752 auto elems = iota(4, 5).slice.flattened; 1753 1754 elems = elems[11 .. $ - 2]; 1755 1756 assert(elems.length == 7); 1757 assert(elems.front == 11); 1758 assert(elems.back == 17); 1759 1760 foreach (i; 0 .. 7) 1761 assert(elems[i] == i + 11); 1762 1763 // assign an element 1764 elems[2 .. 6] = -1; 1765 assert(elems[2 .. 6] == repeat(-1, 4)); 1766 1767 // assign an array 1768 static ar = [-1, -2, -3, -4]; 1769 elems[2 .. 6] = ar; 1770 assert(elems[2 .. 6] == ar); 1771 1772 // assign a slice 1773 ar[] *= 2; 1774 auto sl = ar.sliced(ar.length); 1775 elems[2 .. 6] = sl; 1776 assert(elems[2 .. 6] == sl); 1777 } 1778 1779 @safe @nogc pure nothrow version(mir_ndslice_test) unittest 1780 { 1781 import mir.ndslice.dynamic : allReversed; 1782 1783 auto slice = iota(3, 4, 5); 1784 1785 foreach (ref e; slice.universal.flattened.retro) 1786 { 1787 //... 1788 } 1789 1790 foreach_reverse (ref e; slice.universal.flattened) 1791 { 1792 //... 1793 } 1794 1795 foreach (ref e; slice.universal.allReversed.flattened) 1796 { 1797 //... 1798 } 1799 } 1800 1801 @safe @nogc pure nothrow version(mir_ndslice_test) unittest 1802 { 1803 import std.range.primitives : isRandomAccessRange, hasSlicing; 1804 auto elems = iota(4, 5).flattened; 1805 static assert(isRandomAccessRange!(typeof(elems))); 1806 static assert(hasSlicing!(typeof(elems))); 1807 } 1808 1809 // Checks strides 1810 @safe @nogc pure nothrow version(mir_ndslice_test) unittest 1811 { 1812 import mir.ndslice.dynamic; 1813 import std.range.primitives : isRandomAccessRange; 1814 auto elems = iota(4, 5).universal.everted.flattened; 1815 static assert(isRandomAccessRange!(typeof(elems))); 1816 1817 elems = elems[11 .. $ - 2]; 1818 auto elems2 = elems; 1819 foreach (i; 0 .. 7) 1820 { 1821 assert(elems[i] == elems2.front); 1822 elems2.popFront; 1823 } 1824 } 1825 1826 @safe @nogc pure nothrow version(mir_ndslice_test) unittest 1827 { 1828 import mir.ndslice.slice; 1829 import mir.ndslice.dynamic; 1830 import std.range.primitives : isRandomAccessRange, hasLength; 1831 1832 auto range = (3 * 4 * 5 * 6 * 7).iota; 1833 auto slice0 = range.sliced(3, 4, 5, 6, 7).universal; 1834 auto slice1 = slice0.transposed!(2, 1).pack!2; 1835 auto elems0 = slice0.flattened; 1836 auto elems1 = slice1.flattened; 1837 1838 foreach (S; AliasSeq!(typeof(elems0), typeof(elems1))) 1839 { 1840 static assert(isRandomAccessRange!S); 1841 static assert(hasLength!S); 1842 } 1843 1844 assert(elems0.length == slice0.elementCount); 1845 assert(elems1.length == 5 * 4 * 3); 1846 1847 auto elems2 = elems1; 1848 foreach (q; slice1) 1849 foreach (w; q) 1850 foreach (e; w) 1851 { 1852 assert(!elems2.empty); 1853 assert(e == elems2.front); 1854 elems2.popFront; 1855 } 1856 assert(elems2.empty); 1857 1858 elems0.popFront(); 1859 elems0.popFrontExactly(slice0.elementCount - 14); 1860 assert(elems0.length == 13); 1861 assert(elems0 == range[slice0.elementCount - 13 .. slice0.elementCount]); 1862 1863 foreach (elem; elems0) {} 1864 } 1865 1866 // Issue 15549 1867 version(mir_ndslice_test) unittest 1868 { 1869 import std.range.primitives; 1870 import mir.ndslice.allocation; 1871 alias A = typeof(iota(1, 2, 3, 4).pack!1); 1872 static assert(isRandomAccessRange!A); 1873 static assert(hasLength!A); 1874 static assert(hasSlicing!A); 1875 alias B = typeof(slice!int(1, 2, 3, 4).pack!3); 1876 static assert(isRandomAccessRange!B); 1877 static assert(hasLength!B); 1878 static assert(hasSlicing!B); 1879 } 1880 1881 // Issue 16010 1882 version(mir_ndslice_test) unittest 1883 { 1884 auto s = iota(3, 4).flattened; 1885 foreach (_; 0 .. s.length) 1886 s = s[1 .. $]; 1887 } 1888 1889 /++ 1890 Returns a slice, the elements of which are equal to the initial multidimensional index value. 1891 For a flattened (contiguous) index, see $(LREF iota). 1892 1893 Params: 1894 N = dimension count 1895 lengths = list of dimension lengths 1896 Returns: 1897 `N`-dimensional slice composed of indices 1898 See_also: $(LREF iota) 1899 +/ 1900 Slice!(FieldIterator!(ndIotaField!N), N) 1901 ndiota 1902 (size_t N) 1903 (size_t[N] lengths...) 1904 if (N) 1905 { 1906 return FieldIterator!(ndIotaField!N)(0, ndIotaField!N(lengths[1 .. $])).sliced(lengths); 1907 } 1908 1909 /// 1910 @safe pure nothrow @nogc version(mir_ndslice_test) unittest 1911 { 1912 auto slice = ndiota(2, 3); 1913 static immutable array = 1914 [[[0, 0], [0, 1], [0, 2]], 1915 [[1, 0], [1, 1], [1, 2]]]; 1916 1917 assert(slice == array); 1918 } 1919 1920 /// 1921 @safe pure nothrow version(mir_ndslice_test) unittest 1922 { 1923 auto im = ndiota(7, 9); 1924 1925 assert(im[2, 1] == [2, 1]); 1926 1927 //slicing works correctly 1928 auto cm = im[1 .. $, 4 .. $]; 1929 assert(cm[2, 1] == [3, 5]); 1930 } 1931 1932 version(mir_ndslice_test) unittest 1933 { 1934 auto r = ndiota(1); 1935 auto d = r.front; 1936 r.popFront; 1937 import std.range.primitives; 1938 static assert(isRandomAccessRange!(typeof(r))); 1939 } 1940 1941 /++ 1942 Evenly spaced numbers over a specified interval. 1943 1944 Params: 1945 T = floating point or complex numbers type 1946 lengths = list of dimension lengths. Each length must be greater then 1. 1947 intervals = list of [start, end] pairs. 1948 Returns: 1949 `n`-dimensional grid of evenly spaced numbers over specified intervals. 1950 See_also: $(LREF) 1951 +/ 1952 auto linspace(T, size_t N)(size_t[N] lengths, T[2][N] intervals...) 1953 if (N && (isFloatingPoint!T || isComplex!T)) 1954 { 1955 Repeat!(N, LinspaceField!T) fields; 1956 foreach(i; Iota!N) 1957 { 1958 assert(lengths[i] > 1, "linspace: all lengths must be greater then 1."); 1959 fields[i] = LinspaceField!T(lengths[i], intervals[i][0], intervals[i][1]); 1960 } 1961 static if (N == 1) 1962 return slicedField(fields); 1963 else 1964 return cartesian(fields); 1965 } 1966 1967 // example from readme 1968 version(mir_ndslice_test) unittest 1969 { 1970 import mir.ndslice; 1971 1972 enum fmt = "%(%(%.2f %)\n%)\n"; 1973 1974 auto a = magic(5).as!float; 1975 // writefln(fmt, a); 1976 1977 auto b = linspace!float([5, 5], [1f, 2f], [0f, 1f]).map!"a * a + b"; 1978 // writefln(fmt, b); 1979 1980 auto c = slice!float(5, 5); 1981 c[] = transposed(a + b / 2); 1982 } 1983 1984 /// 1D 1985 @safe pure nothrow 1986 version(mir_ndslice_test) unittest 1987 { 1988 auto s = linspace!double([5], [1.0, 2.0]); 1989 assert(s == [1.0, 1.25, 1.5, 1.75, 2.0]); 1990 1991 // reverse order 1992 assert(linspace!double([5], [2.0, 1.0]) == s.retro); 1993 1994 // remove endpoint 1995 s.popBack; 1996 assert(s == [1.0, 1.25, 1.5, 1.75]); 1997 } 1998 1999 /// 2D 2000 @safe pure nothrow 2001 version(mir_ndslice_test) unittest 2002 { 2003 import mir.functional: tuple; 2004 2005 auto s = linspace!double([5, 3], [1.0, 2.0], [0.0, 1.0]); 2006 2007 assert(s == [ 2008 [tuple(1.00, 0.00), tuple(1.00, 0.5), tuple(1.00, 1.0)], 2009 [tuple(1.25, 0.00), tuple(1.25, 0.5), tuple(1.25, 1.0)], 2010 [tuple(1.50, 0.00), tuple(1.50, 0.5), tuple(1.50, 1.0)], 2011 [tuple(1.75, 0.00), tuple(1.75, 0.5), tuple(1.75, 1.0)], 2012 [tuple(2.00, 0.00), tuple(2.00, 0.5), tuple(2.00, 1.0)], 2013 ]); 2014 2015 assert(s.map!"a * b" == [ 2016 [0.0, 0.500, 1.00], 2017 [0.0, 0.625, 1.25], 2018 [0.0, 0.750, 1.50], 2019 [0.0, 0.875, 1.75], 2020 [0.0, 1.000, 2.00], 2021 ]); 2022 } 2023 2024 /// Complex numbers 2025 @safe pure nothrow 2026 version(mir_ndslice_test) unittest 2027 { 2028 import mir.complex; 2029 alias C = Complex!double; 2030 auto s = linspace!C([3], [C(1.0, 0), C(2.0, 4)]); 2031 assert(s == [C(1.0, 0), C(1.5, 2), C(2.0, 4)]); 2032 } 2033 2034 /++ 2035 Returns a slice with identical elements. 2036 `RepeatSlice` stores only single value. 2037 Params: 2038 lengths = list of dimension lengths 2039 Returns: 2040 `n`-dimensional slice composed of identical values, where `n` is dimension count. 2041 +/ 2042 Slice!(FieldIterator!(RepeatField!T), M, Universal) 2043 repeat(T, size_t M)(T value, size_t[M] lengths...) @trusted 2044 if (M && !isSlice!T) 2045 { 2046 size_t[M] ls = lengths; 2047 return typeof(return)( 2048 ls, 2049 sizediff_t[M].init, 2050 typeof(return).Iterator(0, RepeatField!T(cast(RepeatField!T.UT) value))); 2051 } 2052 2053 /// ditto 2054 Slice!(SliceIterator!(Iterator, N, kind), M, Universal) 2055 repeat 2056 (SliceKind kind, size_t N, Iterator, size_t M) 2057 (Slice!(Iterator, N, kind) slice, size_t[M] lengths...) 2058 if (M) 2059 { 2060 import core.lifetime: move; 2061 size_t[M] ls = lengths; 2062 return typeof(return)( 2063 ls, 2064 sizediff_t[M].init, 2065 typeof(return).Iterator( 2066 slice._structure, 2067 move(slice._iterator))); 2068 } 2069 2070 /// 2071 @safe pure nothrow 2072 version(mir_ndslice_test) unittest 2073 { 2074 auto sl = iota(3).repeat(4); 2075 assert(sl == [[0, 1, 2], 2076 [0, 1, 2], 2077 [0, 1, 2], 2078 [0, 1, 2]]); 2079 } 2080 2081 /// 2082 @safe pure nothrow version(mir_ndslice_test) unittest 2083 { 2084 import mir.ndslice.dynamic : transposed; 2085 2086 auto sl = iota(3) 2087 .repeat(4) 2088 .unpack 2089 .universal 2090 .transposed; 2091 2092 assert(sl == [[0, 0, 0, 0], 2093 [1, 1, 1, 1], 2094 [2, 2, 2, 2]]); 2095 } 2096 2097 /// 2098 @safe pure nothrow version(mir_ndslice_test) unittest 2099 { 2100 import mir.ndslice.allocation; 2101 2102 auto sl = iota([3], 6).slice; 2103 auto slC = sl.repeat(2, 3); 2104 sl[1] = 4; 2105 assert(slC == [[[6, 4, 8], 2106 [6, 4, 8], 2107 [6, 4, 8]], 2108 [[6, 4, 8], 2109 [6, 4, 8], 2110 [6, 4, 8]]]); 2111 } 2112 2113 /// 2114 @safe pure nothrow version(mir_ndslice_test) unittest 2115 { 2116 import mir.primitives: DeepElementType; 2117 2118 auto sl = repeat(4.0, 2, 3); 2119 assert(sl == [[4.0, 4.0, 4.0], 2120 [4.0, 4.0, 4.0]]); 2121 2122 static assert(is(DeepElementType!(typeof(sl)) == double)); 2123 2124 sl[1, 1] = 3; 2125 assert(sl == [[3.0, 3.0, 3.0], 2126 [3.0, 3.0, 3.0]]); 2127 } 2128 2129 /++ 2130 Cycle repeates 1-dimensional field/range/array/slice in a fixed length 1-dimensional slice. 2131 +/ 2132 auto cycle(Field)(Field field, size_t loopLength, size_t length) 2133 if (!isSlice!Field && !is(Field : T[], T)) 2134 { 2135 return CycleField!Field(loopLength, field).slicedField(length); 2136 } 2137 2138 /// ditto 2139 auto cycle(size_t loopLength, Field)(Field field, size_t length) 2140 if (!isSlice!Field && !is(Field : T[], T)) 2141 { 2142 static assert(loopLength); 2143 return CycleField!(Field, loopLength)(field).slicedField(length); 2144 } 2145 2146 /// ditto 2147 auto cycle(Iterator, SliceKind kind)(Slice!(Iterator, 1, kind) slice, size_t length) 2148 { 2149 assert(slice.length); 2150 static if (kind == Universal) 2151 return slice.hideStride.cycle(length); 2152 else 2153 return CycleField!Iterator(slice._lengths[0], slice._iterator).slicedField(length); 2154 } 2155 2156 /// ditto 2157 auto cycle(size_t loopLength, Iterator, SliceKind kind)(Slice!(Iterator, 1, kind) slice, size_t length) 2158 { 2159 static assert(loopLength); 2160 assert(loopLength <= slice.length); 2161 static if (kind == Universal) 2162 return slice.hideStride.cycle!loopLength(length); 2163 else 2164 return CycleField!(Iterator, loopLength)(slice._iterator).slicedField(length); 2165 } 2166 2167 /// ditto 2168 auto cycle(T)(T[] array, size_t length) 2169 { 2170 return cycle(array.sliced, length); 2171 } 2172 2173 /// ditto 2174 auto cycle(size_t loopLength, T)(T[] array, size_t length) 2175 { 2176 return cycle!loopLength(array.sliced, length); 2177 } 2178 2179 /// ditto 2180 auto cycle(size_t loopLength, T)(T withAsSlice, size_t length) 2181 if (hasAsSlice!T) 2182 { 2183 return cycle!loopLength(withAsSlice.asSlice, length); 2184 } 2185 2186 /// 2187 @safe pure nothrow version(mir_ndslice_test) unittest 2188 { 2189 auto slice = iota(3); 2190 assert(slice.cycle(7) == [0, 1, 2, 0, 1, 2, 0]); 2191 assert(slice.cycle!2(7) == [0, 1, 0, 1, 0, 1, 0]); 2192 assert([0, 1, 2].cycle(7) == [0, 1, 2, 0, 1, 2, 0]); 2193 assert([4, 3, 2, 1].cycle!4(7) == [4, 3, 2, 1, 4, 3, 2]); 2194 } 2195 2196 /++ 2197 Strides 1-dimensional slice. 2198 Params: 2199 slice = 1-dimensional unpacked slice. 2200 factor = positive stride size. 2201 Returns: 2202 Contiguous slice with strided iterator. 2203 See_also: $(SUBREF dynamic, strided) 2204 +/ 2205 auto stride 2206 (Iterator, size_t N, SliceKind kind) 2207 (Slice!(Iterator, N, kind) slice, ptrdiff_t factor) 2208 if (N == 1) 2209 in 2210 { 2211 assert (factor > 0, "factor must be positive."); 2212 } 2213 do 2214 { 2215 static if (kind == Contiguous) 2216 return slice.universal.stride(factor); 2217 else 2218 { 2219 import mir.ndslice.dynamic: strided; 2220 return slice.strided!0(factor).hideStride; 2221 } 2222 } 2223 2224 ///ditto 2225 template stride(size_t factor = 2) 2226 if (factor > 1) 2227 { 2228 auto stride 2229 (Iterator, size_t N, SliceKind kind) 2230 (Slice!(Iterator, N, kind) slice) 2231 { 2232 import core.lifetime: move; 2233 static if (N > 1) 2234 { 2235 return stride(slice.move.ipack!1.map!(.stride!factor)); 2236 } 2237 else 2238 static if (kind == Contiguous) 2239 { 2240 immutable rem = slice._lengths[0] % factor; 2241 slice._lengths[0] /= factor; 2242 if (rem) 2243 slice._lengths[0]++; 2244 return Slice!(StrideIterator!(Iterator, factor), 1, kind)(slice._structure, StrideIterator!(Iterator, factor)(move(slice._iterator))); 2245 } 2246 else 2247 { 2248 return .stride(slice.move, factor); 2249 } 2250 } 2251 2252 /// ditto 2253 auto stride(T)(T[] array) 2254 { 2255 return stride(array.sliced); 2256 } 2257 2258 /// ditto 2259 auto stride(T)(T withAsSlice) 2260 if (hasAsSlice!T) 2261 { 2262 return stride(withAsSlice.asSlice); 2263 } 2264 } 2265 2266 /// ditto 2267 auto stride(T)(T[] array, ptrdiff_t factor) 2268 { 2269 return stride(array.sliced, factor); 2270 } 2271 2272 /// ditto 2273 auto stride(T)(T withAsSlice, ptrdiff_t factor) 2274 if (hasAsSlice!T) 2275 { 2276 return stride(withAsSlice.asSlice, factor); 2277 } 2278 2279 /// 2280 @safe pure nothrow @nogc version(mir_ndslice_test) unittest 2281 { 2282 auto slice = iota(6); 2283 static immutable str = [0, 2, 4]; 2284 assert(slice.stride(2) == str); // runtime factor 2285 assert(slice.stride!2 == str); // compile time factor 2286 assert(slice.stride == str); // default compile time factor is 2 2287 assert(slice.universal.stride(2) == str); 2288 } 2289 2290 /// ND-compile time 2291 @safe pure nothrow @nogc version(mir_ndslice_test) unittest 2292 { 2293 auto slice = iota(4, 6); 2294 static immutable str = [[0, 2, 4], [12, 14, 16]]; 2295 assert(slice.stride!2 == str); // compile time factor 2296 assert(slice.stride == str); // default compile time factor is 2 2297 } 2298 2299 /++ 2300 Reverses order of iteration for all dimensions. 2301 Params: 2302 slice = slice, range, or array. 2303 Returns: 2304 Slice/range with reversed order of iteration for all dimensions. 2305 See_also: $(SUBREF dynamic, reversed), $(SUBREF dynamic, allReversed). 2306 +/ 2307 auto retro 2308 (Iterator, size_t N, SliceKind kind) 2309 (Slice!(Iterator, N, kind) slice) 2310 @trusted 2311 { 2312 import core.lifetime: move; 2313 static if (kind == Contiguous || kind == Canonical) 2314 { 2315 size_t[slice.N] lengths; 2316 foreach (i; Iota!(slice.N)) 2317 lengths[i] = slice._lengths[i]; 2318 static if (slice.S) 2319 { 2320 sizediff_t[slice.S] strides; 2321 foreach (i; Iota!(slice.S)) 2322 strides[i] = slice._strides[i]; 2323 alias structure = AliasSeq!(lengths, strides); 2324 } 2325 else 2326 { 2327 alias structure = lengths; 2328 } 2329 static if (is(Iterator : RetroIterator!It, It)) 2330 { 2331 alias Ret = Slice!(It, N, kind); 2332 slice._iterator._iterator -= slice.lastIndex; 2333 return Ret(structure, slice._iterator._iterator.move); 2334 } 2335 else 2336 { 2337 alias Ret = Slice!(RetroIterator!Iterator, N, kind); 2338 slice._iterator += slice.lastIndex; 2339 return Ret(structure, RetroIterator!Iterator(slice._iterator.move)); 2340 } 2341 } 2342 else 2343 { 2344 import mir.ndslice.dynamic: allReversed; 2345 return slice.move.allReversed; 2346 } 2347 } 2348 2349 /// ditto 2350 auto retro(T)(T[] array) 2351 { 2352 return retro(array.sliced); 2353 } 2354 2355 /// ditto 2356 auto retro(T)(T withAsSlice) 2357 if (hasAsSlice!T) 2358 { 2359 return retro(withAsSlice.asSlice); 2360 } 2361 2362 /// ditto 2363 auto retro(Range)(Range r) 2364 if (!hasAsSlice!Range && !isSlice!Range && !is(Range : T[], T)) 2365 { 2366 import std.traits: Unqual; 2367 2368 static if (is(Unqual!Range == Range)) 2369 { 2370 import core.lifetime: move; 2371 static if (is(Range : RetroRange!R, R)) 2372 { 2373 return move(r._source); 2374 } 2375 else 2376 { 2377 return RetroRange!Range(move(r)); 2378 } 2379 } 2380 else 2381 { 2382 return .retro!(Unqual!Range)(r); 2383 } 2384 } 2385 2386 /// ditto 2387 struct RetroRange(Range) 2388 { 2389 import mir.primitives: hasLength; 2390 2391 /// 2392 Range _source; 2393 2394 private enum hasAccessByRef = __traits(compiles, &_source.front); 2395 2396 @property 2397 { 2398 bool empty()() const { return _source.empty; } 2399 static if (hasLength!Range) 2400 auto length()() const { return _source.length; } 2401 auto ref front()() { return _source.back; } 2402 auto ref back()() { return _source.front; } 2403 static if (__traits(hasMember, Range, "save")) 2404 auto save()() { return RetroRange(_source.save); } 2405 alias opDollar = length; 2406 2407 static if (!hasAccessByRef) 2408 { 2409 import std.traits: ForeachType; 2410 2411 void front()(ForeachType!R val) 2412 { 2413 import mir.functional: forward; 2414 _source.back = forward!val; 2415 } 2416 2417 void back()(ForeachType!R val) 2418 { 2419 import mir.functional: forward; 2420 _source.front = forward!val; 2421 } 2422 } 2423 } 2424 2425 void popFront()() { _source.popBack(); } 2426 void popBack()() { _source.popFront(); } 2427 2428 static if (is(typeof(_source.moveBack()))) 2429 auto moveFront()() { return _source.moveBack(); } 2430 2431 static if (is(typeof(_source.moveFront()))) 2432 auto moveBack()() { return _source.moveFront(); } 2433 } 2434 2435 /// 2436 @safe pure nothrow @nogc version(mir_ndslice_test) unittest 2437 { 2438 auto slice = iota(2, 3); 2439 static immutable reversed = [[5, 4, 3], [2, 1, 0]]; 2440 assert(slice.retro == reversed); 2441 assert(slice.canonical.retro == reversed); 2442 assert(slice.universal.retro == reversed); 2443 2444 static assert(is(typeof(slice.retro.retro) == typeof(slice))); 2445 static assert(is(typeof(slice.canonical.retro.retro) == typeof(slice.canonical))); 2446 static assert(is(typeof(slice.universal.retro) == typeof(slice.universal))); 2447 } 2448 2449 /// Ranges 2450 @safe pure nothrow @nogc version(mir_ndslice_test) unittest 2451 { 2452 import mir.algorithm.iteration: equal; 2453 import std.range: std_iota = iota; 2454 2455 assert(std_iota(4).retro.equal(iota(4).retro)); 2456 static assert(is(typeof(std_iota(4).retro.retro) == typeof(std_iota(4)))); 2457 } 2458 2459 /++ 2460 Bitwise slice over an integral slice. 2461 Params: 2462 slice = a contiguous or canonical slice on top of integral iterator. 2463 Returns: A bitwise slice. 2464 +/ 2465 auto bitwise 2466 (Iterator, size_t N, SliceKind kind, I = typeof(Iterator.init[size_t.init])) 2467 (Slice!(Iterator, N, kind) slice) 2468 if (__traits(isIntegral, I) && (kind != Universal || N == 1)) 2469 { 2470 import core.lifetime: move; 2471 static if (kind == Universal) 2472 { 2473 return slice.move.flattened.bitwise; 2474 } 2475 else 2476 { 2477 static if (is(Iterator : FieldIterator!Field, Field)) 2478 { 2479 enum simplified = true; 2480 alias It = FieldIterator!(BitField!Field); 2481 } 2482 else 2483 { 2484 enum simplified = false; 2485 alias It = FieldIterator!(BitField!Iterator); 2486 } 2487 alias Ret = Slice!(It, N, kind); 2488 auto structure_ = Ret._Structure.init; 2489 foreach(i; Iota!(Ret.N)) 2490 structure_[0][i] = slice._lengths[i]; 2491 structure_[0][$ - 1] *= I.sizeof * 8; 2492 foreach(i; Iota!(Ret.S)) 2493 structure_[1][i] = slice._strides[i]; 2494 static if (simplified) 2495 return Ret(structure_, It(slice._iterator._index * I.sizeof * 8, BitField!Field(slice._iterator._field.move))); 2496 else 2497 return Ret(structure_, It(0, BitField!Iterator(slice._iterator.move))); 2498 } 2499 } 2500 2501 /// ditto 2502 auto bitwise(T)(T[] array) 2503 { 2504 return bitwise(array.sliced); 2505 } 2506 2507 /// ditto 2508 auto bitwise(T)(T withAsSlice) 2509 if (hasAsSlice!T) 2510 { 2511 return bitwise(withAsSlice.asSlice); 2512 } 2513 2514 /// 2515 @safe pure nothrow @nogc 2516 version(mir_ndslice_test) unittest 2517 { 2518 size_t[10] data; 2519 auto bits = data[].bitwise; 2520 assert(bits.length == data.length * size_t.sizeof * 8); 2521 bits[111] = true; 2522 assert(bits[111]); 2523 2524 bits.popFront; 2525 assert(bits[110]); 2526 bits[] = true; 2527 bits[110] = false; 2528 bits = bits[10 .. $]; 2529 assert(bits[100] == false); 2530 } 2531 2532 @safe pure nothrow @nogc 2533 version(mir_ndslice_test) unittest 2534 { 2535 size_t[10] data; 2536 auto slice = FieldIterator!(size_t[])(0, data[]).sliced(10); 2537 slice.popFrontExactly(2); 2538 auto bits_normal = data[].sliced.bitwise; 2539 auto bits = slice.bitwise; 2540 assert(bits.length == (data.length - 2) * size_t.sizeof * 8); 2541 bits[111] = true; 2542 assert(bits[111]); 2543 assert(bits_normal[111 + size_t.sizeof * 2 * 8]); 2544 auto ubits = slice.universal.bitwise; 2545 assert(bits.map!"~a" == bits.map!"!a"); 2546 static assert (is(typeof(bits.map!"~a") == typeof(bits.map!"!a"))); 2547 assert(bits.map!"~a" == bits.map!"!!!a"); 2548 static assert (!is(typeof(bits.map!"~a") == typeof(bits.map!"!!!a"))); 2549 assert(bits == ubits); 2550 2551 bits.popFront; 2552 assert(bits[110]); 2553 bits[] = true; 2554 bits[110] = false; 2555 bits = bits[10 .. $]; 2556 assert(bits[100] == false); 2557 } 2558 2559 /++ 2560 Bitwise field over an integral field. 2561 Params: 2562 field = an integral field. 2563 Returns: A bitwise field. 2564 +/ 2565 auto bitwiseField(Field, I = typeof(Field.init[size_t.init]))(Field field) 2566 if (__traits(isUnsigned, I)) 2567 { 2568 import core.lifetime: move; 2569 return BitField!(Field, I)(field.move); 2570 } 2571 2572 /++ 2573 Bitpack slice over an integral slice. 2574 2575 Bitpack is used to represent unsigned integer slice with fewer number of bits in integer binary representation. 2576 2577 Params: 2578 pack = counts of bits in the integer. 2579 slice = a contiguous or canonical slice on top of integral iterator. 2580 Returns: A bitpack slice. 2581 +/ 2582 auto bitpack 2583 (size_t pack, Iterator, size_t N, SliceKind kind, I = typeof(Iterator.init[size_t.init])) 2584 (Slice!(Iterator, N, kind) slice) 2585 if (__traits(isIntegral, I) && (kind == Contiguous || kind == Canonical) && pack > 1) 2586 { 2587 import core.lifetime: move; 2588 static if (is(Iterator : FieldIterator!Field, Field) && I.sizeof * 8 % pack == 0) 2589 { 2590 enum simplified = true; 2591 alias It = FieldIterator!(BitpackField!(Field, pack)); 2592 } 2593 else 2594 { 2595 enum simplified = false; 2596 alias It = FieldIterator!(BitpackField!(Iterator, pack)); 2597 } 2598 alias Ret = Slice!(It, N, kind); 2599 auto structure = Ret._Structure.init; 2600 foreach(i; Iota!(Ret.N)) 2601 structure[0][i] = slice._lengths[i]; 2602 structure[0][$ - 1] *= I.sizeof * 8; 2603 structure[0][$ - 1] /= pack; 2604 foreach(i; Iota!(Ret.S)) 2605 structure[1][i] = slice._strides[i]; 2606 static if (simplified) 2607 return Ret(structure, It(slice._iterator._index * I.sizeof * 8 / pack, BitpackField!(Field, pack)(slice._iterator._field.move))); 2608 else 2609 return Ret(structure, It(0, BitpackField!(Iterator, pack)(slice._iterator.move))); 2610 } 2611 2612 /// ditto 2613 auto bitpack(size_t pack, T)(T[] array) 2614 { 2615 return bitpack!pack(array.sliced); 2616 } 2617 2618 /// ditto 2619 auto bitpack(size_t pack, T)(T withAsSlice) 2620 if (hasAsSlice!T) 2621 { 2622 return bitpack!pack(withAsSlice.asSlice); 2623 } 2624 2625 /// 2626 @safe pure nothrow @nogc 2627 version(mir_ndslice_test) unittest 2628 { 2629 size_t[10] data; 2630 // creates a packed unsigned integer slice with max allowed value equal to `2^^6 - 1 == 63`. 2631 auto packs = data[].bitpack!6; 2632 assert(packs.length == data.length * size_t.sizeof * 8 / 6); 2633 packs[$ - 1] = 24; 2634 assert(packs[$ - 1] == 24); 2635 2636 packs.popFront; 2637 assert(packs[$ - 1] == 24); 2638 } 2639 2640 /++ 2641 Bytegroup slice over an integral slice. 2642 2643 Groups existing slice into fixed length chunks and uses them as data store for destination type. 2644 2645 Correctly handles scalar types on both little-endian and big-endian platforms. 2646 2647 Params: 2648 group = count of iterator items used to store the destination type. 2649 DestinationType = deep element type of the result slice. 2650 slice = a contiguous or canonical slice. 2651 Returns: A bytegroup slice. 2652 +/ 2653 Slice!(BytegroupIterator!(Iterator, group, DestinationType), N, kind) 2654 bytegroup 2655 (size_t group, DestinationType, Iterator, size_t N, SliceKind kind) 2656 (Slice!(Iterator, N, kind) slice) 2657 if ((kind == Contiguous || kind == Canonical) && group) 2658 { 2659 import core.lifetime: move; 2660 auto structure = slice._structure; 2661 structure[0][$ - 1] /= group; 2662 return typeof(return)(structure, BytegroupIterator!(Iterator, group, DestinationType)(slice._iterator.move)); 2663 } 2664 2665 /// ditto 2666 auto bytegroup(size_t pack, DestinationType, T)(T[] array) 2667 { 2668 return bytegroup!(pack, DestinationType)(array.sliced); 2669 } 2670 2671 /// ditto 2672 auto bytegroup(size_t pack, DestinationType, T)(T withAsSlice) 2673 if (hasAsSlice!T) 2674 { 2675 return bytegroup!(pack, DestinationType)(withAsSlice.asSlice); 2676 } 2677 2678 /// 24 bit integers 2679 @safe pure nothrow @nogc 2680 version(mir_ndslice_test) unittest 2681 { 2682 import mir.ndslice.slice: DeepElementType, sliced; 2683 2684 ubyte[20] data; 2685 // creates a packed unsigned integer slice with max allowed value equal to `2^^6 - 1 == 63`. 2686 auto int24ar = data[].bytegroup!(3, int); // 24 bit integers 2687 assert(int24ar.length == data.length / 3); 2688 2689 enum checkInt = ((1 << 20) - 1); 2690 2691 int24ar[3] = checkInt; 2692 assert(int24ar[3] == checkInt); 2693 2694 int24ar.popFront; 2695 assert(int24ar[2] == checkInt); 2696 2697 static assert(is(DeepElementType!(typeof(int24ar)) == int)); 2698 } 2699 2700 /// 48 bit integers 2701 @safe pure nothrow @nogc 2702 version(mir_ndslice_test) unittest 2703 { 2704 import mir.ndslice.slice: DeepElementType, sliced; 2705 ushort[20] data; 2706 // creates a packed unsigned integer slice with max allowed value equal to `2^^6 - 1 == 63`. 2707 auto int48ar = data[].sliced.bytegroup!(3, long); // 48 bit integers 2708 assert(int48ar.length == data.length / 3); 2709 2710 enum checkInt = ((1L << 44) - 1); 2711 2712 int48ar[3] = checkInt; 2713 assert(int48ar[3] == checkInt); 2714 2715 int48ar.popFront; 2716 assert(int48ar[2] == checkInt); 2717 2718 static assert(is(DeepElementType!(typeof(int48ar)) == long)); 2719 } 2720 2721 /++ 2722 Implements the homonym function (also known as `transform`) present 2723 in many languages of functional flavor. The call `map!(fun)(slice)` 2724 returns a slice of which elements are obtained by applying `fun` 2725 for all elements in `slice`. The original slices are 2726 not changed. Evaluation is done lazily. 2727 2728 Note: 2729 $(SUBREF dynamic, transposed) and 2730 $(SUBREF topology, pack) can be used to specify dimensions. 2731 Params: 2732 fun = One or more functions. 2733 See_Also: 2734 $(LREF cached), $(LREF vmap), $(LREF rcmap), $(LREF indexed), 2735 $(LREF pairwise), $(LREF subSlices), $(LREF slide), $(LREF zip), 2736 $(HTTP en.wikipedia.org/wiki/Map_(higher-order_function), Map (higher-order function)) 2737 +/ 2738 template map(fun...) 2739 if (fun.length) 2740 { 2741 import mir.functional: adjoin, naryFun, pipe; 2742 static if (fun.length == 1) 2743 { 2744 static if (__traits(isSame, naryFun!(fun[0]), fun[0])) 2745 { 2746 alias f = fun[0]; 2747 @fmamath: 2748 /++ 2749 Params: 2750 slice = An ndslice, array, or an input range. 2751 Returns: 2752 ndslice or an input range with each fun applied to all the elements. If there is more than one 2753 fun, the element type will be `Tuple` containing one element for each fun. 2754 +/ 2755 auto map(Iterator, size_t N, SliceKind kind) 2756 (Slice!(Iterator, N, kind) slice) 2757 { 2758 import core.lifetime: move; 2759 alias MIterator = typeof(_mapIterator!f(slice._iterator)); 2760 import mir.ndslice.traits: isIterator; 2761 alias testIter = typeof(MIterator.init[0]); 2762 static assert(isIterator!MIterator, "mir.ndslice.map: probably the lambda function contains a compile time bug."); 2763 return Slice!(MIterator, N, kind)(slice._structure, _mapIterator!f(slice._iterator.move)); 2764 } 2765 2766 /// ditto 2767 auto map(T)(T[] array) 2768 { 2769 return map(array.sliced); 2770 } 2771 2772 /// ditto 2773 auto map(T)(T withAsSlice) 2774 if (hasAsSlice!T) 2775 { 2776 return map(withAsSlice.asSlice); 2777 } 2778 2779 /// ditto 2780 auto map(Range)(Range r) 2781 if (!hasAsSlice!Range && !isSlice!Range && !is(Range : T[], T)) 2782 { 2783 import core.lifetime: forward; 2784 import mir.primitives: isInputRange; 2785 static assert (isInputRange!Range, "map can work with ndslice, array, or an input range."); 2786 return MapRange!(f, ImplicitlyUnqual!Range)(forward!r); 2787 } 2788 } 2789 else alias map = .map!(staticMap!(naryFun, fun)); 2790 } 2791 else alias map = .map!(adjoin!fun); 2792 } 2793 2794 /// ditto 2795 struct MapRange(alias fun, Range) 2796 { 2797 import std.range.primitives; 2798 2799 Range _input; 2800 2801 static if (isInfinite!Range) 2802 { 2803 enum bool empty = false; 2804 } 2805 else 2806 { 2807 bool empty() @property 2808 { 2809 return _input.empty; 2810 } 2811 } 2812 2813 void popFront() 2814 { 2815 assert(!empty, "Attempting to popFront an empty map."); 2816 _input.popFront(); 2817 } 2818 2819 auto ref front() @property 2820 { 2821 assert(!empty, "Attempting to fetch the front of an empty map."); 2822 return fun(_input.front); 2823 } 2824 2825 static if (isBidirectionalRange!Range) 2826 auto ref back()() @property 2827 { 2828 assert(!empty, "Attempting to fetch the back of an empty map."); 2829 return fun(_input.back); 2830 } 2831 2832 static if (isBidirectionalRange!Range) 2833 void popBack()() 2834 { 2835 assert(!empty, "Attempting to popBack an empty map."); 2836 _input.popBack(); 2837 } 2838 2839 static if (hasLength!Range) 2840 auto length() @property 2841 { 2842 return _input.length; 2843 } 2844 2845 static if (isForwardRange!Range) 2846 auto save()() @property 2847 { 2848 return typeof(this)(_input.save); 2849 } 2850 } 2851 2852 /// 2853 @safe pure nothrow 2854 version(mir_ndslice_test) unittest 2855 { 2856 import mir.ndslice.topology : iota; 2857 auto s = iota(2, 3).map!(a => a * 3); 2858 assert(s == [[ 0, 3, 6], 2859 [ 9, 12, 15]]); 2860 } 2861 2862 /// String lambdas 2863 @safe pure nothrow 2864 version(mir_ndslice_test) unittest 2865 { 2866 import mir.ndslice.topology : iota; 2867 assert(iota(2, 3).map!"a * 2" == [[0, 2, 4], [6, 8, 10]]); 2868 } 2869 2870 /// Input ranges 2871 @safe pure nothrow 2872 version(mir_ndslice_test) unittest 2873 { 2874 import mir.algorithm.iteration: filter, equal; 2875 assert (6.iota.filter!"a % 2".map!"a * 10".equal([10, 30, 50])); 2876 } 2877 2878 /// Packed tensors 2879 @safe pure nothrow 2880 version(mir_ndslice_test) unittest 2881 { 2882 import mir.ndslice.topology : iota, windows; 2883 import mir.math.sum: sum; 2884 2885 // iota windows map sums ( reduce!"a + b" ) 2886 // -------------- 2887 // ------- | --- --- | ------ 2888 // | 0 1 2 | => || 0 1 || 1 2 || => | 8 12 | 2889 // | 3 4 5 | || 3 4 || 4 5 || ------ 2890 // ------- | --- --- | 2891 // -------------- 2892 auto s = iota(2, 3) 2893 .windows(2, 2) 2894 .map!sum; 2895 2896 assert(s == [[8, 12]]); 2897 } 2898 2899 /// Zipped tensors 2900 @safe pure nothrow 2901 version(mir_ndslice_test) unittest 2902 { 2903 import mir.ndslice.topology : iota, zip; 2904 2905 // 0 1 2 2906 // 3 4 5 2907 auto sl1 = iota(2, 3); 2908 // 1 2 3 2909 // 4 5 6 2910 auto sl2 = iota([2, 3], 1); 2911 2912 auto z = zip(sl1, sl2); 2913 2914 assert(zip(sl1, sl2).map!"a + b" == sl1 + sl2); 2915 assert(zip(sl1, sl2).map!((a, b) => a + b) == sl1 + sl2); 2916 } 2917 2918 /++ 2919 Multiple functions can be passed to `map`. 2920 In that case, the element type of `map` is a tuple containing 2921 one element for each function. 2922 +/ 2923 @safe pure nothrow 2924 version(mir_ndslice_test) unittest 2925 { 2926 import mir.ndslice.topology : iota; 2927 2928 auto sl = iota(2, 3); 2929 auto s = sl.map!("a + a", "a * a"); 2930 2931 auto sums = [[0, 2, 4], [6, 8, 10]]; 2932 auto products = [[0, 1, 4], [9, 16, 25]]; 2933 2934 assert(s.map!"a[0]" == sl + sl); 2935 assert(s.map!"a[1]" == sl * sl); 2936 } 2937 2938 /++ 2939 `map` can be aliased to a symbol and be used separately: 2940 +/ 2941 pure nothrow version(mir_ndslice_test) unittest 2942 { 2943 import mir.ndslice.topology : iota; 2944 2945 alias halfs = map!"double(a) / 2"; 2946 assert(halfs(iota(2, 3)) == [[0.0, 0.5, 1], [1.5, 2, 2.5]]); 2947 } 2948 2949 /++ 2950 Type normalization 2951 +/ 2952 version(mir_ndslice_test) unittest 2953 { 2954 import mir.functional : pipe; 2955 import mir.ndslice.topology : iota; 2956 auto a = iota(2, 3).map!"a + 10".map!(pipe!("a * 2", "a + 1")); 2957 auto b = iota(2, 3).map!(pipe!("a + 10", "a * 2", "a + 1")); 2958 assert(a == b); 2959 static assert(is(typeof(a) == typeof(b))); 2960 } 2961 2962 /// Use map with byDim/alongDim to apply functions to each dimension 2963 version(mir_ndslice_test) 2964 @safe pure 2965 unittest 2966 { 2967 import mir.ndslice.topology: byDim, alongDim; 2968 import mir.ndslice.fuse: fuse; 2969 import mir.math.stat: mean; 2970 import mir.algorithm.iteration: all; 2971 import mir.math.common: approxEqual; 2972 2973 auto x = [ 2974 [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], 2975 [2.0, 7.5, 5.0, 1.0, 1.5, 0.0] 2976 ].fuse; 2977 2978 // Use byDim/alongDim with map to compute mean of row/column. 2979 assert(x.byDim!0.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6])); 2980 assert(x.byDim!1.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125])); 2981 assert(x.alongDim!1.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6])); 2982 assert(x.alongDim!0.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125])); 2983 } 2984 2985 /++ 2986 Use map with a lambda and with byDim/alongDim, but may need to allocate result. 2987 This example uses fuse, which allocates. Note: fuse!1 will transpose the result. 2988 +/ 2989 version(mir_ndslice_test) 2990 @safe pure 2991 unittest { 2992 import mir.ndslice.topology: iota, byDim, alongDim, map; 2993 import mir.ndslice.fuse: fuse; 2994 import mir.ndslice.slice: sliced; 2995 2996 auto x = [1, 2, 3].sliced; 2997 auto y = [1, 2].sliced; 2998 2999 auto s1 = iota(2, 3).byDim!0.map!(a => a * x).fuse; 3000 assert(s1 == [[ 0, 2, 6], 3001 [ 3, 8, 15]]); 3002 auto s2 = iota(2, 3).byDim!1.map!(a => a * y).fuse!1; 3003 assert(s2 == [[ 0, 1, 2], 3004 [ 6, 8, 10]]); 3005 auto s3 = iota(2, 3).alongDim!1.map!(a => a * x).fuse; 3006 assert(s1 == [[ 0, 2, 6], 3007 [ 3, 8, 15]]); 3008 auto s4 = iota(2, 3).alongDim!0.map!(a => a * y).fuse!1; 3009 assert(s2 == [[ 0, 1, 2], 3010 [ 6, 8, 10]]); 3011 } 3012 3013 /// 3014 pure version(mir_ndslice_test) unittest 3015 { 3016 import mir.algorithm.iteration: reduce; 3017 import mir.math.common: fmax; 3018 import mir.math.stat: mean; 3019 import mir.math.sum; 3020 /// Returns maximal column average. 3021 auto maxAvg(S)(S matrix) { 3022 return reduce!fmax(0.0, matrix.alongDim!1.map!mean); 3023 } 3024 // 1 2 3025 // 3 4 3026 auto matrix = iota([2, 2], 1); 3027 assert(maxAvg(matrix) == 3.5); 3028 } 3029 3030 /++ 3031 Implements the homonym function (also known as `transform`) present 3032 in many languages of functional flavor. The call `slice.vmap(fun)` 3033 returns a slice of which elements are obtained by applying `fun` 3034 for all elements in `slice`. The original slices are 3035 not changed. Evaluation is done lazily. 3036 3037 Note: 3038 $(SUBREF dynamic, transposed) and 3039 $(SUBREF topology, pack) can be used to specify dimensions. 3040 Params: 3041 slice = ndslice 3042 callable = callable object, structure, delegate, or function pointer. 3043 See_Also: 3044 $(LREF cached), $(LREF map), $(LREF rcmap), $(LREF indexed), 3045 $(LREF pairwise), $(LREF subSlices), $(LREF slide), $(LREF zip), 3046 $(HTTP en.wikipedia.org/wiki/Map_(higher-order_function), Map (higher-order function)) 3047 +/ 3048 @fmamath auto vmap(Iterator, size_t N, SliceKind kind, Callable) 3049 ( 3050 Slice!(Iterator, N, kind) slice, 3051 Callable callable, 3052 ) 3053 { 3054 import core.lifetime: move; 3055 alias It = VmapIterator!(Iterator, Callable); 3056 return Slice!(It, N, kind)(slice._structure, It(slice._iterator.move, callable.move)); 3057 } 3058 3059 /// ditto 3060 auto vmap(T, Callable)(T[] array, Callable callable) 3061 { 3062 import core.lifetime: move; 3063 return vmap(array.sliced, callable.move); 3064 } 3065 3066 /// ditto 3067 auto vmap(T, Callable)(T withAsSlice, Callable callable) 3068 if (hasAsSlice!T) 3069 { 3070 import core.lifetime: move; 3071 return vmap(withAsSlice.asSlice, callable.move); 3072 } 3073 3074 /// 3075 @safe pure nothrow 3076 version(mir_ndslice_test) unittest 3077 { 3078 import mir.ndslice.topology : iota; 3079 3080 static struct Mul { 3081 double factor; this(double f) { factor = f; } 3082 auto opCall(long x) const {return x * factor; } 3083 auto lightConst()() const @property { return Mul(factor); } 3084 } 3085 3086 auto callable = Mul(3); 3087 auto s = iota(2, 3).vmap(callable); 3088 3089 assert(s == [[ 0, 3, 6], 3090 [ 9, 12, 15]]); 3091 } 3092 3093 /// Packed tensors. 3094 @safe pure nothrow 3095 version(mir_ndslice_test) unittest 3096 { 3097 import mir.math.sum: sum; 3098 import mir.ndslice.topology : iota, windows; 3099 3100 // iota windows vmap scaled sums 3101 // -------------- 3102 // ------- | --- --- | ----- 3103 // | 0 1 2 | => || 0 1 || 1 2 || => | 4 6 | 3104 // | 3 4 5 | || 3 4 || 4 5 || ----- 3105 // ------- | --- --- | 3106 // -------------- 3107 3108 struct Callable 3109 { 3110 double factor; 3111 this(double f) {factor = f;} 3112 auto opCall(S)(S x) { return x.sum * factor; } 3113 3114 auto lightConst()() const @property { return Callable(factor); } 3115 auto lightImmutable()() immutable @property { return Callable(factor); } 3116 } 3117 3118 auto callable = Callable(0.5); 3119 3120 auto s = iota(2, 3) 3121 .windows(2, 2) 3122 .vmap(callable); 3123 3124 assert(s == [[4, 6]]); 3125 } 3126 3127 /// Zipped tensors 3128 @safe pure nothrow 3129 version(mir_ndslice_test) unittest 3130 { 3131 import mir.ndslice.topology : iota, zip; 3132 3133 struct Callable 3134 { 3135 double factor; 3136 this(double f) {factor = f;} 3137 auto opCall(S, T)(S x, T y) { return x + y * factor; } 3138 3139 auto lightConst()() const { return Callable(factor); } 3140 auto lightImmutable()() immutable { return Callable(factor); } 3141 } 3142 3143 auto callable = Callable(10); 3144 3145 // 0 1 2 3146 // 3 4 5 3147 auto sl1 = iota(2, 3); 3148 // 1 2 3 3149 // 4 5 6 3150 auto sl2 = iota([2, 3], 1); 3151 3152 auto z = zip(sl1, sl2); 3153 3154 assert(zip(sl1, sl2).vmap(callable) == 3155 [[10, 21, 32], 3156 [43, 54, 65]]); 3157 } 3158 3159 // TODO 3160 /+ 3161 Multiple functions can be passed to `vmap`. 3162 In that case, the element type of `vmap` is a tuple containing 3163 one element for each function. 3164 +/ 3165 @safe pure nothrow 3166 version(none) version(mir_ndslice_test) unittest 3167 { 3168 import mir.ndslice.topology : iota; 3169 3170 auto s = iota(2, 3).vmap!("a + a", "a * a"); 3171 3172 auto sums = [[0, 2, 4], [6, 8, 10]]; 3173 auto products = [[0, 1, 4], [9, 16, 25]]; 3174 3175 foreach (i; 0..s.length!0) 3176 foreach (j; 0..s.length!1) 3177 { 3178 auto values = s[i, j]; 3179 assert(values.a == sums[i][j]); 3180 assert(values.b == products[i][j]); 3181 } 3182 } 3183 3184 /// Use vmap with byDim/alongDim to apply functions to each dimension 3185 version(mir_ndslice_test) 3186 @safe pure 3187 unittest 3188 { 3189 import mir.ndslice.fuse: fuse; 3190 import mir.math.stat: mean; 3191 import mir.algorithm.iteration: all; 3192 import mir.math.common: approxEqual; 3193 3194 auto x = [ 3195 [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], 3196 [2.0, 7.5, 5.0, 1.0, 1.5, 0.0] 3197 ].fuse; 3198 3199 static struct Callable 3200 { 3201 double factor; 3202 this(double f) {factor = f;} 3203 auto opCall(U)(U x) const {return x.mean + factor; } 3204 auto lightConst()() const @property { return Callable(factor); } 3205 } 3206 3207 auto callable = Callable(0.0); 3208 3209 // Use byDim/alongDim with map to compute callable of row/column. 3210 assert(x.byDim!0.vmap(callable).all!approxEqual([12.25 / 6, 17.0 / 6])); 3211 assert(x.byDim!1.vmap(callable).all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125])); 3212 assert(x.alongDim!1.vmap(callable).all!approxEqual([12.25 / 6, 17.0 / 6])); 3213 assert(x.alongDim!0.vmap(callable).all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125])); 3214 } 3215 3216 /++ 3217 Use vmap with a lambda and with byDim/alongDim, but may need to allocate result. 3218 This example uses fuse, which allocates. Note: fuse!1 will transpose the result. 3219 +/ 3220 version(mir_ndslice_test) 3221 @safe pure 3222 unittest { 3223 import mir.ndslice.topology: iota, alongDim, map; 3224 import mir.ndslice.fuse: fuse; 3225 import mir.ndslice.slice: sliced; 3226 3227 static struct Mul(T) 3228 { 3229 T factor; 3230 this(T f) { factor = f; } 3231 auto opCall(U)(U x) {return x * factor; } 3232 auto lightConst()() const @property { return Mul!(typeof(factor.lightConst))(factor.lightConst); } 3233 } 3234 3235 auto a = [1, 2, 3].sliced; 3236 auto b = [1, 2].sliced; 3237 auto A = Mul!(typeof(a))(a); 3238 auto B = Mul!(typeof(b))(b); 3239 3240 auto x = [ 3241 [0, 1, 2], 3242 [3, 4, 5] 3243 ].fuse; 3244 3245 auto s1 = x.byDim!0.vmap(A).fuse; 3246 assert(s1 == [[ 0, 2, 6], 3247 [ 3, 8, 15]]); 3248 auto s2 = x.byDim!1.vmap(B).fuse!1; 3249 assert(s2 == [[ 0, 1, 2], 3250 [ 6, 8, 10]]); 3251 auto s3 = x.alongDim!1.vmap(A).fuse; 3252 assert(s1 == [[ 0, 2, 6], 3253 [ 3, 8, 15]]); 3254 auto s4 = x.alongDim!0.vmap(B).fuse!1; 3255 assert(s2 == [[ 0, 1, 2], 3256 [ 6, 8, 10]]); 3257 } 3258 3259 private auto hideStride 3260 (Iterator, SliceKind kind) 3261 (Slice!(Iterator, 1, kind) slice) 3262 { 3263 import core.lifetime: move; 3264 static if (kind == Universal) 3265 return Slice!(StrideIterator!Iterator)( 3266 slice._lengths, 3267 StrideIterator!Iterator(slice._strides[0], move(slice._iterator))); 3268 else 3269 return slice; 3270 } 3271 3272 private auto unhideStride 3273 (Iterator, size_t N, SliceKind kind) 3274 (Slice!(Iterator, N, kind) slice) 3275 { 3276 static if (is(Iterator : StrideIterator!It, It)) 3277 { 3278 import core.lifetime: move; 3279 static if (kind == Universal) 3280 { 3281 alias Ret = SliceKind!(It, N, Universal); 3282 auto strides = slice._strides; 3283 foreach(i; Iota!(Ret.S)) 3284 strides[i] = slice._strides[i] * slice._iterator._stride; 3285 return Slice!(It, N, Universal)(slice._lengths, strides, slice._iterator._iterator.move); 3286 } 3287 else 3288 return slice.move.universal.unhideStride; 3289 } 3290 else 3291 return slice; 3292 } 3293 3294 /++ 3295 Implements the homonym function (also known as `transform`) present 3296 in many languages of functional flavor. The call `rmap!(fun)(slice)` 3297 returns an RC array (1D) or RC slice (ND) of which elements are obtained by applying `fun` 3298 for all elements in `slice`. The original slices are 3299 not changed. Evaluation is done eagerly. 3300 3301 Note: 3302 $(SUBREF dynamic, transposed) and 3303 $(SUBREF topology, pack) can be used to specify dimensions. 3304 Params: 3305 fun = One or more functions. 3306 See_Also: 3307 $(LREF cached), $(LREF map), $(LREF vmap), $(LREF indexed), 3308 $(LREF pairwise), $(LREF subSlices), $(LREF slide), $(LREF zip), 3309 $(HTTP en.wikipedia.org/wiki/Map_(higher-order_function), Map (higher-order function)) 3310 +/ 3311 template rcmap(fun...) 3312 if (fun.length) 3313 { 3314 import mir.functional: adjoin, naryFun, pipe; 3315 static if (fun.length == 1) 3316 { 3317 static if (__traits(isSame, naryFun!(fun[0]), fun[0])) 3318 { 3319 alias f = fun[0]; 3320 @fmamath: 3321 /++ 3322 Params: 3323 slice = An ndslice, array, or an input range. 3324 Returns: 3325 ndslice or an input range with each fun applied to all the elements. If there is more than one 3326 fun, the element type will be `Tuple` containing one element for each fun. 3327 +/ 3328 auto rcmap(Iterator, size_t N, SliceKind kind) 3329 (Slice!(Iterator, N, kind) slice) 3330 { 3331 import core.lifetime: move; 3332 auto shape = slice.shape; 3333 auto r = slice.move.flattened; 3334 if (false) 3335 { 3336 auto e = f(r.front); 3337 r.popFront; 3338 auto d = r.empty; 3339 } 3340 return () @trusted 3341 { 3342 import mir.rc.array: RCArray; 3343 import std.traits: Unqual; 3344 import mir.conv: emplaceRef; 3345 3346 alias T = typeof(f(r.front)); 3347 auto ret = RCArray!T(r.length); 3348 auto next = ret.ptr; 3349 while (!r.empty) 3350 { 3351 emplaceRef(*cast(Unqual!T*)next++, f(r.front)); 3352 r.popFront; 3353 } 3354 static if (N == 1) 3355 { 3356 return ret; 3357 } 3358 else 3359 { 3360 return ret.moveToSlice.sliced(shape); 3361 } 3362 } (); 3363 } 3364 3365 /// ditto 3366 auto rcmap(T)(T[] array) 3367 { 3368 return rcmap(array.sliced); 3369 } 3370 3371 /// ditto 3372 auto rcmap(T)(T withAsSlice) 3373 if (hasAsSlice!T) 3374 { 3375 static if (__traits(hasMember, T, "moveToSlice")) 3376 return rcmap(withAsSlice.moveToSlice); 3377 else 3378 return rcmap(withAsSlice.asSlice); 3379 } 3380 3381 /// ditto 3382 auto rcmap(Range)(Range r) 3383 if (!hasAsSlice!Range && !isSlice!Range && !is(Range : T[], T)) 3384 { 3385 import core.lifetime: forward; 3386 import mir.appender: scopedBuffer; 3387 import mir.primitives: isInputRange; 3388 import mir.rc.array: RCArray; 3389 alias T = typeof(f(r.front)); 3390 auto buffer = scopedBuffer!T; 3391 while (!r.empty) 3392 { 3393 buffer.put(f(r.front)); 3394 r.popFront; 3395 } 3396 return () @trusted 3397 { 3398 auto ret = RCArray!T(buffer.length, false); 3399 buffer.moveDataAndEmplaceTo(ret[]); 3400 return ret; 3401 } (); 3402 } 3403 } 3404 else alias rcmap = .rcmap!(staticMap!(naryFun, fun)); 3405 } 3406 else alias rcmap = .rcmap!(adjoin!fun); 3407 } 3408 3409 /// Returns RCArray for input ranges and one-dimensional slices. 3410 @safe pure nothrow @nogc 3411 version(mir_ndslice_test) unittest 3412 { 3413 import mir.algorithm.iteration: filter, equal; 3414 auto factor = 10; 3415 auto step = 20; 3416 assert (3.iota.rcmap!(a => a * factor).moveToSlice.equal(3.iota * factor)); 3417 assert (6.iota.filter!"a % 2".rcmap!(a => a * factor).moveToSlice.equal([3].iota(factor, step))); 3418 } 3419 3420 /// For multidimensional case returns `Slice!(RCI!T, N)`. 3421 @safe pure nothrow @nogc 3422 version(mir_ndslice_test) unittest 3423 { 3424 import mir.ndslice.topology : iota; 3425 auto factor = 3; 3426 auto s = iota(2, 3).rcmap!(a => a * factor); 3427 assert(s == iota(2, 3) * factor); 3428 } 3429 3430 /// String lambdas 3431 @safe pure nothrow 3432 version(mir_ndslice_test) unittest 3433 { 3434 import mir.ndslice.topology : iota; 3435 assert(iota(2, 3).rcmap!"a * 2" == iota(2, 3) * 2); 3436 } 3437 3438 @safe pure nothrow @nogc 3439 version(mir_ndslice_test) unittest 3440 { 3441 import mir.algorithm.iteration: filter, equal; 3442 auto factor = 10; 3443 auto step = 20; 3444 assert (3.iota.as!(const int).rcmap!(a => a * factor).moveToSlice.equal(3.iota * factor)); 3445 assert (6.iota.filter!"a % 2".as!(immutable int).rcmap!(a => a * factor).moveToSlice.equal([3].iota(factor, step))); 3446 } 3447 3448 /++ 3449 Creates a random access cache for lazyly computed elements. 3450 Params: 3451 original = original ndslice 3452 caches = cached values 3453 flags = array composed of flags that indicates if values are already computed 3454 Returns: 3455 ndslice, which is internally composed of three ndslices: `original`, allocated cache and allocated bit-ndslice. 3456 See_also: $(LREF cachedGC), $(LREF map), $(LREF vmap), $(LREF indexed) 3457 +/ 3458 Slice!(CachedIterator!(Iterator, CacheIterator, FlagIterator), N, kind) 3459 cached(Iterator, SliceKind kind, size_t N, CacheIterator, FlagIterator)( 3460 Slice!(Iterator, N, kind) original, 3461 Slice!(CacheIterator, N, kind) caches, 3462 Slice!(FlagIterator, N, kind) flags, 3463 ) 3464 { 3465 assert(original.shape == caches.shape, "caches.shape should be equal to original.shape"); 3466 assert(original.shape == flags.shape, "flags.shape should be equal to original.shape"); 3467 return typeof(return)( 3468 original._structure, 3469 IteratorOf!(typeof(return))( 3470 original._iterator, 3471 caches._iterator, 3472 flags._iterator, 3473 )); 3474 } 3475 3476 /// 3477 @safe pure nothrow 3478 version(mir_ndslice_test) unittest 3479 { 3480 import mir.ndslice.topology: cached, iota, map; 3481 import mir.ndslice.allocation: bitSlice, uninitSlice; 3482 3483 int[] funCalls; 3484 3485 auto v = 5.iota!int 3486 .map!((i) { 3487 funCalls ~= i; 3488 return 2 ^^ i; 3489 }); 3490 auto flags = v.length.bitSlice; 3491 auto cache = v.length.uninitSlice!int; 3492 // cached lazy slice: 1 2 4 8 16 3493 auto sl = v.cached(cache, flags); 3494 3495 assert(funCalls == []); 3496 assert(sl[1] == 2); // remember result 3497 assert(funCalls == [1]); 3498 assert(sl[1] == 2); // reuse result 3499 assert(funCalls == [1]); 3500 3501 assert(sl[0] == 1); 3502 assert(funCalls == [1, 0]); 3503 funCalls = []; 3504 3505 // set values directly 3506 sl[1 .. 3] = 5; 3507 assert(sl[1] == 5); 3508 assert(sl[2] == 5); 3509 // no function calls 3510 assert(funCalls == []); 3511 } 3512 3513 /// Cache of immutable elements 3514 @safe pure nothrow 3515 version(mir_ndslice_test) unittest 3516 { 3517 import mir.ndslice.slice: DeepElementType; 3518 import mir.ndslice.topology: cached, iota, map, as; 3519 import mir.ndslice.allocation: bitSlice, uninitSlice; 3520 3521 int[] funCalls; 3522 3523 auto v = 5.iota!int 3524 .map!((i) { 3525 funCalls ~= i; 3526 return 2 ^^ i; 3527 }) 3528 .as!(immutable int); 3529 auto flags = v.length.bitSlice; 3530 auto cache = v.length.uninitSlice!(immutable int); 3531 3532 // cached lazy slice: 1 2 4 8 16 3533 auto sl = v.cached(cache, flags); 3534 3535 static assert(is(DeepElementType!(typeof(sl)) == immutable int)); 3536 3537 assert(funCalls == []); 3538 assert(sl[1] == 2); // remember result 3539 assert(funCalls == [1]); 3540 assert(sl[1] == 2); // reuse result 3541 assert(funCalls == [1]); 3542 3543 assert(sl[0] == 1); 3544 assert(funCalls == [1, 0]); 3545 } 3546 3547 /++ 3548 Creates a random access cache for lazyly computed elements. 3549 Params: 3550 original = ND Contiguous or 1D Universal ndslice. 3551 Returns: 3552 ndslice, which is internally composed of three ndslices: `original`, allocated cache and allocated bit-ndslice. 3553 See_also: $(LREF cached), $(LREF map), $(LREF vmap), $(LREF indexed) 3554 +/ 3555 Slice!(CachedIterator!(Iterator, typeof(Iterator.init[0])*, FieldIterator!(BitField!(size_t*))), N) 3556 cachedGC(Iterator, size_t N)(Slice!(Iterator, N) original) @trusted 3557 { 3558 import std.traits: hasElaborateAssign, Unqual; 3559 import mir.ndslice.allocation: bitSlice, slice, uninitSlice; 3560 alias C = typeof(Iterator.init[0]); 3561 alias UC = Unqual!C; 3562 static if (hasElaborateAssign!UC) 3563 alias newSlice = slice; 3564 else 3565 alias newSlice = uninitSlice; 3566 return typeof(return)( 3567 original._structure, 3568 IteratorOf!(typeof(return))( 3569 original._iterator, 3570 newSlice!C(original._lengths)._iterator, 3571 original._lengths.bitSlice._iterator, 3572 )); 3573 } 3574 3575 /// ditto 3576 auto cachedGC(Iterator)(Slice!(Iterator, 1, Universal) from) 3577 { 3578 return from.flattened.cachedGC; 3579 } 3580 3581 /// ditto 3582 auto cachedGC(T)(T withAsSlice) 3583 if (hasAsSlice!T) 3584 { 3585 return cachedGC(withAsSlice.asSlice); 3586 } 3587 3588 /// 3589 @safe pure nothrow 3590 version(mir_ndslice_test) unittest 3591 { 3592 import mir.ndslice.topology: cachedGC, iota, map; 3593 3594 int[] funCalls; 3595 3596 // cached lazy slice: 1 2 4 8 16 3597 auto sl = 5.iota!int 3598 .map!((i) { 3599 funCalls ~= i; 3600 return 2 ^^ i; 3601 }) 3602 .cachedGC; 3603 3604 assert(funCalls == []); 3605 assert(sl[1] == 2); // remember result 3606 assert(funCalls == [1]); 3607 assert(sl[1] == 2); // reuse result 3608 assert(funCalls == [1]); 3609 3610 assert(sl[0] == 1); 3611 assert(funCalls == [1, 0]); 3612 funCalls = []; 3613 3614 // set values directly 3615 sl[1 .. 3] = 5; 3616 assert(sl[1] == 5); 3617 assert(sl[2] == 5); 3618 // no function calls 3619 assert(funCalls == []); 3620 } 3621 3622 /// Cache of immutable elements 3623 @safe pure nothrow 3624 version(mir_ndslice_test) unittest 3625 { 3626 import mir.ndslice.slice: DeepElementType; 3627 import mir.ndslice.topology: cachedGC, iota, map, as; 3628 3629 int[] funCalls; 3630 3631 // cached lazy slice: 1 2 4 8 16 3632 auto sl = 5.iota!int 3633 .map!((i) { 3634 funCalls ~= i; 3635 return 2 ^^ i; 3636 }) 3637 .as!(immutable int) 3638 .cachedGC; 3639 3640 static assert(is(DeepElementType!(typeof(sl)) == immutable int)); 3641 3642 assert(funCalls == []); 3643 assert(sl[1] == 2); // remember result 3644 assert(funCalls == [1]); 3645 assert(sl[1] == 2); // reuse result 3646 assert(funCalls == [1]); 3647 3648 assert(sl[0] == 1); 3649 assert(funCalls == [1, 0]); 3650 } 3651 3652 /++ 3653 Convenience function that creates a lazy view, 3654 where each element of the original slice is converted to the type `T`. 3655 It uses $(LREF map) and $(REF_ALTTEXT $(TT to), to, mir,conv)$(NBSP) 3656 composition under the hood. 3657 Params: 3658 slice = a slice to create a view on. 3659 Returns: 3660 A lazy slice with elements converted to the type `T`. 3661 See_also: $(LREF map), $(LREF vmap) 3662 +/ 3663 template as(T) 3664 { 3665 /// 3666 @fmamath auto as(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) 3667 { 3668 static if (is(slice.DeepElement == T)) 3669 return slice; 3670 else 3671 static if (is(Iterator : T*)) 3672 return slice.toConst; 3673 else 3674 { 3675 import core.lifetime: move; 3676 import mir.conv: to; 3677 return map!(to!T)(slice.move); 3678 } 3679 } 3680 3681 /// ditto 3682 auto as(S)(S[] array) 3683 { 3684 return as(array.sliced); 3685 } 3686 3687 /// ditto 3688 auto as(S)(S withAsSlice) 3689 if (hasAsSlice!S) 3690 { 3691 return as(withAsSlice.asSlice); 3692 } 3693 3694 /// ditto 3695 auto as(Range)(Range r) 3696 if (!hasAsSlice!Range && !isSlice!Range && !is(Range : T[], T)) 3697 { 3698 static if (is(ForeachType!Range == T)) 3699 return r; 3700 else 3701 { 3702 import core.lifetime: move; 3703 import mir.conv: to; 3704 return map!(to!T)(r.move); 3705 } 3706 } 3707 } 3708 3709 /// 3710 @safe pure nothrow version(mir_ndslice_test) unittest 3711 { 3712 import mir.ndslice.slice: Slice; 3713 import mir.ndslice.allocation : slice; 3714 import mir.ndslice.topology : diagonal, as; 3715 3716 auto matrix = slice!double([2, 2], 0); 3717 auto stringMatrixView = matrix.as!int; 3718 assert(stringMatrixView == 3719 [[0, 0], 3720 [0, 0]]); 3721 3722 matrix.diagonal[] = 1; 3723 assert(stringMatrixView == 3724 [[1, 0], 3725 [0, 1]]); 3726 3727 /// allocate new slice composed of strings 3728 Slice!(int*, 2) stringMatrix = stringMatrixView.slice; 3729 } 3730 3731 /// Special behavior for pointers to a constant data. 3732 @safe pure nothrow version(mir_ndslice_test) unittest 3733 { 3734 import mir.ndslice.allocation : slice; 3735 import mir.ndslice.slice: Contiguous, Slice; 3736 3737 Slice!(double*, 2) matrix = slice!double([2, 2], 0); 3738 Slice!(const(double)*, 2) const_matrix = matrix.as!(const double); 3739 } 3740 3741 /// Ranges 3742 @safe pure nothrow version(mir_ndslice_test) unittest 3743 { 3744 import mir.algorithm.iteration: filter, equal; 3745 assert(5.iota.filter!"a % 2".as!double.map!"a / 2".equal([0.5, 1.5])); 3746 } 3747 3748 /++ 3749 Takes a field `source` and a slice `indices`, and creates a view of source as if its elements were reordered according to indices. 3750 `indices` may include only a subset of the elements of `source` and may also repeat elements. 3751 3752 Params: 3753 source = a filed, source of data. `source` must be an array or a pointer, or have `opIndex` primitive. Full random access range API is not required. 3754 indices = a slice, source of indices. 3755 Returns: 3756 n-dimensional slice with the same kind, shape and strides. 3757 3758 See_also: `indexed` is similar to $(LREF vmap), but a field (`[]`) is used instead of a function (`()`), and order of arguments is reversed. 3759 +/ 3760 Slice!(IndexIterator!(Iterator, Field), N, kind) 3761 indexed(Field, Iterator, size_t N, SliceKind kind) 3762 (Field source, Slice!(Iterator, N, kind) indices) 3763 { 3764 import core.lifetime: move; 3765 return typeof(return)( 3766 indices._structure, 3767 IndexIterator!(Iterator, Field)( 3768 indices._iterator.move, 3769 source)); 3770 } 3771 3772 /// ditto 3773 auto indexed(Field, S)(Field source, S[] indices) 3774 { 3775 return indexed(source, indices.sliced); 3776 } 3777 3778 /// ditto 3779 auto indexed(Field, S)(Field source, S indices) 3780 if (hasAsSlice!S) 3781 { 3782 return indexed(source, indices.asSlice); 3783 } 3784 3785 /// 3786 @safe pure nothrow version(mir_ndslice_test) unittest 3787 { 3788 auto source = [1, 2, 3, 4, 5]; 3789 auto indices = [4, 3, 1, 2, 0, 4]; 3790 auto ind = source.indexed(indices); 3791 assert(ind == [5, 4, 2, 3, 1, 5]); 3792 3793 assert(ind.retro == source.indexed(indices.retro)); 3794 3795 ind[3] += 10; // for index 2 3796 // 0 1 2 3 4 3797 assert(source == [1, 2, 13, 4, 5]); 3798 } 3799 3800 /++ 3801 Maps index pairs to subslices. 3802 Params: 3803 sliceable = pointer, array, ndslice, series, or something sliceable with `[a .. b]`. 3804 slices = ndslice composed of index pairs. 3805 Returns: 3806 ndslice composed of subslices. 3807 See_also: $(LREF chopped), $(LREF pairwise). 3808 +/ 3809 Slice!(SubSliceIterator!(Iterator, Sliceable), N, kind) 3810 subSlices(Iterator, size_t N, SliceKind kind, Sliceable)( 3811 Sliceable sliceable, 3812 Slice!(Iterator, N, kind) slices, 3813 ) 3814 { 3815 import core.lifetime: move; 3816 return typeof(return)( 3817 slices._structure, 3818 SubSliceIterator!(Iterator, Sliceable)(slices._iterator.move, sliceable.move) 3819 ); 3820 } 3821 3822 /// ditto 3823 auto subSlices(S, Sliceable)(Sliceable sliceable, S[] slices) 3824 { 3825 return subSlices(sliceable, slices.sliced); 3826 } 3827 3828 /// ditto 3829 auto subSlices(S, Sliceable)(Sliceable sliceable, S slices) 3830 if (hasAsSlice!S) 3831 { 3832 return subSlices(sliceable, slices.asSlice); 3833 } 3834 3835 /// 3836 @safe pure version(mir_ndslice_test) unittest 3837 { 3838 import mir.functional: staticArray; 3839 auto subs =[ 3840 staticArray(2, 4), 3841 staticArray(2, 10), 3842 ]; 3843 auto sliceable = 10.iota; 3844 3845 auto r = sliceable.subSlices(subs); 3846 assert(r == [ 3847 iota([4 - 2], 2), 3848 iota([10 - 2], 2), 3849 ]); 3850 } 3851 3852 /++ 3853 Maps index pairs to subslices. 3854 Params: 3855 bounds = ndslice composed of consequent (`a_i <= a_(i+1)`) pairwise index bounds. 3856 sliceable = pointer, array, ndslice, series, or something sliceable with `[a_i .. a_(i+1)]`. 3857 Returns: 3858 ndslice composed of subslices. 3859 See_also: $(LREF pairwise), $(LREF subSlices). 3860 +/ 3861 Slice!(ChopIterator!(Iterator, Sliceable)) chopped(Iterator, Sliceable)( 3862 Sliceable sliceable, 3863 Slice!Iterator bounds, 3864 ) 3865 in 3866 { 3867 debug(mir) 3868 foreach(b; bounds.pairwise!"a <= b") 3869 assert(b); 3870 } 3871 do { 3872 import core.lifetime: move; 3873 sizediff_t length = bounds._lengths[0] <= 1 ? 0 : bounds._lengths[0] - 1; 3874 static if (hasLength!Sliceable) 3875 { 3876 if (length && bounds[length - 1] > sliceable.length) 3877 { 3878 version (D_Exceptions) 3879 { import mir.exception : toMutable; throw choppedException.toMutable; } 3880 else 3881 assert(0, choppedExceptionMsg); 3882 } 3883 } 3884 3885 return typeof(return)([size_t(length)], ChopIterator!(Iterator, Sliceable)(bounds._iterator.move, sliceable.move)); 3886 } 3887 3888 /// ditto 3889 auto chopped(S, Sliceable)(Sliceable sliceable, S[] bounds) 3890 { 3891 return chopped(sliceable, bounds.sliced); 3892 } 3893 3894 /// ditto 3895 auto chopped(S, Sliceable)(Sliceable sliceable, S bounds) 3896 if (hasAsSlice!S) 3897 { 3898 return chopped(sliceable, bounds.asSlice); 3899 } 3900 3901 /// 3902 @safe pure version(mir_ndslice_test) unittest 3903 { 3904 import mir.functional: staticArray; 3905 import mir.ndslice.slice: sliced; 3906 auto pairwiseIndexes = [2, 4, 10].sliced; 3907 auto sliceable = 10.iota; 3908 3909 auto r = sliceable.chopped(pairwiseIndexes); 3910 assert(r == [ 3911 iota([4 - 2], 2), 3912 iota([10 - 4], 4), 3913 ]); 3914 } 3915 3916 /++ 3917 Groups slices into a slice of refTuples. The slices must have identical strides or be 1-dimensional. 3918 Params: 3919 sameStrides = if `true` assumes that all slices has the same strides. 3920 slices = list of slices 3921 Returns: 3922 n-dimensional slice of elements tuple 3923 See_also: $(SUBREF slice, Slice.strides). 3924 +/ 3925 template zip(bool sameStrides = false) 3926 { 3927 /++ 3928 Groups slices into a slice of refTuples. The slices must have identical strides or be 1-dimensional. 3929 Params: 3930 slices = list of slices 3931 Returns: 3932 n-dimensional slice of elements tuple 3933 See_also: $(SUBREF slice, Slice.strides). 3934 +/ 3935 @fmamath 3936 auto zip(Slices...)(Slices slices) 3937 if (Slices.length > 1 && allSatisfy!(isConvertibleToSlice, Slices)) 3938 { 3939 static if (allSatisfy!(isSlice, Slices)) 3940 { 3941 enum N = Slices[0].N; 3942 foreach(i, S; Slices[1 .. $]) 3943 { 3944 static assert(S.N == N, "zip: all Slices must have the same dimension count"); 3945 assert(slices[i + 1]._lengths == slices[0]._lengths, "zip: all slices must have the same lengths"); 3946 static if (sameStrides) 3947 assert(slices[i + 1].strides == slices[0].strides, "zip: all slices must have the same strides when unpacked"); 3948 } 3949 static if (!sameStrides && minElem(staticMap!(kindOf, Slices)) != Contiguous) 3950 { 3951 static assert(N == 1, "zip: cannot zip canonical and universal multidimensional slices if `sameStrides` is false"); 3952 mixin(`return .zip(` ~ _iotaArgs!(Slices.length, "slices[", "].hideStride, ") ~`);`); 3953 } 3954 else 3955 { 3956 enum kind = maxElem(staticMap!(kindOf, Slices)); 3957 alias Iterator = ZipIterator!(staticMap!(_IteratorOf, Slices)); 3958 alias Ret = Slice!(Iterator, N, kind); 3959 auto structure = Ret._Structure.init; 3960 structure[0] = slices[0]._lengths; 3961 foreach (i; Iota!(Ret.S)) 3962 structure[1][i] = slices[0]._strides[i]; 3963 return Ret(structure, mixin("Iterator(" ~ _iotaArgs!(Slices.length, "slices[", "]._iterator, ") ~ ")")); 3964 } 3965 } 3966 else 3967 { 3968 return .zip(toSlices!slices); 3969 } 3970 } 3971 } 3972 3973 /// 3974 @safe pure nothrow version(mir_ndslice_test) unittest 3975 { 3976 import mir.ndslice.allocation : slice; 3977 import mir.ndslice.topology : flattened, iota; 3978 3979 auto alpha = iota!int(4, 3); 3980 auto beta = slice!int(4, 3).universal; 3981 3982 auto m = zip!true(alpha, beta); 3983 foreach (r; m) 3984 foreach (e; r) 3985 e.b = e.a; 3986 assert(alpha == beta); 3987 3988 beta[] = 0; 3989 foreach (e; m.flattened) 3990 e.b = cast(int)e.a; 3991 assert(alpha == beta); 3992 } 3993 3994 @safe pure nothrow version(mir_ndslice_test) unittest 3995 { 3996 import mir.ndslice.allocation : slice; 3997 import mir.ndslice.topology : flattened, iota; 3998 3999 auto alpha = iota!int(4).universal; 4000 auto beta = new int[4]; 4001 4002 auto m = zip(alpha, beta); 4003 foreach (e; m) 4004 e.b = e.a; 4005 assert(alpha == beta); 4006 } 4007 4008 /++ 4009 Selects a slice from a zipped slice. 4010 Params: 4011 name = name of a slice to unzip. 4012 slice = zipped slice 4013 Returns: 4014 unzipped slice 4015 +/ 4016 auto unzip 4017 (char name, size_t N, SliceKind kind, Iterators...) 4018 (Slice!(ZipIterator!Iterators, N, kind) slice) 4019 { 4020 import core.lifetime: move; 4021 enum size_t i = name - 'a'; 4022 static assert(i < Iterators.length, `unzip: constraint: size_t(name - 'a') < Iterators.length`); 4023 return Slice!(Iterators[i], N, kind)(slice._structure, slice._iterator._iterators[i].move).unhideStride; 4024 } 4025 4026 /// ditto 4027 auto unzip 4028 (char name, size_t N, SliceKind kind, Iterators...) 4029 (ref Slice!(ZipIterator!Iterators, N, kind) slice) 4030 { 4031 enum size_t i = name - 'a'; 4032 static assert(i < Iterators.length, `unzip: constraint: size_t(name - 'a') < Iterators.length`); 4033 return Slice!(Iterators[i], N, kind)(slice._structure, slice._iterator._iterators[i]).unhideStride; 4034 } 4035 4036 /// 4037 pure nothrow version(mir_ndslice_test) unittest 4038 { 4039 import mir.ndslice.allocation : slice; 4040 import mir.ndslice.topology : iota; 4041 4042 auto alpha = iota!int(4, 3); 4043 auto beta = iota!int([4, 3], 1).slice; 4044 4045 auto m = zip(alpha, beta); 4046 4047 static assert(is(typeof(unzip!'a'(m)) == typeof(alpha))); 4048 static assert(is(typeof(unzip!'b'(m)) == typeof(beta))); 4049 4050 assert(m.unzip!'a' == alpha); 4051 assert(m.unzip!'b' == beta); 4052 } 4053 4054 private enum TotalDim(NdFields...) = [staticMap!(DimensionCount, NdFields)].sum; 4055 4056 private template applyInner(alias fun, size_t N) 4057 { 4058 static if (N == 0) 4059 alias applyInner = fun; 4060 else 4061 { 4062 import mir.functional: pipe; 4063 alias applyInner = pipe!(zip!true, map!(.applyInner!(fun, N - 1))); 4064 } 4065 } 4066 4067 /++ 4068 Lazy convolution for tensors. 4069 4070 Suitable for advanced convolution algorithms. 4071 4072 Params: 4073 params = convolution windows length. 4074 fun = one dimensional convolution function with `params` arity. 4075 SDimensions = dimensions to perform lazy convolution along. Negative dimensions are supported. 4076 See_also: $(LREF slide), $(LREF pairwise), $(LREF diff). 4077 +/ 4078 template slideAlong(size_t params, alias fun, SDimensions...) 4079 if (params <= 'z' - 'a' + 1 && SDimensions.length > 0) 4080 { 4081 import mir.functional: naryFun; 4082 4083 static if (allSatisfy!(isSizediff_t, SDimensions) && params > 1 && __traits(isSame, naryFun!fun, fun)) 4084 { 4085 @fmamath: 4086 /++ 4087 Params: slice = ndslice or array 4088 Returns: lazy convolution result 4089 +/ 4090 auto slideAlong(Iterator, size_t N, SliceKind kind) 4091 (Slice!(Iterator, N, kind) slice) 4092 { 4093 import core.lifetime: move; 4094 static if (N > 1 && kind == Contiguous) 4095 { 4096 return slideAlong(slice.move.canonical); 4097 } 4098 else 4099 static if (N == 1 && kind == Universal) 4100 { 4101 return slideAlong(slice.move.flattened); 4102 } 4103 else 4104 { 4105 alias Dimensions = staticMap!(ShiftNegativeWith!N, SDimensions); 4106 enum dimension = Dimensions[$ - 1]; 4107 size_t len = slice._lengths[dimension] - (params - 1); 4108 if (sizediff_t(len) <= 0) // overfow 4109 len = 0; 4110 slice._lengths[dimension] = len; 4111 static if (dimension + 1 == N || kind == Universal) 4112 { 4113 alias I = SlideIterator!(Iterator, params, fun); 4114 auto ret = Slice!(I, N, kind)(slice._structure, I(move(slice._iterator))); 4115 } 4116 else 4117 { 4118 alias Z = ZipIterator!(Repeat!(params, Iterator)); 4119 Z z; 4120 foreach_reverse (p; Iota!(1, params)) 4121 z._iterators[p] = slice._iterator + slice._strides[dimension] * p; 4122 z._iterators[0] = move(slice._iterator); 4123 alias M = MapIterator!(Z, fun); 4124 auto ret = Slice!(M, N, kind)(slice._structure, M(move(z))); 4125 } 4126 static if (Dimensions.length == 1) 4127 { 4128 return ret; 4129 } 4130 else 4131 { 4132 return .slideAlong!(params, fun, Dimensions[0 .. $ - 1])(ret); 4133 } 4134 } 4135 } 4136 4137 /// ditto 4138 auto slideAlong(S)(S[] slice) 4139 { 4140 return slideAlong(slice.sliced); 4141 } 4142 4143 /// ditto 4144 auto slideAlong(S)(S slice) 4145 if (hasAsSlice!S) 4146 { 4147 return slideAlong(slice.asSlice); 4148 } 4149 } 4150 else 4151 static if (params == 1) 4152 alias slideAlong = .map!(naryFun!fun); 4153 else alias slideAlong = .slideAlong!(params, naryFun!fun, staticMap!(toSizediff_t, SDimensions)); 4154 } 4155 4156 /// 4157 @safe pure nothrow @nogc version(mir_ndslice_test) unittest 4158 { 4159 auto data = [4, 5].iota; 4160 4161 alias scaled = a => a * 0.25; 4162 4163 auto v = data.slideAlong!(3, "a + 2 * b + c", 0).map!scaled; 4164 auto h = data.slideAlong!(3, "a + 2 * b + c", 1).map!scaled; 4165 4166 assert(v == [4, 5].iota[1 .. $ - 1, 0 .. $]); 4167 assert(h == [4, 5].iota[0 .. $, 1 .. $ - 1]); 4168 } 4169 4170 /++ 4171 Lazy convolution for tensors. 4172 4173 Suitable for simple convolution algorithms. 4174 4175 Params: 4176 params = windows length. 4177 fun = one dimensional convolution function with `params` arity. 4178 See_also: $(LREF slideAlong), $(LREF withNeighboursSum), $(LREF pairwise), $(LREF diff). 4179 +/ 4180 template slide(size_t params, alias fun) 4181 if (params <= 'z' - 'a' + 1) 4182 { 4183 import mir.functional: naryFun; 4184 4185 static if (params > 1 && __traits(isSame, naryFun!fun, fun)) 4186 { 4187 @fmamath: 4188 /++ 4189 Params: slice = ndslice or array 4190 Returns: lazy convolution result 4191 +/ 4192 auto slide(Iterator, size_t N, SliceKind kind) 4193 (Slice!(Iterator, N, kind) slice) 4194 { 4195 import core.lifetime: move; 4196 return slice.move.slideAlong!(params, fun, Iota!N); 4197 } 4198 4199 /// ditto 4200 auto slide(S)(S[] slice) 4201 { 4202 return slide(slice.sliced); 4203 } 4204 4205 /// ditto 4206 auto slide(S)(S slice) 4207 if (hasAsSlice!S) 4208 { 4209 return slide(slice.asSlice); 4210 } 4211 } 4212 else 4213 static if (params == 1) 4214 alias slide = .map!(naryFun!fun); 4215 else alias slide = .slide!(params, naryFun!fun); 4216 } 4217 4218 /// 4219 version(mir_ndslice_test) unittest 4220 { 4221 auto data = 10.iota; 4222 auto sw = data.slide!(3, "a + 2 * b + c"); 4223 4224 import mir.utility: max; 4225 assert(sw.length == max(0, cast(ptrdiff_t)data.length - 3 + 1)); 4226 assert(sw == sw.length.iota.map!"(a + 1) * 4"); 4227 assert(sw == [4, 8, 12, 16, 20, 24, 28, 32]); 4228 } 4229 4230 /++ 4231 ND-use case 4232 +/ 4233 @safe pure nothrow @nogc version(mir_ndslice_test) unittest 4234 { 4235 auto data = [4, 5].iota; 4236 4237 enum factor = 1.0 / 4 ^^ data.shape.length; 4238 alias scaled = a => a * factor; 4239 4240 auto sw = data.slide!(3, "a + 2 * b + c").map!scaled; 4241 4242 assert(sw == [4, 5].iota[1 .. $ - 1, 1 .. $ - 1]); 4243 } 4244 4245 /++ 4246 Pairwise map for tensors. 4247 4248 The computation is performed on request, when the element is accessed. 4249 4250 Params: 4251 fun = function to accumulate 4252 lag = an integer indicating which lag to use 4253 Returns: lazy ndslice composed of `fun(a_n, a_n+1)` values. 4254 4255 See_also: $(LREF slide), $(LREF slideAlong), $(LREF subSlices). 4256 +/ 4257 alias pairwise(alias fun, size_t lag = 1) = slide!(lag + 1, fun); 4258 4259 /// 4260 @safe pure nothrow version(mir_ndslice_test) unittest 4261 { 4262 import mir.ndslice.slice: sliced; 4263 assert([2, 4, 3, -1].sliced.pairwise!"a + b" == [6, 7, 2]); 4264 } 4265 4266 /// N-dimensional 4267 @safe pure nothrow 4268 version(mir_ndslice_test) unittest 4269 { 4270 // performs pairwise along each dimension 4271 // 0 1 2 3 4272 // 4 5 6 7 4273 // 8 9 10 11 4274 assert([3, 4].iota.pairwise!"a + b" == [[10, 14, 18], [26, 30, 34]]); 4275 } 4276 4277 /++ 4278 Differences between tensor elements. 4279 4280 The computation is performed on request, when the element is accessed. 4281 4282 Params: 4283 lag = an integer indicating which lag to use 4284 Returns: lazy differences. 4285 4286 See_also: $(LREF slide), $(LREF slide). 4287 +/ 4288 alias diff(size_t lag = 1) = pairwise!(('a' + lag) ~ " - a", lag); 4289 4290 /// 4291 version(mir_ndslice_test) unittest 4292 { 4293 import mir.ndslice.slice: sliced; 4294 assert([2, 4, 3, -1].sliced.diff == [2, -1, -4]); 4295 } 4296 4297 /// N-dimensional 4298 @safe pure nothrow @nogc 4299 version(mir_ndslice_test) unittest 4300 { 4301 // 0 1 2 3 4302 // 4 5 6 7 => 4303 // 8 9 10 11 4304 4305 // 1 1 1 4306 // 1 1 1 => 4307 // 1 1 1 4308 4309 // 0 0 0 4310 // 0 0 0 4311 4312 assert([3, 4].iota.diff == repeat(0, [2, 3])); 4313 } 4314 4315 /// packed slices 4316 version(mir_ndslice_test) unittest 4317 { 4318 // 0 1 2 3 4319 // 4 5 6 7 4320 // 8 9 10 11 4321 auto s = iota(3, 4); 4322 assert(iota(3, 4).byDim!0.diff == [ 4323 [4, 4, 4, 4], 4324 [4, 4, 4, 4]]); 4325 assert(iota(3, 4).byDim!1.diff == [ 4326 [1, 1, 1], 4327 [1, 1, 1], 4328 [1, 1, 1]]); 4329 } 4330 4331 /++ 4332 Drops borders for all dimensions. 4333 4334 Params: 4335 slice = ndslice 4336 Returns: 4337 Tensors with striped borders 4338 See_also: 4339 $(LREF universal), 4340 $(LREF assumeCanonical), 4341 $(LREF assumeContiguous). 4342 +/ 4343 Slice!(Iterator, N, N > 1 && kind == Contiguous ? Canonical : kind, Labels) 4344 dropBorders 4345 (Iterator, size_t N, SliceKind kind, Labels...) 4346 (Slice!(Iterator, N, kind, Labels) slice) 4347 { 4348 static if (N > 1 && kind == Contiguous) 4349 { 4350 import core.lifetime: move; 4351 auto ret = slice.move.canonical; 4352 } 4353 else 4354 { 4355 alias ret = slice; 4356 } 4357 ret.popFrontAll; 4358 ret.popBackAll; 4359 return ret; 4360 } 4361 4362 /// 4363 version(mir_ndslice_test) unittest 4364 { 4365 assert([4, 5].iota.dropBorders == [[6, 7, 8], [11, 12, 13]]); 4366 } 4367 4368 /++ 4369 Lazy zip view of elements packed with sum of their neighbours. 4370 4371 Params: 4372 fun = neighbours accumulation function. 4373 See_also: $(LREF slide), $(LREF slideAlong). 4374 +/ 4375 template withNeighboursSum(alias fun = "a + b") 4376 { 4377 import mir.functional: naryFun; 4378 4379 static if (__traits(isSame, naryFun!fun, fun)) 4380 { 4381 @fmamath: 4382 /++ 4383 Params: 4384 slice = ndslice or array 4385 Returns: 4386 Lazy zip view of elements packed with sum of their neighbours. 4387 +/ 4388 auto withNeighboursSum(Iterator, size_t N, SliceKind kind) 4389 (Slice!(Iterator, N, kind) slice) 4390 { 4391 import core.lifetime: move; 4392 static if (N > 1 && kind == Contiguous) 4393 { 4394 return withNeighboursSum(slice.move.canonical); 4395 } 4396 else 4397 static if (N == 1 && kind == Universal) 4398 { 4399 return withNeighboursSum(slice.move.flattened); 4400 } 4401 else 4402 { 4403 enum around = kind != Universal; 4404 alias Z = NeighboursIterator!(Iterator, N - around, fun, around); 4405 4406 size_t shift; 4407 foreach (dimension; Iota!N) 4408 { 4409 slice._lengths[dimension] -= 2; 4410 if (sizediff_t(slice._lengths[dimension]) <= 0) // overfow 4411 slice._lengths[dimension] = 0; 4412 shift += slice._stride!dimension; 4413 } 4414 4415 Z z; 4416 z._iterator = move(slice._iterator); 4417 z._iterator += shift; 4418 foreach (dimension; Iota!(N - around)) 4419 { 4420 z._neighbours[dimension][0] = z._iterator - slice._strides[dimension]; 4421 z._neighbours[dimension][1] = z._iterator + slice._strides[dimension]; 4422 } 4423 return Slice!(Z, N, kind)(slice._structure, move(z)); 4424 } 4425 } 4426 4427 /// ditto 4428 auto withNeighboursSum(S)(S[] slice) 4429 { 4430 return withNeighboursSum(slice.sliced); 4431 } 4432 4433 /// ditto 4434 auto withNeighboursSum(S)(S slice) 4435 if (hasAsSlice!S) 4436 { 4437 return withNeighboursSum(slice.asSlice); 4438 } 4439 } 4440 else alias withNeighboursSum = .withNeighboursSum!(naryFun!fun); 4441 } 4442 4443 /// 4444 @safe pure nothrow @nogc version(mir_ndslice_test) unittest 4445 { 4446 import mir.ndslice.allocation: slice; 4447 import mir.algorithm.iteration: all; 4448 4449 auto wn = [4, 5].iota.withNeighboursSum; 4450 assert(wn.all!"a[0] == a[1] * 0.25"); 4451 assert(wn.map!"a" == wn.map!"b * 0.25"); 4452 } 4453 4454 @safe pure nothrow @nogc version(mir_ndslice_test) unittest 4455 { 4456 import mir.ndslice.allocation: slice; 4457 import mir.algorithm.iteration: all; 4458 4459 auto wn = [4, 5].iota.withNeighboursSum.universal; 4460 assert(wn.all!"a[0] == a[1] * 0.25"); 4461 assert(wn.map!"a" == wn.map!"b * 0.25"); 4462 } 4463 4464 /++ 4465 Cartesian product. 4466 4467 Constructs lazy cartesian product $(SUBREF slice, Slice) without memory allocation. 4468 4469 Params: 4470 fields = list of fields with lengths or ndFields with shapes 4471 Returns: $(SUBREF ndfield, Cartesian)`!NdFields(fields).`$(SUBREF slice, slicedNdField)`;` 4472 +/ 4473 auto cartesian(NdFields...)(NdFields fields) 4474 if (NdFields.length > 1 && allSatisfy!(templateOr!(hasShape, hasLength), NdFields)) 4475 { 4476 return Cartesian!NdFields(fields).slicedNdField; 4477 } 4478 4479 /// 1D x 1D 4480 version(mir_ndslice_test) unittest 4481 { 4482 auto a = [10, 20, 30]; 4483 auto b = [ 1, 2, 3]; 4484 4485 auto c = cartesian(a, b) 4486 .map!"a + b"; 4487 4488 assert(c == [ 4489 [11, 12, 13], 4490 [21, 22, 23], 4491 [31, 32, 33]]); 4492 } 4493 4494 /// 1D x 2D 4495 version(mir_ndslice_test) unittest 4496 { 4497 auto a = [10, 20, 30]; 4498 auto b = iota([2, 3], 1); 4499 4500 auto c = cartesian(a, b) 4501 .map!"a + b"; 4502 4503 assert(c.shape == [3, 2, 3]); 4504 4505 assert(c == [ 4506 [ 4507 [11, 12, 13], 4508 [14, 15, 16], 4509 ], 4510 [ 4511 [21, 22, 23], 4512 [24, 25, 26], 4513 ], 4514 [ 4515 [31, 32, 33], 4516 [34, 35, 36], 4517 ]]); 4518 } 4519 4520 /// 1D x 1D x 1D 4521 version(mir_ndslice_test) unittest 4522 { 4523 auto u = [100, 200]; 4524 auto v = [10, 20, 30]; 4525 auto w = [1, 2]; 4526 4527 auto c = cartesian(u, v, w) 4528 .map!"a + b + c"; 4529 4530 assert(c.shape == [2, 3, 2]); 4531 4532 assert(c == [ 4533 [ 4534 [111, 112], 4535 [121, 122], 4536 [131, 132], 4537 ], 4538 [ 4539 [211, 212], 4540 [221, 222], 4541 [231, 232], 4542 ]]); 4543 } 4544 4545 /++ 4546 $(LINK2 https://en.wikipedia.org/wiki/Kronecker_product, Kronecker product). 4547 4548 Constructs lazy kronecker product $(SUBREF slice, Slice) without memory allocation. 4549 +/ 4550 template kronecker(alias fun = product) 4551 { 4552 import mir.functional: naryFun; 4553 static if (__traits(isSame, naryFun!fun, fun)) 4554 4555 /++ 4556 Params: 4557 fields = list of either fields with lengths or ndFields with shapes. 4558 All ndFields must have the same dimension count. 4559 Returns: 4560 $(SUBREF ndfield, Kronecker)`!(fun, NdFields)(fields).`$(SUBREF slice, slicedNdField) 4561 +/ 4562 @fmamath auto kronecker(NdFields...)(NdFields fields) 4563 if (allSatisfy!(hasShape, NdFields) || allSatisfy!(hasLength, NdFields)) 4564 { 4565 return Kronecker!(fun, NdFields)(fields).slicedNdField; 4566 } 4567 else 4568 alias kronecker = .kronecker!(naryFun!fun); 4569 } 4570 4571 /// 2D 4572 version(mir_ndslice_test) unittest 4573 { 4574 import mir.ndslice.allocation: slice; 4575 import mir.ndslice.slice: sliced; 4576 4577 // eye 4578 auto a = slice!double([4, 4], 0); 4579 a.diagonal[] = 1; 4580 4581 auto b = [ 1, -1, 4582 -1, 1].sliced(2, 2); 4583 4584 auto c = kronecker(a, b); 4585 4586 assert(c == [ 4587 [ 1, -1, 0, 0, 0, 0, 0, 0], 4588 [-1, 1, 0, 0, 0, 0, 0, 0], 4589 [ 0, 0, 1, -1, 0, 0, 0, 0], 4590 [ 0, 0, -1, 1, 0, 0, 0, 0], 4591 [ 0, 0, 0, 0, 1, -1, 0, 0], 4592 [ 0, 0, 0, 0, -1, 1, 0, 0], 4593 [ 0, 0, 0, 0, 0, 0, 1, -1], 4594 [ 0, 0, 0, 0, 0, 0, -1, 1]]); 4595 } 4596 4597 /// 1D 4598 version(mir_ndslice_test) unittest 4599 { 4600 auto a = iota([3], 1); 4601 4602 auto b = [ 1, -1]; 4603 4604 auto c = kronecker(a, b); 4605 4606 assert(c == [1, -1, 2, -2, 3, -3]); 4607 } 4608 4609 /// 2D with 3 arguments 4610 version(mir_ndslice_test) unittest 4611 { 4612 import mir.ndslice.allocation: slice; 4613 import mir.ndslice.slice: sliced; 4614 4615 auto a = [ 1, 2, 4616 3, 4].sliced(2, 2); 4617 4618 auto b = [ 1, 0, 4619 0, 1].sliced(2, 2); 4620 4621 auto c = [ 1, -1, 4622 -1, 1].sliced(2, 2); 4623 4624 auto d = kronecker(a, b, c); 4625 4626 assert(d == [ 4627 [ 1, -1, 0, 0, 2, -2, 0, 0], 4628 [-1, 1, 0, 0, -2, 2, 0, 0], 4629 [ 0, 0, 1, -1, 0, 0, 2, -2], 4630 [ 0, 0, -1, 1, 0, 0, -2, 2], 4631 [ 3, -3, 0, 0, 4, -4, 0, 0], 4632 [-3, 3, 0, 0, -4, 4, 0, 0], 4633 [ 0, 0, 3, -3, 0, 0, 4, -4], 4634 [ 0, 0, -3, 3, 0, 0, -4, 4]]); 4635 } 4636 4637 /++ 4638 $(HTTPS en.wikipedia.org/wiki/Magic_square, Magic square). 4639 Params: 4640 length = square matrix length. 4641 Returns: 4642 Lazy magic matrix. 4643 +/ 4644 auto magic(size_t length) 4645 { 4646 assert(length > 0); 4647 static if (is(size_t == ulong)) 4648 assert(length <= uint.max); 4649 else 4650 assert(length <= ushort.max); 4651 import mir.ndslice.field: MagicField; 4652 return MagicField(length).slicedField(length, length); 4653 } 4654 4655 /// 4656 @safe pure nothrow 4657 version(mir_ndslice_test) unittest 4658 { 4659 import mir.math.sum; 4660 import mir.ndslice: slice, magic, byDim, map, as, repeat, diagonal, antidiagonal; 4661 4662 bool isMagic(S)(S matrix) 4663 { 4664 auto n = matrix.length; 4665 auto c = n * (n * n + 1) / 2; // magic number 4666 return // check shape 4667 matrix.length!0 > 0 && matrix.length!0 == matrix.length!1 4668 && // each row sum should equal magic number 4669 matrix.byDim!0.map!sum == c.repeat(n) 4670 && // each columns sum should equal magic number 4671 matrix.byDim!1.map!sum == c.repeat(n) 4672 && // diagonal sum should equal magic number 4673 matrix.diagonal.sum == c 4674 && // antidiagonal sum should equal magic number 4675 matrix.antidiagonal.sum == c; 4676 } 4677 4678 assert(isMagic(magic(1))); 4679 assert(!isMagic(magic(2))); // 2x2 magic square does not exist 4680 foreach(n; 3 .. 24) 4681 assert(isMagic(magic(n))); 4682 assert(isMagic(magic(3).as!double.slice)); 4683 } 4684 4685 /++ 4686 Chops 1D input slice into n chunks with ascending or descending lengths. 4687 4688 `stairs` can be used to pack and unpack symmetric and triangular matrix storage. 4689 4690 Note: `stairs` is defined for 1D (packet) input and 2D (general) input. 4691 This part of documentation is for 1D input. 4692 4693 Params: 4694 type = $(UL 4695 $(LI `"-"` for stairs with lengths `n, n-1, ..., 1`.) 4696 $(LI `"+"` for stairs with lengths `1, 2, ..., n`;) 4697 ) 4698 slice = input slice with length equal to `n * (n + 1) / 2` 4699 n = stairs count 4700 Returns: 4701 1D contiguous slice composed of 1D contiguous slices. 4702 4703 See_also: $(LREF triplets) $(LREF ._stairs.2) 4704 +/ 4705 Slice!(StairsIterator!(Iterator, type)) stairs(string type, Iterator)(Slice!Iterator slice, size_t n) 4706 if (type == "+" || type == "-") 4707 { 4708 assert(slice.length == (n + 1) * n / 2, "stairs: slice length must be equal to n * (n + 1) / 2, where n is stairs count."); 4709 static if (type == "+") 4710 size_t length = 1; 4711 else 4712 size_t length = n; 4713 return StairsIterator!(Iterator, type)(length, slice._iterator).sliced(n); 4714 } 4715 4716 /// ditto 4717 Slice!(StairsIterator!(S*, type)) stairs(string type, S)(S[] slice, size_t n) 4718 if (type == "+" || type == "-") 4719 { 4720 return stairs!type(slice.sliced, n); 4721 } 4722 4723 /// ditto 4724 auto stairs(string type, S)(S slice, size_t n) 4725 if (hasAsSlice!S && (type == "+" || type == "-")) 4726 { 4727 return stairs!type(slice.asSlice, n); 4728 } 4729 4730 /// 4731 version(mir_ndslice_test) unittest 4732 { 4733 import mir.ndslice.topology: iota, stairs; 4734 4735 auto pck = 15.iota; 4736 auto inc = pck.stairs!"+"(5); 4737 auto dec = pck.stairs!"-"(5); 4738 4739 assert(inc == [ 4740 [0], 4741 [1, 2], 4742 [3, 4, 5], 4743 [6, 7, 8, 9], 4744 [10, 11, 12, 13, 14]]); 4745 assert(inc[1 .. $][2] == [6, 7, 8, 9]); 4746 4747 assert(dec == [ 4748 [0, 1, 2, 3, 4], 4749 [5, 6, 7, 8], 4750 [9, 10, 11], 4751 [12, 13], 4752 [14]]); 4753 assert(dec[1 .. $][2] == [12, 13]); 4754 4755 static assert(is(typeof(inc.front) == typeof(pck))); 4756 static assert(is(typeof(dec.front) == typeof(pck))); 4757 } 4758 4759 /++ 4760 Slice composed of rows of lower or upper triangular matrix. 4761 4762 `stairs` can be used to pack and unpack symmetric and triangular matrix storage. 4763 4764 Note: `stairs` is defined for 1D (packet) input and 2D (general) input. 4765 This part of documentation is for 2D input. 4766 4767 Params: 4768 type = $(UL 4769 $(LI `"+"` for stairs with lengths `1, 2, ..., n`, lower matrix;) 4770 $(LI `"-"` for stairs with lengths `n, n-1, ..., 1`, upper matrix.) 4771 ) 4772 slice = input slice with length equal to `n * (n + 1) / 2` 4773 Returns: 4774 1D slice composed of 1D contiguous slices. 4775 4776 See_also: $(LREF _stairs) $(SUBREF dynamic, transposed), $(LREF universal) 4777 +/ 4778 auto stairs(string type, Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) slice) 4779 if (type == "+" || type == "-") 4780 { 4781 assert(slice.length!0 == slice.length!1, "stairs: input slice must be a square matrix."); 4782 static if (type == "+") 4783 { 4784 return slice 4785 .pack!1 4786 .map!"a" 4787 .zip([slice.length].iota!size_t(1)) 4788 .map!"a[0 .. b]"; 4789 } 4790 else 4791 { 4792 return slice 4793 .pack!1 4794 .map!"a" 4795 .zip([slice.length].iota!size_t) 4796 .map!"a[b .. $]"; 4797 } 4798 } 4799 4800 /// 4801 version(mir_ndslice_test) unittest 4802 { 4803 import mir.ndslice.topology: iota, as, stairs; 4804 4805 auto gen = [3, 3].iota.as!double; 4806 auto inc = gen.stairs!"+"; 4807 auto dec = gen.stairs!"-"; 4808 4809 assert(inc == [ 4810 [0], 4811 [3, 4], 4812 [6, 7, 8]]); 4813 4814 assert(dec == [ 4815 [0, 1, 2], 4816 [4, 5], 4817 [8]]); 4818 4819 static assert(is(typeof(inc.front) == typeof(gen.front))); 4820 static assert(is(typeof(dec.front) == typeof(gen.front))); 4821 4822 ///////////////////////////////////////// 4823 // Pack lower and upper matrix parts 4824 auto n = gen.length; 4825 auto m = n * (n + 1) / 2; 4826 // allocate memory 4827 import mir.ndslice.allocation: uninitSlice; 4828 auto lowerData = m.uninitSlice!double; 4829 auto upperData = m.uninitSlice!double; 4830 // construct packed stairs 4831 auto lower = lowerData.stairs!"+"(n); 4832 auto upper = upperData.stairs!"-"(n); 4833 // copy data 4834 import mir.algorithm.iteration: each; 4835 each!"a[] = b"(lower, inc); 4836 each!"a[] = b"(upper, dec); 4837 4838 assert(&lower[0][0] is &lowerData[0]); 4839 assert(&upper[0][0] is &upperData[0]); 4840 4841 assert(lowerData == [0, 3, 4, 6, 7, 8]); 4842 assert(upperData == [0, 1, 2, 4, 5, 8]); 4843 } 4844 4845 /++ 4846 Returns a slice that can be iterated along dimension. Transposes other dimensions on top and then packs them. 4847 4848 Combines $(LREF byDim) and $(LREF evertPack). 4849 4850 Params: 4851 SDimensions = dimensions to iterate along, length of d, `1 <= d < n`. Negative dimensions are supported. 4852 Returns: 4853 `(n-d)`-dimensional slice composed of d-dimensional slices 4854 See_also: 4855 $(LREF byDim), 4856 $(LREF iota), 4857 $(SUBREF allocation, slice), 4858 $(LREF ipack), 4859 $(SUBREF dynamic, transposed). 4860 +/ 4861 template alongDim(SDimensions...) 4862 if (SDimensions.length > 0) 4863 { 4864 static if (allSatisfy!(isSizediff_t, SDimensions)) 4865 { 4866 /++ 4867 Params: 4868 slice = input n-dimensional slice, n > d 4869 Returns: 4870 `(n-d)`-dimensional slice composed of d-dimensional slices 4871 +/ 4872 @fmamath auto alongDim(Iterator, size_t N, SliceKind kind) 4873 (Slice!(Iterator, N, kind) slice) 4874 if (N > SDimensions.length) 4875 { 4876 import core.lifetime: move; 4877 return slice.move.byDim!SDimensions.evertPack; 4878 } 4879 } 4880 else 4881 { 4882 alias alongDim = .alongDim!(staticMap!(toSizediff_t, SDimensions)); 4883 } 4884 } 4885 4886 /// 2-dimensional slice support 4887 @safe @nogc pure nothrow 4888 version(mir_ndslice_test) unittest 4889 { 4890 import mir.ndslice; 4891 4892 // ------------ 4893 // | 0 1 2 3 | 4894 // | 4 5 6 7 | 4895 // | 8 9 10 11 | 4896 // ------------ 4897 auto slice = iota(3, 4); 4898 //-> 4899 // | 3 | 4900 //-> 4901 // | 4 | 4902 size_t[1] shape3 = [3]; 4903 size_t[1] shape4 = [4]; 4904 4905 // ------------ 4906 // | 0 1 2 3 | 4907 // | 4 5 6 7 | 4908 // | 8 9 10 11 | 4909 // ------------ 4910 auto x = slice.alongDim!(-1); // -1 is the last dimension index, the same as 1 for this case. 4911 static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t), 1, Universal))); 4912 4913 assert(x.shape == shape3); 4914 assert(x.front.shape == shape4); 4915 assert(x.front == iota(4)); 4916 x.popFront; 4917 assert(x.front == iota([4], 4)); 4918 4919 // --------- 4920 // | 0 4 8 | 4921 // | 1 5 9 | 4922 // | 2 6 10 | 4923 // | 3 7 11 | 4924 // --------- 4925 auto y = slice.alongDim!0; // alongDim!(-2) is the same for matrices. 4926 static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal)))); 4927 4928 assert(y.shape == shape4); 4929 assert(y.front.shape == shape3); 4930 assert(y.front == iota([3], 0, 4)); 4931 y.popFront; 4932 assert(y.front == iota([3], 1, 4)); 4933 } 4934 4935 /// 3-dimensional slice support, N-dimensional also supported 4936 @safe @nogc pure nothrow 4937 version(mir_ndslice_test) unittest 4938 { 4939 import mir.ndslice; 4940 4941 // ---------------- 4942 // | 0 1 2 3 4 | 4943 // | 5 6 7 8 9 | 4944 // | 10 11 12 13 14 | 4945 // | 15 16 17 18 19 | 4946 // - - - - - - - - 4947 // | 20 21 22 23 24 | 4948 // | 25 26 27 28 29 | 4949 // | 30 31 32 33 34 | 4950 // | 35 36 37 38 39 | 4951 // - - - - - - - - 4952 // | 40 41 42 43 44 | 4953 // | 45 46 47 48 49 | 4954 // | 50 51 52 53 54 | 4955 // | 55 56 57 58 59 | 4956 // ---------------- 4957 auto slice = iota(3, 4, 5); 4958 4959 size_t[2] shape45 = [4, 5]; 4960 size_t[2] shape35 = [3, 5]; 4961 size_t[2] shape34 = [3, 4]; 4962 size_t[2] shape54 = [5, 4]; 4963 size_t[1] shape3 = [3]; 4964 size_t[1] shape4 = [4]; 4965 size_t[1] shape5 = [5]; 4966 4967 // ---------- 4968 // | 0 20 40 | 4969 // | 5 25 45 | 4970 // | 10 30 50 | 4971 // | 15 35 55 | 4972 // - - - - - 4973 // | 1 21 41 | 4974 // | 6 26 46 | 4975 // | 11 31 51 | 4976 // | 16 36 56 | 4977 // - - - - - 4978 // | 2 22 42 | 4979 // | 7 27 47 | 4980 // | 12 32 52 | 4981 // | 17 37 57 | 4982 // - - - - - 4983 // | 3 23 43 | 4984 // | 8 28 48 | 4985 // | 13 33 53 | 4986 // | 18 38 58 | 4987 // - - - - - 4988 // | 4 24 44 | 4989 // | 9 29 49 | 4990 // | 14 34 54 | 4991 // | 19 39 59 | 4992 // ---------- 4993 auto a = slice.alongDim!0.transposed; 4994 static assert(is(typeof(a) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal), 2, Universal))); 4995 4996 assert(a.shape == shape54); 4997 assert(a.front.shape == shape4); 4998 assert(a.front.unpack == iota([3, 4], 0, 5).universal.transposed); 4999 a.popFront; 5000 assert(a.front.front == iota([3], 1, 20)); 5001 5002 // ---------------- 5003 // | 0 1 2 3 4 | 5004 // | 5 6 7 8 9 | 5005 // | 10 11 12 13 14 | 5006 // | 15 16 17 18 19 | 5007 // - - - - - - - - 5008 // | 20 21 22 23 24 | 5009 // | 25 26 27 28 29 | 5010 // | 30 31 32 33 34 | 5011 // | 35 36 37 38 39 | 5012 // - - - - - - - - 5013 // | 40 41 42 43 44 | 5014 // | 45 46 47 48 49 | 5015 // | 50 51 52 53 54 | 5016 // | 55 56 57 58 59 | 5017 // ---------------- 5018 auto x = slice.alongDim!(1, 2); 5019 static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2), 1, Universal))); 5020 5021 assert(x.shape == shape3); 5022 assert(x.front.shape == shape45); 5023 assert(x.front == iota([4, 5])); 5024 x.popFront; 5025 assert(x.front == iota([4, 5], (4 * 5))); 5026 5027 // ---------------- 5028 // | 0 1 2 3 4 | 5029 // | 20 21 22 23 24 | 5030 // | 40 41 42 43 44 | 5031 // - - - - - - - - 5032 // | 5 6 7 8 9 | 5033 // | 25 26 27 28 29 | 5034 // | 45 46 47 48 49 | 5035 // - - - - - - - - 5036 // | 10 11 12 13 14 | 5037 // | 30 31 32 33 34 | 5038 // | 50 51 52 53 54 | 5039 // - - - - - - - - 5040 // | 15 16 17 18 19 | 5041 // | 35 36 37 38 39 | 5042 // | 55 56 57 58 59 | 5043 // ---------------- 5044 auto y = slice.alongDim!(0, 2); 5045 static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2, Canonical), 1, Universal))); 5046 5047 assert(y.shape == shape4); 5048 assert(y.front.shape == shape35); 5049 int err; 5050 assert(y.front == slice.universal.strided!1(4).reshape([3, -1], err)); 5051 y.popFront; 5052 assert(y.front.front == iota([5], 5)); 5053 5054 // ------------- 5055 // | 0 5 10 15 | 5056 // | 20 25 30 35 | 5057 // | 40 45 50 55 | 5058 // - - - - - - - 5059 // | 1 6 11 16 | 5060 // | 21 26 31 36 | 5061 // | 41 46 51 56 | 5062 // - - - - - - - 5063 // | 2 7 12 17 | 5064 // | 22 27 32 37 | 5065 // | 42 47 52 57 | 5066 // - - - - - - - 5067 // | 3 8 13 18 | 5068 // | 23 28 33 38 | 5069 // | 43 48 53 58 | 5070 // - - - - - - - 5071 // | 4 9 14 19 | 5072 // | 24 29 34 39 | 5073 // | 44 49 54 59 | 5074 // ------------- 5075 auto z = slice.alongDim!(0, 1); 5076 static assert(is(typeof(z) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2, Universal)))); 5077 5078 assert(z.shape == shape5); 5079 assert(z.front.shape == shape34); 5080 assert(z.front == iota([3, 4], 0, 5)); 5081 z.popFront; 5082 assert(z.front.front == iota([4], 1, 5)); 5083 } 5084 5085 /// Use alongDim to calculate column mean/row mean of 2-dimensional slice 5086 version(mir_ndslice_test) 5087 @safe pure 5088 unittest 5089 { 5090 import mir.ndslice.topology: alongDim; 5091 import mir.ndslice.fuse: fuse; 5092 import mir.math.stat: mean; 5093 import mir.algorithm.iteration: all; 5094 import mir.math.common: approxEqual; 5095 5096 auto x = [ 5097 [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], 5098 [2.0, 7.5, 5.0, 1.0, 1.5, 0.0] 5099 ].fuse; 5100 5101 // Use alongDim with map to compute mean of row/column. 5102 assert(x.alongDim!1.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6])); 5103 assert(x.alongDim!0.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125])); 5104 5105 // FIXME 5106 // Without using map, computes the mean of the whole slice 5107 // assert(x.alongDim!1.mean == x.sliced.mean); 5108 // assert(x.alongDim!0.mean == x.sliced.mean); 5109 } 5110 5111 /++ 5112 Use alongDim and map with a lambda, but may need to allocate result. This example 5113 uses fuse, which allocates. Note: fuse!1 will transpose the result. 5114 +/ 5115 version(mir_ndslice_test) 5116 @safe pure 5117 unittest { 5118 import mir.ndslice.topology: iota, alongDim, map; 5119 import mir.ndslice.fuse: fuse; 5120 import mir.ndslice.slice: sliced; 5121 5122 auto x = [1, 2, 3].sliced; 5123 auto y = [1, 2].sliced; 5124 5125 auto s1 = iota(2, 3).alongDim!1.map!(a => a * x).fuse; 5126 assert(s1 == [[ 0, 2, 6], 5127 [ 3, 8, 15]]); 5128 auto s2 = iota(2, 3).alongDim!0.map!(a => a * y).fuse!1; 5129 assert(s2 == [[ 0, 1, 2], 5130 [ 6, 8, 10]]); 5131 } 5132 5133 /++ 5134 Returns a slice that can be iterated by dimension. Transposes dimensions on top and then packs them. 5135 5136 Combines $(SUBREF dynamic, transposed), $(LREF ipack), and SliceKind Selectors. 5137 5138 Params: 5139 SDimensions = dimensions to perform iteration on, length of d, `1 <= d <= n`. Negative dimensions are supported. 5140 Returns: 5141 d-dimensional slice composed of `(n-d)`-dimensional slices 5142 See_also: 5143 $(LREF alongDim), 5144 $(SUBREF allocation, slice), 5145 $(LREF ipack), 5146 $(SUBREF dynamic, transposed). 5147 +/ 5148 template byDim(SDimensions...) 5149 if (SDimensions.length > 0) 5150 { 5151 static if (allSatisfy!(isSizediff_t, SDimensions)) 5152 { 5153 /++ 5154 Params: 5155 slice = input n-dimensional slice, n >= d 5156 Returns: 5157 d-dimensional slice composed of `(n-d)`-dimensional slices 5158 +/ 5159 @fmamath auto byDim(Iterator, size_t N, SliceKind kind) 5160 (Slice!(Iterator, N, kind) slice) 5161 if (N >= SDimensions.length) 5162 { 5163 5164 alias Dimensions = staticMap!(ShiftNegativeWith!N, SDimensions); 5165 5166 mixin DimensionsCountCTError; 5167 5168 static if (N == 1) 5169 { 5170 return slice; 5171 } 5172 else 5173 { 5174 import core.lifetime: move; 5175 import mir.ndslice.dynamic: transposed; 5176 import mir.algorithm.iteration: all; 5177 5178 auto trans = slice 5179 .move 5180 .transposed!Dimensions; 5181 static if (Dimensions.length == N) 5182 { 5183 return trans; 5184 } 5185 else 5186 { 5187 auto ret = trans.move.ipack!(Dimensions.length); 5188 static if ((kind == Contiguous || kind == Canonical && N - Dimensions.length == 1) && [Dimensions].all!(a => a < Dimensions.length)) 5189 { 5190 return ret 5191 .move 5192 .evertPack 5193 .assumeContiguous 5194 .evertPack; 5195 } 5196 else 5197 static if (kind == Canonical && [Dimensions].all!(a => a < N - 1)) 5198 { 5199 return ret 5200 .move 5201 .evertPack 5202 .assumeCanonical 5203 .evertPack; 5204 } 5205 else 5206 static if ((kind == Contiguous || kind == Canonical && Dimensions.length == 1) && [Dimensions] == [Iota!(N - Dimensions.length, N)]) 5207 { 5208 return ret.assumeContiguous; 5209 } 5210 else 5211 static if ((kind == Contiguous || kind == Canonical) && Dimensions[$-1] == N - 1) 5212 { 5213 return ret.assumeCanonical; 5214 } 5215 else 5216 { 5217 return ret; 5218 } 5219 } 5220 } 5221 } 5222 } 5223 else 5224 { 5225 alias byDim = .byDim!(staticMap!(toSizediff_t, SDimensions)); 5226 } 5227 } 5228 5229 /// 2-dimensional slice support 5230 @safe @nogc pure nothrow 5231 version(mir_ndslice_test) unittest 5232 { 5233 import mir.ndslice; 5234 5235 // ------------ 5236 // | 0 1 2 3 | 5237 // | 4 5 6 7 | 5238 // | 8 9 10 11 | 5239 // ------------ 5240 auto slice = iota(3, 4); 5241 //-> 5242 // | 3 | 5243 //-> 5244 // | 4 | 5245 size_t[1] shape3 = [3]; 5246 size_t[1] shape4 = [4]; 5247 5248 // ------------ 5249 // | 0 1 2 3 | 5250 // | 4 5 6 7 | 5251 // | 8 9 10 11 | 5252 // ------------ 5253 auto x = slice.byDim!0; // byDim!(-2) is the same for matrices. 5254 static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t), 1, Universal))); 5255 5256 assert(x.shape == shape3); 5257 assert(x.front.shape == shape4); 5258 assert(x.front == iota(4)); 5259 x.popFront; 5260 assert(x.front == iota([4], 4)); 5261 5262 // --------- 5263 // | 0 4 8 | 5264 // | 1 5 9 | 5265 // | 2 6 10 | 5266 // | 3 7 11 | 5267 // --------- 5268 auto y = slice.byDim!(-1); // -1 is the last dimension index, the same as 1 for this case. 5269 static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal)))); 5270 5271 assert(y.shape == shape4); 5272 assert(y.front.shape == shape3); 5273 assert(y.front == iota([3], 0, 4)); 5274 y.popFront; 5275 assert(y.front == iota([3], 1, 4)); 5276 } 5277 5278 /// 3-dimensional slice support, N-dimensional also supported 5279 @safe @nogc pure nothrow 5280 version(mir_ndslice_test) unittest 5281 { 5282 import mir.ndslice; 5283 5284 // ---------------- 5285 // | 0 1 2 3 4 | 5286 // | 5 6 7 8 9 | 5287 // | 10 11 12 13 14 | 5288 // | 15 16 17 18 19 | 5289 // - - - - - - - - 5290 // | 20 21 22 23 24 | 5291 // | 25 26 27 28 29 | 5292 // | 30 31 32 33 34 | 5293 // | 35 36 37 38 39 | 5294 // - - - - - - - - 5295 // | 40 41 42 43 44 | 5296 // | 45 46 47 48 49 | 5297 // | 50 51 52 53 54 | 5298 // | 55 56 57 58 59 | 5299 // ---------------- 5300 auto slice = iota(3, 4, 5); 5301 5302 size_t[2] shape45 = [4, 5]; 5303 size_t[2] shape35 = [3, 5]; 5304 size_t[2] shape34 = [3, 4]; 5305 size_t[2] shape54 = [5, 4]; 5306 size_t[1] shape3 = [3]; 5307 size_t[1] shape4 = [4]; 5308 size_t[1] shape5 = [5]; 5309 5310 // ---------------- 5311 // | 0 1 2 3 4 | 5312 // | 5 6 7 8 9 | 5313 // | 10 11 12 13 14 | 5314 // | 15 16 17 18 19 | 5315 // - - - - - - - - 5316 // | 20 21 22 23 24 | 5317 // | 25 26 27 28 29 | 5318 // | 30 31 32 33 34 | 5319 // | 35 36 37 38 39 | 5320 // - - - - - - - - 5321 // | 40 41 42 43 44 | 5322 // | 45 46 47 48 49 | 5323 // | 50 51 52 53 54 | 5324 // | 55 56 57 58 59 | 5325 // ---------------- 5326 auto x = slice.byDim!0; 5327 static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2), 1, Universal))); 5328 5329 assert(x.shape == shape3); 5330 assert(x.front.shape == shape45); 5331 assert(x.front == iota([4, 5])); 5332 x.popFront; 5333 assert(x.front == iota([4, 5], (4 * 5))); 5334 5335 // ---------------- 5336 // | 0 1 2 3 4 | 5337 // | 20 21 22 23 24 | 5338 // | 40 41 42 43 44 | 5339 // - - - - - - - - 5340 // | 5 6 7 8 9 | 5341 // | 25 26 27 28 29 | 5342 // | 45 46 47 48 49 | 5343 // - - - - - - - - 5344 // | 10 11 12 13 14 | 5345 // | 30 31 32 33 34 | 5346 // | 50 51 52 53 54 | 5347 // - - - - - - - - 5348 // | 15 16 17 18 19 | 5349 // | 35 36 37 38 39 | 5350 // | 55 56 57 58 59 | 5351 // ---------------- 5352 auto y = slice.byDim!1; 5353 static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2, Canonical), 1, Universal))); 5354 5355 assert(y.shape == shape4); 5356 assert(y.front.shape == shape35); 5357 int err; 5358 assert(y.front == slice.universal.strided!1(4).reshape([3, -1], err)); 5359 y.popFront; 5360 assert(y.front.front == iota([5], 5)); 5361 5362 // ------------- 5363 // | 0 5 10 15 | 5364 // | 20 25 30 35 | 5365 // | 40 45 50 55 | 5366 // - - - - - - - 5367 // | 1 6 11 16 | 5368 // | 21 26 31 36 | 5369 // | 41 46 51 56 | 5370 // - - - - - - - 5371 // | 2 7 12 17 | 5372 // | 22 27 32 37 | 5373 // | 42 47 52 57 | 5374 // - - - - - - - 5375 // | 3 8 13 18 | 5376 // | 23 28 33 38 | 5377 // | 43 48 53 58 | 5378 // - - - - - - - 5379 // | 4 9 14 19 | 5380 // | 24 29 34 39 | 5381 // | 44 49 54 59 | 5382 // ------------- 5383 auto z = slice.byDim!2; 5384 static assert(is(typeof(z) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2, Universal)))); 5385 5386 assert(z.shape == shape5); 5387 assert(z.front.shape == shape34); 5388 assert(z.front == iota([3, 4], 0, 5)); 5389 z.popFront; 5390 assert(z.front.front == iota([4], 1, 5)); 5391 5392 // ---------- 5393 // | 0 20 40 | 5394 // | 5 25 45 | 5395 // | 10 30 50 | 5396 // | 15 35 55 | 5397 // - - - - - 5398 // | 1 21 41 | 5399 // | 6 26 46 | 5400 // | 11 31 51 | 5401 // | 16 36 56 | 5402 // - - - - - 5403 // | 2 22 42 | 5404 // | 7 27 47 | 5405 // | 12 32 52 | 5406 // | 17 37 57 | 5407 // - - - - - 5408 // | 3 23 43 | 5409 // | 8 28 48 | 5410 // | 13 33 53 | 5411 // | 18 38 58 | 5412 // - - - - - 5413 // | 4 24 44 | 5414 // | 9 29 49 | 5415 // | 14 34 54 | 5416 // | 19 39 59 | 5417 // ---------- 5418 auto a = slice.byDim!(2, 1); 5419 static assert(is(typeof(a) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal), 2, Universal))); 5420 5421 assert(a.shape == shape54); 5422 assert(a.front.shape == shape4); 5423 assert(a.front.unpack == iota([3, 4], 0, 5).universal.transposed); 5424 a.popFront; 5425 assert(a.front.front == iota([3], 1, 20)); 5426 } 5427 5428 /// Use byDim to calculate column mean/row mean of 2-dimensional slice 5429 version(mir_ndslice_test) 5430 @safe pure 5431 unittest 5432 { 5433 import mir.ndslice.topology: byDim; 5434 import mir.ndslice.fuse: fuse; 5435 import mir.math.stat: mean; 5436 import mir.algorithm.iteration: all; 5437 import mir.math.common: approxEqual; 5438 5439 auto x = [ 5440 [0.0, 1.0, 1.5, 2.0, 3.5, 4.25], 5441 [2.0, 7.5, 5.0, 1.0, 1.5, 0.0] 5442 ].fuse; 5443 5444 // Use byDim with map to compute mean of row/column. 5445 assert(x.byDim!0.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6])); 5446 assert(x.byDim!1.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125])); 5447 5448 // FIXME 5449 // Without using map, computes the mean of the whole slice 5450 // assert(x.byDim!0.mean == x.sliced.mean); 5451 // assert(x.byDim!1.mean == x.sliced.mean); 5452 } 5453 5454 /++ 5455 Use byDim and map with a lambda, but may need to allocate result. This example 5456 uses fuse, which allocates. Note: fuse!1 will transpose the result. 5457 +/ 5458 version(mir_ndslice_test) 5459 @safe pure 5460 unittest { 5461 import mir.ndslice.topology: iota, byDim, map; 5462 import mir.ndslice.fuse: fuse; 5463 import mir.ndslice.slice: sliced; 5464 5465 auto x = [1, 2, 3].sliced; 5466 auto y = [1, 2].sliced; 5467 5468 auto s1 = iota(2, 3).byDim!0.map!(a => a * x).fuse; 5469 assert(s1 == [[ 0, 2, 6], 5470 [ 3, 8, 15]]); 5471 auto s2 = iota(2, 3).byDim!1.map!(a => a * y).fuse!1; 5472 assert(s2 == [[ 0, 1, 2], 5473 [ 6, 8, 10]]); 5474 } 5475 5476 // Ensure works on canonical 5477 @safe @nogc pure nothrow 5478 version(mir_ndslice_test) unittest 5479 { 5480 import mir.ndslice.topology : iota, canonical; 5481 // ------------ 5482 // | 0 1 2 3 | 5483 // | 4 5 6 7 | 5484 // | 8 9 10 11 | 5485 // ------------ 5486 auto slice = iota(3, 4).canonical; 5487 //-> 5488 // | 3 | 5489 //-> 5490 // | 4 | 5491 size_t[1] shape3 = [3]; 5492 size_t[1] shape4 = [4]; 5493 5494 // ------------ 5495 // | 0 1 2 3 | 5496 // | 4 5 6 7 | 5497 // | 8 9 10 11 | 5498 // ------------ 5499 auto x = slice.byDim!0; 5500 static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t), 1, Universal))); 5501 5502 assert(x.shape == shape3); 5503 assert(x.front.shape == shape4); 5504 assert(x.front == iota(4)); 5505 x.popFront; 5506 assert(x.front == iota([4], 4)); 5507 5508 // --------- 5509 // | 0 4 8 | 5510 // | 1 5 9 | 5511 // | 2 6 10 | 5512 // | 3 7 11 | 5513 // --------- 5514 auto y = slice.byDim!1; 5515 static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal)))); 5516 5517 assert(y.shape == shape4); 5518 assert(y.front.shape == shape3); 5519 assert(y.front == iota([3], 0, 4)); 5520 y.popFront; 5521 assert(y.front == iota([3], 1, 4)); 5522 } 5523 5524 // Ensure works on universal 5525 @safe @nogc pure nothrow 5526 version(mir_ndslice_test) unittest 5527 { 5528 import mir.ndslice.topology : iota, universal; 5529 // ------------ 5530 // | 0 1 2 3 | 5531 // | 4 5 6 7 | 5532 // | 8 9 10 11 | 5533 // ------------ 5534 auto slice = iota(3, 4).universal; 5535 //-> 5536 // | 3 | 5537 //-> 5538 // | 4 | 5539 size_t[1] shape3 = [3]; 5540 size_t[1] shape4 = [4]; 5541 5542 // ------------ 5543 // | 0 1 2 3 | 5544 // | 4 5 6 7 | 5545 // | 8 9 10 11 | 5546 // ------------ 5547 auto x = slice.byDim!0; 5548 static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal), 1, Universal))); 5549 5550 assert(x.shape == shape3); 5551 assert(x.front.shape == shape4); 5552 assert(x.front == iota(4)); 5553 x.popFront; 5554 assert(x.front == iota([4], 4)); 5555 5556 // --------- 5557 // | 0 4 8 | 5558 // | 1 5 9 | 5559 // | 2 6 10 | 5560 // | 3 7 11 | 5561 // --------- 5562 auto y = slice.byDim!1; 5563 static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal), 1, Universal))); 5564 5565 assert(y.shape == shape4); 5566 assert(y.front.shape == shape3); 5567 assert(y.front == iota([3], 0, 4)); 5568 y.popFront; 5569 assert(y.front == iota([3], 1, 4)); 5570 } 5571 5572 // 1-dimensional slice support 5573 @safe @nogc pure nothrow 5574 version(mir_ndslice_test) unittest 5575 { 5576 import mir.ndslice.topology : iota; 5577 // ------- 5578 // | 0 1 2 | 5579 // ------- 5580 auto slice = iota(3); 5581 auto x = slice.byDim!0; 5582 static assert (is(typeof(x) == typeof(slice))); 5583 assert(x == slice); 5584 } 5585 5586 /++ 5587 Constructs a new view of an n-dimensional slice with dimension `axis` removed. 5588 5589 Throws: 5590 `AssertError` if the length of the corresponding dimension doesn' equal 1. 5591 Params: 5592 axis = dimension to remove, if it is single-dimensional 5593 slice = n-dimensional slice 5594 Returns: 5595 new view of a slice with dimension removed 5596 See_also: $(LREF unsqueeze), $(LREF iota). 5597 +/ 5598 template squeeze(sizediff_t axis = 0) 5599 { 5600 Slice!(Iterator, N - 1, kind != Canonical ? kind : ((axis == N - 1 || axis == -1) ? Universal : (N == 2 ? Contiguous : kind))) 5601 squeeze(Iterator, size_t N, SliceKind kind) 5602 (Slice!(Iterator, N, kind) slice) 5603 if (-sizediff_t(N) <= axis && axis < sizediff_t(N) && N > 1) 5604 in { 5605 assert(slice._lengths[axis < 0 ? N + axis : axis] == 1); 5606 } 5607 do { 5608 import mir.utility: swap; 5609 enum sizediff_t a = axis < 0 ? N + axis : axis; 5610 typeof(return) ret; 5611 foreach (i; Iota!(0, a)) 5612 ret._lengths[i] = slice._lengths[i]; 5613 foreach (i; Iota!(a + 1, N)) 5614 ret._lengths[i - 1] = slice._lengths[i]; 5615 static if (kind == Universal) 5616 { 5617 foreach (i; Iota!(0, a)) 5618 ret._strides[i] = slice._strides[i]; 5619 foreach (i; Iota!(a + 1, N)) 5620 ret._strides[i - 1] = slice._strides[i]; 5621 } 5622 else 5623 static if (kind == Canonical) 5624 { 5625 static if (a == N - 1) 5626 { 5627 foreach (i; Iota!(0, N - 1)) 5628 ret._strides[i] = slice._strides[i]; 5629 } 5630 else 5631 { 5632 foreach (i; Iota!(0, a)) 5633 ret._strides[i] = slice._strides[i]; 5634 foreach (i; Iota!(a + 1, N - 1)) 5635 ret._strides[i - 1] = slice._strides[i]; 5636 } 5637 } 5638 swap(ret._iterator, slice._iterator); 5639 return ret; 5640 } 5641 } 5642 5643 /// 5644 version(mir_ndslice_test) 5645 unittest 5646 { 5647 import mir.ndslice.topology : iota; 5648 import mir.ndslice.allocation : slice; 5649 5650 // [[0, 1, 2]] -> [0, 1, 2] 5651 assert([1, 3].iota.squeeze == [3].iota); 5652 // [[0], [1], [2]] -> [0, 1, 2] 5653 assert([3, 1].iota.squeeze!1 == [3].iota); 5654 assert([3, 1].iota.squeeze!(-1) == [3].iota); 5655 5656 assert([1, 3].iota.canonical.squeeze == [3].iota); 5657 assert([3, 1].iota.canonical.squeeze!1 == [3].iota); 5658 assert([3, 1].iota.canonical.squeeze!(-1) == [3].iota); 5659 5660 assert([1, 3].iota.universal.squeeze == [3].iota); 5661 assert([3, 1].iota.universal.squeeze!1 == [3].iota); 5662 assert([3, 1].iota.universal.squeeze!(-1) == [3].iota); 5663 5664 assert([1, 3, 4].iota.squeeze == [3, 4].iota); 5665 assert([3, 1, 4].iota.squeeze!1 == [3, 4].iota); 5666 assert([3, 4, 1].iota.squeeze!(-1) == [3, 4].iota); 5667 5668 assert([1, 3, 4].iota.canonical.squeeze == [3, 4].iota); 5669 assert([3, 1, 4].iota.canonical.squeeze!1 == [3, 4].iota); 5670 assert([3, 4, 1].iota.canonical.squeeze!(-1) == [3, 4].iota); 5671 5672 assert([1, 3, 4].iota.universal.squeeze == [3, 4].iota); 5673 assert([3, 1, 4].iota.universal.squeeze!1 == [3, 4].iota); 5674 assert([3, 4, 1].iota.universal.squeeze!(-1) == [3, 4].iota); 5675 } 5676 5677 /++ 5678 Constructs a view of an n-dimensional slice with a dimension added at `axis`. Used 5679 to unsqueeze a squeezed slice. 5680 5681 Params: 5682 slice = n-dimensional slice 5683 axis = dimension to be unsqueezed (add new dimension), default values is 0, the first dimension 5684 Returns: 5685 unsqueezed n+1-dimensional slice of the same slice kind 5686 See_also: $(LREF squeeze), $(LREF iota). 5687 +/ 5688 Slice!(Iterator, N + 1, kind) unsqueeze(Iterator, size_t N, SliceKind kind) 5689 (Slice!(Iterator, N, kind) slice, sizediff_t axis) 5690 in { 5691 assert(-sizediff_t(N + 1) <= axis && axis <= sizediff_t(N)); 5692 } 5693 do { 5694 import mir.utility: swap; 5695 typeof(return) ret; 5696 auto a = axis < 0 ? axis + N + 1 : axis; 5697 foreach (i; 0 .. a) 5698 ret._lengths[i] = slice._lengths[i]; 5699 ret._lengths[a] = 1; 5700 foreach (i; a .. N) 5701 ret._lengths[i + 1] = slice._lengths[i]; 5702 static if (kind == Universal) 5703 { 5704 foreach (i; 0 .. a) 5705 ret._strides[i] = slice._strides[i]; 5706 foreach (i; a .. N) 5707 ret._strides[i + 1] = slice._strides[i]; 5708 } 5709 else 5710 static if (kind == Canonical) 5711 { 5712 if (a == N) 5713 { 5714 foreach (i; Iota!(0, N - 1)) 5715 ret._strides[i] = slice._strides[i]; 5716 ret._strides[N - 1] = 1; 5717 } 5718 else 5719 { 5720 foreach (i; 0 .. a) 5721 ret._strides[i] = slice._strides[i]; 5722 foreach (i; a .. N - 1) 5723 ret._strides[i + 1] = slice._strides[i]; 5724 } 5725 } 5726 swap(ret._iterator, slice._iterator); 5727 return ret; 5728 } 5729 5730 /// ditto 5731 template unsqueeze(sizediff_t axis = 0) 5732 { 5733 Slice!(Iterator, N + 1, kind) unsqueeze(Iterator, size_t N, SliceKind kind) 5734 (Slice!(Iterator, N, kind) slice) 5735 in { 5736 assert(-sizediff_t(N + 1) <= axis && axis <= sizediff_t(N)); 5737 } 5738 do { 5739 import mir.utility: swap; 5740 typeof(return) ret; 5741 enum a = axis < 0 ? axis + N + 1 : axis; 5742 foreach (i; Iota!a) 5743 ret._lengths[i] = slice._lengths[i]; 5744 ret._lengths[a] = 1; 5745 foreach (i; Iota!(a, N)) 5746 ret._lengths[i + 1] = slice._lengths[i]; 5747 static if (kind == Universal) 5748 { 5749 foreach (i; Iota!a) 5750 ret._strides[i] = slice._strides[i]; 5751 foreach (i; Iota!(a, N)) 5752 ret._strides[i + 1] = slice._strides[i]; 5753 } 5754 else 5755 static if (kind == Canonical) 5756 { 5757 static if (a == N) 5758 { 5759 foreach (i; Iota!(0, N - 1)) 5760 ret._strides[i] = slice._strides[i]; 5761 ret._strides[N - 1] = 1; 5762 } 5763 else 5764 { 5765 foreach (i; Iota!(0, a)) 5766 ret._strides[i] = slice._strides[i]; 5767 foreach (i; Iota!(a, N - 1)) 5768 ret._strides[i + 1] = slice._strides[i]; 5769 } 5770 } 5771 swap(ret._iterator, slice._iterator); 5772 return ret; 5773 } 5774 } 5775 5776 /// 5777 version (mir_ndslice_test) 5778 @safe pure nothrow @nogc 5779 unittest 5780 { 5781 // [0, 1, 2] -> [[0, 1, 2]] 5782 assert([3].iota.unsqueeze == [1, 3].iota); 5783 5784 assert([3].iota.universal.unsqueeze == [1, 3].iota); 5785 assert([3, 4].iota.unsqueeze == [1, 3, 4].iota); 5786 assert([3, 4].iota.canonical.unsqueeze == [1, 3, 4].iota); 5787 assert([3, 4].iota.universal.unsqueeze == [1, 3, 4].iota); 5788 5789 // [0, 1, 2] -> [[0], [1], [2]] 5790 assert([3].iota.unsqueeze(-1) == [3, 1].iota); 5791 assert([3].iota.unsqueeze!(-1) == [3, 1].iota); 5792 5793 assert([3].iota.universal.unsqueeze(-1) == [3, 1].iota); 5794 assert([3].iota.universal.unsqueeze!(-1) == [3, 1].iota); 5795 assert([3, 4].iota.unsqueeze(-1) == [3, 4, 1].iota); 5796 assert([3, 4].iota.unsqueeze!(-1) == [3, 4, 1].iota); 5797 assert([3, 4].iota.canonical.unsqueeze(-1) == [3, 4, 1].iota); 5798 assert([3, 4].iota.canonical.unsqueeze!(-1) == [3, 4, 1].iota); 5799 assert([3, 4].iota.universal.unsqueeze(-1) == [3, 4, 1].iota); 5800 assert([3, 4].iota.universal.unsqueeze!(-1) == [3, 4, 1].iota); 5801 } 5802 5803 /++ 5804 Field (element's member) projection. 5805 5806 Params: 5807 name = element's member name 5808 Returns: 5809 lazy n-dimensional slice of the same shape 5810 See_also: 5811 $(LREF map) 5812 +/ 5813 5814 template member(string name) 5815 if (name.length) 5816 { 5817 /++ 5818 Params: 5819 slice = n-dimensional slice composed of structs, classes or unions 5820 Returns: 5821 lazy n-dimensional slice of the same shape 5822 +/ 5823 Slice!(MemberIterator!(Iterator, name), N, kind) member(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) 5824 { 5825 return typeof(return)(slice._structure, MemberIterator!(Iterator, name)(slice._iterator)); 5826 } 5827 5828 /// ditto 5829 Slice!(MemberIterator!(T*, name)) member(T)(T[] array) 5830 { 5831 return member(array.sliced); 5832 } 5833 5834 /// ditto 5835 auto member(T)(T withAsSlice) 5836 if (hasAsSlice!T) 5837 { 5838 return member(withAsSlice.asSlice); 5839 } 5840 } 5841 5842 /// 5843 version(mir_ndslice_test) 5844 @safe pure unittest 5845 { 5846 // struct, union or class 5847 struct S 5848 { 5849 // Property support 5850 // Getter always must be defined. 5851 double _x; 5852 double x() @property 5853 { 5854 return x; 5855 } 5856 void x(double x) @property 5857 { 5858 _x = x; 5859 } 5860 5861 /// Field support 5862 double y; 5863 5864 /// Zero argument function support 5865 double f() 5866 { 5867 return _x * 2; 5868 } 5869 } 5870 5871 import mir.ndslice.allocation: slice; 5872 import mir.ndslice.topology: iota; 5873 5874 auto matrix = slice!S(2, 3); 5875 matrix.member!"x"[] = [2, 3].iota; 5876 matrix.member!"y"[] = matrix.member!"f"; 5877 assert(matrix.member!"y" == [2, 3].iota * 2); 5878 } 5879 5880 /++ 5881 Functional deep-element wise reduce of a slice composed of fields or iterators. 5882 +/ 5883 template orthogonalReduceField(alias fun) 5884 { 5885 import mir.functional: naryFun; 5886 static if (__traits(isSame, naryFun!fun, fun)) 5887 { 5888 @fmamath: 5889 /++ 5890 Params: 5891 slice = Non empty input slice composed of fields or iterators. 5892 Returns: 5893 a lazy field with each element of which is reduced value of element of the same index of all iterators. 5894 +/ 5895 OrthogonalReduceField!(Iterator, fun, I) orthogonalReduceField(I, Iterator)(I initialValue, Slice!Iterator slice) 5896 { 5897 return typeof(return)(slice, initialValue); 5898 } 5899 5900 /// ditto 5901 OrthogonalReduceField!(T*, fun, I) orthogonalReduceField(I, T)(I initialValue, T[] array) 5902 { 5903 return orthogonalReduceField(initialValue, array.sliced); 5904 } 5905 5906 /// ditto 5907 auto orthogonalReduceField(I, T)(I initialValue, T withAsSlice) 5908 if (hasAsSlice!T) 5909 { 5910 return orthogonalReduceField(initialValue, withAsSlice.asSlice); 5911 } 5912 } 5913 else alias orthogonalReduceField = .orthogonalReduceField!(naryFun!fun); 5914 } 5915 5916 /// bit array operations 5917 version(mir_ndslice_test) 5918 unittest 5919 { 5920 import mir.ndslice.slice: slicedField; 5921 import mir.ndslice.allocation: bitSlice; 5922 import mir.ndslice.dynamic: strided; 5923 import mir.ndslice.topology: iota, orthogonalReduceField; 5924 auto len = 100; 5925 auto a = len.bitSlice; 5926 auto b = len.bitSlice; 5927 auto c = len.bitSlice; 5928 a[len.iota.strided!0(7)][] = true; 5929 b[len.iota.strided!0(11)][] = true; 5930 c[len.iota.strided!0(13)][] = true; 5931 5932 // this is valid since bitslices above are oroginal slices of allocated memory. 5933 auto and = 5934 orthogonalReduceField!"a & b"(size_t.max, [ 5935 a.iterator._field._field, // get raw data pointers 5936 b.iterator._field._field, 5937 c.iterator._field._field, 5938 ]) // operation on size_t 5939 .bitwiseField 5940 .slicedField(len); 5941 5942 assert(and == (a & b & c)); 5943 } 5944 5945 /++ 5946 Constructs a lazy view of triplets with `left`, `center`, and `right` members. 5947 5948 Returns: Slice of the same length composed of $(SUBREF iterator, Triplet) triplets. 5949 The `center` member is type of a slice element. 5950 The `left` and `right` members has the same type as slice. 5951 5952 The module contains special function $(LREF collapse) to handle 5953 left and right side of triplets in one expression. 5954 5955 Params: 5956 slice = a slice or an array to iterate over 5957 5958 Example: 5959 ------ 5960 triplets(eeeeee) => 5961 5962 ||c|lllll| 5963 |r|c|llll| 5964 |rr|c|lll| 5965 |rrr|c|ll| 5966 |rrrr|c|l| 5967 |rrrrr|c|| 5968 ------ 5969 5970 See_also: $(LREF stairs). 5971 +/ 5972 Slice!(TripletIterator!(Iterator, kind)) triplets(Iterator, SliceKind kind)(Slice!(Iterator, 1, kind) slice) 5973 { 5974 return typeof(return)(slice.length, typeof(return).Iterator(0, slice)); 5975 } 5976 5977 /// ditto 5978 Slice!(TripletIterator!(T*)) triplets(T)(scope return T[] slice) 5979 { 5980 return .triplets(slice.sliced); 5981 } 5982 5983 /// ditto 5984 auto triplets(string type, S)(S slice, size_t n) 5985 if (hasAsSlice!S) 5986 { 5987 return .triplets(slice.asSlice); 5988 } 5989 5990 /// 5991 version(mir_ndslice_test) unittest 5992 { 5993 import mir.ndslice.slice: sliced; 5994 import mir.ndslice.topology: triplets, member, iota; 5995 5996 auto a = [4, 5, 2, 8]; 5997 auto h = a.triplets; 5998 5999 assert(h[1].center == 5); 6000 assert(h[1].left == [4]); 6001 assert(h[1].right == [2, 8]); 6002 6003 h[1].center = 9; 6004 assert(a[1] == 9); 6005 6006 assert(h.member!"center" == a); 6007 6008 // `triplets` topology can be used with iota to index a slice 6009 auto s = a.sliced; 6010 auto w = s.length.iota.triplets[1]; 6011 6012 assert(&s[w.center] == &a[1]); 6013 assert(s[w.left].field is a[0 .. 1]); 6014 assert(s[w.right].field is a[2 .. $]); 6015 }