1 /++ 2 $(H1 Lubeck 2 - `@nogc` Linear Algebra) 3 +/ 4 module lubeck2; 5 6 import mir.algorithm.iteration: equal, each; 7 import mir.blas; 8 import mir.exception; 9 import mir.internal.utility: isComplex, realType; 10 import mir.lapack; 11 import mir.math.common: sqrt, approxEqual, pow, fabs; 12 import mir.ndslice; 13 import mir.rc.array; 14 import mir.utility: min, max; 15 import std.traits: isFloatingPoint, Unqual; 16 import std.typecons: Flag, Yes, No; 17 18 /++ 19 Identity matrix. 20 21 Params: 22 n = number of columns 23 m = optional number of rows, default n 24 Results: 25 Matrix which is 1 on the diagonal and 0 elsewhere 26 +/ 27 @safe pure nothrow @nogc 28 Slice!(RCI!T, 2) eye(T = double)( 29 size_t n, 30 size_t m = 0 31 ) 32 if (isFloatingPoint!T || isComplex!T) 33 in 34 { 35 assert(n>0); 36 assert(m>=0); 37 } 38 out (i) 39 { 40 assert(i.length!0==n); 41 assert(i.length!1== (m==0 ? n : m)); 42 } 43 do 44 { 45 auto c = rcslice!T([n, (m==0?n:m)], cast(T)0); 46 c.diagonal[] = cast(T)1; 47 return c; 48 } 49 50 /// Real numbers 51 @safe pure nothrow 52 unittest 53 { 54 import mir.ndslice; 55 import mir.math; 56 57 assert(eye(1)== [ 58 [1]]); 59 assert(eye(2)== [ 60 [1, 0], 61 [0, 1]]); 62 assert(eye(3)== [ 63 [1, 0, 0], 64 [0, 1, 0], 65 [0, 0, 1]]); 66 assert(eye(1,2) == [ 67 [1,0]]); 68 assert(eye(2,1) == [ 69 [1], 70 [0]]); 71 } 72 73 /++ 74 General matrix-matrix multiplication. Allocates result to using Mir refcounted arrays. 75 Params: 76 a = m(rows) x k(cols) matrix 77 b = k(rows) x n(cols) matrix 78 Result: 79 m(rows) x n(cols) 80 +/ 81 @safe pure nothrow @nogc 82 Slice!(RCI!T, 2) mtimes(T, SliceKind kindA, SliceKind kindB)( 83 Slice!(const(T)*, 2, kindA) a, 84 Slice!(const(T)*, 2, kindB) b 85 ) 86 if (isFloatingPoint!T || isComplex!T) 87 in 88 { 89 assert(a.length!1 == b.length!0); 90 } 91 out (c) 92 { 93 assert(c.length!0 == a.length!0); 94 assert(c.length!1 == b.length!1); 95 } 96 do 97 { 98 // optimisations for spcecial cases can be added in the future 99 auto c = mininitRcslice!T(a.length!0, b.length!1); 100 gemm(cast(T)1, a, b, cast(T)0, c.lightScope); 101 return c; 102 } 103 104 /// ditto 105 @safe pure nothrow @nogc 106 Slice!(RCI!(Unqual!A), 2) mtimes(A, B, SliceKind kindA, SliceKind kindB)( 107 auto ref const Slice!(RCI!A, 2, kindA) a, 108 auto ref const Slice!(RCI!B, 2, kindB) b 109 ) 110 if (is(Unqual!A == Unqual!B)) 111 in 112 { 113 assert(a.length!1 == b.length!0); 114 } 115 do 116 { 117 auto scopeA = a.lightScope.lightConst; 118 auto scopeB = b.lightScope.lightConst; 119 return .mtimes(scopeA, scopeB); 120 } 121 122 @safe pure nothrow @nogc 123 Slice!(RCI!(Unqual!A), 2) mtimes(A, B, SliceKind kindA, SliceKind kindB)( 124 auto ref const Slice!(RCI!A, 2, kindA) a, 125 Slice!(const(B)*, 2, kindB) b 126 ) 127 if (is(Unqual!A == Unqual!B)) 128 in 129 { 130 assert(a.length!1 == b.length!0); 131 } 132 do 133 { 134 auto scopeA = a.lightScope.lightConst; 135 return .mtimes(scopeA, b); 136 } 137 138 @safe pure nothrow @nogc 139 Slice!(RCI!(Unqual!A), 2) mtimes(A, B, SliceKind kindA, SliceKind kindB)( 140 Slice!(const(A)*, 2, kindA) a, 141 auto ref const Slice!(RCI!B, 2, kindB) b 142 ) 143 if (is(Unqual!A == Unqual!B)) 144 in 145 { 146 assert(a.length!1 == b.length!0); 147 } 148 do 149 { 150 auto scopeB = b.lightScope.lightConst; 151 return .mtimes(a, scopeB); 152 } 153 /// Real numbers 154 @safe pure nothrow 155 unittest 156 { 157 import mir.ndslice; 158 import mir.math; 159 160 auto a = mininitRcslice!double(3, 5); 161 auto b = mininitRcslice!double(5, 4); 162 163 a[] = 164 [[-5, 1, 7, 7, -4], 165 [-1, -5, 6, 3, -3], 166 [-5, -2, -3, 6, 0]]; 167 168 b[] = 169 [[-5, -3, 3, 1], 170 [ 4, 3, 6, 4], 171 [-4, -2, -2, 2], 172 [-1, 9, 4, 8], 173 [ 9, 8, 3, -2]]; 174 175 assert(mtimes!(double, double)(a, b) == 176 [[-42, 35, -7, 77], 177 [-69, -21, -42, 21], 178 [ 23, 69, 3, 29]]); 179 } 180 181 /// Complex numbers 182 @safe pure nothrow 183 unittest 184 { 185 import mir.ndslice; 186 import mir.math; 187 188 auto a = mininitRcslice!cdouble(3, 5); 189 auto b = mininitRcslice!cdouble(5, 4); 190 191 a[] = 192 [[-5 + 0i, 1, 7, 7, -4], 193 [-1 + 0i, -5, 6, 3, -3], 194 [-5 + 0i, -2, -3, 6, 0]]; 195 196 b[] = 197 [[-5 + 0i, -3, 3, 1], 198 [ 4 + 0i, 3, 6, 4], 199 [-4 + 0i, -2, -2, 2], 200 [-1 + 0i, 9, 4, 8], 201 [ 9 + 0i, 8, 3, -2]]; 202 203 assert(mtimes!(cdouble, cdouble)(a, b) == 204 [[-42, 35, -7, 77], 205 [-69, -21, -42, 21], 206 [ 23, 69, 3, 29]]); 207 } 208 209 /++ 210 Solve systems of linear equations AX = B for X. 211 Computes minimum-norm solution to a linear least squares problem 212 if A is not a square matrix. 213 +/ 214 @safe pure @nogc 215 Slice!(RCI!T, 2) mldivide (T, SliceKind kindA, SliceKind kindB)( 216 Slice!(const(T)*, 2, kindA) a, 217 Slice!(const(T)*, 2, kindB) b, 218 ) 219 if (isFloatingPoint!T || isComplex!T) 220 { 221 enforce!"mldivide: parameter shapes mismatch"(a.length!0 == b.length!0); 222 223 auto rcat = a.transposed.as!T.rcslice; 224 auto at = rcat.lightScope.canonical; 225 auto rcbt = b.transposed.as!T.rcslice; 226 auto bt = rcbt.lightScope.canonical; 227 size_t info; 228 if (a.length!0 == a.length!1) 229 { 230 auto rcipiv = at.length.mininitRcslice!lapackint; 231 auto ipiv = rcipiv.lightScope; 232 foreach(i; 0 .. ipiv.length) 233 ipiv[i] = 0; 234 info = gesv!T(at, ipiv, bt); 235 //info > 0 means some diagonla elem of U is 0 so no solution 236 } 237 else 238 { 239 static if(!isComplex!T) 240 { 241 size_t liwork = void; 242 auto lwork = gelsd_wq(at, bt, liwork); 243 auto rcs = min(at.length!0, at.length!1).mininitRcslice!T; 244 auto s = rcs.lightScope; 245 auto rcwork = lwork.rcslice!T; 246 auto work = rcwork.lightScope; 247 auto rciwork = liwork.rcslice!lapackint; 248 auto iwork = rciwork.lightScope; 249 size_t rank = void; 250 T rcond = -1; 251 252 info = gelsd!T(at, bt, s, rcond, rank, work, iwork); 253 //info > 0 means that many components failed to converge 254 } 255 else 256 { 257 size_t liwork = void; 258 size_t lrwork = void; 259 auto lwork = gelsd_wq(at, bt, lrwork, liwork); 260 auto rcs = min(at.length!0, at.length!1).mininitRcslice!(realType!T); 261 auto s = rcs.lightScope; 262 auto rcwork = lwork.rcslice!T; 263 auto work = rcwork.lightScope; 264 auto rciwork = liwork.rcslice!lapackint; 265 auto iwork = rciwork.lightScope; 266 auto rcrwork = lrwork.rcslice!(realType!T); 267 auto rwork = rcrwork.lightScope; 268 size_t rank = void; 269 realType!T rcond = -1; 270 271 info = gelsd!T(at, bt, s, rcond, rank, work, rwork, iwork); 272 //info > 0 means that many components failed to converge 273 } 274 bt = bt[0 .. $, 0 .. at.length!0]; 275 } 276 enforce!"mldivide: some off-diagonal elements of an intermediate bidiagonal form did not converge to zero."(!info); 277 return bt.transposed.as!T.rcslice; 278 } 279 280 /// ditto 281 @safe pure @nogc 282 Slice!(RCI!(Unqual!A), 2) 283 mldivide 284 (A, B, SliceKind kindA, SliceKind kindB)( 285 auto ref const Slice!(RCI!A, 2, kindA) a, 286 auto ref const Slice!(RCI!B, 2, kindB) b 287 ) 288 do 289 { 290 auto al = a.lightScope.lightConst; 291 auto bl = b.lightScope.lightConst; 292 return mldivide(al, bl); 293 } 294 295 /// ditto 296 @safe pure @nogc 297 Slice!(RCI!(Unqual!A), 1) 298 mldivide 299 (A, B, SliceKind kindA, SliceKind kindB)( 300 auto ref const Slice!(RCI!A, 2, kindA) a, 301 auto ref const Slice!(RCI!B, 1, kindB) b 302 ) 303 do 304 { 305 auto al = a.lightScope.lightConst; 306 auto bl = b.lightScope.lightConst; 307 return mldivide(al, bl); 308 } 309 310 /// ditto 311 @safe pure @nogc 312 Slice!(RCI!T, 1) mldivide (T, SliceKind kindA, SliceKind kindB)( 313 Slice!(const(T)*, 2, kindA) a, 314 Slice!(const(T)*, 1, kindB) b, 315 ) 316 if (isFloatingPoint!T || isComplex!T) 317 { 318 import mir.ndslice.topology: flattened; 319 return mldivide(a, b.sliced(b.length, 1)).flattened; 320 } 321 322 pure unittest 323 { 324 auto a = mininitRcslice!double(2, 2); 325 a[] = [[2,3], 326 [1, 4]]; 327 auto res = mldivide(eye(2), a); 328 assert(equal!approxEqual(res, a)); 329 auto b = mininitRcslice!cdouble(2, 2); 330 b[] = [[2+1i,3+2i], 331 [1+3i, 4+4i]]; 332 auto cres = mldivide(eye!cdouble(2), b); 333 assert(cres == b); 334 auto c = mininitRcslice!double(2, 2); 335 c[] = [[5,3], 336 [2,6]]; 337 auto d = mininitRcslice!double(2,1); 338 d[] = [[4], 339 [1]]; 340 auto e = mininitRcslice!double(2,1); 341 e[] = [[23], 342 [14]]; 343 res = mldivide(c, e); 344 assert(equal!approxEqual(res, d)); 345 } 346 347 pure unittest 348 { 349 import mir.ndslice; 350 import mir.math; 351 352 auto a = mininitRcslice!double(6, 4); 353 a[] = [ 354 -0.57, -1.28, -0.39, 0.25, 355 -1.93, 1.08, -0.31, -2.14, 356 2.30, 0.24, 0.40, -0.35, 357 -1.93, 0.64, -0.66, 0.08, 358 0.15, 0.30, 0.15, -2.13, 359 -0.02, 1.03, -1.43, 0.50, 360 ].sliced(6, 4); 361 362 auto b = mininitRcslice!double(6,1); 363 b[] = [ 364 -2.67, 365 -0.55, 366 3.34, 367 -0.77, 368 0.48, 369 4.10, 370 ].sliced(6,1); 371 372 auto x = mininitRcslice!double(4,1); 373 x[] = [ 374 1.5339, 375 1.8707, 376 -1.5241, 377 0.0392 378 ].sliced(4,1); 379 380 auto res = mldivide(a, b); 381 assert(equal!((a, b) => fabs(a - b) < 5e-5)(res, x)); 382 383 auto ca = mininitRcslice!cdouble(6, 4); 384 ca[] = [ 385 -0.57 + 0.0i, -1.28 + 0.0i, -0.39 + 0.0i, 0.25 + 0.0i, 386 -1.93 + 0.0i, 1.08 + 0.0i, -0.31 + 0.0i, -2.14 + 0.0i, 387 2.30 + 0.0i, 0.24 + 0.0i, 0.40 + 0.0i, -0.35 + 0.0i, 388 -1.93 + 0.0i, 0.64 + 0.0i, -0.66 + 0.0i, 0.08 + 0.0i, 389 0.15 + 0.0i, 0.30 + 0.0i, 0.15 + 0.0i, -2.13 + 0.0i, 390 -0.02 + 0.0i, 1.03 + 0.0i, -1.43 + 0.0i, 0.50 + 0.0i, 391 ].sliced(6, 4); 392 393 auto cb = mininitRcslice!cdouble(6,1); 394 cb[] = [ 395 -2.67 + 0.0i, 396 -0.55 + 0.0i, 397 3.34 + 0.0i, 398 -0.77 + 0.0i, 399 0.48 + 0.0i, 400 4.10 + 0.0i, 401 ].sliced(6,1); 402 403 auto cx = mininitRcslice!cdouble(4,1); 404 cx[] = [ 405 1.5339 + 0.0i, 406 1.8707 + 0.0i, 407 -1.5241 + 0.0i, 408 0.0392 + 0.0i 409 ].sliced(4,1); 410 411 auto cres = mldivide(ca, cb); 412 assert(equal!((a, b) => fabs(a - b) < 5e-5)(cres, x)); 413 } 414 415 /++ 416 Solve systems of linear equations AX = I for X, where I is the identity. 417 X is the right inverse of A if it exists, it's also a (Moore-Penrose) Pseudoinverse if A is invertible then X is the inverse. 418 Computes minimum-norm solution to a linear least squares problem 419 if A is not a square matrix. 420 +/ 421 @safe pure @nogc Slice!(RCI!A, 2) mlinverse(A, SliceKind kindA)( 422 auto ref Slice!(RCI!A, 2, kindA) a 423 ) 424 { 425 auto aScope = a.lightScope.lightConst; 426 return mlinverse!A(aScope); 427 } 428 429 @safe pure @nogc Slice!(RCI!A, 2) mlinverse(A, SliceKind kindA)( 430 Slice!(const(A)*, 2, kindA) a 431 ) 432 { 433 auto a_i = a.as!A.rcslice; 434 auto a_i_light = a_i.lightScope.canonical; 435 auto rcipiv = min(a_i.length!0, a_i.length!1).mininitRcslice!lapackint; 436 auto ipiv = rcipiv.lightScope; 437 auto info = getrf!A(a_i_light, ipiv); 438 if (info == 0) 439 { 440 auto rcwork = getri_wq!A(a_i_light).mininitRcslice!A; 441 auto work = rcwork.lightScope; 442 info = getri!A(a_i_light, ipiv, work); 443 } 444 enforce!"Matrix is not invertible as has zero determinant"(!info); 445 return a_i; 446 } 447 448 pure unittest 449 { 450 import mir.ndslice; 451 import mir.math; 452 453 auto a = mininitRcslice!double(2, 2); 454 a[] = [[1,0], 455 [0,-1]]; 456 auto ans = mlinverse!double(a); 457 assert(equal!approxEqual(ans, a)); 458 } 459 460 pure 461 unittest 462 { 463 import mir.ndslice; 464 import mir.math; 465 466 auto a = mininitRcslice!double(2, 2); 467 a[] = [[ 0, 1], 468 [-1, 0]]; 469 auto aInv = mininitRcslice!double(2, 2); 470 aInv[] = [[0, -1], 471 [1, 0]]; 472 auto ans = a.mlinverse; 473 assert(equal!approxEqual(ans, aInv)); 474 } 475 476 ///Singuar value decomposition result 477 struct SvdResult(T) 478 { 479 /// 480 Slice!(RCI!T, 2) u; 481 ///Singular Values 482 Slice!(RCI!T) sigma; 483 /// 484 Slice!(RCI!T, 2) vt; 485 } 486 487 /++ 488 Computes the singular value decomposition. 489 Params: 490 a = input `M x N` matrix 491 slim = If true the first `min(M,N)` columns of `u` and the first 492 `min(M,N)` rows of `vt` are returned in the ndslices `u` and `vt`. 493 out result = $(LREF SvdResult). ] 494 Returns: error code from CBlas 495 +/ 496 @safe pure @nogc SvdResult!T svd 497 ( 498 T, 499 string algorithm = "gesvd", 500 SliceKind kind, 501 )( 502 Slice!(const(T)*, 2, kind) a, 503 Flag!"slim" slim = No.slim, 504 ) 505 if (algorithm == "gesvd" || algorithm == "gesdd") 506 { 507 auto m = cast(lapackint)a.length!1; 508 auto n = cast(lapackint)a.length!0; 509 510 auto s = mininitRcslice!T(min(m, n)); 511 auto u = mininitRcslice!T(slim ? s.length : m, m); 512 auto vt = mininitRcslice!T(n, slim ? s.length : n); 513 514 static if (algorithm == "gesvd") 515 { 516 auto jobu = slim ? 'S' : 'A'; 517 auto jobvt = slim ? 'S' : 'A'; 518 auto rca_sliced = a.as!T.rcslice; 519 auto rca = rca_sliced.lightScope.canonical; 520 auto rcwork = gesvd_wq(jobu, jobvt, rca, u.lightScope.canonical, vt.lightScope.canonical).mininitRcslice!T; 521 auto work = rcwork.lightScope; 522 auto info = gesvd(jobu, jobvt, rca, s.lightScope, u.lightScope.canonical, vt.lightScope.canonical, work); 523 } 524 else // gesdd 525 { 526 auto rciwork = mininitRcslice!lapackint(s.length * 8); 527 auto iwork = rciwork.lightScope; 528 auto jobz = slim ? 'S' : 'A'; 529 auto rca_sliced = a.as!T.rcslice; 530 auto rca = rca_sliced.lightScope.canonical; 531 auto rcwork = gesdd_wq(jobz, rca, u.lightScope, vt.lightScope).minitRcslice!T; 532 auto work = rcwork.lightScope; 533 auto info = gesdd(jobz, rca, s.lightScope, u.lightScope, vt.lightScope, work, iwork); 534 } 535 enum msg = (algorithm == "gesvd" ? "svd: DBDSDC did not converge, updating process failed" : "svd: DBDSQR did not converge"); 536 enforce!("svd: " ~ msg)(!info); 537 return SvdResult!T(vt, s, u); //transposed 538 } 539 540 @safe pure SvdResult!T svd 541 ( 542 T, 543 string algorithm = "gesvd", 544 SliceKind kind, 545 )( 546 auto ref scope const Slice!(RCI!T,2,kind) matrix, 547 Flag!"slim" slim = No.slim 548 ) 549 { 550 auto matrixScope = matrix.lightScope.lightConst; 551 return svd!(T, algorithm)(matrixScope, slim); 552 } 553 554 pure unittest 555 { 556 import mir.ndslice; 557 import mir.math; 558 559 auto a = mininitRcslice!double(6, 4); 560 a[] = [[7.52, -1.10, -7.95, 1.08], 561 [-0.76, 0.62, 9.34, -7.10], 562 [5.13, 6.62, -5.66, 0.87], 563 [-4.75, 8.52, 5.75, 5.30], 564 [1.33, 4.91, -5.49, -3.52], 565 [-2.40, -6.77, 2.34, 3.95]]; 566 567 auto r1 = a.svd; 568 569 auto sigma1 = rcslice!double(a.shape, 0); 570 sigma1.diagonal[] = r1.sigma; 571 auto m1 = (r1.u).mtimes(sigma1).mtimes(r1.vt); 572 assert(equal!((a, b) => fabs(a-b)< 1e-8)(a, m1)); 573 574 auto r2 = a.svd; 575 576 auto sigma2 = rcslice!double(a.shape, 0.0); 577 sigma2.diagonal[] = r2.sigma; 578 auto m2 = r2.u.mtimes(sigma2).mtimes(r2.vt); 579 580 assert(equal!((a, b) => fabs(a-b)< 1e-8)(a, m2)); 581 582 auto r = a.svd(Yes.slim); 583 assert(r.u.shape == [6, 4]); 584 assert(r.vt.shape == [4, 4]); 585 } 586 587 pure unittest 588 { 589 import mir.ndslice; 590 import mir.math; 591 592 auto a = mininitRcslice!double(6, 4); 593 a[] = [[7.52, -1.10, -7.95, 1.08], 594 [-0.76, 0.62, 9.34, -7.10], 595 [5.13, 6.62, -5.66, 0.87], 596 [-4.75, 8.52, 5.75, 5.30], 597 [1.33, 4.91, -5.49, -3.52], 598 [-2.40, -6.77, 2.34, 3.95]]; 599 600 auto r1 = a.svd(No.slim); 601 602 auto sigma1 = rcslice!double(a.shape, 0.0); 603 sigma1.diagonal[] = r1.sigma; 604 auto m1 = r1.u.mtimes(sigma1).mtimes(r1.vt); 605 606 assert(equal!((a, b) => fabs(a-b)< 1e-8)(a, m1)); 607 } 608 609 610 struct EigenResult(T) 611 { 612 Slice!(RCI!(complexType!T), 2) eigenvectors; 613 Slice!(RCI!(complexType!T)) eigenvalues; 614 } 615 616 struct QRResult(T) 617 { 618 Slice!(RCI!T,2) Q; 619 Slice!(RCI!T,2) R; 620 621 @safe this 622 ( 623 SliceKind kindA, 624 SliceKind kindTau 625 )( 626 Slice!(RCI!T, 2, kindA) a, 627 Slice!(RCI!T, 1, kindTau) tau 628 ) 629 { 630 auto aScope = a.lightScope.lightConst; 631 auto tauScope = tau.lightScope.lightConst; 632 this(aScope, tauScope); 633 } 634 635 @safe this 636 ( 637 SliceKind kindA, 638 SliceKind kindTau 639 )( 640 Slice!(const(T)*, 2, kindA) a, 641 Slice!(const(T)*, 1, kindTau) tau 642 ) 643 { 644 R = mininitRcslice!T(a.shape); 645 foreach (i; 0 .. R.length!0) 646 { 647 foreach (j; 0 .. R.length!1) 648 { 649 if (i >= j) 650 { 651 R[j, i] = a[i, j]; 652 } 653 else 654 { 655 R[j ,i] = cast(T)0; 656 } 657 } 658 } 659 auto rcwork = mininitRcslice!T(a.length!0); 660 auto work = rcwork.lightScope; 661 auto aSliced = a.as!T.rcslice; 662 auto aScope = aSliced.lightScope.canonical; 663 auto tauSliced = tau.as!T.rcslice; 664 auto tauScope = tauSliced.lightScope; 665 orgqr!T(aScope, tauScope, work); 666 Q = aScope.transposed.as!T.rcslice; 667 } 668 } 669 670 @safe pure QRResult!T qr(T, SliceKind kind)( 671 auto ref const Slice!(RCI!T, 2, kind) matrix 672 ) 673 { 674 auto matrixScope = matrix.lightScope.lightConst; 675 return qr!(T, kind)(matrixScope); 676 } 677 678 @safe pure QRResult!T qr(T, SliceKind kind)( 679 auto ref const Slice!(const(T)*, 2, kind) matrix 680 ) 681 { 682 auto a = matrix.transposed.as!T.rcslice; 683 auto tau = mininitRcslice!T(a.length!0); 684 auto rcwork = mininitRcslice!T(a.length!0); 685 auto work = rcwork.lightScope; 686 auto aScope = a.lightScope.canonical; 687 auto tauScope = tau.lightScope; 688 geqrf!T(aScope, tauScope, work); 689 return QRResult!T(aScope, tauScope); 690 } 691 692 pure nothrow 693 unittest 694 { 695 import mir.ndslice; 696 import mir.math; 697 698 auto data = mininitRcslice!double(3, 3); 699 data[] = [[12, -51, 4], 700 [ 6, 167, -68], 701 [-4, 24, -41]]; 702 703 auto res = qr(data); 704 auto q = mininitRcslice!double(3, 3); 705 q[] = [[-6.0/7.0, 69.0/175.0, 58.0/175.0], 706 [-3.0/7.0, -158.0/175.0, -6.0/175.0], 707 [ 2.0/7.0, -6.0/35.0 , 33.0/35.0 ]]; 708 auto aE = function (double x, double y) => approxEqual(x, y, 0.00005, 0.00005); 709 auto r = mininitRcslice!double(3, 3); 710 r[] = [[-14, -21, 14], 711 [ 0, -175, 70], 712 [ 0, 0, -35]]; 713 assert(equal!approxEqual(mtimes(res.Q, res.R), data)); 714 } 715 716 @safe pure @nogc 717 EigenResult!(realType!T) eigen(T, SliceKind kind)( 718 auto ref Slice!(const(T)*,2, kind) a, 719 ) 720 { 721 enforce!"eigen: input matrix must be square"(a.length!0 == a.length!1); 722 const n = a.length; 723 auto rcw = n.mininitRcslice!(complexType!T); 724 auto w = rcw.lightScope; 725 auto rcwork = mininitRcslice!T(16 * n); 726 auto work = rcwork.lightScope; 727 auto z = [n, n].mininitRcslice!(complexType!T);//vl (left eigenvectors) 728 auto rca = a.transposed.as!T.rcslice; 729 auto as = rca.lightScope; 730 static if (isComplex!T) 731 { 732 auto rcrwork = [2 * n].mininitRcslice!(realType!T); 733 auto rwork = rcrwork.lightScope; 734 auto info = geev!(T, realType!T)('N', 'V', as.canonical, w, Slice!(T*, 2, Canonical).init, z.lightScope.canonical, work, rwork); 735 enforce!"eigen failed"(!info); 736 } 737 else 738 { 739 alias C = complexType!T; 740 auto wr = sliced((cast(T[]) w.field)[0 .. n]); 741 auto wi = sliced((cast(T[]) w.field)[n .. n * 2]); 742 auto rczr = [n, n].mininitRcslice!T; 743 auto zr = rczr.lightScope; 744 auto info = geev!T('N', 'V', as.canonical, wr, wi, Slice!(T*, 2, Canonical).init, zr.canonical, work); 745 enforce!"eigen failed"(!info); 746 work[0 .. n] = wr; 747 work[n .. n * 2] = wi; 748 foreach (i, ref e; w.field) 749 { 750 e = work[i] + work[n + i] * 1fi; 751 auto zi = z.lightScope[i]; 752 if (e.im > 0) 753 zi[] = zip(zr[i], zr[i + 1]).map!"a + b * 1fi"; 754 else 755 if (e.im < 0) 756 zi[] = zip(zr[i - 1], zr[i]).map!"a - b * 1fi"; 757 else 758 zi[] = zr[i].as!C; 759 } 760 } 761 return typeof(return)(z, rcw); 762 } 763 764 //@safe pure @nogc 765 EigenResult!(realType!T) eigen(T, SliceKind kind)( 766 auto ref Slice!(RCI!T,2, kind) a 767 ) 768 { 769 auto as = a.lightScope.lightConst; 770 return eigen(as); 771 } 772 773 /// 774 // pure 775 unittest 776 { 777 import mir.ndslice; 778 import mir.math; 779 780 auto data = 781 [[ 0, 1], 782 [-1, 0]].fuse.as!double.rcslice; 783 auto eigenvalues = [0 + 1i, 0 - 1i].sliced; 784 auto eigenvectors = 785 [[0 - 1i, 1 + 0i], 786 [0 + 1i, 1 + 0i]].fuse; 787 788 auto res = data.eigen; 789 790 assert(res.eigenvalues.equal!approxEqual(eigenvalues)); 791 foreach (i; 0 .. eigenvectors.length) 792 assert((res.eigenvectors.lightScope[i] / eigenvectors[i]).diff.slice.nrm2.approxEqual(0)); 793 } 794 795 796 /// 797 @safe pure 798 unittest 799 { 800 import mir.ndslice; 801 import mir.math; 802 import mir.blas; 803 804 auto data = 805 [[0, 1, 0], 806 [0, 0, 1], 807 [1, 0, 0]].fuse.as!double.rcslice; 808 auto c = 3.0.sqrt; 809 auto eigenvalues = [(-1 + c * 1i) / 2, (-1 - c * 1i) / 2, 1 + 0i]; 810 811 auto eigenvectors = 812 [[-1 + c * 1i , -1 - c * 1i , 2 + 0i], 813 [-1 - c * 1i , -1 + c * 1i , 2 + 0i], 814 [1 + 0i, 1 + 0i, 1 + 0i]].fuse; 815 816 auto res = data.eigen; 817 818 assert(res.eigenvalues.equal!approxEqual(eigenvalues)); 819 foreach (i; 0 .. eigenvectors.length) 820 assert((res.eigenvectors.lightScope[i] / eigenvectors[i]).diff.slice.nrm2.approxEqual(0)); 821 822 auto cdata = data.lightScope.as!cdouble.rcslice; 823 res = cdata.eigen; 824 825 assert(res.eigenvalues.equal!approxEqual(eigenvalues)); 826 foreach (i; 0 .. eigenvectors.length) 827 assert((res.eigenvectors.lightScope[i] / eigenvectors[i]).diff.slice.nrm2.approxEqual(0)); 828 } 829 830 831 /// Principal component analysis result. 832 struct PcaResult(T) 833 { 834 /// Eigenvectors (Eigenvectors[i] is the ith eigenvector) 835 Slice!(RCI!T,2) eigenvectors; 836 /// Principal component scores. (Input matrix rotated to basis of eigenvectors) 837 Slice!(RCI!T, 2) scores; 838 /// Principal component variances. (Eigenvalues) 839 Slice!(RCI!T) eigenvalues; 840 /// The means of each data column (0 if not centred) 841 Slice!(RCI!T) mean; 842 /// The standard deviations of each column (1 if not normalized) 843 Slice!(RCI!T) stdDev; 844 /// Principal component Loadings vectors (Eigenvectors times sqrt(Eigenvalue)) 845 Slice!(RCI!T, 2) loadings() 846 { 847 auto result = mininitRcslice!T(eigenvectors.shape); 848 for (size_t i = 0; i < eigenvectors.length!0 && i < eigenvalues.length; i++){ 849 if(eigenvalues[i] != 0) 850 foreach (j; 0 .. eigenvectors.length!1) 851 result[i, j] = eigenvectors[i, j] * sqrt(eigenvalues[i]); 852 else 853 result[i][] = eigenvectors[i][]; 854 } 855 return result; 856 } 857 858 Slice!(RCI!T, 2) loadingsScores() {// normalized data in basis of {sqrt(eval)*evect} 859 //if row i is a vector p, the original data point is mean[] + p[] * stdDev[] 860 auto result = mininitRcslice!T(scores.shape); 861 foreach (i; 0 .. scores.length!0){ 862 for (size_t j=0; j < scores.length!1 && j < eigenvalues.length; j++) 863 { 864 if(eigenvalues[j] != 0) 865 result[i, j] = scores[i, j] / sqrt(eigenvalues[j]); 866 else 867 result[i, j] = scores[i, j]; 868 } 869 } 870 return result; 871 } 872 873 Slice!(RCI!T) explainedVariance() { 874 import mir.math.sum: sum; 875 //auto totalVariance = eigenvalues.sum!"kb2"; 876 auto totalVariance = 0.0; 877 foreach (val; eigenvalues) 878 totalVariance += val; 879 auto result = mininitRcslice!double(eigenvalues.shape); 880 foreach (i; 0 .. result.length!0) 881 result[i] = eigenvalues[i] / totalVariance; 882 return result; 883 } 884 885 Slice!(RCI!T,2) q() 886 { 887 return eigenvectors; 888 } 889 890 //returns matrix to transform into basis of 'n' most significatn eigenvectors 891 Slice!(RCI!(T),2) q(size_t n) 892 in { 893 assert(n <= eigenvectors.length!0); 894 } 895 do { 896 auto res = mininitRcslice!T(eigenvectors.shape); 897 res[] = eigenvectors; 898 for ( ; n < eigenvectors.length!0; n++) 899 res[n][] = 0; 900 return res; 901 } 902 //returns matrix to transform into basis of eigenvectors with value larger than delta 903 Slice!(RCI!(T),2) q(T delta) 904 do 905 { 906 auto res = mininitRcslice!T(eigenvectors.shape); 907 res[] = eigenvectors; 908 foreach (i; 0 .. eigenvectors.length!0) 909 if (fabs(eigenvalues[i]) <= delta) 910 res[i][] = 0; 911 return res; 912 } 913 914 //transforms data into eigenbasis 915 Slice!(RCI!(T),2) transform(Slice!(RCI!(T),2) data) 916 { 917 return q.mlinverse.mtimes(data); 918 } 919 920 //transforms data into eigenbasis 921 Slice!(RCI!(T),2) transform(Slice!(RCI!(T),2) data, size_t n) 922 { 923 return q(n).mlinverse.mtimes(data); 924 } 925 //transforms data into eigenbasis 926 Slice!(RCI!(T),2) transform(Slice!(RCI!(T),2) data, T delta) 927 { 928 return q(delta).mlinverse.mtimes(data); 929 } 930 } 931 932 933 /++ 934 Principal component analysis of raw data. 935 Template: 936 correlation = Flag to use correlation matrix instead of covariance 937 Params: 938 data = input `M x N` matrix, where 'M (rows)>= N(cols)' 939 devEst = 940 meanEst = 941 fixEigenvectorDirections = 942 Returns: $(LREF PcaResult) 943 +/ 944 @safe pure @nogc 945 PcaResult!T pca( 946 SliceKind kind, 947 T 948 )( 949 Slice!(const(T)*, 2, kind) data, 950 DeviationEstimator devEst = DeviationEstimator.sample, 951 MeanEstimator meanEst = MeanEstimator.average, 952 Flag!"fixEigenvectorDirections" fixEigenvectorDirections = Yes.fixEigenvectorDirections, 953 ) 954 in 955 { 956 assert(data.length!0 >= data.length!1); 957 } 958 do 959 { 960 real n = (data.length!0 <= 1 ? 1 : data.length!0 -1 );//num observations 961 auto mean = rcslice!(T,1)([data.length!1], cast(T)0); 962 auto stdDev = rcslice!(T,1)([data.length!1], cast(T)1); 963 auto centeredData = centerColumns!T(data, mean, meanEst); 964 //this part gets the eigenvectors of the sample covariance without explicitly calculating the covariance matrix 965 //to implement a minimum covariance deteriminant this block would need to be redone 966 //firstly one would calculate the MCD then call eigen to get it's eigenvectors 967 auto processedData = normalizeColumns!T(centeredData, stdDev, devEst); 968 auto svdResult = processedData.svd(Yes.slim); 969 with (svdResult) 970 { 971 //u[i][] is the ith eigenvector 972 foreach (i; 0 .. u.length!0){ 973 foreach (j; 0 .. u.length!1){ 974 u[i, j] *= sigma[j]; 975 } 976 } 977 auto eigenvalues = mininitRcslice!double(sigma.shape); 978 for (size_t i = 0; i < sigma.length && i < eigenvalues.length; i++){ 979 eigenvalues[i] = sigma[i] * sigma[i] / n; //square singular values to get eigenvalues 980 } 981 if (fixEigenvectorDirections) 982 { 983 foreach(size_t i; 0 .. sigma.length) 984 { 985 //these are directed so the 0th component is +ve 986 if (vt[i, 0] < 0) 987 { 988 vt[i][] *= -1; 989 u[0 .. $, i] *= -1; 990 } 991 } 992 } 993 PcaResult!T result; 994 result.scores = u; 995 result.eigenvectors = vt; 996 result.eigenvalues = eigenvalues; 997 result.mean = mean; 998 result.stdDev = stdDev; 999 return result; 1000 } 1001 } 1002 1003 @safe pure @nogc 1004 PcaResult!T pca(T, SliceKind kind) 1005 ( 1006 auto ref const Slice!(RCI!T, 2, kind) data, 1007 DeviationEstimator devEst = DeviationEstimator.sample, 1008 MeanEstimator meanEst = MeanEstimator.average, 1009 Flag!"fixEigenvectorDirections" fixEigenvectorDirections = Yes.fixEigenvectorDirections, 1010 ) 1011 do 1012 { 1013 auto d = data.lightScope.lightConst; 1014 return d.pca(devEst, meanEst, fixEigenvectorDirections); 1015 } 1016 1017 pure 1018 unittest 1019 { 1020 import mir.ndslice; 1021 import mir.math; 1022 1023 auto data = mininitRcslice!double(3, 2); 1024 data[] = [[ 1, -1], 1025 [ 0, 1], 1026 [-1, 0]]; 1027 //cov =0.5 * [[ 2, -1], 1028 // [-1, 2]] 1029 const auto const_data = data; 1030 auto mean = mininitRcslice!double(2); 1031 assert(data == centerColumns(const_data, mean)); 1032 assert(mean == [0,0]); 1033 PcaResult!double res = const_data.pca; 1034 1035 auto evs = mininitRcslice!double(2, 2); 1036 evs[] = [[1, -1], 1037 [1, 1]]; 1038 evs[] /= sqrt(2.0); 1039 assert(equal!approxEqual(res.eigenvectors, evs)); 1040 1041 auto score = mininitRcslice!double(3, 2); 1042 score[] = [[ 1.0, 0.0], 1043 [-0.5, 0.5], 1044 [-0.5, -0.5]]; 1045 score[] *= sqrt(2.0); 1046 assert(equal!approxEqual(res.scores, score)); 1047 1048 auto evals = mininitRcslice!double(2); 1049 evals[] = [1.5, 0.5]; 1050 assert(equal!approxEqual(res.eigenvalues, evals)); 1051 } 1052 1053 pure 1054 unittest 1055 { 1056 import mir.ndslice; 1057 import mir.math; 1058 1059 auto data = mininitRcslice!double(10, 3); 1060 data[] = [[7, 4, 3], 1061 [4, 1, 8], 1062 [6, 3, 5], 1063 [8, 6, 1], 1064 [8, 5, 7], 1065 [7, 2, 9], 1066 [5, 3, 3], 1067 [9, 5, 8], 1068 [7, 4, 5], 1069 [8, 2, 2]]; 1070 1071 auto m1 = 69.0/10.0; 1072 auto m2 = 35.0/10.0; 1073 auto m3 = 51.0/10.0; 1074 1075 auto centeredData = mininitRcslice!double(10, 3); 1076 centeredData[] = [[7-m1, 4-m2, 3-m3], 1077 [4-m1, 1-m2, 8-m3], 1078 [6-m1, 3-m2, 5-m3], 1079 [8-m1, 6-m2, 1-m3], 1080 [8-m1, 5-m2, 7-m3], 1081 [7-m1, 2-m2, 9-m3], 1082 [5-m1, 3-m2, 3-m3], 1083 [9-m1, 5-m2, 8-m3], 1084 [7-m1, 4-m2, 5-m3], 1085 [8-m1, 2-m2, 2-m3]]; 1086 auto mean = mininitRcslice!double(3); 1087 auto cenRes = centerColumns(data, mean); 1088 1089 assert(equal!approxEqual(centeredData, cenRes)); 1090 assert(equal!approxEqual(mean, [m1,m2,m3])); 1091 1092 auto res = data.pca; 1093 auto coeff = mininitRcslice!double(3, 3); 1094 coeff[] = [[0.6420046 , 0.6863616 , -0.3416692 ], 1095 [0.38467229, 0.09713033, 0.91792861], 1096 [0.6632174 , -0.7207450 , -0.2016662 ]]; 1097 1098 auto score = mininitRcslice!double(10, 3); 1099 score[] = [[ 0.5148128, -0.63083556, -0.03351152], 1100 [-2.6600105, 0.06280922, -0.33089322], 1101 [-0.5840389, -0.29060575, -0.15658900], 1102 [ 2.0477577, -0.90963475, -0.36627323], 1103 [ 0.8832739, 0.99120250, -0.34153847], 1104 [-1.0837642, 1.20857108, 0.44706241], 1105 [-0.7618703, -1.19712391, -0.44810271], 1106 [ 1.1828371, 1.57067601, 0.02182598], 1107 [ 0.2713493, 0.02325373, -0.17721300], 1108 [ 0.1896531, -0.82831257, 1.38523276]]; 1109 1110 auto stdDev = mininitRcslice!double(3); 1111 stdDev[] = [1.3299527, 0.9628478, 0.5514979]; 1112 1113 auto eigenvalues = mininitRcslice!double(3); 1114 eigenvalues[] = [1.768774, 0.9270759, 0.3041499]; 1115 1116 assert(equal!approxEqual(res.eigenvectors, coeff)); 1117 assert(equal!approxEqual(res.scores, score)); 1118 assert(equal!approxEqual(res.eigenvalues, eigenvalues)); 1119 } 1120 1121 pure 1122 unittest 1123 { 1124 import mir.ndslice; 1125 import mir.math; 1126 1127 auto data = mininitRcslice!double(13, 4); 1128 data[] =[[ 7, 26, 6, 60], 1129 [ 1, 29, 15, 52], 1130 [11, 56, 8, 20], 1131 [11, 31, 8, 47], 1132 [ 7, 52, 6, 33], 1133 [11, 55, 9, 22], 1134 [ 3, 71, 17, 6], 1135 [ 1, 31, 22, 44], 1136 [ 2, 54, 18, 22], 1137 [21, 47, 4, 26], 1138 [ 1, 40, 23, 34], 1139 [11, 66, 9, 12], 1140 [10, 68, 8, 12]]; 1141 1142 auto m1 = 97.0/13.0; 1143 auto m2 = 626.0/13.0; 1144 auto m3 = 153.0/13.0; 1145 auto m4 = 390.0/13.0; 1146 1147 auto centeredData = mininitRcslice!double(13, 4); 1148 centeredData[] = [[ 7-m1, 26-m2, 6-m3, 60-m4], 1149 [ 1-m1, 29-m2, 15-m3, 52-m4], 1150 [11-m1, 56-m2, 8-m3, 20-m4], 1151 [11-m1, 31-m2, 8-m3, 47-m4], 1152 [ 7-m1, 52-m2, 6-m3, 33-m4], 1153 [11-m1, 55-m2, 9-m3, 22-m4], 1154 [ 3-m1, 71-m2, 17-m3, 6-m4], 1155 [ 1-m1, 31-m2, 22-m3, 44-m4], 1156 [ 2-m1, 54-m2, 18-m3, 22-m4], 1157 [21-m1, 47-m2, 4-m3, 26-m4], 1158 [ 1-m1, 40-m2, 23-m3, 34-m4], 1159 [11-m1, 66-m2, 9-m3, 12-m4], 1160 [10-m1, 68-m2 ,8-m3, 12-m4]]; 1161 auto mean = mininitRcslice!double(4); 1162 auto cenRes = centerColumns(data, mean); 1163 1164 assert(equal!approxEqual(centeredData, cenRes)); 1165 assert(mean == [m1,m2,m3,m4]); 1166 1167 auto res = data.pca(DeviationEstimator.none); 1168 auto coeff = mininitRcslice!double(4, 4); 1169 coeff[] = [[0.067799985695474, 0.678516235418647, -0.029020832106229, -0.730873909451461], 1170 [0.646018286568728, 0.019993340484099, -0.755309622491133, 0.108480477171676], 1171 [0.567314540990512, -0.543969276583817, 0.403553469172668, -0.468397518388289], 1172 [0.506179559977705, 0.493268092159297, 0.515567418476836, 0.484416225289198]]; 1173 1174 auto score = mininitRcslice!double(13, 4); 1175 score[] = [[-36.821825999449700, 6.870878154227367, -4.590944457629745, 0.396652582713912], 1176 [-29.607273420710964, -4.610881963526308, -2.247578163663940, -0.395843536696492], 1177 [ 12.981775719737618, 4.204913183175938, 0.902243082694698, -1.126100587210615], 1178 [-23.714725720918022, 6.634052554708721, 1.854742000806314, -0.378564808384691], 1179 [ 0.553191676624597, 4.461732123178686, -6.087412652325177, 0.142384896047281], 1180 [ 10.812490833309816, 3.646571174544059, 0.912970791674604, -0.134968810314680], 1181 [ 32.588166608817929, -8.979846284936063, -1.606265913996588, 0.081763927599947], 1182 [-22.606395499005586, -10.725906457369449, 3.236537714483416, 0.324334774646368], 1183 [ 9.262587237675838, -8.985373347478788, -0.016909578102172, -0.543746175981799], 1184 [ 3.283969329640680, 14.157277337500918, 7.046512994833761, 0.340509860960606], 1185 [ -9.220031117829379, -12.386080787220454, 3.428342878284624, 0.435152769664895], 1186 [ 25.584908517429557, 2.781693148152386, -0.386716066864491, 0.446817950545605], 1187 [ 26.903161834677597, 2.930971165042989, -2.445522630195304, 0.411607156409658]]; 1188 1189 auto eigenvalues = mininitRcslice!double(4); 1190 1191 1192 eigenvalues[] = [517.7968780739053, 67.4964360487231, 12.4054300480810, 0.2371532651878]; 1193 1194 assert(equal!approxEqual(res.eigenvectors, coeff)); 1195 assert(equal!approxEqual(res.scores, score)); 1196 assert(equal!approxEqual(res.eigenvalues, eigenvalues)); 1197 } 1198 1199 ///complex extensions 1200 1201 private T conj(T)( 1202 const T z 1203 ) 1204 if (isComplex!T) 1205 { 1206 return z.re - (1fi* z.im); 1207 } 1208 1209 private template complexType(C) 1210 { 1211 static if (isComplex!C) 1212 alias complexType = Unqual!C; 1213 else static if (is(Unqual!C == double)) 1214 alias complexType = cdouble; 1215 else static if (is (Unqual!C == float)) 1216 alias complexType = cfloat; 1217 else static if (is (Unqual!C == real)) 1218 alias complexType = creal; 1219 } 1220 1221 /// 1222 enum MeanEstimator 1223 { 1224 /// 1225 none, 1226 /// 1227 average, 1228 /// 1229 median 1230 } 1231 1232 /// 1233 @safe pure nothrow @nogc 1234 T median(T)(auto ref Slice!(RCI!T) data) 1235 { 1236 auto dataScope = data.lightScope.lightConst; 1237 return median!T(dataScope); 1238 } 1239 1240 /// ditto 1241 @safe pure nothrow @nogc 1242 T median(T)(Slice!(const(T)*) data) 1243 { 1244 import mir.ndslice.sorting: sort; 1245 size_t len = cast(int) data.length; 1246 size_t n = len / 2; 1247 auto temp = data.as!T.rcslice; 1248 temp.lightScope.sort(); 1249 return len % 2 ? temp[n] : 0.5f * (temp[n - 1] + temp[n]); 1250 } 1251 1252 /// 1253 @safe pure 1254 unittest 1255 { 1256 import mir.ndslice; 1257 import mir.math; 1258 1259 auto a = mininitRcslice!double(3); 1260 a[] = [3, 1, 7]; 1261 auto med = median!double(a.flattened); 1262 assert(med == 3.0); 1263 assert(a == [3, 1, 7]);//add in stddev out param 1264 double aDev; 1265 auto aCenter = centerColumns(a, aDev, MeanEstimator.median); 1266 assert(aCenter == [0.0, -2.0, 4.0]); 1267 assert(aDev == 3.0); 1268 auto b = mininitRcslice!double(4); 1269 b[] = [4,2,5,1]; 1270 auto medB = median!double(b.flattened); 1271 assert(medB == 3.0); 1272 assert(b == [4,2,5,1]); 1273 double bDev; 1274 auto bCenter = centerColumns(b, bDev, MeanEstimator.median); 1275 assert(bCenter == [1.0, -1.0, 2.0, -2.0]); 1276 assert(bDev == 3.0); 1277 } 1278 1279 /++ 1280 Mean Centring of raw data. 1281 Params: 1282 matrix = input `M x N` matrix 1283 mean = column means 1284 est = mean estimation method 1285 Returns: 1286 `M x N` matrix with each column translated by the column mean 1287 +/ 1288 @safe pure nothrow @nogc 1289 Slice!(RCI!T,2) centerColumns(T, SliceKind kind) 1290 ( 1291 Slice!(const(T)*, 2, kind) matrix, 1292 out Slice!(RCI!T) mean, 1293 MeanEstimator est = MeanEstimator.average, 1294 ) 1295 { 1296 mean = rcslice!T([matrix.length!1], cast(T)0); 1297 if (est == MeanEstimator.none) 1298 { 1299 return matrix.as!T.rcslice; 1300 } 1301 auto at = matrix.transposed.as!T.rcslice; 1302 auto len = at.length!1; 1303 foreach (i; 0 .. at.length!0) 1304 { 1305 if (est == MeanEstimator.average) 1306 { 1307 foreach(j; 0 .. at.length!1) 1308 mean[i] += (at[i][j]/len); 1309 } 1310 else // (est == MeanEstimator.median) 1311 { 1312 mean[i] = median(at[i].flattened); 1313 } 1314 at[i][] -= mean[i]; 1315 } 1316 auto atSliced = at.transposed.as!T.rcslice; 1317 return atSliced; 1318 } 1319 1320 /// ditto 1321 @safe pure nothrow @nogc 1322 Slice!(RCI!T) centerColumns(T) 1323 ( 1324 Slice!(const(T)*) col, 1325 out T mean, 1326 MeanEstimator est = MeanEstimator.average, 1327 ) 1328 { 1329 mean = cast(T)0; 1330 if (est == MeanEstimator.none) 1331 { 1332 return col.as!T.rcslice; 1333 } 1334 auto len = col.length; 1335 if (est == MeanEstimator.average) 1336 { 1337 foreach(j; 0 .. len) 1338 mean += (col[j]/len); 1339 } 1340 else // (est == MeanEstimator.median) 1341 { 1342 mean = median(col); 1343 } 1344 auto result = mininitRcslice!T(len); 1345 foreach (j; 0 .. len) 1346 result[j] = col[j] - mean; 1347 return result; 1348 } 1349 1350 /// ditto 1351 @safe pure nothrow @nogc 1352 Slice!(RCI!T) centerColumns(T) 1353 ( 1354 auto ref const Slice!(RCI!T) col, 1355 out T mean, 1356 MeanEstimator est = MeanEstimator.average, 1357 ) 1358 { 1359 auto colScope = col.lightScope; 1360 return centerColumns!(T)(colScope, mean, est); 1361 } 1362 1363 /// ditto 1364 @safe pure nothrow @nogc 1365 Slice!(RCI!T,2) centerColumns(T, SliceKind kind) 1366 ( 1367 auto ref const Slice!(RCI!T,2,kind) matrix, 1368 out Slice!(RCI!T) mean, 1369 MeanEstimator est = MeanEstimator.average, 1370 ) 1371 { 1372 auto matrixScope = matrix.lightScope; 1373 return centerColumns(matrixScope, mean, est); 1374 } 1375 1376 /// 1377 @safe pure nothrow 1378 unittest 1379 { 1380 import mir.ndslice; 1381 import mir.math; 1382 1383 auto data = mininitRcslice!double(2,1); 1384 data[] = [[1], 1385 [3]]; 1386 auto mean = mininitRcslice!double(1); 1387 auto res = centerColumns(data, mean, MeanEstimator.average); 1388 assert(mean[0] == 2); 1389 assert(res == [[-1],[1]]); 1390 } 1391 1392 /// 1393 enum DeviationEstimator 1394 { 1395 /// 1396 none, 1397 /// 1398 sample, 1399 /// median absolute deviation 1400 mad 1401 } 1402 1403 /++ 1404 Normalization of raw data. 1405 Params: 1406 matrix = input `M x N` matrix, each row an observation and each column mean centred 1407 stdDev = column standard deviation 1408 devEst = estimation method 1409 Returns: 1410 `M x N` matrix with each column divided by it's standard deviation 1411 +/ 1412 @safe pure nothrow @nogc Slice!(RCI!T,2) normalizeColumns(T, SliceKind kind)( 1413 auto ref const Slice!(const(T)*,2,kind) matrix, 1414 out Slice!(RCI!T) stdDev, 1415 DeviationEstimator devEst = DeviationEstimator.sample 1416 ) 1417 { 1418 stdDev = rcslice!T([matrix.length!1], cast(T)0); 1419 if (devEst == DeviationEstimator.none) 1420 { 1421 auto matrixSliced = matrix.as!T.rcslice; 1422 return matrixSliced; 1423 } 1424 else 1425 { 1426 import mir.math.sum: sum; 1427 auto mTSliced = matrix.transposed.as!T.rcslice; 1428 auto mT = mTSliced.lightScope.canonical; 1429 foreach (i; 0 .. mT.length!0) 1430 { 1431 auto processedRow = mininitRcslice!T(mT.length!1); 1432 if (devEst == DeviationEstimator.sample) 1433 { 1434 foreach (j; 0 .. mT.length!1) 1435 processedRow[j] = mT[i, j] * mT[i, j]; 1436 stdDev[i] = sqrt(processedRow.sum!"kb2" / (mT[i].length - 1)); 1437 } 1438 else if (devEst == DeviationEstimator.mad) 1439 { 1440 foreach (j; 0 .. mT.length!1) 1441 processedRow[j] = fabs(mT[i,j]); 1442 stdDev[i] = median!T(processedRow); 1443 } 1444 mT[i][] /= stdDev[i]; 1445 } 1446 auto mSliced = mT.transposed.rcslice; 1447 return mSliced; 1448 } 1449 } 1450 1451 /// ditto 1452 @safe pure nothrow @nogc Slice!(RCI!T,2) normalizeColumns(T, SliceKind kind)( 1453 auto ref const Slice!(RCI!T,2,kind) matrix, 1454 out Slice!(RCI!T) stdDev, 1455 DeviationEstimator devEst = DeviationEstimator.sample, 1456 ) 1457 { 1458 auto matrixScope = matrix.lightScope.lightConst; 1459 return normalizeColumns!(T, kind)(matrixScope, stdDev, devEst); 1460 } 1461 1462 /// 1463 @safe pure nothrow unittest 1464 { 1465 import mir.ndslice; 1466 import mir.math; 1467 1468 auto data = mininitRcslice!double(2,2); 1469 data[] = [[ 2, -1], 1470 [-2, 1]]; 1471 //sd1 = 2 * sqrt(2.0); 1472 //sd2 = sqrt(2.0); 1473 auto x = 1.0 / sqrt(2.0); 1474 auto scaled = mininitRcslice!double(2,2); 1475 scaled[] = [[ x, -x], 1476 [-x, x]]; 1477 auto stdDev = mininitRcslice!double(2); 1478 assert(normalizeColumns(data, stdDev) == scaled); 1479 assert(stdDev == [2*sqrt(2.0), sqrt(2.0)]); 1480 }