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