1 /++ 2 This is a submodule of $(MREF mir,ndslice). 3 4 Allocation routines that construct ndslices from ndranges. 5 6 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) 7 Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments 8 Authors: Ilia Ki 9 10 See_also: $(SUBMODULE concatenation) submodule. 11 12 Macros: 13 SUBMODULE = $(MREF_ALTTEXT $1, mir, ndslice, $1) 14 SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) 15 T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) 16 +/ 17 module mir.ndslice.fuse; 18 19 import mir.internal.utility; 20 import mir.ndslice.slice; 21 import mir.primitives; 22 import mir.qualifier; 23 import std.meta; 24 import std.traits; 25 26 /++ 27 Fuses ndrange `r` into GC-allocated ($(LREF fuse)) or RC-allocated ($(LREF rcfuse)) ndslice. 28 Can be used to join rows or columns into a matrix. 29 30 Params: 31 Dimensions = (optional) indices of dimensions to be brought to the first position 32 Returns: 33 ndslice 34 +/ 35 alias fuse(Dimensions...) = fuseImpl!(false, void, Dimensions); 36 /// ditto 37 alias rcfuse(Dimensions...) = fuseImpl!(true, void, Dimensions); 38 39 /// 40 @safe pure version(mir_ndslice_test) unittest 41 { 42 import mir.ndslice.fuse; 43 import mir.ndslice.slice : Contiguous, Slice; 44 import mir.ndslice.topology: iota, map, as; 45 import mir.rc.array: RCI; 46 47 enum ror = [ 48 [0, 1, 2, 3], 49 [4, 5, 6, 7], 50 [8, 9,10,11]]; 51 52 // 0 1 2 3 53 // 4 5 6 7 54 // 8 9 10 11 55 auto matrix = ror.fuse; 56 57 auto rcmatrix = ror.rcfuse; // nogc version 58 59 assert(matrix == [3, 4].iota); 60 assert(rcmatrix == [3, 4].iota); 61 static assert(ror.fuse == [3, 4].iota); // CTFE-able 62 63 // matrix is contiguos 64 static assert(is(typeof(matrix) == Slice!(int*, 2))); 65 static assert(is(typeof(rcmatrix) == Slice!(RCI!int, 2))); 66 67 /// also works with strings 68 auto strMatrix = ror.map!(as!string).fuse; 69 static assert(is(typeof(strMatrix) == Slice!(string*, 2))); 70 assert(strMatrix.as!int == matrix); 71 } 72 73 /// Transposed 74 @safe pure version(mir_ndslice_test) unittest 75 { 76 import mir.ndslice.fuse; 77 import mir.ndslice.topology: iota; 78 import mir.ndslice.dynamic: transposed; 79 import mir.ndslice.slice : Contiguous, Slice; 80 81 enum ror = [ 82 [0, 1, 2, 3], 83 [4, 5, 6, 7], 84 [8, 9,10,11]]; 85 86 // 0 4 8 87 // 1 5 9 88 // 2 6 10 89 // 3 7 11 90 91 // `!1` brings dimensions under index 1 to the front (0 index). 92 auto matrix = ror.fuse!1; 93 94 assert(matrix == [3, 4].iota.transposed!1); 95 // TODO: CTFE 96 // static assert(ror.fuse!1 == [3, 4].iota.transposed!1); // CTFE-able 97 // matrix is contiguos 98 static assert(is(typeof(matrix) == Slice!(int*, 2))); 99 } 100 101 /// 3D 102 @safe pure version(mir_ndslice_test) unittest 103 { 104 import mir.ndslice.fuse; 105 import mir.ndslice.topology: iota; 106 import mir.ndslice.dynamic: transposed; 107 108 auto ror = 109 [[[ 0, 1, 2, 3], 110 [ 4, 5, 6, 7]], 111 [[ 8, 9,10,11], 112 [12,13,14,15]]]; 113 114 auto nd = [2, 2, 4].iota; 115 116 assert(ror.fuse == nd); 117 assert(ror.fuse!2 == nd.transposed!2); 118 assert(ror.fuse!(1, 2) == nd.transposed!(1, 2)); 119 assert(ror.fuse!(2, 1) == nd.transposed!(2, 1)); 120 } 121 122 /// Work with RC Arrays of RC Arrays 123 @safe pure version(mir_ndslice_test) unittest 124 { 125 import mir.ndslice.fuse; 126 import mir.ndslice.slice; 127 import mir.ndslice.topology: map; 128 import mir.rc.array; 129 130 Slice!(const(double)*, 2) conv(RCArray!(const RCArray!(const double)) a) 131 { 132 return a[].map!"a[]".fuse; 133 } 134 } 135 136 /++ 137 Fuses ndrange `r` into GC-allocated ($(LREF fuseAs)) or RC-allocated ($(LREF rcfuseAs)) ndslice. 138 Can be used to join rows or columns into a matrix. 139 140 Params: 141 T = output type of ndslice elements 142 Dimensions = (optional) indices of dimensions to be brought to the first position 143 Returns: 144 ndslice 145 +/ 146 alias fuseAs(T, Dimensions...) = fuseImpl!(false, T, Dimensions); 147 /// ditto 148 alias rcfuseAs(T, Dimensions...) = fuseImpl!(true, T, Dimensions); 149 150 /// 151 @safe pure version(mir_ndslice_test) unittest 152 { 153 import mir.ndslice.fuse; 154 import mir.ndslice.slice : Contiguous, Slice; 155 import mir.ndslice.topology: iota; 156 import mir.rc.array: RCI; 157 158 enum ror = [ 159 [0, 1, 2, 3], 160 [4, 5, 6, 7], 161 [8, 9,10,11]]; 162 163 // 0 1 2 3 164 // 4 5 6 7 165 // 8 9 10 11 166 auto matrix = ror.fuseAs!double; 167 168 auto rcmatrix = ror.rcfuseAs!double; // nogc version 169 170 assert(matrix == [3, 4].iota); 171 assert(rcmatrix == [3, 4].iota); 172 static assert(ror.fuseAs!double == [3, 4].iota); // CTFE-able 173 174 // matrix is contiguos 175 static assert(is(typeof(matrix) == Slice!(double*, 2))); 176 static assert(is(typeof(rcmatrix) == Slice!(RCI!double, 2))); 177 } 178 179 /// 180 template fuseImpl(bool RC, T_, Dimensions...) 181 { 182 import mir.ndslice.internal: isSize_t, toSize_t; 183 static if (allSatisfy!(isSize_t, Dimensions)) 184 /++ 185 Params: 186 r = parallelotope (ndrange) with length/shape and input range primitives. 187 +/ 188 auto fuseImpl(NDRange)(NDRange r) 189 if (isFusable!NDRange) 190 { 191 import mir.conv: emplaceRef; 192 import mir.algorithm.iteration: each; 193 import mir.ndslice.allocation; 194 auto shape = fuseShape(r); 195 static if (is(T_ == void)) 196 alias T = FuseElementType!NDRange; 197 else 198 alias T = T_; 199 alias UT = Unqual!T; 200 static if (RC) 201 { 202 import mir.rc.array: RCI; 203 alias R = Slice!(RCI!T, fuseDimensionCount!NDRange); 204 Slice!(RCI!UT, fuseDimensionCount!NDRange) ret; 205 } 206 else 207 { 208 alias R = Slice!(T*, fuseDimensionCount!NDRange); 209 Slice!(UT*, fuseDimensionCount!NDRange) ret; 210 } 211 static if (Dimensions.length) 212 { 213 import mir.ndslice.topology: iota; 214 import mir.ndslice.dynamic: transposed, completeTranspose; 215 enum perm = completeTranspose!(shape.length)([Dimensions]); 216 size_t[shape.length] shapep; 217 foreach(i; Iota!(shape.length)) 218 shapep[i] = shape[perm[i]]; 219 // enum iperm = perm.length.iota[completeTranspose!(shape.length)([Dimensions])[].sliced].slice; 220 alias InverseDimensions = aliasSeqOf!( 221 (size_t[] perm){ 222 auto ar = new size_t[perm.length]; 223 ar.sliced[perm.sliced] = perm.length.iota; 224 return ar; 225 }(perm) 226 ); 227 static if (RC) 228 { 229 ret = shapep.uninitRcslice!UT; 230 ret.lightScope.transposed!InverseDimensions.each!(emplaceRef!T)(r); 231 } 232 else 233 { 234 if (__ctfe) 235 { 236 ret = shapep.slice!UT; 237 ret.transposed!InverseDimensions.each!"a = b"(r); 238 } 239 else () @trusted 240 { 241 ret = shapep.uninitSlice!UT; 242 ret.transposed!InverseDimensions.each!(emplaceRef!T)(r); 243 } (); 244 245 } 246 } 247 else 248 { 249 static if (RC) 250 { 251 ret = shape.uninitRCslice!UT; 252 ret.lightScope.each!(emplaceRef!T)(r); 253 } 254 else 255 { 256 if (__ctfe) 257 { 258 ret = shape.slice!UT; 259 ret.each!"a = b"(r); 260 } 261 else () @trusted 262 { 263 ret = shape.uninitSlice!UT; 264 ret.each!(emplaceRef!T)(r); 265 } (); 266 } 267 } 268 static if (RC) 269 { 270 import core.lifetime: move; 271 return move(*(() @trusted => cast(R*)&ret)()); 272 } 273 else 274 { 275 return *(() @trusted => cast(R*)&ret)(); 276 } 277 } 278 else 279 alias fuseImpl = .fuseImpl!(RC, T_, staticMap!(toSize_t, Dimensions)); 280 } 281 282 private template fuseDimensionCount(R) 283 { 284 static if (!isFusable!R) 285 enum size_t fuseDimensionCount = 0; 286 else 287 static if (is(typeof(R.init.shape) : size_t[N], size_t N) && (isDynamicArray!R || __traits(hasMember, R, "front"))) 288 { 289 static if (N == 1) 290 static if (isSomeChar!(typeof(R.init.front))) 291 enum size_t fuseDimensionCount = 0; 292 else 293 enum size_t fuseDimensionCount = N + fuseDimensionCount!(DeepElementType!R); 294 else 295 enum size_t fuseDimensionCount = N + fuseDimensionCount!(DeepElementType!R); 296 } 297 else 298 enum size_t fuseDimensionCount = 0; 299 } 300 301 /// 302 @safe pure version(mir_ndslice_test) unittest 303 { 304 import mir.ndslice; 305 306 auto m = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].sliced(2, 3, 2); 307 static assert(fuseDimensionCount!(typeof(m)) == 3); 308 309 auto p = m.pack!1; 310 static assert(fuseDimensionCount!(typeof(p)) == 3); 311 312 auto q = m.pack!2; 313 static assert(fuseDimensionCount!(typeof(q)) == 3); 314 } 315 316 private static immutable shapeExceptionMsg = "fuseShape Exception: elements have different shapes/lengths"; 317 318 version(D_Exceptions) 319 static immutable shapeException = new Exception(shapeExceptionMsg); 320 321 private template isFusable(Range) 322 { 323 static if (hasShape!Range) 324 { 325 enum isFusable = !isSomeChar!(typeof(Range.init.front)); 326 } 327 else 328 { 329 enum isFusable = false; 330 } 331 } 332 333 /+ 334 TODO docs 335 +/ 336 size_t[fuseDimensionCount!Range] fuseShape(Range)(Range r) 337 if (isFusable!Range) 338 { 339 enum N = r.shape.length; 340 enum RN = typeof(return).length; 341 static if (RN == N) 342 { 343 return r.shape; 344 } 345 else 346 { 347 typeof(return) ret; 348 ret[0 .. N] = r.shape; 349 if (!ret[0 .. N].anyEmptyShape) 350 { 351 static if (isSlice!Range) 352 { 353 ret[N .. $] = fuseShape(r.first); 354 } 355 else static if (isArray!Range) 356 { 357 bool next; 358 foreach (ref elem; r) 359 { 360 const elemShape = fuseShape(elem); 361 if (next) 362 { 363 if (elemShape != ret[N .. $]) 364 { 365 version (D_Exceptions) 366 { import mir.exception : toMutable; throw shapeException.toMutable; } 367 else 368 assert(0, shapeExceptionMsg); 369 } 370 } 371 else 372 { 373 ret[N .. $] = elemShape; 374 next = true; 375 } 376 } 377 } 378 else 379 static assert(false); 380 } 381 return ret; 382 } 383 } 384 385 /// basic 386 @safe pure version(mir_ndslice_test) unittest 387 { 388 import mir.ndslice; 389 390 auto m = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].sliced(2, 3, 2); 391 auto s = fuseShape(m); 392 assert(s == [2, 3, 2]); 393 } 394 395 /// pack!1 396 @safe pure version(mir_ndslice_test) unittest 397 { 398 import mir.ndslice; 399 400 auto m = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].sliced(2, 3, 2); 401 auto p = m.pack!1; 402 auto s = fuseShape(p); 403 assert(s == [2, 3, 2]); 404 } 405 406 /// pack!2 407 @safe pure version(mir_ndslice_test) unittest 408 { 409 import mir.ndslice; 410 411 auto m = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12].sliced(2, 3, 2); 412 auto p = m.pack!2; 413 auto s = fuseShape(p); 414 assert(s == [2, 3, 2]); 415 } 416 417 private template FuseElementType(NDRange) 418 if (isFusable!NDRange) 419 { 420 static assert (fuseDimensionCount!NDRange); 421 static if (fuseDimensionCount!NDRange == 1) 422 alias FuseElementType = typeof(NDRange.init.front); 423 else 424 static if (isFusable!(typeof(NDRange.init.front))) 425 alias FuseElementType = FuseElementType!(typeof(NDRange.init.front)); 426 else 427 alias FuseElementType = typeof(NDRange.init.front); 428 } 429 430 /++ 431 Fuses `cells` into GC-allocated ndslice. 432 433 Params: 434 cells = ndrange of ndcells, ndrange and ndcell should have `shape` and multidimensional input range primivies (`front!d`, `empty!d`, `popFront!d`). 435 Returns: ndslice composed of fused cells. 436 See_also: $(SUBREF chunks, chunks) 437 +/ 438 auto fuseCells(S)(S cells) 439 { 440 alias T = DeepElementType!(DeepElementType!S); 441 alias UT = Unqual!T; 442 if (__ctfe) 443 { 444 import mir.ndslice.allocation: slice; 445 auto ret = cells.fuseCellsShape.slice!UT; 446 ret.fuseCellsAssign!"a = b" = cells; 447 static if (is(T == immutable)) 448 return (() @trusted => cast(immutable) ret)()[]; 449 else 450 static if (is(T == const)) 451 return (() @trusted => cast(const) ret)()[]; 452 else 453 return ret; 454 } 455 else return () @trusted 456 { 457 import mir.ndslice.allocation: uninitSlice; 458 import mir.conv; 459 auto ret = cells.fuseCellsShape.uninitSlice!UT; 460 ret.fuseCellsAssign!(emplaceRef!T) = cells; 461 alias R = Slice!(T*, ret.N); 462 return R(ret._structure, cast(T*)ret._iterator); 463 } (); 464 } 465 466 /// 1D 467 @safe pure version(mir_ndslice_test) unittest 468 { 469 import mir.ndslice.topology: iota; 470 enum ar = [[0, 1], [], [2, 3, 4, 5], [6], [7, 8, 9]]; 471 static assert ([[0, 1], [], [2, 3, 4, 5], [6], [7, 8, 9]].fuseCells == 10.iota); 472 assert (ar.fuseCells == 10.iota); 473 } 474 475 /// 2D 476 @safe pure version(mir_ndslice_test) unittest 477 { 478 import mir.ndslice.topology: iota; 479 import mir.ndslice.chunks; 480 481 auto sl = iota(11, 17); 482 assert(sl.chunks!(0, 1)(3, 4).fuseCells == sl); 483 } 484 485 /+ 486 TODO docs 487 +/ 488 auto fuseCellsAssign(alias fun = "a = b", Iterator, size_t N, SliceKind kind, S)(Slice!(Iterator, N, kind) to, S cells) 489 { 490 assert(to.shape == cells.fuseCellsShape, "'cells.fuseCellsShape' should be equal to 'to.shape'"); 491 492 if (cells.anyEmpty) 493 goto R; 494 495 import mir.functional: naryFun; 496 import mir.ndslice.topology: canonical; 497 static if (kind == Contiguous) 498 fuseCellsEmplaceImpl!(naryFun!fun, 0, N)(to.canonical, cells); 499 else 500 fuseCellsEmplaceImpl!(naryFun!fun, 0, N)(to, cells); 501 R: return to; 502 } 503 504 /+ 505 TODO docs 506 +/ 507 size_t[S.init.shape.length] fuseCellsShape(S)(S cells) @property 508 { 509 typeof(return) ret; 510 enum N = ret.length; 511 static if (N == 1) 512 { 513 foreach (ref e; cells) 514 ret[0] += e.length; 515 } 516 else 517 { 518 import mir.ndslice.topology: repeat; 519 enum expr = "e" ~ ".front".repeat(N).fuseCells.field; 520 foreach (i; Iota!N) 521 for (auto e = cells.save; !e.empty!i; e.popFront!i) 522 ret[i] += mixin(expr).length!i; 523 } 524 return ret; 525 } 526 527 private auto fuseCellsEmplaceImpl(alias fun, size_t i, size_t M, Iterator, size_t N, SliceKind kind, S)(Slice!(Iterator, N, kind) to, S cells) 528 { 529 do 530 { 531 auto from = cells.front; 532 static if (M == 1) 533 { 534 auto n = from.length!i; 535 } 536 else 537 { 538 import mir.ndslice.topology: repeat; 539 enum expr = "from" ~ ".front".repeat(N - 1 - i).fuseCells.field; 540 auto n = mixin(expr).length!i; 541 } 542 assert (to.length!i >= n); 543 static if (i + 1 == M) 544 { 545 import mir.algorithm.iteration: each; 546 each!fun(to.selectFront!i(n), from); 547 } 548 else 549 { 550 .fuseCellsEmplaceImpl!(fun, i + 1, N)(to.selectFront!i(n), from); 551 } 552 to.popFrontExactly!i(n); 553 cells.popFront; 554 } 555 while(!cells.empty); 556 return to; 557 }