1 // Written in the D programming language 2 /** 3 * Implementation of matrix in mathematical definition. 4 * Features: 5 * $(OL 6 * $(LI All matrix actions is checked at compile time) 7 * $(LI Matrix contains only it's data, and nothing additional) 8 * $(LI Almost all matrix actions is `pure` and `nothrow`) 9 * ) 10 * 11 * Usage: 12 * Just create matrix as in documentation below and treat it like a simple 13 * two-dimensional array (if you want to get some data from matrix or put it in) 14 * or number (if you need to do some mathematical action with matrix). 15 * 16 * Copyright: 17 * Copyright Vlad Rindevich, 2014. 18 * 19 * License: 20 * $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0). 21 * 22 * Authors: 23 * Vlad Rindevich (rindevich.vs@gmail.com). 24 */ 25 module mathed.types.matrix; 26 27 public import mathed.utils.traits : isMatrix; 28 29 private 30 { 31 import std.traits : isNumeric; 32 import std.conv : to; 33 import std.array : Appender, appender; 34 } 35 36 alias Matrix!(1, 1) Matrix1f; 37 alias Matrix!(2, 2) Matrix2f; 38 alias Matrix!(3, 3) Matrix3f; 39 alias Matrix!(4, 4) Matrix4f; 40 41 alias Matrix!(1, 1, int) Matrix1i; 42 alias Matrix!(2, 2, int) Matrix2i; 43 alias Matrix!(3, 3, int) Matrix3i; 44 alias Matrix!(4, 4, int) Matrix4i; 45 46 /** 47 * Main matrix interface. 48 */ 49 struct Matrix (size_t Lines, size_t Cols, Type = float) 50 if (Lines > 0 && Cols > 0) 51 { 52 private 53 { 54 alias Matrix!(Lines, Cols, Type) Self; 55 56 /* 57 * Matrix core array. 58 */ 59 static if (isNumeric!Type) 60 Type[Lines * Cols] data = mixin (DefaultInit (Lines * Cols)); 61 else 62 Type[Lines * Cols] data; 63 } 64 65 /// Gets quantity of matrix lines. 66 alias Lines lines; 67 68 /// Gets quantity of matrix columns. 69 alias Cols cols; 70 71 /// Gets type of matrix data 72 alias Type type; 73 74 unittest 75 { 76 auto m = Matrix!(2, 3, int) 77 ( 78 3, -1, 6, 79 2, 1, 5, 80 ); 81 82 assert (m.cols == 3); 83 assert (m.lines == 2); 84 } 85 86 @trusted: 87 88 /** 89 * Matrix default constructor. It receives a bunch of values in amount 90 * of product of matrix lines and columns. 91 */ 92 this (in Type[Cols * Lines] values...) pure nothrow 93 { 94 set (values); 95 } 96 97 /** 98 * Sets all matrix values in one action. It receives bunch of values. 99 */ 100 void set (in Type[Cols * Lines] values...) pure nothrow 101 { 102 foreach (size_t index, ref element; data) 103 element = values[index]; 104 } 105 106 /// 107 unittest 108 { 109 // Creating matrix 110 auto m = Matrix3i 111 ( 112 3, -1, 6, 113 2, 1, 5, 114 -3, 1, 0 115 ); 116 117 // Testing some value to be the same as added 118 assert (m[2][0] == -3); 119 120 // Setting all values to zero. 121 m.set 122 ( 123 0, 0, 0, 124 0, 0, 0, 125 0, 0, 0 126 ); 127 128 // Testing some values to be zero. 129 assert (m[0][0] == 0); 130 } 131 132 /** 133 * Stringifies matrix data. 134 */ 135 string toString () 136 { 137 Appender!string result = appender ("["); 138 139 foreach (size_t index; 0..Lines) 140 result.put (this[index].to!string () 141 ~ (index != Lines - 1 ? ", " : "")); 142 143 result.put ("]"); 144 145 return result.data; 146 } 147 148 unittest 149 { 150 auto m = Matrix3i 151 ( 152 3, -1, 6, 153 2, 1, 5, 154 -3, 1, 0 155 ); 156 157 auto str = m.to!string (); 158 assert (str == "[[3, -1, 6], [2, 1, 5], [-3, 1, 0]]"); 159 } 160 161 /** 162 * Gets matrix line. 163 */ 164 ref auto opIndex (size_t line) pure nothrow 165 { 166 return data[Cols * line .. Cols + Cols * line]; 167 } 168 169 unittest 170 { 171 auto m = Matrix3i 172 ( 173 3, -1, 6, 174 2, 1, 5, 175 -3, 1, 0 176 ); 177 178 assert (m[2][0] == -3); 179 } 180 181 /** 182 * Iterates matrix without returning any element number. 183 */ 184 int opApply (int delegate (ref Type) foreach_) 185 { 186 int result; 187 188 foreach (ref element; data) 189 { 190 result = foreach_ (element); 191 if (result) break; 192 } 193 194 return result; 195 } 196 197 /** 198 * Iterates matrix by element number. E.g. returned number of m[1][2] 199 * element in Matrix3x3 iteration will be `5`. 200 */ 201 int opApply (int delegate (ref size_t, ref Type) foreach_) 202 { 203 int result; 204 205 foreach (size_t index, ref element; data) 206 { 207 result = foreach_ (index, element); 208 if (result) break; 209 } 210 211 return result; 212 } 213 214 /** 215 * Iterates matrix returning line and column number with element. 216 */ 217 int opApply (int delegate (ref size_t, ref size_t, ref Type) foreach_) 218 { 219 int result; 220 221 size_t line, col; 222 foreach (size_t index, ref element; data) 223 { 224 result = foreach_ (line, col, element); 225 col++; 226 227 if (index == Cols - 1 + Cols * line) 228 { 229 line++; 230 col = 0; 231 } 232 233 if (result) break; 234 } 235 236 return result; 237 } 238 239 /// 240 unittest 241 { 242 auto m = Matrix3i 243 ( 244 3, -1, 6, 245 2, 1, 5, 246 -3, 1, 0 247 ); 248 249 // Let's iterate matrix by element number 250 size_t index; 251 foreach (i, ref element; m) 252 { 253 if (element == 5) 254 { 255 index = i; 256 break; 257 } 258 } 259 assert (index == 5); 260 261 // Let's iterate matrix with line and column number 262 size_t line, col; 263 foreach (i, j, ref element; m) 264 { 265 if (element == 5) 266 { 267 line = i; 268 col = j; 269 break; 270 } 271 } 272 assert (line == 1 && col == 2); 273 } 274 275 /** 276 * Inverses matrix sign 277 */ 278 auto opUnary(string op)() pure nothrow 279 if( op == "-" ) 280 in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); } 281 body 282 { 283 return opBinary!"*" (-1); 284 } 285 286 /** 287 * Processes matrix addition and subtraction. 288 */ 289 Self opBinary (string op, SumType)(in Matrix!(Lines, Cols, SumType) summand) pure nothrow 290 if ((op == "+" || op == "-") && is (SumType : Type)) 291 in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); } 292 body 293 { 294 Self newMatrix; 295 foreach (size_t index, ref element; newMatrix.data) 296 mixin ("element = data[index] " ~ op ~ " cast(Type) summand.data[index];"); 297 return newMatrix; 298 } 299 300 /// ditto 301 void opOpAssign (string op, SumType)(in Matrix!(Lines, Cols, SumType) summand) pure nothrow 302 if ((op == "+" || op == "-") && is (SumType : Type)) 303 in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); } 304 body 305 { 306 this = opBinary!op (summand); 307 } 308 309 unittest 310 { 311 auto m = Matrix3i 312 ( 313 3, -1, 6, 314 2, 1, 5, 315 -3, 1, 0 316 ); 317 318 auto x = m + m; 319 m += m; 320 321 auto equalX = Matrix3i 322 ( 323 6, -2, 12, 324 4, 2, 10, 325 -6, 2, 0 326 ); 327 328 assert (x == equalX); 329 assert (m == equalX); 330 } 331 332 /** 333 * Processes matrix multiplication and division with number. 334 */ 335 Self opBinary (string op, Number)(in Number num) pure nothrow 336 if ((op == "*" || op == "/") && isNumeric!Number) 337 in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); } 338 body 339 { 340 Self newMatrix; 341 foreach (size_t index, ref element; newMatrix.data) 342 mixin ("element = data[index] " ~ op ~ " num;"); 343 return newMatrix; 344 } 345 346 /// ditto 347 Self opBinaryRight (string op, Number)(in Number num) pure nothrow 348 if ((op == "*" || op == "/") && isNumeric!Number) 349 in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); } 350 body 351 { 352 return opBinary!op (num); 353 } 354 355 /// ditto 356 void opOpAssign (string op, Number)(in Number num) pure nothrow 357 if ((op == "*" || op == "/") && isNumeric!Number) 358 in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); } 359 body 360 { 361 this = opBinary!op (num); 362 } 363 364 unittest 365 { 366 auto m = Matrix3i 367 ( 368 3, -1, 6, 369 2, 1, 5, 370 -3, 1, 0 371 ); 372 373 auto y = m * 5; 374 auto yy = 5 * m; 375 m *= 5; 376 377 auto equalY = Matrix3i 378 ( 379 15, -5, 30, 380 10, 5, 25, 381 -15, 5, 0 382 ); 383 384 assert (y == equalY); 385 assert (yy == equalY); 386 assert (m == equalY); 387 } 388 389 /** 390 * Processes matrix multiplication with another matrix. 391 */ 392 auto opBinary (string op, Factor)(in Factor factor) pure nothrow 393 if (op == "*" && isMatrix!Factor && Cols == Factor.lines 394 && is (Factor.type : Type)) 395 in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); } 396 body 397 { 398 Matrix!(Lines, Factor.cols, Type) newMatrix; 399 400 size_t line, col; 401 foreach (ref element; newMatrix.data) 402 { 403 if (col == Factor.cols) 404 { 405 line++; 406 col = 0; 407 } 408 409 foreach (k; 0 .. Cols) 410 element += data[Cols * line + k] 411 * factor.data[Factor.cols * k + col]; 412 413 col++; 414 } 415 416 return newMatrix; 417 } 418 419 /// ditto 420 void opOpAssign (string op, Factor)(in Factor factor) pure nothrow 421 if (op == "*" && isMatrix!Factor && Cols == Factor.lines 422 && is (Factor.type : Type)) 423 in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); } 424 body 425 { 426 this = opBinary!op (factor); 427 } 428 429 unittest 430 { 431 auto m = Matrix!(3, 3, int) 432 ( 433 3, -1, 6, 434 2, 1, 5, 435 -3, 1, 0 436 ); 437 438 auto n = Matrix!(3, 2, int) 439 ( 440 3, 4, 441 1, 0, 442 5, 2 443 ); 444 445 auto z = m * n; 446 447 auto equalZ = Matrix!(3, 2, int) 448 ( 449 38, 24, 450 32, 18, 451 -8, -12 452 ); 453 454 assert (z == equalZ); 455 } 456 457 /** 458 * Casts matrix to a new type. It should be matrix type too, with equal 459 * quantity of lines and cols. 460 */ 461 NewType opCast (NewType)() pure nothrow 462 if (isMatrix!NewType && NewType.lines == Lines 463 && NewType.cols == Cols && (is (NewType.type : Type) 464 || is (Type : NewType.type))) 465 in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); } 466 body 467 { 468 NewType newMatrix; 469 470 foreach (size_t index, ref element; newMatrix.data) 471 element = cast(NewType.type) data[index]; 472 473 return newMatrix; 474 } 475 476 /// ditto 477 Matrix!(Lines, Cols, NewType) castTo (NewType)() 478 if (is (NewType : Type) || is (Type : NewType)) 479 in { static assert (isNumeric!Type, NOT_NUMERIC_FORBIDDEN); } 480 body 481 { 482 return cast(Matrix!(Lines, Cols, NewType)) this; 483 } 484 485 unittest 486 { 487 auto m = Matrix!(3, 3, int) 488 ( 489 3, -1, 6, 490 2, 1, 5, 491 -3, 1, 0 492 ); 493 494 auto n = cast(Matrix!(3, 3)) m; 495 auto l = m.castTo!double (); 496 497 assert (is (n.type == float)); 498 assert (is (l.type == double)); 499 500 assert (is (typeof (n[0][0]) == float)); 501 assert (is (typeof (l[0][0]) == double)); 502 503 assert (isMatrix!n); 504 assert (isMatrix!l); 505 } 506 507 /** 508 * Casts matrix to a string. 509 */ 510 string opCast (NewType)() 511 if (is (NewType == string)) 512 { 513 return toString (); 514 } 515 516 /** 517 * Transposes matrix. 518 */ 519 @property auto t () pure nothrow 520 { 521 Matrix!(Cols, Lines, Type) newMatrix; 522 523 size_t line, col; 524 foreach (ref element; newMatrix.data) 525 { 526 if (line == Lines) 527 { 528 col++; 529 line = 0; 530 } 531 532 element = data[Cols * line + col]; 533 534 line++; 535 } 536 537 return newMatrix; 538 } 539 540 unittest 541 { 542 auto m = Matrix3i 543 ( 544 3, -1, 6, 545 2, 1, 5, 546 -3, 1, 0 547 ); 548 549 assert (m.t.t == m); 550 } 551 552 static if (Lines == Cols && isNumeric!Type) 553 { 554 /** 555 * Returns indentity matrix instead of zero. 556 */ 557 @trusted static @property Self identity () pure nothrow 558 { 559 Self newMatrix; 560 newMatrix.data = mixin (ConstructIdentity ()); 561 return newMatrix; 562 } 563 564 /** 565 * Makes matrix with diagonal consists of received values. 566 */ 567 @trusted static Self diag (Type[Lines] values...) pure nothrow 568 { 569 Self newMatrix; 570 571 size_t line, col, index; 572 foreach (ref element; newMatrix.data) 573 { 574 if (col == Cols) 575 { 576 line++; 577 col = 0; 578 } 579 580 if (line == col) 581 { 582 element = values[index]; 583 index++; 584 } 585 else 586 element = 0; 587 588 col++; 589 } 590 591 return newMatrix; 592 } 593 } 594 595 private: 596 597 static if (isNumeric!Type) 598 { 599 @trusted static string ConstructIdentity () pure nothrow 600 { 601 string buffer = "[ "; 602 603 size_t line, col; 604 foreach (size_t index; 0 .. Lines * Cols) 605 { 606 if (col == Cols) 607 { 608 line++; 609 col = 0; 610 } 611 612 if (line == col) 613 buffer ~= "1, "; 614 else 615 buffer ~= "0, "; 616 617 col++; 618 } 619 620 return buffer ~= " ]"; 621 } 622 } 623 } 624 625 package static @trusted string DefaultInit (size_t Size) pure nothrow 626 { 627 string buffer = "[ "; 628 foreach (size_t index; 0 .. Size) 629 buffer ~= "0, "; 630 return buffer ~ " ]"; 631 } 632 633 private: 634 635 enum NOT_NUMERIC_FORBIDDEN = "Impossible to apply mathematical action to " 636 ~ "not-numeric matrix";