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 }