The OpenD Programming Language

1 /+
2 ## Guide for Slice/BLAS contributors
3 
4 1. Make sure functions are
5        a. inlined(!),
6        b. `@nogc`,
7        c. `nothrow`,
8        d. `pure`.
9     For this reason, it is preferable to use _simple_ `assert`s with messages
10     that can be computed at compile time.
11     The goals are:
12         1. to reduce executable size for _any_ compilation mode
13         2. to reduce template bloat in object files
14         3. to reduce compilation time
15         4. to allow users to write extern C bindings for code libraries on `Slice` type.
16 
17 2. `std.format`, `std.string`, and `std.conv` should not be used in error
18     message formatting.`"Use" ~ Concatenation.stringof`.
19 
20 3. `mixin template`s may be used for pretty error message formatting.
21 
22 4. `Exception`s/`enforce`s should no be used to check indices and lengths.
23     Exceptions are only allowed for algorithms where validation of input data is
24     too complicated for the user. `reshape` function is a good example of a case
25     where Exceptions are required.
26     If a function might throw an exception, an example with exception handing should be added.
27 
28 5.  For simple checks like matrix transposition, compile time flags should not be used.
29     It is much better to opt for runtime matrix transposition.
30     Furthermore, Slice type provides runtime matrix transposition out of the box.
31 
32 6.  _Fortran_VS_C_ flags should not be used. They are about notation,
33     but not about the algorithm itself. For math world users,
34     a corresponding code example might be included in the documentation.
35     `transposed` / `everted` can be used in cache-friendly codes.
36 
37 7. Compile time evaluation should not be used to produce dummy types like `IdentityMatrix`.
38 
39 8. Memory allocation and algorithm logic should be separated whenever possible.
40 
41 9. CTFE version(mir_ndslice_test) unittests should be added to new functions.
42 +/
43 
44 /**
45 $(H1 Multidimensional Random Access Ranges)
46 
47 The package provides a multidimensional array implementation.
48 It would be well suited to creating machine learning and image
49 processing algorithms, but should also be general enough for use anywhere with
50 homogeneously-typed multidimensional data.
51 In addition, it includes various functions for iteration, accessing, and manipulation.
52 
53 Quick_Start:
54 $(SUBREF slice, sliced) is a function designed to create
55 a multidimensional view over a range.
56 Multidimensional view is presented by $(SUBREF slice, Slice) type.
57 
58 ------
59 import mir.ndslice;
60 
61 auto matrix = slice!double(3, 4);
62 matrix[] = 0;
63 matrix.diagonal[] = 1;
64 
65 auto row = matrix[2];
66 row[3] = 6;
67 assert(matrix[2, 3] == 6); // D & C index order
68 ------
69 
70 Note:
71 In many examples $(REF iota, mir,_ndslice,topology) is used
72 instead of a regular array, which makes it
73 possible to carry out tests without memory allocation.
74 
75 $(SCRIPT inhibitQuickIndex = 1;)
76 
77 $(DIVC quickindex,
78 $(BOOKTABLE,
79 
80 $(TR $(TH Submodule) $(TH Declarations))
81 
82 $(TR $(TDNW $(SUBMODULE slice) $(BR)
83         $(SMALL $(SUBREF slice, Slice) structure
84             $(BR) Basic constructors))
85      $(TD
86         $(SUBREF slice, Canonical)
87         $(SUBREF slice, Contiguous)
88         $(SUBREF slice, DeepElementType)
89         $(SUBREF slice, isSlice)
90         $(SUBREF slice, kindOf)
91         $(SUBREF slice, Slice)
92         $(SUBREF slice, sliced)
93         $(SUBREF slice, slicedField)
94         $(SUBREF slice, slicedNdField)
95         $(SUBREF slice, SliceKind)
96         $(SUBREF slice, Structure)
97         $(SUBREF slice, Universal)
98     )
99 )
100 
101 $(TR $(TDNW $(SUBMODULE allocation) $(BR)
102         $(SMALL Allocation utilities))
103      $(TD
104         $(SUBREF allocation, bitRcslice)
105         $(SUBREF allocation, bitSlice)
106         $(SUBREF allocation, makeNdarray)
107         $(SUBREF allocation, makeSlice)
108         $(SUBREF allocation, makeUninitSlice)
109         $(SUBREF allocation, mininitRcslice)
110         $(SUBREF allocation, ndarray)
111         $(SUBREF allocation, rcslice)
112         $(SUBREF allocation, shape)
113         $(SUBREF allocation, slice)
114         $(SUBREF allocation, stdcFreeAlignedSlice)
115         $(SUBREF allocation, stdcFreeSlice)
116         $(SUBREF allocation, stdcSlice)
117         $(SUBREF allocation, stdcUninitAlignedSlice)
118         $(SUBREF allocation, stdcUninitSlice)
119         $(SUBREF allocation, uninitAlignedSlice)
120         $(SUBREF allocation, uninitSlice)
121     )
122 )
123 
124 $(TR $(TDNW $(SUBMODULE topology) $(BR)
125         $(SMALL Subspace manipulations
126             $(BR) Advanced constructors
127             $(BR) SliceKind conversion utilities))
128      $(TD
129         $(SUBREF topology, alongDim)
130         $(SUBREF topology, as)
131         $(SUBREF topology, asKindOf)
132         $(SUBREF topology, assumeCanonical)
133         $(SUBREF topology, assumeContiguous)
134         $(SUBREF topology, assumeHypercube)
135         $(SUBREF topology, assumeSameShape)
136         $(SUBREF topology, bitpack)
137         $(SUBREF topology, bitwise)
138         $(SUBREF topology, blocks)
139         $(SUBREF topology, byDim)
140         $(SUBREF topology, bytegroup)
141         $(SUBREF topology, cached)
142         $(SUBREF topology, cachedGC)
143         $(SUBREF topology, canonical)
144         $(SUBREF topology, cartesian)
145         $(SUBREF topology, chopped)
146         $(SUBREF topology, cycle)
147         $(SUBREF topology, diagonal)
148         $(SUBREF topology, diff)
149         $(SUBREF topology, dropBorders)
150         $(SUBREF topology, evertPack)
151         $(SUBREF topology, flattened)
152         $(SUBREF topology, indexed)
153         $(SUBREF topology, iota)
154         $(SUBREF topology, ipack)
155         $(SUBREF topology, kronecker)
156         $(SUBREF topology, linspace)
157         $(SUBREF topology, magic)
158         $(SUBREF topology, map)
159         $(SUBREF topology, member)
160         $(SUBREF topology, ndiota)
161         $(SUBREF topology, orthogonalReduceField)
162         $(SUBREF topology, pack)
163         $(SUBREF topology, pairwise)
164         $(SUBREF topology, repeat)
165         $(SUBREF topology, reshape)
166         $(SUBREF topology, ReshapeError)
167         $(SUBREF topology, retro)
168         $(SUBREF topology, slide)
169         $(SUBREF topology, slideAlong)
170         $(SUBREF topology, squeeze)
171         $(SUBREF topology, stairs)
172         $(SUBREF topology, stride)
173         $(SUBREF topology, subSlices)
174         $(SUBREF topology, triplets)
175         $(SUBREF topology, universal)
176         $(SUBREF topology, unsqueeze)
177         $(SUBREF topology, unzip)
178         $(SUBREF topology, vmap)
179         $(SUBREF topology, windows)
180         $(SUBREF topology, zip)
181     )
182 )
183 
184 $(TR $(TDNW $(SUBMODULE filling) $(BR)
185     $(SMALL Specialized initialisation routines))
186      $(TD
187         $(SUBREF filling, fillVandermonde)
188     )
189 )
190 
191 $(TR $(TDNW $(SUBMODULE fuse) $(BR)
192     $(SMALL Data fusing (stacking)
193         $(BR) See also $(SUBMODULE concatenation) submodule.
194     ))
195      $(TD
196         $(SUBREF fuse, fuse)
197         $(SUBREF fuse, fuseAs)
198         $(SUBREF fuse, rcfuse)
199         $(SUBREF fuse, rcfuseAs)
200         $(SUBREF fuse, fuseCells)
201     )
202 )
203 
204 $(TR $(TDNW $(SUBMODULE concatenation) $(BR)
205     $(SMALL Concatenation, padding, and algorithms
206         $(BR) See also $(SUBMODULE fuse) submodule.
207     ))
208      $(TD
209         $(SUBREF concatenation, forEachFragment)
210         $(SUBREF concatenation, isConcatenation)
211         $(SUBREF concatenation, pad)
212         $(SUBREF concatenation, padEdge)
213         $(SUBREF concatenation, padWrap)
214         $(SUBREF concatenation, padSymmetric)
215         $(SUBREF concatenation, concatenation)
216         $(SUBREF concatenation, Concatenation)
217         $(SUBREF concatenation, concatenationDimension)
218         $(SUBREF concatenation, until)
219     )
220 )
221 
222 $(TR $(TDNW $(SUBMODULE dynamic)
223         $(BR) $(SMALL Dynamic dimension manipulators))
224      $(TD
225         $(SUBREF dynamic, allReversed)
226         $(SUBREF dynamic, dropToHypercube)
227         $(SUBREF dynamic, everted)
228         $(SUBREF dynamic, normalizeStructure)
229         $(SUBREF dynamic, reversed)
230         $(SUBREF dynamic, rotated)
231         $(SUBREF dynamic, strided)
232         $(SUBREF dynamic, swapped)
233         $(SUBREF dynamic, transposed)
234     )
235 )
236 
237 $(TR $(TDNW $(SUBMODULE sorting)
238         $(BR) $(SMALL Sorting utilities))
239      $(TD
240         $(SUBREF sorting, sort)
241         Examples for `isSorted`, `isStrictlyMonotonic`, `makeIndex`, and `schwartzSort`.
242     )
243 )
244 
245 $(TR $(TDNW $(SUBMODULE mutation)
246         $(BR) $(SMALL Mutation utilities))
247      $(TD
248         $(SUBREF mutation, copyMinor)
249         $(SUBREF mutation, reverseInPlace)
250     )
251 )
252 
253 $(TR $(TDNW $(SUBMODULE iterator)
254         $(BR) $(SMALL Declarations))
255      $(TD
256         $(SUBREF iterator, BytegroupIterator)
257         $(SUBREF iterator, CachedIterator)
258         $(SUBREF iterator, ChopIterator)
259         $(SUBREF iterator, FieldIterator)
260         $(SUBREF iterator, FlattenedIterator)
261         $(SUBREF iterator, IndexIterator)
262         $(SUBREF iterator, IotaIterator)
263         $(SUBREF iterator, MapIterator)
264         $(SUBREF iterator, MemberIterator)
265         $(SUBREF iterator, RetroIterator)
266         $(SUBREF iterator, SliceIterator)
267         $(SUBREF iterator, SlideIterator)
268         $(SUBREF iterator, StairsIterator)
269         $(SUBREF iterator, StrideIterator)
270         $(SUBREF iterator, SubSliceIterator)
271         $(SUBREF iterator, Triplet)
272         $(SUBREF iterator, TripletIterator)
273         $(SUBREF iterator, ZipIterator)
274     )
275 )
276 
277 $(TR $(TDNW $(SUBMODULE field)
278         $(BR) $(SMALL Declarations))
279      $(TD
280         $(SUBREF field, BitField)
281         $(SUBREF field, BitpackField)
282         $(SUBREF field, CycleField)
283         $(SUBREF field, LinspaceField)
284         $(SUBREF field, MagicField)
285         $(SUBREF field, MapField)
286         $(SUBREF field, ndIotaField)
287         $(SUBREF field, OrthogonalReduceField)
288         $(SUBREF field, RepeatField)
289     )
290 )
291 
292 $(TR $(TDNW $(SUBMODULE ndfield)
293         $(BR) $(SMALL Declarations))
294      $(TD
295         $(SUBREF ndfield, Cartesian)
296         $(SUBREF ndfield, Kronecker)
297     )
298 )
299 
300 $(TR $(TDNW $(SUBMODULE chunks)
301         $(BR) $(SMALL Declarations))
302      $(TD
303         $(SUBREF field, chunks)
304         $(SUBREF field, Chunks)
305         $(SUBREF field, isChunks)
306         $(SUBREF field, popFrontTuple)
307     )
308 )
309 
310 $(TR $(TDNW $(SUBMODULE traits)
311         $(BR) $(SMALL Declarations))
312      $(TD
313         $(SUBREF traits, isIterator)
314         $(SUBREF traits, isVector)
315         $(SUBREF traits, isMatrix)
316         $(SUBREF traits, isContiguousSlice)
317         $(SUBREF traits, isCanonicalSlice)
318         $(SUBREF traits, isUniversalSlice)
319         $(SUBREF traits, isContiguousVector)
320         $(SUBREF traits, isUniversalVector)
321         $(SUBREF traits, isContiguousMatrix)
322         $(SUBREF traits, isCanonicalMatrix)
323         $(SUBREF traits, isUniversalMatrix)
324     )
325 )
326 
327 ))
328 
329 $(H2 Example: Image Processing)
330 
331 A median filter is implemented as an example. The function
332 `movingWindowByChannel` can also be used with other filters that use a sliding
333 window as the argument, in particular with convolution matrices such as the
334 $(LINK2 https://en.wikipedia.org/wiki/Sobel_operator, Sobel operator).
335 
336 `movingWindowByChannel` iterates over an image in sliding window mode.
337 Each window is transferred to a `filter`, which calculates the value of the
338 pixel that corresponds to the given window.
339 
340 This function does not calculate border cases in which a window overlaps
341 the image partially. However, the function can still be used to carry out such
342 calculations. That can be done by creating an amplified image, with the edges
343 reflected from the original image, and then applying the given function to the
344 new file.
345 
346 Note: You can find the example at
347 $(LINK2 https://github.com/libmir/mir/blob/master/examples/median_filter.d, GitHub).
348 
349 -------
350 /++
351 Params:
352     filter = unary function. Dimension window 2D is the argument.
353     image = image dimensions `(h, w, c)`,
354         where с is the number of channels in the image
355     nr = number of rows in the window
356     nс = number of columns in the window
357 
358 Returns:
359     image dimensions `(h - nr + 1, w - nc + 1, c)`,
360         where с is the number of channels in the image.
361         Dense data layout is guaranteed.
362 +/
363 Slice!(ubyte*, 3) movingWindowByChannel
364 (Slice!(Universal, [3], ubyte*) image, size_t nr, size_t nc, ubyte delegate(Slice!(Universal, [2], ubyte*)) filter)
365 {
366         // 0. 3D
367         // The last dimension represents the color channel.
368     return image
369         // 1. 2D composed of 1D
370         // Packs the last dimension.
371         .pack!1
372         // 2. 2D composed of 2D composed of 1D
373         // Splits image into overlapping windows.
374         .windows(nr, nc)
375         // 3. 5D
376         // Unpacks the windows.
377         .unpack
378         .transposed!(0, 1, 4)
379         // 4. 5D
380         // Brings the color channel dimension to the third position.
381         .pack!2
382         // 2D to pixel lazy conversion.
383         .map!filter
384         // Creates the new image. The only memory allocation in this function.
385         .slice;
386 }
387 -------
388 
389 A function that calculates the value of iterator median is also necessary.
390 
391 -------
392 /++
393 
394 Params:
395     r = input range
396     buf = buffer with length no less than the number of elements in `r`
397 Returns:
398     median value over the range `r`
399 +/
400 T median(Range, T)(Slice!(Universal, [2], Range) sl, T[] buf)
401 {
402     import std.algorithm.sorting : topN;
403     // copy sl to the buffer
404     auto retPtr = reduce!(
405         (ptr, elem) { *ptr = elem; return ptr + 1;} )(buf.ptr, sl);
406     auto n = retPtr - buf.ptr;
407     buf[0 .. n].topN(n / 2);
408     return buf[n / 2];
409 }
410 -------
411 
412 The `main` function:
413 
414 -------
415 void main(string[] args)
416 {
417     import std.conv : to;
418     import std.getopt : getopt, defaultGetoptPrinter;
419     import std.path : stripExtension;
420 
421     uint nr, nc, def = 3;
422     auto helpInformation = args.getopt(
423         "nr", "number of rows in window, default value is " ~ def.to!string, &nr,
424         "nc", "number of columns in window, default value is equal to nr", &nc);
425     if (helpInformation.helpWanted)
426     {
427         defaultGetoptPrinter(
428             "Usage: median-filter [<options...>] [<file_names...>]\noptions:",
429             helpInformation.options);
430         return;
431     }
432     if (!nr) nr = def;
433     if (!nc) nc = nr;
434 
435     auto buf = new ubyte[nr * nc];
436 
437     foreach (name; args[1 .. $])
438     {
439         import imageformats; // can be found at code.dlang.org
440 
441         IFImage image = read_image(name);
442 
443         auto ret = image.pixels
444             .sliced(cast(size_t)image.h, cast(size_t)image.w, cast(size_t)image.c)
445             .movingWindowByChannel
446                 !(window => median(window, buf))
447                  (nr, nc);
448 
449         write_image(
450             name.stripExtension ~ "_filtered.png",
451             ret.length!1,
452             ret.length!0,
453             (&ret[0, 0, 0])[0 .. ret.elementCount]);
454     }
455 }
456 -------
457 
458 This program works both with color and grayscale images.
459 
460 -------
461 $ median-filter --help
462 Usage: median-filter [<options...>] [<file_names...>]
463 options:
464      --nr number of rows in window, default value is 3
465      --nc number of columns in window default value equals to nr
466 -h --help This help information.
467 -------
468 
469 $(H2 Compared with `numpy.ndarray`)
470 
471 numpy is undoubtedly one of the most effective software packages that has
472 facilitated the work of many engineers and scientists. However, due to the
473 specifics of implementation of Python, a programmer who wishes to use the
474 functions not represented in numpy may find that the built-in functions
475 implemented specifically for numpy are not enough, and their Python
476 implementations work at a very low speed. Extending numpy can be done, but
477 is somewhat laborious as even the most basic numpy functions that refer
478 directly to `ndarray` data must be implemented in C for reasonable performance.
479 
480 At the same time, while working with `ndslice`, an engineer has access to the
481 whole set of standard D library, so the functions he creates will be as
482 efficient as if they were written in C.
483 
484 
485 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
486 Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments
487 Authors: Ilia Ki
488 Acknowledgements: John Loughran Colvin
489 
490 Macros:
491 SUBMODULE = $(MREF_ALTTEXT $1, mir, ndslice, $1)
492 SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP)
493 T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
494 TDNW2 = <td class="donthyphenate nobr" rowspan="2">$0</td>
495 */
496 module mir.ndslice;
497 
498 public import mir.algorithm.iteration;
499 public import mir.ndslice.allocation;
500 public import mir.ndslice.chunks;
501 public import mir.ndslice.concatenation;
502 public import mir.ndslice.dynamic;
503 public import mir.ndslice.field;
504 public import mir.ndslice.filling;
505 public import mir.ndslice.fuse;
506 public import mir.ndslice.iterator;
507 public import mir.ndslice.mutation;
508 public import mir.ndslice.ndfield;
509 public import mir.ndslice.slice;
510 public import mir.ndslice.topology;
511 public import mir.ndslice.traits;
512 
513 
514 version(mir_ndslice_test) unittest
515 {
516     auto matrix = new double[12].sliced(3, 4);
517     matrix[] = 0;
518     matrix.diagonal[] = 1;
519 
520     auto row = matrix[2];
521     row[3] = 6;
522     assert(matrix[2, 3] == 6); // D & C index order
523     //assert(matrix(3, 2) == 6); // Math & Fortran index order
524 }
525 
526 // relaxed example
527 version(mir_ndslice_test) unittest
528 {
529     import mir.qualifier;
530 
531     static Slice!(ubyte*, 3) movingWindowByChannel
532     (Slice!(ubyte*, 3, Universal) image, size_t nr, size_t nc, ubyte delegate(LightConstOf!(Slice!(ubyte*, 2, Universal))) filter)
533     {
534         return image
535             .pack!1
536             .windows(nr, nc)
537             .unpack
538             .unpack
539             .transposed!(0, 1, 4)
540             .pack!2
541             .map!filter
542             .slice;
543     }
544 
545     static T median(Iterator, T)(Slice!(Iterator,  2, Universal) sl, T[] buf)
546     {
547         import std.algorithm.sorting : topN;
548         // copy sl to the buffer
549         auto retPtr = reduce!(
550             (ptr, elem) {
551                 *ptr = elem;
552                 return ptr + 1;
553             } )(buf.ptr, sl);
554         auto n = retPtr - buf.ptr;
555         buf[0 .. n].topN(n / 2);
556         return buf[n / 2];
557     }
558 
559     import std.conv : to;
560     import std.getopt : getopt, defaultGetoptPrinter;
561     import std.path : stripExtension;
562 
563     auto args = ["std"];
564     uint nr, nc, def = 3;
565     auto helpInformation = args.getopt(
566         "nr", "number of rows in window, default value is " ~ def.to!string, &nr,
567         "nc", "number of columns in window default value equals to nr", &nc);
568     if (helpInformation.helpWanted)
569     {
570         defaultGetoptPrinter(
571             "Usage: median-filter [<options...>] [<file_names...>]\noptions:",
572             helpInformation.options);
573         return;
574     }
575     if (!nr) nr = def;
576     if (!nc) nc = nr;
577 
578     auto buf = new ubyte[nr * nc];
579 
580     foreach (name; args[1 .. $])
581     {
582         auto ret =
583             movingWindowByChannel
584                  (new ubyte[300].sliced(10, 10, 3).universal, nr, nc, window => median(window, buf));
585     }
586 }
587 
588 @safe @nogc pure nothrow version(mir_ndslice_test) unittest
589 {
590     immutable r = 1000.iota;
591 
592     auto t0 = r.sliced(1000);
593     assert(t0.front == 0);
594     assert(t0.back == 999);
595     assert(t0[9] == 9);
596 
597     auto t1 = t0[10 .. 20];
598     assert(t1.front == 10);
599     assert(t1.back == 19);
600     assert(t1[9] == 19);
601 
602     t1.popFront();
603     assert(t1.front == 11);
604     t1.popFront();
605     assert(t1.front == 12);
606 
607     t1.popBack();
608     assert(t1.back == 18);
609     t1.popBack();
610     assert(t1.back == 17);
611 
612     assert(t1 == iota([6], 12));
613 }
614 
615 pure nothrow version(mir_ndslice_test) unittest
616 {
617     import mir.ndslice.topology : iota;
618     import mir.array.allocation : array;
619     auto r = 1000.iota.array;
620 
621     auto t0 = r.sliced(1000);
622     assert(t0.length == 1000);
623     assert(t0.front == 0);
624     assert(t0.back == 999);
625     assert(t0[9] == 9);
626 
627     auto t1 = t0[10 .. 20];
628     assert(t1.front == 10);
629     assert(t1.back == 19);
630     assert(t1[9] == 19);
631 
632     t1.popFront();
633     assert(t1.front == 11);
634     t1.popFront();
635     assert(t1.front == 12);
636 
637     t1.popBack();
638     assert(t1.back == 18);
639     t1.popBack();
640     assert(t1.back == 17);
641 
642     assert(t1 == iota([6], 12));
643 
644     t1.front = 13;
645     assert(t1.front == 13);
646     t1.front++;
647     assert(t1.front == 14);
648     t1.front += 2;
649     assert(t1.front == 16);
650     t1.front = 12;
651     assert((t1.front = 12) == 12);
652 
653     t1.back = 13;
654     assert(t1.back == 13);
655     t1.back++;
656     assert(t1.back == 14);
657     t1.back += 2;
658     assert(t1.back == 16);
659     t1.back = 12;
660     assert((t1.back = 12) == 12);
661 
662     t1[3] = 13;
663     assert(t1[3] == 13);
664     t1[3]++;
665     assert(t1[3] == 14);
666     t1[3] += 2;
667     assert(t1[3] == 16);
668     t1[3] = 12;
669     assert((t1[3] = 12) == 12);
670 
671     t1[3 .. 5] = 100;
672     assert(t1[2] != 100);
673     assert(t1[3] == 100);
674     assert(t1[4] == 100);
675     assert(t1[5] != 100);
676 
677     t1[3 .. 5] += 100;
678     assert(t1[2] <  100);
679     assert(t1[3] == 200);
680     assert(t1[4] == 200);
681     assert(t1[5] <  100);
682 
683     --t1[3 .. 5];
684 
685     assert(t1[2] <  100);
686     assert(t1[3] == 199);
687     assert(t1[4] == 199);
688     assert(t1[5] <  100);
689 
690     --t1[];
691     assert(t1[3] == 198);
692     assert(t1[4] == 198);
693 
694     t1[] += 2;
695     assert(t1[3] == 200);
696     assert(t1[4] == 200);
697 
698     t1[].opIndexOpAssign!"*"(t1);
699     assert(t1[3] == 40000);
700     assert(t1[4] == 40000);
701 
702 
703     assert(&t1[$ - 1] is &(t1.back()));
704 }
705 
706 @safe @nogc pure nothrow version(mir_ndslice_test) unittest
707 {
708     import std.range : iota;
709     auto r = (10_000L * 2 * 3 * 4).iota;
710 
711     auto t0 = r.slicedField(10, 20, 30, 40);
712     assert(t0.length == 10);
713     assert(t0.length!0 == 10);
714     assert(t0.length!1 == 20);
715     assert(t0.length!2 == 30);
716     assert(t0.length!3 == 40);
717 }
718 
719 pure nothrow version(mir_ndslice_test) unittest
720 {
721     auto tensor = new int[3 * 4 * 8].sliced(3, 4, 8);
722     assert(&(tensor.back.back.back()) is &tensor[2, 3, 7]);
723     assert(&(tensor.front.front.front()) is &tensor[0, 0, 0]);
724 }
725 
726 pure nothrow version(mir_ndslice_test) unittest
727 {
728     auto slice = new int[24].sliced(2, 3, 4);
729     auto r0 = slice.pack!1[1, 2];
730     slice.pack!1[1, 2][] = 4;
731     auto r1 = slice[1, 2];
732     assert(slice[1, 2, 3] == 4);
733 }
734 
735 pure nothrow version(mir_ndslice_test) unittest
736 {
737     auto ar = new int[3 * 8 * 9];
738 
739     auto tensor = ar.sliced(3, 8, 9);
740     tensor[0, 1, 2] = 4;
741     tensor[0, 1, 2]++;
742     assert(tensor[0, 1, 2] == 5);
743     tensor[0, 1, 2]--;
744     assert(tensor[0, 1, 2] == 4);
745     tensor[0, 1, 2] += 2;
746     assert(tensor[0, 1, 2] == 6);
747 
748     auto matrix = tensor[0 .. $, 1, 0 .. $];
749     matrix[] = 10;
750     assert(tensor[0, 1, 2] == 10);
751     assert(matrix[0, 2] == tensor[0, 1, 2]);
752     assert(&matrix[0, 2] is &tensor[0, 1, 2]);
753 }
754 
755 version(mir_ndslice_test)
756 pure nothrow
757 unittest
758 {
759     auto x = iota(2, 3);
760     auto y = iota([2, 3], 1);
761     auto combine1 = x.zip(y).map!"b";
762     auto combine2 = x.zip(y).map!("b", "a * b").map!(a => a[0]);
763 
764     assert(combine1[0, 0] == 1);
765     assert(combine2[0, 0] == 1);
766     static assert(is(typeof(combine2[0, 0]) == sizediff_t));
767 }