├── .circleci └── config.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── dub.sdl ├── example ├── dub.sdl └── source │ └── app.d ├── index.d ├── meson.build ├── source └── kaleidic │ ├── lubeck.d │ └── lubeck2.d └── subprojects ├── cblas-d.wrap ├── lapack-d.wrap ├── mir-algorithm.wrap ├── mir-blas.wrap ├── mir-core.wrap ├── mir-lapack.wrap ├── mir-linux-kernel.wrap └── mir-random.wrap /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | mirci: libmir/upload_docs@0.1.6 5 | 6 | workflows: 7 | version: 2 8 | build-deploy: 9 | jobs: 10 | - mirci/test_and_build_docs: 11 | company: kaleidicassociates 12 | filters: 13 | tags: 14 | only: /^v(\d)+(\.(\d)+)+$/ 15 | - mirci/upload_docs: 16 | to: lubeck.libmir.org 17 | requires: 18 | - mirci/test_and_build_docs 19 | filters: 20 | branches: 21 | ignore: /.*/ 22 | tags: 23 | only: /^v(\d)+(\.(\d)+)+$/ 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .dub 2 | docs.json 3 | __dummy.html 4 | *.o 5 | *.obj 6 | __test__*__ 7 | 8 | *.sublime-project 9 | 10 | example/dub.selections.json 11 | 12 | example/example 13 | 14 | dub.selections.json 15 | 16 | *.a 17 | 18 | lubeck-test-library 19 | 20 | *.lst 21 | .vscode 22 | *.directory 23 | *.sublime-workspace 24 | lubeck-test-unittest 25 | .DS_Store 26 | *.lib 27 | *.dll 28 | *.exe 29 | build 30 | builddir 31 | subprojects/*/ 32 | 33 | *.pdb 34 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: focal 2 | sudo: false 3 | 4 | language: d 5 | 6 | d: 7 | - dmd 8 | - ldc 9 | 10 | script: 11 | - dub test -c unittest-blas 12 | - dub run -c blas --root example 13 | 14 | addons: 15 | apt: 16 | packages: 17 | - liblapack-dev libblas-dev 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: 2 | 3 | The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. 4 | 5 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![Gitter](https://img.shields.io/gitter/room/libmir/public.svg)](https://gitter.im/libmir/public) 3 | [![Build Status](https://www.travis-ci.org/kaleidicassociates/lubeck.svg?branch=master)](https://www.travis-ci.org/kaleidicassociates/lubeck) 4 | [![Dub downloads](https://img.shields.io/dub/dt/lubeck.svg)](http://code.dlang.org/packages/lubeck) 5 | [![Dub downloads](https://img.shields.io/dub/dm/lubeck.svg)](http://code.dlang.org/packages/lubeck) 6 | [![License](https://img.shields.io/dub/l/lubeck.svg)](http://code.dlang.org/packages/lubeck) 7 | [![Latest version](https://img.shields.io/dub/v/lubeck.svg)](http://code.dlang.org/packages/lubeck) 8 | 9 | # Lubeck 10 | High level linear algebra library for Dlang 11 | 12 | ## Required system libraries 13 | 14 | See [wiki: Link with CBLAS & LAPACK](https://github.com/libmir/mir-lapack/wiki/Link-with-CBLAS-&-LAPACK). 15 | 16 | ## API 17 | - `mtimes` - General matrix-matrix, row-matrix, matrix-column, and row-column multiplications. 18 | - `mldivide` - Solve systems of linear equations AX = B for X. Computes minimum-norm solution to a linear least squares problem 19 | if A is not a square matrix. 20 | - `inv` - Inverse of matrix. 21 | - `svd` - Singular value decomposition. 22 | - `pca` - Principal component analysis of raw data. 23 | - `pinv` - Moore-Penrose pseudoinverse of matrix. 24 | - `det`/`detSymmetric` - General/symmetric matrix determinant. 25 | - `eigSymmetric` - Eigenvalues and eigenvectors of symmetric matrix. 26 | - Qr decomposition: `qrDecomp` with `solve` method 27 | - Cholesky: `choleskyDecomp` with `solve` method 28 | - LU decomposition: `luDecomp` with `solve` method 29 | - LDL decomposition: `ldlDecomp` with `solve` method 30 | 31 | ## Example 32 | 33 | ```d 34 | /+dub.sdl: 35 | dependency "lubeck" version="~>0.1" 36 | libs "lapack" "blas" 37 | +/ 38 | // or libs "openblas" 39 | import std.stdio; 40 | import mir.ndslice: magic, repeat, as, slice; 41 | import kaleidic.lubeck: mtimes; 42 | 43 | void main() 44 | { 45 | auto n = 5; 46 | // Magic Square 47 | auto matrix = n.magic.as!double.slice; 48 | // [1 1 1 1 1] 49 | auto vec = 1.repeat(n).as!double.slice; 50 | // Uses CBLAS for multiplication 51 | matrix.mtimes(vec).writeln; 52 | matrix.mtimes(matrix).writeln; 53 | } 54 | ``` 55 | 56 | [![Open on run.dlang.io](https://img.shields.io/badge/run.dlang.io-open-blue.svg)](https://run.dlang.io/is/RQRMoo) 57 | 58 | ### Related packages 59 | - [mir-algorithm](https://github.com/libmir/mir-algorithm) 60 | - [mir-lapack](https://github.com/libmir/mir-lapack) 61 | - [mir-blas](https://github.com/libmir/mir-blas) 62 | - [lapack](https://github.com/libmir/lapack) 63 | - [cblas](https://github.com/DlangScience/cblas) 64 | 65 | --------------- 66 | 67 | This work has been sponsored by [Symmetry Investments](http://symmetryinvestments.com) and [Kaleidic Associates](https://github.com/kaleidicassociates). 68 | 69 | 70 | About Kaleidic Associates 71 | ------------------------- 72 | We are a boutique consultancy that advises a small number of hedge fund clients. We are 73 | not accepting new clients currently, but if you are interested in working either remotely 74 | or locally in London or Hong Kong, and if you are a talented hacker with a moral compass 75 | who aspires to excellence then feel free to drop me a line: laeeth at kaleidic.io 76 | 77 | We work with our partner Symmetry Investments, and some background on the firm can be 78 | found here: 79 | 80 | http://symmetryinvestments.com/about-us/ 81 | -------------------------------------------------------------------------------- /dub.sdl: -------------------------------------------------------------------------------- 1 | name "lubeck" 2 | description "High level linear algebra library for Dlang" 3 | authors "Ilya Yaroshenko" "Thomas Webster" "Lars Tandle Kyllingstad (SciD author)" 4 | copyright "Copyright © 2017-2020, Symmetry Investments & Kaleidic Associates; Copyright (c) 2009, Lars T. Kyllingstad (SciD)" 5 | license "BSL-1.0" 6 | 7 | dependency "mir-lapack" version="~>1.2.9" 8 | 9 | configuration "library" { 10 | targetType "library" 11 | # default or user specified mir-lapack sub-configuration is used 12 | } 13 | 14 | configuration "unittest" { 15 | dependency "mir-random" version=">=2.2.5" 16 | } 17 | 18 | configuration "openblas" { 19 | subConfiguration "mir-lapack" "openblas" 20 | } 21 | 22 | configuration "threelib" { 23 | subConfiguration "mir-lapack" "threelib" 24 | } 25 | 26 | configuration "cblas" { 27 | subConfiguration "mir-lapack" "cblas" 28 | } 29 | 30 | configuration "blas" { 31 | subConfiguration "mir-lapack" "blas" 32 | } 33 | 34 | configuration "lapack" { 35 | subConfiguration "mir-lapack" "lapack" 36 | } 37 | 38 | configuration "mkl-sequential" { 39 | subConfiguration "mir-lapack" "mkl-sequential" 40 | } 41 | 42 | configuration "mkl-sequential-ilp" { 43 | subConfiguration "mir-lapack" "mkl-sequential-ilp" 44 | } 45 | 46 | configuration "mkl-tbb-thread" { 47 | subConfiguration "mir-lapack" "mkl-tbb-thread" 48 | } 49 | 50 | configuration "mkl-tbb-thread-ilp" { 51 | subConfiguration "mir-lapack" "mkl-tbb-thread-ilp" 52 | } 53 | 54 | configuration "mkl-sequential-dll" { 55 | subConfiguration "mir-lapack" "mkl-sequential-dll" 56 | } 57 | 58 | configuration "mkl-sequential-ilp-dll" { 59 | subConfiguration "mir-lapack" "mkl-sequential-ilp-dll" 60 | } 61 | 62 | configuration "mkl-tbb-thread-dll" { 63 | subConfiguration "mir-lapack" "mkl-tbb-thread-dll" 64 | } 65 | 66 | configuration "mkl-tbb-thread-ilp-dll" { 67 | subConfiguration "mir-lapack" "mkl-tbb-thread-ilp-dll" 68 | } 69 | 70 | configuration "zerolib" { 71 | subConfiguration "mir-lapack" "zerolib" 72 | } 73 | 74 | configuration "unittest-openblas" { 75 | dependency "mir-random" version=">=2.2.5" 76 | subConfiguration "mir-lapack" "openblas" 77 | } 78 | 79 | configuration "unittest-threelib" { 80 | dependency "mir-random" version=">=2.2.5" 81 | subConfiguration "mir-lapack" "threelib" 82 | } 83 | 84 | configuration "unittest-cblas" { 85 | dependency "mir-random" version=">=2.2.5" 86 | subConfiguration "mir-lapack" "cblas" 87 | } 88 | 89 | configuration "unittest-blas" { 90 | dependency "mir-random" version=">=2.2.5" 91 | subConfiguration "mir-lapack" "blas" 92 | } 93 | 94 | configuration "unittest-lapack" { 95 | dependency "mir-random" version=">=2.2.5" 96 | subConfiguration "mir-lapack" "lapack" 97 | } 98 | 99 | configuration "unittest-mkl-sequential" { 100 | dependency "mir-random" version=">=2.2.5" 101 | subConfiguration "mir-lapack" "mkl-sequential" 102 | } 103 | 104 | buildType "unittest-cov" { 105 | buildOptions "unittests" "coverage" "debugMode" "debugInfo" 106 | dependency "mir-random" version=">=2.2.5" 107 | } -------------------------------------------------------------------------------- /example/dub.sdl: -------------------------------------------------------------------------------- 1 | name "example" 2 | authors "Ilya Yaroshenko" 3 | dependency "lubeck" path="../" 4 | description "Lubeck example" 5 | license "BSL-1.0" 6 | targetType "executable" 7 | 8 | configuration "executable" { 9 | targetType "executable" 10 | # default or user specified mir-lapack sub-configuration is used 11 | } 12 | 13 | configuration "openblas" { 14 | targetType "executable" 15 | subConfiguration "lubeck" "openblas" 16 | } 17 | 18 | configuration "blas" { 19 | targetType "executable" 20 | subConfiguration "lubeck" "blas" 21 | } 22 | -------------------------------------------------------------------------------- /example/source/app.d: -------------------------------------------------------------------------------- 1 | import mir.ndslice.slice: sliced; 2 | import kaleidic.lubeck: mldivide; 3 | 4 | // Solves AX=B 5 | int main() 6 | { 7 | auto a = [ 8 | 1, -1, 1, 9 | 2, 2, -4, 10 | -1, 5, 0].sliced(3, 3); 11 | auto b = [ 12 | 2.0, 0, 13 | -6 , -6, 14 | 9 , 1].sliced(3, 2); 15 | auto t = [ 16 | 1.0, -1, 17 | 2 , 0, 18 | 3 , 1].sliced(3, 2); 19 | 20 | auto x = a.mldivide(b); 21 | 22 | // Check result 23 | if(x != t) 24 | return 1; 25 | 26 | // OK 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /index.d: -------------------------------------------------------------------------------- 1 | Ddoc 2 | 3 | $(H1 High level linear algebra library for Dlang) 4 | 5 | $(P The following table is a quick reference guide for which Lubeck modules to 6 | use for a given category of functionality.) 7 | 8 | $(BOOKTABLE , 9 | $(TR 10 | $(TH Modules) 11 | $(TH Description) 12 | ) 13 | $(TR 14 | $(TDNW $(MREF kaleidic, lubeck)) 15 | $(TD First API that uses garbage collection for memory managment.) 16 | ) 17 | $(TR 18 | $(TDNW $(MREF kaleidic, lubeck2)) 19 | $(TD Second implementation that uses reference counted matrices and vectors.) 20 | ) 21 | ) 22 | 23 | $(BR) 24 | $(BR) 25 | 26 | $(MREF kaleidic, lubeck) and $(MREF kaleidic, lubeck2) doesn't provide the same functionality or API, although many things are similar. 27 | 28 | Macros: 29 | TITLE=Lubeck 30 | WIKI=Lubeck 31 | DDOC_BLANKLINE= 32 | _= 33 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('lubeck', 'd', version: '1.0.0', license: 'BSL-1.0') 2 | 3 | description = 'Lubeck - High level linear algebra library for Dlang' 4 | 5 | subprojects = [ 6 | 'cblas-d', 7 | 'lapack-d', 8 | 'mir-algorithm', 9 | 'mir-blas', 10 | 'mir-core', 11 | 'mir-lapack', 12 | 'mir-random', 13 | ] 14 | 15 | if target_machine.system() == 'linux' 16 | subprojects += 'mir-linux-kernel' 17 | endif 18 | 19 | has_cpp_headers = false 20 | 21 | sources_list = [ 22 | 'kaleidic/lubeck', 23 | 'kaleidic/lubeck2', 24 | ] 25 | 26 | sources = [] 27 | foreach s : sources_list 28 | sources += 'source/' + s + '.d' 29 | endforeach 30 | 31 | add_project_arguments([ 32 | '-preview=dip1008', 33 | '-lowmem', 34 | ], language: 'd') 35 | 36 | required_deps = [] 37 | 38 | foreach p : subprojects 39 | required_deps += dependency(p, fallback : [p, 'this_dep']) 40 | endforeach 41 | 42 | directories = ['source'] 43 | 44 | if has_cpp_headers 45 | directories += 'include' 46 | endif 47 | 48 | directories = include_directories(directories) 49 | 50 | this_lib = library(meson.project_name(), 51 | sources, 52 | include_directories: directories, 53 | install: true, 54 | version: meson.project_version(), 55 | dependencies: required_deps, 56 | ) 57 | 58 | this_dep = declare_dependency( 59 | link_with: [this_lib], 60 | include_directories: directories, 61 | dependencies: required_deps, 62 | ) 63 | 64 | test_versions = ['lubeck_test'] 65 | 66 | if has_cpp_headers 67 | install_subdir('include/', 68 | strip_directory :true, 69 | install_dir: 'include/', 70 | ) 71 | endif 72 | 73 | install_subdir('source/', 74 | strip_directory : true, 75 | install_dir: 'include/d/' + meson.project_name(), 76 | ) 77 | 78 | import('pkgconfig').generate(this_lib, 79 | description: description, 80 | subdirs: 'd/' + meson.project_name(), 81 | ) 82 | 83 | lubeck_dep = this_dep 84 | lubeck_lib = this_lib 85 | 86 | test_subdirs = [] 87 | -------------------------------------------------------------------------------- /source/kaleidic/lubeck.d: -------------------------------------------------------------------------------- 1 | /++ 2 | $(H1 Lubeck - Linear Algebra) 3 | 4 | Authors: Ilya Yaroshenko, Lars Tandle Kyllingstad (SciD author) 5 | +/ 6 | module kaleidic.lubeck; 7 | 8 | import cblas : Diag; 9 | import mir.blas; 10 | import mir.internal.utility : realType, isComplex; 11 | import mir.lapack; 12 | import mir.math.common; 13 | import mir.ndslice.allocation; 14 | import mir.ndslice.dynamic: transposed; 15 | import mir.ndslice.slice; 16 | import mir.ndslice.topology; 17 | import mir.ndslice.traits : isMatrix; 18 | import mir.utility; 19 | import std.meta; 20 | import std.traits; 21 | import std.typecons: Flag, Yes, No; 22 | import mir.complex: Complex; 23 | public import mir.lapack: lapackint; 24 | 25 | template CommonType(A) 26 | { 27 | alias CommonType = A; 28 | } 29 | 30 | template CommonType(A, B) 31 | { 32 | static if (isComplex!A || isComplex!B) 33 | alias CommonType = Complex!(CommonType!(realType!A, realType!B)); 34 | else 35 | alias CommonType = typeof(A.init + B.init); 36 | } 37 | 38 | version(LDC) 39 | import ldc.attributes: fastmath; 40 | else 41 | enum { fastmath }; 42 | 43 | private template IterationType(Iterator) 44 | { 45 | alias T = Unqual!(typeof(Iterator.init[0])); 46 | static if (isIntegral!T || is(T == real)) 47 | alias IterationType = double; 48 | else 49 | static if (is(T == Complex!real)) 50 | alias IterationType = Complex!double; 51 | else 52 | { 53 | static assert( 54 | is(T == double) || 55 | is(T == float) || 56 | is(T == Complex!double) || 57 | is(T == Complex!float)); 58 | alias IterationType = T; 59 | } 60 | } 61 | 62 | /++ 63 | Gets the type that can be used with Blas routines that all types can be implicitly converted to. 64 | 65 | +/ 66 | alias BlasType(Iterators...) = 67 | CommonType!(staticMap!(IterationType, Iterators)); 68 | 69 | /++ 70 | General matrix-matrix multiplication. Allocates result to an uninitialized slice using GC. 71 | Params: 72 | a = m(rows) x k(cols) matrix 73 | b = k(rows) x n(cols) matrix 74 | Result: 75 | m(rows) x n(cols) 76 | +/ 77 | Slice!(BlasType!(IteratorA, IteratorB)*, 2) 78 | mtimes(IteratorA, SliceKind kindA, IteratorB, SliceKind kindB)( 79 | Slice!(IteratorA, 2, kindA) a, 80 | Slice!(IteratorB, 2, kindB) b) 81 | { 82 | assert(a.length!1 == b.length!0); 83 | 84 | // reallocate data if required 85 | alias A = BlasType!IteratorA; 86 | alias B = BlasType!IteratorB; 87 | alias C = CommonType!(A, B); 88 | static if (!is(Unqual!IteratorA == C*)) 89 | return .mtimes(a.as!C.slice, b); 90 | else 91 | static if (!is(Unqual!IteratorB == C*)) 92 | return .mtimes(a, b.as!C.slice); 93 | else 94 | { 95 | static if (kindA != Contiguous) 96 | if (a._stride!0 != 1 && a._stride!1 != 1 97 | || a._stride!0 <= 0 98 | || a._stride!1 <= 0) 99 | return .mtimes(a.slice, b); 100 | 101 | static if (kindB != Contiguous) 102 | if (b._stride!0 != 1 && b._stride!1 != 1 103 | || b._stride!0 <= 0 104 | || b._stride!1 <= 0) 105 | return .mtimes(a, b.slice); 106 | 107 | auto c = uninitSlice!C(a.length!0, b.length!1); 108 | 109 | if (a.length!1 == 1 && b.length!0 == 1) 110 | { 111 | c[] = cast(C) 0; 112 | ger(cast(C)1, a.front!1, b.front, c); 113 | } 114 | else 115 | { 116 | gemm(cast(C)1, a, b, cast(C)0, c); 117 | } 118 | return c; 119 | } 120 | } 121 | 122 | /// 123 | unittest 124 | { 125 | import mir.ndslice; 126 | 127 | auto a = 128 | [-5, 1, 7, 7, -4, 129 | -1, -5, 6, 3, -3, 130 | -5, -2, -3, 6, 0].sliced(3, 5); 131 | 132 | auto b = slice!double(5, 4); 133 | b[] = 134 | [[-5, -3, 3, 1], 135 | [ 4, 3, 6, 4], 136 | [-4, -2, -2, 2], 137 | [-1, 9, 4, 8], 138 | [ 9, 8, 3, -2]]; 139 | 140 | assert(mtimes(a, b) == 141 | [[-42, 35, -7, 77], 142 | [-69, -21, -42, 21], 143 | [ 23, 69, 3, 29]] 144 | ); 145 | } 146 | 147 | /// ger specialized case in mtimes 148 | unittest 149 | { 150 | import mir.ndslice; 151 | 152 | // from https://github.com/kaleidicassociates/lubeck/issues/8 153 | { 154 | auto a = [1.0f, 2.0f].sliced(2, 1); 155 | auto b = [1.0f, 2.0f].sliced(2, 1); 156 | assert(mtimes(a, b.transposed) == [[1, 2], [2, 4]]); 157 | } 158 | { 159 | auto a = [1.0, 2.0].sliced(1, 2); 160 | auto b = [1.0, 2.0].sliced(1, 2); 161 | assert(mtimes(a.transposed, b) == [[1, 2], [2, 4]]); 162 | } 163 | } 164 | 165 | /// 166 | unittest 167 | { 168 | import mir.ndslice; 169 | 170 | // from https://github.com/kaleidicassociates/lubeck/issues/3 171 | Slice!(float*, 2) a = slice!float(1, 1); 172 | Slice!(float*, 2, Universal) b1 = slice!float(16, 1).transposed; 173 | Slice!(float*, 2) b2 = slice!float(1, 16); 174 | 175 | a[] = 3; 176 | b1[] = 4; 177 | b2[] = 4; 178 | 179 | // Confirm that this message does not appear 180 | // Outputs: ** On entry to SGEMM parameter number 8 had an illegal value 181 | assert(a.mtimes(b1) == a.mtimes(b2)); 182 | } 183 | 184 | /++ 185 | General matrix-matrix multiplication. Allocates result to an uninitialized slice using GC. 186 | Params: 187 | a = m(rows) x k(cols) matrix 188 | b = k(rows) x 1(cols) vector 189 | Result: 190 | m(rows) x 1(cols) 191 | +/ 192 | Slice!(BlasType!(IteratorA, IteratorB)*) 193 | mtimes(IteratorA, SliceKind kindA, IteratorB, SliceKind kindB)( 194 | Slice!(IteratorA, 2, kindA) a, 195 | Slice!(IteratorB, 1, kindB) b) 196 | { 197 | assert(a.length!1 == b.length!0); 198 | 199 | // reallocate data if required 200 | alias A = BlasType!IteratorA; 201 | alias B = BlasType!IteratorB; 202 | alias C = CommonType!(A, B); 203 | static if (!is(Unqual!IteratorA == C*)) 204 | return .mtimes(a.as!C.slice, b); 205 | else 206 | static if (!is(Unqual!IteratorB == C*)) 207 | return .mtimes(a, b.as!C.slice); 208 | else 209 | { 210 | static if (kindA != Contiguous) 211 | if (a._stride!0 != 1 && a._stride!1 != 1 212 | || a._stride!0 <= 0 213 | || a._stride!1 <= 0) 214 | return .mtimes(a.slice, b); 215 | 216 | static if (kindB != Contiguous) 217 | if (b._stride!1 <= 0) 218 | return .mtimes(a, b.slice); 219 | 220 | auto c = uninitSlice!C(a.length!0); 221 | gemv(cast(C)1, a, b, cast(C)0, c); 222 | return c; 223 | } 224 | } 225 | 226 | /++ 227 | General matrix-matrix multiplication. 228 | Params: 229 | a = 1(rows) x k(cols) vector 230 | b = k(rows) x n(cols) matrix 231 | Result: 232 | 1(rows) x n(cols) 233 | +/ 234 | Slice!(BlasType!(IteratorA, IteratorB)*) 235 | mtimes(IteratorA, SliceKind kindA, IteratorB, SliceKind kindB)( 236 | Slice!(IteratorB, 1, kindB) a, 237 | Slice!(IteratorA, 2, kindA) b, 238 | ) 239 | { 240 | return .mtimes(b.universal.transposed, a); 241 | } 242 | 243 | /// 244 | unittest 245 | { 246 | import mir.ndslice; 247 | 248 | auto a = 249 | [-5, 1, 7, 7, -4, 250 | -1, -5, 6, 3, -3, 251 | -5, -2, -3, 6, 0] 252 | .sliced(3, 5) 253 | .universal 254 | .transposed; 255 | 256 | auto b = slice!double(5); 257 | b[] = [-5, 4,-4,-1, 9]; 258 | 259 | assert(mtimes(b, a) == [-42, -69, 23]); 260 | } 261 | 262 | /++ 263 | Vector-vector multiplication (dot product). 264 | Params: 265 | a = 1(rows) x k(cols) vector 266 | b = k(rows) x 1(cols) matrix 267 | Result: 268 | scalar 269 | +/ 270 | CommonType!(BlasType!IteratorA, BlasType!IteratorB) 271 | mtimes(IteratorA, SliceKind kindA, IteratorB, SliceKind kindB)( 272 | Slice!(IteratorB, 1, kindB) a, 273 | Slice!(IteratorA, 1, kindA) b, 274 | ) 275 | { 276 | alias A = BlasType!IteratorA; 277 | alias B = BlasType!IteratorB; 278 | alias C = CommonType!(A, B); 279 | static if (is(IteratorB == C*) && is(IteratorA == C*)) 280 | { 281 | return dot(a, b); 282 | } 283 | else 284 | { 285 | auto c = cast(typeof(return)) 0; 286 | import mir.algorithm.iteration: reduce; 287 | return c.reduce!"a + b * c"(a.as!(typeof(return)), b.as!(typeof(return))); 288 | } 289 | } 290 | 291 | /// 292 | unittest 293 | { 294 | import mir.ndslice; 295 | 296 | auto a = [1, 2, 4].sliced; 297 | auto b = [3, 4, 2].sliced; 298 | assert(a.mtimes(b) == 19); 299 | } 300 | 301 | /++ 302 | Calculates the inverse of a matrix. 303 | +/ 304 | auto inv(Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) a) 305 | in 306 | { 307 | assert (a.length!0 == a.length!1, "matrix must be square"); 308 | } 309 | do 310 | { 311 | alias T = BlasType!Iterator; 312 | 313 | auto m = a.as!T.slice.canonical; 314 | auto ipiv = m.length.uninitSlice!lapackint; 315 | 316 | auto info = getrf!T(m, ipiv); 317 | if (info == 0) 318 | { 319 | info = getri(m, ipiv, m.getri_wq.uninitSlice!T); 320 | } 321 | 322 | import mir.exception: enforce; 323 | enforce!"inv: matrix is singular"(info == 0); 324 | return m; 325 | } 326 | 327 | /// 328 | unittest 329 | { 330 | import mir.complex; 331 | import mir.ndslice; 332 | 333 | auto a = [ 334 | 1, 0, 2, 335 | 2, 2, 0, 336 | 0, 1, 1] 337 | .sliced(3, 3); 338 | 339 | enum : double { _13 = 1.0/3.0, _16 = 1.0/6.0, _23 = 2.0/3.0 } 340 | auto ans = [ 341 | _13, _13, -_23, 342 | -_13,_16, _23, 343 | _13, -_16, _13] 344 | .sliced(a.shape); 345 | 346 | import mir.algorithm.iteration: equal; 347 | import mir.math.common: approxEqual; 348 | assert(equal!((a, b) => a.approxEqual(b, 1e-10L, 1e-10L))(a.inv, ans)); 349 | assert(equal!((a, b) => a.approxEqual(b, 1e-10L, 1e-10L))(a.map!(a => Complex!double(a, 0)).inv.member!"re", ans)); 350 | } 351 | 352 | /// 353 | unittest 354 | { 355 | import mir.ndslice.topology: iota; 356 | 357 | try 358 | { 359 | auto m = [3, 3].iota.inv; 360 | assert (false, "Matrix should be detected as singular"); 361 | } 362 | catch (Exception e) 363 | { 364 | assert (true); 365 | } 366 | } 367 | 368 | /// 369 | struct SvdResult(T) 370 | { 371 | /// 372 | Slice!(T*, 2) u; 373 | /// 374 | Slice!(realType!T*) sigma; 375 | /// 376 | Slice!(T*, 2) vt; 377 | } 378 | 379 | /++ 380 | Computes the singular value decomposition. 381 | 382 | Params: 383 | matrix = input `M x N` matrix 384 | slim = If true the first `min(M,N)` columns of `u` and the first 385 | `min(M,N)` rows of `vt` are returned in the ndslices `u` and `vt`. 386 | Returns: $(LREF SvdResult). Results are allocated by the GC. 387 | +/ 388 | auto svd( 389 | Flag!"allowDestroy" allowDestroy = No.allowDestroy, 390 | string algorithm = "gesvd", 391 | SliceKind kind, 392 | Iterator 393 | )( 394 | Slice!(Iterator, 2, kind) matrix, 395 | Flag!"slim" slim = No.slim, 396 | ) 397 | if (algorithm == "gesvd" || algorithm == "gesdd") 398 | { 399 | import lapack; 400 | alias T = BlasType!Iterator; 401 | static if (allowDestroy && kind != Universal && is(Iterstor == T*)) 402 | alias a = matrix.canonical; 403 | else 404 | auto a = matrix.as!T.slice.canonical; 405 | 406 | auto m = cast(lapackint)a.length!1; 407 | auto n = cast(lapackint)a.length!0; 408 | 409 | auto s = uninitSlice!(realType!T)(min(m, n)); 410 | auto u = uninitSlice!T(slim ? s.length : m, m); 411 | auto vt = uninitSlice!T(n, slim ? s.length : n); 412 | 413 | if (m == 0 || n == 0) 414 | { 415 | u[] = 0; 416 | u.diagonal[] = 1; 417 | vt[] = 0; 418 | vt.diagonal[] = 1; 419 | } 420 | else 421 | { 422 | static if (algorithm == "gesvd") 423 | { 424 | auto jobu = slim ? 'S' : 'A'; 425 | auto jobvt = slim ? 'S' : 'A'; 426 | auto work = gesvd_wq(jobu, jobvt, a, u.canonical, vt.canonical).uninitSlice!T; 427 | 428 | static if(isComplex!T) { 429 | auto rwork = uninitSlice!(realType!T)(max(1, 5 * min(m, n))); 430 | auto info = gesvd!T(jobu, jobvt, a, s, u.canonical, vt.canonical, work, rwork); 431 | } else { 432 | auto info = gesvd(jobu, jobvt, a, s, u.canonical, vt.canonical, work); 433 | } 434 | 435 | enum msg = "svd: DBDSQR did not converge"; 436 | } 437 | else // gesdd 438 | { 439 | auto iwork = uninitSlice!lapackint(s.length * 8); 440 | auto jobz = slim ? 'S' : 'A'; 441 | auto work = gesdd_wq(jobz, a, u.canonical, vt.canonical).uninitSlice!T; 442 | 443 | static if(isComplex!T) { 444 | auto mx = max(m, n); 445 | auto mn = min(m, n); 446 | auto rwork = uninitSlice!(realType!T)(max(5*mn^^2 + 5*mn, 2*mx*mn + 2*mn^^2 + mn)); 447 | auto info = gesdd!T(jobz, a, s, u.canonical, vt.canonical, work, rwork, iwork); 448 | } else { 449 | auto info = gesdd(jobz, a, s, u.canonical, vt.canonical, work, iwork); 450 | } 451 | 452 | enum msg = "svd: DBDSDC did not converge, updating process failed"; 453 | } 454 | import mir.exception: enforce; 455 | enforce!msg(info == 0); 456 | } 457 | return SvdResult!T(vt, s, u); //transposed 458 | } 459 | 460 | /// 461 | unittest 462 | { 463 | import mir.ndslice; 464 | 465 | auto a = [ 466 | 7.52, -1.10, -7.95, 1.08, 467 | -0.76, 0.62, 9.34, -7.10, 468 | 5.13, 6.62, -5.66, 0.87, 469 | -4.75, 8.52, 5.75, 5.30, 470 | 1.33, 4.91, -5.49, -3.52, 471 | -2.40, -6.77, 2.34, 3.95] 472 | .sliced(6, 4); 473 | 474 | auto r = a.svd; 475 | 476 | auto sigma = slice!double(a.shape, 0); 477 | sigma.diagonal[] = r.sigma; 478 | auto m = r.u.mtimes(sigma).mtimes(r.vt); 479 | 480 | import mir.algorithm.iteration: equal; 481 | import mir.math.common: approxEqual; 482 | assert(equal!((a, b) => a.approxEqual(b, 1e-8, 1e-8))(a, m)); 483 | } 484 | 485 | /// 486 | unittest 487 | { 488 | import std.typecons: Yes; 489 | import mir.ndslice; 490 | 491 | auto a = [ 492 | 7.52, -1.10, -7.95, 1.08, 493 | -0.76, 0.62, 9.34, -7.10, 494 | 5.13, 6.62, -5.66, 0.87, 495 | -4.75, 8.52, 5.75, 5.30, 496 | 1.33, 4.91, -5.49, -3.52, 497 | -2.40, -6.77, 2.34, 3.95] 498 | .sliced(6, 4); 499 | 500 | auto r = a.svd(Yes.slim); 501 | assert(r.u.shape == [6, 4]); 502 | assert(r.vt.shape == [4, 4]); 503 | } 504 | 505 | unittest 506 | { 507 | import mir.algorithm.iteration: all; 508 | 509 | // empty matrix as input means that u or vt is identity matrix 510 | auto identity = slice!double([4, 4], 0); 511 | identity.diagonal[] = 1; 512 | 513 | auto a = slice!double(0, 4); 514 | auto res = a.svd; 515 | 516 | import mir.conv: to; 517 | 518 | assert(res.u.shape == [0, 0]); 519 | assert(res.vt.shape == [4, 4]); 520 | assert(res.vt.all!approxEqual(identity), res.vt.to!string); 521 | 522 | auto b = slice!double(4, 0); 523 | res = b.svd; 524 | 525 | assert(res.u.shape == [4, 4]); 526 | assert(res.vt.shape == [0, 0]); 527 | 528 | assert(res.u.all!approxEqual(identity), res.u.to!string); 529 | } 530 | 531 | unittest 532 | { 533 | import mir.ndslice; 534 | 535 | alias C = Complex!double; 536 | 537 | auto a = [ 538 | 7.52, -1.10, -7.95, 1.08, 539 | -0.76, 0.62, 9.34, -7.10, 540 | 5.13, 6.62, -5.66, 0.87, 541 | -4.75, 8.52, 5.75, 5.30, 542 | 1.33, 4.91, -5.49, -3.52, 543 | -2.40, -6.77, 2.34, 3.95] 544 | .sliced(6, 4).map!(a => C(a)).slice(); 545 | 546 | auto r = a.svd; 547 | 548 | auto sigma = slice!C(a.shape, C(0)); 549 | sigma.diagonal[] = r.sigma; 550 | auto m = r.u.mtimes(sigma).mtimes(r.vt); 551 | 552 | import mir.algorithm.iteration: equal; 553 | import mir.complex.math: approxEqual; 554 | assert(equal!((a, b) => a.approxEqual(b, 1e-8, 1e-8))(a, m)); 555 | } 556 | 557 | /++ 558 | Solve systems of linear equations AX = B for X. 559 | Computes minimum-norm solution to a linear least squares problem 560 | if A is not a square matrix. 561 | +/ 562 | Slice!(BlasType!(IteratorA, IteratorB)*, 2) 563 | mldivide 564 | (IteratorA, SliceKind kindA, IteratorB, SliceKind kindB)( 565 | Slice!(IteratorA, 2, kindA) a, 566 | Slice!(IteratorB, 2, kindB) b) 567 | { 568 | import mir.conv: to; 569 | 570 | assert(a.length!0 == b.length!0); 571 | 572 | alias A = BlasType!IteratorA; 573 | alias B = BlasType!IteratorB; 574 | alias C = CommonType!(A, B); 575 | 576 | auto a_ = a.universal.transposed.as!C.slice.canonical; 577 | auto b_ = b.universal.transposed.as!C.slice.canonical; 578 | 579 | if (a.length!0 == a.length!1) 580 | { 581 | auto ipiv = a_.length.uninitSlice!lapackint; 582 | auto info = gesv(a_, ipiv, b_); 583 | } 584 | else 585 | { 586 | static if(!isComplex!C) 587 | { 588 | size_t liwork = void; 589 | auto lwork = gelsd_wq(a_, b_, liwork); 590 | auto s = min(a_.length!0, a_.length!1).uninitSlice!C; 591 | auto work = lwork.uninitSlice!C; 592 | auto iwork = liwork.uninitSlice!lapackint; 593 | size_t rank; 594 | C rcond = -1; 595 | 596 | auto info = gelsd(a_, b_, s, rcond, rank, work, iwork); 597 | } 598 | else 599 | { 600 | size_t liwork = void; 601 | size_t lrwork = void; 602 | auto lwork = gelsd_wq(a_, b_, lrwork, liwork); 603 | auto s = min(a_.length!0, a_.length!1).uninitSlice!(realType!C); 604 | auto work = lwork.uninitSlice!C; 605 | auto iwork = liwork.uninitSlice!lapackint; 606 | auto rwork = lrwork.uninitSlice!(realType!C); 607 | size_t rank; 608 | realType!C rcond = -1; 609 | 610 | auto info = gelsd!C(a_, b_, s, rcond, rank, work, rwork, iwork); 611 | } 612 | 613 | if (info) 614 | throw new Exception(to!string(info) ~ " off-diagonal elements of an intermediate bidiagonal form did not converge to zero."); 615 | b_ = b_[0 .. $, 0 .. a_.length!0]; 616 | } 617 | 618 | return b_.universal.transposed.slice; 619 | } 620 | 621 | /// ditto 622 | Slice!(BlasType!(IteratorA, IteratorB)*) 623 | mldivide 624 | (IteratorA, SliceKind kindA, IteratorB, SliceKind kindB)( 625 | Slice!(IteratorA, 2, kindA) a, 626 | Slice!(IteratorB, 1, kindB) b) 627 | { 628 | return a.mldivide(b.repeat(1).unpack.universal.transposed).front!1.assumeContiguous; 629 | } 630 | 631 | /// AX=B 632 | unittest 633 | { 634 | import mir.complex; 635 | import std.meta: AliasSeq; 636 | import mir.ndslice; 637 | 638 | foreach(C; AliasSeq!(double, Complex!double)) 639 | { 640 | static if(is(C == Complex!double)) 641 | alias transform = a => C(a, 0); 642 | else 643 | enum transform = "a"; 644 | 645 | auto a = [ 646 | 1, -1, 1, 647 | 2, 2, -4, 648 | -1, 5, 0].sliced(3, 3).map!transform; 649 | auto b = [ 650 | 2.0, 0, 651 | -6 , -6, 652 | 9 , 1].sliced(3, 2).map!transform; 653 | auto t = [ 654 | 1.0, -1, 655 | 2 , 0, 656 | 3 , 1].sliced(3, 2).map!transform; 657 | 658 | auto x = mldivide(a, b); 659 | assert(x == t); 660 | } 661 | } 662 | 663 | /// Ax=B 664 | unittest 665 | { 666 | import mir.complex; 667 | import std.meta: AliasSeq; 668 | import mir.ndslice; 669 | 670 | foreach(C; AliasSeq!(double, Complex!double)) 671 | { 672 | static if(is(C == Complex!double)) 673 | alias transform = a => C(a, 0); 674 | else 675 | enum transform = "a"; 676 | 677 | auto a = [ 678 | 1, -1, 1, 679 | 2, 2, -4, 680 | -1, 5, 0].sliced(3, 3).map!transform; 681 | auto b = [ 682 | 2.0, 683 | -6 , 684 | 9 ].sliced(3).map!transform; 685 | auto t = [ 686 | 1.0, 687 | 2 , 688 | 3 ].sliced(3).map!transform; 689 | 690 | auto x = mldivide(a, b); 691 | assert(x == t); 692 | } 693 | } 694 | 695 | /// Least-Squares Solution of Underdetermined System 696 | unittest 697 | { 698 | import mir.complex; 699 | import std.meta: AliasSeq; 700 | import mir.ndslice; 701 | 702 | foreach(C; AliasSeq!(double, )) //Complex!double fails for DMD>=2085 703 | { 704 | static if(is(C == Complex!double)) 705 | alias transform = a => C(a, 0); 706 | else 707 | enum transform = "a"; 708 | 709 | auto a = [ 710 | -0.57, -1.28, -0.39, 0.25, 711 | -1.93, 1.08, -0.31, -2.14, 712 | 2.30, 0.24, 0.40, -0.35, 713 | -1.93, 0.64, -0.66, 0.08, 714 | 0.15, 0.30, 0.15, -2.13, 715 | -0.02, 1.03, -1.43, 0.50, 716 | ].sliced(6, 4).map!transform; 717 | 718 | auto b = [ 719 | -2.67, 720 | -0.55, 721 | 3.34, 722 | -0.77, 723 | 0.48, 724 | 4.10, 725 | ].sliced.map!transform; 726 | 727 | auto x = [ 728 | 1.5339, 729 | 1.8707, 730 | -1.5241, 731 | 0.0392].sliced.map!transform; 732 | 733 | import mir.math.common: approxEqual; 734 | import mir.algorithm.iteration: all; 735 | alias appr = all!((a, b) => approxEqual(a, b, 1e-3, 1e-3)); 736 | assert(appr(a.mldivide(b), x)); 737 | } 738 | } 739 | 740 | /++ 741 | Solves the equation 742 | 743 | A*X = B, 744 | 745 | where A is an n by n tridiagonal matrix, by Gaussian elimination with 746 | partial pivoting. 747 | +/ 748 | auto solveTridiagonal(Flag!"allowDestroy" allowDestroy = No.allowDestroy, IteratorD, SliceKind kindD, IteratorB, SliceKind kindB, size_t N) 749 | ( 750 | Slice!(IteratorD, 1, kindD) lowerDiag, 751 | Slice!(IteratorD, 1, kindD) mainDiag, 752 | Slice!(IteratorD, 1, kindD) upperDiag, 753 | Slice!(IteratorB, N, kindB) b, 754 | ) 755 | if (N == 1 || N == 2) 756 | { 757 | alias D = BlasType!IteratorD; 758 | alias B = BlasType!IteratorB; 759 | alias T = CommonType!(B, D); 760 | 761 | if (!(mainDiag.length == 0 && lowerDiag.length == 0 || lowerDiag.length + 1 == mainDiag.length)) 762 | throw new Exception("solveTridiagonal: 'lowerDiag' has to have length equal to N - 1, where N is length of 'mainDiag'."); 763 | if (!(upperDiag.length == lowerDiag.length)) 764 | throw new Exception("solveTridiagonal: 'upperDiag' has to have length equal to N - 1, where N is length of 'mainDiag'."); 765 | if (!(mainDiag.length!0 == b.length)) 766 | throw new Exception("solveTridiagonal: The input 'b' has to be N-by-NRHS matrix where N is length of 'mainDiag'."); 767 | 768 | static if (allowDestroy && kindD != Universal && is(IterstorD == T*)) 769 | { 770 | alias dl = lowerDiag; 771 | alias d = mainDiag; 772 | alias du = upperDiag; 773 | } 774 | else 775 | { 776 | auto dl = lowerDiag.as!T.slice; 777 | auto d = mainDiag.as!T.slice; 778 | auto du = upperDiag.as!T.slice; 779 | } 780 | 781 | static if(N == 1) 782 | auto k = b.sliced(1, b.length); 783 | else 784 | auto k = b.transposed; 785 | 786 | static if(is(IteratorB == T*)) 787 | auto m = (allowDestroy && k._stride!1 == 1) ? k.assumeCanonical : k.as!T.slice.canonical; 788 | else 789 | auto m = k.as!T.slice.canonical; 790 | 791 | auto info = gtsv(dl, d, du, m); 792 | assert(info == 0); 793 | 794 | static if (N == 1) 795 | return m.front; 796 | else 797 | return m.transposed; 798 | } 799 | 800 | /// 801 | unittest 802 | { 803 | auto dl = [3.,1,3].sliced; 804 | auto d = [10.,10.,7.,4.].sliced; 805 | auto du = [2.,4.,5.].sliced; 806 | auto b = [3,4,5,6.].sliced; 807 | auto res = [0.1487758945386064, 0.756120527306968, -1.0018832391713748, 2.251412429378531]; 808 | 809 | import mir.math.common: approxEqual; 810 | import mir.algorithm.iteration: equal; 811 | alias appr = equal!approxEqual; 812 | 813 | assert(appr(solveTridiagonal(dl, d, du, b), res)); 814 | assert(appr(solveTridiagonal(dl, d, du, b.sliced(b.length, 1)), res.sliced(res.length, 1))); 815 | } 816 | 817 | /++ 818 | Solves a triangular system of the form 819 | 820 | A * X = B or A**T * X = B, 821 | 822 | where A is a triangular matrix of order N, and B is an N-by-NRHS 823 | matrix. A check is made to verify that A is nonsingular. 824 | 825 | Params: 826 | uplo = 827 | - 'U': A is upper triangular; 828 | - 'L': A is lower triangular. 829 | trans = 830 | Specifies the form of the system of equations: 831 | - 'N': A * X = B (No transpose) 832 | - 'T': A**T * X = B (Transpose) 833 | - 'C': A**H * X = B (Conjugate transpose = Transpose) 834 | diag = 835 | - 'N': A is non-unit triangular; 836 | - 'U': A is unit triangular. 837 | a = A 838 | b = B 839 | +/ 840 | auto solveTriangular(Flag!"allowDestroy" allowDestroy = No.allowDestroy, IteratorA, SliceKind kindA, IteratorB, SliceKind kindB, size_t N) 841 | ( 842 | char uplo, 843 | Slice!(IteratorA, 2, kindA) a, 844 | Slice!(IteratorB, N, kindB) b, 845 | char trans = 'N', 846 | char diag = 'N', 847 | ) 848 | if (N == 1 || N == 2) 849 | { 850 | import mir.conv: to; 851 | 852 | alias A = BlasType!IteratorA; 853 | alias B = BlasType!IteratorB; 854 | alias T = CommonType!(B, A); 855 | 856 | if (uplo != 'U' && uplo != 'L') 857 | throw new Exception("solveTriangular: uplo has to be equal 'U' or 'L'"); 858 | if (trans != 'N' && trans != 'T' && trans != 'H') 859 | throw new Exception("solveTriangular: trans has to be equal 'N', 'T', or 'H'"); 860 | if (diag != 'N' && diag != 'U') 861 | throw new Exception("solveTriangular: diag has to be equal 'N' or 'U'"); 862 | 863 | if (a.length!0 != a.length!1) 864 | throw new Exception("solveTriangular: The input 'a' must be a square matrix."); 865 | if (a.length!0 != b.length!0) 866 | throw new Exception("solveTriangular: The input 'b' has to be N-by-NRHS matrix where N is order of 'a'."); 867 | 868 | uplo = uplo == 'U' ? 'L' : 'U'; 869 | 870 | static if (isComplex!T) 871 | auto isH = trans == 'H'; 872 | else 873 | enum isH = false; 874 | 875 | if (!isH) 876 | trans = trans == 'N' ? 'T' : 'N'; 877 | 878 | static if(is(IteratorA == T*)) 879 | auto a_ = isH ? 880 | a.transposed.as!T.slice.canonical : 881 | (allowDestroy && a._stride!1 == 1) ? 882 | a.assumeCanonical : 883 | a.as!T.slice.canonical; 884 | else 885 | auto a_ = isH ? 886 | a.transposed.as!T.slice.canonical : 887 | a.as!T.slice.canonical; 888 | 889 | static if(N == 1) 890 | auto k = b.sliced(1, b.length); 891 | else 892 | auto k = b.transposed; 893 | 894 | static if(is(IteratorB == T*)) 895 | auto m = (allowDestroy && k._stride!1 == 1) ? k.assumeCanonical : k.as!T.slice.canonical; 896 | else 897 | auto m = k.as!T.slice.canonical; 898 | 899 | auto info = trtrs(uplo, trans, diag, a_, m); 900 | 901 | if (info) 902 | throw new Exception("The " ~ to!string(info) ~ "-th diagonal element of A is zero, 903 | indicating that the matrix is singular and the solutions 904 | X have not been computed."); 905 | 906 | static if (N == 1) 907 | return m.front; 908 | else 909 | return m.transposed; 910 | } 911 | 912 | /// 913 | unittest 914 | { 915 | auto a = [3.0, 1, -5, 0, -2, 4, 0, 0, 2].sliced(3, 3); 916 | auto b = [1.0, 10, 6].sliced; 917 | auto res = [5, 1, 3]; 918 | 919 | import mir.math.common: approxEqual; 920 | import mir.algorithm.iteration: equal; 921 | alias appr = equal!approxEqual; 922 | 923 | assert(appr(solveTriangular('U', a, b), res)); 924 | assert(appr(solveTriangular('U', a, b.sliced(b.length, 1)), res.sliced(res.length, 1))); 925 | } 926 | 927 | /// Principal component analises result. 928 | struct PcaResult(T) 929 | { 930 | /// Principal component coefficients, also known as loadings. 931 | Slice!(T*, 2) coeff; 932 | /// Principal component scores. 933 | Slice!(T*, 2) score; 934 | /// Principal component variances. 935 | Slice!(T*) latent; 936 | } 937 | 938 | /++ 939 | Principal component analysis of raw data. 940 | 941 | Params: 942 | matrix = input `M x N` matrix, where 'M (rows)>= N(cols)' 943 | cc = Flag to centern columns. True by default. 944 | Returns: $(LREF PcaResult) 945 | +/ 946 | auto pca(Flag!"allowDestroy" allowDestroy = No.allowDestroy, Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) matrix, in Flag!"centerColumns" cc = Yes.centerColumns) 947 | in 948 | { 949 | assert(matrix.length!0 >= matrix.length!1); 950 | } 951 | do 952 | { 953 | import mir.math.sum: sum; 954 | import mir.algorithm.iteration: maxIndex, eachUploPair; 955 | import mir.utility: swap; 956 | 957 | alias T = BlasType!Iterator; 958 | SvdResult!T svdResult; 959 | if (cc) 960 | { 961 | static if (allowDestroy && kind != Universal && is(Iterstor == T*)) 962 | alias m = matrix; 963 | else 964 | auto m = matrix.as!T.slice; 965 | foreach (col; m.universal.transposed) 966 | col[] -= col.sum!"kb2" / col.length; 967 | svdResult = m.svd!(Yes.allowDestroy)(Yes.slim); 968 | } 969 | else 970 | { 971 | svdResult = matrix.svd!(allowDestroy)(Yes.slim); 972 | } 973 | with (svdResult) 974 | { 975 | foreach (row; u) 976 | row[] *= sigma; 977 | T c = max(0, ptrdiff_t(matrix.length) - cast(bool) cc); 978 | foreach (ref s; sigma) 979 | s = s * s / c; 980 | foreach(size_t i; 0 .. sigma.length) 981 | { 982 | auto col = vt[i]; 983 | if (col[col.map!fabs.maxIndex] < 0) 984 | { 985 | col[] *= -1; 986 | u[0 .. $, i] *= -1; 987 | } 988 | } 989 | vt.eachUploPair!swap; 990 | return PcaResult!T(vt, u, sigma); 991 | } 992 | } 993 | 994 | /// 995 | unittest 996 | { 997 | import mir.ndslice; 998 | 999 | import mir.math.common: approxEqual; 1000 | import mir.algorithm.iteration: equal; 1001 | 1002 | auto ingedients = [ 1003 | 7, 26, 6, 60, 1004 | 1, 29, 15, 52, 1005 | 11, 56, 8, 20, 1006 | 11, 31, 8, 47, 1007 | 7, 52, 6, 33, 1008 | 11, 55, 9, 22, 1009 | 3, 71, 17, 6, 1010 | 1, 31, 22, 44, 1011 | 2, 54, 18, 22, 1012 | 21, 47, 4, 26, 1013 | 1, 40, 23, 34, 1014 | 11, 66, 9, 12, 1015 | 10, 68, 8, 12].sliced(13, 4); 1016 | 1017 | auto res = ingedients.pca; 1018 | 1019 | auto coeff = [ 1020 | -0.067799985695474, -0.646018286568728, 0.567314540990512, 0.506179559977705, 1021 | -0.678516235418647, -0.019993340484099, -0.543969276583817, 0.493268092159297, 1022 | 0.029020832106229, 0.755309622491133, 0.403553469172668, 0.515567418476836, 1023 | 0.730873909451461, -0.108480477171676, -0.468397518388289, 0.484416225289198, 1024 | ].sliced(4, 4); 1025 | 1026 | auto score = [ 1027 | 36.821825999449700, -6.870878154227367, -4.590944457629745, 0.396652582713912, 1028 | 29.607273420710964, 4.610881963526308, -2.247578163663940, -0.395843536696492, 1029 | -12.981775719737618, -4.204913183175938, 0.902243082694698, -1.126100587210615, 1030 | 23.714725720918022, -6.634052554708721, 1.854742000806314, -0.378564808384691, 1031 | -0.553191676624597, -4.461732123178686, -6.087412652325177, 0.142384896047281, 1032 | -10.812490833309816, -3.646571174544059, 0.912970791674604, -0.134968810314680, 1033 | -32.588166608817929, 8.979846284936063, -1.606265913996588, 0.081763927599947, 1034 | 22.606395499005586, 10.725906457369449, 3.236537714483416, 0.324334774646368, 1035 | -9.262587237675838, 8.985373347478788, -0.016909578102172, -0.543746175981799, 1036 | -3.283969329640680, -14.157277337500918, 7.046512994833761, 0.340509860960606, 1037 | 9.220031117829379, 12.386080787220454, 3.428342878284624, 0.435152769664895, 1038 | -25.584908517429557, -2.781693148152386, -0.386716066864491, 0.446817950545605, 1039 | -26.903161834677597, -2.930971165042989, -2.445522630195304, 0.411607156409658, 1040 | ].sliced(13, 4); 1041 | 1042 | auto latent = [5.177968780739053, 0.674964360487231, 0.124054300480810, 0.002371532651878].sliced; 1043 | latent[] *= 100; 1044 | 1045 | assert(equal!approxEqual(res.coeff, coeff)); 1046 | assert(equal!approxEqual(res.score, score)); 1047 | assert(equal!approxEqual(res.latent, latent)); 1048 | } 1049 | 1050 | /++ 1051 | Computes Moore-Penrose pseudoinverse of matrix. 1052 | 1053 | Params: 1054 | matrix = Input `M x N` matrix. 1055 | tolerance = The computation is based on SVD and any singular values less than tolerance are treated as zero. 1056 | Returns: Moore-Penrose pseudoinverse matrix 1057 | +/ 1058 | Slice!(BlasType!Iterator*, 2) 1059 | pinv(Flag!"allowDestroy" allowDestroy = No.allowDestroy, Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) matrix, double tolerance = double.nan) 1060 | { 1061 | import mir.algorithm.iteration: find, each; 1062 | import std.math: nextUp; 1063 | 1064 | auto svd = matrix.svd!allowDestroy(Yes.slim); 1065 | if (tolerance != tolerance) 1066 | { 1067 | auto n = svd.sigma.front; 1068 | auto eps = n.nextUp - n; 1069 | tolerance = max(matrix.length!0, matrix.length!1) * eps; 1070 | } 1071 | auto st = svd.sigma.find!(a => !(a >= tolerance)); 1072 | static if (is(typeof(st) : sizediff_t)) 1073 | alias si = st; 1074 | else 1075 | auto si = st[0]; 1076 | auto s = svd.sigma[0 .. $ - si]; 1077 | s.each!"a = 1 / a"; 1078 | svd.vt[0 .. s.length].pack!1.map!"a".zip(s).each!"a.a[] *= a.b"; 1079 | auto v = svd.vt[0 .. s.length].universal.transposed; 1080 | auto ut = svd.u.universal.transposed[0 .. s.length]; 1081 | return v.mtimes(ut); 1082 | } 1083 | 1084 | /// 1085 | unittest 1086 | { 1087 | import mir.ndslice; 1088 | 1089 | auto a = [ 1090 | 64, 2, 3, 61, 60, 6, 1091 | 9, 55, 54, 12, 13, 51, 1092 | 17, 47, 46, 20, 21, 43, 1093 | 40, 26, 27, 37, 36, 30, 1094 | 32, 34, 35, 29, 28, 38, 1095 | 41, 23, 22, 44, 45, 19, 1096 | 49, 15, 14, 52, 53, 11, 1097 | 8, 58, 59, 5, 4, 62].sliced(8, 6); 1098 | 1099 | auto b = a.pinv; 1100 | 1101 | auto result = [ 1102 | 0.0177, -0.0165, -0.0164, 0.0174, 0.0173, -0.0161, -0.0160, 0.0170, 1103 | -0.0121, 0.0132, 0.0130, -0.0114, -0.0112, 0.0124, 0.0122, -0.0106, 1104 | -0.0055, 0.0064, 0.0060, -0.0043, -0.0040, 0.0049, 0.0045, -0.0028, 1105 | -0.0020, 0.0039, 0.0046, -0.0038, -0.0044, 0.0064, 0.0070, -0.0063, 1106 | -0.0086, 0.0108, 0.0115, -0.0109, -0.0117, 0.0139, 0.0147, -0.0141, 1107 | 0.0142, -0.0140, -0.0149, 0.0169, 0.0178, -0.0176, -0.0185, 0.0205].sliced(6, 8); 1108 | 1109 | import mir.math.common: approxEqual; 1110 | import mir.algorithm.iteration: all; 1111 | 1112 | assert(b.all!((a, b) => approxEqual(a, b, 1e-2, 1e-2))(result)); 1113 | } 1114 | 1115 | /++ 1116 | Covariance matrix. 1117 | 1118 | Params: 1119 | matrix = matrix whose rows represent observations and whose columns represent random variables. 1120 | Returns: 1121 | Normalized by `N-1` covariance matrix. 1122 | +/ 1123 | Slice!(BlasType!Iterator*, 2) 1124 | cov(Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) matrix) 1125 | { 1126 | import mir.math.sum: sum; 1127 | import mir.algorithm.iteration: each, eachUploPair; 1128 | alias A = BlasType!Iterator; 1129 | static if (kind == Contiguous) 1130 | auto mc = matrix.canonical; 1131 | else 1132 | alias mc = matrix; 1133 | auto m = mc.shape.uninitSlice!A.canonical; 1134 | auto s = m; 1135 | 1136 | auto factor = 1 / A(m.length!0 - 1).sqrt; 1137 | while(m.length!1) 1138 | { 1139 | auto shift = - mc.front!1.sum!A / m.length!0; 1140 | m.front!1.each!((ref a, b) { a = (b + shift) * factor; })(mc.front!1); 1141 | mc.popFront!1; 1142 | m.popFront!1; 1143 | } 1144 | 1145 | auto alpha = cast(A) 1; 1146 | auto beta = cast(A) 0; 1147 | auto c = [s.length!1, s.length!1].uninitSlice!A; 1148 | syrk(Uplo.Upper, alpha, s.universal.transposed, beta, c); 1149 | 1150 | c.eachUploPair!"b = a"; 1151 | return c; 1152 | } 1153 | 1154 | /// 1155 | unittest 1156 | { 1157 | import mir.ndslice; 1158 | 1159 | import std.stdio; 1160 | import mir.ndslice; 1161 | 1162 | auto c = 8.magic[0..$-1].cov; 1163 | 1164 | auto result = [ 1165 | 350.0000, -340.6667, -331.3333, 322.0000, 312.6667, -303.3333, -294.0000, 284.6667, 1166 | -340.6667, 332.4762, 324.2857, -316.0952, -307.9048, 299.7143, 291.5238, -283.3333, 1167 | -331.3333, 324.2857, 317.2381, -310.1905, -303.1429, 296.0952, 289.0476, -282.0000, 1168 | 322.0000, -316.0952, -310.1905, 304.2857, 298.3810, -292.4762, -286.5714, 280.6667, 1169 | 312.6667, -307.9048, -303.1429, 298.3810, 293.6190, -288.8571, -284.0952, 279.3333, 1170 | -303.3333, 299.7143, 296.0952, -292.4762, -288.8571, 285.2381, 281.6190, -278.0000, 1171 | -294.0000, 291.5238, 289.0476, -286.5714, -284.0952, 281.6190, 279.1429, -276.6667, 1172 | 284.6667, -283.3333, -282.0000, 280.6667, 279.3333, -278.0000, -276.6667, 275.3333].sliced(8, 8); 1173 | import mir.math.common: approxEqual; 1174 | import mir.algorithm.iteration: all; 1175 | assert(c.all!((a, b) => approxEqual(a, b, 1e-5, 1e-5))(result)); 1176 | } 1177 | 1178 | /++ 1179 | Pearson product-moment correlation coefficients. 1180 | 1181 | Params: 1182 | matrix = matrix whose rows represent observations and whose columns represent random variables. 1183 | Returns: 1184 | The correlation coefficient matrix of the variables. 1185 | +/ 1186 | Slice!(BlasType!Iterator*, 2) 1187 | corrcoef(Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) matrix) 1188 | { 1189 | import mir.math.common: sqrt, fmin, fmax; 1190 | import core.lifetime: move; 1191 | import mir.algorithm.iteration: eachUploPair; 1192 | 1193 | auto ret = cov(move(matrix)); 1194 | 1195 | foreach (i; 0 .. ret.length) 1196 | { 1197 | auto isq = 1 / sqrt(ret[i, i]); 1198 | ret[i, i] = 1; 1199 | ret[i, i + 1 .. ret.length] *= isq; 1200 | ret[0 .. i, i] *= isq; 1201 | } 1202 | 1203 | ret.eachUploPair!((ref a, ref b){b = a = a.fmax(-1).fmin(+1);}); 1204 | return ret; 1205 | } 1206 | 1207 | /// 1208 | unittest 1209 | { 1210 | import mir.ndslice; 1211 | 1212 | import std.stdio; 1213 | import mir.ndslice; 1214 | 1215 | auto m = 1216 | [0.77395605, 0.43887844, 0.85859792, 1217 | 0.69736803, 0.09417735, 0.97562235, 1218 | 0.7611397 , 0.78606431, 0.12811363].sliced(3, 3); 1219 | 1220 | auto result = 1221 | [1. , 0.99256089, -0.68080986, 1222 | 0.99256089, 1. , -0.76492172, 1223 | -0.68080986, -0.76492172, 1. ].sliced(3, 3); 1224 | 1225 | auto corr = m.transposed.corrcoef; 1226 | 1227 | import mir.math.common: approxEqual; 1228 | import mir.algorithm.iteration: all; 1229 | assert(corr.all!((a, b) => approxEqual(a, b, 1e-5, 1e-5))(result)); 1230 | } 1231 | 1232 | /++ 1233 | Matrix determinant. 1234 | +/ 1235 | auto detSymmetric(Iterator, SliceKind kind)(char store, Slice!(Iterator, 2, kind) a) 1236 | in 1237 | { 1238 | assert(store == 'U' || store == 'L'); 1239 | assert (a.length!0 == a.length!1, "matrix must be square"); 1240 | assert (a.length!0, "matrix must not be empty"); 1241 | } 1242 | do 1243 | { 1244 | import mir.algorithm.iteration: each; 1245 | import mir.ndslice.topology: diagonal; 1246 | import mir.math.numeric: ProdAccumulator; 1247 | 1248 | alias T = BlasType!Iterator; 1249 | 1250 | auto packed = uninitSlice!T(a.length * (a.length + 1) / 2); 1251 | auto ipiv = a.length.uninitSlice!lapackint; 1252 | int sign; 1253 | ProdAccumulator!T prod; 1254 | if (store == 'L') 1255 | { 1256 | auto pck = packed.stairs!"+"(a.length); 1257 | auto gen = a.stairs!"+"; 1258 | pck.each!"a[] = b"(gen); 1259 | auto info = sptrf(pck, ipiv); 1260 | if (info > 0) 1261 | return cast(T) 0; 1262 | for (size_t j; j < ipiv.length; j++) 1263 | { 1264 | auto i = ipiv[j]; 1265 | // 1x1 block at m[k,k] 1266 | if (i > 0) 1267 | { 1268 | prod.put(pck[j].back); 1269 | sign ^= i != j + 1; // i.e. row interchanged with another 1270 | } 1271 | else 1272 | { 1273 | i = -i; 1274 | auto offDiag = pck[j + 1][$ - 2]; 1275 | auto blockDet = pck[j].back * pck[j + 1].back - offDiag * offDiag; 1276 | prod.put(blockDet); 1277 | sign ^= i != j + 1 && i != j + 2; // row interchanged with other 1278 | j++; 1279 | } 1280 | } 1281 | } 1282 | else 1283 | { 1284 | auto pck = packed.stairs!"-"(a.length); 1285 | auto gen = a.stairs!"-"; 1286 | pck.each!"a[] = b"(gen); 1287 | auto info = sptrf(pck, ipiv); 1288 | if (info > 0) 1289 | return cast(T) 0; 1290 | for (size_t j; j < ipiv.length; j++) 1291 | { 1292 | auto i = ipiv[j]; 1293 | // 1x1 block at m[k,k] 1294 | if (i > 0) 1295 | { 1296 | prod.put(pck[j].front); 1297 | sign ^= i != j + 1; // i.e. row interchanged with another 1298 | } 1299 | else 1300 | { 1301 | i = -i; 1302 | auto offDiag = pck[j][1]; 1303 | auto blockDet = pck[j].front * pck[j + 1].front - offDiag * offDiag; 1304 | prod.put(blockDet); 1305 | sign ^= i != j + 1 && i != j + 2; // row interchanged with other 1306 | j++; 1307 | } 1308 | } 1309 | } 1310 | if(sign & 1) 1311 | prod.x = -prod.x; 1312 | return prod.prod; 1313 | } 1314 | 1315 | /// ditto 1316 | auto det(Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) a) 1317 | in 1318 | { 1319 | assert (a.length!0 == a.length!1, "matrix must be square"); 1320 | } 1321 | do 1322 | { 1323 | import mir.ndslice.topology: diagonal, zip, iota; 1324 | import mir.math.numeric: ProdAccumulator; 1325 | 1326 | alias T = BlasType!Iterator; 1327 | 1328 | auto m = a.as!T.slice.canonical; 1329 | auto ipiv = a.length.uninitSlice!lapackint; 1330 | 1331 | // LU factorization 1332 | auto info = m.getrf(ipiv); 1333 | 1334 | // If matrix is singular, determinant is zero. 1335 | if (info > 0) 1336 | { 1337 | return cast(T) 0; 1338 | } 1339 | 1340 | // The determinant is the product of the diagonal entries 1341 | // of the upper triangular matrix. The array ipiv contains 1342 | // the pivots. 1343 | int sign; 1344 | ProdAccumulator!T prod; 1345 | foreach (tup; m.diagonal.zip(ipiv, [ipiv.length].iota(1))) 1346 | { 1347 | prod.put(tup.a); 1348 | sign ^= tup.b != tup.c; // i.e. row interchanged with another 1349 | } 1350 | if(sign & 1) 1351 | prod.x = -prod.x; 1352 | return prod.prod; 1353 | } 1354 | 1355 | /// 1356 | unittest 1357 | { 1358 | import mir.ndslice; 1359 | import mir.math; 1360 | 1361 | // Check for zero-determinant shortcut. 1362 | auto ssing = [4, 2, 2, 1].sliced(2, 2); 1363 | auto ssingd = det(ssing); 1364 | assert (det(ssing) == 0); 1365 | assert (detSymmetric('L', ssing) == 0); 1366 | 1367 | // check determinant of empty matrix 1368 | assert(slice!double(0, 0).det == 1); 1369 | // check determinant of zero matrix 1370 | assert(repeat(0, 9).sliced(3, 3).det == 0); 1371 | 1372 | // General dense matrix. 1373 | int dn = 101; 1374 | auto d = uninitSlice!double(dn, dn); 1375 | foreach (k; 0 .. dn) 1376 | foreach (l; 0 .. dn) 1377 | d[k,l] = 0.5 * (k == l ? (k + 1) * (k + 1) + 1 : 2 * (k + 1) * (l + 1)); 1378 | 1379 | auto dd = det(d); 1380 | import mir.math.common: approxEqual; 1381 | assert (approxEqual(dd, 3.539152633479803e289, double.epsilon.sqrt)); 1382 | 1383 | // Symmetric packed matrix 1384 | auto spa = [ 1.0, -2, 3, 4, 5, -6, -7, -8, -9, 10].sliced.stairs!"+"(4); 1385 | auto sp = [spa.length, spa.length].uninitSlice!double; 1386 | import mir.algorithm.iteration: each; 1387 | sp.stairs!"+".each!"a[] = b"(spa); 1388 | assert (detSymmetric('L', sp).approxEqual(5874.0, double.epsilon.sqrt)); 1389 | assert (detSymmetric('U', sp.universal.transposed).approxEqual(5874.0, double.epsilon.sqrt)); 1390 | } 1391 | 1392 | /++ 1393 | Eigenvalues and eigenvectors POD. 1394 | 1395 | See_also: $(LREF eigSymmetric). 1396 | +/ 1397 | struct EigSymmetricResult(T) 1398 | { 1399 | /// Eigenvalues 1400 | Slice!(T*) values; 1401 | /// Eigenvectors stored in rows 1402 | Slice!(T*, 2) vectors; 1403 | } 1404 | 1405 | /++ 1406 | Eigenvalues and eigenvectors of symmetric matrix. 1407 | 1408 | Returns: 1409 | $(LREF EigSymmetricResult) 1410 | +/ 1411 | auto eigSymmetric(Flag!"computeVectors" cv = Yes.computeVectors, Iterator, SliceKind kind)(char store, Slice!(Iterator, 2, kind) a) 1412 | in 1413 | { 1414 | assert(store == 'U' || store == 'L'); 1415 | assert (a.length!0 == a.length!1, "matrix must be square"); 1416 | assert (a.length!0, "matrix must not be empty"); 1417 | } 1418 | do 1419 | { 1420 | import mir.conv: to; 1421 | import mir.algorithm.iteration: each; 1422 | import mir.ndslice.topology: diagonal; 1423 | import mir.math.numeric: ProdAccumulator; 1424 | 1425 | alias T = BlasType!Iterator; 1426 | 1427 | auto packed = [a.length * (a.length + 1) / 2].uninitSlice!T; 1428 | auto w = [a.length].uninitSlice!T; 1429 | static if (cv) 1430 | { 1431 | auto z = [a.length, a.length].uninitSlice!T; 1432 | } 1433 | else 1434 | { 1435 | T[1] _vData = void; 1436 | auto z = _vData[].sliced(1, 1); 1437 | } 1438 | auto work = [a.length * 3].uninitSlice!T; 1439 | size_t info = void; 1440 | auto jobz = cv ? 'V' : 'N'; 1441 | if (store == 'L') 1442 | { 1443 | auto pck = packed.stairs!"+"(a.length); 1444 | auto gen = a.stairs!"+"; 1445 | pck.each!"a[] = b"(gen); 1446 | info = spev!T(jobz, pck, w, z.canonical, work); 1447 | } 1448 | else 1449 | { 1450 | auto pck = packed.stairs!"-"(a.length); 1451 | auto gen = a.stairs!"-"; 1452 | pck.each!"a[] = b"(gen); 1453 | info = spev!T(jobz, pck, w, z.canonical, work); 1454 | } 1455 | if (info) 1456 | throw new Exception("The algorithm failed to converge. " ~ info.to!string ~ 1457 | " off-diagonal elements of an intermediate tridiagonal form did not converge to zero."); 1458 | static if (cv) 1459 | { 1460 | return EigSymmetricResult!T(w, z); 1461 | } 1462 | else 1463 | { 1464 | return EigSymmetricResult!T(w); 1465 | } 1466 | } 1467 | 1468 | /// 1469 | unittest 1470 | { 1471 | import mir.ndslice; 1472 | 1473 | import mir.ndslice.slice: sliced; 1474 | import mir.ndslice.topology: universal, map; 1475 | import mir.ndslice.dynamic: transposed; 1476 | import mir.math.common: approxEqual; 1477 | import mir.algorithm.iteration: all; 1478 | 1479 | auto a = [ 1480 | 1.0000, 0.5000, 0.3333, 0.2500, 1481 | 0.5000, 1.0000, 0.6667, 0.5000, 1482 | 0.3333, 0.6667, 1.0000, 0.7500, 1483 | 0.2500, 0.5000, 0.7500, 1.0000].sliced(4, 4); 1484 | 1485 | auto eigr = eigSymmetric('L', a); 1486 | 1487 | alias appr = all!((a, b) => approxEqual(a, b, 1e-3, 1e-3)); 1488 | 1489 | assert(appr(eigr.values, [0.2078,0.4078,0.8482,2.5362])); 1490 | 1491 | auto test = [ 1492 | 0.0693, -0.4422, -0.8105, 0.3778, 1493 | -0.3618, 0.7420, -0.1877, 0.5322, 1494 | 0.7694, 0.0486, 0.3010, 0.5614, 1495 | -0.5219, -0.5014, 0.4662, 0.5088].sliced(4, 4).transposed; 1496 | 1497 | 1498 | foreach (i; 0 .. 4) 1499 | assert(appr(eigr.vectors[i], test[i]) || appr(eigr.vectors[i].map!"-a", test[i])); 1500 | } 1501 | 1502 | version (unittest) 1503 | { 1504 | /++ 1505 | Swaps rows of input matrix 1506 | Params 1507 | ipiv = pivot points 1508 | Returns: 1509 | a = shifted matrix 1510 | +/ 1511 | private void moveRows(Iterator, SliceKind kind) 1512 | (Slice!(Iterator, 2, kind) a, 1513 | Slice!(lapackint*) ipiv) 1514 | { 1515 | import mir.algorithm.iteration: each; 1516 | foreach_reverse(i;0..ipiv.length) 1517 | { 1518 | if(ipiv[i] == i + 1) 1519 | continue; 1520 | each!swap(a[i], a[ipiv[i] - 1]); 1521 | } 1522 | } 1523 | } 1524 | 1525 | unittest 1526 | { 1527 | import mir.ndslice; 1528 | 1529 | auto A = 1530 | [ 9, 9, 9, 1531 | 8, 8, 8, 1532 | 7, 7, 7, 1533 | 6, 6, 6, 1534 | 5, 5, 5, 1535 | 4, 4, 4, 1536 | 3, 3, 3, 1537 | 2, 2, 2, 1538 | 1, 1, 1, 1539 | 0, 0, 0 ] 1540 | .sliced(10, 3) 1541 | .as!double.slice; 1542 | auto ipiv = [ lapackint(10), 9, 8, 7, 6, 6, 7, 8, 9, 10 ].sliced(10); 1543 | moveRows(A, ipiv); 1544 | 1545 | auto B = 1546 | [ 0, 0, 0, 1547 | 1, 1, 1, 1548 | 2, 2, 2, 1549 | 3, 3, 3, 1550 | 4, 4, 4, 1551 | 5, 5, 5, 1552 | 6, 6, 6, 1553 | 7, 7, 7, 1554 | 8, 8, 8, 1555 | 9, 9, 9 ] 1556 | .sliced(10, 3) 1557 | .as!double.slice; 1558 | 1559 | import mir.algorithm.iteration: equal; 1560 | assert(equal!((a, b) => fabs(a - b) < 1e-12)(B, A)); 1561 | } 1562 | 1563 | unittest 1564 | { 1565 | auto A = 1566 | [ 1, 1, 1, 1567 | 2, 2, 2, 1568 | 3, 3, 3, 1569 | 4, 4, 4, 1570 | 5, 5, 5, 1571 | 6, 6, 6, 1572 | 7, 7, 7, 1573 | 8, 8, 8, 1574 | 9, 9, 9, 1575 | 0, 0, 0 ] 1576 | .sliced(10, 3) 1577 | .as!double.slice; 1578 | auto ipiv = [ lapackint(2), 3, 4, 5, 6, 7, 8, 9, 10, 10 ].sliced(10); 1579 | moveRows(A, ipiv); 1580 | 1581 | auto B = 1582 | [ 0, 0, 0, 1583 | 1, 1, 1, 1584 | 2, 2, 2, 1585 | 3, 3, 3, 1586 | 4, 4, 4, 1587 | 5, 5, 5, 1588 | 6, 6, 6, 1589 | 7, 7, 7, 1590 | 8, 8, 8, 1591 | 9, 9, 9 ] 1592 | .sliced(10, 3) 1593 | .as!double.slice; 1594 | 1595 | import mir.algorithm.iteration: equal; 1596 | assert(equal!((a, b) => fabs(a - b) < 1e-12)(B, A)); 1597 | } 1598 | 1599 | ///LUResult consist lu factorization. 1600 | struct LUResult(T) 1601 | { 1602 | /++ 1603 | Matrix in witch lower triangular is L part of factorization 1604 | (diagonal elements of L are not stored), upper triangular 1605 | is U part of factorization. 1606 | +/ 1607 | Slice!(T*, 2, Canonical) lut; 1608 | /++ 1609 | The pivot indices, for 1 <= i <= min(M,N), row i of the matrix 1610 | was interchanged with row ipiv(i). 1611 | +/ 1612 | Slice!(lapackint*) ipiv; 1613 | ///L part of the factorization. 1614 | auto l() @property 1615 | { 1616 | import mir.algorithm.iteration: eachUpper; 1617 | auto l = lut.transposed[0..lut.length!1, 0..min(lut.length!0, lut.length!1)].slice.canonical; 1618 | l.eachUpper!"a = 0"; 1619 | l.diagonal[] = 1; 1620 | return l; 1621 | } 1622 | ///U part of the factorization. 1623 | auto u() @property 1624 | { 1625 | import mir.algorithm.iteration: eachLower; 1626 | auto u = lut.transposed[0..min(lut.length!0, lut.length!1), 0..lut.length!0].slice.canonical; 1627 | u.eachLower!"a = 0"; 1628 | return u; 1629 | } 1630 | 1631 | /// 1632 | auto solve(Flag!"allowDestroy" allowDestroy = No.allowDestroy, Iterator, size_t N, SliceKind kind)( 1633 | char trans, 1634 | Slice!(Iterator, N, kind) b) 1635 | { 1636 | return luSolve!(allowDestroy)(trans, lut, ipiv, b); 1637 | } 1638 | } 1639 | 1640 | /++ 1641 | Computes LU factorization of a general 'M x N' matrix 'A' using partial 1642 | pivoting with row interchanges. 1643 | The factorization has the form: 1644 | \A = P * L * U 1645 | Where P is a permutation matrix, L is lower triangular with unit 1646 | diagonal elements (lower trapezoidal if m > n), and U is upper 1647 | triangular (upper trapezoidal if m < n). 1648 | Params: 1649 | allowDestroy = flag to delete the source matrix. 1650 | a = input 'M x N' matrix for factorization. 1651 | Returns: $(LREF LUResalt) 1652 | +/ 1653 | auto luDecomp(Flag!"allowDestroy" allowDestroy = No.allowDestroy, 1654 | Iterator, SliceKind kind) 1655 | (Slice!(Iterator, 2, kind) a) 1656 | { 1657 | alias T = BlasType!Iterator; 1658 | auto ipiv = uninitSlice!lapackint(min(a.length!0, a.length!1)); 1659 | auto b = a.transposed; 1660 | auto m = (allowDestroy && b._stride!1 == 1) ? b.assumeCanonical : a.transposed.as!T.slice.canonical; 1661 | 1662 | getrf(m, ipiv); 1663 | return LUResult!T(m, ipiv); 1664 | } 1665 | 1666 | /++ 1667 | Solves a system of linear equations 1668 | \A * X = B, or 1669 | \A**T * X = B 1670 | with a general 'N x N' matrix 'A' using the LU factorization computed by luDecomp. 1671 | Params: 1672 | allowDestroy = flag to delete the source matrix. 1673 | lut = factorization of matrix 'A', A = P * L * U. 1674 | ipiv = the pivot indices from luDecomp. 1675 | b = the right hand side matrix B. 1676 | trans = specifies the form of the system of equations: 1677 | 'N': A * X = B (No transpose) 1678 | 'T': A**T * X = B (Transpose) 1679 | 'C': A**T * X = B (Conjugate transpose = Transpose) 1680 | Returns: 1681 | Return solve of the system linear equations. 1682 | +/ 1683 | auto luSolve(Flag!"allowDestroy" allowDestroy = No.allowDestroy, SliceKind kindB, size_t N, IteratorB, IteratorLU)( 1684 | char trans, 1685 | Slice!(IteratorLU, 2, Canonical) lut, 1686 | Slice!(lapackint*) ipiv, 1687 | Slice!(IteratorB, N, kindB) b, 1688 | ) 1689 | in 1690 | { 1691 | assert(lut.length!0 == lut.length!1, "matrix must be squared"); 1692 | assert(ipiv.length == lut.length, "size of ipiv must be equal to the number of rows a"); 1693 | assert(lut.length!1 == b.length!0, "number of columns a should be equal to the number of rows b"); 1694 | } 1695 | do 1696 | { 1697 | alias LU = BlasType!IteratorLU; 1698 | alias B = BlasType!IteratorB; 1699 | alias T = CommonType!(LU, B); 1700 | static if(is(T* == IteratorLU)) 1701 | auto lut_ = lut; 1702 | else 1703 | auto lut_ = lut.as!T.slice.canonical; 1704 | 1705 | //convect vector to matrix. 1706 | static if(N == 1) 1707 | auto k = b.sliced(1, b.length); 1708 | else 1709 | auto k = b.transposed; 1710 | 1711 | static if(is(IteratorB == T*)) 1712 | auto m = (allowDestroy && k._stride!1 == 1) ? k.assumeCanonical : k.as!T.slice.canonical; 1713 | else 1714 | auto m = k.as!T.slice.canonical; 1715 | getrs!T(trans, lut_, m, ipiv); 1716 | return m.transposed; 1717 | } 1718 | 1719 | unittest 1720 | { 1721 | auto A = 1722 | [ 1, 4, -3, 5, 6, 1723 | -2, 8, 5, 7, 8, 1724 | 3, 4, 7, 9, 1, 1725 | 2, 4, 6, 3, 2, 1726 | 6, 8, 3, 5, 2 ] 1727 | .sliced(5, 5) 1728 | .as!double.slice 1729 | .canonical; 1730 | auto B = 1731 | [ 1, 3, 4, 8, 0, 0, 0, 1732 | 2, 1, 7, 1, 0, 0, 0, 1733 | 3, 5, 7, 7, 0, 0, 0, 1734 | 4, 4, 9, 8, 0, 0, 0, 1735 | 5, 5, 8, 1, 0, 0, 0 ] 1736 | .sliced(5, 7) 1737 | .as!double.slice 1738 | .universal; 1739 | 1740 | auto B_ = B[0..$, 0..4]; 1741 | auto LU = A.luDecomp(); 1742 | auto m = luSolve('N', LU.lut, LU.ipiv, B_); 1743 | 1744 | import mir.math.common: approxEqual; 1745 | import mir.algorithm.iteration: equal; 1746 | alias appr = equal!((a, b) => approxEqual(a, b, 1e-5, 1e-5)); 1747 | assert(appr(mtimes(A, m), B_)); 1748 | } 1749 | 1750 | /// 1751 | unittest 1752 | { 1753 | import mir.math; 1754 | import mir.ndslice; 1755 | 1756 | auto A = 1757 | [ 1, 4, -3, 5, 6, 1758 | -2, 8, 5, 7, 8, 1759 | 3, 4, 7, 9, 1, 1760 | 2, 4, 6, 3, 2, 1761 | 6, 8, 3, 5, 2 ] 1762 | .sliced(5, 5) 1763 | .as!double.slice 1764 | .canonical; 1765 | 1766 | import mir.random.variable; 1767 | import mir.random.algorithm; 1768 | auto B = randomSlice(uniformVar(-100, 100), 5, 100); 1769 | 1770 | auto LU = A.luDecomp(); 1771 | auto X = LU.solve('N', B); 1772 | 1773 | import mir.algorithm.iteration: equal; 1774 | assert(equal!((a, b) => fabs(a - b) < 1e-12)(mtimes(A, X), B)); 1775 | } 1776 | 1777 | unittest 1778 | { 1779 | auto A = 1780 | [ 1, 4, -3, 5, 6, 1781 | -2, 8, 5, 7, 8, 1782 | 3, 4, 7, 9, 1, 1783 | 2, 4, 6, 3, 2, 1784 | 6, 8, 3, 5, 2 ] 1785 | .sliced(5, 5) 1786 | .as!double.slice 1787 | .canonical; 1788 | auto B = 1789 | [ 2, 8, 3, 5, 8, 1790 | 8, 1, 4, 9, 86, 1791 | 1, 6, 7, 1, 67, 1792 | 6, 1, 5, 4, 45, 1793 | 1, 2, 3, 1, 11 ] 1794 | .sliced(5, 5) 1795 | .as!double.slice 1796 | .universal; 1797 | auto C = B.slice; 1798 | 1799 | auto LU = A.luDecomp(); 1800 | auto m = luSolve!(Yes.allowDestroy)('N', LU.lut, LU.ipiv, B.transposed); 1801 | auto m2 = LU.solve('N', C); 1802 | 1803 | import mir.math.common: approxEqual; 1804 | import mir.algorithm.iteration: equal; 1805 | alias appr = equal!((a, b) => approxEqual(a, b, 1e-5, 1e-5)); 1806 | assert(appr(mtimes(A, m), C.transposed)); 1807 | assert(equal!approxEqual(mtimes(A, m2), C)); 1808 | } 1809 | 1810 | unittest 1811 | { 1812 | auto A = 1813 | [ 1, 4, -3, 5, 6, 1814 | -2, 8, 5, 7, 8, 1815 | 3, 4, 7, 9, 1, 1816 | 2, 4, 6, 3, 2, 1817 | 6, 8, 3, 5, 2 ] 1818 | .sliced(5, 5) 1819 | .as!float.slice 1820 | .canonical; 1821 | auto B = [ 1, 2, 3, 4, 5 ].sliced(5).as!double.slice; 1822 | auto C = B.slice.sliced(5, 1); 1823 | 1824 | auto LU = A.luDecomp(); 1825 | auto m = luSolve!(Yes.allowDestroy)('N', LU.lut, LU.ipiv, B); 1826 | 1827 | import mir.math.common: approxEqual; 1828 | import mir.algorithm.iteration: equal; 1829 | alias appr = equal!((a, b) => approxEqual(a, b, 1e-5, 1e-5)); 1830 | assert(appr(mtimes(A, m), C)); 1831 | } 1832 | 1833 | unittest 1834 | { 1835 | auto A = 1836 | [ 1, 4, -3, 5, 6, 1837 | -2, 8, 5, 7, 8, 1838 | 3, 4, 7, 9, 1, 1839 | 2, 4, 6, 3, 2, 1840 | 6, 8, 3, 5, 2 ] 1841 | .sliced(5, 5) 1842 | .as!double.slice 1843 | .canonical; 1844 | auto B = [ 1, 15, 4, 5, 8, 1845 | 3, 20, 1, 9, 11 ].sliced(5, 2).as!float.slice; 1846 | 1847 | auto LU = A.luDecomp(); 1848 | auto m = luSolve('N', LU.lut, LU.ipiv, B); 1849 | 1850 | import mir.math.common: approxEqual; 1851 | import mir.algorithm.iteration: equal; 1852 | alias appr = equal!((a, b) => approxEqual(a, b, 1e-5, 1e-5)); 1853 | assert(appr(mtimes(A, m), B)); 1854 | } 1855 | 1856 | unittest 1857 | { 1858 | auto A = 1859 | [ 11, 14, -31, 53, 62, 1860 | -92, 83, 52, 74, 83, 1861 | 31, 45, 73, 96, 17, 1862 | 23, 14, 65, 35, 26, 1863 | 62, 28, 34, 51, 25 ] 1864 | .sliced(5, 5) 1865 | .as!float.slice 1866 | .universal; 1867 | auto B = 1868 | [ 6, 1, 3, 1, 11, 1869 | 12, 5, 7, 6, 78, 1870 | 8, 4, 1, 5, 54, 1871 | 3, 1, 8, 1, 45, 1872 | 1, 6, 8, 6, 312 ] 1873 | .sliced(5, 5) 1874 | .as!double.slice; 1875 | auto B2 = B.slice; 1876 | auto C = B.slice; 1877 | 1878 | auto LU = luDecomp(A.transposed); 1879 | auto m = luSolve!(Yes.allowDestroy)('T', LU.lut, LU.ipiv, B); 1880 | auto m2 = luSolve!(Yes.allowDestroy)('N', LU.lut, LU.ipiv, B2); 1881 | 1882 | import mir.math.common: approxEqual; 1883 | import mir.algorithm.iteration: equal; 1884 | alias appr = equal!((a, b) => approxEqual(a, b, 1e-5, 1e-5)); 1885 | 1886 | assert(appr(mtimes(A, m), C)); 1887 | assert(appr(mtimes(A.transposed, m2), C)); 1888 | } 1889 | 1890 | unittest 1891 | { 1892 | auto A = 1893 | [ 54, 93, 14, 44, 33, 1894 | 51, 85, 28, 81, 75, 1895 | 89, 17, 15, 44, 58, 1896 | 75, 80, 18, 35, 14, 1897 | 21, 48, 72, 21, 88 ] 1898 | .sliced(5, 5) 1899 | .as!double.slice 1900 | .universal; 1901 | auto B = 1902 | [ 5, 7, 8, 3, 78, 1903 | 1, 2, 5, 4, 5, 1904 | 2, 4, 1, 5, 15, 1905 | 1, 1, 4, 1, 154, 1906 | 1, 3, 1, 8, 17 ] 1907 | .sliced(5, 5) 1908 | .as!float.slice 1909 | .canonical; 1910 | 1911 | auto LU = A.luDecomp(); 1912 | auto m = luSolve('N', LU.lut, LU.ipiv, B); 1913 | 1914 | import mir.math.common: approxEqual; 1915 | import mir.algorithm.iteration: equal; 1916 | alias appr = equal!((a, b) => approxEqual(a, b, 1e-5, 1e-5)); 1917 | assert(appr(mtimes(A, m), B)); 1918 | } 1919 | 1920 | unittest 1921 | { 1922 | 1923 | auto B = 1924 | [ 1, 4, -3, 1925 | -2, 8, 5, 1926 | 3, 4, 7, 1927 | 2, 4, 6 ] 1928 | .sliced(4, 3) 1929 | .as!double.slice; 1930 | 1931 | auto LU = B.luDecomp!(Yes.allowDestroy)(); 1932 | LU.l; LU.u; 1933 | auto res = mtimes(LU.l, LU.u); 1934 | moveRows(res, LU.ipiv); 1935 | 1936 | import mir.algorithm.iteration: equal; 1937 | import mir.math.common: approxEqual; 1938 | assert(res.equal!approxEqual(B)); 1939 | } 1940 | 1941 | unittest 1942 | { 1943 | import mir.ndslice; 1944 | 1945 | auto B = 1946 | [ 3, -7, -2, 2, 1947 | -3, 5, 1, 0, 1948 | 6, -4, 0, -5, 1949 | -9, 5, -5, 12 ] 1950 | .sliced(4, 4) 1951 | .as!double.slice 1952 | .canonical; 1953 | auto C = B.transposed.slice; 1954 | 1955 | auto LU = B.transposed.luDecomp!(Yes.allowDestroy)(); 1956 | auto res = mtimes(LU.l, LU.u); 1957 | moveRows(res, LU.ipiv); 1958 | 1959 | import mir.algorithm.iteration: equal; 1960 | import mir.math.common: approxEqual; 1961 | assert(res.equal!approxEqual(C)); 1962 | } 1963 | 1964 | ///Consist LDL factorization; 1965 | struct LDLResult(T) 1966 | { 1967 | /++ 1968 | uplo = 'U': Upper triangle is stored; 1969 | 'L': lower triangle is stored. 1970 | +/ 1971 | char uplo; 1972 | /++ 1973 | Matrix in witch lower triangular matrix is 'L' part of 1974 | factorization, diagonal is 'D' part. 1975 | +/ 1976 | Slice!(T*, 2, Canonical) matrix; 1977 | /++ 1978 | The pivot indices. 1979 | If ipiv(k) > 0, then rows and columns k and ipiv(k) were 1980 | interchanged and D(k, k) is a '1 x 1' diagonal block. 1981 | If ipiv(k) = ipiv(k + 1) < 0, then rows and columns k+1 and 1982 | -ipiv(k) were interchanged and D(k:k+1, k:k+1) is a '2 x 2' 1983 | diagonal block. 1984 | +/ 1985 | Slice!(lapackint*) ipiv; 1986 | /++ 1987 | Return solves a system of linear equations 1988 | \A * X = B, 1989 | using LDL factorization. 1990 | +/ 1991 | auto solve(Flag!"allowDestroy" allowDestroy = No.allowDestroy, SliceKind kindB, size_t N, IteratorB)( 1992 | Slice!(IteratorB, N, kindB) b) 1993 | { 1994 | return ldlSolve!(allowDestroy)(uplo, matrix, ipiv, b); 1995 | } 1996 | } 1997 | 1998 | /++ 1999 | Computes the factorization of a real symmetric matrix A using the 2000 | Bunch-Kaufman diagonal pivoting method. 2001 | The for of the factorization is: 2002 | \A = L*D*L**T 2003 | Where L is product if permutation and unit lower triangular matrices, 2004 | and D is symmetric and block diagonal with '1 x 1' and '2 x 2' 2005 | diagonal blocks. 2006 | Params: 2007 | allowDestroy = flag to delete the source matrix. 2008 | a = input symmetric 'n x n' matrix for factorization. 2009 | uplo = 'U': Upper triangle is stored; 2010 | 'L': lower triangle is stored. 2011 | Returns:$(LREF LDLResult) 2012 | +/ 2013 | auto ldlDecomp(Flag!"allowDestroy" allowDestroy = No.allowDestroy, Iterator, SliceKind kind)( 2014 | char uplo, 2015 | Slice!(Iterator, 2, kind) a) 2016 | in 2017 | { 2018 | assert(a.length!0 == a.length!1, "matrix must be squared"); 2019 | } 2020 | do 2021 | { 2022 | alias T = BlasType!Iterator; 2023 | auto work = [T.sizeof * a.length].uninitSlice!T; 2024 | auto ipiv = a.length.uninitSlice!lapackint; 2025 | auto m = (allowDestroy && a._stride!1 == 1) ? a.assumeCanonical : a.transposed.as!T.slice.canonical; 2026 | 2027 | sytrf!T(uplo, m, ipiv, work); 2028 | return LDLResult!T(uplo, m, ipiv); 2029 | } 2030 | 2031 | /++ 2032 | Solves a system of linear equations \A * X = B with symmetric matrix 'A' using the 2033 | factorization 2034 | \A = U * D * U**T, or 2035 | \A = L * D * L**T 2036 | computed by ldlDecomp. 2037 | Params: 2038 | allowDestroy = flag to delete the source matrix. 2039 | a = 'LD' or 'UD' matrix computed by ldlDecomp. 2040 | ipiv = details of the interchanges and the block structure of D as determined by ldlDecomp. 2041 | b = the right hand side matrix. 2042 | uplo = specifies whether the details of the factorization are stored as an upper or 2043 | lower triangular matrix: 2044 | 'U': Upper triangular, form is \A = U * D * U**T; 2045 | 'L': Lower triangular, form is \A = L * D * L**T. 2046 | Returns: 2047 | The solution matrix. 2048 | +/ 2049 | auto ldlSolve(Flag!"allowDestroy" allowDestroy = No.allowDestroy, SliceKind kindB, size_t N, IteratorB, IteratorA)( 2050 | char uplo, 2051 | Slice!(IteratorA, 2, Canonical) a, 2052 | Slice!(lapackint*) ipiv, 2053 | Slice!(IteratorB, N, kindB) b) 2054 | in 2055 | { 2056 | assert(a.length!0 == a.length!1, "matrix must be squared"); 2057 | assert(ipiv.length == a.length, "size of ipiv must be equal to the number of rows a"); 2058 | assert(a.length!1 == b.length!0, "number of columns a should be equal to the number of rows b"); 2059 | } 2060 | do 2061 | { 2062 | alias A = BlasType!IteratorA; 2063 | alias B = BlasType!IteratorB; 2064 | alias T = CommonType!(A, B); 2065 | static if(is(T* == IteratorA)) 2066 | auto a_ = a; 2067 | else 2068 | auto a_ = a.as!T.slice.canonical; 2069 | 2070 | //convect vector to matrix. 2071 | static if(N == 1) 2072 | auto k = b.sliced(1, b.length); 2073 | else 2074 | auto k = b.transposed; 2075 | 2076 | auto work = [T.sizeof * a.length].uninitSlice!T; 2077 | static if(is(IteratorB == T*)) 2078 | auto m = (allowDestroy && k._stride!1 == 1) ? k.assumeCanonical : k.as!T.slice.canonical; 2079 | else 2080 | auto m = k.as!T.slice.canonical; 2081 | sytrs2!T(a_, m, ipiv, work, uplo); 2082 | return m.transposed; 2083 | } 2084 | 2085 | /// 2086 | unittest 2087 | { 2088 | import mir.ndslice; 2089 | 2090 | auto A = 2091 | [ 2.07, 3.87, 4.20, -1.15, 2092 | 3.87, -0.21, 1.87, 0.63, 2093 | 4.20, 1.87, 1.15, 2.06, 2094 | -1.15, 0.63, 2.06, -1.81 ] 2095 | .sliced(4, 4) 2096 | .as!double.slice 2097 | .canonical; 2098 | 2099 | import mir.random.variable; 2100 | import mir.random.algorithm; 2101 | auto B = randomSlice(uniformVar(-100, 100), 4, 100); 2102 | 2103 | auto LDL = ldlDecomp('L', A); 2104 | auto X = LDL.solve(B); 2105 | 2106 | import mir.math.common: approxEqual; 2107 | import mir.algorithm.iteration: equal; 2108 | alias appr = equal!((a, b) => approxEqual(a, b, 1e-5, 1e-5)); 2109 | assert(appr(mtimes(A, X), B)); 2110 | } 2111 | 2112 | unittest 2113 | { 2114 | auto A = 2115 | [ 9, -1, 2, 2116 | -1, 8, -5, 2117 | 2, -5, 7 ] 2118 | .sliced(3, 3) 2119 | .as!float.slice 2120 | .canonical; 2121 | auto A_ = A.slice; 2122 | 2123 | auto B = 2124 | [ 5, 7, 1, 2125 | 1, 8, 5, 2126 | 9, 3, 2 ] 2127 | .sliced(3, 3) 2128 | .as!double.slice 2129 | .canonical; 2130 | auto B_ = B.slice; 2131 | 2132 | auto LDL = ldlDecomp!(Yes.allowDestroy)('L', A); 2133 | auto X = ldlSolve!(Yes.allowDestroy)(LDL.uplo, A, LDL.ipiv, B.transposed); 2134 | 2135 | import mir.math.common: approxEqual; 2136 | import mir.algorithm.iteration: equal; 2137 | alias appr = equal!((a, b) => approxEqual(a, b, 1e-5, 1e-5)); 2138 | assert(appr(mtimes(A_, X), B_.transposed)); 2139 | } 2140 | 2141 | unittest 2142 | { 2143 | auto A = 2144 | [10, 20, 30, 2145 | 20, 45, 80, 2146 | 30, 80, 171 ] 2147 | .sliced(3, 3) 2148 | .as!double.slice 2149 | .canonical; 2150 | auto B = [ 1, 4, 7 ].sliced(3).as!float.slice.canonical; 2151 | auto B_ = B.sliced(3, 1); 2152 | 2153 | auto LDL = ldlDecomp('L', A); 2154 | auto X = LDL.solve(B); 2155 | 2156 | import mir.math.common: approxEqual; 2157 | import mir.algorithm.iteration: equal; 2158 | alias appr = equal!((a, b) => approxEqual(a, b, 1e-5, 1e-5)); 2159 | assert(appr(mtimes(A, X), B_)); 2160 | } 2161 | 2162 | struct choleskyResult(T) 2163 | { 2164 | /++ 2165 | If uplo = 'L': lower triangle of 'matrix' is stored. 2166 | If uplo = 'U': upper triangle of 'matrix' is stored. 2167 | +/ 2168 | char uplo; 2169 | /++ 2170 | if uplo = 'L', the leading 'N x N' lower triangular part of A 2171 | contains the lower triangular part of the matrix A, and the 2172 | strictly upper triangular part of A is zeroed. 2173 | if uplo = 'U', the leading 'N x N' upper triangular part of A 2174 | contains the upper triangular part of the matrix A, and the 2175 | strictly lower triangular part of A is zeroed. 2176 | +/ 2177 | Slice!(T*, 2, Canonical) matrix; 2178 | /++ 2179 | Return solves a system of linear equations 2180 | \A * X = B, 2181 | using Cholesky factorization. 2182 | +/ 2183 | auto solve(Flag!"allowDestroy" allowDestroy = No.allowDestroy, 2184 | Iterator, size_t N, SliceKind kind) 2185 | (Slice!(Iterator, N, kind) b) 2186 | { 2187 | return choleskySolve!(allowDestroy)(uplo, matrix, b); 2188 | } 2189 | } 2190 | /++ 2191 | Computs Cholesky decomposition of symmetric positive definite matrix 'A'. 2192 | The factorization has the form: 2193 | \A = U**T * U, if UPLO = 'U', or 2194 | \A = L * L**T, if UPLO = 'L' 2195 | Where U is an upper triangular matrix and L is lower triangular. 2196 | Params: 2197 | allowDestroy = flag to delete the source matrix. 2198 | a = symmetric 'N x N' matrix. 2199 | uplo = if uplo is Upper, then upper triangle of A is stored, else 2200 | lower. 2201 | Returns: $(LREF choleskyResult) 2202 | +/ 2203 | auto choleskyDecomp( Flag!"allowDestroy" allowDestroy = No.allowDestroy, Iterator, SliceKind kind)( 2204 | char uplo, 2205 | Slice!(Iterator, 2, kind) a) 2206 | in 2207 | { 2208 | assert(uplo == 'L' || uplo == 'U'); 2209 | assert(a.length!0 == a.length!1, "matrix must be squared"); 2210 | } 2211 | do 2212 | { 2213 | import mir.conv: to; 2214 | alias T = BlasType!Iterator; 2215 | static if(is(Iterator == T*)) 2216 | auto m = (allowDestroy && a._stride!1 == 1) ? a.assumeCanonical : a.as!T.slice.canonical; 2217 | else 2218 | auto m = a.as!T.slice.canonical; 2219 | if (auto info = potrf!T(uplo == 'U' ? 'L' : 'U', m)) 2220 | throw new Exception("Leading minor of order " ~ info.to!string ~ " is not positive definite, and the factorization could not be completed."); 2221 | import mir.algorithm.iteration: eachUploPair; 2222 | auto d = m.universal; 2223 | if (uplo == 'U') 2224 | d = d.transposed; 2225 | d.eachUploPair!("a = 0"); 2226 | return choleskyResult!T(uplo, m); 2227 | } 2228 | 2229 | /// 2230 | unittest 2231 | { 2232 | import mir.algorithm.iteration: equal, eachUploPair; 2233 | import mir.ndslice; 2234 | import mir.random.algorithm; 2235 | import mir.random.variable; 2236 | import mir.math.common: approxEqual; 2237 | 2238 | auto A = 2239 | [ 25, double.nan, double.nan, 2240 | 15, 18, double.nan, 2241 | -5, 0, 11 ] 2242 | .sliced(3, 3); 2243 | 2244 | auto B = randomSlice(uniformVar(-100, 100), 3, 100); 2245 | 2246 | auto C = choleskyDecomp('L', A); 2247 | auto X = C.solve(B); 2248 | 2249 | A.eachUploPair!"a = b"; 2250 | assert(equal!approxEqual(mtimes(A, X), B)); 2251 | } 2252 | 2253 | /++ 2254 | Solves a system of linear equations A * X = B with a symmetric matrix A using the 2255 | Cholesky factorization: 2256 | \A = U**T * U or 2257 | \A = L * L**T 2258 | computed by choleskyDecomp. 2259 | Params: 2260 | allowDestroy = flag to delete the source matrix. 2261 | c = the triangular factor 'U' or 'L' from the Cholesky factorization 2262 | \A = U**T * U or 2263 | \A = L * L**T, 2264 | as computed by choleskyDecomp. 2265 | b = the right hand side matrix. 2266 | uplo = 'U': Upper triangle of A is stored; 2267 | 'L': Lower triangle of A is stored. 2268 | Returns: 2269 | The solution matrix X. 2270 | +/ 2271 | auto choleskySolve(Flag!"allowDestroy" allowDestroy = No.allowDestroy, SliceKind kindB, size_t N, IteratorB, IteratorC)( 2272 | char uplo, 2273 | Slice!(IteratorC, 2, Canonical) c, 2274 | Slice!(IteratorB, N, kindB) b) 2275 | in 2276 | { 2277 | assert(uplo == 'L' || uplo == 'U'); 2278 | assert(c.length!0 == c.length!1, "matrix must be squared"); 2279 | assert(c.length!1 == b.length!0, "number of columns a should be equal to the number of rows b"); 2280 | } 2281 | do 2282 | { 2283 | uplo = uplo == 'U' ? 'L' : 'U'; 2284 | alias B = BlasType!IteratorB; 2285 | alias C = BlasType!IteratorC; 2286 | alias T = CommonType!(B, C); 2287 | static if(is(T* == IteratorC)) 2288 | auto c_ = c; 2289 | else 2290 | auto c_ = c.as!T.slice.canonical; 2291 | 2292 | //convect vector to matrix. 2293 | static if(N == 1) 2294 | auto k = b.sliced(1, b.length); 2295 | else 2296 | auto k = b.transposed; 2297 | 2298 | static if(is(IteratorB == T*)) 2299 | auto m = (allowDestroy && k._stride!1 == 1) ? k.assumeCanonical : k.as!T.slice.canonical; 2300 | else 2301 | auto m = k.as!T.slice.canonical; 2302 | potrs!T(uplo, c_, m); 2303 | return m.transposed; 2304 | } 2305 | 2306 | /// 2307 | unittest 2308 | { 2309 | import mir.ndslice.slice: sliced; 2310 | import mir.ndslice.topology: as; 2311 | import std.typecons: Flag, Yes; 2312 | 2313 | auto A = 2314 | [ 1.0, 1, 3, 2315 | 1 , 5, 5, 2316 | 3 , 5, 19 ].sliced(3, 3); 2317 | 2318 | auto B = [ 10.0, 157, 80 ].sliced; 2319 | auto C_ = B.dup.sliced(3, 1); 2320 | 2321 | auto C = choleskyDecomp('U', A); 2322 | auto X = choleskySolve!(Yes.allowDestroy)(C.uplo, C.matrix, B); 2323 | 2324 | import mir.math.common: approxEqual; 2325 | import mir.algorithm.iteration: equal; 2326 | alias appr = equal!((a, b) => approxEqual(a, b, 1e-5, 1e-5)); 2327 | assert(appr(mtimes(A, X), C_)); 2328 | } 2329 | 2330 | unittest 2331 | { 2332 | auto A = 2333 | [6.0f, 15, 55, 2334 | 15, 55, 225, 2335 | 55, 225, 979 ].sliced(3, 3).canonical; 2336 | auto B = 2337 | [ 7.0, 3, 2338 | 2, 1, 2339 | 1, 8 ].sliced(3, 2).universal; 2340 | 2341 | auto C = choleskyDecomp('L', A); 2342 | auto X = choleskySolve(C.uplo, C.matrix, B); 2343 | 2344 | import mir.math.common: approxEqual; 2345 | import mir.algorithm.iteration: equal; 2346 | alias appr = equal!((a, b) => approxEqual(a, b, 1e-5, 1e-5)); 2347 | 2348 | assert(appr(mtimes(A, X), B)); 2349 | } 2350 | 2351 | 2352 | /// 2353 | struct QRResult(T) 2354 | { 2355 | /++ 2356 | Matrix in witch the elements on and above the diagonal of the array contain the min(M, N) x N 2357 | upper trapezoidal matrix 'R' (R is upper triangular if m >= n). The elements below the 2358 | diagonal, with the array tau, represent the orthogonal matrix 'Q' as product of min(m, n). 2359 | +/ 2360 | Slice!(T*, 2, Canonical) matrix; 2361 | ///The scalar factors of the elementary reflectors 2362 | Slice!(T*) tau; 2363 | /++ 2364 | Solve the least squares problem: 2365 | \min ||A * X - B|| 2366 | Using the QR factorization: 2367 | \A = Q * R 2368 | computed by qrDecomp. 2369 | +/ 2370 | auto solve(Flag!"allowDestroy" allowDestroy = No.allowDestroy, 2371 | Iterator, size_t N, SliceKind kind) 2372 | (Slice!(Iterator, N, kind) b) 2373 | { 2374 | return qrSolve!(allowDestroy)(matrix, tau, b); 2375 | } 2376 | 2377 | /++ 2378 | Extract the Q matrix 2379 | +/ 2380 | auto Q(Flag!"allowDestroy" allowDestroy = No.allowDestroy) 2381 | { 2382 | auto work = [matrix.length].uninitSlice!T; 2383 | 2384 | auto m = (allowDestroy && matrix._stride!1 == 1) ? matrix.assumeCanonical : matrix.as!T.slice.canonical; 2385 | 2386 | static if(is(T == double) || is(T == float)) 2387 | orgqr!T(m, tau, work); 2388 | else 2389 | ungqr!T(m, tau, work); 2390 | return m.transposed; 2391 | } 2392 | 2393 | /++ 2394 | Extract the R matrix 2395 | +/ 2396 | auto R() 2397 | { 2398 | import mir.algorithm.iteration: eachLower; 2399 | 2400 | auto r = [tau.length, tau.length].uninitSlice!T; 2401 | if (matrix.shape[0] == matrix.shape[1]) { 2402 | r[] = matrix.transposed.slice; 2403 | } else { 2404 | r[] = matrix[0..tau.length, 0..tau.length].transposed.slice; 2405 | } 2406 | r.eachLower!((ref a) {a = cast(T)0;}); 2407 | return r.universal; 2408 | } 2409 | 2410 | /++ 2411 | Reconstruct the original matrix given a QR decomposition 2412 | +/ 2413 | auto reconstruct() 2414 | { 2415 | auto q = Q(); 2416 | auto r = R(); 2417 | return reconstruct(q, r); 2418 | } 2419 | 2420 | /++ 2421 | Reconstruct the original matrix given a QR decomposition 2422 | +/ 2423 | auto reconstruct(T, U)(T q, U r) 2424 | if (isMatrix!T && isMatrix!U) 2425 | { 2426 | return mtimes(q, r).universal; 2427 | } 2428 | } 2429 | 2430 | /// 2431 | unittest 2432 | { 2433 | import mir.ndslice; 2434 | 2435 | auto A = 2436 | [ 1, 1, 0, 2437 | 1, 0, 1, 2438 | 0, 1, 1 ] 2439 | .sliced(3, 3) 2440 | .as!double.slice; 2441 | 2442 | auto Q_test = 2443 | [ -0.7071068, 0.4082483, -0.5773503, 2444 | -0.7071068, -0.4082483, 0.5773503, 2445 | 0, 0.8164966, 0.5773503] 2446 | .sliced(3, 3) 2447 | .as!double.slice; 2448 | 2449 | auto R_test = 2450 | [ -1.414214, -0.7071068, -0.7071068, 2451 | 0, 1.2247449, 0.4082483, 2452 | 0, 0, 1.1547005] 2453 | .sliced(3, 3) 2454 | .as!double.slice; 2455 | 2456 | auto val = qrDecomp(A); 2457 | 2458 | //saving these values to doublecheck they don't change later 2459 | auto val_matrix = val.matrix.slice; 2460 | auto val_tau = val.tau.slice; 2461 | 2462 | import mir.math.common: approxEqual; 2463 | import mir.ndslice : equal; 2464 | 2465 | auto r = val.R; 2466 | assert(equal!approxEqual(val.R, R_test)); 2467 | 2468 | auto q = val.Q; 2469 | assert(equal!approxEqual(val.Q, Q_test)); 2470 | 2471 | //double-checking values do not change 2472 | assert(equal!approxEqual(val_matrix, val.matrix)); 2473 | assert(equal!approxEqual(val_tau, val.tau)); 2474 | 2475 | auto a = val.reconstruct; 2476 | assert(equal!approxEqual(A, a)); 2477 | } 2478 | 2479 | unittest 2480 | { 2481 | auto A = 2482 | [ 3, -6, 2483 | 4, -8, 2484 | 0, 1] 2485 | .sliced(3, 2) 2486 | .as!double.slice; 2487 | 2488 | auto Q_check = 2489 | [ -0.6, 0, 2490 | -0.8, 0, 2491 | 0.0, -1] 2492 | .sliced(3, 2) 2493 | .as!double.slice; 2494 | 2495 | auto R_check = 2496 | [ -5, 10, 2497 | 0, -1] 2498 | .sliced(2, 2) 2499 | .as!double.slice; 2500 | 2501 | auto C = qrDecomp(A); 2502 | auto q = C.Q; 2503 | auto r = C.R; 2504 | auto A_reconstructed = C.reconstruct(q, r); 2505 | 2506 | import mir.math.common: approxEqual; 2507 | import mir.algorithm.iteration: equal; 2508 | alias appr = equal!((a, b) => approxEqual(a, b, 1e-5, 1e-5)); 2509 | assert(appr(q, Q_check)); 2510 | assert(equal!approxEqual(r, R_check)); 2511 | assert(equal!approxEqual(A_reconstructed, A)); 2512 | } 2513 | 2514 | /++ 2515 | Computes a QR factorization of matrix 'a'. 2516 | Params: 2517 | allowDestroy = flag to delete the source matrix. 2518 | a = initial matrix 2519 | Returns: $(LREF QRResult) 2520 | +/ 2521 | auto qrDecomp(Flag!"allowDestroy" allowDestroy = No.allowDestroy, 2522 | Iterator, SliceKind kind) 2523 | (Slice!(Iterator, 2, kind) a) 2524 | { 2525 | alias T = BlasType!Iterator; 2526 | auto work = [T.sizeof * a.length].uninitSlice!T; 2527 | auto tau = (cast(int) min(a.length!0, a.length!1)).uninitSlice!T; 2528 | auto m = (allowDestroy && a._stride!1 == 1) ? a.assumeCanonical : a.transposed.as!T.slice.canonical; 2529 | 2530 | geqrf!T(m, tau, work); 2531 | return QRResult!T(m, tau); 2532 | } 2533 | 2534 | /++ 2535 | Solve the least squares problem: 2536 | \min ||A * X - B|| 2537 | Using the QR factorization: 2538 | \A = Q * R 2539 | computed by qrDecomp. 2540 | Params: 2541 | allowDestroy = flag to delete the source matrix. 2542 | a = detalis of the QR factorization of the original matrix as returned by qrDecomp. 2543 | tau = details of the orhtogonal matrix Q. 2544 | b = right hand side matrix. 2545 | Returns: solution matrix. 2546 | +/ 2547 | auto qrSolve(Flag!"allowDestroy" allowDestroy = No.allowDestroy, 2548 | SliceKind kindB, size_t N, IteratorB, IteratorA, IteratorT) 2549 | (Slice!(IteratorA, 2, Canonical) a, 2550 | Slice!(IteratorT) tau, 2551 | Slice!(IteratorB, N, kindB) b 2552 | ) 2553 | in 2554 | { 2555 | assert(a.length!1 == b.length!0, "number of columns a should be equal to the number of rows b"); 2556 | } 2557 | do 2558 | { 2559 | alias A = BlasType!IteratorA; 2560 | alias B = BlasType!IteratorB; 2561 | alias T = CommonType!(A, B); 2562 | static if(is(T* == IteratorA)) 2563 | auto a_ = a; 2564 | else 2565 | auto a_ = a.as!T.slice.canonical; 2566 | static if(is(T* == IteratorT)) 2567 | auto tau_ = tau; 2568 | else 2569 | auto tau_ = tau.as!T.slice.canonical; 2570 | 2571 | //convect vector to matrix. 2572 | static if(N == 1) 2573 | auto k = b.sliced(1, b.length); 2574 | else 2575 | auto k = b.transposed; 2576 | 2577 | static if(is(IteratorB == T*)) 2578 | auto m = (allowDestroy && k._stride!1 == 1) ? k.assumeCanonical : k.as!T.slice.canonical; 2579 | else 2580 | auto m = k.as!T.slice.canonical; 2581 | auto work = [m.length!0].uninitSlice!T; 2582 | static if(is(T == double) || is(T == float)) 2583 | ormqr!T('L', 'T', a_, tau_, m, work); 2584 | else 2585 | unmqr!T('L', 'C', a_, tau_, m, work); 2586 | 2587 | if (a_.length!0 != a_.length!1) { 2588 | a_ = a_[0..tau.length, 0..tau.length]; 2589 | m = m.selectFront!1(tau.length); 2590 | } 2591 | trsm!T(Side.Right, Uplo.Lower, Diag.NonUnit, cast(T) 1.0, a_, m); 2592 | 2593 | return m.transposed; 2594 | } 2595 | 2596 | /// 2597 | unittest 2598 | { 2599 | import mir.ndslice; 2600 | 2601 | auto A = 2602 | [ 3, 1, -1, 2, 2603 | -5, 1, 3, -4, 2604 | 2, 0, 1, -1, 2605 | 1, -5, 3, -3 ] 2606 | .sliced(4, 4) 2607 | .as!double.slice; 2608 | 2609 | import mir.random.variable; 2610 | import mir.random.algorithm; 2611 | auto B = randomSlice(uniformVar(-100, 100), 4, 100); 2612 | 2613 | auto C = qrDecomp(A); 2614 | auto X = C.solve(B); 2615 | 2616 | import mir.math.common: approxEqual; 2617 | import mir.algorithm.iteration: equal; 2618 | alias appr = equal!((a, b) => approxEqual(a, b, 1e-5, 1e-5)); 2619 | assert(appr(mtimes(A, X), B)); 2620 | } 2621 | 2622 | unittest 2623 | { 2624 | auto A = 2625 | [ 3, 1, -1, 2, 2626 | -5, 1, 3, -4, 2627 | 2, 0, 1, -1, 2628 | 1, -5, 3, -3 ] 2629 | .sliced(4, 4) 2630 | .as!float.slice; 2631 | auto B = [ 6, -12, 1, 3 ].sliced(4).as!double.slice.canonical; 2632 | auto B_ = B.slice.sliced(B.length, 1); 2633 | auto C = qrDecomp(A); 2634 | auto X = qrSolve(C.matrix, C.tau, B); 2635 | 2636 | import mir.math.common: approxEqual; 2637 | import mir.algorithm.iteration: equal; 2638 | alias appr = equal!((a, b) => approxEqual(a, b, 1e-5, 1e-5)); 2639 | assert(appr(mtimes(A, X), B_)); 2640 | } 2641 | 2642 | unittest 2643 | { 2644 | auto A = 2645 | [ 1, 1, 0, 2646 | 1, 0, 1, 2647 | 0, 1, 1 ] 2648 | .sliced(3, 3) 2649 | .as!double.slice; 2650 | 2651 | auto B = 2652 | [ 7, 6, 98, 2653 | 4, 8, 17, 2654 | 5, 3, 24 ] 2655 | .sliced(3, 3) 2656 | .as!float.slice; 2657 | auto C = qrDecomp(A); 2658 | auto X = qrSolve(C.matrix, C.tau, B); 2659 | 2660 | import mir.math.common: approxEqual; 2661 | import mir.algorithm.iteration: equal; 2662 | 2663 | assert(equal!approxEqual(mtimes(A, X), B)); 2664 | } 2665 | 2666 | unittest 2667 | { 2668 | import mir.complex; 2669 | auto A = 2670 | [ 1, 1, 0, 2671 | 1, 0, 1, 2672 | 0, 1, 1 ] 2673 | .sliced(3, 3) 2674 | .as!(Complex!double).slice; 2675 | 2676 | auto B = 2677 | [ 15, 78, 11, 2678 | 21, 47, 71, 2679 | 81, 11, 81 ] 2680 | .sliced(3, 3) 2681 | .as!(Complex!float).slice; 2682 | auto C = qrDecomp(A); 2683 | auto X = qrSolve(C.matrix, C.tau, B); 2684 | auto res = mtimes(A, X); 2685 | 2686 | import mir.algorithm.iteration: equal; 2687 | assert(equal!((a, b) => fabs(a - b) < 1e-12)(res, B)); 2688 | } 2689 | 2690 | unittest 2691 | { 2692 | auto A = 2693 | [ 3, -6, 2694 | 4, -8, 2695 | 0, 1] 2696 | .sliced(3, 2) 2697 | .as!double.slice; 2698 | 2699 | auto B = [-1, 7, 2] 2700 | .sliced(3) 2701 | .as!double.slice.canonical; 2702 | 2703 | auto X_check = [5, 2] 2704 | .sliced(2, 1) 2705 | .as!double.slice; 2706 | 2707 | auto C = qrDecomp(A); 2708 | auto X = qrSolve(C.matrix, C.tau, B); 2709 | 2710 | import mir.math.common: approxEqual; 2711 | import mir.algorithm.iteration: equal; 2712 | alias appr = equal!((a, b) => approxEqual(a, b, 1e-5, 1e-5)); 2713 | assert(appr(X, X_check)); 2714 | } 2715 | -------------------------------------------------------------------------------- /source/kaleidic/lubeck2.d: -------------------------------------------------------------------------------- 1 | /++ 2 | $(H1 Lubeck 2 - `@nogc` Linear Algebra) 3 | +/ 4 | module kaleidic.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 | import mir.complex: Complex; 18 | 19 | /++ 20 | Identity matrix. 21 | 22 | Params: 23 | n = number of columns 24 | m = optional number of rows, default n 25 | Results: 26 | Matrix which is 1 on the diagonal and 0 elsewhere 27 | +/ 28 | @safe pure nothrow @nogc 29 | Slice!(RCI!T, 2) eye(T = double)( 30 | size_t n, 31 | size_t m = 0 32 | ) 33 | if (isFloatingPoint!T || isComplex!T) 34 | in 35 | { 36 | assert(n>0); 37 | assert(m>=0); 38 | } 39 | out (i) 40 | { 41 | assert(i.length!0==n); 42 | assert(i.length!1== (m==0 ? n : m)); 43 | } 44 | do 45 | { 46 | auto c = rcslice!T([n, (m==0?n:m)], cast(T)0); 47 | c.diagonal[] = cast(T)1; 48 | return c; 49 | } 50 | 51 | /// Real numbers 52 | @safe pure nothrow 53 | unittest 54 | { 55 | import mir.ndslice; 56 | import mir.math; 57 | 58 | assert(eye(1)== [ 59 | [1]]); 60 | assert(eye(2)== [ 61 | [1, 0], 62 | [0, 1]]); 63 | assert(eye(3)== [ 64 | [1, 0, 0], 65 | [0, 1, 0], 66 | [0, 0, 1]]); 67 | assert(eye(1,2) == [ 68 | [1,0]]); 69 | assert(eye(2,1) == [ 70 | [1], 71 | [0]]); 72 | } 73 | 74 | /++ 75 | Matrix multiplication. Allocates result to using Mir refcounted arrays. 76 | 77 | This function has multiple overloads that include the following functionality: 78 | - a.mtimes(b) where `a` and `b` are both two-dimensional slices. The result is a 79 | two-dimensional slice. 80 | - a.mtimes(b) where `a` is a two-dimensional slice and `b` is a one-dimensional 81 | slice. The result is a one-dimensional slice. In this case, `b` can be thought 82 | of as a column vector. 83 | - b.mtimes(a) where `a` is a two-dimensional slice and `b` is a one-dimensional 84 | slice. The result is a one-dimensional slice. In this case, `b` can be thought 85 | of as a row vector. 86 | 87 | Params: 88 | a = m(rows) x k(cols) matrix 89 | b = k(rows) x n(cols) matrix 90 | Result: 91 | m(rows) x n(cols) 92 | +/ 93 | @safe pure nothrow @nogc 94 | Slice!(RCI!T, 2) mtimes(T, SliceKind kindA, SliceKind kindB)( 95 | Slice!(const(T)*, 2, kindA) a, 96 | Slice!(const(T)*, 2, kindB) b 97 | ) 98 | if (isFloatingPoint!T || isComplex!T) 99 | in 100 | { 101 | assert(a.length!1 == b.length!0); 102 | } 103 | out (c) 104 | { 105 | assert(c.length!0 == a.length!0); 106 | assert(c.length!1 == b.length!1); 107 | } 108 | do 109 | { 110 | auto c = mininitRcslice!T(a.length!0, b.length!1); 111 | gemm(cast(T)1, a, b, cast(T)0, c.lightScope); 112 | return c; 113 | } 114 | 115 | /// ditto 116 | @safe pure nothrow @nogc 117 | Slice!(RCI!(Unqual!A), 2) mtimes(A, B, SliceKind kindA, SliceKind kindB)( 118 | auto ref const Slice!(RCI!A, 2, kindA) a, 119 | auto ref const Slice!(RCI!B, 2, kindB) b 120 | ) 121 | if (is(Unqual!A == Unqual!B)) 122 | in 123 | { 124 | assert(a.length!1 == b.length!0); 125 | } 126 | do 127 | { 128 | auto scopeA = a.lightScope.lightConst; 129 | auto scopeB = b.lightScope.lightConst; 130 | return .mtimes(scopeA, scopeB); 131 | } 132 | 133 | /// ditto 134 | @safe pure nothrow @nogc 135 | Slice!(RCI!(Unqual!A), 2) mtimes(A, B, SliceKind kindA, SliceKind kindB)( 136 | auto ref const Slice!(RCI!A, 2, kindA) a, 137 | Slice!(const(B)*, 2, kindB) b 138 | ) 139 | if (is(Unqual!A == Unqual!B)) 140 | in 141 | { 142 | assert(a.length!1 == b.length!0); 143 | } 144 | do 145 | { 146 | auto scopeA = a.lightScope.lightConst; 147 | return .mtimes(scopeA, b); 148 | } 149 | 150 | /// ditto 151 | @safe pure nothrow @nogc 152 | Slice!(RCI!(Unqual!A), 2) mtimes(A, B, SliceKind kindA, SliceKind kindB)( 153 | Slice!(const(A)*, 2, kindA) a, 154 | auto ref const Slice!(RCI!B, 2, kindB) b 155 | ) 156 | if (is(Unqual!A == Unqual!B)) 157 | in 158 | { 159 | assert(a.length!1 == b.length!0); 160 | } 161 | do 162 | { 163 | auto scopeB = b.lightScope.lightConst; 164 | return .mtimes(a, scopeB); 165 | } 166 | 167 | /++ 168 | Params: 169 | a = m(rows) x n(cols) matrix 170 | b = n(rows) x 1(cols) vector 171 | Result: 172 | m(rows) x 1(cols) 173 | +/ 174 | @safe pure nothrow @nogc 175 | Slice!(RCI!T, 1) mtimes(T, SliceKind kindA, SliceKind kindB)( 176 | Slice!(const(T)*, 2, kindA) a, 177 | Slice!(const(T)*, 1, kindB) b 178 | ) 179 | if (isFloatingPoint!T || isComplex!T) 180 | in 181 | { 182 | assert(a.length!1 == b.length); 183 | } 184 | out (c) 185 | { 186 | assert(c.length == a.length); 187 | } 188 | do 189 | { 190 | auto c = mininitRcslice!T(a.length!0); 191 | gemv(cast(T)1, a, b, cast(T)0, c.lightScope); 192 | return c; 193 | } 194 | 195 | /// ditto 196 | @safe pure nothrow @nogc 197 | Slice!(RCI!(Unqual!A), 1) mtimes(A, B, SliceKind kindA, SliceKind kindB)( 198 | auto ref const Slice!(RCI!A, 2, kindA) a, 199 | auto ref const Slice!(RCI!B, 1, kindB) b 200 | ) 201 | if (is(Unqual!A == Unqual!B)) 202 | in 203 | { 204 | assert(a.length!1 == b.length); 205 | } 206 | do 207 | { 208 | auto scopeA = a.lightScope.lightConst; 209 | auto scopeB = b.lightScope.lightConst; 210 | return .mtimes(scopeA, scopeB); 211 | } 212 | 213 | /// ditto 214 | @safe pure nothrow @nogc 215 | Slice!(RCI!(Unqual!A), 1) mtimes(A, B, SliceKind kindA, SliceKind kindB)( 216 | auto ref const Slice!(RCI!A, 2, kindA) a, 217 | Slice!(const(B)*, 1, kindB) b 218 | ) 219 | if (is(Unqual!A == Unqual!B)) 220 | in 221 | { 222 | assert(a.length!1 == b.length); 223 | } 224 | do 225 | { 226 | auto scopeA = a.lightScope.lightConst; 227 | return .mtimes(scopeA, b); 228 | } 229 | 230 | /// ditto 231 | @safe pure nothrow @nogc 232 | Slice!(RCI!(Unqual!A), 1) mtimes(A, B, SliceKind kindA, SliceKind kindB)( 233 | Slice!(const(A)*, 2, kindA) a, 234 | auto ref const Slice!(RCI!B, 1, kindB) b 235 | ) 236 | if (is(Unqual!A == Unqual!B)) 237 | in 238 | { 239 | assert(a.length!1 == b.length); 240 | } 241 | do 242 | { 243 | auto scopeB = b.lightScope.lightConst; 244 | return .mtimes(a, scopeB); 245 | } 246 | 247 | /++ 248 | Params: 249 | b = 1(rows) x n(cols) vector 250 | a = n(rows) x m(cols) matrix 251 | Result: 252 | 1(rows) x m(cols) 253 | +/ 254 | @safe pure nothrow @nogc 255 | Slice!(RCI!T, 1) mtimes(T, SliceKind kindA, SliceKind kindB)( 256 | Slice!(const(T)*, 1, kindB) b, 257 | Slice!(const(T)*, 2, kindA) a 258 | ) 259 | if (isFloatingPoint!T || isComplex!T) 260 | in 261 | { 262 | assert(a.length!0 == b.length); 263 | } 264 | out (c) 265 | { 266 | assert(c.length == a.length!1); 267 | } 268 | do 269 | { 270 | return .mtimes(a.universal.transposed, b); 271 | } 272 | 273 | /// ditto 274 | @safe pure nothrow @nogc 275 | Slice!(RCI!(Unqual!A), 1) mtimes(A, B, SliceKind kindA, SliceKind kindB)( 276 | auto ref const Slice!(RCI!B, 1, kindB) b, 277 | auto ref const Slice!(RCI!A, 2, kindA) a 278 | ) 279 | if (is(Unqual!A == Unqual!B)) 280 | in 281 | { 282 | assert(a.length!0 == b.length); 283 | } 284 | do 285 | { 286 | auto scopeA = a.lightScope.lightConst; 287 | auto scopeB = b.lightScope.lightConst; 288 | return .mtimes(scopeB, scopeA); 289 | } 290 | 291 | /// ditto 292 | @safe pure nothrow @nogc 293 | Slice!(RCI!(Unqual!A), 1) mtimes(A, B, SliceKind kindA, SliceKind kindB)( 294 | Slice!(const(B)*, 1, kindB) b, 295 | auto ref const Slice!(RCI!A, 2, kindA) a 296 | ) 297 | if (is(Unqual!A == Unqual!B)) 298 | in 299 | { 300 | assert(a.length!0 == b.length); 301 | } 302 | do 303 | { 304 | auto scopeA = a.lightScope.lightConst; 305 | return .mtimes(b, scopeA); 306 | } 307 | 308 | /// ditto 309 | @safe pure nothrow @nogc 310 | Slice!(RCI!(Unqual!A), 1) mtimes(A, B, SliceKind kindA, SliceKind kindB)( 311 | auto ref const Slice!(RCI!B, 1, kindB) b, 312 | Slice!(const(A)*, 2, kindA) a 313 | ) 314 | if (is(Unqual!A == Unqual!B)) 315 | in 316 | { 317 | assert(a.length!0 == b.length); 318 | } 319 | do 320 | { 321 | auto scopeB = b.lightScope.lightConst; 322 | return .mtimes(scopeB, a); 323 | } 324 | 325 | /++ 326 | Params: 327 | a = 1(rows) x n(cols) vector 328 | b = n(rows) x 1(cols) vector 329 | Result: 330 | dot product 331 | +/ 332 | @safe pure nothrow @nogc 333 | Unqual!A mtimes(A, B, SliceKind kindA, SliceKind kindB)( 334 | Slice!(const(A)*, 1, kindA) a, 335 | Slice!(const(B)*, 1, kindB) b 336 | ) 337 | if (is(Unqual!A == Unqual!B)) 338 | in 339 | { 340 | assert(a.length == b.length); 341 | } 342 | do 343 | { 344 | import mir.blas: dot; 345 | 346 | return dot(a, b); 347 | } 348 | 349 | /// ditto 350 | @safe pure nothrow @nogc 351 | Unqual!A mtimes(A, B, SliceKind kindA, SliceKind kindB)( 352 | auto ref const Slice!(RCI!A, 1, kindA) a, 353 | auto ref const Slice!(RCI!B, 1, kindB) b 354 | ) 355 | if (is(Unqual!A == Unqual!B)) 356 | in 357 | { 358 | assert(a.length == b.length); 359 | } 360 | do 361 | { 362 | auto scopeA = a.lightScope.lightConst; 363 | auto scopeB = b.lightScope.lightConst; 364 | return mtimes(scopeA, scopeB); 365 | } 366 | 367 | /// ditto 368 | @safe pure nothrow @nogc 369 | Unqual!A mtimes(A, B, SliceKind kindA, SliceKind kindB)( 370 | auto ref const Slice!(RCI!A, 1, kindA) a, 371 | Slice!(const(B)*, 1, kindB) b 372 | ) 373 | if (is(Unqual!A == Unqual!B)) 374 | in 375 | { 376 | assert(a.length == b.length); 377 | } 378 | do 379 | { 380 | auto scopeA = a.lightScope.lightConst; 381 | return mtimes(scopeA, b); 382 | } 383 | 384 | /// ditto 385 | @safe pure nothrow @nogc 386 | Unqual!A mtimes(A, B, SliceKind kindA, SliceKind kindB)( 387 | Slice!(const(A)*, 1, kindA) a, 388 | auto ref const Slice!(RCI!B, 1, kindB) b 389 | ) 390 | if (is(Unqual!A == Unqual!B)) 391 | in 392 | { 393 | assert(a.length == b.length); 394 | } 395 | do 396 | { 397 | auto scopeB = b.lightScope.lightConst; 398 | return mtimes(a, scopeB); 399 | } 400 | 401 | /// Matrix-matrix multiplication (real) 402 | @safe pure nothrow 403 | unittest 404 | { 405 | import mir.ndslice; 406 | import mir.math; 407 | 408 | auto a = mininitRcslice!double(3, 5); 409 | auto b = mininitRcslice!double(5, 4); 410 | 411 | a[] = 412 | [[-5, 1, 7, 7, -4], 413 | [-1, -5, 6, 3, -3], 414 | [-5, -2, -3, 6, 0]]; 415 | 416 | b[] = 417 | [[-5, -3, 3, 1], 418 | [ 4, 3, 6, 4], 419 | [-4, -2, -2, 2], 420 | [-1, 9, 4, 8], 421 | [ 9, 8, 3, -2]]; 422 | 423 | assert(mtimes(a, b) == 424 | [[-42, 35, -7, 77], 425 | [-69, -21, -42, 21], 426 | [ 23, 69, 3, 29]]); 427 | 428 | assert(mtimes(b.transposed, a.transposed) == 429 | [[-42, -69, 23], 430 | [ 35, -21, 69], 431 | [ -7, -42, 3], 432 | [ 77, 21, 29]]); 433 | } 434 | 435 | // test mixed strides 436 | @safe pure nothrow 437 | unittest 438 | { 439 | import mir.ndslice; 440 | import mir.math; 441 | 442 | auto a = mininitRcslice!double(3, 5); 443 | auto b = mininitRcslice!double(5, 4); 444 | 445 | a[] = 446 | [[-5, 1, 7, 7, -4], 447 | [-1, -5, 6, 3, -3], 448 | [-5, -2, -3, 6, 0]]; 449 | 450 | b[] = 451 | [[-5, -3, 3, 1], 452 | [ 4, 3, 6, 4], 453 | [-4, -2, -2, 2], 454 | [-1, 9, 4, 8], 455 | [ 9, 8, 3, -2]]; 456 | 457 | auto at = mininitRcslice!double(5, 3); 458 | at[] = 459 | [[-5, -1, -5], 460 | [ 1, -5, -2], 461 | [ 7, 6, -3], 462 | [ 7, 3, 6], 463 | [-4, -3, 0]]; 464 | assert(mtimes(b.transposed, at) == 465 | [[-42, -69, 23], 466 | [ 35, -21, 69], 467 | [ -7, -42, 3], 468 | [ 77, 21, 29]]); 469 | 470 | auto bt = mininitRcslice!double(4, 5); 471 | bt[] = 472 | [[-5, 4, -4, -1, 9], 473 | [-3, 3, -2, 9, 8], 474 | [ 3, 6, -2, 4, 3], 475 | [ 1, 4, 2, 8, -2]]; 476 | assert(mtimes(bt, a.transposed) == 477 | [[-42, -69, 23], 478 | [ 35, -21, 69], 479 | [ -7, -42, 3], 480 | [ 77, 21, 29]]); 481 | } 482 | 483 | /// Matrix-matrix multiplication (complex) 484 | @safe pure nothrow 485 | unittest 486 | { 487 | import mir.complex; 488 | import mir.ndslice; 489 | import mir.math; 490 | 491 | auto a = mininitRcslice!(Complex!double)(3, 5); 492 | auto b = mininitRcslice!(Complex!double)(5, 4); 493 | 494 | a[] = 495 | [[-5, 1, 7, 7, -4], 496 | [-1, -5, 6, 3, -3], 497 | [-5, -2, -3, 6, 0]]; 498 | 499 | b[] = 500 | [[-5, -3, 3, 1], 501 | [ 4, 3, 6, 4], 502 | [-4, -2, -2, 2], 503 | [-1, 9, 4, 8], 504 | [ 9, 8, 3, -2]]; 505 | 506 | assert(mtimes(a, b) == 507 | [[-42, 35, -7, 77], 508 | [-69, -21, -42, 21], 509 | [ 23, 69, 3, 29]]); 510 | } 511 | 512 | /// Matrix-matrix multiplication, specialization for MxN times Nx1 513 | @safe pure nothrow @nogc 514 | unittest 515 | { 516 | import mir.algorithm.iteration: equal; 517 | import mir.ndslice.allocation: mininitRcslice; 518 | 519 | static immutable a = [[3.0, 5, 2, -3], [-2.0, 2, 3, 10], [0.0, 2, 1, 1]]; 520 | static immutable b = [2.0, 3, 4, 5]; 521 | static immutable c = [14.0, 64, 15]; 522 | 523 | auto X = mininitRcslice!double(3, 4); 524 | auto y = mininitRcslice!double(4); 525 | auto result = mininitRcslice!double(3); 526 | 527 | X[] = a; 528 | y[] = b; 529 | result[] = c; 530 | 531 | auto Xy = X.mtimes(y); 532 | assert(Xy.equal(result)); 533 | auto yXT = y.mtimes(X.transposed); 534 | assert(yXT.equal(result)); 535 | } 536 | 537 | /// Reference-counted dot product 538 | @safe pure nothrow @nogc 539 | unittest 540 | { 541 | import mir.ndslice.allocation: mininitRcslice; 542 | 543 | static immutable a = [-5.0, 1, 7, 7, -4]; 544 | static immutable b = [ 4.0, -4, -2, 10, 4]; 545 | 546 | auto x = mininitRcslice!double(5); 547 | auto y = mininitRcslice!double(5); 548 | 549 | x[] = a; 550 | y[] = b; 551 | 552 | assert(x.mtimes(y) == 16); 553 | } 554 | 555 | /// Mix slice & RC dot product 556 | @safe pure nothrow 557 | unittest 558 | { 559 | import mir.ndslice.allocation: mininitRcslice; 560 | import mir.ndslice.slice: sliced; 561 | 562 | static immutable a = [-5.0, 1, 7, 7, -4]; 563 | static immutable b = [ 4.0, -4, -2, 10, 4]; 564 | 565 | auto x = mininitRcslice!double(5); 566 | auto y = b.sliced; 567 | 568 | x[] = a; 569 | 570 | assert(x.mtimes(y) == 16); 571 | assert(y.mtimes(x) == 16); 572 | } 573 | 574 | /++ 575 | Symmetric matrix multiplication. Allocates result to using Mir refcounted arrays. 576 | 577 | Similar to `mtimes`, but allows for the `a` parameter to be symmetric. If 578 | `is(side == Side.Left)` (the default), then the function returns the result of 579 | `a * b`. If `is(side == Side.Right)`, then returns the result of `b * a`. 580 | 581 | Params: 582 | side = controls whether `a` is on the left or right 583 | uplo = controls whether `a` is upper symmetric or lower symmetric 584 | +/ 585 | template mtimesSymmetric(Side side = Side.Left, Uplo uplo = Uplo.Upper) 586 | { 587 | /+ 588 | Params: 589 | a = m(rows) x k(cols) matrix 590 | b = k(rows) x n(cols) matrix 591 | Result: 592 | m(rows) x n(cols) 593 | +/ 594 | Slice!(RCI!T, 2) mtimesSymmetric(T, SliceKind kindA, SliceKind kindB)( 595 | Slice!(const(T)*, 2, kindA) a, 596 | Slice!(const(T)*, 2, kindB) b 597 | ) 598 | if (isFloatingPoint!T) 599 | in 600 | { 601 | assert(a.length!1 == b.length!0); 602 | static if (side == Side.Left) 603 | assert(a.length!0 == a.length!1); 604 | else 605 | assert(b.length!0 == b.length!1); 606 | } 607 | out (c) 608 | { 609 | assert(c.length!0 == a.length!0); 610 | assert(c.length!1 == b.length!1); 611 | } 612 | do 613 | { 614 | auto c = mininitRcslice!T(a.length!0, b.length!1); 615 | static if (side == Side.Left) 616 | symm(side, uplo, cast(T)1, a, b, cast(T)0, c.lightScope); 617 | else 618 | symm(side, uplo, cast(T)1, b, a, cast(T)0, c.lightScope); 619 | return c; 620 | } 621 | 622 | /// ditto 623 | @safe pure nothrow @nogc 624 | Slice!(RCI!(Unqual!A), 2) mtimesSymmetric(A, B, SliceKind kindA, SliceKind kindB)( 625 | auto ref const Slice!(RCI!A, 2, kindA) a, 626 | auto ref const Slice!(RCI!B, 2, kindB) b 627 | ) 628 | if (is(Unqual!A == Unqual!B)) 629 | in 630 | { 631 | assert(a.length!1 == b.length!0); 632 | } 633 | do 634 | { 635 | auto scopeA = a.lightScope.lightConst; 636 | auto scopeB = b.lightScope.lightConst; 637 | return .mtimesSymmetric!(side, uplo)(scopeA, scopeB); 638 | } 639 | 640 | @safe pure nothrow @nogc 641 | Slice!(RCI!(Unqual!A), 2) mtimesSymmetric(A, B, SliceKind kindA, SliceKind kindB)( 642 | auto ref const Slice!(RCI!A, 2, kindA) a, 643 | Slice!(const(B)*, 2, kindB) b 644 | ) 645 | if (is(Unqual!A == Unqual!B)) 646 | in 647 | { 648 | assert(a.length!1 == b.length!0); 649 | } 650 | do 651 | { 652 | auto scopeA = a.lightScope.lightConst; 653 | return .mtimesSymmetric!(side, uplo)(scopeA, b); 654 | } 655 | 656 | /// ditto 657 | @safe pure nothrow @nogc 658 | Slice!(RCI!(Unqual!A), 2) mtimesSymmetric(A, B, SliceKind kindA, SliceKind kindB)( 659 | Slice!(const(A)*, 2, kindA) a, 660 | auto ref const Slice!(RCI!B, 2, kindB) b 661 | ) 662 | if (is(Unqual!A == Unqual!B)) 663 | in 664 | { 665 | assert(a.length!1 == b.length!0); 666 | } 667 | do 668 | { 669 | auto scopeB = b.lightScope.lightConst; 670 | return .mtimesSymmetric!(side, uplo)(a, scopeB); 671 | } 672 | } 673 | 674 | /// ditto, except `side` is inferred in matrix-vector and vector-matrix case 675 | template mtimesSymmetric(Uplo uplo = Uplo.Upper) 676 | { 677 | /++ 678 | Params: 679 | a = m(rows) x n(cols) matrix 680 | b = n(rows) x 1(cols) vector 681 | Result: 682 | m(rows) x 1(cols) 683 | +/ 684 | @safe pure nothrow @nogc 685 | Slice!(RCI!T, 1) mtimesSymmetric(T, SliceKind kindA, SliceKind kindB)( 686 | Slice!(const(T)*, 2, kindA) a, 687 | Slice!(const(T)*, 1, kindB) b 688 | ) 689 | if (isFloatingPoint!T) 690 | in 691 | { 692 | assert(a.length!1 == b.length); 693 | assert(a.length!0 == a.length!1); 694 | } 695 | out (c) 696 | { 697 | assert(c.length == a.length); 698 | } 699 | do 700 | { 701 | auto c = mininitRcslice!T(a.length!0); 702 | symv(uplo, cast(T)1, a, b, cast(T)0, c.lightScope); 703 | return c; 704 | } 705 | 706 | /// ditto 707 | @safe pure nothrow @nogc 708 | Slice!(RCI!(Unqual!A), 1) mtimesSymmetric(A, B, SliceKind kindA, SliceKind kindB)( 709 | auto ref const Slice!(RCI!A, 2, kindA) a, 710 | auto ref const Slice!(RCI!B, 1, kindB) b 711 | ) 712 | if (is(Unqual!A == Unqual!B)) 713 | in 714 | { 715 | assert(a.length!1 == b.length); 716 | } 717 | do 718 | { 719 | auto scopeA = a.lightScope.lightConst; 720 | auto scopeB = b.lightScope.lightConst; 721 | return .mtimesSymmetric!(uplo)(scopeA, scopeB); 722 | } 723 | 724 | /// ditto 725 | @safe pure nothrow @nogc 726 | Slice!(RCI!(Unqual!A), 1) mtimesSymmetric(A, B, SliceKind kindA, SliceKind kindB)( 727 | auto ref const Slice!(RCI!A, 2, kindA) a, 728 | Slice!(const(B)*, 1, kindB) b 729 | ) 730 | if (is(Unqual!A == Unqual!B)) 731 | in 732 | { 733 | assert(a.length!1 == b.length); 734 | } 735 | do 736 | { 737 | auto scopeA = a.lightScope.lightConst; 738 | return .mtimesSymmetric!(uplo)(scopeA, b); 739 | } 740 | 741 | /// ditto 742 | @safe pure nothrow @nogc 743 | Slice!(RCI!(Unqual!A), 1) mtimesSymmetric(A, B, SliceKind kindA, SliceKind kindB)( 744 | Slice!(const(A)*, 2, kindA) a, 745 | auto ref const Slice!(RCI!B, 1, kindB) b 746 | ) 747 | if (is(Unqual!A == Unqual!B)) 748 | in 749 | { 750 | assert(a.length!1 == b.length); 751 | } 752 | do 753 | { 754 | auto scopeB = b.lightScope.lightConst; 755 | return .mtimesSymmetric!(uplo)(a, scopeB); 756 | } 757 | 758 | /++ 759 | Params: 760 | b = 1(rows) x n(cols) vector 761 | a = n(rows) x m(cols) matrix 762 | Result: 763 | 1(rows) x m(cols) 764 | +/ 765 | @safe pure nothrow @nogc 766 | Slice!(RCI!T, 1) mtimesSymmetric(T, SliceKind kindA, SliceKind kindB)( 767 | Slice!(const(T)*, 1, kindB) b, 768 | Slice!(const(T)*, 2, kindA) a 769 | ) 770 | if (isFloatingPoint!T || isComplex!T) 771 | in 772 | { 773 | assert(a.length!0 == b.length); 774 | } 775 | out (c) 776 | { 777 | assert(c.length == a.length!1); 778 | } 779 | do 780 | { 781 | auto c = mininitRcslice!T(b.length); 782 | symv(uplo, cast(T)1, a, b, cast(T)0, c.lightScope); 783 | return c; 784 | } 785 | 786 | /// ditto 787 | @safe pure nothrow @nogc 788 | Slice!(RCI!(Unqual!A), 1) mtimesSymmetric(A, B, SliceKind kindA, SliceKind kindB)( 789 | auto ref const Slice!(RCI!B, 1, kindB) b, 790 | auto ref const Slice!(RCI!A, 2, kindA) a 791 | ) 792 | if (is(Unqual!A == Unqual!B)) 793 | in 794 | { 795 | assert(a.length!0 == b.length); 796 | } 797 | do 798 | { 799 | auto scopeA = a.lightScope.lightConst; 800 | auto scopeB = b.lightScope.lightConst; 801 | return .mtimesSymmetric!(uplo)(scopeB, scopeA); 802 | } 803 | 804 | /// ditto 805 | @safe pure nothrow @nogc 806 | Slice!(RCI!(Unqual!A), 1) mtimesSymmetric(A, B, SliceKind kindA, SliceKind kindB)( 807 | Slice!(const(B)*, 1, kindB) b, 808 | auto ref const Slice!(RCI!A, 2, kindA) a 809 | ) 810 | if (is(Unqual!A == Unqual!B)) 811 | in 812 | { 813 | assert(a.length!0 == b.length); 814 | } 815 | do 816 | { 817 | auto scopeA = a.lightScope.lightConst; 818 | return .mtimesSymmetric!(uplo)(b, scopeA); 819 | } 820 | 821 | /// ditto 822 | @safe pure nothrow @nogc 823 | Slice!(RCI!(Unqual!A), 1) mtimesSymmetric(A, B, SliceKind kindA, SliceKind kindB)( 824 | auto ref const Slice!(RCI!B, 1, kindB) b, 825 | Slice!(const(A)*, 2, kindA) a 826 | ) 827 | if (is(Unqual!A == Unqual!B)) 828 | in 829 | { 830 | assert(a.length!0 == b.length); 831 | } 832 | do 833 | { 834 | auto scopeB = b.lightScope.lightConst; 835 | return .mtimesSymmetric!(uplo)(scopeB, a); 836 | } 837 | } 838 | 839 | /// ditto 840 | template mtimesSymmetric(string side, string uplo = "Upper") 841 | { 842 | mixin("alias mtimesSymmetric = .mtimesSymmetric!(Side." ~ side ~ ", Uplo." ~ uplo ~ ");"); 843 | } 844 | 845 | /// Symmetric Matrix-Matrix multiplication 846 | @safe pure nothrow @nogc 847 | unittest 848 | { 849 | import mir.algorithm.iteration: equal; 850 | import mir.ndslice.allocation: mininitRcslice; 851 | 852 | static immutable a = [[3.0, 5, 2], [5.0, 2, 3], [2.0, 3, 1]]; 853 | static immutable b = [[2.0, 3], [4.0, 3], [0.0, -5]]; 854 | static immutable bt = [[2.0, 4, 0], [3.0, 3, -5]]; 855 | static immutable c = [[26.0, 14], [18.0, 6], [16.0, 10]]; 856 | 857 | auto X = mininitRcslice!double(3, 3); 858 | auto Y = mininitRcslice!double(3, 2); 859 | auto YT = mininitRcslice!double(2, 3); 860 | auto result = mininitRcslice!double(3, 2); 861 | 862 | X[] = a; 863 | Y[] = b; 864 | YT[] = bt; 865 | result[] = c; 866 | 867 | auto XY = X.mtimesSymmetric(Y); 868 | assert(XY.equal(result)); 869 | 870 | auto YTX = YT.mtimesSymmetric!"Right"(X); 871 | assert(YTX.equal(result.transposed)); 872 | // May need to allocate transposed LHS 873 | auto YtransX = Y.transposed.rcslice.mtimesSymmetric!"Right"(X); 874 | assert(YtransX.equal(result.transposed)); 875 | // Can avoid by transposing problem and result 876 | auto YtransX_v2 = X.mtimesSymmetric(Y).transposed; 877 | assert(YtransX_v2.equal(result.transposed)); 878 | 879 | } 880 | 881 | /// Symmetric Matrix, specialization for MxN times Nx1 882 | @safe pure nothrow @nogc 883 | unittest 884 | { 885 | import mir.algorithm.iteration: equal; 886 | import mir.ndslice.allocation: mininitRcslice; 887 | 888 | static immutable a = [[3.0, 5, 2], [5.0, 2, 3], [2.0, 3, 1]]; 889 | static immutable b = [2.0, 3, 4]; 890 | static immutable c = [29, 28, 17]; 891 | 892 | auto X = mininitRcslice!double(3, 3); 893 | auto y = mininitRcslice!double(3); 894 | auto result = mininitRcslice!double(3); 895 | 896 | X[] = a; 897 | y[] = b; 898 | result[] = c; 899 | 900 | auto Xy = X.mtimesSymmetric(y); 901 | assert(Xy.equal(result)); 902 | auto yX = y.mtimesSymmetric(X); 903 | assert(yX.equal(result)); 904 | } 905 | 906 | /// Symmetric Matrix, specialization for MxN times Nx1 (GC version) 907 | @safe pure nothrow 908 | unittest 909 | { 910 | import mir.algorithm.iteration: equal; 911 | import mir.ndslice.allocation: uninitSlice; 912 | 913 | static immutable a = [[3.0, 5, 2], [5.0, 2, 3], [2.0, 3, 1]]; 914 | static immutable b = [2.0, 3, 4]; 915 | static immutable c = [29, 28, 17]; 916 | 917 | auto X = uninitSlice!double(3, 3); 918 | auto y = uninitSlice!double(3); 919 | auto result = uninitSlice!double(3); 920 | 921 | X[] = a; 922 | y[] = b; 923 | result[] = c; 924 | 925 | auto Xy = X.mtimesSymmetric(y); 926 | assert(Xy.equal(result)); 927 | auto yX = y.mtimesSymmetric(X); 928 | assert(yX.equal(result)); 929 | } 930 | 931 | /++ 932 | Solve systems of linear equations AX = B for X. 933 | Computes minimum-norm solution to a linear least squares problem 934 | if A is not a square matrix. 935 | +/ 936 | @safe pure @nogc 937 | Slice!(RCI!T, 2) mldivide (T, SliceKind kindA, SliceKind kindB)( 938 | Slice!(const(T)*, 2, kindA) a, 939 | Slice!(const(T)*, 2, kindB) b, 940 | ) 941 | if (isFloatingPoint!T || isComplex!T) 942 | { 943 | enforce!"mldivide: parameter shapes mismatch"(a.length!0 == b.length!0); 944 | 945 | auto rcat = a.transposed.as!T.rcslice; 946 | auto at = rcat.lightScope.canonical; 947 | auto rcbt = b.transposed.as!T.rcslice; 948 | auto bt = rcbt.lightScope.canonical; 949 | size_t info; 950 | if (a.length!0 == a.length!1) 951 | { 952 | auto rcipiv = at.length.mininitRcslice!lapackint; 953 | auto ipiv = rcipiv.lightScope; 954 | foreach(i; 0 .. ipiv.length) 955 | ipiv[i] = 0; 956 | info = gesv!T(at, ipiv, bt); 957 | //info > 0 means some diagonla elem of U is 0 so no solution 958 | } 959 | else 960 | { 961 | static if(!isComplex!T) 962 | { 963 | size_t liwork = void; 964 | auto lwork = gelsd_wq(at, bt, liwork); 965 | auto rcs = min(at.length!0, at.length!1).mininitRcslice!T; 966 | auto s = rcs.lightScope; 967 | auto rcwork = lwork.rcslice!T; 968 | auto work = rcwork.lightScope; 969 | auto rciwork = liwork.rcslice!lapackint; 970 | auto iwork = rciwork.lightScope; 971 | size_t rank = void; 972 | T rcond = -1; 973 | 974 | info = gelsd!T(at, bt, s, rcond, rank, work, iwork); 975 | //info > 0 means that many components failed to converge 976 | } 977 | else 978 | { 979 | size_t liwork = void; 980 | size_t lrwork = void; 981 | auto lwork = gelsd_wq(at, bt, lrwork, liwork); 982 | auto rcs = min(at.length!0, at.length!1).mininitRcslice!(realType!T); 983 | auto s = rcs.lightScope; 984 | auto rcwork = lwork.rcslice!T; 985 | auto work = rcwork.lightScope; 986 | auto rciwork = liwork.rcslice!lapackint; 987 | auto iwork = rciwork.lightScope; 988 | auto rcrwork = lrwork.rcslice!(realType!T); 989 | auto rwork = rcrwork.lightScope; 990 | size_t rank = void; 991 | realType!T rcond = -1; 992 | 993 | info = gelsd!T(at, bt, s, rcond, rank, work, rwork, iwork); 994 | //info > 0 means that many components failed to converge 995 | } 996 | bt = bt[0 .. $, 0 .. at.length!0]; 997 | } 998 | enforce!"mldivide: some off-diagonal elements of an intermediate bidiagonal form did not converge to zero."(!info); 999 | return bt.transposed.as!T.rcslice; 1000 | } 1001 | 1002 | /// ditto 1003 | @safe pure @nogc 1004 | Slice!(RCI!(Unqual!A), 2) 1005 | mldivide 1006 | (A, B, SliceKind kindA, SliceKind kindB)( 1007 | auto ref const Slice!(RCI!A, 2, kindA) a, 1008 | auto ref const Slice!(RCI!B, 2, kindB) b 1009 | ) 1010 | do 1011 | { 1012 | auto al = a.lightScope.lightConst; 1013 | auto bl = b.lightScope.lightConst; 1014 | return mldivide(al, bl); 1015 | } 1016 | 1017 | /// ditto 1018 | @safe pure @nogc 1019 | Slice!(RCI!(Unqual!A), 1) 1020 | mldivide 1021 | (A, B, SliceKind kindA, SliceKind kindB)( 1022 | auto ref const Slice!(RCI!A, 2, kindA) a, 1023 | auto ref const Slice!(RCI!B, 1, kindB) b 1024 | ) 1025 | do 1026 | { 1027 | auto al = a.lightScope.lightConst; 1028 | auto bl = b.lightScope.lightConst; 1029 | return mldivide(al, bl); 1030 | } 1031 | 1032 | /// ditto 1033 | @safe pure @nogc 1034 | Slice!(RCI!T, 1) mldivide (T, SliceKind kindA, SliceKind kindB)( 1035 | Slice!(const(T)*, 2, kindA) a, 1036 | Slice!(const(T)*, 1, kindB) b, 1037 | ) 1038 | if (isFloatingPoint!T || isComplex!T) 1039 | { 1040 | import mir.ndslice.topology: flattened; 1041 | return mldivide(a, b.sliced(b.length, 1)).flattened; 1042 | } 1043 | 1044 | pure unittest 1045 | { 1046 | import mir.complex; 1047 | auto a = mininitRcslice!double(2, 2); 1048 | a[] = [[2,3], 1049 | [1, 4]]; 1050 | auto res = mldivide(eye(2), a); 1051 | assert(equal!approxEqual(res, a)); 1052 | auto b = mininitRcslice!(Complex!double)(2, 2); 1053 | b[] = [[Complex!double(2, 1),Complex!double(3, 2)], 1054 | [Complex!double(1, 3), Complex!double(4, 4)]]; 1055 | auto cres = mldivide(eye!(Complex!double)(2), b); 1056 | assert(cres == b); 1057 | auto c = mininitRcslice!double(2, 2); 1058 | c[] = [[5,3], 1059 | [2,6]]; 1060 | auto d = mininitRcslice!double(2,1); 1061 | d[] = [[4], 1062 | [1]]; 1063 | auto e = mininitRcslice!double(2,1); 1064 | e[] = [[23], 1065 | [14]]; 1066 | res = mldivide(c, e); 1067 | assert(equal!approxEqual(res, d)); 1068 | } 1069 | 1070 | pure unittest 1071 | { 1072 | import mir.complex; 1073 | import mir.ndslice; 1074 | import mir.math; 1075 | 1076 | auto a = mininitRcslice!double(6, 4); 1077 | a[] = [ 1078 | -0.57, -1.28, -0.39, 0.25, 1079 | -1.93, 1.08, -0.31, -2.14, 1080 | 2.30, 0.24, 0.40, -0.35, 1081 | -1.93, 0.64, -0.66, 0.08, 1082 | 0.15, 0.30, 0.15, -2.13, 1083 | -0.02, 1.03, -1.43, 0.50, 1084 | ].sliced(6, 4); 1085 | 1086 | auto b = mininitRcslice!double(6,1); 1087 | b[] = [ 1088 | -2.67, 1089 | -0.55, 1090 | 3.34, 1091 | -0.77, 1092 | 0.48, 1093 | 4.10, 1094 | ].sliced(6,1); 1095 | 1096 | auto x = mininitRcslice!double(4,1); 1097 | x[] = [ 1098 | 1.5339, 1099 | 1.8707, 1100 | -1.5241, 1101 | 0.0392 1102 | ].sliced(4,1); 1103 | 1104 | auto res = mldivide(a, b); 1105 | assert(equal!((a, b) => fabs(a - b) < 5e-5)(res, x)); 1106 | 1107 | auto ca = mininitRcslice!(Complex!double)(6, 4); 1108 | ca[] = [ 1109 | -0.57, -1.28, -0.39, 0.25, 1110 | -1.93, 1.08, -0.31, -2.14, 1111 | 2.30, 0.24, 0.40, -0.35, 1112 | -1.93, 0.64, -0.66, 0.08, 1113 | 0.15, 0.30, 0.15, -2.13, 1114 | -0.02, 1.03, -1.43, 0.50, 1115 | ].sliced(6, 4); 1116 | 1117 | auto cb = mininitRcslice!(Complex!double)(6,1); 1118 | cb[] = [ 1119 | -2.67, 1120 | -0.55, 1121 | 3.34, 1122 | -0.77, 1123 | 0.48, 1124 | 4.10, 1125 | ].sliced(6,1); 1126 | 1127 | auto cx = mininitRcslice!(Complex!double)(4,1); 1128 | cx[] = [ 1129 | 1.5339, 1130 | 1.8707, 1131 | -1.5241, 1132 | 0.0392 1133 | ].sliced(4,1); 1134 | 1135 | auto cres = mldivide(ca, cb); 1136 | assert(equal!((a, b) => fabs(a - b) < 5e-5)(cres, x)); 1137 | } 1138 | 1139 | /++ 1140 | Solve systems of linear equations AX = I for X, where I is the identity. 1141 | 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. 1142 | Computes minimum-norm solution to a linear least squares problem 1143 | if A is not a square matrix. 1144 | +/ 1145 | @safe pure @nogc Slice!(RCI!A, 2) mlinverse(A, SliceKind kindA)( 1146 | auto ref Slice!(RCI!A, 2, kindA) a 1147 | ) 1148 | { 1149 | auto aScope = a.lightScope.lightConst; 1150 | return mlinverse!A(aScope); 1151 | } 1152 | 1153 | @safe pure @nogc Slice!(RCI!A, 2) mlinverse(A, SliceKind kindA)( 1154 | Slice!(const(A)*, 2, kindA) a 1155 | ) 1156 | { 1157 | auto a_i = a.as!A.rcslice; 1158 | auto a_i_light = a_i.lightScope.canonical; 1159 | auto rcipiv = min(a_i.length!0, a_i.length!1).mininitRcslice!lapackint; 1160 | auto ipiv = rcipiv.lightScope; 1161 | auto info = getrf!A(a_i_light, ipiv); 1162 | if (info == 0) 1163 | { 1164 | auto rcwork = getri_wq!A(a_i_light).mininitRcslice!A; 1165 | auto work = rcwork.lightScope; 1166 | info = getri!A(a_i_light, ipiv, work); 1167 | } 1168 | enforce!"Matrix is not invertible as has zero determinant"(!info); 1169 | return a_i; 1170 | } 1171 | 1172 | pure unittest 1173 | { 1174 | import mir.ndslice; 1175 | import mir.math; 1176 | 1177 | auto a = mininitRcslice!double(2, 2); 1178 | a[] = [[1,0], 1179 | [0,-1]]; 1180 | auto ans = mlinverse!double(a); 1181 | assert(equal!approxEqual(ans, a)); 1182 | } 1183 | 1184 | pure 1185 | unittest 1186 | { 1187 | import mir.ndslice; 1188 | import mir.math; 1189 | 1190 | auto a = mininitRcslice!double(2, 2); 1191 | a[] = [[ 0, 1], 1192 | [-1, 0]]; 1193 | auto aInv = mininitRcslice!double(2, 2); 1194 | aInv[] = [[0, -1], 1195 | [1, 0]]; 1196 | auto ans = a.mlinverse; 1197 | assert(equal!approxEqual(ans, aInv)); 1198 | } 1199 | 1200 | ///Singuar value decomposition result 1201 | struct SvdResult(T) 1202 | { 1203 | /// 1204 | Slice!(RCI!T, 2) u; 1205 | ///Singular Values 1206 | Slice!(RCI!(realType!T)) sigma; 1207 | /// 1208 | Slice!(RCI!T, 2) vt; 1209 | } 1210 | 1211 | /++ 1212 | Computes the singular value decomposition. 1213 | Params: 1214 | a = input `M x N` matrix 1215 | slim = If true the first `min(M,N)` columns of `u` and the first 1216 | `min(M,N)` rows of `vt` are returned in the ndslices `u` and `vt`. 1217 | out result = $(LREF SvdResult). ] 1218 | Returns: error code from CBlas 1219 | +/ 1220 | @safe pure @nogc SvdResult!T svd 1221 | ( 1222 | T, 1223 | string algorithm = "gesvd", 1224 | SliceKind kind, 1225 | )( 1226 | Slice!(const(T)*, 2, kind) a, 1227 | Flag!"slim" slim = No.slim, 1228 | ) 1229 | if (algorithm == "gesvd" || algorithm == "gesdd") 1230 | { 1231 | auto m = cast(lapackint)a.length!1; 1232 | auto n = cast(lapackint)a.length!0; 1233 | 1234 | auto s = mininitRcslice!(realType!T)(min(m, n)); 1235 | auto u = mininitRcslice!T(slim ? s.length : m, m); 1236 | auto vt = mininitRcslice!T(n, slim ? s.length : n); 1237 | 1238 | if (m == 0 || n == 0) 1239 | { 1240 | u.lightScope[] = 0; 1241 | u.lightScope.diagonal[] = 1; 1242 | vt.lightScope[] = 0; 1243 | vt.lightScope.diagonal[] = 1; 1244 | } 1245 | else 1246 | { 1247 | static if (algorithm == "gesvd") 1248 | { 1249 | auto jobu = slim ? 'S' : 'A'; 1250 | auto jobvt = slim ? 'S' : 'A'; 1251 | auto rca_sliced = a.as!T.rcslice; 1252 | auto rca = rca_sliced.lightScope.canonical; 1253 | auto rcwork = gesvd_wq(jobu, jobvt, rca, u.lightScope.canonical, vt.lightScope.canonical).mininitRcslice!T; 1254 | auto work = rcwork.lightScope; 1255 | 1256 | static if(isComplex!T) { 1257 | auto rwork = mininitRcslice!(realType!T)(max(1, 5 * min(m, n))); 1258 | auto info = gesvd!T(jobu, jobvt, rca, s.lightScope, u.lightScope.canonical, vt.lightScope.canonical, work, rwork.lightScope); 1259 | } else { 1260 | auto info = gesvd!T(jobu, jobvt, rca, s.lightScope, u.lightScope.canonical, vt.lightScope.canonical, work); 1261 | } 1262 | } 1263 | else // gesdd 1264 | { 1265 | auto rciwork = mininitRcslice!lapackint(s.length * 8); 1266 | auto iwork = rciwork.lightScope; 1267 | auto jobz = slim ? 'S' : 'A'; 1268 | auto rca_sliced = a.as!T.rcslice; 1269 | auto rca = rca_sliced.lightScope.canonical; 1270 | auto rcwork = gesdd_wq(jobz, rca, u.lightScope, vt.lightScope).minitRcslice!T; 1271 | auto work = rcwork.lightScope; 1272 | 1273 | static if(isComplex!T) { 1274 | auto mx = max(m, n); 1275 | auto mn = min(m, n); 1276 | auto rwork = mininitRcslice!(realType!T)(max(5*mn^^2 + 5*mn, 2*mx*mn + 2*mn^^2 + mn)); 1277 | auto info = gesdd!T(jobz, rca, s.lightScope, u.lightScope, vt.lightScope, work, rwork.lightScope, iwork); 1278 | } else { 1279 | auto info = gesdd!T(jobz, rca, s.lightScope, u.lightScope, vt.lightScope, work, iwork); 1280 | } 1281 | } 1282 | enum msg = (algorithm == "gesvd" ? "svd: DBDSDC did not converge, updating process failed" : "svd: DBDSQR did not converge"); 1283 | enforce!("svd: " ~ msg)(!info); 1284 | } 1285 | return SvdResult!T(vt, s, u); //transposed 1286 | } 1287 | 1288 | @safe pure SvdResult!T svd 1289 | ( 1290 | T, 1291 | string algorithm = "gesvd", 1292 | SliceKind kind, 1293 | )( 1294 | auto ref scope const Slice!(RCI!T,2,kind) matrix, 1295 | Flag!"slim" slim = No.slim 1296 | ) 1297 | { 1298 | auto matrixScope = matrix.lightScope.lightConst; 1299 | return svd!(T, algorithm)(matrixScope, slim); 1300 | } 1301 | 1302 | pure unittest 1303 | { 1304 | import mir.ndslice; 1305 | import mir.math; 1306 | 1307 | auto a = mininitRcslice!double(6, 4); 1308 | a[] = [[7.52, -1.10, -7.95, 1.08], 1309 | [-0.76, 0.62, 9.34, -7.10], 1310 | [5.13, 6.62, -5.66, 0.87], 1311 | [-4.75, 8.52, 5.75, 5.30], 1312 | [1.33, 4.91, -5.49, -3.52], 1313 | [-2.40, -6.77, 2.34, 3.95]]; 1314 | 1315 | auto r1 = a.svd; 1316 | 1317 | auto sigma1 = rcslice!double(a.shape, 0); 1318 | sigma1.diagonal[] = r1.sigma; 1319 | auto m1 = (r1.u).mtimes(sigma1).mtimes(r1.vt); 1320 | assert(equal!((a, b) => fabs(a-b)< 1e-8)(a, m1)); 1321 | 1322 | auto r2 = a.svd; 1323 | 1324 | auto sigma2 = rcslice!double(a.shape, 0.0); 1325 | sigma2.diagonal[] = r2.sigma; 1326 | auto m2 = r2.u.mtimes(sigma2).mtimes(r2.vt); 1327 | 1328 | assert(equal!((a, b) => fabs(a-b)< 1e-8)(a, m2)); 1329 | 1330 | auto r = a.svd(Yes.slim); 1331 | assert(r.u.shape == [6, 4]); 1332 | assert(r.vt.shape == [4, 4]); 1333 | } 1334 | 1335 | pure unittest 1336 | { 1337 | import mir.ndslice; 1338 | import mir.math; 1339 | 1340 | auto a = mininitRcslice!double(6, 4); 1341 | a[] = [[7.52, -1.10, -7.95, 1.08], 1342 | [-0.76, 0.62, 9.34, -7.10], 1343 | [5.13, 6.62, -5.66, 0.87], 1344 | [-4.75, 8.52, 5.75, 5.30], 1345 | [1.33, 4.91, -5.49, -3.52], 1346 | [-2.40, -6.77, 2.34, 3.95]]; 1347 | 1348 | auto r1 = a.svd(No.slim); 1349 | 1350 | auto sigma1 = rcslice!double(a.shape, 0.0); 1351 | sigma1.diagonal[] = r1.sigma; 1352 | auto m1 = r1.u.mtimes(sigma1).mtimes(r1.vt); 1353 | 1354 | assert(equal!((a, b) => fabs(a-b)< 1e-8)(a, m1)); 1355 | } 1356 | 1357 | unittest 1358 | { 1359 | import mir.algorithm.iteration: all; 1360 | 1361 | // empty matrix as input means that u or vt is identity matrix 1362 | auto identity = slice!double([4, 4], 0); 1363 | identity.diagonal[] = 1; 1364 | 1365 | auto a = slice!double(0, 4); 1366 | auto res = a.svd; 1367 | 1368 | import mir.conv: to; 1369 | 1370 | assert(res.u.shape == [0, 0]); 1371 | assert(res.vt.shape == [4, 4]); 1372 | assert(res.vt.all!approxEqual(identity), res.vt.to!string); 1373 | 1374 | auto b = slice!double(4, 0); 1375 | res = b.svd; 1376 | 1377 | assert(res.u.shape == [4, 4]); 1378 | assert(res.vt.shape == [0, 0]); 1379 | 1380 | assert(res.u.all!approxEqual(identity), res.u.to!string); 1381 | } 1382 | 1383 | pure unittest 1384 | { 1385 | import mir.ndslice; 1386 | import mir.math; 1387 | import mir.complex : cabs; 1388 | 1389 | alias C = Complex!double; 1390 | 1391 | auto a = mininitRcslice!C(6, 4); 1392 | a[] = [[7.52, -1.10, -7.95, 1.08], 1393 | [-0.76, 0.62, 9.34, -7.10], 1394 | [5.13, 6.62, -5.66, 0.87], 1395 | [-4.75, 8.52, 5.75, 5.30], 1396 | [1.33, 4.91, -5.49, -3.52], 1397 | [-2.40, -6.77, 2.34, 3.95]]; 1398 | 1399 | auto r1 = a.svd; 1400 | 1401 | auto sigma1 = rcslice!C(a.shape, C(0)); 1402 | sigma1.diagonal[] = r1.sigma; 1403 | auto m1 = (r1.u).mtimes(sigma1).mtimes(r1.vt); 1404 | assert(equal!((a, b) => cabs(a-b)< 1e-8)(a, m1)); 1405 | 1406 | auto r2 = a.svd; 1407 | 1408 | auto sigma2 = rcslice!C(a.shape, C(0)); 1409 | sigma2.diagonal[] = r2.sigma; 1410 | auto m2 = r2.u.mtimes(sigma2).mtimes(r2.vt); 1411 | 1412 | assert(equal!((a, b) => cabs(a-b)< 1e-8)(a, m2)); 1413 | 1414 | auto r = a.svd(Yes.slim); 1415 | assert(r.u.shape == [6, 4]); 1416 | assert(r.vt.shape == [4, 4]); 1417 | } 1418 | 1419 | struct EigenResult(T) 1420 | { 1421 | Slice!(RCI!(complexType!T), 2) eigenvectors; 1422 | Slice!(RCI!(complexType!T)) eigenvalues; 1423 | } 1424 | 1425 | struct QRResult(T) 1426 | { 1427 | Slice!(RCI!T,2) Q; 1428 | Slice!(RCI!T,2) R; 1429 | 1430 | @safe this 1431 | ( 1432 | SliceKind kindA, 1433 | SliceKind kindTau 1434 | )( 1435 | Slice!(RCI!T, 2, kindA) a, 1436 | Slice!(RCI!T, 1, kindTau) tau 1437 | ) 1438 | { 1439 | auto aScope = a.lightScope.lightConst; 1440 | auto tauScope = tau.lightScope.lightConst; 1441 | this(aScope, tauScope); 1442 | } 1443 | 1444 | @safe this 1445 | ( 1446 | SliceKind kindA, 1447 | SliceKind kindTau 1448 | )( 1449 | Slice!(const(T)*, 2, kindA) a, 1450 | Slice!(const(T)*, 1, kindTau) tau 1451 | ) 1452 | { 1453 | R = mininitRcslice!T(a.shape); 1454 | foreach (i; 0 .. R.length!0) 1455 | { 1456 | foreach (j; 0 .. R.length!1) 1457 | { 1458 | if (i >= j) 1459 | { 1460 | R[j, i] = a[i, j]; 1461 | } 1462 | else 1463 | { 1464 | R[j ,i] = cast(T)0; 1465 | } 1466 | } 1467 | } 1468 | auto rcwork = mininitRcslice!T(a.length!0); 1469 | auto work = rcwork.lightScope; 1470 | auto aSliced = a.as!T.rcslice; 1471 | auto aScope = aSliced.lightScope.canonical; 1472 | auto tauSliced = tau.as!T.rcslice; 1473 | auto tauScope = tauSliced.lightScope; 1474 | 1475 | static if(is(T == double) || is(T == float)) 1476 | orgqr!T(aScope, tauScope, work); 1477 | else 1478 | ungqr!T(aScope, tauScope, work); 1479 | 1480 | Q = aScope.transposed.as!T.rcslice; 1481 | } 1482 | } 1483 | 1484 | @safe pure QRResult!T qr(T, SliceKind kind)( 1485 | auto ref const Slice!(RCI!T, 2, kind) matrix 1486 | ) 1487 | { 1488 | auto matrixScope = matrix.lightScope.lightConst; 1489 | return qr!(T, kind)(matrixScope); 1490 | } 1491 | 1492 | @safe pure QRResult!T qr(T, SliceKind kind)( 1493 | auto ref const Slice!(const(T)*, 2, kind) matrix 1494 | ) 1495 | { 1496 | auto a = matrix.transposed.as!T.rcslice; 1497 | auto tau = mininitRcslice!T(a.length!0); 1498 | auto rcwork = mininitRcslice!T(a.length!0); 1499 | auto work = rcwork.lightScope; 1500 | auto aScope = a.lightScope.canonical; 1501 | auto tauScope = tau.lightScope; 1502 | geqrf!T(aScope, tauScope, work); 1503 | return QRResult!T(aScope, tauScope); 1504 | } 1505 | 1506 | pure nothrow 1507 | unittest 1508 | { 1509 | import mir.ndslice; 1510 | import mir.math; 1511 | 1512 | auto data = mininitRcslice!double(3, 3); 1513 | data[] = [[12, -51, 4], 1514 | [ 6, 167, -68], 1515 | [-4, 24, -41]]; 1516 | 1517 | auto res = qr(data); 1518 | auto q = mininitRcslice!double(3, 3); 1519 | q[] = [[-6.0/7.0, 69.0/175.0, 58.0/175.0], 1520 | [-3.0/7.0, -158.0/175.0, -6.0/175.0], 1521 | [ 2.0/7.0, -6.0/35.0 , 33.0/35.0 ]]; 1522 | auto aE = function (double x, double y) => approxEqual(x, y, 0.00005, 0.00005); 1523 | auto r = mininitRcslice!double(3, 3); 1524 | r[] = [[-14, -21, 14], 1525 | [ 0, -175, 70], 1526 | [ 0, 0, -35]]; 1527 | assert(equal!approxEqual(mtimes(res.Q, res.R), data)); 1528 | } 1529 | 1530 | pure nothrow 1531 | unittest 1532 | { 1533 | import mir.ndslice; 1534 | import mir.math; 1535 | import mir.complex.math: approxEqual; 1536 | 1537 | auto data = mininitRcslice!(Complex!double)(3, 3); 1538 | data[] = [[12, -51, 4], 1539 | [ 6, 167, -68], 1540 | [-4, 24, -41]]; 1541 | 1542 | auto res = qr(data); 1543 | auto q = mininitRcslice!(Complex!double)(3, 3); 1544 | q[] = [[-6.0/7.0, 69.0/175.0, 58.0/175.0], 1545 | [-3.0/7.0, -158.0/175.0, -6.0/175.0], 1546 | [ 2.0/7.0, -6.0/35.0 , 33.0/35.0 ]]; 1547 | auto aE = function (Complex!double x, Complex!double y) => approxEqual(x, y, 0.00005, 0.00005); 1548 | auto r = mininitRcslice!(Complex!double)(3, 3); 1549 | r[] = [[-14, -21, 14], 1550 | [ 0, -175, 70], 1551 | [ 0, 0, -35]]; 1552 | assert(equal!approxEqual(mtimes(res.Q, res.R), data)); 1553 | } 1554 | 1555 | 1556 | @safe pure @nogc 1557 | EigenResult!(realType!T) eigen(T, SliceKind kind)( 1558 | auto ref Slice!(const(T)*,2, kind) a, 1559 | ) 1560 | { 1561 | enforce!"eigen: input matrix must be square"(a.length!0 == a.length!1); 1562 | const n = a.length; 1563 | auto rcw = n.mininitRcslice!(complexType!T); 1564 | auto w = rcw.lightScope; 1565 | auto rcwork = mininitRcslice!T(16 * n); 1566 | auto work = rcwork.lightScope; 1567 | auto z = [n, n].mininitRcslice!(complexType!T);//vl (left eigenvectors) 1568 | auto rca = a.transposed.as!T.rcslice; 1569 | auto as = rca.lightScope; 1570 | static if (isComplex!T) 1571 | { 1572 | auto rcrwork = [2 * n].mininitRcslice!(realType!T); 1573 | auto rwork = rcrwork.lightScope; 1574 | auto info = geev!(T, realType!T)('N', 'V', as.canonical, w, Slice!(T*, 2, Canonical).init, z.lightScope.canonical, work, rwork); 1575 | enforce!"eigen failed"(!info); 1576 | } 1577 | else 1578 | { 1579 | alias C = complexType!T; 1580 | auto wr = sliced((cast(T[]) w.field)[0 .. n]); 1581 | auto wi = sliced((cast(T[]) w.field)[n .. n * 2]); 1582 | auto rczr = [n, n].mininitRcslice!T; 1583 | auto zr = rczr.lightScope; 1584 | auto info = geev!T('N', 'V', as.canonical, wr, wi, Slice!(T*, 2, Canonical).init, zr.canonical, work); 1585 | enforce!"eigen failed"(!info); 1586 | work[0 .. n] = wr; 1587 | work[n .. n * 2] = wi; 1588 | foreach (i, ref e; w.field) 1589 | { 1590 | e = C(work[i], work[n + i]); 1591 | auto zi = z.lightScope[i]; 1592 | if (e.im > 0) 1593 | zi[] = zip(zr[i], zr[i + 1]).map!((a, b) => C(a, b)); 1594 | else 1595 | if (e.im < 0) 1596 | zi[] = zip(zr[i - 1], zr[i]).map!((a, b) => C(a, -b)); 1597 | else 1598 | zi[] = zr[i].as!C; 1599 | } 1600 | } 1601 | return typeof(return)(z, rcw); 1602 | } 1603 | 1604 | //@safe pure @nogc 1605 | EigenResult!(realType!T) eigen(T, SliceKind kind)( 1606 | auto ref Slice!(RCI!T,2, kind) a 1607 | ) 1608 | { 1609 | auto as = a.lightScope.lightConst; 1610 | return eigen(as); 1611 | } 1612 | 1613 | /// 1614 | // pure 1615 | unittest 1616 | { 1617 | import mir.blas; 1618 | import mir.complex; 1619 | import mir.ndslice; 1620 | import mir.math; 1621 | import mir.complex.math: approxEqual; 1622 | 1623 | auto data = 1624 | [[ 0, 1], 1625 | [-1, 0]].fuse.as!double.rcslice; 1626 | auto eigenvalues = [Complex!double(0, 1), Complex!double(0, -1)].sliced; 1627 | auto eigenvectors = 1628 | [[Complex!double(0, -1), Complex!double(1, 0)], 1629 | [Complex!double(0, 1), Complex!double(1, 0)]].fuse; 1630 | 1631 | auto res = data.eigen; 1632 | 1633 | assert(res.eigenvalues.equal!approxEqual(eigenvalues)); 1634 | 1635 | import mir.math.common: approxEqual; 1636 | foreach (i; 0 .. eigenvectors.length) 1637 | assert((res.eigenvectors.lightScope[i] / eigenvectors[i]).diff.slice.nrm2.approxEqual(0)); 1638 | } 1639 | 1640 | 1641 | /// 1642 | @safe pure 1643 | unittest 1644 | { 1645 | import mir.complex; 1646 | import mir.ndslice; 1647 | import mir.math; 1648 | import mir.blas; 1649 | import mir.math.common: fapproxEqual = approxEqual; 1650 | import mir.complex.math: approxEqual; 1651 | 1652 | auto data = 1653 | [[0, 1, 0], 1654 | [0, 0, 1], 1655 | [1, 0, 0]].fuse.as!double.rcslice; 1656 | auto c = 3.0.sqrt; 1657 | auto eigenvalues = [Complex!double(-1, c) / 2, Complex!double(-1, - c) / 2, Complex!double(1, 0)]; 1658 | 1659 | auto eigenvectors = 1660 | [[Complex!double(-1, +c), Complex!double(-1, -c) , Complex!double(2, 0)], 1661 | [Complex!double(-1, -c), Complex!double(-1, +c) , Complex!double(2, 0)], 1662 | [Complex!double(1, 0), Complex!double(1, 0), Complex!double(1, 0)]].fuse; 1663 | 1664 | auto res = data.eigen; 1665 | 1666 | assert(res.eigenvalues.equal!approxEqual(eigenvalues)); 1667 | foreach (i; 0 .. eigenvectors.length) 1668 | assert((res.eigenvectors.lightScope[i] / eigenvectors[i]).diff.slice.nrm2.fapproxEqual(0)); 1669 | 1670 | auto cdata = data.lightScope.as!(Complex!double).rcslice; 1671 | res = cdata.eigen; 1672 | 1673 | assert(res.eigenvalues.equal!approxEqual(eigenvalues)); 1674 | foreach (i; 0 .. eigenvectors.length) 1675 | assert((res.eigenvectors.lightScope[i] / eigenvectors[i]).diff.slice.nrm2.fapproxEqual(0)); 1676 | } 1677 | 1678 | 1679 | /// Principal component analysis result. 1680 | struct PcaResult(T) 1681 | { 1682 | /// Eigenvectors (Eigenvectors[i] is the ith eigenvector) 1683 | Slice!(RCI!T,2) eigenvectors; 1684 | /// Principal component scores. (Input matrix rotated to basis of eigenvectors) 1685 | Slice!(RCI!T, 2) scores; 1686 | /// Principal component variances. (Eigenvalues) 1687 | Slice!(RCI!T) eigenvalues; 1688 | /// The means of each data column (0 if not centred) 1689 | Slice!(RCI!T) mean; 1690 | /// The standard deviations of each column (1 if not normalized) 1691 | Slice!(RCI!T) stdDev; 1692 | /// Principal component Loadings vectors (Eigenvectors times sqrt(Eigenvalue)) 1693 | Slice!(RCI!T, 2) loadings() 1694 | { 1695 | auto result = mininitRcslice!T(eigenvectors.shape); 1696 | for (size_t i = 0; i < eigenvectors.length!0 && i < eigenvalues.length; i++){ 1697 | if(eigenvalues[i] != 0) 1698 | foreach (j; 0 .. eigenvectors.length!1) 1699 | result[i, j] = eigenvectors[i, j] * sqrt(eigenvalues[i]); 1700 | else 1701 | result[i][] = eigenvectors[i][]; 1702 | } 1703 | return result; 1704 | } 1705 | 1706 | Slice!(RCI!T, 2) loadingsScores() {// normalized data in basis of {sqrt(eval)*evect} 1707 | //if row i is a vector p, the original data point is mean[] + p[] * stdDev[] 1708 | auto result = mininitRcslice!T(scores.shape); 1709 | foreach (i; 0 .. scores.length!0){ 1710 | for (size_t j=0; j < scores.length!1 && j < eigenvalues.length; j++) 1711 | { 1712 | if(eigenvalues[j] != 0) 1713 | result[i, j] = scores[i, j] / sqrt(eigenvalues[j]); 1714 | else 1715 | result[i, j] = scores[i, j]; 1716 | } 1717 | } 1718 | return result; 1719 | } 1720 | 1721 | Slice!(RCI!T) explainedVariance() { 1722 | import mir.math.sum: sum; 1723 | //auto totalVariance = eigenvalues.sum!"kb2"; 1724 | auto totalVariance = 0.0; 1725 | foreach (val; eigenvalues) 1726 | totalVariance += val; 1727 | auto result = mininitRcslice!double(eigenvalues.shape); 1728 | foreach (i; 0 .. result.length!0) 1729 | result[i] = eigenvalues[i] / totalVariance; 1730 | return result; 1731 | } 1732 | 1733 | Slice!(RCI!T,2) q() 1734 | { 1735 | return eigenvectors; 1736 | } 1737 | 1738 | //returns matrix to transform into basis of 'n' most significatn eigenvectors 1739 | Slice!(RCI!(T),2) q(size_t n) 1740 | in { 1741 | assert(n <= eigenvectors.length!0); 1742 | } 1743 | do { 1744 | auto res = mininitRcslice!T(eigenvectors.shape); 1745 | res[] = eigenvectors; 1746 | for ( ; n < eigenvectors.length!0; n++) 1747 | res[n][] = 0; 1748 | return res; 1749 | } 1750 | //returns matrix to transform into basis of eigenvectors with value larger than delta 1751 | Slice!(RCI!(T),2) q(T delta) 1752 | do 1753 | { 1754 | auto res = mininitRcslice!T(eigenvectors.shape); 1755 | res[] = eigenvectors; 1756 | foreach (i; 0 .. eigenvectors.length!0) 1757 | if (fabs(eigenvalues[i]) <= delta) 1758 | res[i][] = 0; 1759 | return res; 1760 | } 1761 | 1762 | //transforms data into eigenbasis 1763 | Slice!(RCI!(T),2) transform(Slice!(RCI!(T),2) data) 1764 | { 1765 | return q.mlinverse.mtimes(data); 1766 | } 1767 | 1768 | //transforms data into eigenbasis 1769 | Slice!(RCI!(T),2) transform(Slice!(RCI!(T),2) data, size_t n) 1770 | { 1771 | return q(n).mlinverse.mtimes(data); 1772 | } 1773 | //transforms data into eigenbasis 1774 | Slice!(RCI!(T),2) transform(Slice!(RCI!(T),2) data, T delta) 1775 | { 1776 | return q(delta).mlinverse.mtimes(data); 1777 | } 1778 | } 1779 | 1780 | 1781 | /++ 1782 | Principal component analysis of raw data. 1783 | Template: 1784 | correlation = Flag to use correlation matrix instead of covariance 1785 | Params: 1786 | data = input `M x N` matrix, where 'M (rows)>= N(cols)' 1787 | devEst = 1788 | meanEst = 1789 | fixEigenvectorDirections = 1790 | Returns: $(LREF PcaResult) 1791 | +/ 1792 | @safe pure @nogc 1793 | PcaResult!T pca( 1794 | SliceKind kind, 1795 | T 1796 | )( 1797 | Slice!(const(T)*, 2, kind) data, 1798 | DeviationEstimator devEst = DeviationEstimator.sample, 1799 | MeanEstimator meanEst = MeanEstimator.average, 1800 | Flag!"fixEigenvectorDirections" fixEigenvectorDirections = Yes.fixEigenvectorDirections, 1801 | ) 1802 | in 1803 | { 1804 | assert(data.length!0 >= data.length!1); 1805 | } 1806 | do 1807 | { 1808 | real n = (data.length!0 <= 1 ? 1 : data.length!0 -1 );//num observations 1809 | auto mean = rcslice!(T,1)([data.length!1], cast(T)0); 1810 | auto stdDev = rcslice!(T,1)([data.length!1], cast(T)1); 1811 | auto centeredData = centerColumns!T(data, mean, meanEst); 1812 | //this part gets the eigenvectors of the sample covariance without explicitly calculating the covariance matrix 1813 | //to implement a minimum covariance deteriminant this block would need to be redone 1814 | //firstly one would calculate the MCD then call eigen to get it's eigenvectors 1815 | auto processedData = normalizeColumns!T(centeredData, stdDev, devEst); 1816 | auto svdResult = processedData.svd(Yes.slim); 1817 | with (svdResult) 1818 | { 1819 | //u[i][] is the ith eigenvector 1820 | foreach (i; 0 .. u.length!0){ 1821 | foreach (j; 0 .. u.length!1){ 1822 | u[i, j] *= sigma[j]; 1823 | } 1824 | } 1825 | auto eigenvalues = mininitRcslice!double(sigma.shape); 1826 | for (size_t i = 0; i < sigma.length && i < eigenvalues.length; i++){ 1827 | eigenvalues[i] = sigma[i] * sigma[i] / n; //square singular values to get eigenvalues 1828 | } 1829 | if (fixEigenvectorDirections) 1830 | { 1831 | foreach(size_t i; 0 .. sigma.length) 1832 | { 1833 | //these are directed so the 0th component is +ve 1834 | if (vt[i, 0] < 0) 1835 | { 1836 | vt[i][] *= -1; 1837 | u[0 .. $, i] *= -1; 1838 | } 1839 | } 1840 | } 1841 | PcaResult!T result; 1842 | result.scores = u; 1843 | result.eigenvectors = vt; 1844 | result.eigenvalues = eigenvalues; 1845 | result.mean = mean; 1846 | result.stdDev = stdDev; 1847 | return result; 1848 | } 1849 | } 1850 | 1851 | @safe pure @nogc 1852 | PcaResult!T pca(T, SliceKind kind) 1853 | ( 1854 | auto ref const Slice!(RCI!T, 2, kind) data, 1855 | DeviationEstimator devEst = DeviationEstimator.sample, 1856 | MeanEstimator meanEst = MeanEstimator.average, 1857 | Flag!"fixEigenvectorDirections" fixEigenvectorDirections = Yes.fixEigenvectorDirections, 1858 | ) 1859 | do 1860 | { 1861 | auto d = data.lightScope.lightConst; 1862 | return d.pca(devEst, meanEst, fixEigenvectorDirections); 1863 | } 1864 | 1865 | pure 1866 | unittest 1867 | { 1868 | import mir.ndslice; 1869 | import mir.math; 1870 | 1871 | auto data = mininitRcslice!double(3, 2); 1872 | data[] = [[ 1, -1], 1873 | [ 0, 1], 1874 | [-1, 0]]; 1875 | //cov =0.5 * [[ 2, -1], 1876 | // [-1, 2]] 1877 | const auto const_data = data; 1878 | auto mean = mininitRcslice!double(2); 1879 | assert(data == centerColumns(const_data, mean)); 1880 | assert(mean == [0,0]); 1881 | PcaResult!double res = const_data.pca; 1882 | 1883 | auto evs = mininitRcslice!double(2, 2); 1884 | evs[] = [[1, -1], 1885 | [1, 1]]; 1886 | evs[] /= sqrt(2.0); 1887 | assert(equal!approxEqual(res.eigenvectors, evs)); 1888 | 1889 | auto score = mininitRcslice!double(3, 2); 1890 | score[] = [[ 1.0, 0.0], 1891 | [-0.5, 0.5], 1892 | [-0.5, -0.5]]; 1893 | score[] *= sqrt(2.0); 1894 | assert(equal!approxEqual(res.scores, score)); 1895 | 1896 | auto evals = mininitRcslice!double(2); 1897 | evals[] = [1.5, 0.5]; 1898 | assert(equal!approxEqual(res.eigenvalues, evals)); 1899 | } 1900 | 1901 | pure 1902 | unittest 1903 | { 1904 | import mir.ndslice; 1905 | import mir.math; 1906 | 1907 | auto data = mininitRcslice!double(10, 3); 1908 | data[] = [[7, 4, 3], 1909 | [4, 1, 8], 1910 | [6, 3, 5], 1911 | [8, 6, 1], 1912 | [8, 5, 7], 1913 | [7, 2, 9], 1914 | [5, 3, 3], 1915 | [9, 5, 8], 1916 | [7, 4, 5], 1917 | [8, 2, 2]]; 1918 | 1919 | auto m1 = 69.0/10.0; 1920 | auto m2 = 35.0/10.0; 1921 | auto m3 = 51.0/10.0; 1922 | 1923 | auto centeredData = mininitRcslice!double(10, 3); 1924 | centeredData[] = [[7-m1, 4-m2, 3-m3], 1925 | [4-m1, 1-m2, 8-m3], 1926 | [6-m1, 3-m2, 5-m3], 1927 | [8-m1, 6-m2, 1-m3], 1928 | [8-m1, 5-m2, 7-m3], 1929 | [7-m1, 2-m2, 9-m3], 1930 | [5-m1, 3-m2, 3-m3], 1931 | [9-m1, 5-m2, 8-m3], 1932 | [7-m1, 4-m2, 5-m3], 1933 | [8-m1, 2-m2, 2-m3]]; 1934 | auto mean = mininitRcslice!double(3); 1935 | auto cenRes = centerColumns(data, mean); 1936 | 1937 | assert(equal!approxEqual(centeredData, cenRes)); 1938 | assert(equal!approxEqual(mean, [m1,m2,m3])); 1939 | 1940 | auto res = data.pca; 1941 | auto coeff = mininitRcslice!double(3, 3); 1942 | coeff[] = [[0.6420046 , 0.6863616 , -0.3416692 ], 1943 | [0.38467229, 0.09713033, 0.91792861], 1944 | [0.6632174 , -0.7207450 , -0.2016662 ]]; 1945 | 1946 | auto score = mininitRcslice!double(10, 3); 1947 | score[] = [[ 0.5148128, -0.63083556, -0.03351152], 1948 | [-2.6600105, 0.06280922, -0.33089322], 1949 | [-0.5840389, -0.29060575, -0.15658900], 1950 | [ 2.0477577, -0.90963475, -0.36627323], 1951 | [ 0.8832739, 0.99120250, -0.34153847], 1952 | [-1.0837642, 1.20857108, 0.44706241], 1953 | [-0.7618703, -1.19712391, -0.44810271], 1954 | [ 1.1828371, 1.57067601, 0.02182598], 1955 | [ 0.2713493, 0.02325373, -0.17721300], 1956 | [ 0.1896531, -0.82831257, 1.38523276]]; 1957 | 1958 | auto stdDev = mininitRcslice!double(3); 1959 | stdDev[] = [1.3299527, 0.9628478, 0.5514979]; 1960 | 1961 | auto eigenvalues = mininitRcslice!double(3); 1962 | eigenvalues[] = [1.768774, 0.9270759, 0.3041499]; 1963 | 1964 | assert(equal!approxEqual(res.eigenvectors, coeff)); 1965 | assert(equal!approxEqual(res.scores, score)); 1966 | assert(equal!approxEqual(res.eigenvalues, eigenvalues)); 1967 | } 1968 | 1969 | pure 1970 | unittest 1971 | { 1972 | import mir.ndslice; 1973 | import mir.math; 1974 | 1975 | auto data = mininitRcslice!double(13, 4); 1976 | data[] =[[ 7, 26, 6, 60], 1977 | [ 1, 29, 15, 52], 1978 | [11, 56, 8, 20], 1979 | [11, 31, 8, 47], 1980 | [ 7, 52, 6, 33], 1981 | [11, 55, 9, 22], 1982 | [ 3, 71, 17, 6], 1983 | [ 1, 31, 22, 44], 1984 | [ 2, 54, 18, 22], 1985 | [21, 47, 4, 26], 1986 | [ 1, 40, 23, 34], 1987 | [11, 66, 9, 12], 1988 | [10, 68, 8, 12]]; 1989 | 1990 | auto m1 = 97.0/13.0; 1991 | auto m2 = 626.0/13.0; 1992 | auto m3 = 153.0/13.0; 1993 | auto m4 = 390.0/13.0; 1994 | 1995 | auto centeredData = mininitRcslice!double(13, 4); 1996 | centeredData[] = [[ 7-m1, 26-m2, 6-m3, 60-m4], 1997 | [ 1-m1, 29-m2, 15-m3, 52-m4], 1998 | [11-m1, 56-m2, 8-m3, 20-m4], 1999 | [11-m1, 31-m2, 8-m3, 47-m4], 2000 | [ 7-m1, 52-m2, 6-m3, 33-m4], 2001 | [11-m1, 55-m2, 9-m3, 22-m4], 2002 | [ 3-m1, 71-m2, 17-m3, 6-m4], 2003 | [ 1-m1, 31-m2, 22-m3, 44-m4], 2004 | [ 2-m1, 54-m2, 18-m3, 22-m4], 2005 | [21-m1, 47-m2, 4-m3, 26-m4], 2006 | [ 1-m1, 40-m2, 23-m3, 34-m4], 2007 | [11-m1, 66-m2, 9-m3, 12-m4], 2008 | [10-m1, 68-m2 ,8-m3, 12-m4]]; 2009 | auto mean = mininitRcslice!double(4); 2010 | auto cenRes = centerColumns(data, mean); 2011 | 2012 | assert(equal!approxEqual(centeredData, cenRes)); 2013 | assert(mean == [m1,m2,m3,m4]); 2014 | 2015 | auto res = data.pca(DeviationEstimator.none); 2016 | auto coeff = mininitRcslice!double(4, 4); 2017 | coeff[] = [[0.067799985695474, 0.678516235418647, -0.029020832106229, -0.730873909451461], 2018 | [0.646018286568728, 0.019993340484099, -0.755309622491133, 0.108480477171676], 2019 | [0.567314540990512, -0.543969276583817, 0.403553469172668, -0.468397518388289], 2020 | [0.506179559977705, 0.493268092159297, 0.515567418476836, 0.484416225289198]]; 2021 | 2022 | auto score = mininitRcslice!double(13, 4); 2023 | score[] = [[-36.821825999449700, 6.870878154227367, -4.590944457629745, 0.396652582713912], 2024 | [-29.607273420710964, -4.610881963526308, -2.247578163663940, -0.395843536696492], 2025 | [ 12.981775719737618, 4.204913183175938, 0.902243082694698, -1.126100587210615], 2026 | [-23.714725720918022, 6.634052554708721, 1.854742000806314, -0.378564808384691], 2027 | [ 0.553191676624597, 4.461732123178686, -6.087412652325177, 0.142384896047281], 2028 | [ 10.812490833309816, 3.646571174544059, 0.912970791674604, -0.134968810314680], 2029 | [ 32.588166608817929, -8.979846284936063, -1.606265913996588, 0.081763927599947], 2030 | [-22.606395499005586, -10.725906457369449, 3.236537714483416, 0.324334774646368], 2031 | [ 9.262587237675838, -8.985373347478788, -0.016909578102172, -0.543746175981799], 2032 | [ 3.283969329640680, 14.157277337500918, 7.046512994833761, 0.340509860960606], 2033 | [ -9.220031117829379, -12.386080787220454, 3.428342878284624, 0.435152769664895], 2034 | [ 25.584908517429557, 2.781693148152386, -0.386716066864491, 0.446817950545605], 2035 | [ 26.903161834677597, 2.930971165042989, -2.445522630195304, 0.411607156409658]]; 2036 | 2037 | auto eigenvalues = mininitRcslice!double(4); 2038 | 2039 | 2040 | eigenvalues[] = [517.7968780739053, 67.4964360487231, 12.4054300480810, 0.2371532651878]; 2041 | 2042 | assert(equal!approxEqual(res.eigenvectors, coeff)); 2043 | assert(equal!approxEqual(res.scores, score)); 2044 | assert(equal!approxEqual(res.eigenvalues, eigenvalues)); 2045 | } 2046 | 2047 | ///complex extensions 2048 | 2049 | private T conj(T)( 2050 | const T z 2051 | ) 2052 | if (isComplex!T) 2053 | { 2054 | return z.re - (1fi* z.im); 2055 | } 2056 | 2057 | private template complexType(C) 2058 | { 2059 | import mir.complex: Complex; 2060 | static if (isComplex!C) 2061 | alias complexType = Unqual!C; 2062 | else static if (is(Unqual!C == double)) 2063 | alias complexType = Complex!(Unqual!C); 2064 | } 2065 | 2066 | /// 2067 | enum MeanEstimator 2068 | { 2069 | /// 2070 | none, 2071 | /// 2072 | average, 2073 | /// 2074 | median 2075 | } 2076 | 2077 | /// 2078 | @safe pure nothrow @nogc 2079 | T median(T)(auto ref Slice!(RCI!T) data) 2080 | { 2081 | auto dataScope = data.lightScope.lightConst; 2082 | return median!T(dataScope); 2083 | } 2084 | 2085 | /// ditto 2086 | @safe pure nothrow @nogc 2087 | T median(T)(Slice!(const(T)*) data) 2088 | { 2089 | import mir.ndslice.sorting: sort; 2090 | size_t len = cast(int) data.length; 2091 | size_t n = len / 2; 2092 | auto temp = data.as!T.rcslice; 2093 | temp.lightScope.sort(); 2094 | return len % 2 ? temp[n] : 0.5f * (temp[n - 1] + temp[n]); 2095 | } 2096 | 2097 | /// 2098 | @safe pure 2099 | unittest 2100 | { 2101 | import mir.ndslice; 2102 | import mir.math; 2103 | 2104 | auto a = mininitRcslice!double(3); 2105 | a[] = [3, 1, 7]; 2106 | auto med = median!double(a.flattened); 2107 | assert(med == 3.0); 2108 | assert(a == [3, 1, 7]);//add in stddev out param 2109 | double aDev; 2110 | auto aCenter = centerColumns(a, aDev, MeanEstimator.median); 2111 | assert(aCenter == [0.0, -2.0, 4.0]); 2112 | assert(aDev == 3.0); 2113 | auto b = mininitRcslice!double(4); 2114 | b[] = [4,2,5,1]; 2115 | auto medB = median!double(b.flattened); 2116 | assert(medB == 3.0); 2117 | assert(b == [4,2,5,1]); 2118 | double bDev; 2119 | auto bCenter = centerColumns(b, bDev, MeanEstimator.median); 2120 | assert(bCenter == [1.0, -1.0, 2.0, -2.0]); 2121 | assert(bDev == 3.0); 2122 | } 2123 | 2124 | /++ 2125 | Mean Centring of raw data. 2126 | Params: 2127 | matrix = input `M x N` matrix 2128 | mean = column means 2129 | est = mean estimation method 2130 | Returns: 2131 | `M x N` matrix with each column translated by the column mean 2132 | +/ 2133 | @safe pure nothrow @nogc 2134 | Slice!(RCI!T,2) centerColumns(T, SliceKind kind) 2135 | ( 2136 | Slice!(const(T)*, 2, kind) matrix, 2137 | out Slice!(RCI!T) mean, 2138 | MeanEstimator est = MeanEstimator.average, 2139 | ) 2140 | { 2141 | mean = rcslice!T([matrix.length!1], cast(T)0); 2142 | if (est == MeanEstimator.none) 2143 | { 2144 | return matrix.as!T.rcslice; 2145 | } 2146 | auto at = matrix.transposed.as!T.rcslice; 2147 | auto len = at.length!1; 2148 | foreach (i; 0 .. at.length!0) 2149 | { 2150 | if (est == MeanEstimator.average) 2151 | { 2152 | foreach(j; 0 .. at.length!1) 2153 | mean[i] += (at[i][j]/len); 2154 | } 2155 | else // (est == MeanEstimator.median) 2156 | { 2157 | mean[i] = median(at[i].flattened); 2158 | } 2159 | at[i][] -= mean[i]; 2160 | } 2161 | auto atSliced = at.transposed.as!T.rcslice; 2162 | return atSliced; 2163 | } 2164 | 2165 | /// ditto 2166 | @safe pure nothrow @nogc 2167 | Slice!(RCI!T) centerColumns(T) 2168 | ( 2169 | Slice!(const(T)*) col, 2170 | out T mean, 2171 | MeanEstimator est = MeanEstimator.average, 2172 | ) 2173 | { 2174 | mean = cast(T)0; 2175 | if (est == MeanEstimator.none) 2176 | { 2177 | return col.as!T.rcslice; 2178 | } 2179 | auto len = col.length; 2180 | if (est == MeanEstimator.average) 2181 | { 2182 | foreach(j; 0 .. len) 2183 | mean += (col[j]/len); 2184 | } 2185 | else // (est == MeanEstimator.median) 2186 | { 2187 | mean = median(col); 2188 | } 2189 | auto result = mininitRcslice!T(len); 2190 | foreach (j; 0 .. len) 2191 | result[j] = col[j] - mean; 2192 | return result; 2193 | } 2194 | 2195 | /// ditto 2196 | @safe pure nothrow @nogc 2197 | Slice!(RCI!T) centerColumns(T) 2198 | ( 2199 | auto ref const Slice!(RCI!T) col, 2200 | out T mean, 2201 | MeanEstimator est = MeanEstimator.average, 2202 | ) 2203 | { 2204 | auto colScope = col.lightScope; 2205 | return centerColumns!(T)(colScope, mean, est); 2206 | } 2207 | 2208 | /// ditto 2209 | @safe pure nothrow @nogc 2210 | Slice!(RCI!T,2) centerColumns(T, SliceKind kind) 2211 | ( 2212 | auto ref const Slice!(RCI!T,2,kind) matrix, 2213 | out Slice!(RCI!T) mean, 2214 | MeanEstimator est = MeanEstimator.average, 2215 | ) 2216 | { 2217 | auto matrixScope = matrix.lightScope; 2218 | return centerColumns(matrixScope, mean, est); 2219 | } 2220 | 2221 | /// 2222 | @safe pure nothrow 2223 | unittest 2224 | { 2225 | import mir.ndslice; 2226 | import mir.math; 2227 | 2228 | auto data = mininitRcslice!double(2,1); 2229 | data[] = [[1], 2230 | [3]]; 2231 | auto mean = mininitRcslice!double(1); 2232 | auto res = centerColumns(data, mean, MeanEstimator.average); 2233 | assert(mean[0] == 2); 2234 | assert(res == [[-1],[1]]); 2235 | } 2236 | 2237 | /// 2238 | enum DeviationEstimator 2239 | { 2240 | /// 2241 | none, 2242 | /// 2243 | sample, 2244 | /// median absolute deviation 2245 | mad 2246 | } 2247 | 2248 | /++ 2249 | Normalization of raw data. 2250 | Params: 2251 | matrix = input `M x N` matrix, each row an observation and each column mean centred 2252 | stdDev = column standard deviation 2253 | devEst = estimation method 2254 | Returns: 2255 | `M x N` matrix with each column divided by it's standard deviation 2256 | +/ 2257 | @safe pure nothrow @nogc Slice!(RCI!T,2) normalizeColumns(T, SliceKind kind)( 2258 | auto ref const Slice!(const(T)*,2,kind) matrix, 2259 | out Slice!(RCI!T) stdDev, 2260 | DeviationEstimator devEst = DeviationEstimator.sample 2261 | ) 2262 | { 2263 | stdDev = rcslice!T([matrix.length!1], cast(T)0); 2264 | if (devEst == DeviationEstimator.none) 2265 | { 2266 | auto matrixSliced = matrix.as!T.rcslice; 2267 | return matrixSliced; 2268 | } 2269 | else 2270 | { 2271 | import mir.math.sum: sum; 2272 | auto mTSliced = matrix.transposed.as!T.rcslice; 2273 | auto mT = mTSliced.lightScope.canonical; 2274 | foreach (i; 0 .. mT.length!0) 2275 | { 2276 | auto processedRow = mininitRcslice!T(mT.length!1); 2277 | if (devEst == DeviationEstimator.sample) 2278 | { 2279 | foreach (j; 0 .. mT.length!1) 2280 | processedRow[j] = mT[i, j] * mT[i, j]; 2281 | stdDev[i] = sqrt(processedRow.sum!"kb2" / (mT[i].length - 1)); 2282 | } 2283 | else if (devEst == DeviationEstimator.mad) 2284 | { 2285 | foreach (j; 0 .. mT.length!1) 2286 | processedRow[j] = fabs(mT[i,j]); 2287 | stdDev[i] = median!T(processedRow); 2288 | } 2289 | mT[i][] /= stdDev[i]; 2290 | } 2291 | auto mSliced = mT.transposed.rcslice; 2292 | return mSliced; 2293 | } 2294 | } 2295 | 2296 | /// ditto 2297 | @safe pure nothrow @nogc Slice!(RCI!T,2) normalizeColumns(T, SliceKind kind)( 2298 | auto ref const Slice!(RCI!T,2,kind) matrix, 2299 | out Slice!(RCI!T) stdDev, 2300 | DeviationEstimator devEst = DeviationEstimator.sample, 2301 | ) 2302 | { 2303 | auto matrixScope = matrix.lightScope.lightConst; 2304 | return normalizeColumns!(T, kind)(matrixScope, stdDev, devEst); 2305 | } 2306 | 2307 | /// 2308 | @safe pure nothrow unittest 2309 | { 2310 | import mir.ndslice; 2311 | import mir.math; 2312 | 2313 | auto data = mininitRcslice!double(2,2); 2314 | data[] = [[ 2, -1], 2315 | [-2, 1]]; 2316 | //sd1 = 2 * sqrt(2.0); 2317 | //sd2 = sqrt(2.0); 2318 | auto x = 1.0 / sqrt(2.0); 2319 | auto scaled = mininitRcslice!double(2,2); 2320 | scaled[] = [[ x, -x], 2321 | [-x, x]]; 2322 | auto stdDev = mininitRcslice!double(2); 2323 | assert(normalizeColumns(data, stdDev) == scaled); 2324 | assert(stdDev == [2*sqrt(2.0), sqrt(2.0)]); 2325 | } 2326 | -------------------------------------------------------------------------------- /subprojects/cblas-d.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory=cblas-d 3 | url=https://github.com/DlangScience/cblas.git 4 | revision=head -------------------------------------------------------------------------------- /subprojects/lapack-d.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory=lapack-d 3 | url=https://github.com/libmir/lapack.git 4 | revision=head -------------------------------------------------------------------------------- /subprojects/mir-algorithm.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory=mir-algorithm 3 | url=https://github.com/libmir/mir-algorithm.git 4 | revision=head -------------------------------------------------------------------------------- /subprojects/mir-blas.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory=mir-blas 3 | url=https://github.com/libmir/mir-blas.git 4 | revision=head -------------------------------------------------------------------------------- /subprojects/mir-core.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory=mir-core 3 | url=https://github.com/libmir/mir-core.git 4 | revision=head -------------------------------------------------------------------------------- /subprojects/mir-lapack.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory=mir-lapack 3 | url=https://github.com/libmir/mir-lapack.git 4 | revision=head -------------------------------------------------------------------------------- /subprojects/mir-linux-kernel.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory=mir-linux-kernel 3 | url=https://github.com/libmir/mir-linux-kernel.git 4 | revision=head -------------------------------------------------------------------------------- /subprojects/mir-random.wrap: -------------------------------------------------------------------------------- 1 | [wrap-git] 2 | directory=mir-random 3 | url=https://github.com/libmir/mir-random.git 4 | revision=head --------------------------------------------------------------------------------