1 /// Matrix linear algebra module 2 module gfx.math.mat; 3 4 alias FMat(size_t R, size_t C) = Mat!(float, R, C); 5 alias DMat(size_t R, size_t C) = Mat!(double, R, C); 6 alias IMat(size_t R, size_t C) = Mat!(int, R, C); 7 8 alias Mat2x2(T) = Mat!(T, 2, 2); 9 alias Mat3x3(T) = Mat!(T, 3, 3); 10 alias Mat4x4(T) = Mat!(T, 4, 4); 11 alias Mat2x3(T) = Mat!(T, 2, 3); 12 alias Mat2x4(T) = Mat!(T, 2, 4); 13 alias Mat3x4(T) = Mat!(T, 3, 4); 14 alias Mat3x2(T) = Mat!(T, 3, 2); 15 alias Mat4x2(T) = Mat!(T, 4, 2); 16 alias Mat4x3(T) = Mat!(T, 4, 3); 17 18 alias FMat2x2 = Mat!(float, 2, 2); 19 alias FMat3x3 = Mat!(float, 3, 3); 20 alias FMat4x4 = Mat!(float, 4, 4); 21 alias FMat2x3 = Mat!(float, 2, 3); 22 alias FMat2x4 = Mat!(float, 2, 4); 23 alias FMat3x4 = Mat!(float, 3, 4); 24 alias FMat3x2 = Mat!(float, 3, 2); 25 alias FMat4x2 = Mat!(float, 4, 2); 26 alias FMat4x3 = Mat!(float, 4, 3); 27 28 alias DMat2x2 = Mat!(double, 2, 2); 29 alias DMat3x3 = Mat!(double, 3, 3); 30 alias DMat4x4 = Mat!(double, 4, 4); 31 alias DMat2x3 = Mat!(double, 2, 3); 32 alias DMat2x4 = Mat!(double, 2, 4); 33 alias DMat3x4 = Mat!(double, 3, 4); 34 alias DMat3x2 = Mat!(double, 3, 2); 35 alias DMat4x2 = Mat!(double, 4, 2); 36 alias DMat4x3 = Mat!(double, 4, 3); 37 38 alias IMat2x2 = Mat!(int, 2, 2); 39 alias IMat3x3 = Mat!(int, 3, 3); 40 alias IMat4x4 = Mat!(int, 4, 4); 41 alias IMat2x3 = Mat!(int, 2, 3); 42 alias IMat2x4 = Mat!(int, 2, 4); 43 alias IMat3x4 = Mat!(int, 3, 4); 44 alias IMat3x2 = Mat!(int, 3, 2); 45 alias IMat4x2 = Mat!(int, 4, 2); 46 alias IMat4x3 = Mat!(int, 4, 3); 47 48 // further shortcuts 49 alias Mat2(T) = Mat2x2!T; 50 alias Mat3(T) = Mat3x3!T; 51 alias Mat4(T) = Mat4x4!T; 52 alias FMat2 = FMat2x2; 53 alias FMat3 = FMat3x3; 54 alias FMat4 = FMat4x4; 55 alias DMat2 = DMat2x2; 56 alias DMat3 = DMat3x3; 57 alias DMat4 = DMat4x4; 58 alias IMat2 = IMat2x2; 59 alias IMat3 = IMat3x3; 60 alias IMat4 = IMat4x4; 61 62 /// Build a matrix whose component type and size is inferred from arguments. 63 /// Arguments must be rows or matrices with consistent column count. 64 auto mat(Rows...)(in Rows rows) 65 { 66 import gfx.math.vec : CompTup; 67 import std.traits : CommonType; 68 69 static assert(hasConsistentLength!Rows, "All rows must have the same number of components"); 70 immutable rt = rowTuple(rows); 71 alias CT = CompTup!(typeof(rt).Types); 72 alias Comp = CommonType!(CT.Types); 73 enum rowLength = rt.length; 74 enum colLength = rt[0].length; 75 return Mat!(Comp, rowLength, colLength)(rt.expand); 76 } 77 78 /// 79 unittest 80 { 81 import gfx.math.vec : dvec; 82 import std.algorithm : equal; 83 import std.traits : Unqual; 84 85 immutable r1 = dvec(1, 2, 3); 86 immutable r2 = dvec(4, 5, 6); 87 immutable m1 = mat(r1, r2); 88 static assert( is(Unqual!(typeof(m1)) == DMat2x3) ); 89 assert(equal(m1.data, [ 1, 2, 3, 4, 5, 6 ])); 90 91 immutable m2 = mat(r2, m1, r1); 92 static assert( is(Unqual!(typeof(m2)) == DMat4x3) ); 93 assert(equal(m2.data, [ 4, 5, 6, 1, 2, 3, 4, 5, 6, 1, 2, 3])); 94 95 // The following would yield an inconsistent column count. 96 static assert( !__traits(compiles, mat(r1, dvec(1, 2))) ); 97 } 98 99 /// Matrix type for linear algebra 100 struct Mat(T, size_t R, size_t C) 101 { 102 import gfx.math.vec : isVec, Vec; 103 import std.meta : allSatisfy, Repeat; 104 import std.traits : CommonType, isArray, isNumeric, isImplicitlyConvertible; 105 import std.typecons : Tuple; 106 107 private T[R*C] _rep; 108 109 /// The number of rows in the matrix. 110 enum rowLength = R; 111 /// The number of columns in the matrix. 112 enum columnLength = C; 113 /// The number of components in the matrix; 114 enum compLength = R*C; 115 /// The matrix rows type. 116 alias Row = Vec!(T, columnLength); 117 /// The matrix columns type. 118 alias Column = Vec!(T, rowLength); 119 /// The type of the componetypeof(rt.expand)nts. 120 alias Component = T; 121 /// row major container: length is the number of rows 122 enum length = rowLength; 123 124 /// The identity matrix. 125 enum identity = mixin(identityCode); 126 127 128 /// Build a matrix from its elements. 129 /// To be provided row major. 130 this (Args...)(in Args args) 131 if (Args.length == R*C && allSatisfy!(isNumeric, Args) && 132 isImplicitlyConvertible!(CommonType!Args, T)) 133 { 134 _rep = [ args ]; 135 } 136 137 /// Build a matrix from the provided rows. 138 /// Each row must be an array (static or dynamic) and have the correct number 139 /// of elements. 140 this (Args...)(in Args args) 141 if (Args.length == rowLength && allSatisfy!(isArray, Args)) 142 { 143 import std.traits : isStaticArray; 144 145 static if (allSatisfy!(isStaticArray, Args)) { 146 static foreach (r, arg; args) { 147 static assert(arg.length == columnLength); 148 _rep[ r*columnLength .. (r+1)*columnLength ] = arg; 149 } 150 } 151 else { 152 foreach (r, arg; args) { 153 assert(arg.length == columnLength); 154 _rep[ r*columnLength .. (r+1)*columnLength ] = arg; 155 } 156 } 157 } 158 159 /// ditto 160 this (Args...)(in Args args) 161 if (Args.length == rowLength && allSatisfy!(isVec, Args)) 162 { 163 static foreach(r, arg; args) 164 { 165 static assert(arg.length == columnLength, "incorrect row size"); 166 static if (is(typeof(arg[0]) == T)) 167 { 168 _rep[r*columnLength .. (r+1)*columnLength] = arg._rep; 169 } 170 else 171 { 172 static foreach (c; 0 .. columnLength) 173 { 174 _rep[r*columnLength + c] = cast(T)arg[c]; 175 } 176 } 177 } 178 } 179 180 // representation in tuple 181 182 /// Alias to a type sequence holding all rows 183 alias RowSeq = Repeat!(rowLength, Row); 184 /// Alias to a type sequence holding all components 185 alias CompSeq = Repeat!(rowLength*columnLength, T); 186 187 /// All rows in a tuple 188 @property Tuple!RowSeq rowTup() const 189 { 190 return Tuple!RowSeq(cast(Row[rowLength])_rep); 191 } 192 /// All components in a tuple 193 @property Tuple!CompSeq compTup() const 194 { 195 return Tuple!CompSeq(_rep); 196 } 197 198 // array representation 199 200 /// Direct access to underlying data. 201 /// Reminder: row major order! 202 @property inout(Component)[] data() inout 203 { 204 return _rep[]; 205 } 206 207 /// Cast a matrix to another type 208 Mat!(U, rowLength, columnLength) opCast(V : Mat!(U, rowLength, columnLength), U)() const 209 if (__traits(compiles, cast(U)T.init)) 210 { 211 Mat!(U, rowLength, columnLength) res = void; 212 static foreach (i; 0 .. compLength) { 213 res._rep[i] = cast(U)_rep[i]; 214 } 215 return res; 216 } 217 218 // compile time indexing 219 220 /// Index a component at compile time 221 @property Component ct(size_t r, size_t c)() const 222 { 223 static assert(r < rowLength && c < columnLength); 224 enum ind = r*columnLength + c; 225 return _rep[ind]; 226 } 227 228 /// Assign a compile time indexed component 229 void ct(size_t r, size_t c)(in Component comp) 230 { 231 static assert(r < rowLength && c < columnLength); 232 enum ind = r*columnLength + c; 233 _rep[ind] = comp; 234 } 235 236 /// Index a row at compile time 237 @property Row ct(size_t r)() const 238 { 239 static assert(r < rowLength); 240 enum start = r*columnLength; 241 enum end = (r+1)*columnLength; 242 return Row(_rep[start .. end]); 243 } 244 245 /// Assign a row indexed at compile time 246 void ct(size_t r)(in Row row) 247 { 248 static assert(r < rowLength); 249 enum start = r*columnLength; 250 enum end = (r+1)*columnLength; 251 _rep[start .. end] = row.array; 252 } 253 254 /// Index a column at compile time 255 @property Column ctCol(size_t c)() const 256 { 257 static assert(c < columnLength); 258 Column col = void; 259 static foreach (r; 0 .. rowLength) {{ 260 enum ind = r*columnLength + c; 261 col[r] = _rep[ind]; 262 }} 263 return col; 264 } 265 266 /// Assign a column indexed at compile time 267 void ctCol(size_t c)(in Column col) 268 { 269 static assert(r < columnLength); 270 static foreach (r; 0 .. rowLength) {{ 271 enum ind = r*columnLength + c; 272 _rep[ind] = col[r]; 273 }} 274 } 275 276 // runtime indexing 277 278 /// Index a matrix row. 279 Row row(in size_t r) const 280 { 281 assert(r < rowLength); 282 return Row(_rep[r*columnLength .. (r+1)*columnLength]); 283 } 284 285 /// Index a matrix column. 286 Column column(in size_t c) const 287 { 288 assert(c < columnLength); 289 Column res=void; 290 static foreach (r; 0 .. rowLength) 291 { 292 res[r] = _rep[index(r, c)]; 293 } 294 return res; 295 } 296 297 /// Index a matrix component 298 T comp(in size_t r, in size_t c) const 299 { 300 return _rep[index(r, c)]; 301 } 302 303 /// Return a slice of the matrix 304 @property Mat!(T, RE-RS, CE-CS) slice(size_t RS, size_t RE, size_t CS, size_t CE)() const 305 if (RE > RS && RE <= rowLength && CE > CS && CE <= columnLength) 306 { 307 Mat!(T, RE-RS, CE-CS) res = void; 308 static foreach (r; RS .. RE) 309 { 310 static foreach (c; CS .. CE) 311 { 312 res[r-RS, c-CS] = comp(r, c); 313 } 314 } 315 return res; 316 } 317 318 /// Assign a slice of this matrix 319 /// e.g: $(D_CODE mat.slice!(0, 2) = otherMat;) 320 @property void slice(size_t RS, size_t CS, U, size_t UR, size_t UC)(in Mat!(U, UR, UC) mat) 321 if (RS+UR <= rowLength && CS+UC <= columnLength && isImplicitlyConvertible!(U, T)) 322 { 323 static foreach (r; 0 .. UR) 324 { 325 static foreach (c; 0 .. UC) 326 { 327 _rep[index(r+RS, c+CS)] = mat[r, c]; 328 } 329 } 330 } 331 332 /// Build an augmented matrix (add oth to the right of this matrix) 333 /// --- 334 /// immutable m = IMat2(4, 5, 6, 8); 335 /// assert( m ~ IMat2.identity == IMat2x4(4, 5, 1, 0, 6, 8, 0, 1)); 336 /// --- 337 auto opBinary(string op, U, size_t UC)(in Mat!(U, rowLength, UC) mat) const 338 if (op == "~") 339 { 340 alias ResMat = Mat!(CommonType!(T, U), rowLength, columnLength+UC); 341 ResMat res = void; 342 static foreach (r; 0 .. rowLength) { 343 static foreach (c; 0 .. columnLength) { 344 res[r, c] = _rep[index(r, c)]; 345 } 346 static foreach (c; columnLength .. columnLength+UC) { 347 res[r, c] = mat[r, c-columnLength]; 348 } 349 } 350 return res; 351 } 352 353 /// Index a matrix component. 354 T opIndex(in size_t r, in size_t c) const 355 { 356 return _rep[index(r, c)]; 357 } 358 359 /// Assign a matrix component. 360 void opIndexAssign(in T val, in size_t r, in size_t c) 361 { 362 _rep[index(r, c)] = val; 363 } 364 365 /// Apply op on a matrix component. 366 void opIndexOpAssign(string op)(in T val, in size_t r, in size_t c) 367 if (op == "+" || op == "-" || op == "*" || op == "/") 368 { 369 mixin("_rep[index(r, c)] "~op~"= val;"); 370 } 371 372 /// Index a matrix row 373 Row opIndex(in size_t r) const 374 { 375 return row(r); 376 } 377 378 /// Assign a row 379 void opIndexAssign(in Row row, in size_t r) { 380 _rep[r*columnLength .. (r+1)*columnLength] = row.data; 381 } 382 383 /// Number of components per direction. 384 size_t opDollar(size_t i)() const 385 { 386 static assert(i < 2, "A matrix only has 2 dimensions."); 387 static if(i == 0) 388 { 389 return rowLength; 390 } 391 else 392 { 393 return columnLength; 394 } 395 } 396 397 /// Add/Subtract by a matrix to its right. 398 auto opBinary(string op, U)(in Mat!(U, rowLength, columnLength) oth) const 399 if ((op == "+" || op == "-") && !is(CommonType!(T, U) == void)) 400 { 401 alias ResMat = Mat!(CommonType!(T, U), rowLength, columnLength); 402 ResMat res = void; 403 static foreach (i; 0 .. rowLength*columnLength) 404 { 405 mixin("res._rep[i] = _rep[i] "~op~" oth._rep[i];"); 406 } 407 return res; 408 } 409 410 /// Multiply by a matrix to its right. 411 auto opBinary(string op, U, size_t UR, size_t UC)(in Mat!(U, UR, UC) oth) const 412 if (op == "*" && columnLength == UR && !is(CommonType!(T, U) == void)) 413 { 414 // multiply the rows of this by the columns of oth. 415 // 1 2 3 7 8 1*7 + 2*9 + 3*3 1*8 + 2*1 + 3*5 416 // 4 5 6 x 9 1 = 4*7 + 5*9 + 6*3 4*8 + 5*9 + 6*3 417 // 3 5 418 419 alias ResMat = Mat!(CommonType!(T, U), rowLength, UC); 420 ResMat res = void; 421 422 // following is tested but reduces performance by an order of magnitude 423 // it vectorizes row * col multiplication but has more indexing and moving of data 424 // import core.simd : float4; 425 // static if (is(T==float) && is(U==float) && R==4 && C==4 && __traits(compiles, float4.init * float4.init)) { 426 // static foreach (r; 0 .. 4) 427 // { 428 // static foreach (c; 0 .. 4) 429 // {{ 430 // float4 thisRow; 431 // float4 othCol; 432 // float4 vecRes; 433 // thisRow.array[0..4] = (ct!r).array; 434 // othCol.array[0..4] = (oth.ctCol!c).array; 435 // vecRes = thisRow * othCol; 436 437 // float[4] arr = vecRes.array; 438 // ResMat.Component resComp = 0; 439 // static foreach (rc; 0 .. columnLength) 440 // { 441 // resComp += arr[rc]; 442 // } 443 // res.ct!(r, c) = resComp; 444 // }} 445 // } 446 // } 447 // else { 448 static foreach(r; 0 .. rowLength) 449 { 450 static foreach (c; 0 .. UC) 451 {{ 452 ResMat.Component resComp = 0; 453 static foreach (rc; 0 .. columnLength) 454 { 455 resComp += ct!(r, rc) * oth.ct!(rc, c); 456 } 457 res.ct!(r, c) = resComp; 458 }} 459 } 460 // } 461 462 return res; 463 } 464 465 /// Multiply a matrix by a vector to its right. 466 auto opBinary(string op, U, size_t N)(in Vec!(U, N) vec) const 467 if (op == "*" && N == columnLength && !is(CommonType!(T, U) == void)) 468 { 469 // same as matrix with one column 470 alias ResVec = Vec!(CommonType!(T, U), rowLength); 471 ResVec res = void; 472 static foreach (r; 0 .. rowLength) 473 {{ 474 ResVec.Component resComp = 0; 475 static foreach (c; 0 .. columnLength) 476 { 477 resComp += ct!(r, c) * vec[c]; 478 } 479 res[r] = resComp; 480 }} 481 return res; 482 } 483 484 /// Multiply a matrix by a vector to its left. 485 auto opBinaryRight(string op, U, size_t N)(in Vec!(U, N) vec) const 486 if (op == "*" && N == rowLength && !is(CommonType!(T, U) == void)) 487 { 488 // same as matrix with one row 489 alias ResVec = Vec!(CommonType!(T, U), columnLength); 490 ResVec res = void; 491 static foreach (c; 0 .. columnLength) 492 {{ 493 ResVec.Component resComp = 0; 494 static foreach (r; 0 .. rowLength) 495 { 496 resComp += vec[r] * ct!(r, c); 497 } 498 res[c] = resComp; 499 }} 500 return res; 501 } 502 503 504 /// Operation of a matrix with a scalar on its right. 505 auto opBinary(string op, U)(in U val) const 506 if ((op == "+" || op == "-" || op == "*" || op == "/") && 507 !is(CommonType!(T, U) == void)) 508 { 509 // import core.simd; 510 511 alias ResT = CommonType!(T, U); 512 alias ResMat = Mat!(ResT, rowLength, columnLength); 513 ResMat res = void; 514 515 // static if ((compLength%8)==0 && hasSIMD && hasAVX && is(ResT == float) && is(typeof(mixin("float8.init"~op~"val")))) { 516 // simdScalarOp!(ResMat, U, float8, 8, "vec"~op~"val")(res, val); 517 // } 518 // else static if ((compLength%4)==0 && hasSIMD && is(ResT == float) && is(typeof(mixin("float4.init"~op~"val")))) { 519 // simdScalarOp!(ResMat, U, float4, 4, "vec"~op~"val")(res, val); 520 // } 521 // else { 522 static foreach (i; 0 .. compLength) { 523 mixin("res._rep[i] = _rep[i] "~op~" val;"); 524 } 525 // } 526 return res; 527 } 528 529 /// Operation of a matrix with a scalar on its left. 530 auto opBinaryRight(string op, U)(in U val) const 531 if ((op == "+" || op == "-" || op == "*" || op == "/") && 532 !is(CommonType!(T, U) == void)) 533 { 534 // import core.simd; 535 536 alias ResT = CommonType!(T, U); 537 alias ResMat = Mat!(ResT, rowLength, columnLength); 538 ResMat res = void; 539 540 // static if ((compLength%8)==0 && hasSIMD && hasAVX && is(ResT == float) && is(typeof(mixin("val"~op~"float8.init")))) { 541 // simdScalarOp!(ResMat, U, float8, 8, "val"~op~"vec")(res, val); 542 // } 543 // else static if ((compLength%4)==0 && hasSIMD && is(ResT == float) && is(typeof(mixin("val"~op~"float4.init")))) { 544 // simdScalarOp!(ResMat, U, float4, 4, "val"~op~"vec")(res, val); 545 // } 546 // else { 547 static foreach (i; 0 .. compLength) { 548 mixin("res._rep[i] = val "~op~" _rep[i];"); 549 } 550 // } 551 return res; 552 } 553 554 /// Assign operation of a matrix with a scalar on its right. 555 auto opOpAssign(string op, U)(in U val) 556 if ((op == "+" || op == "-" || op == "*" || op == "/") && 557 !is(CommonType!(T, U) == void)) 558 { 559 static foreach (i; 0 .. compLength) { 560 mixin("_rep[i] "~op~"= val;"); 561 } 562 return this; 563 } 564 565 /// Assign operation of a matrix with a matrix on its right. 566 auto opOpAssign(string op, M)(in M mat) 567 if ((op == "+" || op == "-" || op == "*") && is(typeof(mixin("this"~op~"mat")))) 568 { 569 const newThis = mixin("this "~op~" mat"); 570 _rep = newThis._rep; 571 return this; 572 } 573 574 string toString() const 575 { 576 /// [ 577 /// [ 1.0000, 2.0000 ], 578 /// [ 3.0000, 4.0000 ] 579 /// ] 580 import std.format : format; 581 582 string res = "[\n"; 583 static foreach (r; 0 .. rowLength) 584 {{ 585 import std.traits : isFloatingPoint; 586 587 static if (isFloatingPoint!T) { 588 enum fmt = " [ %(%#10.4f%|, %) ]"; 589 } 590 else { 591 enum fmt = " [ %(% 10s%|, %) ]"; 592 } 593 res ~= format(fmt, _rep[r*columnLength .. (r+1)*columnLength]); 594 if (r != rowLength-1) res ~= ",\n"; 595 else res ~= "\n"; 596 }} 597 return res ~ "]"; 598 } 599 600 private static @property ctIndex(size_t r, size_t c)() 601 { 602 static assert(r < rowLength && c < columnLength); 603 return r * columnLength + c; 604 } 605 606 private static size_t index(in size_t r, in size_t c) 607 { 608 assert(r < rowLength && c < columnLength); 609 return r * columnLength + c; 610 } 611 612 private static @property string identityCode() 613 { 614 string code = "Mat ("; 615 foreach (r; 0 .. rowLength) 616 { 617 foreach (c; 0 .. columnLength) 618 { 619 code ~= r == c ? "1, " : "0, "; 620 } 621 } 622 return code ~ ")"; 623 } 624 625 private void simdScalarOp(M, U, SimdVecT, size_t size, string expr)(ref M res, in U val) const 626 { 627 static assert( (compLength % size) == 0 ); 628 SimdVecT vec = void; 629 static foreach (i; 0 .. compLength/size) {{ 630 enum start = i*size; enum end = start+size; 631 vec.array[0 .. size] = _rep[start .. end]; 632 vec = mixin(expr); 633 res._rep[start .. end] = vec.array[0 .. size]; 634 }} 635 } 636 } 637 638 /// 639 unittest 640 { 641 import gfx.math.approx : approxUlp; 642 643 assert(approxUlp(FMat2x2.identity, FMat2x2( 644 1, 0, 645 0, 1 646 ))); 647 } 648 649 /// 650 unittest 651 { 652 import gfx.math.approx : approxUlp; 653 654 immutable ml = Mat!(float, 2, 3)( 655 1, 2, 3, 656 4, 5, 6, 657 ); 658 immutable mr = Mat!(float, 3, 2)( 659 1, 2, 660 3, 4, 661 5, 6, 662 ); 663 immutable exp = Mat!(float, 2, 2)( 664 1+6+15, 2+8+18, 665 4+15+30, 8+20+36 666 ); 667 assert(approxUlp(ml * mr, exp)); 668 } 669 670 /// Give the transposed form of a matrix. 671 auto transpose(M)(in M mat) if (isMat!M) 672 { 673 alias T = M.Component; 674 enum R = M.rowLength; 675 enum C = M.columnLength; 676 677 Mat!(T, C, R) res = void; 678 static foreach (r; 0 .. R) { 679 static foreach (c; 0 .. C) { 680 res[c, r] = mat[r, c]; 681 } 682 } 683 return res; 684 } 685 686 /// Compute the inverse of a matrix with Gaussian elimination method. 687 /// Complexity O(n3). 688 @property M gaussianInverse(M)(in M mat) 689 if (isMat!M) 690 { 691 import std.traits : isFloatingPoint; 692 693 static assert(isFloatingPoint!(M.Component), "gaussianInverse only works with floating point matrices"); 694 static assert(M.rowLength == M.columnLength, "gaussianInverse only works with square matrices"); 695 696 alias T = M.Component; 697 enum N = M.rowLength; 698 699 auto pivot = mat ~ Mat!(real, N, N).identity; 700 static assert(is(pivot.Component == real)); 701 ptrdiff_t pivotR = -1; 702 static foreach (c; 0 .. N) 703 {{ 704 // find the max row of column c. 705 auto colMax = pivot[pivotR+1, c]; 706 ptrdiff_t maxR = pivotR+1; 707 foreach (r; pivotR+2 .. N) 708 { 709 const val = pivot[r, c]; 710 if (val > colMax) 711 { 712 maxR = r; 713 colMax = val; 714 } 715 } 716 if (colMax != 0) 717 { 718 pivotR += 1; 719 // normalizing the row where the max was found 720 static foreach (cc; 0 .. 2*N) 721 { 722 pivot[maxR, cc] /= colMax; 723 } 724 // switching pivot row with the max row 725 if (pivotR != maxR) 726 { 727 static foreach (cc; 0 .. 2*N) 728 {{ 729 const swapTmp = pivot[maxR, cc]; 730 pivot[maxR, cc] = pivot[pivotR, cc]; 731 pivot[pivotR, cc] = swapTmp; 732 }} 733 } 734 static foreach (r; 0 .. N) 735 { 736 if (r != pivotR) 737 { 738 const fact = pivot.ct!(r, c); 739 if (fact != 0) 740 { 741 static foreach (cc; 0 .. 2*N) 742 { 743 pivot.ct!(r, cc) = pivot.ct!(r, cc) - fact * pivot[pivotR, cc]; 744 } 745 } 746 } 747 } 748 } 749 }} 750 return cast(Mat!(T, N, N)) pivot.slice!(0, N, N, 2*N); 751 } 752 753 /// 754 unittest 755 { 756 /// Example from https://en.wikipedia.org/wiki/Gaussian_elimination 757 const m = FMat3( 758 2, -1, 0, 759 -1, 2, -1, 760 0, -1, 2 761 ); 762 const invM = gaussianInverse(m); 763 764 import gfx.math.approx : approxUlp; 765 assert(approxUlp(invM, FMat3( 766 0.75f, 0.5f, 0.25f, 767 0.5f, 1f, 0.5f, 768 0.25f, 0.5f, 0.75f 769 ))); 770 assert(approxUlp(gaussianInverse(invM), m)); 771 } 772 773 774 /// Check whether MatT is a Mat 775 template isMat(MatT) 776 { 777 import std.traits : TemplateOf; 778 static if (is(typeof(__traits(isSame, TemplateOf!MatT, Mat)))) 779 { 780 enum isMat = __traits(isSame, TemplateOf!MatT, Mat); 781 } 782 else 783 { 784 enum isMat = false; 785 } 786 } 787 788 /// Check whether MatT is a Mat with R rows and C columns 789 template isMat(size_t R, size_t C, MatT) 790 { 791 static if (isMat!MatT) 792 { 793 enum isMat = MatT.rowLength == R && MatT.columnLength == C; 794 } 795 else 796 { 797 enum isMat = false; 798 } 799 } 800 801 /// Check whether M is 2x2 matrix 802 enum isMat2(M) = isMat!(2, 2, M); 803 /// Check whether M is 3x3 matrix 804 enum isMat3(M) = isMat!(3, 3, M); 805 /// Check whether M is 4x4 matrix 806 enum isMat4(M) = isMat!(4, 4, M); 807 808 /// Check if all types of MatSeq are instantiation of Mat 809 template areMat(MatSeq...) 810 { 811 enum areMat = allSatisfy!(isMat, MatSeq); 812 } 813 814 /// Check if all types of MatSeq are instantiation of Mat with R rows and C columns 815 template areMat(size_t R, size_t C, MatSeq...) 816 { 817 static assert(MatSeq.length > 0); 818 static if (MatSeq.length == 1) 819 { 820 enum areMat = isMat!(R, C, MatSeq[0]); 821 } 822 else 823 { 824 enum areMat = isMat!(R, C, MatSeq[0]) && areMat!(R, C, MatSeq[1 .. $]); 825 } 826 } 827 828 private: 829 830 831 version (D_SIMD) { enum hasSIMD = true; } else { enum hasSIMD = false; } 832 version (D_AVX) { enum hasAVX = true; } else { enum hasAVX = false; } 833 version (D_AVX2) { enum hasAVX2 = true; } else { enum hasAVX2 = false; } 834 835 836 /// Return a sequence of vectors and matrices as a flat tuple of rows. 837 template rowTuple(Rows...) 838 { 839 auto rowTuple(Rows rows) 840 { 841 import gfx.math.vec : isVec; 842 import std.traits : isStaticArray; 843 import std.typecons : tuple; 844 845 static if (Rows.length == 0) { 846 return tuple(); 847 } 848 else static if (Rows.length == 1) { 849 static if (isStaticArray!(Rows[0])) { 850 return tuple(vec(rows[0])); 851 } 852 else static if (isVec!(Rows[0])) { 853 return tuple(rows[0]); 854 } 855 else static if (isMat!(Rows[0])) { 856 return rows[0].rowTup; 857 } 858 else { 859 static assert(false, "only vectors and matrices allowed in rowTuple"); 860 } 861 } 862 else static if (Rows.length > 1) { 863 return tuple( 864 rowTuple(rows[0]).expand, rowTuple(rows[1 .. $]).expand 865 ); 866 } 867 else { 868 static assert(false, "wrong arguments to rowTuple"); 869 } 870 } 871 } 872 873 874 /// The type of row tuple from a sequence of vectors and tuples 875 template RowTuple (Rows...) 876 { 877 alias RowTuple = typeof(rowTuple(Rows.init)); 878 } 879 880 /// The number of rows in a row sequence 881 template rowLength (Rows...) 882 { 883 enum rowLength = RowTuple!(Rows).length; 884 } 885 886 /// Check whether a row sequence has a consistent length. 887 /// (i.e. all rows have the same length). 888 template hasConsistentLength (Rows...) 889 { 890 import std.meta : allSatisfy; 891 892 alias Tup = RowTuple!Rows; 893 template lengthOK(R) 894 { 895 enum lengthOK = R.length == Tup[0].length; 896 } 897 enum hasConsistentLength = allSatisfy!(lengthOK, Tup.Types[1 .. $]); 898 }