1 /++ 2 $(H1 Lubeck - Linear Algebra) 3 4 Authors: Ilya Yaroshenko, Lars Tandle Kyllingstad (SciD author) 5 +/ 6 module lubeck; 7 8 import cblas : Diag; 9 import mir.blas; 10 import mir.lapack; 11 import mir.math.common; 12 import mir.ndslice.allocation; 13 import mir.ndslice.dynamic: transposed; 14 import mir.ndslice.slice; 15 import mir.ndslice.topology; 16 import mir.utility; 17 import std.meta; 18 import std.traits; 19 import std.typecons: Flag, Yes, No; 20 21 version(LDC) 22 import ldc.attributes: fastmath; 23 else 24 enum { fastmath }; 25 26 private template IterationType(Iterator) 27 { 28 alias T = Unqual!(typeof(Iterator.init[0])); 29 static if (isIntegral!T || is(T == real)) 30 alias IterationType = double; 31 else 32 static if (is(T == creal)) 33 alias IterationType = cdouble; 34 else 35 { 36 static assert( 37 is(T == double) || 38 is(T == float) || 39 is(T == cdouble) || 40 is(T == cfloat)); 41 alias IterationType = T; 42 } 43 } 44 45 private alias BlasType(Iterators...) = 46 CommonType!(staticMap!(IterationType, Iterators)); 47 48 /++ 49 General matrix-matrix multiplication. Allocates result to an uninitialized slice using GC. 50 Params: 51 a = m(rows) x k(cols) matrix 52 b = k(rows) x n(cols) matrix 53 Result: 54 m(rows) x n(cols) 55 +/ 56 Slice!(Contiguous, [2], BlasType!(IteratorA, IteratorB)*) 57 mtimes(SliceKind kindA, IteratorA, SliceKind kindB, IteratorB)( 58 Slice!(kindA, [2], IteratorA) a, 59 Slice!(kindB, [2], IteratorB) b) 60 { 61 assert(a.length!1 == b.length!0); 62 63 // reallocate data if required 64 alias A = BlasType!IteratorA; 65 alias B = BlasType!IteratorB; 66 alias C = CommonType!(A, B); 67 static if (!is(Unqual!IteratorA == C*)) 68 return .mtimes(a.as!C.slice, b); 69 else 70 static if (!is(Unqual!IteratorB == C*)) 71 return .mtimes(a, b.as!C.slice); 72 else 73 { 74 static if (kindA != Contiguous) 75 if (a._stride!0 != 1 && a._stride!1 != 1 76 || a._stride!0 <= 0 77 || a._stride!1 <= 0) 78 return .mtimes(a.slice, b); 79 80 static if (kindB != Contiguous) 81 if (b._stride!0 != 1 && b._stride!1 != 1 82 || b._stride!0 <= 0 83 || b._stride!1 <= 0) 84 return .mtimes(a, b.slice); 85 86 auto c = uninitSlice!C(a.length!0, b.length!1); 87 88 if (a.length!1 == 1 && b.length!0 == 1) 89 { 90 c[] = cast(C) 0; 91 ger(cast(C)1, a.front!1, b.front, c); 92 } 93 else 94 { 95 gemm(cast(C)1, a, b, cast(C)0, c); 96 } 97 return c; 98 } 99 } 100 101 /// 102 unittest 103 { 104 auto a = 105 [-5, 1, 7, 7, -4, 106 -1, -5, 6, 3, -3, 107 -5, -2, -3, 6, 0].sliced(3, 5); 108 109 auto b = slice!double(5, 4); 110 b[] = 111 [[-5, -3, 3, 1], 112 [ 4, 3, 6, 4], 113 [-4, -2, -2, 2], 114 [-1, 9, 4, 8], 115 [ 9, 8, 3, -2]]; 116 117 assert(mtimes(a, b) == 118 [[-42, 35, -7, 77], 119 [-69, -21, -42, 21], 120 [ 23, 69, 3, 29]] 121 ); 122 } 123 124 /// ger specialized case in mtimes 125 unittest 126 { 127 // from https://github.com/kaleidicassociates/lubeck/issues/8 128 { 129 auto a = [1.0f, 2.0f].sliced(2, 1); 130 auto b = [1.0f, 2.0f].sliced(2, 1); 131 assert(mtimes(a, b.transposed) == [[1, 2], [2, 4]]); 132 } 133 { 134 auto a = [1.0, 2.0].sliced(1, 2); 135 auto b = [1.0, 2.0].sliced(1, 2); 136 assert(mtimes(a.transposed, b) == [[1, 2], [2, 4]]); 137 } 138 } 139 140 /// 141 unittest 142 { 143 // from https://github.com/kaleidicassociates/lubeck/issues/3 144 Slice!(cast(SliceKind)2, [2LU], float*) a = slice!float(1, 1); 145 Slice!(cast(SliceKind)0, [2LU], float*) b1 = slice!float(16, 1).transposed; 146 Slice!(cast(SliceKind)2, [2LU], float*) b2 = slice!float(1, 16); 147 148 a[] = 3; 149 b1[] = 4; 150 b2[] = 4; 151 152 // Confirm that this message does not appear 153 // Outputs: ** On entry to SGEMM parameter number 8 had an illegal value 154 assert(a.mtimes(b1) == a.mtimes(b2)); 155 } 156 157 /++ 158 General matrix-matrix multiplication. Allocates result to an uninitialized slice using GC. 159 Params: 160 a = m(rows) x k(cols) matrix 161 b = k(rows) x 1(cols) vector 162 Result: 163 m(rows) x 1(cols) 164 +/ 165 Slice!(Contiguous, [1], BlasType!(IteratorA, IteratorB)*) 166 mtimes(SliceKind kindA, IteratorA, SliceKind kindB, IteratorB)( 167 Slice!(kindA, [2], IteratorA) a, 168 Slice!(kindB, [1], IteratorB) b) 169 { 170 assert(a.length!1 == b.length!0); 171 172 // reallocate data if required 173 alias A = BlasType!IteratorA; 174 alias B = BlasType!IteratorB; 175 alias C = CommonType!(A, B); 176 static if (!is(Unqual!IteratorA == C*)) 177 return .mtimes(a.as!C.slice, b); 178 else 179 static if (!is(Unqual!IteratorB == C*)) 180 return .mtimes(a, b.as!C.slice); 181 else 182 { 183 static if (kindA != Contiguous) 184 if (a._stride!0 != 1 && a._stride!1 != 1 185 || a._stride!0 <= 0 186 || a._stride!1 <= 0) 187 return .mtimes(a.slice, b); 188 189 static if (kindB != Contiguous) 190 if (b._stride!1 <= 0) 191 return .mtimes(a, b.slice); 192 193 auto c = uninitSlice!C(a.length!0); 194 gemv(cast(C)1, a, b, cast(C)0, c); 195 return c; 196 } 197 } 198 199 /++ 200 General matrix-matrix multiplication. 201 Params: 202 a = 1(rows) x k(cols) vector 203 b = k(rows) x n(cols) matrix 204 Result: 205 1(rows) x n(cols) 206 +/ 207 Slice!(Contiguous, [1], BlasType!(IteratorA, IteratorB)*) 208 mtimes(SliceKind kindA, IteratorA, SliceKind kindB, IteratorB)( 209 Slice!(kindB, [1], IteratorB) a, 210 Slice!(kindA, [2], IteratorA) b, 211 ) 212 { 213 return .mtimes(b.universal.transposed, a); 214 } 215 216 /// 217 unittest 218 { 219 auto a = 220 [-5, 1, 7, 7, -4, 221 -1, -5, 6, 3, -3, 222 -5, -2, -3, 6, 0] 223 .sliced(3, 5) 224 .universal 225 .transposed; 226 227 auto b = slice!double(5); 228 b[] = [-5, 4,-4,-1, 9]; 229 230 assert(mtimes(b, a) == [-42, -69, 23]); 231 } 232 233 /++ 234 Vector-vector multiplication (dot product). 235 Params: 236 a = 1(rows) x k(cols) vector 237 b = k(rows) x 1(cols) matrix 238 Result: 239 scalar 240 +/ 241 CommonType!(BlasType!IteratorA, BlasType!IteratorB) 242 mtimes(SliceKind kindA, IteratorA, SliceKind kindB, IteratorB)( 243 Slice!(kindB, [1], IteratorB) a, 244 Slice!(kindA, [1], IteratorA) b, 245 ) 246 { 247 alias A = BlasType!IteratorA; 248 alias B = BlasType!IteratorB; 249 alias C = CommonType!(A, B); 250 static if (is(IteratorB == C*) && is(IteratorA == C*)) 251 { 252 return dot(a, b); 253 } 254 else 255 { 256 auto c = cast(typeof(return)) 0; 257 import mir.ndslice.algorithm: reduce; 258 return c.reduce!"a + b * c"(a.as!(typeof(return)), b.as!(typeof(return))); 259 } 260 } 261 262 /// 263 unittest 264 { 265 auto a = [1, 2, 4].sliced; 266 auto b = [3, 4, 2].sliced; 267 assert(a.mtimes(b) == 19); 268 } 269 270 /++ 271 Calculates the inverse of a matrix. 272 +/ 273 auto inv(SliceKind kind, Iterator)(Slice!(kind, [2], Iterator) a) 274 in 275 { 276 assert (a.length!0 == a.length!1, "matrix must be square"); 277 } 278 body 279 { 280 alias T = BlasType!Iterator; 281 282 auto m = a.as!T.slice.canonical; 283 auto ipiv = m.length.uninitSlice!lapackint; 284 285 auto info = getrf!T(m, ipiv); 286 if (info == 0) 287 { 288 info = getri(m, ipiv, m.getri_wq.uninitSlice!T); 289 } 290 291 import std.exception: enforce; 292 enforce(info == 0, "inv: matrix is singular"); 293 return m; 294 } 295 296 /// 297 unittest 298 { 299 auto a = [ 300 1, 0, 2, 301 2, 2, 0, 302 0, 1, 1] 303 .sliced(3, 3); 304 305 enum : double { _13 = 1.0/3.0, _16 = 1.0/6.0, _23 = 2.0/3.0 } 306 auto ans = [ 307 _13, _13, -_23, 308 -_13,_16, _23, 309 _13, -_16, _13] 310 .sliced(a.shape); 311 312 import mir.ndslice.algorithm: equal; 313 import std.math: approxEqual; 314 assert(equal!((a, b) => a.approxEqual(b, 1e-10L, 1e-10L))(a.inv, ans)); 315 assert(equal!((a, b) => a.approxEqual(b, 1e-10L, 1e-10L))(a.as!cdouble.inv.as!double, ans)); 316 } 317 318 /// 319 unittest 320 { 321 import mir.ndslice.topology: iota; 322 323 try 324 { 325 auto m = [3, 3].iota.inv; 326 assert (false, "Matrix should be detected as singular"); 327 } 328 catch (Exception e) 329 { 330 assert (true); 331 } 332 } 333 334 /// 335 struct SvdResult(T) 336 { 337 /// 338 Slice!(Contiguous, [2], T*) u; 339 /// 340 Slice!(Contiguous, [1], T*) sigma; 341 /// 342 Slice!(Contiguous, [2], T*) vt; 343 } 344 345 /++ 346 Computes the singular value decomposition. 347 348 Params: 349 matrix = input `M x N` matrix 350 slim = If true the first `min(M,N)` columns of `u` and the first 351 `min(M,N)` rows of `vt` are returned in the ndslices `u` and `vt`. 352 Returns: $(LREF SvdResult). Results are allocated by the GC. 353 +/ 354 auto svd( 355 Flag!"allowDestroy" allowDestroy = No.allowDestroy, 356 string algorithm = "gesvd", 357 SliceKind kind, 358 Iterator 359 )( 360 Slice!(kind, [2], Iterator) matrix, 361 Flag!"slim" slim = No.slim, 362 ) 363 if (algorithm == "gesvd" || algorithm == "gesdd") 364 { 365 import lapack; 366 alias T = BlasType!Iterator; 367 static if (allowDestroy && kind != Universal && is(Iterstor == T*)) 368 alias a = matrix.canonical; 369 else 370 auto a = matrix.as!T.slice.canonical; 371 372 auto m = cast(lapackint)a.length!1; 373 auto n = cast(lapackint)a.length!0; 374 375 auto s = uninitSlice!T(min(m, n)); 376 auto u = uninitSlice!T(slim ? s.length : m, m); 377 auto vt = uninitSlice!T(n, slim ? s.length : n); 378 379 static if (algorithm == "gesvd") 380 { 381 auto jobu = slim ? 'S' : 'A'; 382 auto jobvt = slim ? 'S' : 'A'; 383 auto work = gesvd_wq(jobu, jobvt, a, u.canonical, vt.canonical).uninitSlice!T; 384 auto info = gesvd(jobu, jobvt, a, s, u.canonical, vt.canonical, work); 385 enum msg = "svd: DBDSQR did not converge"; 386 } 387 else // gesdd 388 { 389 auto iwork = uninitSlice!lapackint(s.length * 8); 390 auto jobz = slim ? 'S' : 'A'; 391 auto work = gesdd_wq(jobz, a, u.canonical, vt.canonical).uninitSlice!T; 392 auto info = gesdd(jobz, a, s, u.canonical, vt.canonical, work, iwork); 393 enum msg = "svd: DBDSDC did not converge, updating process failed"; 394 } 395 396 import std.exception: enforce; 397 enforce(info == 0, msg); 398 return SvdResult!T(vt, s, u); //transposed 399 } 400 401 /// 402 unittest 403 { 404 auto a = [ 405 7.52, -1.10, -7.95, 1.08, 406 -0.76, 0.62, 9.34, -7.10, 407 5.13, 6.62, -5.66, 0.87, 408 -4.75, 8.52, 5.75, 5.30, 409 1.33, 4.91, -5.49, -3.52, 410 -2.40, -6.77, 2.34, 3.95] 411 .sliced(6, 4); 412 413 auto r = a.svd; 414 415 auto sigma = slice!double(a.shape, 0); 416 sigma.diagonal[] = r.sigma; 417 auto m = r.u.mtimes(sigma).mtimes(r.vt); 418 419 import mir.ndslice.algorithm: equal; 420 import std.math: approxEqual; 421 assert(equal!((a, b) => a.approxEqual(b, 1e-8, 1e-8))(a, m)); 422 } 423 424 /// 425 unittest 426 { 427 auto a = [ 428 7.52, -1.10, -7.95, 1.08, 429 -0.76, 0.62, 9.34, -7.10, 430 5.13, 6.62, -5.66, 0.87, 431 -4.75, 8.52, 5.75, 5.30, 432 1.33, 4.91, -5.49, -3.52, 433 -2.40, -6.77, 2.34, 3.95] 434 .sliced(6, 4); 435 436 auto r = a.svd(Yes.slim); 437 assert(r.u.shape == [6, 4]); 438 assert(r.vt.shape == [4, 4]); 439 } 440 441 /++ 442 Solve systems of linear equations AX = B for X. 443 Computes minimum-norm solution to a linear least squares problem 444 if A is not a square matrix. 445 +/ 446 Slice!(Contiguous, [2], BlasType!(IteratorA, IteratorB)*) 447 mldivide 448 (SliceKind kindA, IteratorA, SliceKind kindB, IteratorB)( 449 Slice!(kindA, [2], IteratorA) a, 450 Slice!(kindB, [2], IteratorB) b) 451 { 452 import std.exception: enforce; 453 import std.conv: to; 454 455 assert(a.length!0 == b.length!0); 456 457 alias A = BlasType!IteratorA; 458 alias B = BlasType!IteratorB; 459 alias C = CommonType!(A, B); 460 461 auto a_ = a.universal.transposed.as!C.slice.canonical; 462 auto b_ = b.universal.transposed.as!C.slice.canonical; 463 464 if (a.length!0 == a.length!1) 465 { 466 auto ipiv = a_.length.uninitSlice!lapackint; 467 auto info = gesv(a_, ipiv, b_); 468 } 469 else 470 { 471 size_t liwork = void; 472 auto lwork = gelsd_wq(a_, b_, liwork); 473 auto s = min(a_.length!0, a_.length!1).uninitSlice!C; 474 auto work = lwork.uninitSlice!C; 475 auto iwork = liwork.uninitSlice!lapackint; 476 size_t rank; 477 C rcond = -1; 478 479 auto info = gelsd(a_, b_, s, rcond, rank, work, iwork); 480 481 enforce(info == 0, to!string(info) ~ " off-diagonal elements of an intermediate bidiagonal form did not converge to zero."); 482 b_ = b_[0 .. $, 0 .. a_.length!0]; 483 } 484 return b_.universal.transposed.slice; 485 } 486 487 /// ditto 488 Slice!(Contiguous, [1], BlasType!(IteratorA, IteratorB)*) 489 mldivide 490 (SliceKind kindA, IteratorA, SliceKind kindB, IteratorB)( 491 Slice!(kindA, [2], IteratorA) a, 492 Slice!(kindB, [1], IteratorB) b) 493 { 494 return a.mldivide(b.repeat(1).unpack.universal.transposed).front!1.assumeContiguous; 495 } 496 497 /// AX=B 498 unittest 499 { 500 auto a = [ 501 1, -1, 1, 502 2, 2, -4, 503 -1, 5, 0].sliced(3, 3); 504 auto b = [ 505 2.0, 0, 506 -6 , -6, 507 9 , 1].sliced(3, 2); 508 auto t = [ 509 1.0, -1, 510 2 , 0, 511 3 , 1].sliced(3, 2); 512 513 auto x = mldivide(a, b); 514 assert(x == t); 515 } 516 517 /// Ax=B 518 unittest 519 { 520 auto a = [ 521 1, -1, 1, 522 2, 2, -4, 523 -1, 5, 0].sliced(3, 3); 524 auto b = [ 525 2.0, 526 -6 , 527 9 ].sliced(3); 528 auto t = [ 529 1.0, 530 2 , 531 3 ].sliced(3); 532 533 auto x = mldivide(a, b); 534 assert(x == t); 535 } 536 537 /// Least-Squares Solution of Underdetermined System 538 unittest 539 { 540 auto a = [ 541 -0.57, -1.28, -0.39, 0.25, 542 -1.93, 1.08, -0.31, -2.14, 543 2.30, 0.24, 0.40, -0.35, 544 -1.93, 0.64, -0.66, 0.08, 545 0.15, 0.30, 0.15, -2.13, 546 -0.02, 1.03, -1.43, 0.50, 547 ].sliced(6, 4); 548 549 auto b = [ 550 -2.67, 551 -0.55, 552 3.34, 553 -0.77, 554 0.48, 555 4.10, 556 ].sliced; 557 558 auto x = [ 559 1.5339, 560 1.8707, 561 -1.5241, 562 0.0392].sliced; 563 564 import std.math: approxEqual; 565 assert(a.mldivide(b).approxEqual(x)); 566 } 567 568 /// Principal component analises result. 569 struct PcaResult(T) 570 { 571 /// Principal component coefficients, also known as loadings. 572 Slice!(Contiguous, [2], T*) coeff; 573 /// Principal component scores. 574 Slice!(Contiguous, [2], T*) score; 575 /// Principal component variances. 576 Slice!(Contiguous, [1], T*) latent; 577 } 578 579 /++ 580 Principal component analysis of raw data. 581 582 Params: 583 matrix = input `M x N` matrix, where 'M (rows)>= N(cols)' 584 cc = Flag to centern columns. True by default. 585 Returns: $(LREF PcaResult) 586 +/ 587 auto pca(Flag!"allowDestroy" allowDestroy = No.allowDestroy, SliceKind kind, Iterator)(Slice!(kind, [2], Iterator) matrix, in Flag!"centerColumns" cc = Yes.centerColumns) 588 in 589 { 590 assert(matrix.length!0 >= matrix.length!1); 591 } 592 body 593 { 594 import mir.math.sum: sum; 595 import mir.ndslice.algorithm: maxIndex, eachUploPair; 596 import mir.utility: swap; 597 598 alias T = BlasType!Iterator; 599 SvdResult!T svdResult; 600 if (cc) 601 { 602 static if (allowDestroy && kind != Universal && is(Iterstor == T*)) 603 alias m = matrix; 604 else 605 auto m = matrix.as!T.slice; 606 foreach (col; m.universal.transposed) 607 col[] -= col.sum!"kb2" / col.length; 608 svdResult = m.svd!(Yes.allowDestroy)(Yes.slim); 609 } 610 else 611 { 612 svdResult = matrix.svd!(allowDestroy)(Yes.slim); 613 } 614 with (svdResult) 615 { 616 foreach (row; u) 617 row[] *= sigma; 618 T c = max(0, ptrdiff_t(matrix.length) - cast(bool) cc); 619 foreach (ref s; sigma) 620 s = s * s / c; 621 foreach(size_t i; 0 .. sigma.length) 622 { 623 auto col = vt[i]; 624 if (col[col.map!fabs.maxIndex] < 0) 625 { 626 col[] *= -1; 627 u[0 .. $, i] *= -1; 628 } 629 } 630 vt.eachUploPair!swap; 631 return PcaResult!T(vt, u, sigma); 632 } 633 } 634 635 /// 636 unittest 637 { 638 import std.math: approxEqual; 639 import mir.ndslice.algorithm: equal; 640 641 auto ingedients = [ 642 7, 26, 6, 60, 643 1, 29, 15, 52, 644 11, 56, 8, 20, 645 11, 31, 8, 47, 646 7, 52, 6, 33, 647 11, 55, 9, 22, 648 3, 71, 17, 6, 649 1, 31, 22, 44, 650 2, 54, 18, 22, 651 21, 47, 4, 26, 652 1, 40, 23, 34, 653 11, 66, 9, 12, 654 10, 68, 8, 12].sliced(13, 4); 655 656 auto res = ingedients.pca; 657 658 auto coeff = [ 659 -0.067799985695474, -0.646018286568728, 0.567314540990512, 0.506179559977705, 660 -0.678516235418647, -0.019993340484099, -0.543969276583817, 0.493268092159297, 661 0.029020832106229, 0.755309622491133, 0.403553469172668, 0.515567418476836, 662 0.730873909451461, -0.108480477171676, -0.468397518388289, 0.484416225289198, 663 ].sliced(4, 4); 664 665 auto score = [ 666 36.821825999449700, -6.870878154227367, -4.590944457629745, 0.396652582713912, 667 29.607273420710964, 4.610881963526308, -2.247578163663940, -0.395843536696492, 668 -12.981775719737618, -4.204913183175938, 0.902243082694698, -1.126100587210615, 669 23.714725720918022, -6.634052554708721, 1.854742000806314, -0.378564808384691, 670 -0.553191676624597, -4.461732123178686, -6.087412652325177, 0.142384896047281, 671 -10.812490833309816, -3.646571174544059, 0.912970791674604, -0.134968810314680, 672 -32.588166608817929, 8.979846284936063, -1.606265913996588, 0.081763927599947, 673 22.606395499005586, 10.725906457369449, 3.236537714483416, 0.324334774646368, 674 -9.262587237675838, 8.985373347478788, -0.016909578102172, -0.543746175981799, 675 -3.283969329640680, -14.157277337500918, 7.046512994833761, 0.340509860960606, 676 9.220031117829379, 12.386080787220454, 3.428342878284624, 0.435152769664895, 677 -25.584908517429557, -2.781693148152386, -0.386716066864491, 0.446817950545605, 678 -26.903161834677597, -2.930971165042989, -2.445522630195304, 0.411607156409658, 679 ].sliced(13, 4); 680 681 auto latent = [5.177968780739053, 0.674964360487231, 0.124054300480810, 0.002371532651878].sliced; 682 latent[] *= 100; 683 684 assert(equal!approxEqual(res.coeff, coeff)); 685 assert(equal!approxEqual(res.score, score)); 686 assert(equal!approxEqual(res.latent, latent)); 687 } 688 689 /++ 690 Computes Moore-Penrose pseudoinverse of matrix. 691 692 Params: 693 matrix = Input `M x N` matrix. 694 tolerance = The computation is based on SVD and any singular values less than tolerance are treated as zero. 695 Returns: Moore-Penrose pseudoinverse matrix 696 +/ 697 Slice!(Contiguous, [2], BlasType!Iterator*) 698 pinv(Flag!"allowDestroy" allowDestroy = No.allowDestroy, SliceKind kind, Iterator)(Slice!(kind, [2], Iterator) matrix, BlasType!Iterator tolerance = BlasType!Iterator.nan) 699 { 700 import mir.ndslice.algorithm: find, each; 701 import std.math: nextUp; 702 703 auto svd = matrix.svd!allowDestroy(Yes.slim); 704 if (tolerance != tolerance) 705 { 706 auto n = svd.sigma.front; 707 auto eps = n.nextUp - n; 708 tolerance = max(matrix.length!0, matrix.length!1) * eps; 709 } 710 auto s = svd.sigma[0 .. $ - svd.sigma.find!(a => !(a >= tolerance))[0]]; 711 s.each!"a = 1 / a"; 712 svd.vt[0 .. s.length].pack!1.map!"a".zip(s).each!"a.a[] *= a.b"; 713 auto v = svd.vt[0 .. s.length].universal.transposed; 714 auto ut = svd.u.universal.transposed[0 .. s.length]; 715 return v.mtimes(ut); 716 } 717 718 /// 719 unittest 720 { 721 auto a = [ 722 64, 2, 3, 61, 60, 6, 723 9, 55, 54, 12, 13, 51, 724 17, 47, 46, 20, 21, 43, 725 40, 26, 27, 37, 36, 30, 726 32, 34, 35, 29, 28, 38, 727 41, 23, 22, 44, 45, 19, 728 49, 15, 14, 52, 53, 11, 729 8, 58, 59, 5, 4, 62].sliced(8, 6); 730 731 auto b = a.pinv; 732 733 auto result = [ 734 0.0177, -0.0165, -0.0164, 0.0174, 0.0173, -0.0161, -0.0160, 0.0170, 735 -0.0121, 0.0132, 0.0130, -0.0114, -0.0112, 0.0124, 0.0122, -0.0106, 736 -0.0055, 0.0064, 0.0060, -0.0043, -0.0040, 0.0049, 0.0045, -0.0028, 737 -0.0020, 0.0039, 0.0046, -0.0038, -0.0044, 0.0064, 0.0070, -0.0063, 738 -0.0086, 0.0108, 0.0115, -0.0109, -0.0117, 0.0139, 0.0147, -0.0141, 739 0.0142, -0.0140, -0.0149, 0.0169, 0.0178, -0.0176, -0.0185, 0.0205]; 740 741 import std.math: approxEqual; 742 743 assert(b.field.approxEqual(result, 1e-2, 1e-2)); 744 } 745 746 /++ 747 Covariance matrix. 748 749 Params: 750 matrix = matrix whose rows represent observations and whose columns represent random variables. 751 Reuturns: 752 Normalized by `N-1` covariance matrix. 753 +/ 754 Slice!(Contiguous, [2], BlasType!Iterator*) 755 cov(SliceKind kind, Iterator)(Slice!(kind, [2], Iterator) matrix) 756 { 757 import mir.math.sum: sum; 758 import mir.ndslice.algorithm: each, eachUploPair; 759 alias A = BlasType!Iterator; 760 static if (kind == Contiguous) 761 auto mc = matrix.canonical; 762 else 763 alias mc = matrix; 764 auto m = mc.shape.uninitSlice!A.canonical; 765 auto s = m; 766 767 auto factor = 1 / A(m.length!0 - 1).sqrt; 768 while(m.length!1) 769 { 770 auto shift = - mc.front!1.sum!A / m.length!0; 771 m.front!1.each!((ref a, b) { a = (b + shift) * factor; })(mc.front!1); 772 mc.popFront!1; 773 m.popFront!1; 774 } 775 776 auto alpha = cast(A) 1; 777 auto beta = cast(A) 0; 778 auto c = [s.length!1, s.length!1].uninitSlice!A; 779 syrk(Uplo.Upper, alpha, s.universal.transposed, beta, c); 780 781 c.eachUploPair!"b = a"; 782 return c; 783 } 784 785 /// 786 unittest 787 { 788 import std.stdio; 789 import mir.ndslice; 790 791 auto c = 8.magic[0..$-1].cov; 792 793 import std.math: approxEqual; 794 assert(c.field.approxEqual([ 795 350.0000, -340.6667, -331.3333, 322.0000, 312.6667, -303.3333, -294.0000, 284.6667, 796 -340.6667, 332.4762, 324.2857, -316.0952, -307.9048, 299.7143, 291.5238, -283.3333, 797 -331.3333, 324.2857, 317.2381, -310.1905, -303.1429, 296.0952, 289.0476, -282.0000, 798 322.0000, -316.0952, -310.1905, 304.2857, 298.3810, -292.4762, -286.5714, 280.6667, 799 312.6667, -307.9048, -303.1429, 298.3810, 293.6190, -288.8571, -284.0952, 279.3333, 800 -303.3333, 299.7143, 296.0952, -292.4762, -288.8571, 285.2381, 281.6190, -278.0000, 801 -294.0000, 291.5238, 289.0476, -286.5714, -284.0952, 281.6190, 279.1429, -276.6667, 802 284.6667, -283.3333, -282.0000, 280.6667, 279.3333, -278.0000, -276.6667, 275.3333])); 803 } 804 805 /++ 806 Matrix determinant. 807 +/ 808 auto detSymmetric(SliceKind kind, Iterator)(Slice!(kind, [2], Iterator) a, char store = 'L') 809 in 810 { 811 assert(store == 'U' || store == 'L'); 812 assert (a.length!0 == a.length!1, "matrix must be square"); 813 assert (a.length!0, "matrix must not be empty"); 814 } 815 body 816 { 817 import mir.ndslice.algorithm: each; 818 import mir.ndslice.topology: diagonal; 819 import mir.math.numeric: Prod; 820 821 alias T = BlasType!Iterator; 822 823 auto packed = uninitSlice!T(a.length * (a.length + 1) / 2); 824 auto ipiv = a.length.uninitSlice!lapackint; 825 int sign; 826 Prod!T prod; 827 if (store == 'L') 828 { 829 auto pck = packed.stairs!"+"(a.length); 830 auto gen = a.stairs!"+"; 831 pck.each!"a[] = b"(gen); 832 auto info = sptrf(pck, ipiv); 833 if (info > 0) 834 return cast(T) 0; 835 for (size_t j; j < ipiv.length; j++) 836 { 837 auto i = ipiv[j]; 838 // 1x1 block at m[k,k] 839 if (i > 0) 840 { 841 prod.put(pck[j].back); 842 sign ^= i != j + 1; // i.e. row interchanged with another 843 } 844 else 845 { 846 i = -i; 847 auto offDiag = pck[j + 1][$ - 2]; 848 auto blockDet = pck[j].back * pck[j + 1].back - offDiag * offDiag; 849 prod.put(blockDet); 850 sign ^= i != j + 1 && i != j + 2; // row interchanged with other 851 j++; 852 } 853 } 854 } 855 else 856 { 857 auto pck = packed.stairs!"-"(a.length); 858 auto gen = a.stairs!"-"; 859 pck.each!"a[] = b"(gen); 860 auto info = sptrf(pck, ipiv); 861 if (info > 0) 862 return cast(T) 0; 863 for (size_t j; j < ipiv.length; j++) 864 { 865 auto i = ipiv[j]; 866 // 1x1 block at m[k,k] 867 if (i > 0) 868 { 869 prod.put(pck[j].front); 870 sign ^= i != j + 1; // i.e. row interchanged with another 871 } 872 else 873 { 874 i = -i; 875 auto offDiag = pck[j][1]; 876 auto blockDet = pck[j].front * pck[j + 1].front - offDiag * offDiag; 877 prod.put(blockDet); 878 sign ^= i != j + 1 && i != j + 2; // row interchanged with other 879 j++; 880 } 881 } 882 } 883 if(sign & 1) 884 prod.x = -prod.x; 885 return prod.value; 886 } 887 888 /// ditto 889 auto det(SliceKind kind, Iterator)(Slice!(kind, [2], Iterator) a) 890 in 891 { 892 assert (a.length!0 == a.length!1, "matrix must be square"); 893 } 894 body 895 { 896 import mir.ndslice.topology: diagonal, zip, iota; 897 import mir.math.numeric: Prod; 898 899 alias T = BlasType!Iterator; 900 901 auto m = a.as!T.slice.canonical; 902 auto ipiv = a.length.uninitSlice!lapackint; 903 904 // LU factorization 905 auto info = m.getrf(ipiv); 906 907 // If matrix is singular, determinant is zero. 908 if (info > 0) 909 { 910 return cast(T) 0; 911 } 912 913 // The determinant is the product of the diagonal entries 914 // of the upper triangular matrix. The array ipiv contains 915 // the pivots. 916 int sign; 917 Prod!T prod; 918 foreach (tup; m.diagonal.zip(ipiv, [ipiv.length].iota(1))) 919 { 920 prod.put(tup.a); 921 sign ^= tup.b != tup.c; // i.e. row interchanged with another 922 } 923 if(sign & 1) 924 prod.x = -prod.x; 925 return prod.value; 926 } 927 928 /// 929 unittest 930 { 931 // Check for zero-determinant shortcut. 932 auto ssing = [4, 2, 2, 1].sliced(2, 2); 933 auto ssingd = det(ssing); 934 assert (det(ssing) == 0); 935 assert (detSymmetric(ssing) == 0); 936 assert (detSymmetric(ssing, 'L') == 0); 937 938 //import std.stdio; 939 940 941 // General dense matrix. 942 int dn = 101; 943 auto d = uninitSlice!double(dn, dn); 944 foreach (k; 0 .. dn) 945 foreach (l; 0 .. dn) 946 d[k,l] = 0.5 * (k == l ? (k + 1) * (k + 1) + 1 : 2 * (k + 1) * (l + 1)); 947 948 auto dd = det(d); 949 import std.math: approxEqual; 950 assert (approxEqual(dd, 3.539152633479803e289, double.epsilon.sqrt)); 951 952 // Symmetric packed matrix 953 auto spa = [ 1.0, -2, 3, 4, 5, -6, -7, -8, -9, 10].sliced.stairs!"+"(4); 954 auto sp = [spa.length, spa.length].uninitSlice!double; 955 import mir.ndslice.algorithm: each; 956 sp.stairs!"+".each!"a[] = b"(spa); 957 assert (sp.detSymmetric('L').approxEqual(5874.0, double.epsilon.sqrt)); 958 assert (sp.universal.transposed.detSymmetric('U').approxEqual(5874.0, double.epsilon.sqrt)); 959 } 960 961 /++ 962 Eigenvalues and eigenvectors POD. 963 964 See_also: $(LREF eigSymmetric). 965 +/ 966 struct EigSymmetricResult(T) 967 { 968 /// Eigenvalues 969 Slice!(Contiguous, [1], T*) values; 970 /// Eigenvectors stored in rows 971 Slice!(Contiguous, [2], T*) vectors; 972 } 973 974 /++ 975 Eigenvalues and eigenvectors of symmetric matrix. 976 977 Returns: 978 $(LREF EigSymmetricResult) 979 +/ 980 auto eigSymmetric(Flag!"computeVectors" cv = Yes.computeVectors, SliceKind kind, Iterator)(Slice!(kind, [2], Iterator) a, char store = 'L') 981 in 982 { 983 assert(store == 'U' || store == 'L'); 984 assert (a.length!0 == a.length!1, "matrix must be square"); 985 assert (a.length!0, "matrix must not be empty"); 986 } 987 body 988 { 989 import mir.ndslice.algorithm: each; 990 import mir.ndslice.topology: diagonal; 991 import mir.math.numeric: Prod; 992 993 alias T = BlasType!Iterator; 994 995 auto packed = [a.length * (a.length + 1) / 2].uninitSlice!T; 996 auto w = [a.length].uninitSlice!T; 997 static if (cv) 998 { 999 auto z = [a.length, a.length].uninitSlice!T; 1000 } 1001 else 1002 { 1003 T[1] _vData = void; 1004 auto z = _vData.sliced(1, 1); 1005 } 1006 auto work = [a.length * 3].uninitSlice!T; 1007 size_t info = void; 1008 auto jobz = cv ? 'V' : 'N'; 1009 if (store == 'L') 1010 { 1011 auto pck = packed.stairs!"+"(a.length); 1012 auto gen = a.stairs!"+"; 1013 pck.each!"a[] = b"(gen); 1014 info = spev!T(jobz, pck, w, z.canonical, work); 1015 } 1016 else 1017 { 1018 auto pck = packed.stairs!"-"(a.length); 1019 auto gen = a.stairs!"-"; 1020 pck.each!"a[] = b"(gen); 1021 info = spev!T(jobz, pck, w, z.canonical, work); 1022 } 1023 import std.exception: enforce; 1024 import std.format: format; 1025 enforce (info == 0, format("The algorithm failed to converge." ~ 1026 "%s off-diagonal elements of an intermediate tridiagonal form did not converge to zero.", info)); 1027 static if (cv) 1028 { 1029 return EigSymmetricResult!T(w, z); 1030 } 1031 else 1032 { 1033 return EigSymmetricResult!T(w); 1034 } 1035 } 1036 1037 /// 1038 unittest 1039 { 1040 import mir.ndslice.slice: sliced; 1041 import mir.ndslice.topology: universal, map; 1042 import mir.ndslice.dynamic: transposed; 1043 import std.math: approxEqual; 1044 1045 auto a = [ 1046 1.0000, 0.5000, 0.3333, 0.2500, 1047 0.5000, 1.0000, 0.6667, 0.5000, 1048 0.3333, 0.6667, 1.0000, 0.7500, 1049 0.2500, 0.5000, 0.7500, 1.0000].sliced(4, 4); 1050 1051 auto eigr = a.eigSymmetric; 1052 1053 assert(eigr.values.approxEqual([0.2078,0.4078,0.8482,2.5362])); 1054 1055 auto test = [ 1056 0.0693, -0.4422, -0.8105, 0.3778, 1057 -0.3618, 0.7420, -0.1877, 0.5322, 1058 0.7694, 0.0486, 0.3010, 0.5614, 1059 -0.5219, -0.5014, 0.4662, 0.5088] 1060 .sliced(4, 4) 1061 .universal 1062 .transposed; 1063 1064 foreach (i; 0 .. 4) 1065 assert(eigr.vectors[i].approxEqual(test[i]) || 1066 eigr.vectors[i].map!"-a".approxEqual(test[i])); 1067 } 1068 1069 version (unittest) 1070 { 1071 /++ 1072 Swaps rows of input matrix 1073 Params 1074 ipiv = pivot points 1075 Returns: 1076 a = shifted matrix 1077 +/ 1078 private void moveRows(SliceKind kind, Iterator) 1079 (Slice!(kind, [2], Iterator) a, 1080 Slice!(Contiguous, [1], lapackint*) ipiv) 1081 { 1082 import mir.ndslice.algorithm: each; 1083 foreach_reverse(i;0..ipiv.length) 1084 { 1085 if(ipiv[i] == i + 1) 1086 continue; 1087 each!swap(a[i], a[ipiv[i] - 1]); 1088 } 1089 } 1090 } 1091 1092 /// 1093 unittest 1094 { 1095 auto A = 1096 [ 9, 9, 9, 1097 8, 8, 8, 1098 7, 7, 7, 1099 6, 6, 6, 1100 5, 5, 5, 1101 4, 4, 4, 1102 3, 3, 3, 1103 2, 2, 2, 1104 1, 1, 1, 1105 0, 0, 0 ] 1106 .sliced(10, 3) 1107 .as!double.slice; 1108 auto ipiv = [ 10, 9, 8, 7, 6, 6, 7, 8, 9, 10 ].sliced(10); 1109 moveRows(A, ipiv); 1110 1111 auto B = 1112 [ 0, 0, 0, 1113 1, 1, 1, 1114 2, 2, 2, 1115 3, 3, 3, 1116 4, 4, 4, 1117 5, 5, 5, 1118 6, 6, 6, 1119 7, 7, 7, 1120 8, 8, 8, 1121 9, 9, 9 ] 1122 .sliced(10, 3) 1123 .as!double.slice; 1124 1125 import mir.ndslice.algorithm: equal; 1126 assert(equal!((a, b) => fabs(a - b) < 1e-12)(B, A)); 1127 } 1128 1129 unittest 1130 { 1131 auto A = 1132 [ 1, 1, 1, 1133 2, 2, 2, 1134 3, 3, 3, 1135 4, 4, 4, 1136 5, 5, 5, 1137 6, 6, 6, 1138 7, 7, 7, 1139 8, 8, 8, 1140 9, 9, 9, 1141 0, 0, 0 ] 1142 .sliced(10, 3) 1143 .as!double.slice; 1144 auto ipiv = [ 2, 3, 4, 5, 6, 7, 8, 9, 10, 10 ].sliced(10); 1145 moveRows(A, ipiv); 1146 1147 auto B = 1148 [ 0, 0, 0, 1149 1, 1, 1, 1150 2, 2, 2, 1151 3, 3, 3, 1152 4, 4, 4, 1153 5, 5, 5, 1154 6, 6, 6, 1155 7, 7, 7, 1156 8, 8, 8, 1157 9, 9, 9 ] 1158 .sliced(10, 3) 1159 .as!double.slice; 1160 1161 import mir.ndslice.algorithm: equal; 1162 assert(equal!((a, b) => fabs(a - b) < 1e-12)(B, A)); 1163 } 1164 1165 ///LUResult consist lu factorization. 1166 struct LUResult(T) 1167 { 1168 /++ 1169 Matrix in witch lower triangular is L part of factorization 1170 (diagonal elements of L are not stored), upper triangular 1171 is U part of factorization. 1172 +/ 1173 Slice!(Canonical, [2], T*) lut; 1174 /++ 1175 The pivot indices, for 1 <= i <= min(M,N), row i of the matrix 1176 was interchanged with row ipiv(i). 1177 +/ 1178 Slice!(Contiguous, [1], lapackint*) ipiv; 1179 ///L part of the factorization. 1180 auto l() @property 1181 { 1182 import mir.ndslice.algorithm: eachUpper; 1183 auto l = lut.transposed[0..lut.length!1, 0..min(lut.length!0, lut.length!1)].slice.canonical; 1184 l.eachUpper!"a = 0"; 1185 l.diagonal[] = 1; 1186 return l; 1187 } 1188 ///U part of the factorization. 1189 auto u() @property 1190 { 1191 import mir.ndslice.algorithm: eachLower; 1192 auto u = lut.transposed[0..min(lut.length!0, lut.length!1), 0..lut.length!0].slice.canonical; 1193 u.eachLower!"a = 0"; 1194 return u; 1195 } 1196 auto solve(Flag!"allowDestroy" allowDestroy = No.allowDestroy, 1197 SliceKind kind, size_t[] n, Iterator) 1198 (Slice!(kind, n, Iterator) b, 1199 char trans = 'N') 1200 { 1201 return luSolve!(allowDestroy)(lut, ipiv, b, trans); 1202 } 1203 } 1204 1205 /++ 1206 Computes LU factorization of a general 'M x N' matrix 'A' using partial 1207 pivoting with row interchanges. 1208 The factorization has the form: 1209 \A = P * L * U 1210 Where P is a permutation matrix, L is lower triangular with unit 1211 diagonal elements (lower trapezoidal if m > n), and U is upper 1212 triangular (upper trapezoidal if m < n). 1213 Params: 1214 allowDestroy = flag to delete the source matrix. 1215 a = input 'M x N' matrix for factorization. 1216 Returns: $(LREF LUResalt) 1217 +/ 1218 auto luDecomp(Flag!"allowDestroy" allowDestroy = No.allowDestroy, 1219 SliceKind kind, Iterator) 1220 (Slice!(kind, [2], Iterator) a) 1221 { 1222 alias T = BlasType!Iterator; 1223 auto ipiv = uninitSlice!lapackint(min(a.length!0, a.length!1)); 1224 auto b = a.transposed; 1225 auto m = (allowDestroy && b._stride!1 == 1) ? b.assumeCanonical : a.transposed.as!T.slice.canonical; 1226 1227 getrf(m, ipiv); 1228 return LUResult!T(m, ipiv); 1229 } 1230 1231 /++ 1232 Solves a system of linear equations 1233 \A * X = B, or 1234 \A**T * X = B 1235 with a general 'N x N' matrix 'A' using the LU factorization computed by luDecomp. 1236 Params: 1237 allowDestroy = flag to delete the source matrix. 1238 lut = factorization of matrix 'A', A = P * L * U. 1239 ipiv = the pivot indices from luDecomp. 1240 b = the right hand side matrix B. 1241 trans = specifies the form of the system of equations: 1242 = 'N': A * X = B (No transpose) 1243 = 'T': A**T * X = B (Transpose) 1244 = 'C': A**T * X = B (Conjugate transpose = Transpose) 1245 Returns: 1246 Return solve of the system linear equations. 1247 +/ 1248 auto luSolve(Flag!"allowDestroy" allowDestroy = No.allowDestroy, 1249 SliceKind kindB, size_t[] n, IteratorB, IteratorLU) 1250 (Slice!(Canonical, [2], IteratorLU) lut, 1251 Slice!(Contiguous, [1], lapackint*) ipiv, 1252 Slice!(kindB, n, IteratorB) b, 1253 char trans = 'N' 1254 ) 1255 in 1256 { 1257 assert(lut.length!0 == lut.length!1, "matrix must be squared"); 1258 assert(ipiv.length == lut.length, "size of ipiv must be equal to the number of rows a"); 1259 assert(lut.length!1 == b.length!0, "number of columns a should be equal to the number of rows b"); 1260 } 1261 body 1262 { 1263 alias LU = BlasType!IteratorLU; 1264 alias B = BlasType!IteratorB; 1265 alias T = CommonType!(LU, B); 1266 static if(is(T* == IteratorLU)) 1267 auto lut_ = lut; 1268 else 1269 auto lut_ = lut.as!T.slice.canonical; 1270 1271 //convect vector to matrix. 1272 static if(n[0] == 1) 1273 auto k = b.sliced(1, b.length); 1274 else 1275 auto k = b.transposed; 1276 1277 static if(is(IteratorB == T*)) 1278 auto m = (allowDestroy && k._stride!1 == 1) ? k.assumeCanonical : k.as!T.slice.canonical; 1279 else 1280 auto m = k.as!T.slice.canonical; 1281 getrs!T(lut_, m, ipiv, trans); 1282 return m.transposed; 1283 } 1284 1285 unittest 1286 { 1287 auto A = 1288 [ 1, 4, -3, 5, 6, 1289 -2, 8, 5, 7, 8, 1290 3, 4, 7, 9, 1, 1291 2, 4, 6, 3, 2, 1292 6, 8, 3, 5, 2 ] 1293 .sliced(5, 5) 1294 .as!double.slice 1295 .canonical; 1296 auto B = 1297 [ 1, 3, 4, 8, 0, 0, 0, 1298 2, 1, 7, 1, 0, 0, 0, 1299 3, 5, 7, 7, 0, 0, 0, 1300 4, 4, 9, 8, 0, 0, 0, 1301 5, 5, 8, 1, 0, 0, 0 ] 1302 .sliced(5, 7) 1303 .as!double.slice 1304 .universal; 1305 1306 auto B_ = B[0..$, 0..4]; 1307 auto LU = A.luDecomp(); 1308 auto m = luSolve(LU.lut, LU.ipiv, B_); 1309 1310 import std.math: approxEqual; 1311 import mir.ndslice.algorithm: equal; 1312 assert(equal!approxEqual(mtimes(A, m), B_)); 1313 } 1314 1315 /// 1316 unittest 1317 { 1318 auto A = 1319 [ 1, 4, -3, 5, 6, 1320 -2, 8, 5, 7, 8, 1321 3, 4, 7, 9, 1, 1322 2, 4, 6, 3, 2, 1323 6, 8, 3, 5, 2 ] 1324 .sliced(5, 5) 1325 .as!double.slice 1326 .canonical; 1327 1328 import mir.random.variable; 1329 import mir.random.algorithm; 1330 auto B = randomSlice!double(uniformVar(-100, 100), 5, 100); 1331 1332 auto LU = A.luDecomp(); 1333 auto X = LU.solve(B); 1334 1335 import mir.ndslice.algorithm: equal; 1336 assert(equal!((a, b) => fabs(a - b) < 1e-12)(mtimes(A, X), B)); 1337 } 1338 1339 unittest 1340 { 1341 auto A = 1342 [ 1, 4, -3, 5, 6, 1343 -2, 8, 5, 7, 8, 1344 3, 4, 7, 9, 1, 1345 2, 4, 6, 3, 2, 1346 6, 8, 3, 5, 2 ] 1347 .sliced(5, 5) 1348 .as!double.slice 1349 .canonical; 1350 auto B = 1351 [ 2, 8, 3, 5, 8, 1352 8, 1, 4, 9, 86, 1353 1, 6, 7, 1, 67, 1354 6, 1, 5, 4, 45, 1355 1, 2, 3, 1, 11 ] 1356 .sliced(5, 5) 1357 .as!double.slice 1358 .universal; 1359 auto C = B.slice; 1360 1361 auto LU = A.luDecomp(); 1362 auto m = luSolve!(Yes.allowDestroy)(LU.lut, LU.ipiv, B.transposed); 1363 auto m2 = LU.solve(C); 1364 1365 import std.math: approxEqual; 1366 import mir.ndslice.algorithm: equal; 1367 assert(equal!approxEqual(mtimes(A, m), C.transposed)); 1368 assert(equal!approxEqual(mtimes(A, m2), C)); 1369 } 1370 1371 unittest 1372 { 1373 auto A = 1374 [ 1, 4, -3, 5, 6, 1375 -2, 8, 5, 7, 8, 1376 3, 4, 7, 9, 1, 1377 2, 4, 6, 3, 2, 1378 6, 8, 3, 5, 2 ] 1379 .sliced(5, 5) 1380 .as!float.slice 1381 .canonical; 1382 auto B = [ 1, 2, 3, 4, 5 ].sliced(5).as!double.slice; 1383 auto C = B.slice.sliced(5, 1); 1384 1385 auto LU = A.luDecomp(); 1386 auto m = luSolve!(Yes.allowDestroy)(LU.lut, LU.ipiv, B); 1387 1388 import std.math: approxEqual; 1389 import mir.ndslice.algorithm: equal; 1390 assert(equal!approxEqual(mtimes(A, m), C)); 1391 } 1392 1393 unittest 1394 { 1395 auto A = 1396 [ 1, 4, -3, 5, 6, 1397 -2, 8, 5, 7, 8, 1398 3, 4, 7, 9, 1, 1399 2, 4, 6, 3, 2, 1400 6, 8, 3, 5, 2 ] 1401 .sliced(5, 5) 1402 .as!double.slice 1403 .canonical; 1404 auto B = [ 1, 15, 4, 5, 8, 1405 3, 20, 1, 9, 11 ].sliced(5, 2).as!float.slice; 1406 1407 auto LU = A.luDecomp(); 1408 auto m = luSolve(LU.lut, LU.ipiv, B); 1409 1410 import std.math: approxEqual; 1411 import mir.ndslice.algorithm: equal; 1412 assert(equal!approxEqual(mtimes(A, m), B)); 1413 } 1414 1415 unittest 1416 { 1417 auto A = 1418 [ 11, 14, -31, 53, 62, 1419 -92, 83, 52, 74, 83, 1420 31, 45, 73, 96, 17, 1421 23, 14, 65, 35, 26, 1422 62, 28, 34, 51, 25 ] 1423 .sliced(5, 5) 1424 .as!float.slice 1425 .universal; 1426 auto B = 1427 [ 6, 1, 3, 1, 11, 1428 12, 5, 7, 6, 78, 1429 8, 4, 1, 5, 54, 1430 3, 1, 8, 1, 45, 1431 1, 6, 8, 6, 312 ] 1432 .sliced(5, 5) 1433 .as!double.slice; 1434 auto B2 = B.slice; 1435 auto C = B.slice; 1436 1437 auto LU = luDecomp(A.transposed); 1438 auto m = luSolve!(Yes.allowDestroy)(LU.lut, LU.ipiv, B, 'T'); 1439 auto m2 = luSolve!(Yes.allowDestroy)(LU.lut, LU.ipiv, B2); 1440 1441 import std.math: approxEqual; 1442 import mir.ndslice.algorithm: equal; 1443 assert(equal!approxEqual(mtimes(A, m), C)); 1444 assert(equal!approxEqual(mtimes(A.transposed, m2), C)); 1445 } 1446 1447 unittest 1448 { 1449 auto A = 1450 [ 54, 93, 14, 44, 33, 1451 51, 85, 28, 81, 75, 1452 89, 17, 15, 44, 58, 1453 75, 80, 18, 35, 14, 1454 21, 48, 72, 21, 88 ] 1455 .sliced(5, 5) 1456 .as!double.slice 1457 .universal; 1458 auto B = 1459 [ 5, 7, 8, 3, 78, 1460 1, 2, 5, 4, 5, 1461 2, 4, 1, 5, 15, 1462 1, 1, 4, 1, 154, 1463 1, 3, 1, 8, 17 ] 1464 .sliced(5, 5) 1465 .as!float.slice 1466 .canonical; 1467 1468 auto LU = A.luDecomp(); 1469 auto m = luSolve(LU.lut, LU.ipiv, B); 1470 1471 import std.math: approxEqual; 1472 import mir.ndslice.algorithm: equal; 1473 assert(equal!approxEqual(mtimes(A, m), B)); 1474 } 1475 1476 unittest 1477 { 1478 1479 auto B = 1480 [ 1, 4, -3, 1481 -2, 8, 5, 1482 3, 4, 7, 1483 2, 4, 6 ] 1484 .sliced(4, 3) 1485 .as!double.slice; 1486 1487 auto LU = B.luDecomp!(Yes.allowDestroy)(); 1488 LU.l; LU.u; 1489 auto res = mtimes(LU.l, LU.u); 1490 moveRows(res, LU.ipiv); 1491 1492 import mir.ndslice.algorithm: equal; 1493 import std.math: approxEqual; 1494 assert(res.equal!approxEqual(B)); 1495 } 1496 1497 /// 1498 unittest 1499 { 1500 auto B = 1501 [ 3, -7, -2, 2, 1502 -3, 5, 1, 0, 1503 6, -4, 0, -5, 1504 -9, 5, -5, 12 ] 1505 .sliced(4, 4) 1506 .as!double.slice 1507 .canonical; 1508 auto C = B.transposed.slice; 1509 1510 auto LU = B.transposed.luDecomp!(Yes.allowDestroy)(); 1511 auto res = mtimes(LU.l, LU.u); 1512 moveRows(res, LU.ipiv); 1513 1514 import mir.ndslice.algorithm: equal; 1515 import std.math: approxEqual; 1516 assert(res.equal!approxEqual(C)); 1517 } 1518 1519 ///Consist LDL factorization; 1520 struct LDLResult(T) 1521 { 1522 /++ 1523 Matrix in witch lower triangular matrix is 'L' part of 1524 factorization, diagonal is 'D' part. 1525 +/ 1526 Slice!(Canonical, [2], T*) matrix; 1527 /++ 1528 The pivot indices. 1529 If ipiv(k) > 0, then rows and columns k and ipiv(k) were 1530 interchanged and D(k, k) is a '1 x 1' diagonal block. 1531 If ipiv(k) = ipiv(k + 1) < 0, then rows and columns k+1 and 1532 -ipiv(k) were interchanged and D(k:k+1, k:k+1) is a '2 x 2' 1533 diagonal block. 1534 +/ 1535 Slice!(Contiguous, [1], lapackint*) ipiv; 1536 /++ 1537 uplo = 'U': Upper triangle is stored; 1538 = 'L': lower triangle is stored. 1539 +/ 1540 char uplo; 1541 /++ 1542 Return solves a system of linear equations 1543 \A * X = B, 1544 using LDL factorization. 1545 +/ 1546 auto solve(Flag!"allowDestroy" allowDestroy = No.allowDestroy, 1547 SliceKind kindB, size_t[] n, IteratorB) 1548 (Slice!(kindB, n, IteratorB) b) 1549 { 1550 return ldlSolve!(allowDestroy)(matrix, ipiv, b, uplo); 1551 } 1552 } 1553 1554 /++ 1555 Computes the factorization of a real symmetric matrix A using the 1556 Bunch-Kaufman diagonal pivoting method. 1557 The for of the factorization is: 1558 \A = L*D*L**T 1559 Where L is product if permutation and unit lower triangular matrices, 1560 and D is symmetric and block diagonal with '1 x 1' and '2 x 2' 1561 diagonal blocks. 1562 Params: 1563 allowDestroy = flag to delete the source matrix. 1564 a = input symmetric 'n x n' matrix for factorization. 1565 uplo = 'U': Upper triangle is stored; 1566 = 'L': lower triangle is stored. 1567 Returns:$(LREF LDLResult) 1568 +/ 1569 auto ldlDecomp(Flag!"allowDestroy" allowDestroy = No.allowDestroy, 1570 SliceKind kind, Iterator) 1571 (Slice!(kind, [2], Iterator) a, 1572 char uplo = 'L') 1573 in 1574 { 1575 assert(a.length!0 == a.length!1, "matrix must be squared"); 1576 } 1577 body 1578 { 1579 alias T = BlasType!Iterator; 1580 auto work = [T.sizeof * a.length].uninitSlice!T; 1581 auto ipiv = a.length.uninitSlice!lapackint; 1582 auto m = (allowDestroy && a._stride!1 == 1) ? a.assumeCanonical : a.transposed.as!T.slice.canonical; 1583 1584 sytrf!T(m, ipiv, work, uplo); 1585 return LDLResult!T(m, ipiv, uplo); 1586 } 1587 1588 /++ 1589 Solves a system of linear equations \A * X = B with symmetric matrix 'A' using the 1590 factorization 1591 \A = U * D * U**T, or 1592 \A = L * D * L**T 1593 computed by ldlDecomp. 1594 Params: 1595 allowDestroy = flag to delete the source matrix. 1596 a = 'LD' or 'UD' matrix computed by ldlDecomp. 1597 ipiv = details of the interchanges and the block structure of D as determined by ldlDecomp. 1598 b = the right hand side matrix. 1599 uplo = specifies whether the details of the factorization are stored as an upper or 1600 lower triangular matrix: 1601 = 'U': Upper triangular, form is \A = U * D * U**T; 1602 = 'L': Lower triangular, form is \A = L * D * L**T. 1603 Returns: 1604 The solution matrix. 1605 +/ 1606 auto ldlSolve(Flag!"allowDestroy" allowDestroy = No.allowDestroy, 1607 SliceKind kindB, size_t[] n, IteratorB, IteratorA) 1608 (Slice!(Canonical, [2], IteratorA) a, 1609 Slice!(Contiguous, [1], lapackint*) ipiv, 1610 Slice!(kindB, n, IteratorB) b, 1611 char uplo = 'L' 1612 ) 1613 in 1614 { 1615 assert(a.length!0 == a.length!1, "matrix must be squared"); 1616 assert(ipiv.length == a.length, "size of ipiv must be equal to the number of rows a"); 1617 assert(a.length!1 == b.length!0, "number of columns a should be equal to the number of rows b"); 1618 } 1619 body 1620 { 1621 alias A = BlasType!IteratorA; 1622 alias B = BlasType!IteratorB; 1623 alias T = CommonType!(A, B); 1624 static if(is(T* == IteratorA)) 1625 auto a_ = a; 1626 else 1627 auto a_ = a.as!T.slice.canonical; 1628 1629 //convect vector to matrix. 1630 static if(n[0] == 1) 1631 auto k = b.sliced(1, b.length); 1632 else 1633 auto k = b.transposed; 1634 1635 auto work = [T.sizeof * a.length].uninitSlice!T; 1636 static if(is(IteratorB == T*)) 1637 auto m = (allowDestroy && k._stride!1 == 1) ? k.assumeCanonical : k.as!T.slice.canonical; 1638 else 1639 auto m = k.as!T.slice.canonical; 1640 sytrs2!T(a_, m, ipiv, work, uplo); 1641 return m.transposed; 1642 } 1643 1644 /// 1645 unittest 1646 { 1647 auto A = 1648 [ 2.07, 3.87, 4.20, -1.15, 1649 3.87, -0.21, 1.87, 0.63, 1650 4.20, 1.87, 1.15, 2.06, 1651 -1.15, 0.63, 2.06, -1.81 ] 1652 .sliced(4, 4) 1653 .as!double.slice 1654 .canonical; 1655 1656 import mir.random.variable; 1657 import mir.random.algorithm; 1658 auto B = randomSlice!double(uniformVar(-100, 100), 4, 100); 1659 1660 auto LDL = A.ldlDecomp('L'); 1661 auto X = LDL.solve(B); 1662 1663 import std.math: approxEqual; 1664 import mir.ndslice.algorithm: equal; 1665 assert(equal!approxEqual(mtimes(A, X), B)); 1666 } 1667 1668 unittest 1669 { 1670 auto A = 1671 [ 9, -1, 2, 1672 -1, 8, -5, 1673 2, -5, 7 ] 1674 .sliced(3, 3) 1675 .as!float.slice 1676 .canonical; 1677 auto A_ = A.slice; 1678 1679 auto B = 1680 [ 5, 7, 1, 1681 1, 8, 5, 1682 9, 3, 2 ] 1683 .sliced(3, 3) 1684 .as!double.slice 1685 .canonical; 1686 auto B_ = B.slice; 1687 1688 auto LDL = A.ldlDecomp!(Yes.allowDestroy)('L'); 1689 auto X = ldlSolve!(Yes.allowDestroy)(A, LDL.ipiv, B.transposed, LDL.uplo); 1690 1691 import std.math: approxEqual; 1692 import mir.ndslice.algorithm: equal; 1693 assert(equal!approxEqual(mtimes(A_, X), B_.transposed)); 1694 } 1695 1696 unittest 1697 { 1698 auto A = 1699 [10, 20, 30, 1700 20, 45, 80, 1701 30, 80, 171 ] 1702 .sliced(3, 3) 1703 .as!double.slice 1704 .canonical; 1705 auto B = [ 1, 4, 7 ].sliced(3).as!float.slice.canonical; 1706 auto B_ = B.sliced(3, 1); 1707 1708 auto LDL = A.ldlDecomp('L'); 1709 auto X = LDL.solve(B); 1710 1711 import std.math: approxEqual; 1712 import mir.ndslice.algorithm: equal; 1713 assert(equal!approxEqual(mtimes(A, X), B_)); 1714 } 1715 1716 struct choleskyResult(T) 1717 { 1718 /++ 1719 If uplo = 'L': lower triangle of 'matrix' is stored. 1720 If uplo = 'U': upper triangle of 'matrix' is stored. 1721 +/ 1722 char uplo; 1723 /++ 1724 if uplo = Lower, the leading 'N x N' lower triangular part of A 1725 contains the lower triangular part of the matrix A, and the 1726 strictly upper triangular part if A is not referenced. 1727 if uplo = Upper, the leading 'N x N' upper triangular part of A 1728 contains the upper triangular part of the matrix A, and the 1729 strictly lower triangular part if A is not referenced. 1730 +/ 1731 Slice!(Canonical, [2], T*) matrix; 1732 /++ 1733 Return solves a system of linear equations 1734 \A * X = B, 1735 using Cholesky factorization. 1736 +/ 1737 auto solve(Flag!"allowDestroy" allowDestroy = No.allowDestroy, 1738 SliceKind kind, size_t[] n, Iterator) 1739 (Slice!(kind, n, Iterator) b) 1740 { 1741 return choleskySolve!(allowDestroy)(matrix, b, uplo); 1742 } 1743 } 1744 /++ 1745 Computs Cholesky decomposition of symmetric positive definite matrix 'A'. 1746 The factorization has the form: 1747 \A = U**T * U, if UPLO = Upper, or 1748 \A = L * L**T, if UPLO = Lower 1749 Where U is an upper triangular matrix and L is lower triangular. 1750 Params: 1751 allowDestroy = flag to delete the source matrix. 1752 a = symmetric 'N x N' matrix. 1753 uplo = if uplo is Upper, then upper triangle of A is stored, else 1754 lower. 1755 Returns: $(LREF choleskyResult) 1756 +/ 1757 auto choleskyDecomp(Flag!"allowDestroy" allowDestroy = No.allowDestroy, 1758 SliceKind kind, Iterator) 1759 (Slice!(kind, [2], Iterator) a, 1760 char uplo) 1761 in 1762 { 1763 assert(a.length!0 == a.length!1, "matrix must be squared"); 1764 } 1765 body 1766 { 1767 alias T = BlasType!Iterator; 1768 auto m = (allowDestroy && a._stride!1 == 1) ? a.assumeCanonical : a.as!T.slice.canonical; 1769 potrf!T(m, uplo); 1770 return choleskyResult!T(uplo, m); 1771 } 1772 1773 /++ 1774 Solves a system of linear equations A * X = B with a symmetric matrix A using the 1775 Cholesky factorization: 1776 \A = U**T * U or 1777 \A = L * L**T 1778 computed by choleskyDecomp. 1779 Params: 1780 allowDestroy = flag to delete the source matrix. 1781 c = the triangular factor 'U' or 'L' from the Cholesky factorization 1782 \A = U**T * U or 1783 \A = L * L**T, 1784 as computed by choleskyDecomp. 1785 b = the right hand side matrix. 1786 uplo = 'U': Upper triangle of A is stored; 1787 = 'L': Lower triangle of A is stored. 1788 Returns: 1789 The solution matrix X. 1790 +/ 1791 auto choleskySolve(Flag!"allowDestroy" allowDestroy = No.allowDestroy, 1792 SliceKind kindB, size_t[] n, IteratorB, IteratorC) 1793 (Slice!(Canonical, [2], IteratorC) c, 1794 Slice!(kindB, n, IteratorB) b, 1795 char uplo) 1796 in 1797 { 1798 assert(c.length!0 == c.length!1, "matrix must be squared"); 1799 assert(c.length!1 == b.length!0, "number of columns a should be equal to the number of rows b"); 1800 } 1801 body 1802 { 1803 alias B = BlasType!IteratorB; 1804 alias C = BlasType!IteratorC; 1805 alias T = CommonType!(B, C); 1806 static if(is(T* == IteratorC)) 1807 auto c_ = c; 1808 else 1809 auto c_ = c.as!T.slice.canonical; 1810 1811 //convect vector to matrix. 1812 static if(n[0] == 1) 1813 auto k = b.sliced(1, b.length); 1814 else 1815 auto k = b.transposed; 1816 1817 static if(is(IteratorB == T*)) 1818 auto m = (allowDestroy && k._stride!1 == 1) ? k.assumeCanonical : k.as!T.slice.canonical; 1819 else 1820 auto m = k.as!T.slice.canonical; 1821 potrs!T(c_, m, uplo); 1822 return m.transposed; 1823 } 1824 1825 /// 1826 unittest 1827 { 1828 auto A = 1829 [ 25, 15, -5, 1830 15, 18, 0, 1831 -5, 0, 11 ] 1832 .sliced(3, 3) 1833 .as!double.slice; 1834 1835 import mir.random.variable; 1836 import mir.random.algorithm; 1837 auto B = randomSlice!double(uniformVar(-100, 100), 3, 100); 1838 1839 auto C = A.choleskyDecomp('L'); 1840 auto X = C.solve(B); 1841 1842 import std.math: approxEqual; 1843 import mir.ndslice.algorithm: equal; 1844 assert(equal!approxEqual(mtimes(A, X), B)); 1845 } 1846 1847 unittest 1848 { 1849 auto A = 1850 [ 1, 1, 3, 1851 1, 5, 5, 1852 3, 5, 19 ] 1853 .sliced(3, 3) 1854 .as!double.slice 1855 .universal; 1856 auto B = [ 10, 157, 80 ].sliced(3).as!float.slice; 1857 auto C_ = B.slice.sliced(3, 1); 1858 1859 auto C = A.choleskyDecomp('U'); 1860 auto X = choleskySolve!(Yes.allowDestroy)(C.matrix, B, C.uplo); 1861 1862 import std.math: approxEqual; 1863 import mir.ndslice.algorithm: equal; 1864 assert(equal!approxEqual(mtimes(A, X), C_)); 1865 } 1866 1867 unittest 1868 { 1869 auto A = 1870 [ 6, 15, 55, 1871 15, 55, 225, 1872 55, 225, 979 ] 1873 .sliced(3, 3) 1874 .as!float.slice 1875 .canonical; 1876 auto B = 1877 [ 7, 3, 1878 2, 1, 1879 1, 8 ] 1880 .sliced(3, 2) 1881 .as!double.slice 1882 .universal; 1883 1884 auto C = A.choleskyDecomp('L'); 1885 auto X = choleskySolve(C.matrix, B, C.uplo); 1886 1887 import std.math: approxEqual; 1888 import mir.ndslice.algorithm: equal; 1889 assert(equal!approxEqual(mtimes(A, X), B)); 1890 } 1891 1892 1893 /// 1894 struct QRResult(T) 1895 { 1896 /++ 1897 Matrix in witch the elements on and above the diagonal of the array contain the min(M, N) x N 1898 upper trapezoidal matrix 'R' (R is upper triangular if m >= n). The elements below the 1899 diagonal, with the array tau, represent the orthogonal matrix 'Q' as product of min(m, n). 1900 +/ 1901 Slice!(Canonical, [2], T*) matrix; 1902 ///The scalar factors of the elementary reflectors 1903 Slice!(Contiguous, [1], T*) tau; 1904 /++ 1905 Solve the least squares problem: 1906 \min ||A * X - B|| 1907 Using the QR factorization: 1908 \A = Q * R 1909 computed by qrDecomp. 1910 +/ 1911 auto solve(Flag!"allowDestroy" allowDestroy = No.allowDestroy, 1912 SliceKind kind, size_t[] n, Iterator) 1913 (Slice!(kind, n, Iterator) b) 1914 { 1915 return qrSolve!(allowDestroy)(matrix, tau, b); 1916 } 1917 } 1918 1919 /++ 1920 Computes a QR factorization of matrix 'a'. 1921 Params: 1922 allowDestroy = flag to delete the source matrix. 1923 a = initial matrix 1924 Returns: $(LREF QRResult) 1925 +/ 1926 auto qrDecomp(Flag!"allowDestroy" allowDestroy = No.allowDestroy, 1927 SliceKind kind, Iterator) 1928 (Slice!(kind, [2], Iterator) a) 1929 { 1930 alias T = BlasType!Iterator; 1931 auto work = [T.sizeof * a.length].uninitSlice!T; 1932 auto tau = (cast(int) min(a.length!0, a.length!1)).uninitSlice!T; 1933 auto m = (allowDestroy && a._stride!1 == 1) ? a.assumeCanonical : a.transposed.as!T.slice.canonical; 1934 1935 geqrf!T(m, tau, work); 1936 return QRResult!T(m, tau); 1937 } 1938 1939 /++ 1940 Solve the least squares problem: 1941 \min ||A * X - B|| 1942 Using the QR factorization: 1943 \A = Q * R 1944 computed by qrDecomp. 1945 Params: 1946 allowDestroy = flag to delete the source matrix. 1947 a = detalis of the QR factorization of the original matrix as returned by qrDecomp. 1948 tau = details of the orhtogonal matrix Q. 1949 b = right hand side matrix. 1950 Returns: solution matrix. 1951 +/ 1952 auto qrSolve(Flag!"allowDestroy" allowDestroy = No.allowDestroy, 1953 SliceKind kindB, size_t[] n, IteratorB, IteratorA, IteratorT) 1954 (Slice!(Canonical, [2], IteratorA) a, 1955 Slice!(Contiguous, [1], IteratorT) tau, 1956 Slice!(kindB, n, IteratorB) b 1957 ) 1958 in 1959 { 1960 assert(a.length!0 == a.length!1, "matrix must be squared"); 1961 assert(a.length!1 == b.length!0, "number of columns a should be equal to the number of rows b"); 1962 } 1963 body 1964 { 1965 alias A = BlasType!IteratorA; 1966 alias B = BlasType!IteratorB; 1967 alias T = CommonType!(A, B); 1968 static if(is(T* == IteratorA)) 1969 auto a_ = a; 1970 else 1971 auto a_ = a.as!T.slice.canonical; 1972 static if(is(T* == IteratorT)) 1973 auto tau_ = tau; 1974 else 1975 auto tau_ = tau.as!T.slice.canonical; 1976 1977 //convect vector to matrix. 1978 static if(n[0] == 1) 1979 auto k = b.sliced(1, b.length); 1980 else 1981 auto k = b.transposed; 1982 1983 static if(is(IteratorB == T*)) 1984 auto m = (allowDestroy && k._stride!1 == 1) ? k.assumeCanonical : k.as!T.slice.canonical; 1985 else 1986 auto m = k.as!T.slice.canonical; 1987 auto work = [m.length!0].uninitSlice!T; 1988 static if(is(T == double) || is(T == float)) 1989 ormqr!T(a_, tau_, m, work, 'L', 'T'); 1990 else 1991 unmqr!T(a_, tau_, m, work, 'L', 'C'); 1992 trsm!T(Side.Right, Uplo.Lower, Diag.NonUnit, cast(T) 1.0, a_, m); 1993 1994 return m.transposed; 1995 } 1996 1997 /// 1998 unittest 1999 { 2000 auto A = 2001 [ 3, 1, -1, 2, 2002 -5, 1, 3, -4, 2003 2, 0, 1, -1, 2004 1, -5, 3, -3 ] 2005 .sliced(4, 4) 2006 .as!double.slice; 2007 2008 import mir.random.variable; 2009 import mir.random.algorithm; 2010 auto B = randomSlice!double(uniformVar(-100, 100), 4, 100); 2011 2012 auto C = qrDecomp(A); 2013 auto X = C.solve(B); 2014 2015 import std.math: approxEqual; 2016 import mir.ndslice.algorithm: equal; 2017 assert(equal!approxEqual(mtimes(A, X), B)); 2018 } 2019 2020 2021 unittest 2022 { 2023 auto A = 2024 [ 3, 1, -1, 2, 2025 -5, 1, 3, -4, 2026 2, 0, 1, -1, 2027 1, -5, 3, -3 ] 2028 .sliced(4, 4) 2029 .as!float.slice; 2030 auto B = [ 6, -12, 1, 3 ].sliced(4).as!double.slice.canonical; 2031 auto B_ = B.slice.sliced(B.length, 1); 2032 auto C = qrDecomp(A); 2033 auto X = qrSolve(C.matrix, C.tau, B); 2034 2035 import std.math: approxEqual; 2036 import mir.ndslice.algorithm: equal; 2037 assert(equal!approxEqual(mtimes(A, X), B_)); 2038 } 2039 2040 unittest 2041 { 2042 auto A = 2043 [ 1, 1, 0, 2044 1, 0, 1, 2045 0, 1, 1 ] 2046 .sliced(3, 3) 2047 .as!double.slice; 2048 2049 auto B = 2050 [ 7, 6, 98, 2051 4, 8, 17, 2052 5, 3, 24 ] 2053 .sliced(3, 3) 2054 .as!float.slice; 2055 auto C = qrDecomp(A); 2056 auto X = qrSolve(C.matrix, C.tau, B); 2057 2058 import std.math: approxEqual; 2059 import mir.ndslice.algorithm: equal; 2060 2061 assert(equal!approxEqual(mtimes(A, X), B)); 2062 } 2063 2064 unittest 2065 { 2066 auto A = 2067 [ 1, 1, 0, 2068 1, 0, 1, 2069 0, 1, 1 ] 2070 .sliced(3, 3) 2071 .as!cdouble.slice; 2072 2073 auto B = 2074 [ 15, 78, 11, 2075 21, 47, 71, 2076 81, 11, 81 ] 2077 .sliced(3, 3) 2078 .as!cfloat.slice; 2079 auto C = qrDecomp(A); 2080 auto X = qrSolve(C.matrix, C.tau, B); 2081 auto res = mtimes(A, X); 2082 2083 import mir.ndslice.algorithm: equal; 2084 assert(equal!((a, b) => fabs(a - b) < 1e-12)(res, B)); 2085 }