1 /++ 2 This is a submodule of $(MREF mir,ndslice). 3 4 The module contains $(LREF _chunks) routine. 5 $(LREF Chunks) structure is multidimensional random access range with slicing. 6 7 $(SUBREF slice, slicedField), $(SUBREF slice, slicedNdField) can be used to construct ndslice view on top of $(LREF Chunks). 8 9 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) 10 Copyright: 2020 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments 11 Authors: Ilya Yaroshenko 12 13 Macros: 14 SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) 15 T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) 16 +/ 17 module mir.ndslice.chunks; 18 19 import mir.internal.utility; 20 import mir.math.common: optmath; 21 import mir.ndslice.internal; 22 import mir.ndslice.iterator: IotaIterator; 23 import mir.ndslice.slice; 24 25 import std.meta; 26 import std.traits; 27 28 /++ 29 Creates $(LREF Chunks). 30 31 Params: 32 Dimensions = compile time list of dimensions to chunk 33 34 See_also: $(SUBREF topology, blocks) $(SUBREF fuse, fuseCells) 35 +/ 36 template chunks(Dimensions...) 37 if (Dimensions.length) 38 { 39 static if (allSatisfy!(isSize_t, Dimensions)) 40 /++ 41 Params: 42 slice = Slice to chunk. 43 chunkLengths = Chunk shape. It must not have a zero length. 44 Returns: $(LREF Chunks). 45 +/ 46 Chunks!([Dimensions], Iterator, N, kind == Contiguous && [Dimensions] != [0] ? Canonical : kind) 47 chunks(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice, size_t[Dimensions.length] chunkLengths...) 48 { 49 static if (kindOf!(typeof(typeof(return).init._slice)) != kind) 50 { 51 import mir.ndslice.topology: canonical; 52 auto p = slice.canonical; 53 } 54 else 55 { 56 alias p = slice; 57 } 58 auto ret = typeof(return)(chunkLengths, p); 59 foreach (i; Iota!(Dimensions.length)) 60 ret._norm!i; 61 return ret; 62 } 63 else 64 alias chunks = .chunks!(staticMap!(toSize_t, Dimensions)); 65 } 66 67 /// ditto 68 Chunks!([0], Iterator, N, kind) chunks(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice, size_t chunkLength) 69 { 70 return .chunks!0(slice, chunkLength); 71 } 72 73 74 /// 1Dx1D 75 @safe pure nothrow @nogc version(mir_test) unittest 76 { 77 import mir.ndslice.chunks: chunks, isChunks; 78 import mir.ndslice.topology: iota; 79 80 // 0 1 2 3 4 5 6 7 8 9 10 81 auto sl = iota(11); 82 // 0 1 2 | 3 4 5 | 6 7 8 | 9 10 83 auto ch = sl.chunks(3); 84 85 static assert(isChunks!(typeof(ch)) == [0]); // isChunks returns dimension indices 86 87 assert(ch.length == 4); 88 assert(ch.shape == cast(size_t[1])[4]); 89 90 // 0 1 2 91 assert(ch.front == iota([3], 0)); 92 ch.popFront; 93 94 // 3 4 5 95 assert(ch.front == iota([3], 3)); 96 assert(ch.length == 3); 97 98 // 9 10 99 assert(ch[$ - 1] == ch.back); 100 assert(ch.back == iota([2], 9)); 101 102 ch.popBack; 103 assert(ch.back == iota([3], 6)); 104 105 assert(ch[$ - 1 .. $].length == 1); 106 assert(ch[$ .. $].length == 0); 107 assert(ch[0 .. 0].empty); 108 109 import std.range.primitives: isRandomAccessRange; 110 static assert(isRandomAccessRange!(typeof(ch))); 111 } 112 113 /// 2Dx2D 114 @safe pure nothrow 115 version(mir_test) unittest 116 { 117 import mir.ndslice.chunks: chunks, isChunks; 118 import mir.ndslice.topology: iota; 119 120 // 0 1 2 3 4 5 6 7 8 9 121 // 10 11 12 13 14 15 16 17 18 19 122 // 20 21 22 23 24 25 26 27 28 29 123 // 30 31 32 33 34 35 36 37 38 39 124 // 40 41 42 43 44 45 46 47 48 49 125 // 50 51 52 53 54 55 56 57 58 59 126 // 60 61 62 63 64 65 66 67 68 69 127 // 70 71 72 73 74 75 76 77 78 79 128 // 80 81 82 83 84 85 86 87 88 89 129 // 90 91 92 93 94 95 96 97 98 99 130 // 100 101 102 103 104 105 106 107 108 109 131 auto sl = iota(11, 10); // [0, 1, .. 10] 132 133 // ---------------- ---------------- -------- 134 // | 0 1 2 3 | | 4 5 6 7 | | 8 9 | 135 // | 10 11 12 13 | | 14 15 16 17 | | 18 19 | 136 // | 20 21 22 23 | | 24 25 26 27 | | 28 29 | 137 // |----------------| |----------------| |--------| 138 // | 30 31 32 33 | | 34 35 36 37 | | 38 39 | 139 // | 40 41 42 43 | | 44 45 46 47 | | 48 49 | 140 // | 50 51 52 53 | | 54 55 56 57 | | 58 59 | 141 // |----------------| |----------------| |--------| 142 // | 60 61 62 63 | | 64 65 66 67 | | 68 69 | 143 // | 70 71 72 73 | | 74 75 76 77 | | 78 79 | 144 // | 80 81 82 83 | | 84 85 86 87 | | 88 89 | 145 // |----------------| |----------------| |--------| 146 // | 90 91 92 93 | | 94 95 96 97 | | 98 99 | 147 // |100 101 102 103 | |104 105 106 107 | |108 109 | 148 // ---------------- ---------------- -------- 149 // Chunk columns first, then blocks rows. 150 auto ch = sl.chunks!(1, 0)(4, 3); 151 152 assert(ch.shape == [3, 4]); 153 assert(ch.slice == sl); 154 assert(ch.front.slice == sl[0 .. $, 0 .. 4]); 155 156 assert(ch.front.front == sl[0 .. 3, 0 .. 4]); 157 158 assert(ch.front!0[1] == sl[3 .. 6, 0 .. 4]); 159 assert(ch.front!1[1] == sl[0 .. 3, 4 .. 8]); 160 161 assert (ch[$ - 1, $ - 1] == [[98, 99], [108, 109]]); 162 163 static assert(isChunks!(typeof(ch)) == [1, 0]); // isChunks returns dimension indices 164 165 assert(ch.length == 3); 166 assert(ch.length!1 == 4); 167 168 ch.popFront; 169 assert(ch.front.front == sl[0 .. 3, 4 .. 8]); 170 ch.popFront!1; 171 assert(ch.front.front == sl[3 .. 6, 4 .. 8]); 172 173 assert(ch.back.slice == sl[3 .. $, 8 .. $]); 174 ch.popBack; 175 assert(ch.back.slice == sl[3 .. $, 4 .. 8]); 176 177 import std.range.primitives: isRandomAccessRange; 178 static assert(isRandomAccessRange!(typeof(ch))); 179 } 180 181 /// 1Dx2D 182 version(mir_test) unittest 183 { 184 import mir.ndslice.chunks: chunks, isChunks; 185 import mir.ndslice.topology: iota; 186 187 // 0 1 2 3 4 5 6 7 8 9 188 // 10 11 12 13 14 15 16 17 18 19 189 // 20 21 22 23 24 25 26 27 28 29 190 // 30 31 32 33 34 35 36 37 38 39 191 auto sl = iota(4, 10); // [0, 1, .. 10] 192 193 // ---------------- ---------------- -------- 194 // | 0 1 2 3 | | 4 5 6 7 | | 8 9 | 195 // | 10 11 12 13 | | 14 15 16 17 | | 18 19 | 196 // | 20 21 22 23 | | 24 25 26 27 | | 28 29 | 197 // | 30 31 32 33 | | 34 35 36 37 | | 38 39 | 198 // ---------------- ---------------- -------- 199 // Chunk columns 200 auto ch = sl.chunks!1(4); 201 202 assert(ch.slice == sl); 203 assert(ch.front == sl[0 .. $, 0 .. 4]); 204 205 assert(ch.back == sl[0 .. $, 8 .. $]); 206 207 import std.range.primitives: isRandomAccessRange; 208 static assert(isRandomAccessRange!(typeof(ch))); 209 } 210 211 // conversion to ndslice 212 version(mir_test) unittest 213 { 214 import mir.ndslice.slice : slicedField; 215 import mir.ndslice.chunks: chunks; 216 import mir.ndslice.topology: iota, map; 217 import mir.math.sum: sum; 218 219 // 0 1 2 3 4 5 6 7 8 9 10 220 auto sl = iota(11); 221 // 0 1 2 | 3 4 5 | 6 7 8 | 9 10 222 auto ch = sl.chunks(3); 223 // 3 | 12 | 21 | 19 224 auto s = ch.slicedField.map!sum; 225 assert(s == [3, 12, 21, 19]); 226 } 227 228 /++ 229 +/ 230 struct Chunks(size_t[] dimensions, Iterator, size_t N = 1, SliceKind kind = Contiguous) 231 { 232 @optmath: 233 234 /++ 235 Chunk shape. 236 +/ 237 size_t[dimensions.length] chunkLengths()() @property { return _chunkLengths; } 238 /// ditto 239 size_t[dimensions.length] _chunkLengths; 240 241 /// 242 auto lightConst()() const @property 243 { 244 import mir.qualifier; 245 return Chunks!(dimensions, LightConstOf!Iterator, N, kind)(_chunkLengths, _slice.lightConst); 246 } 247 248 /// 249 auto lightImmutable()() immutable @property 250 { 251 import mir.qualifier; 252 return Chunks!(dimensions, LightImmutableOf!Iterator, N, kind)(_chunkLengths, _slice.lightImmutable); 253 } 254 255 alias DeepElement = Slice!(Iterator, N, kind); 256 257 /++ 258 Underlying ndslice. 259 It always correspond to current chunks state. 260 Its shape equal to the concatenation of the all chunks. 261 +/ 262 Slice!(Iterator, N, kind) slice()() @property { return _slice; } 263 /// 264 Slice!(Iterator, N, kind) _slice; 265 266 private auto _norm(size_t dimensionIndex = 0)() @property 267 { 268 assert(_chunkLengths[dimensionIndex]); 269 enum dimension = dimensions[dimensionIndex]; 270 if (_expect(_slice._lengths[dimension] < _chunkLengths[dimensionIndex], false) && _slice._lengths[dimension]) 271 _chunkLengths[dimensionIndex] = _slice._lengths[dimension]; 272 } 273 274 private auto _wrap(size_t dimensionIndex, S)(ref S ret) 275 { 276 static if (dimensions.length == 1) 277 { 278 return ret; 279 } 280 else 281 { 282 size_t[dimensions.length - 1] rcl; 283 foreach (i, j; AliasSeq!(Iota!dimensionIndex, Iota!(dimensionIndex + 1, dimensions.length))) 284 rcl[i] = _chunkLengths[j]; 285 enum newDims = dimensions[0 .. dimensionIndex] ~ dimensions[dimensionIndex + 1 .. $]; 286 return .Chunks!(newDims, Iterator, N, typeof(ret).kind)(rcl, ret); 287 } 288 } 289 290 private ref size_t sliceLength(size_t dimensionIndex)() @property 291 { 292 enum dimension = dimensions[dimensionIndex]; 293 return _slice._lengths[dimension]; 294 } 295 296 /// ndslice-like primitives 297 bool empty(size_t dimensionIndex = 0)() const @property 298 if (dimensionIndex < dimensions.length) 299 { 300 enum dimension = dimensions[dimensionIndex]; 301 return _slice.empty!(dimension); 302 } 303 304 /// 305 size_t[dimensions.length] shape()() const @property 306 { 307 typeof(return) ret; 308 foreach(dimensionIndex; Iota!(ret.length)) 309 { 310 enum dimension = dimensions[dimensionIndex]; 311 auto l = _slice._lengths[dimension]; 312 auto n = _chunkLengths[dimensionIndex]; 313 ret[dimensionIndex] = l / n + (l % n != 0); 314 } 315 return ret; 316 } 317 318 /// ditto 319 size_t length(size_t dimensionIndex = 0)() const @property 320 if (dimensionIndex < dimensions.length) 321 { 322 enum dimension = dimensions[dimensionIndex]; 323 auto l = _slice._lengths[dimension]; 324 auto n = _chunkLengths[dimensionIndex]; 325 return l / n + (l % n != 0); 326 } 327 328 /// ditto 329 auto front(size_t dimensionIndex = 0)() @property 330 if (dimensionIndex < dimensions.length) 331 { 332 enum dimension = dimensions[dimensionIndex]; 333 assert(_chunkLengths[dimensionIndex] <= _slice._lengths[dimension]); 334 auto ret = _slice.selectFront!dimension(_chunkLengths[dimensionIndex]); 335 return _wrap!dimensionIndex(ret); 336 } 337 338 /// 339 auto back(size_t dimensionIndex = 0)() @property 340 if (dimensionIndex < dimensions.length) 341 { 342 assert(!empty!dimensionIndex); 343 enum dimension = dimensions[dimensionIndex]; 344 auto l = _slice._lengths[dimension]; 345 auto n = _chunkLengths[dimensionIndex]; 346 auto rshift = l % n; 347 rshift = !rshift ? n : rshift; 348 auto len = _slice._lengths[dimension]; 349 auto ret = _slice.select!dimension(len - rshift, len); 350 return _wrap!dimensionIndex(ret); 351 } 352 353 /// ditto 354 void popFront(size_t dimensionIndex = 0)() 355 if (dimensionIndex < dimensions.length) 356 { 357 enum dimension = dimensions[dimensionIndex]; 358 assert(!empty!dimensionIndex); 359 _slice.popFrontExactly!dimension(_chunkLengths[dimensionIndex]); 360 _norm!dimensionIndex; 361 } 362 363 /// ditto 364 void popBack(size_t dimensionIndex = 0)() 365 if (dimensionIndex < dimensions.length) 366 { 367 assert(!empty!dimensionIndex); 368 enum dimension = dimensions[dimensionIndex]; 369 auto l = _slice._lengths[dimension]; 370 auto n = _chunkLengths[dimensionIndex]; 371 auto rshift = l % n; 372 rshift = !rshift ? n : rshift; 373 _slice.popBackExactly!dimension(rshift); 374 _norm!dimensionIndex; 375 } 376 377 /// ditto 378 Slice!(IotaIterator!size_t) opSlice(size_t dimensionIndex)(size_t i, size_t j) const 379 if (dimensionIndex < dimensions.length) 380 in 381 { 382 assert(i <= j, 383 "Chunks.opSlice!" ~ dimensionIndex.stringof ~ ": the left opSlice boundary must be less than or equal to the right bound."); 384 enum errorMsg = ": the right opSlice boundary must be less than or equal to the length of the given dimensionIndex."; 385 assert(j <= length!dimensionIndex, 386 "Chunks.opSlice!" ~ dimensionIndex.stringof ~ errorMsg); 387 } 388 do 389 { 390 return typeof(return)(j - i, typeof(return).Iterator(i)); 391 } 392 393 /// ditto 394 ChunksSlice!() opSlice(size_t dimensionIndex)(size_t i, ChunksDollar!() j) const 395 if (dimensionIndex < dimensions.length) 396 in 397 { 398 assert(i <= j, 399 "Chunks.opSlice!" ~ dimensionIndex.stringof ~ ": the left opSlice boundary must be less than or equal to the right bound."); 400 enum errorMsg = ": the right opSlice boundary must be less than or equal to the length of the given dimensionIndex."; 401 assert(j <= length!dimensionIndex, 402 "Chunks.opSlice!" ~ dimensionIndex.stringof ~ errorMsg); 403 } 404 do 405 { 406 return typeof(return)(i, j); 407 } 408 409 /// ditto 410 ChunksDollar!() opDollar(size_t dimensionIndex)() @property 411 { 412 enum dimension = dimensions[dimensionIndex]; 413 return ChunksDollar!()(_slice._lengths[dimension], _chunkLengths[dimensionIndex]); 414 } 415 416 /// ditto 417 auto opIndex(Slices...)(Slices slices) 418 if (Slices.length <= dimensions.length) 419 { 420 static if (slices.length == 0) 421 { 422 return this; 423 } 424 else 425 { 426 alias slice = slices[0]; 427 alias S = Slices[0]; 428 static if (isIndex!S) 429 { 430 auto next = this.select!0(slice); 431 } 432 else 433 static if (is_Slice!S) 434 { 435 auto i = slice._iterator._index; 436 auto j = i + slice._lengths[0]; 437 auto next = this.select!0(i, j); 438 } 439 else 440 { 441 auto next = this.select!0(slice.i, slice.j); 442 } 443 static if (slices.length > 1) 444 { 445 return next[slices[1 .. $]]; 446 } 447 else 448 { 449 return next; 450 } 451 } 452 } 453 454 /// ditto 455 auto opIndex()(size_t[dimensions.length] index) 456 { 457 auto next = this.select!0(index[0]); 458 static if (dimensions.length == 1) 459 { 460 return next; 461 } 462 else 463 { 464 return next[index[1 .. $]]; 465 } 466 } 467 468 /// ditto 469 auto save()() @property 470 { 471 return this; 472 } 473 474 /// 475 auto select(size_t dimensionIndex = 0)(size_t index) @property 476 if (dimensionIndex < dimensions.length) 477 { 478 enum dimension = dimensions[dimensionIndex]; 479 auto chl = _chunkLengths[dimensionIndex]; 480 auto shiftL = chl * index; 481 assert(shiftL <= _slice._lengths[dimension]); 482 auto shiftR = shiftL + chl; 483 if (_expect(shiftR > _slice._lengths[dimension], false)) 484 { 485 shiftR = _slice._lengths[dimension]; 486 } 487 auto ret = _slice.select!dimension(shiftL, shiftR); 488 return _wrap!dimensionIndex(ret); 489 } 490 491 /// ditto 492 auto select(size_t dimensionIndex = 0)(size_t i, size_t j) @property 493 if (dimensionIndex < dimensions.length) 494 { 495 assert(i <= j); 496 enum dimension = dimensions[dimensionIndex]; 497 auto chl = _chunkLengths[dimensionIndex]; 498 auto shiftL = chl * i; 499 auto shiftR = chl * j; 500 assert(shiftL <= _slice._lengths[dimension]); 501 assert(shiftR <= _slice._lengths[dimension]); 502 if (_expect(shiftR > _slice._lengths[dimension], false)) 503 { 504 shiftR = _slice._lengths[dimension]; 505 if (_expect(shiftL > _slice._lengths[dimension], false)) 506 { 507 shiftL = _slice._lengths[dimension]; 508 } 509 } 510 auto ret = _slice.select!dimension(shiftL, shiftR); 511 import std.meta: aliasSeqOf; 512 return ret.chunks!(aliasSeqOf!dimensions)(_chunkLengths); 513 } 514 515 // undocumented 516 auto select(size_t dimensionIndex = 0)(ChunksSlice!() sl) @property 517 if (dimensionIndex < dimensions.length) 518 { 519 assert(sl.i <= _slice._lengths[dimension]); 520 assert(sl.chunkLength == _chunkLengths[dimensionIndex]); 521 assert(sl.length == _slice._lengths[dimension]); 522 523 enum dimension = dimensions[dimensionIndex]; 524 auto chl = _chunkLengths[dimensionIndex]; 525 auto len = sl.i * chl; 526 assert(len <= _slice._lengths[dimension]); 527 if (_expect(len > _slice._lengths[dimension], false)) 528 len = _slice._lengths[dimension]; 529 auto ret = _slice.selectBack!dimension(len); 530 import std.meta: aliasSeqOf; 531 return ret.chunks!(aliasSeqOf!dimensions)(_chunkLengths); 532 } 533 } 534 535 // undocumented 536 struct ChunksSlice() 537 { 538 size_t i; 539 ChunksDollar!() j; 540 } 541 542 // undocumented 543 struct ChunksDollar() 544 { 545 size_t length; 546 size_t chunkLength; 547 size_t value()() @property 548 { 549 return length / chunkLength + (length % chunkLength != 0); 550 } 551 alias value this; 552 } 553 554 /++ 555 Checks if T is $(LREF Chunks) type. 556 Returns: 557 array of dimension indices. 558 +/ 559 template isChunks(T) 560 { 561 static if (is(T : Chunks!(dimensions, Iterator, N, kind), size_t[] dimensions, Iterator, size_t N, SliceKind kind)) 562 enum isChunks = dimensions; 563 else 564 enum isChunks = size_t[].init; 565 } 566 567 /// 568 version(mir_test) unittest 569 { 570 import mir.ndslice.chunks: chunks, isChunks; 571 import mir.ndslice.topology: iota; 572 573 static assert(isChunks!int == null); 574 static assert(isChunks!(typeof(iota(20, 30).chunks!(1, 0)(3, 7))) == [1, 0]); 575 } 576 577 /++ 578 Evaluates `popFront!dimmensionIndex` for multiple $(LREF Chunks) structures at once. 579 All chunks structures must have for the appropriate dimension the same chunk lengths and the same underlying slice lengths. 580 581 Params: 582 dimmensionIndex = dimensionIndex 583 master = the fist chunks structure 584 followers = following chunks structures 585 +/ 586 void popFrontTuple(size_t dimmensionIndex = 0, Master, Followers...)(ref Master master, ref Followers followers) 587 if (isChunks!Master && allSatisfy!(isChunks, Followers)) 588 in 589 { 590 foreach (ref follower; followers) 591 { 592 assert(follower.sliceLength!dimmensionIndex == master.sliceLength!dimmensionIndex); 593 assert(follower._chunkLengths[dimmensionIndex] == master._chunkLengths[dimmensionIndex]); 594 } 595 } 596 do 597 { 598 master._slice.popFrontExactly!(isChunks!Master[dimmensionIndex])(master._chunkLengths[dimmensionIndex]); 599 foreach (i, ref follower; followers) 600 { 601 follower._slice.popFrontExactly!(isChunks!(Followers[i])[dimmensionIndex])(master._chunkLengths[dimmensionIndex]); 602 // hint for optimizer 603 follower.sliceLength!dimmensionIndex = master.sliceLength!dimmensionIndex; 604 } 605 if (_expect(master.sliceLength!dimmensionIndex < master._chunkLengths[dimmensionIndex], false) && master.sliceLength!dimmensionIndex) 606 { 607 master._chunkLengths[dimmensionIndex] = master.sliceLength!dimmensionIndex; 608 foreach(ref follower; followers) 609 { 610 follower._chunkLengths[dimmensionIndex] = master._chunkLengths[dimmensionIndex]; 611 } 612 } 613 } 614 615 /// 616 version(mir_test) unittest 617 { 618 import mir.ndslice.chunks: chunks; 619 import mir.ndslice.topology: iota; 620 621 auto a = iota(10, 20).chunks!(0, 1)(3, 7); 622 auto b = iota(20, 10).chunks!(1, 0)(3, 7); 623 624 auto as = a; 625 auto bs = b; 626 627 as.popFront; 628 bs.popFront; 629 630 popFrontTuple(a, b); 631 632 assert(as.slice == a.slice); 633 assert(bs.slice == b.slice); 634 635 assert(as.chunkLengths == a.chunkLengths); 636 assert(bs.chunkLengths == b.chunkLengths); 637 }