The OpenD Programming Language

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 }