The OpenD Programming Language

1 /++
2 This is a submodule of $(MREF mir,ndslice).
3 
4 Field is a type with `opIndex()(ptrdiff_t index)` primitive.
5 An iterator can be created on top of a field using $(SUBREF iterator, FieldIterator).
6 An ndslice can be created on top of a field using $(SUBREF slice, slicedField).
7 
8 $(BOOKTABLE $(H2 Fields),
9 $(TR $(TH Field Name) $(TH Used By))
10 $(T2 BitField, $(SUBREF topology, bitwise))
11 $(T2 BitpackField, $(SUBREF topology, bitpack))
12 $(T2 CycleField, $(SUBREF topology, cycle) (2 kinds))
13 $(T2 LinspaceField, $(SUBREF topology, linspace))
14 $(T2 MagicField, $(SUBREF topology, magic))
15 $(T2 MapField, $(SUBREF topology, map) and $(SUBREF topology, mapField))
16 $(T2 ndIotaField, $(SUBREF topology, ndiota))
17 $(T2 OrthogonalReduceField, $(SUBREF topology, orthogonalReduceField))
18 $(T2 RepeatField, $(SUBREF topology, repeat))
19 $(T2 SparseField, Used for mutable DOK sparse matrixes )
20 )
21 
22 
23 
24 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
25 Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments
26 Authors: Ilia Ki
27 
28 Macros:
29 SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP)
30 T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
31 +/
32 module mir.ndslice.field;
33 
34 import mir.internal.utility: Iota;
35 import mir.math.common: fmamath;
36 import mir.ndslice.internal;
37 import mir.qualifier;
38 
39 @fmamath:
40 
41 package template ZeroShiftField(T)
42 {
43     static if (hasZeroShiftFieldMember!T)
44         alias ZeroShiftField = typeof(T.init.assumeFieldsHaveZeroShift());
45     else
46         alias ZeroShiftField = T;
47 }
48 
49 package enum hasZeroShiftFieldMember(T) = __traits(hasMember, T, "assumeFieldsHaveZeroShift");
50 
51 package auto applyAssumeZeroShift(Types...)()
52 {
53     import mir.ndslice.topology;
54     string str;
55     foreach(i, T; Types)
56         static if (hasZeroShiftFieldMember!T)
57             str ~= "_fields[" ~ i.stringof ~ "].assumeFieldsHaveZeroShift, ";
58         else
59             str ~= "_fields[" ~ i.stringof ~ "], ";
60     return str;
61 }
62 
63 auto MapField__map(Field, alias fun, alias fun1)(ref MapField!(Field, fun) f)
64 {
65     import core.lifetime: move;
66     import mir.functional: pipe;
67     return MapField!(Field, pipe!(fun, fun1))(move(f._field));
68 }
69 
70 
71 /++
72 `MapField` is used by $(SUBREF topology, map).
73 +/
74 struct MapField(Field, alias _fun)
75 {
76 @fmamath:
77     ///
78     Field _field;
79 
80     ///
81     auto lightConst()() const @property
82     {
83         return MapField!(LightConstOf!Field, _fun)(.lightConst(_field));
84     }
85 
86     ///
87     auto lightImmutable()() immutable @property
88     {
89         return MapField!(LightImmutableOf!Field, _fun)(.lightImmutable(_field));
90     }
91 
92     /++
93     User defined constructor used by $(LREF mapField).
94     +/
95     static alias __map(alias fun1) = MapField__map!(Field, _fun, fun1);
96 
97     auto ref opIndex(T...)(auto ref T index)
98     {
99         import mir.functional: Tuple, unref;
100         static if (is(typeof(_field[index]) : Tuple!K, K...))
101         {
102             auto t = _field[index];
103             return mixin("_fun(" ~ _iotaArgs!(K.length, "t.expand[", "].unref, ") ~ ")");
104         }
105         else
106             return _fun(_field[index]);
107     }
108 
109     static if (__traits(hasMember, Field, "length"))
110     auto length() const @property
111     {
112         return _field.length;
113     }
114 
115     static if (__traits(hasMember, Field, "shape"))
116     auto shape() const @property
117     {
118         return _field.shape;
119     }
120 
121     static if (__traits(hasMember, Field, "elementCount"))
122     auto elementCount() const @property
123     {
124         return _field.elementCount;
125     }
126 
127     static if (hasZeroShiftFieldMember!Field)
128     /// Defined if `Field` has member `assumeFieldsHaveZeroShift`.
129     auto assumeFieldsHaveZeroShift() @property
130     {
131         return _mapField!_fun(_field.assumeFieldsHaveZeroShift);
132     }
133 }
134 
135 /++
136 `VmapField` is used by $(SUBREF topology, map).
137 +/
138 struct VmapField(Field, Fun)
139 {
140 @fmamath:
141     ///
142     Field _field;
143     ///
144     Fun _fun;
145 
146     ///
147     auto lightConst()() const @property
148     {
149         return VmapField!(LightConstOf!Field, _fun)(.lightConst(_field));
150     }
151 
152     ///
153     auto lightImmutable()() immutable @property
154     {
155         return VmapField!(LightImmutableOf!Field, _fun)(.lightImmutable(_field));
156     }
157 
158     auto ref opIndex(T...)(auto ref T index)
159     {
160         import mir.functional: Tuple, unref;
161         static if (is(typeof(_field[index]) : Tuple!K, K...))
162         {
163             auto t = _field[index];
164             return mixin("_fun(" ~ _iotaArgs!(K.length, "t.expand[", "].unref, ") ~ ")");
165         }
166         else
167             return _fun(_field[index]);
168     }
169 
170     static if (__traits(hasMember, Field, "length"))
171     auto length() const @property
172     {
173         return _field.length;
174     }
175 
176     static if (__traits(hasMember, Field, "shape"))
177     auto shape() const @property
178     {
179         return _field.shape;
180     }
181 
182     static if (__traits(hasMember, Field, "elementCount"))
183     auto elementCount()const @property
184     {
185         return _field.elementCount;
186     }
187 
188     static if (hasZeroShiftFieldMember!Field)
189     /// Defined if `Field` has member `assumeFieldsHaveZeroShift`.
190     auto assumeFieldsHaveZeroShift() @property
191     {
192         return _vmapField(_field.assumeFieldsHaveZeroShift, _fun);
193     }
194 }
195 
196 /+
197 Creates a mapped field. Uses `__map` if possible.
198 +/
199 auto _mapField(alias fun, Field)(Field field)
200 {
201     import mir.functional: naryFun;
202     static if ((
203         __traits(isSame, fun, naryFun!"a|b") ||
204         __traits(isSame, fun, naryFun!"a^b") ||
205         __traits(isSame, fun, naryFun!"a&b") ||
206         __traits(isSame, fun, naryFun!"a | b") ||
207         __traits(isSame, fun, naryFun!"a ^ b") ||
208         __traits(isSame, fun, naryFun!"a & b")) &&
209         is(Field : ZipField!(BitField!(LeftField, I), BitField!(RightField, I)), LeftField, RightField, I))
210     {
211         import mir.ndslice.topology: bitwiseField;
212         auto f = ZipField!(LeftField, RightField)(field._fields[0]._field, field._fields[1]._field)._mapField!fun;
213         return f.bitwiseField!(typeof(f), I);
214     }
215     else
216     static if (__traits(hasMember, Field, "__map"))
217         return Field.__map!fun(field);
218     else
219         return MapField!(Field, fun)(field);
220 }
221 
222 /+
223 Creates a mapped field. Uses `__vmap` if possible.
224 +/
225 auto _vmapField(Field, Fun)(Field field, Fun fun)
226 {
227     static if (__traits(hasMember, Field, "__vmap"))
228         return Field.__vmap(field, fun);
229     else
230         return VmapField!(Field, Fun)(field, fun);
231 }
232 
233 /++
234 Iterates multiple fields in lockstep.
235 
236 `ZipField` is used by $(SUBREF topology, zipFields).
237 +/
238 struct ZipField(Fields...)
239     if (Fields.length > 1)
240 {
241 @fmamath:
242     import mir.functional: Tuple, Ref, _ref;
243     import std.meta: anySatisfy;
244 
245     ///
246     Fields _fields;
247 
248     ///
249     auto lightConst()() const @property
250     {
251         import std.format;
252         import mir.ndslice.topology: iota;
253         import std.meta: staticMap;
254         return mixin("ZipField!(staticMap!(LightConstOf, Fields))(%(_fields[%s].lightConst,%)].lightConst)".format(_fields.length.iota));
255     }
256 
257     ///
258     auto lightImmutable()() immutable @property
259     {
260         import std.format;
261         import mir.ndslice.topology: iota;
262         import std.meta: staticMap;
263         return mixin("ZipField!(staticMap!(LightImmutableOf, Fields))(%(_fields[%s].lightImmutable,%)].lightImmutable)".format(_fields.length.iota));
264     }
265 
266     auto opIndex()(ptrdiff_t index)
267     {
268         alias Iterators = Fields;
269         alias _iterators = _fields;
270         import mir.ndslice.iterator: _zip_types, _zip_index;
271         return mixin("Tuple!(_zip_types!Fields)(" ~ _zip_index!Fields ~ ")");
272     }
273 
274     auto opIndexAssign(Types...)(Tuple!(Types) value, ptrdiff_t index)
275         if (Types.length == Fields.length)
276     {
277         foreach(i, ref val; value.expand)
278         {
279             _fields[i][index] = val;
280         }
281         return opIndex(index);
282     }
283 
284     static if (anySatisfy!(hasZeroShiftFieldMember, Fields))
285     /// Defined if at least one of `Fields` has member `assumeFieldsHaveZeroShift`.
286     auto assumeFieldsHaveZeroShift() @property
287     {
288         import std.meta: staticMap;
289         return mixin("ZipField!(staticMap!(ZeroShiftField, Fields))(" ~ applyAssumeZeroShift!Fields ~ ")");
290     }
291 }
292 
293 /++
294 `RepeatField` is used by $(SUBREF topology, repeat).
295 +/
296 struct RepeatField(T)
297 {
298     import std.traits: Unqual;
299 
300 @fmamath:
301     alias UT = Unqual!T;
302 
303     ///
304     UT _value;
305 
306     ///
307     auto lightConst()() const @property @trusted
308     {
309         return RepeatField!(const T)(cast(UT) _value);
310     }
311 
312     ///
313     auto lightImmutable()() immutable @property @trusted
314     {
315         return RepeatField!(immutable T)(cast(UT) _value);
316     }
317 
318     auto ref T opIndex()(ptrdiff_t) @trusted
319     { return cast(T) _value; }
320 }
321 
322 /++
323 `BitField` is used by $(SUBREF topology, bitwise).
324 +/
325 struct BitField(Field, I = typeof(cast()Field.init[size_t.init]))
326     if (__traits(isUnsigned, I))
327 {
328 @fmamath:
329     import mir.bitop: ctlz;
330     package(mir) alias E = I;
331     package(mir) enum shift = ctlz(I.sizeof) + 3;
332 
333     ///
334     Field _field;
335 
336     /// optimization for bitwise operations
337     auto __vmap(Fun : LeftOp!(op, bool), string op)(Fun fun)
338         if (op == "|" || op == "&" || op == "^")
339     {
340         import mir.ndslice.topology: bitwiseField;
341         return _vmapField(_field, RightOp!(op, I)(I(0) - fun.value)).bitwiseField;
342     }
343 
344     /// ditto
345     auto __vmap(Fun : RightOp!(op, bool), string op)(Fun fun)
346         if (op == "|" || op == "&" || op == "^")
347     {
348         import mir.ndslice.topology: bitwiseField;
349         return _vmapField(_field, RightOp!(op, I)(I(0) - fun.value)).bitwiseField;
350     }
351 
352     /// ditto
353     auto __vmap(Fun)(Fun fun)
354     {
355         return VmapField!(typeof(this), Fun)(this, fun);
356     }
357 
358     /// ditto
359     alias __map(alias fun) = BitField__map!(Field, I, fun);
360 
361     ///
362     auto lightConst()() const @property
363     {
364         return BitField!(LightConstOf!Field, I)(mir.qualifier.lightConst(_field));
365     }
366 
367     ///
368     auto lightImmutable()() immutable @property
369     {
370         return BitField!(LightImmutableOf!Field, I)(mir.qualifier.lightImmutable(_field));
371     }
372 
373     bool opIndex()(size_t index)
374     {
375         import mir.bitop: bt;
376         return bt!(Field, I)(_field, index) != 0;
377     }
378 
379     bool opIndexAssign()(bool value, size_t index)
380     {
381         import mir.bitop: bta;
382         bta!(Field, I)(_field, index, value);
383         return value;
384     }
385 
386     static if (hasZeroShiftFieldMember!Field)
387     /// Defined if `Field` has member `assumeFieldsHaveZeroShift`.
388     auto assumeFieldsHaveZeroShift() @property
389     {
390         return BitField!(ZeroShiftField!Field, I)(_field.assumeFieldsHaveZeroShift);
391     }
392 }
393 
394 ///
395 version(mir_ndslice_test) unittest
396 {
397     import mir.ndslice.iterator: FieldIterator;
398     ushort[10] data;
399     auto f = FieldIterator!(BitField!(ushort*))(0, BitField!(ushort*)(data.ptr));
400     f[123] = true;
401     f++;
402     assert(f[122]);
403 }
404 
405 auto BitField__map(Field, I, alias fun)(BitField!(Field, I) field)
406 {
407     import core.lifetime: move;
408     import mir.functional: naryFun;
409     static if (__traits(isSame, fun, naryFun!"~a") || __traits(isSame, fun, naryFun!"!a"))
410     {
411         import mir.ndslice.topology: bitwiseField;
412         auto f = _mapField!(naryFun!"~a")(move(field._field));
413         return f.bitwiseField!(typeof(f), I);
414     }
415     else
416     {
417         return MapField!(BitField!(Field, I), fun)(move(field));
418     }
419 }
420 
421 /++
422 `BitpackField` is used by $(SUBREF topology, bitpack).
423 +/
424 struct BitpackField(Field, uint pack, I = typeof(cast()Field.init[size_t.init]))
425     if (__traits(isUnsigned, I))
426 {
427     //static assert();
428 @fmamath:
429     package(mir) alias E = I;
430     package(mir) enum mask = (I(1) << pack) - 1;
431     package(mir) enum bits = I.sizeof * 8;
432 
433     ///
434     Field _field;
435 
436     ///
437     auto lightConst()() const @property
438     {
439         return BitpackField!(LightConstOf!Field, pack)(.lightConst(_field));
440     }
441 
442     ///
443     auto lightImmutable()() immutable @property
444     {
445         return BitpackField!(LightImmutableOf!Field, pack)(.lightImmutable(_field));
446     }
447 
448     I opIndex()(size_t index)
449     {
450         index *= pack;
451         size_t start = index % bits;
452         index /= bits;
453         auto ret = (_field[index] >>> start) & mask;
454         static if (bits % pack)
455         {
456             sizediff_t end = start - (bits - pack);
457             if (end > 0)
458                 ret ^= cast(I)(_field[index + 1] << (bits - end)) >>> (bits - pack);
459         }
460         return cast(I) ret;
461     }
462 
463     I opIndexAssign()(I value, size_t index)
464     {
465         import std.traits: Unsigned;
466         assert(cast(Unsigned!I)value <= mask);
467         index *= pack;
468         size_t start = index % bits;
469         index /= bits;
470         _field[index] = cast(I)((_field[index] & ~(mask << start)) ^ (value << start));
471         static if (bits % pack)
472         {
473             sizediff_t end = start - (bits - pack);
474             if (end > 0)
475                 _field[index + 1] = cast(I)((_field[index + 1] & ~((I(1) << end) - 1)) ^ (value >>> (pack - end)));
476         }
477         return value;
478     }
479 
480     static if (hasZeroShiftFieldMember!Field)
481     /// Defined if `Field` has member `assumeFieldsHaveZeroShift`.
482     auto assumeFieldsHaveZeroShift() @property
483     {
484         return BitpackField!(ZeroShiftField!Field, pack, I)(_field.assumeFieldsHaveZeroShift);
485     }
486 }
487 
488 ///
489 version(mir_ndslice_test)
490 unittest
491 {
492     import mir.ndslice.iterator: FieldIterator;
493     ushort[10] data;
494     auto f = FieldIterator!(BitpackField!(ushort*, 6))(0, BitpackField!(ushort*, 6)(data.ptr));
495     f[0] = cast(ushort) 31;
496     f[1] = cast(ushort) 13;
497     f[2] = cast(ushort)  8;
498     f[3] = cast(ushort) 43;
499     f[4] = cast(ushort) 28;
500     f[5] = cast(ushort) 63;
501     f[6] = cast(ushort) 39;
502     f[7] = cast(ushort) 23;
503     f[8] = cast(ushort) 44;
504 
505     assert(f[0] == 31);
506     assert(f[1] == 13);
507     assert(f[2] ==  8);
508     assert(f[3] == 43);
509     assert(f[4] == 28);
510     assert(f[5] == 63);
511     assert(f[6] == 39);
512     assert(f[7] == 23);
513     assert(f[8] == 44);
514     assert(f[9] == 0);
515     assert(f[10] == 0);
516     assert(f[11] == 0);
517 }
518 
519 version(mir_ndslice_test)
520 unittest
521 {
522     import mir.ndslice.slice;
523     import mir.ndslice.topology;
524     import mir.ndslice.sorting;
525     uint[2] data;
526     auto packed = data[].sliced.bitpack!18;
527     assert(packed.length == 3);
528     packed[0] = 5;
529     packed[1] = 3;
530     packed[2] = 2;
531     packed.sort;
532     assert(packed[0] == 2);
533     assert(packed[1] == 3);
534     assert(packed[2] == 5);
535 }
536 
537 ///
538 struct OrthogonalReduceField(FieldsIterator, alias fun, T)
539 {
540     import mir.ndslice.slice: Slice;
541 
542 @fmamath:
543     /// non empty slice
544 
545     Slice!FieldsIterator _fields;
546 
547     ///
548     T _initialValue;
549 
550     ///
551     auto lightConst()() const @property
552     {
553         auto fields = _fields.lightConst;
554         return OrthogonalReduceField!(fields.Iterator, fun, T)(fields, _initialValue);
555     }
556 
557     ///
558     auto lightImmutable()() immutable @property
559     {
560         auto fields = _fields.lightImmutable;
561         return OrthogonalReduceField!(fields.Iterator, fun, T)(fields, _initialValue);
562     }
563 
564     /// `r = fun(r, fields[i][index]);` reduction by `i`
565     auto opIndex()(size_t index)
566     {
567         import std.traits: Unqual;
568         auto fields = _fields;
569         T r = _initialValue;
570         if (!fields.empty) do
571         {
572             r = cast(T) fun(r, fields.front[index]);
573             fields.popFront;
574         }
575         while(!fields.empty);
576         return r;
577     }
578 }
579 
580 ///
581 struct CycleField(Field)
582 {
583     import mir.ndslice.slice: Slice;
584 
585 @fmamath:
586     /// Cycle length
587     size_t _length;
588     ///
589     Field _field;
590 
591     ///
592     auto lightConst()() const @property
593     {
594         auto field = .lightConst(_field);
595         return CycleField!(typeof(field))(_length, field);
596     }
597 
598     ///
599     auto lightImmutable()() immutable @property
600     {
601         auto field = .lightImmutable(_field);
602         return CycleField!(typeof(field))(_length, field);
603     }
604 
605     ///
606     auto ref opIndex()(size_t index)
607     {
608         return _field[index % _length];
609     }
610 
611     ///
612     static if (!__traits(compiles, &opIndex(size_t.init)))
613     {
614         auto ref opIndexAssign(T)(auto ref T value, size_t index)
615         {
616             return _field[index % _length] = value;
617         }
618     }
619 
620     static if (hasZeroShiftFieldMember!Field)
621     /// Defined if `Field` has member `assumeFieldsHaveZeroShift`.
622     auto assumeFieldsHaveZeroShift() @property
623     {
624         return CycleField!(ZeroShiftField!Field)(_length, _field.assumeFieldsHaveZeroShift);
625     }
626 }
627 
628 ///
629 struct CycleField(Field, size_t length)
630 {
631     import mir.ndslice.slice: Slice;
632 
633 @fmamath:
634     /// Cycle length
635     enum _length = length;
636     ///
637     Field _field;
638 
639     ///
640     auto lightConst()() const @property
641     {
642         auto field = .lightConst(_field);
643         return CycleField!(typeof(field), _length)(field);
644     }
645 
646     ///
647     auto lightImmutable()() immutable @property
648     {
649         auto field = .lightImmutable(_field);
650         return CycleField!(typeof(field), _length)(field);
651     }
652 
653     ///
654     auto ref opIndex()(size_t index)
655     {
656         return _field[index % _length];
657     }
658 
659     ///
660     static if (!__traits(compiles, &opIndex(size_t.init)))
661     {
662         auto ref opIndexAssign(T)(auto ref T value, size_t index)
663         {
664             return _field[index % _length] = value;
665         }
666     }
667 
668     static if (hasZeroShiftFieldMember!Field)
669     /// Defined if `Field` has member `assumeFieldsHaveZeroShift`.
670     auto assumeFieldsHaveZeroShift() @property
671     {
672         return CycleField!(ZeroShiftField!Field, _length)(_field.assumeFieldsHaveZeroShift);
673     }
674 }
675 
676 /++
677 `ndIotaField` is used by $(SUBREF topology, ndiota).
678 +/
679 struct ndIotaField(size_t N)
680     if (N)
681 {
682 @fmamath:
683     ///
684     size_t[N - 1] _lengths;
685 
686     ///
687     auto lightConst()() const @property
688     {
689         return ndIotaField!N(_lengths);
690     }
691 
692     ///
693     auto lightImmutable()() const @property
694     {
695         return ndIotaField!N(_lengths);
696     }
697 
698     ///
699     size_t[N] opIndex()(size_t index) const
700     {
701         size_t[N] indices;
702         foreach_reverse (i; Iota!(N - 1))
703         {
704             indices[i + 1] = index % _lengths[i];
705             index /= _lengths[i];
706         }
707         indices[0] = index;
708         return indices;
709     }
710 }
711 
712 /++
713 `LinspaceField` is used by $(SUBREF topology, linspace).
714 +/
715 struct LinspaceField(T)
716 {
717     ///
718     size_t _length;
719 
720     ///
721     T _start = cast(T) 0, _stop = cast(T) 0;
722 
723     ///
724     auto lightConst()() scope const @property
725     {
726         return LinspaceField!T(_length, _start, _stop);
727     }
728 
729     ///
730     auto lightImmutable()() scope const @property
731     {
732         return LinspaceField!T(_length, _start, _stop);
733     }
734 
735     // no fastmath
736     ///
737     T opIndex()(sizediff_t index) scope const
738     {
739         sizediff_t d = _length - 1;
740         auto v = typeof(T.init.re)(d - index);
741         auto w = typeof(T.init.re)(index);
742         v /= d;
743         w /= d;
744         auto a = v * _start;
745         auto b = w * _stop;
746         return a + b;
747     }
748 
749 @fmamath:
750 
751     ///
752     size_t length(size_t dimension = 0)() scope const @property
753         if (dimension == 0)
754     {
755         return _length;
756     }
757 
758     ///
759     size_t[1] shape()() scope const @property @nogc
760     {
761         return [_length];
762     }
763 }
764 
765 /++
766 Magic square field.
767 +/
768 struct MagicField
769 {
770 @fmamath:
771 @safe pure nothrow @nogc:
772 
773     /++
774     Magic Square size.
775     +/
776     size_t _n;
777 
778 scope const:
779 
780     ///
781     MagicField lightConst()() @property
782     {
783         return this;
784     }
785 
786     ///
787     MagicField lightImmutable()() @property
788     {
789         return this;
790     }
791 
792     ///
793     size_t length(size_t dimension = 0)() @property
794         if(dimension <= 2)
795     {
796         return _n * _n;
797     }
798 
799     ///
800     size_t[1] shape() @property
801     {
802         return [_n * _n];
803     }
804 
805     ///
806     size_t opIndex(size_t index)
807     {
808         pragma(inline, false);
809         auto d = index / _n;
810         auto m = index % _n;
811         if (_n & 1)
812         {
813             //d = _n - 1 - d;     // MATLAB synchronization
814             //index = d * _n + m; // ditto
815             auto r = (index + 1 - d + (_n - 3) / 2) % _n;
816             auto c = (_n * _n - index + 2 * d) % _n;
817             return r * _n + c + 1;
818         }
819         else
820         if ((_n & 2) == 0)
821         {
822             auto a = (d + 1) & 2;
823             auto b = (m + 1) & 2;
824             return a != b ? index + 1: _n * _n - index;
825         }
826         else
827         {
828             auto n = _n / 2 ;
829             size_t shift;
830             ptrdiff_t q;
831             ptrdiff_t p = m - n;
832             if (p >= 0)
833             {
834                 m = p;
835                 shift = n * n;
836                 auto mul = m <= n / 2 + 1;
837                 q = d - n;
838                 if (q >= 0)
839                 {
840                     d = q;
841                     mul = !mul;
842                 }
843                 if (mul)
844                 {
845                     shift *= 2;
846                 }
847             }
848             else
849             {
850                 auto mul = m < n / 2;
851                 q = d - n;
852                 if (q >= 0)
853                 {
854                     d = q;
855                     mul = !mul;
856                 }
857                 if (d == n / 2 && (m == 0 || m == n / 2))
858                 {
859                     mul = !mul;
860                 }
861                 if (mul)
862                 {
863                     shift = n * n * 3; 
864                 }
865             }
866             index = d * n + m;
867             auto r = (index + 1 - d + (n - 3) / 2) % n;
868             auto c = (n * n - index + 2 * d) % n;
869             return r * n + c + 1 + shift;
870         }
871     }
872 }
873 
874 /++
875 `SparseField` is used to represent Sparse ndarrays in mutable DOK format.
876 +/
877 struct SparseField(T)
878 {
879     ///
880     T[size_t] _table;
881 
882     ///
883     auto lightConst()() const @trusted
884     {
885         return SparseField!(const T)(cast(const(T)[size_t])_table);
886     }
887 
888     ///
889     auto lightImmutable()() immutable @trusted
890     {
891         return SparseField!(immutable T)(cast(immutable(T)[size_t])_table);
892     }
893 
894     ///
895     T opIndex()(size_t index)
896     {
897         import std.traits: isScalarType;
898         static if (isScalarType!T)
899             return _table.get(index, cast(T)0);
900         else
901             return _table.get(index, null);
902     }
903 
904     ///
905     T opIndexAssign()(T value, size_t index)
906     {
907         import std.traits: isScalarType;
908         static if (isScalarType!T)
909         {
910             if (value != 0)
911                 _table[index] = value;
912             else
913                 _table.remove(index);
914         }
915         else
916         {
917             if (value !is null)
918                 _table[index] = value;
919             else
920                 _table.remove(index);
921         }
922         return value;
923     }
924 
925     ///
926     T opIndexUnary(string op)(size_t index)
927         if (op == `++` || op == `--`)
928     {
929         import std.traits: isScalarType;
930         mixin (`auto value = ` ~ op ~ `_table[index];`);
931         static if (isScalarType!T)
932         {
933             if (value == 0)
934                 _table.remove(index);
935         }
936         else
937         {
938             if (value is null)
939                 _table.remove(index);
940         }
941         return value;
942     }
943 
944     ///
945     T opIndexOpAssign(string op)(T value, size_t index)
946         if (op == `+` || op == `-`)
947     {
948         import std.traits: isScalarType;
949         mixin (`value = _table[index] ` ~ op ~ `= value;`); // this works
950         static if (isScalarType!T)
951         {
952             if (value == 0)
953                 _table.remove(index);
954         }
955         else
956         {
957             if (value is null)
958                 _table.remove(index);
959         }
960         return value;
961     }
962 }