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 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments 8 Authors: Ilya Yaroshenko 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_test) unittest 41 { 42 import mir.ndslice.fuse; 43 import mir.ndslice.slice : Contiguous, Slice; 44 import mir.ndslice.topology: iota; 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 68 /// Transposed 69 @safe pure version(mir_test) unittest 70 { 71 import mir.ndslice.fuse; 72 import mir.ndslice.topology: iota; 73 import mir.ndslice.dynamic: transposed; 74 import mir.ndslice.slice : Contiguous, Slice; 75 76 enum ror = [ 77 [0, 1, 2, 3], 78 [4, 5, 6, 7], 79 [8, 9,10,11]]; 80 81 // 0 4 8 82 // 1 5 9 83 // 2 6 10 84 // 3 7 11 85 86 // `!1` brings dimensions under index 1 to the front (0 index). 87 auto matrix = ror.fuse!1; 88 89 assert(matrix == [3, 4].iota.transposed!1); 90 // TODO: CTFE 91 // static assert(ror.fuse!1 == [3, 4].iota.transposed!1); // CTFE-able 92 // matrix is contiguos 93 static assert(is(typeof(matrix) == Slice!(int*, 2))); 94 } 95 96 /// 3D 97 @safe pure version(mir_test) unittest 98 { 99 import mir.ndslice.fuse; 100 import mir.ndslice.topology: iota; 101 import mir.ndslice.dynamic: transposed; 102 103 auto ror = 104 [[[ 0, 1, 2, 3], 105 [ 4, 5, 6, 7]], 106 [[ 8, 9,10,11], 107 [12,13,14,15]]]; 108 109 auto nd = [2, 2, 4].iota; 110 111 assert(ror.fuse == nd); 112 assert(ror.fuse!2 == nd.transposed!2); 113 assert(ror.fuse!(1, 2) == nd.transposed!(1, 2)); 114 assert(ror.fuse!(2, 1) == nd.transposed!(2, 1)); 115 } 116 117 /// Work with RC Arrays of RC Arrays 118 @safe pure version(mir_test) unittest 119 { 120 import mir.ndslice.fuse; 121 import mir.ndslice.slice; 122 import mir.ndslice.topology: map; 123 import mir.rc.array; 124 125 Slice!(const(double)*, 2) conv(RCArray!(const RCArray!(const double)) a) 126 { 127 return a[].map!"a[]".fuse; 128 } 129 } 130 131 /++ 132 Fuses ndrange `r` into GC-allocated ($(LREF fuseAs)) or RC-allocated ($(LREF rcfuseAs)) ndslice. 133 Can be used to join rows or columns into a matrix. 134 135 Params: 136 T = output type of ndslice elements 137 Dimensions = (optional) indices of dimensions to be brought to the first position 138 Returns: 139 ndslice 140 +/ 141 alias fuseAs(T, Dimensions...) = fuseImpl!(false, T, Dimensions); 142 /// ditto 143 alias rcfuseAs(T, Dimensions...) = fuseImpl!(true, T, Dimensions); 144 145 /// 146 @safe pure version(mir_test) unittest 147 { 148 import mir.ndslice.fuse; 149 import mir.ndslice.slice : Contiguous, Slice; 150 import mir.ndslice.topology: iota; 151 import mir.rc.array: RCI; 152 153 enum ror = [ 154 [0, 1, 2, 3], 155 [4, 5, 6, 7], 156 [8, 9,10,11]]; 157 158 // 0 1 2 3 159 // 4 5 6 7 160 // 8 9 10 11 161 auto matrix = ror.fuseAs!double; 162 163 auto rcmatrix = ror.rcfuseAs!double; // nogc version 164 165 assert(matrix == [3, 4].iota); 166 assert(rcmatrix == [3, 4].iota); 167 static assert(ror.fuseAs!double == [3, 4].iota); // CTFE-able 168 169 // matrix is contiguos 170 static assert(is(typeof(matrix) == Slice!(double*, 2))); 171 static assert(is(typeof(rcmatrix) == Slice!(RCI!double, 2))); 172 } 173 174 /// 175 template fuseImpl(bool RC, T_, Dimensions...) 176 { 177 import mir.ndslice.internal: isSize_t, toSize_t; 178 static if (allSatisfy!(isSize_t, Dimensions)) 179 /++ 180 Params: 181 r = parallelotope (ndrange) with length/shape and input range primitives. 182 +/ 183 auto fuseImpl(NDRange)(NDRange r) 184 if (hasShape!NDRange) 185 { 186 import mir.conv: emplaceRef; 187 import mir.algorithm.iteration: each; 188 import mir.ndslice.allocation; 189 auto shape = fuseShape(r); 190 static if (is(T_ == void)) 191 alias T = FuseElementType!NDRange; 192 else 193 alias T = T_; 194 alias UT = Unqual!T; 195 static if (RC) 196 { 197 import mir.rc.array: RCI; 198 alias R = Slice!(RCI!T, fuseDimensionCount!NDRange); 199 Slice!(RCI!UT, fuseDimensionCount!NDRange) ret; 200 } 201 else 202 { 203 alias R = Slice!(T*, fuseDimensionCount!NDRange); 204 Slice!(UT*, fuseDimensionCount!NDRange) ret; 205 } 206 static if (Dimensions.length) 207 { 208 import mir.ndslice.topology: iota; 209 import mir.ndslice.dynamic: transposed, completeTranspose; 210 enum perm = completeTranspose!(shape.length)([Dimensions]); 211 size_t[shape.length] shapep; 212 foreach(i; Iota!(shape.length)) 213 shapep[i] = shape[perm[i]]; 214 // enum iperm = perm.length.iota[completeTranspose!(shape.length)([Dimensions])[].sliced].slice; 215 alias InverseDimensions = aliasSeqOf!( 216 (size_t[] perm){ 217 auto ar = new size_t[perm.length]; 218 ar.sliced[perm.sliced] = perm.length.iota; 219 return ar; 220 }(perm) 221 ); 222 static if (RC) 223 { 224 ret = shapep.uninitRcslice!UT; 225 ret.lightScope.transposed!InverseDimensions.each!(emplaceRef!T)(r); 226 } 227 else 228 { 229 if (__ctfe) 230 { 231 ret = shapep.slice!UT; 232 ret.transposed!InverseDimensions.each!"a = b"(r); 233 } 234 else 235 { 236 ret = shapep.uninitSlice!UT; 237 ret.transposed!InverseDimensions.each!(emplaceRef!T)(r); 238 } 239 240 } 241 } 242 else 243 { 244 static if (RC) 245 { 246 ret = shape.uninitRCslice!UT; 247 ret.lightScope.each!(emplaceRef!T)(r); 248 } 249 else 250 { 251 if (__ctfe) 252 { 253 ret = shape.slice!UT; 254 ret.each!"a = b"(r); 255 } 256 else 257 { 258 ret = shape.uninitSlice!UT; 259 ret.each!(emplaceRef!T)(r); 260 } 261 } 262 } 263 static if (RC) 264 { 265 import core.lifetime: move; 266 return move(*(() @trusted => cast(R*)&ret)()); 267 } 268 else 269 { 270 return *(() @trusted => cast(R*)&ret)(); 271 } 272 } 273 else 274 alias fuseImpl = .fuseImpl!(RC, T_, staticMap!(toSize_t, Dimensions)); 275 } 276 277 private template fuseDimensionCount(R) 278 { 279 static if (is(typeof(R.init.shape) : size_t[N], size_t N) && (isDynamicArray!R || __traits(hasMember, R, "front"))) 280 { 281 import mir.ndslice.topology: repeat; 282 enum size_t fuseDimensionCount = N + fuseDimensionCount!(DeepElementType!R); 283 } 284 else 285 enum size_t fuseDimensionCount = 0; 286 } 287 288 private static immutable shapeExceptionMsg = "fuseShape Exception: elements have different shapes/lengths"; 289 290 version(D_Exceptions) 291 static immutable shapeException = new Exception(shapeExceptionMsg); 292 293 /+ 294 TODO docs 295 +/ 296 size_t[fuseDimensionCount!Range] fuseShape(Range)(Range r) 297 if (hasShape!Range) 298 { 299 // auto outerShape = r.shape; 300 enum N = r.shape.length; 301 enum RN = typeof(return).length; 302 enum M = RN - N; 303 static if (M == 0) 304 { 305 return r.shape; 306 } 307 else 308 { 309 import mir.ndslice.topology: repeat; 310 typeof(return) ret; 311 ret[0 .. N] = r.shape; 312 if (!ret[0 .. N].anyEmptyShape) 313 { 314 ret[N .. $] = fuseShape(mixin("r" ~ ".front".repeat(N).fuseCells.field)); 315 import mir.algorithm.iteration: all; 316 if (!all!((a) => cast(size_t[M]) ret[N .. $] == .fuseShape(a))(r)) 317 { 318 version (D_Exceptions) 319 throw shapeException; 320 else 321 assert(0, shapeExceptionMsg); 322 } 323 } 324 return ret; 325 } 326 } 327 328 private template FuseElementType(NDRange) 329 { 330 import mir.ndslice.topology: repeat; 331 alias FuseElementType = typeof(mixin("NDRange.init" ~ ".front".repeat(fuseDimensionCount!NDRange).fuseCells.field)); 332 } 333 334 /++ 335 Fuses `cells` into GC-allocated ndslice. 336 337 Params: 338 cells = ndrange of ndcells, ndrange and ndcell should have `shape` and multidimensional input range primivies (`front!d`, `empty!d`, `popFront!d`). 339 Returns: ndslice composed of fused cells. 340 See_also: $(SUBREF chunks, chunks) 341 +/ 342 auto fuseCells(S)(S cells) 343 { 344 alias T = DeepElementType!(DeepElementType!S); 345 alias UT = Unqual!T; 346 if (__ctfe) 347 { 348 import mir.ndslice.allocation: slice; 349 auto ret = cells.fuseCellsShape.slice!UT; 350 ret.fuseCellsAssign!"a = b" = cells; 351 static if (is(T == immutable)) 352 return (() @trusted => cast(immutable) ret)()[]; 353 else 354 static if (is(T == const)) 355 return (() @trusted => cast(const) ret)()[]; 356 else 357 return ret; 358 } 359 else 360 { 361 import mir.ndslice.allocation: uninitSlice; 362 import mir.conv; 363 auto ret = cells.fuseCellsShape.uninitSlice!UT; 364 ret.fuseCellsAssign!(emplaceRef!T) = cells; 365 alias R = Slice!(T*, ret.N); 366 return R(ret._structure, (() @trusted => cast(T*)ret._iterator)()); 367 } 368 } 369 370 /// 1D 371 @safe pure version(mir_test) unittest 372 { 373 import mir.ndslice.topology: iota; 374 enum ar = [[0, 1], [], [2, 3, 4, 5], [6], [7, 8, 9]]; 375 static assert ([[0, 1], [], [2, 3, 4, 5], [6], [7, 8, 9]].fuseCells == 10.iota); 376 assert (ar.fuseCells == 10.iota); 377 } 378 379 /// 2D 380 @safe pure version(mir_test) unittest 381 { 382 import mir.ndslice.topology: iota; 383 import mir.ndslice.chunks; 384 385 auto sl = iota(11, 17); 386 assert(sl.chunks!(0, 1)(3, 4).fuseCells == sl); 387 } 388 389 /+ 390 TODO docs 391 +/ 392 auto fuseCellsAssign(alias fun = "a = b", Iterator, size_t N, SliceKind kind, S)(Slice!(Iterator, N, kind) to, S cells) 393 { 394 assert(to.shape == cells.fuseCellsShape, "'cells.fuseCellsShape' should be equal to 'to.shape'"); 395 396 if (cells.anyEmpty) 397 goto R; 398 399 import mir.functional: naryFun; 400 import mir.ndslice.topology: canonical; 401 static if (kind == Contiguous) 402 fuseCellsEmplaceImpl!(naryFun!fun, 0, N)(to.canonical, cells); 403 else 404 fuseCellsEmplaceImpl!(naryFun!fun, 0, N)(to, cells); 405 R: return to; 406 } 407 408 /+ 409 TODO docs 410 +/ 411 size_t[S.init.shape.length] fuseCellsShape(S)(S cells) @property 412 { 413 typeof(return) ret; 414 enum N = ret.length; 415 static if (N == 1) 416 { 417 foreach (ref e; cells) 418 ret[0] += e.length; 419 } 420 else 421 { 422 import mir.ndslice.topology: repeat; 423 enum expr = "e" ~ ".front".repeat(N).fuseCells.field; 424 foreach (i; Iota!N) 425 for (auto e = cells.save; !e.empty!i; e.popFront!i) 426 ret[i] += mixin(expr).length!i; 427 } 428 return ret; 429 } 430 431 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) 432 { 433 do 434 { 435 auto from = cells.front; 436 static if (M == 1) 437 { 438 auto n = from.length!i; 439 } 440 else 441 { 442 import mir.ndslice.topology: repeat; 443 enum expr = "from" ~ ".front".repeat(N - 1 - i).fuseCells.field; 444 auto n = mixin(expr).length!i; 445 } 446 assert (to.length!i >= n); 447 static if (i + 1 == M) 448 { 449 import mir.algorithm.iteration: each; 450 each!fun(to.selectFront!i(n), from); 451 } 452 else 453 { 454 .fuseCellsEmplaceImpl!(fun, i + 1, N)(to.selectFront!i(n), from); 455 } 456 to.popFrontExactly!i(n); 457 cells.popFront; 458 } 459 while(!cells.empty); 460 return to; 461 }