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 }