├── .gitignore ├── .travis.yml ├── LICENSE.txt ├── README.md ├── index.js ├── lib └── node-lapack │ ├── fortranArray.js │ ├── index.js │ └── lapack.js ├── package.json └── spec ├── approxEql.js └── lapack_spec.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | env: 3 | - CXX=g++-4.8 4 | addons: 5 | apt: 6 | sources: 7 | - ubuntu-toolchain-r-test 8 | packages: 9 | - liblapack-dev 10 | - g++-4.8 11 | node_js: 12 | - "0.12" 13 | - "0.10" 14 | - "4" 15 | - "5" 16 | sudo: false 17 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Chris Umbel 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | node-lapack 3 | =========== 4 | 5 | A node.js wrapper for the high-performance [LAPACK](http://www.netlib.org/lapack/) linear algebra library. 6 | 7 | Prerequisites 8 | ============= 9 | 10 | This library require LAPACK to be built and installed as a shared library. 11 | In time the entire build process may be unified into this project, but that's 12 | some time away. 13 | 14 | In the meantime I've placed some basic LAPACK build instructions at the end of this document. 15 | 16 | Installation 17 | ============ 18 | 19 | npm install lapack 20 | 21 | Usage 22 | ===== 23 | 24 | var lapack = require('lapack'); 25 | 26 | /* 27 | LAPACK functions 28 | */ 29 | 30 | var result = lapack.sgeqrf([ 31 | [1, 2, 3], 32 | [3, 4, 5], 33 | [5, 6, 7] 34 | ]); 35 | 36 | console.log(result.R); 37 | console.log(result.tau); 38 | 39 | result = sgesvd('A', 'A', [ 40 | [1, 2, 3], 41 | [3, 4, 5], 42 | [5, 6, 7] 43 | ]); 44 | 45 | console.log(result.U); 46 | console.log(result.S); 47 | console.log(result.VT); 48 | 49 | result = lapack.sgetrf([ 50 | [1, 2, 3], 51 | [3, 4, 5], 52 | [5, 6, 7] 53 | ]); 54 | 55 | console.log(result.LU); 56 | console.log(result.IPIV); 57 | 58 | /* 59 | conveniently wrapped processes 60 | */ 61 | 62 | // perform a complete LU factorization 63 | var lu = lapack.lu([ 64 | [1, 2, 3], 65 | [3, 4, 5], 66 | [5, 6, 7] 67 | ]); 68 | 69 | console.log(lu.L); 70 | console.log(lu.U); 71 | console.log(lu.P); 72 | 73 | 74 | // perform a complete QR factorization 75 | var qr = lapack.qr([ 76 | [1, 2, 3], 77 | [3, 4, 5], 78 | [5, 6, 7] 79 | ]); 80 | 81 | console.log(qr.Q); 82 | console.log(qr.R); 83 | 84 | // solve a system of linear equations 85 | 86 | var a = [ 87 | [2, 4], 88 | [2, 8]]; 89 | 90 | var b = [[2], 91 | [4]]; 92 | 93 | var result = lapack.sgesv(a, b); 94 | console.log(result.X); 95 | console.log(result.P); 96 | 97 | LAPACK Building 98 | =============== 99 | 100 | These instructions are raw and a work in progress, but should work fine for linux and MacOS. The net result will be LAPACK and BLAS shared libraries. 101 | 102 | If you haven't done so already download the LAPACK source http://www.netlib.org/lapack/ 103 | 104 | After unpacking the source create a make.inc based on the make.inc.example provided. For the purposes of this document I'll assume you have the "gfortran" fortran compiler installed. 105 | 106 | Then make the following changes: 107 | 108 | make.inc 109 | -------- 110 | 111 | CHANGE: 112 | 113 | FORTRAN = gfortran 114 | OPTS = -O2 115 | DRVOPTS = $(OPTS) 116 | NOOPT = -O0 117 | LOADER = gfortran 118 | LOADOPTS = 119 | 120 | TO: 121 | 122 | UNAME := $(shell uname) 123 | 124 | FORTRAN = gfortran 125 | OPTS = -O2 -fPIC 126 | DRVOPTS = $(OPTS) 127 | NOOPT = -O0 -fPIC 128 | LOADER = gfortran 129 | LOADOPTS = 130 | 131 | ifeq ($(UNAME), Darwin) 132 | LIBEXT=dylib 133 | else 134 | LIBEXT=so 135 | endif 136 | 137 | BLAS/SRC/Makefile 138 | ----------------- 139 | 140 | CHANGE: 141 | 142 | all: $(BLASLIB) 143 | 144 | TO: 145 | 146 | all: $(BLASLIB) libblas.$(LIBEXT) 147 | 148 | libblas.$(LIBEXT): 149 | $(FORTRAN) -shared -o $@ *.o 150 | cp $@ ../../$@ 151 | 152 | CHANGE: 153 | 154 | clean: 155 | rm -f *.o 156 | 157 | TO: 158 | 159 | clean: 160 | rm -f *.o 161 | rm -f *.a 162 | rm -f *.$(LIBEXT) 163 | 164 | SRC/Makefile 165 | ------------ 166 | 167 | CHANGE: 168 | 169 | all: ../$(LAPACKLIB) 170 | 171 | TO: 172 | 173 | all: ../$(LAPACKLIB) liblapack.$(LIBEXT) 174 | 175 | liblapack.$(LIBEXT): 176 | gfortran -shared -o $@ $(ALLOBJ) -lblas -L.. 177 | cp $@ ../$@ 178 | 179 | CHANGE: 180 | 181 | clean: 182 | rm -f *.o 183 | 184 | TO: 185 | 186 | clean: 187 | rm -f *.o 188 | rm -f *.a 189 | rm -f *.$(LIBEXT) 190 | 191 | Compiling 192 | --------- 193 | 194 | make blaslib 195 | make lapacklib 196 | 197 | Installing 198 | ---------- 199 | 200 | # for linux 201 | cp liblapack.so libblas.so /usr/lib 202 | # for macos 203 | # NOTE! this might be dangerous now. macs ship with liblapack.dylib now? 204 | # regardless, i don't suggest installing a custom lapack right now on Mac 205 | cp liblapack.dylib libblas.dylib /usr/lib 206 | 207 | 208 | License 209 | ======= 210 | 211 | Copyright (c) 2011, Chris Umbel 212 | 213 | Permission is hereby granted, free of charge, to any person obtaining a copy 214 | of this software and associated documentation files (the "Software"), to deal 215 | in the Software without restriction, including without limitation the rights 216 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 217 | copies of the Software, and to permit persons to whom the Software is 218 | furnished to do so, subject to the following conditions: 219 | 220 | The above copyright notice and this permission notice shall be included in 221 | all copies or substantial portions of the Software. 222 | 223 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 224 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 225 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 226 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 227 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 228 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 229 | THE SOFTWARE. 230 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011, Chris Umbel 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | */ 23 | 24 | require('./lib/node-lapack') -------------------------------------------------------------------------------- /lib/node-lapack/fortranArray.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011, Chris Umbel 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | var FFI = require('ffi'); 24 | 25 | function fortranArrayToJSMatrix(fortranArray, m, n, elementSize) { 26 | var op = elementSize == 8 ? 'readDoubleLE' : 'readFloatLE'; 27 | var array = []; 28 | var rowWidth = elementSize * n; 29 | var columnOffset = m * elementSize; 30 | 31 | for(var i = 0; i < m; i++) { 32 | var row = []; 33 | var rowStart = i * elementSize; 34 | 35 | for(var j = 0; j < n; j++) { 36 | row.push(fortranArray[op](columnOffset * j + rowStart)); 37 | } 38 | 39 | array.push(row); 40 | } 41 | 42 | return array; 43 | } 44 | 45 | function jsMatrixToFortranArray(array, elementSize) { 46 | var op = elementSize == 8 ? 'writeDoubleLE' : 'writeFloatLE'; 47 | var m = array.length; 48 | var n = array[0].length; 49 | var fortranArrayStart = fortranArray = alignedBuffer(m * n * elementSize); 50 | for(var j = 0; j < n; j++) { 51 | for(var i = 0; i < m; i++) { 52 | fortranArray[op](array[i][j], elementSize * (j * m + i)); 53 | } 54 | } 55 | 56 | return fortranArrayStart; 57 | } 58 | 59 | function fortranArrayToJSArray(fortranArray, n, op, elementSize) { 60 | var array = []; 61 | 62 | for(var i = 0; i < n; i++) { 63 | array.push(fortranArray[op](i * elementSize)); 64 | } 65 | 66 | return array; 67 | } 68 | 69 | // return an buffer suitably aligned for SSE operations 70 | function alignedBuffer(size) { 71 | var buffer = new Buffer(size + 16); 72 | return buffer.slice(16 - (buffer.address() % 16)); 73 | } 74 | 75 | module.exports.fortranArrayToJSMatrix = fortranArrayToJSMatrix; 76 | module.exports.jsMatrixToFortranArray = jsMatrixToFortranArray; 77 | module.exports.fortranArrayToJSArray = fortranArrayToJSArray; 78 | module.exports.alignedBuffer = alignedBuffer; 79 | -------------------------------------------------------------------------------- /lib/node-lapack/index.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011, Chris Umbel 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | 22 | */ 23 | 24 | var lapack = require('./lapack.js'); 25 | exports.sgeqrf = lapack.sgeqrf; 26 | exports.dgeqrf = lapack.dgeqrf; 27 | exports.sgesvd = lapack.sgesvd; 28 | exports.qr = lapack.qr; 29 | exports.lu = lapack.lu; 30 | exports.sgetrf = lapack.sgetrf; 31 | exports.sgesv = lapack.sgesv; 32 | -------------------------------------------------------------------------------- /lib/node-lapack/lapack.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2011, Chris Umbel 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | 23 | var fortranArray = require('./fortranArray'); 24 | var alignedBuffer = fortranArray.alignedBuffer; 25 | var FFI = require('ffi'); 26 | 27 | var LAPACK; 28 | 29 | try { 30 | LAPACK = new FFI.Library('liblapack', { 31 | "sgeqrf_": ["void", ["pointer", "pointer", "pointer", "pointer", "pointer", 32 | "pointer", "pointer", "pointer"]], 33 | "dgeqrf_": ["void", ["pointer", "pointer", "pointer", "pointer", "pointer", 34 | "pointer", "pointer", "pointer"]], 35 | "sorgqr_": ["void", ["pointer", "pointer", "pointer", "pointer", "pointer", "pointer", 36 | "pointer", "pointer", "pointer"]], 37 | "sgesvd_": ["void", ["pointer", "pointer", "pointer", "pointer", "pointer", 38 | "pointer", "pointer", "pointer", "pointer", "pointer", 39 | "pointer", "pointer", "pointer", "pointer", ]], 40 | "sgetrf_": ["void", ["pointer", "pointer", "pointer", "pointer", "pointer", "pointer"]], 41 | "dgetrf_": ["void", ["pointer", "pointer", "pointer", "pointer", "pointer", "pointer"]], 42 | "sgesv_": ["void", ["pointer", "pointer", "pointer", "pointer", "pointer", "pointer", "pointer", "pointer"]] 43 | 44 | }); 45 | } catch(e) { 46 | console.log("!!! node-lapack requires the native lapack to be built as a shared lib."); 47 | console.log(e); 48 | } 49 | 50 | var FORTRAN_INT = 4; 51 | var FORTRAN_CHAR = 1; 52 | var FORTRAN_FLOAT = 4; 53 | var FORTRAN_DOUBLE = 8; 54 | 55 | function eye(m) { 56 | var matrix = []; 57 | 58 | for(var i = 0; i < m; i++) { 59 | var row = []; 60 | matrix.push(row); 61 | 62 | for(var j = 0; j < m; j++) { 63 | if(i == j) 64 | row.push(1); 65 | else 66 | row.push(0); 67 | } 68 | } 69 | 70 | return matrix; 71 | } 72 | 73 | function matrixOp(matrix, elementSize, callback) { 74 | var m = matrix.length; 75 | var n = matrix[0].length; 76 | var f_m = new Buffer(FORTRAN_INT); 77 | var f_n = new Buffer(FORTRAN_INT); 78 | var f_a = fortranArray.jsMatrixToFortranArray(matrix, elementSize); 79 | var f_lda = new Buffer(FORTRAN_INT); 80 | 81 | f_m.writeInt32LE(m, 0); 82 | f_n.writeInt32LE(n, 0); 83 | f_lda.writeInt32LE(Math.max(1, m), 0); 84 | 85 | callback(m, n, f_m, f_n, f_a, f_lda); 86 | } 87 | 88 | function zeroBottomLeft(matrix) { 89 | // zero out bottom left forming an upper right triangle matrix 90 | for(var i = 1; i < matrix.length; i++) { 91 | for(var j = 0; j < i && j < matrix[0].length; j++) 92 | matrix[i][j] = 0; 93 | } 94 | 95 | return matrix 96 | } 97 | 98 | function sgesv(a, b) { 99 | var f_info = new Buffer(FORTRAN_INT); 100 | var result = {}; 101 | 102 | matrixOp(a, FORTRAN_FLOAT, function(am, an, af_m, af_n, f_a) { 103 | var f_ipiv = new Buffer(am * FORTRAN_INT); 104 | 105 | matrixOp(b, FORTRAN_FLOAT, function(bm, bn, bf_m, bf_n, f_b) { 106 | LAPACK.sgesv_(af_m, bf_n, f_a, af_n, f_ipiv, f_b, bf_m, f_info); 107 | result.X = fortranArray.fortranArrayToJSMatrix(f_b, bm, bn, FORTRAN_FLOAT); 108 | result.P = ipivToP(bm, fortranArray.fortranArrayToJSArray(f_ipiv, bm, 'readInt32LE', FORTRAN_FLOAT)); 109 | }); 110 | }); 111 | 112 | return result; 113 | } 114 | 115 | function qr(matrix) { 116 | var result; 117 | 118 | sgeqrf(matrix, function(qr, m, n, f_m, f_n, f_a, f_lda, f_tau, f_work, f_lwork, f_info) { 119 | var f_k = new Buffer(FORTRAN_INT); 120 | f_k.writeInt32LE(Math.min(m, n), 0); 121 | LAPACK.sorgqr_(f_m, f_n, f_k, f_a, f_lda, f_tau, f_work, f_lwork, f_info); 122 | qr.Q = fortranArray.fortranArrayToJSMatrix(f_a, m, n, FORTRAN_FLOAT); 123 | qr.R = zeroBottomLeft(qr.R); 124 | result = qr; 125 | }); 126 | 127 | return result; 128 | } 129 | 130 | function sgeqrf(matrix, callback) { 131 | return geqrf(matrix, 'readFloatLE', LAPACK.sgeqrf_, FORTRAN_FLOAT, callback); 132 | } 133 | 134 | function dgeqrf(matrix, callback) { 135 | return geqrf(matrix, 'readDoubleLE', LAPACK.dgeqrf_, FORTRAN_DOUBLE, callback); 136 | } 137 | 138 | function geqrf(matrix, op, lapackFunc, elementSize, callback) { 139 | var qr; 140 | 141 | matrixOp(matrix, elementSize, function(m, n, f_m, f_n, f_a, f_lda) { 142 | var f_tau = alignedBuffer(m * n * elementSize); 143 | var f_info = new Buffer(FORTRAN_INT); 144 | var f_lwork = new Buffer(FORTRAN_INT); 145 | var f_work; 146 | f_lwork.writeInt32LE(-1, 0); 147 | 148 | // get optimal size of workspace 149 | f_work = alignedBuffer(FORTRAN_DOUBLE); 150 | lapackFunc(f_m, f_n, f_a, f_lda, f_tau, f_work, f_lwork, f_info); 151 | lwork = f_work[op](0); 152 | 153 | // allocate workspace 154 | f_work = alignedBuffer(lwork * elementSize); 155 | f_lwork.writeInt32LE(lwork, 0); 156 | 157 | // perform QR decomp 158 | lapackFunc(f_m, f_n, f_a, f_lda, f_tau, f_work, f_lwork, f_info); 159 | 160 | qr = { 161 | R: fortranArray.fortranArrayToJSMatrix(f_a, m, n, elementSize), 162 | tau: fortranArray.fortranArrayToJSArray(f_tau, Math.min(m, n), op, elementSize) 163 | }; 164 | 165 | if(callback) 166 | qr = callback(qr, m, n, f_m, f_n, f_a, f_lda, f_tau, f_work, f_lwork, f_info); 167 | }); 168 | 169 | return qr; 170 | } 171 | 172 | function cloneMatrix(matrix, height, width) { 173 | var clone = []; 174 | 175 | height = height || matrix.length; 176 | width = width || matrix[0].length; 177 | 178 | for(var i = 0; i < height; i++) { 179 | var row = []; 180 | clone.push(row); 181 | 182 | for(var j = 0; j < width; j++) { 183 | row.push(matrix[i][j]); 184 | } 185 | } 186 | 187 | return clone; 188 | } 189 | 190 | function swapRows(matrix, i, j) { 191 | var tmp = matrix[j]; 192 | matrix[j] = matrix[i]; 193 | matrix[i] = tmp; 194 | return matrix; 195 | } 196 | 197 | function lu(matrix) { 198 | var result = sgetrf(matrix); 199 | var P = ipivToP(matrix.length, result.IPIV); 200 | var L = cloneMatrix(result.LU); 201 | var m = n = Math.min(matrix.length, matrix[0].length); 202 | 203 | for(var i = 0; i < L.length; i++) { 204 | for(var j = i; j < L[i].length; j++) { 205 | if(i == j) 206 | L[i][j] = 1; 207 | else 208 | L[i][j] = 0; 209 | } 210 | } 211 | 212 | return { 213 | L: L, 214 | U: zeroBottomLeft(cloneMatrix(result.LU, n, n)), 215 | P: P, 216 | IPIV: result.IPIV 217 | }; 218 | } 219 | 220 | function ipivToP(m, ipiv){ 221 | var P = eye(m); 222 | 223 | for(var i = 0; i < ipiv.length; i++) { 224 | if(i != ipiv[i] - 1) 225 | swapRows(P, i, ipiv[i] - 1); 226 | } 227 | 228 | return P; 229 | } 230 | 231 | function sgetrf(matrix) { 232 | return getrf(matrix, LAPACK.sgetrf_, FORTRAN_FLOAT); 233 | } 234 | 235 | function dgetrf(matrix) { 236 | return getrf(matrix, LAPACK.dgetrf_, FORTRAN_DOUBLE); 237 | } 238 | 239 | function getrf(matrix, lapackFunc, elementSize) { 240 | var result = {}; 241 | 242 | matrixOp(matrix, elementSize, function(m, n, f_m, f_n, f_a, f_lda) { 243 | var f_ipiv = new Buffer(Math.min(m, n) * FORTRAN_INT); 244 | var f_info = new Buffer(FORTRAN_INT); 245 | lapackFunc(f_m, f_n, f_a, f_m, f_ipiv, f_info); 246 | result.LU = fortranArray.fortranArrayToJSMatrix(f_a, m, n, elementSize); 247 | result.IPIV = fortranArray.fortranArrayToJSArray(f_ipiv, Math.min(m, n), 'readInt32LE', elementSize); 248 | }); 249 | 250 | return result; 251 | } 252 | 253 | function sgesvd(jobu, jobvt, matrix) { 254 | var f_jobu = new Buffer(FORTRAN_CHAR); 255 | var f_jobvt = new Buffer(FORTRAN_CHAR); 256 | f_jobu.writeUInt8(jobu.charCodeAt(0), 0); 257 | f_jobvt.writeUInt8(jobvt.charCodeAt(0), 0); 258 | var svd; 259 | 260 | matrixOp(matrix, FORTRAN_FLOAT, function(m, n, f_m, f_n, f_a, f_lda) { 261 | var f_s = alignedBuffer(Math.pow(Math.min(m, n), 2) * FORTRAN_FLOAT); 262 | var f_u = alignedBuffer(Math.pow(m, 2) * FORTRAN_FLOAT); 263 | var f_ldu = new Buffer(FORTRAN_INT); 264 | f_ldu.writeInt32LE(m, 0); 265 | 266 | // TODO: punting on dims for now. revisit with http://www.netlib.org/lapack/single/sgesvd.f 267 | var f_vt = alignedBuffer(Math.pow(n, 2) * FORTRAN_FLOAT); 268 | var f_ldvt = new Buffer(FORTRAN_INT); 269 | f_ldvt.writeInt32LE(n, 0); 270 | 271 | var lwork = -1; 272 | var f_work = alignedBuffer(FORTRAN_FLOAT); 273 | var f_lwork = new Buffer(FORTRAN_INT); 274 | f_lwork.writeInt32LE(lwork, 0); 275 | var f_info = new Buffer(FORTRAN_INT); 276 | 277 | LAPACK.sgesvd_(f_jobu, f_jobvt, f_m, f_n, f_a, f_lda, f_s, f_u, f_ldu, f_vt, f_ldvt, 278 | f_work, f_lwork, f_info); 279 | 280 | lwork = f_work.readFloatLE(0); 281 | f_work = alignedBuffer(lwork * FORTRAN_FLOAT); 282 | f_lwork.writeInt32LE(lwork, 0); 283 | 284 | LAPACK.sgesvd_(f_jobu, f_jobvt, f_m, f_n, f_a, f_lda, f_s, f_u, f_ldu, f_vt, f_ldvt, 285 | f_work, f_lwork, f_info); 286 | 287 | svd = { 288 | U: fortranArray.fortranArrayToJSMatrix(f_u, m, m, FORTRAN_FLOAT), 289 | S: fortranArray.fortranArrayToJSMatrix(f_s, n, n, FORTRAN_FLOAT), 290 | VT: fortranArray.fortranArrayToJSMatrix(f_vt, n, n, FORTRAN_FLOAT) 291 | }; 292 | }); 293 | 294 | return svd; 295 | } 296 | 297 | exports.sgeqrf = sgeqrf; 298 | exports.dgeqrf = dgeqrf; 299 | exports.sgesvd = sgesvd; 300 | exports.sgetrf = sgetrf; 301 | exports.dgetrf = sgetrf; 302 | exports.sgesv = sgesv; 303 | exports.qr = qr; 304 | exports.lu = lu; 305 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lapack", 3 | "description": "", 4 | "version": "0.1.0", 5 | "homepage": "https://github.com/NaturalNode/node-lapack", 6 | "engines": { 7 | "node": ">=0.10.0" 8 | }, 9 | "dependencies": { 10 | "ffi": ">=2.0.0" 11 | }, 12 | "author": "Chris Umbel ", 13 | "keywords": [ 14 | "lapack", 15 | "math", 16 | "linear", 17 | "algebra", 18 | "HPC" 19 | ], 20 | "main": "./lib/node-lapack/index.js", 21 | "maintainers": [ 22 | { 23 | "name": "Chris Umbel", 24 | "email": "chris@chrisumbel.com", 25 | "web": "http://www.chrisumbel.com" 26 | } 27 | ], 28 | "license": "MIT", 29 | "repository": { 30 | "type": "git", 31 | "url": "https://github.com/NaturalNode/node-lapack.git" 32 | }, 33 | "scripts": { 34 | "test": "node ./node_modules/.bin/mocha spec/lapack_spec.js" 35 | }, 36 | "devDependencies": { 37 | "mocha": "^2.2.1", 38 | "expect": "^1.6.0" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /spec/approxEql.js: -------------------------------------------------------------------------------- 1 | 2 | var precision = 1e-6; 3 | 4 | function approxEql(matrixA, matrixB) { 5 | var i = matrixA.length; 6 | 7 | while(i--) { 8 | var j = matrixA[0].length; 9 | 10 | while(j--) { 11 | if((Math.abs(matrixA[i][j] - matrixB[i][j]) > precision)) { 12 | return false; 13 | } 14 | } 15 | } 16 | 17 | return true; 18 | } 19 | 20 | module.exports = approxEql; -------------------------------------------------------------------------------- /spec/lapack_spec.js: -------------------------------------------------------------------------------- 1 | 2 | var lapack = require('../lib/node-lapack'); 3 | var approxEql = require('./approxEql'); 4 | var expect = require('expect'); 5 | 6 | describe('lapack', function() { 7 | var M = [ 8 | [2, 1, 1], 9 | [1, 1, 1], 10 | [1, 1, 3] 11 | ]; 12 | 13 | var luIn = [ 14 | [11, 3, 11, 3], 15 | [ 4, 2, 1, 4], 16 | [-4, 5, 3, 1], 17 | [-9, 4, 3, 9] 18 | ]; 19 | 20 | var lu2 = [ 21 | [3, 6], 22 | [2, 3], 23 | [4, 3], 24 | [2, 120], 25 | ]; 26 | 27 | it('should solve', function() { 28 | var A = [ 29 | [2, 4], 30 | [2, 8] 31 | ]; 32 | 33 | var B = [[2], [4]]; 34 | expect(lapack.sgesv(A, B).X).toEqual([[0], [.5]]); 35 | expect(lapack.sgesv(A, B).P).toEqual([[1, 0], [0, 1]]); 36 | }); 37 | 38 | it('should lu rectangular matrix', function() { 39 | var lu = lapack.lu(lu2); 40 | expect(lu.IPIV).toEqual([3, 4]); 41 | expect(lu.L).toEqual([ [ 1, 0 ], 42 | [ 0.5, 1 ], 43 | [ 0.75, 0.03164556622505188 ], 44 | [ 0.5, 0.012658227235078812 ] ]); 45 | expect(lu.U).toEqual([[4, 3], [0, 118.5]]); 46 | }); 47 | 48 | it('should lu', function() { 49 | var lu = lapack.lu(luIn); 50 | expect(approxEql(lu.L, ([ 51 | [ 1, 0, 0, 0 ], 52 | [ -0.8181818723678589, 1, 0, 0 ], 53 | [ 0.3636363744735718, 0.14084506034851074, 1, 0 ], 54 | [ -0.3636363744735718, 0.9436619877815247, 55 | 0.9219222664833069, 1 ] 56 | ]))).toBe(true); 57 | 58 | expect(approxEql(lu.U, [ 59 | [ 11, 3, 11, 3 ], 60 | [ 0, 6.454545497894287, 61 | 12.000000953674316, 11.454545974731445 ], 62 | [ 0, 0, -4.690140724182129, 1.2957748174667358 ], 63 | [ 0, 0, 0, -9.912914276123047 ] 64 | ])).toBe(true); 65 | 66 | expect(approxEql(lu.P, [ 67 | [ 1, 0, 0, 0 ], 68 | [ 0, 0, 0, 1 ], 69 | [ 0, 1, 0, 0 ], 70 | [ 0, 0, 1, 0 ] 71 | ])).toBe(true); 72 | }); 73 | 74 | it('should sgetrf', function() { 75 | var result = lapack.sgetrf(luIn); 76 | expect(approxEql(result.LU, [ [ 11, 3, 11, 3 ], 77 | [ -0.8181818723678589, 78 | 6.454545497894287, 79 | 12.000000953674316, 80 | 11.454545974731445 ], 81 | [ 0.3636363744735718, 82 | 0.14084506034851074, 83 | -4.690140724182129, 84 | 1.2957748174667358 ], 85 | [ -0.3636363744735718, 86 | 0.9436619877815247, 87 | 0.9219222664833069, 88 | -9.912914276123047 ] ])).toBe(true); 89 | expect(approxEql(result.IPIV, [ 3, 2, 3, 4 ])).toBe(true); 90 | }); 91 | 92 | if('should sgetrf and dgetrf approx eql', function() { 93 | expect(approxEql(lapack.sgetrf(luIn).LU, lapack.dgetrf(luIn).LU)).toBe(true); 94 | }); 95 | 96 | it('shoud dgeqrf and sgeqrf approximately equal', function() { 97 | expect(approxEql(lapack.dgeqrf(M).R, 98 | lapack.sgeqrf(M).R)).toBe(true); 99 | }); 100 | 101 | it('shoud sgeqrf', function() { 102 | var qr = lapack.sgeqrf(M); 103 | 104 | expect(approxEql(qr.R, 105 | [ [ -2.4494898, 106 | -1.6329932, 107 | -2.4494896 ], 108 | [ 0.2247450, 109 | -0.57735044, 110 | -1.7320511 ], 111 | [ 0.2247450, 112 | 0.41421360, 113 | 1.4142138 ] ])).toBe(true); 114 | }); 115 | 116 | 117 | it('shoud dgeqrf', function() { 118 | var qr = lapack.dgeqrf(M); 119 | expect(approxEql(qr.R, [[-2.4494898319244385, 120 | -1.632993221282959, 121 | -2.4494895935058594 ], 122 | [ 0.224745035171509, 123 | -0.5773504376411438, 124 | -1.732051134109497 ], 125 | [ 0.224745035171509, 126 | 0.4142135977745056, 127 | 1.41421377658844 ]])).toBe(true); 128 | }); 129 | 130 | it('should qr', function() { 131 | var qr = lapack.qr(M); 132 | expect(approxEql(qr.R, [ [ -2.4494898, 133 | -1.6329932, 134 | -2.4494896 ], 135 | [ 0.0, 136 | -0.57735044, 137 | -1.73205113 ], 138 | [ 0.0, 139 | 0.0, 140 | 1.41421377 ] ] 141 | )).toBe(true); 142 | expect(approxEql(qr.Q, [ 143 | [-0.81649661, 0.57735032, 0.0], 144 | [-0.40824828, -0.57735050, -0.7071067], 145 | [-0.40824828, -0.57735044, 0.70710683] 146 | ])).toBe(true); 147 | }); 148 | 149 | it('should sgesvd', function() { 150 | var svd = lapack.sgesvd('A', 'A', M); 151 | 152 | expect(approxEql(svd.S, [ [ 4.214320182800293, 0, 2.462372871899283e-38 ], 153 | [ 1.4608111381530762, 154 | 2.4612226861197653e-38, 155 | 1.401298464324817e-45 ], 156 | [ 0.3248690962791443, 157 | 1.401298464324817e-45, 158 | 2.462375113976826e-38 ] 159 | ])); 160 | 161 | expect(approxEql(svd.U, [ [ -0.5206573009490967, 162 | 0.7392387390136719, 163 | 0.4271322190761566 ], 164 | [ -0.39711257815361023, 165 | 0.23319198191165924, 166 | -0.8876503705978394 ], 167 | [ -0.7557893991470337, 168 | -0.631781280040741, 169 | 0.1721479445695877 ] ])); 170 | 171 | expect(approxEql(svd.VT, [ [ -0.5206573605537415, 172 | -0.3971126079559326, 173 | -0.7557893991470337 ], 174 | [ 0.7392386794090271, 175 | 0.23319187760353088, 176 | -0.631781280040741 ], 177 | [ 0.42713218927383423, 178 | -0.8876502513885498, 179 | 0.1721479296684265 ] ])); 180 | }); 181 | }); 182 | 183 | --------------------------------------------------------------------------------