1 /** 2 Matrix LU decomposition module. 3 */ 4 module karasux.linear_algebra.lu; 5 6 import std.algorithm : swap; 7 import std.traits : isNumeric; 8 9 import karasux.linear_algebra.matrix : Matrix; 10 import karasux.linear_algebra.vector : Vector; 11 12 @safe: 13 14 /** 15 LU decomposition matrices. 16 */ 17 struct LUDecomposition(size_t N, E) if (isNumeric!E) 18 { 19 /** 20 Matrix type. 21 */ 22 alias Mat = Matrix!(N, N, E); 23 24 /** 25 Vector type. 26 */ 27 alias Vec = Vector!(N, E); 28 29 @disable this(); 30 31 /** 32 Initialize by matrix. 33 34 Params: 35 m = target matrix. 36 */ 37 this()(auto scope ref const(Mat) m) @nogc nothrow pure scope 38 { 39 m.createPivots(this.pivot_); 40 41 Mat swapped; 42 foreach (i, pos; pivot_) 43 { 44 foreach (j; 0 .. N) 45 { 46 swapped[pos, j] = m[i, j]; 47 } 48 } 49 50 swapped.createLUDecomposition(this.l_, this.u_); 51 } 52 53 /** 54 Create inverse matrix. 55 56 Params: 57 dest = destination matrix. 58 Returns: 59 destination matrix reference. 60 */ 61 ref Mat createInverse(return scope ref Mat dest) const @nogc nothrow pure scope 62 { 63 Mat inverseL; 64 l_.inverseLMatrix(inverseL); 65 66 Mat inverseU; 67 u_.inverseUMatrix(inverseU); 68 69 Mat inverseLUP; 70 inverseLUP.mul(inverseU, inverseL); 71 72 foreach (i; 0 .. N) 73 { 74 foreach (j; 0 .. N) 75 { 76 dest[i, j] = inverseLUP[i, pivot_[j]]; 77 } 78 } 79 80 return dest; 81 } 82 83 /** 84 Create inverse matrix. 85 86 Returns: 87 destination matrix reference. 88 */ 89 Mat createInverse() const @nogc nothrow pure scope 90 { 91 Mat dest; 92 return createInverse(dest); 93 } 94 95 /** 96 Solve equation. 97 98 Params: 99 y = equation Y. 100 x = solved X. 101 Returns: 102 reference to X. 103 */ 104 ref Vec solve()(auto scope ref const(Vec) y, return scope ref Vec x) const @nogc nothrow pure scope 105 { 106 Vec swapped; 107 foreach (i, pos; pivot_) 108 { 109 swapped[i] = y[pos]; 110 } 111 112 Vec a; 113 l_.solveByLMatrix(swapped, a); 114 u_.solveByUMatrix(a, x); 115 return x; 116 } 117 118 /** 119 Solve equation. 120 121 Params: 122 y = equation Y. 123 Returns: 124 solved X. 125 */ 126 Vec solve()(auto scope ref const(Vec) y) const @nogc nothrow pure scope 127 { 128 Vec x; 129 return solve(y, x); 130 } 131 132 private: 133 Mat l_; 134 Mat u_; 135 size_t[N] pivot_; 136 } 137 138 /** 139 Construct LU decomposition matrices. 140 141 Params: 142 N = dimensions. 143 E = element type. 144 m = target matrix. 145 Returns: 146 LU decomposition matrices. 147 */ 148 auto luDecomposition(size_t N, E)(auto scope ref const(Matrix!(N, N, E)) m) @nogc nothrow pure 149 if (isNumeric!E) 150 { 151 return LUDecomposition!(N, E)(m); 152 } 153 154 /// 155 @nogc nothrow pure @safe unittest 156 { 157 import karasux.linear_algebra.matrix : isClose; 158 enum N = 4; 159 160 immutable m = Matrix!(N, N).fromRows([ 161 [5.0, 6.0, 7.0, 8.0], 162 [10.0, 21.0, 24.0, 27.0], 163 [15.0, 54.0, 73.0, 81.0], 164 [25.0, 84.0, 179.0, 211.0], 165 ]); 166 immutable lu = m.luDecomposition(); 167 168 immutable inverse = lu.createInverse(); 169 auto result = typeof(lu).Mat(); 170 result.mul(inverse, m); 171 assert(result.isUnitMatrix); 172 } 173 174 /// 175 @nogc nothrow pure @safe unittest 176 { 177 import karasux.linear_algebra.vector : isClose; 178 enum N = 4; 179 180 immutable m = Matrix!(N, N).fromRows([ 181 [5.0, 6.0, 7.0, 8.0], 182 [10.0, 21.0, 24.0, 27.0], 183 [15.0, 54.0, 73.0, 81.0], 184 [25.0, 84.0, 179.0, 211.0], 185 ]); 186 immutable lu = m.luDecomposition(); 187 188 immutable y = typeof(lu).Vec([3.0, 5.0, 4.0, -5.0]); 189 immutable x = lu.solve(y); 190 191 typeof(lu).Vec result; 192 result.mul(m, x); 193 assert(result.isClose(y)); 194 } 195 196 private: 197 198 version(unittest) 199 { 200 bool isUnitMatrix(E, size_t N)(auto scope ref const(Matrix!(N, N, E)) m) @nogc nothrow pure @safe 201 { 202 import std.math : isClose; 203 204 foreach (i; 0 .. N) 205 { 206 foreach (j; 0 .. N) 207 { 208 if (!m[i, j].isClose((i == j) ? 1.0 : 0.0, 1e-4, 1e-4)) 209 { 210 return false; 211 } 212 } 213 } 214 215 return true; 216 } 217 } 218 219 /** 220 Create matrix pivots. 221 222 Params: 223 m = target matrix. 224 pivots = pivots indicies. 225 */ 226 void createPivots(size_t N, E)( 227 auto scope ref const(Matrix!(N, N, E)) m, 228 scope ref size_t[N] pivots) 229 if (isNumeric!E) 230 { 231 foreach (i, ref e; pivots) 232 { 233 e = i; 234 } 235 236 foreach (i; 0 .. N) 237 { 238 size_t maxPos = i; 239 E maxValue = m[maxPos, i]; 240 foreach (j; (i + 1) .. N) 241 { 242 immutable value = m[pivots[j], i]; 243 if (maxValue < value) 244 { 245 maxPos = j; 246 maxValue = value; 247 } 248 } 249 250 swap(pivots[i], pivots[maxPos]); 251 } 252 } 253 254 /// 255 @nogc nothrow pure @safe unittest 256 { 257 enum N = 2; 258 alias Mat = Matrix!(N, N); 259 260 size_t[N] pivots; 261 auto m = Mat.fromRows([ 262 [1.0f, 2.0f], 263 [2.0f, 1.0f]]); 264 265 m.createPivots(pivots); 266 assert(pivots == [1, 0]); 267 268 m = Mat.fromRows([ 269 [1.0f, 1.0f], 270 [2.0f, 3.0f]]); 271 272 m.createPivots(pivots); 273 assert(pivots == [1, 0]); 274 } 275 276 /// 277 @nogc nothrow pure @safe unittest 278 { 279 enum N = 3; 280 alias Mat = Matrix!(N, N); 281 282 size_t[N] pivots; 283 auto m = Mat.fromRows([ 284 [1.0f, 2.0f, 3.0f], 285 [2.0f, 1.0f, 4.0f], 286 [3.0f, 4.0f, 0.0f], 287 ]); 288 289 m.createPivots(pivots); 290 assert(pivots == [2, 0, 1]); 291 } 292 293 /** 294 LU decomposition. 295 296 Params: 297 m = target matrix. 298 l = L destination matrix. 299 u = U destination matrix. 300 */ 301 void createLUDecomposition(size_t N, E)( 302 auto scope ref const(Matrix!(N, N, E)) m, 303 scope ref Matrix!(N, N, E) l, 304 scope ref Matrix!(N, N, E) u) 305 if (isNumeric!E) 306 { 307 // initialize L first row. 308 l[0, 0] = E(1); 309 foreach (column; 1 .. N) 310 { 311 l[0, column] = E(0); 312 } 313 314 // initialize U first row. 315 foreach (column; 0 .. N) 316 { 317 u[0, column] = m[0, column]; 318 } 319 320 auto m00 = m[0, 0]; 321 foreach (row; 1 .. N) 322 { 323 // clear L fixed cells. 324 l[row, row] = E(1); 325 foreach (column; row + 1 .. N) 326 { 327 l[row, column] = E(0); 328 } 329 330 // clear R fiexd cells. 331 foreach (column; 0 .. row) 332 { 333 u[row, column] = E(0); 334 } 335 336 // setting up L first column. 337 l[row, 0] = m[row, 0] / m00; 338 339 // calculate L columns. 340 foreach (column; 1 .. row) 341 { 342 E sum = E(0); 343 foreach (lc; 0 .. column) 344 { 345 sum += l[row, lc] * u[lc, column]; 346 } 347 l[row, column] = (m[row, column] - sum) / u[column, column]; 348 } 349 350 // calculate U columns. 351 foreach (column; row .. N) 352 { 353 E sum = E(0); 354 foreach (lc; 0 .. row) 355 { 356 sum += l[row, lc] * u[lc, column]; 357 } 358 u[row, column] = m[row, column] - sum; 359 } 360 } 361 } 362 363 /// 364 @nogc nothrow pure @safe unittest 365 { 366 import karasux.linear_algebra.matrix : isClose; 367 368 immutable m = Matrix!(2, 2).fromRows([ 369 [4.0f, 3.0f], 370 [6.0f, 3.0f]]); 371 auto l = Matrix!(2, 2)(); 372 auto u = Matrix!(2, 2)(); 373 m.createLUDecomposition(l, u); 374 375 assert(l.isClose(Matrix!(2, 2).fromRows([ 376 [1.0, 0.0], 377 [1.5, 1.0] 378 ]))); 379 assert(u.isClose(Matrix!(2, 2).fromRows([ 380 [4.0, 3.0], 381 [0.0, -1.5] 382 ]))); 383 384 auto mul = Matrix!(2, 2)(); 385 mul.mul(l, u); 386 assert(mul.isClose(m)); 387 } 388 389 @nogc nothrow pure @safe unittest 390 { 391 import karasux.linear_algebra.matrix : isClose; 392 393 immutable m = Matrix!(3, 3).fromRows([ 394 [5.0, 6.0, 7.0], 395 [10.0, 20.0, 23.0], 396 [15.0, 50.0, 67.0], 397 ]); 398 auto l = Matrix!(3, 3)(); 399 auto u = Matrix!(3, 3)(); 400 m.createLUDecomposition(l, u); 401 assert(l.isClose(Matrix!(3, 3).fromRows([ 402 [1.0, 0.0, 0.0], 403 [2.0, 1.0, 0.0], 404 [3.0, 4.0, 1.0], 405 ]))); 406 assert(u.isClose(Matrix!(3, 3).fromRows([ 407 [5.0, 6.0, 7.0], 408 [0.0, 8.0, 9.0], 409 [0.0, 0.0, 10.0], 410 ]))); 411 412 auto mul = Matrix!(3, 3)(); 413 mul.mul(l, u); 414 assert(mul.isClose(m)); 415 } 416 417 @nogc nothrow pure @safe unittest 418 { 419 import karasux.linear_algebra.matrix : isClose; 420 421 immutable m = Matrix!(4, 4).fromRows([ 422 [5.0, 6.0, 7.0, 8.0], 423 [10.0, 21.0, 24.0, 27.0], 424 [15.0, 54.0, 73.0, 81.0], 425 [25.0, 84.0, 179.0, 211.0], 426 ]); 427 auto l = Matrix!(4, 4)(); 428 auto u = Matrix!(4, 4)(); 429 m.createLUDecomposition(l, u); 430 assert(l.isClose(Matrix!(4, 4).fromRows([ 431 [1.0, 0.0, 0.0, 0.0], 432 [2.0, 1.0, 0.0, 0.0], 433 [3.0, 4.0, 1.0, 0.0], 434 [5.0, 6.0, 7.0, 1.0], 435 ]))); 436 assert(u.isClose(Matrix!(4, 4).fromRows([ 437 [5.0, 6.0, 7.0, 8.0], 438 [0.0, 9.0, 10.0, 11.0], 439 [0.0, 0.0, 12.0, 13.0], 440 [0.0, 0.0, 0.0, 14.0], 441 ]))); 442 443 auto mul = Matrix!(4, 4)(); 444 mul.mul(l, u); 445 assert(mul.isClose(m)); 446 } 447 448 /** 449 Inverse lower triangle matrix. 450 451 Params: 452 N = matrix dimensions. 453 E = matrix element. 454 l = lower triangle matrix. 455 inverse = inverse matrix. 456 */ 457 void inverseLMatrix(size_t N, E)( 458 scope ref const(Matrix!(N, N, E)) l, 459 scope ref Matrix!(N, N, E) inverse) 460 if (isNumeric!E) 461 { 462 // for each inverse element. 463 foreach (i; 0 .. N) 464 { 465 foreach (j; 0 .. i) 466 { 467 auto sum = E(0); 468 foreach (k; j .. i) 469 { 470 sum += l[i, k] * inverse[k, j]; 471 } 472 473 inverse[i, j] = -(sum / l[i, i]); 474 } 475 476 // diagonal element. 477 inverse[i, i] = E(1) / l[i, i]; 478 479 // fill uppder triangle elements to 0. 480 foreach (j; (i + 1) .. N) 481 { 482 inverse[i, j] = E(0); 483 } 484 } 485 } 486 487 @nogc nothrow pure @safe unittest 488 { 489 enum N = 1; 490 alias Mat = Matrix!(N, N); 491 492 immutable m = Mat.fromRows([[5.0]]); 493 auto inverse = Mat(); 494 m.inverseLMatrix(inverse); 495 496 auto result = Mat(); 497 result.mul(inverse, m); 498 assert(result.isUnitMatrix); 499 } 500 501 @nogc nothrow pure @safe unittest 502 { 503 enum N = 2; 504 alias Mat = Matrix!(N, N); 505 506 immutable m = Mat.fromRows([ 507 [5.0, 0.0], 508 [6.0, 7.0], 509 ]); 510 auto inverse = Mat(); 511 m.inverseLMatrix(inverse); 512 513 auto result = Mat(); 514 result.mul(inverse, m); 515 assert(result.isUnitMatrix); 516 } 517 518 @nogc nothrow pure @safe unittest 519 { 520 enum N = 3; 521 alias Mat = Matrix!(N, N); 522 523 immutable m = Mat.fromRows([ 524 [5.0, 0.0, 0.0], 525 [6.0, 7.0, 0.0], 526 [8.0, 9.0, 10.0], 527 ]); 528 auto inverse = Mat(); 529 m.inverseLMatrix(inverse); 530 531 auto result = Mat(); 532 result.mul(inverse, m); 533 assert(result.isUnitMatrix); 534 } 535 536 @nogc nothrow pure @safe unittest 537 { 538 enum N = 4; 539 alias Mat = Matrix!(N, N); 540 541 immutable m = Mat.fromRows([ 542 [5.0, 0, 0, 0], 543 [10.0, 21.0, 0, 0], 544 [15.0, 54.0, 73.0, 0], 545 [25.0, 84.0, 179.0, 211.0], 546 ]); 547 auto inverse = Mat(); 548 m.inverseLMatrix(inverse); 549 550 auto result = Mat(); 551 result.mul(inverse, m); 552 assert(result.isUnitMatrix); 553 } 554 555 /** 556 Inverse upper triangle matrix. 557 558 Params: 559 N = matrix dimensions. 560 E = matrix element. 561 l = upper triangle matrix. 562 inverse = inverse matrix. 563 */ 564 void inverseUMatrix(size_t N, E)( 565 scope ref const(Matrix!(N, N, E)) u, 566 scope ref Matrix!(N, N, E) inverse) 567 if (isNumeric!E) 568 { 569 // for each inverse element in reverse order. 570 foreach_reverse (i; 0 .. N) 571 { 572 // fill lower triangle elements to 0. 573 foreach (j; 0 .. i) 574 { 575 inverse[i, j] = E(0); 576 } 577 578 // diagonal element. 579 inverse[i, i] = E(1) / u[i, i]; 580 581 foreach (j; (i + 1) .. N) 582 { 583 auto sum = E(0); 584 foreach_reverse (k; (i + 1) .. (j + 1)) 585 { 586 sum += u[i, k] * inverse[k, j]; 587 } 588 589 inverse[i, j] = -(sum / u[i, i]); 590 } 591 } 592 } 593 594 @nogc nothrow pure @safe unittest 595 { 596 enum N = 1; 597 alias Mat = Matrix!(N, N); 598 599 immutable m = Mat.fromRows([[5.0]]); 600 auto inverse = Mat(); 601 m.inverseUMatrix(inverse); 602 603 auto result = Mat(); 604 result.mul(inverse, m); 605 assert(result.isUnitMatrix); 606 } 607 608 @nogc nothrow pure @safe unittest 609 { 610 enum N = 2; 611 alias Mat = Matrix!(N, N); 612 613 immutable m = Mat.fromRows([ 614 [5.0, 6.0], 615 [0.0, 7.0], 616 ]); 617 auto inverse = Mat(); 618 m.inverseUMatrix(inverse); 619 620 auto result = Mat(); 621 result.mul(inverse, m); 622 assert(result.isUnitMatrix); 623 } 624 625 @nogc nothrow pure @safe unittest 626 { 627 enum N = 3; 628 alias Mat = Matrix!(N, N); 629 630 immutable m = Mat.fromRows([ 631 [5.0, 4.0, 3.0], 632 [0.0, 7.0, 8.0], 633 [0.0, 0.0, 10.0], 634 ]); 635 auto inverse = Mat(); 636 m.inverseUMatrix(inverse); 637 638 auto result = Mat(); 639 result.mul(inverse, m); 640 641 assert(result.isUnitMatrix); 642 } 643 644 @nogc nothrow pure @safe unittest 645 { 646 enum N = 4; 647 alias Mat = Matrix!(N, N); 648 649 immutable m = Mat.fromRows([ 650 [2.0, 5.0, 4.0, 3.0], 651 [0.0, 7.0, 8.0, 1.0], 652 [0.0, 0.0, 10.0, 5.0], 653 [0.0, 0.0, 0.0, 5.0], 654 ]); 655 auto inverse = Mat(); 656 m.inverseUMatrix(inverse); 657 658 auto result = Mat(); 659 result.mul(inverse, m); 660 assert(result.isUnitMatrix); 661 } 662 663 /** 664 Solve equation by L matrix. 665 */ 666 void solveByLMatrix(size_t N, E)( 667 auto scope ref const(Matrix!(N, N, E)) l, 668 auto scope ref const(Vector!(N, E)) y, 669 ref Vector!(N, E) x) 670 { 671 foreach (i; 0 .. N) 672 { 673 E sum = E(0); 674 foreach (j; 0 .. i) 675 { 676 sum += l[i, j] * x[j]; 677 } 678 679 x[i] = (y[i] - sum) / l[i, i]; 680 } 681 } 682 683 @nogc nothrow pure @safe unittest 684 { 685 import karasux.linear_algebra.vector : isClose; 686 687 enum N = 1; 688 alias Mat = Matrix!(N, N, double); 689 alias Vec = Vector!(N, double); 690 691 immutable m = Mat.fromRows([ 692 [3.0], 693 ]); 694 immutable y = Vec([12.0]); 695 Vec x; 696 m.solveByLMatrix(y, x); 697 698 assert(x.isClose(Vec([4.0]))); 699 } 700 701 @nogc nothrow pure @safe unittest 702 { 703 import karasux.linear_algebra.vector : isClose; 704 705 enum N = 2; 706 alias Mat = Matrix!(N, N, double); 707 alias Vec = Vector!(N, double); 708 709 immutable m = Mat.fromRows([ 710 [3.0, 0.0], 711 [4.0, 5.0], 712 ]); 713 immutable y = Vec([9, 3]); 714 Vec x; 715 m.solveByLMatrix(y, x); 716 717 assert(x.isClose(Vec([3.0, -9.0 / 5.0]))); 718 } 719 720 /** 721 Solve equation by U matrix. 722 */ 723 void solveByUMatrix(size_t N, E)( 724 auto scope ref const(Matrix!(N, N, E)) l, 725 auto scope ref const(Vector!(N, E)) y, 726 ref Vector!(N, E) x) 727 { 728 foreach_reverse (i; 0 .. N) 729 { 730 E sum = E(0); 731 foreach_reverse (j; (i + 1) .. N) 732 { 733 sum += l[i, j] * x[j]; 734 } 735 736 x[i] = (y[i] - sum) / l[i, i]; 737 } 738 } 739 740 @nogc nothrow pure @safe unittest 741 { 742 import karasux.linear_algebra.vector : isClose; 743 744 enum N = 2; 745 alias Mat = Matrix!(N, N, double); 746 alias Vec = Vector!(N, double); 747 748 immutable m = Mat.fromRows([ 749 [3.0, 4.0], 750 [0.0, 5.0], 751 ]); 752 immutable y = Vec([9, 3]); 753 Vec x; 754 m.solveByUMatrix(y, x); 755 756 assert(x.isClose(Vec([2.2, 0.6]))); 757 } 758