├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── src ├── Matrix.js ├── determinant.js ├── index.js └── invert.js └── test └── Matrix.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Build dir 11 | lib 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # Commenting this out is preferred by some people, see 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git- 28 | node_modules 29 | 30 | # Users Environment Variables 31 | .lock-wscript 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Chris Andrejewski 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Matt 2 | ==== 3 | 4 | Matt is a JavaScript DSL for Matrices. Determinate, transpose, and invert immutable, ES6-ready matrices with ease. 5 | 6 | Matt is available on [NPM](https://www.npmjs.org/package/matt). 7 | 8 | ```sh 9 | npm install matt 10 | ``` 11 | 12 | Matt is pretty intuitive to use if already familiar with common matrix operations and transforms. It looks like this, required in Node.js: 13 | 14 | ```js 15 | var Matt = require('matt'); 16 | var Matrix = Matt.Matrix; 17 | var assert = require('assert'); 18 | 19 | // list-style (1D Array) 20 | var A = new Matrix(3, 3, [ 21 | 1, 2, 3, 22 | 4, 5, 6, 23 | 7, 8, 9 24 | ]); 25 | 26 | // table-style (2D Array) 27 | var B = new Matrix([ 28 | [1, 2, 3], 29 | [4, 5, 6], 30 | [7, 8, 9] 31 | ]); 32 | 33 | assert(A.equals(B)); 34 | assert(A.transpose().transpose().equals(A)); 35 | assert.equal(A.trace(), B.trace()); 36 | ``` 37 | 38 | Also see **[Seth](https://github.com/andrejewski/seth)**, my other mathematical DSL for Set Theory. 39 | 40 | ## Features 41 | 42 | Matt exposes one core `Matrix` ES6 written and ready class with tons of matrix methods. Methods include `get`, `set`, `getRow`, `setRow`, `getColumn`, `setColumn`, `getDiagonal`, `getRightDiagonal`, `trace`, `rightTrace`, `add`, `subtract`, `multiply`, `joinHorizontal`, `joinVertical`, `clone`, `map`, `fmap`, `forEach`, `reduce`, `scale`, `transpose`, `identity`, `submatrix`, `minor`, `cofactor`, `cofactorMatrix`, `invert`, `determinant`, `isSquare`, `equals`, `toArray`, `toTable`, and `toString`. 43 | 44 | All methods are tested and throw appropriate errors when the operation is impossible. For example, `determinant` will throw when called on a non-square matrix. 45 | 46 | Matrices are immutable. All methods that mutate a matrix will return a new matrix. This means variables will not be overwritten with new data and operations can be chained and composed more functionally. 47 | 48 | ## Documentation 49 | 50 | The method signatures of the Matrix class are listed. 51 | 52 | ``` 53 | constructor(rows Number, cols Number, elements [Any]) Matrix 54 | get(row Number, col Number) Any 55 | set(row Number, col Number, value Any) Matrix 56 | getRow(row Number) [Any] 57 | setRow(row Number, elements [Any]) Matrix 58 | getColumn(col Number) [Any] 59 | setColumn(col Number, elements [Any]) Matrix 60 | getDiagonal() [Any] 61 | getRightDiagonal() [Any] 62 | trace() Number 63 | rightTrace() Number 64 | add(matrix Matrix, ) Matrix 65 | subtract(matrix Matrix, ) Matrix 66 | multiply(matrix Matrix, ) Matrix 67 | joinHorizontal(matrix Matrix) Matrix 68 | joinVertical(matrix Matrix) Matrix 69 | clone() Matrix 70 | map(fn Function(element Any, row Number, col Number, matrix Matrix)) Matrix 71 | fmap(fn Function(element Any, row Number, col Number, matrix Matrix)) Matrix 72 | forEach(fn Function(element Any, row Number, col Number, matrix Matrix)) void 73 | reduce(fn Function(acc Any, value Any, row Number, col Number, matrix Matrix), ) Matrix 74 | scale(num Number) Matrix 75 | transpose() Matrix 76 | identity() Matrix 77 | submatrix(topLeftRow Number, topLeftCol Number, bottomRightRow Number, bottomRightCol Number) Matrix 78 | minor(row Number, col Number) Matrix 79 | cofactor(row Number, col Number) Number 80 | cofactorMatrix() Matrix 81 | invert() Matrix 82 | determinant() Number 83 | isSquare() boolean 84 | equals(matrix Matrix) boolean 85 | toArray() [Any] 86 | toTable() [[Any]] 87 | toString() String 88 | ``` 89 | 90 | For complete documentation, please refer to the tests as they double as documentation quite well. In the `matt` directory run: 91 | 92 | ```sh 93 | npm install && npm run build && npm test 94 | ``` 95 | 96 | ## Performance 97 | 98 | Every effort has been and will be made to keep Matt performant. As JavaScript is a higher-level language, Matt's performance will not rival the C family's any time soon. However with V8 optimizations, new primitive [data types](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays), and projects such as [asm.js](http://asmjs.org/), performance will get better over time. 99 | 100 | Matt was designed to be ready to embrace these changes. Unlike most matrix implementations, Matt deals with one-dimensional instead of two-dimensional arrays. This decision was made for multiple reasons. 101 | 102 | - JavaScript arrays are not really arrays, just objects with some array-like methods. Thus, there is really not much reason to deal with arrays in arrays for the sake of performance. 103 | 104 | - Since V8 and others optimize for array methods, Matt uses built-in functions, initializes arrays where possible, and runs for loops wherever possible to gain those speed boosts. Arrays also enjoy faster lookup than tables (2D arrays) while being less messy. 105 | 106 | - Typing. It is faster and easier to write `2, 2, [1, 2, 3, 4]` than `[[1,2],[3,4]]`; more with larger and nested matrices. It is also better for the new ES6 destructuring syntax we all will be using soon enough. 107 | 108 | These decisions and details are really only important to contributors. The bottom-line is: expect Matt to be just as fast as any other JavaScript matrix library (I know, not setting the bar very high) and to continue seeing more improvements as they become possible in the language. 109 | 110 | The places where performance is worst is the same as all other implementations: determinants and inversions when `N` (i.e. `N * N` matrices) is large and anything when `N` is very, very, very large. 111 | 112 | ## Contributing 113 | 114 | I have never taken a class on advanced linear algebra or matrices (heck I'm still in high school). I did read the [Wikipedia page](http://en.wikipedia.org/wiki/Matrix_(mathematics)) a few dozen times. This is just an interest of mine that I saw was lacking in implementation in the open-source JavaScript community at large, so I wanted to attempt to fill the gap. 115 | 116 | If you are a professional linear algebraist or even an amateur like me, if there is a bug please open an issue. If there is a feature this DSL should have, more common operations or methods, please point me to them or be hardcore and send me the pull request. 117 | 118 | Contributions are incredibly welcome as long as they are standardly applicable and pass the tests (or break bad ones). Tests are written in Mocha and assertions are done with the Node.js core `assert` module. 119 | 120 | ```bash 121 | # generating source 122 | npm run build 123 | # running tests 124 | npm test 125 | ``` 126 | 127 | Follow me on [Twitter](https://twitter.com/compooter) for updates or just for the lolz and please check out my other [repositories](https://github.com/andrejewski) if I have earned it. I thank you for reading. 128 | 129 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "matt", 3 | "version": "0.0.5", 4 | "description": "JavaScript DSL for Matrices", 5 | "main": "lib/index.js", 6 | "scripts": { 7 | "prepublish": "./node_modules/babel-cli/bin/babel.js src --out-dir lib", 8 | "build": "npm run prepublish", 9 | "test": "mocha" 10 | }, 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/andrejewski/matt.git" 14 | }, 15 | "keywords": [ 16 | "matrix", 17 | "matrices", 18 | "vector" 19 | ], 20 | "author": "Chris Andrejewski ", 21 | "license": "ISC", 22 | "bugs": { 23 | "url": "https://github.com/andrejewski/matt/issues" 24 | }, 25 | "homepage": "https://github.com/andrejewski/matt", 26 | "devDependencies": { 27 | "babel-cli": "^6.2.0", 28 | "mocha": "^2.0.1" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Matrix.js: -------------------------------------------------------------------------------- 1 | 2 | import det from './determinant'; 3 | import inv from './invert'; 4 | 5 | export default class Matrix { 6 | constructor(rows, cols, elements) { 7 | if(cols === void 0 && elements === void 0) { 8 | return fromTable(rows); 9 | } 10 | 11 | this.rows = rows; 12 | this.cols = cols; 13 | this.size = rows * cols; 14 | this.elements = elements; 15 | } 16 | 17 | _index(row, col) { 18 | return (row * this.cols) + col; 19 | } 20 | 21 | _reverseIndex(index) { 22 | return [ 23 | Math.floor(index / this.rows), 24 | index % this.rows 25 | ]; 26 | } 27 | 28 | get(row, col) { 29 | var index = this._index(row, col); 30 | return this.elements[index]; 31 | } 32 | 33 | set(row, col, value) { 34 | var elems = this.elements.slice(0); 35 | elems[this._index(row, col)] = value; 36 | return new Matrix(this.rows, this.cols, elems); 37 | } 38 | 39 | getRow(row) { 40 | var lead = row * this.cols; 41 | return this.elements.slice(lead, lead + this.cols); 42 | } 43 | 44 | setRow(row, elements) { 45 | if (!elements || elements.length !== this.rows) { 46 | throw new Error(`Length of provided elements should be ${this.rows}`); 47 | } 48 | return this.map((x, r, c) => { 49 | return (r === row) ? elements[c] : x; 50 | }); 51 | } 52 | 53 | getColumn(col) { 54 | var elems = new Array(this.rows); 55 | for(var i = this.rows - 1; i >= 0; i--) { 56 | elems[i] = this.get(i, col); 57 | } 58 | return elems; 59 | } 60 | 61 | setColumn(col, elements) { 62 | if (!elements || elements.length !== this.cols) { 63 | throw new Error(`Length of provided elements should be ${this.cols}`); 64 | } 65 | return this.map((x, r, c) => { 66 | return (c === col) ? elements[r] : x; 67 | }); 68 | } 69 | 70 | getDiagonal(offset = 0) { 71 | var elems = new Array(this.rows - offset); 72 | for(var i = this.rows - (offset + 1); i >= 0; i--) { 73 | elems[i] = this.get(i, i + offset); 74 | } 75 | return elems; 76 | } 77 | 78 | getRightDiagonal(offset = 0) { 79 | var elems = new Array(this.rows - offset); 80 | for(var i = this.rows - (offset + 1); i >= 0; i--) { 81 | elems[i] = this.get(i, this.cols - (i + 1) - offset); 82 | } 83 | return elems; 84 | } 85 | 86 | trace() { 87 | return this.getDiagonal().reduce((x,y) => x + y); 88 | } 89 | 90 | rightTrace() { 91 | return this.getRightDiagonal().reduce((x,y) => x + y); 92 | } 93 | 94 | add(matrix, operation = (x,y) => x + y) { 95 | if(!( 96 | this.size === matrix.size && 97 | this.rows === matrix.rows && 98 | this.cols === matrix.cols 99 | )) { 100 | throw new Error('Cannot add matrices of different dimensions', this, matrix); 101 | } 102 | var elems = new Array(this.size); 103 | for(var i = elems.length - 1; i >= 0; i--) { 104 | elems[i] = operation(this.elements[i], matrix.elements[i]); 105 | } 106 | return new Matrix(this.rows, this.cols, elems); 107 | } 108 | 109 | subtract(matrix, operation) { 110 | return this.add(matrix.scale(-1), operation); 111 | } 112 | 113 | multiply(matrix, operation = (x,y) => x * y) { 114 | if(this.cols !== matrix.rows) { 115 | throw new Error('Cannot multiply matrices as rows != cols', this, matrix); 116 | } 117 | var size = this.rows * matrix.cols; 118 | var elems = new Array(size); 119 | var ret = new Matrix(this.rows, matrix.cols, elems); 120 | 121 | for(var i = 0; i < size; i++) { 122 | var [row, col] = ret._reverseIndex(i); 123 | var sum = 0; 124 | for(var k = this.cols - 1; k >= 0; k--) { 125 | sum += operation(this.get(row, k), matrix.get(k, col)); 126 | } 127 | elems[i] = sum; 128 | } 129 | return ret; 130 | } 131 | 132 | joinHorizontal(matrix) { 133 | if(this.rows !== matrix.rows) { 134 | throw new Error('Cannot join matrices with different row sizes', this, matrix); 135 | } 136 | var elems = []; 137 | for(var i = this.rows - 1; i >= 0; i--) { 138 | var left = this.getRow(i); 139 | var right = matrix.getRow(i); 140 | elems = elems.concat(left, right); 141 | } 142 | return new Matrix( 143 | this.rows, 144 | this.cols + matrix.cols, 145 | elems 146 | ); 147 | } 148 | 149 | joinVertical(matrix) { 150 | if(this.cols !== matrix.cols) { 151 | throw new Error('Cannot join matrices with different column sizes', this, matrix); 152 | } 153 | return new Matrix( 154 | this.rows + matrix.rows, 155 | this.cols, 156 | this.elements.concat(matrix.elements) 157 | ); 158 | } 159 | 160 | clone() { 161 | return new Matrix( 162 | this.rows, 163 | this.cols, 164 | this.elements 165 | ); 166 | } 167 | 168 | map(fn) { 169 | var elems = this.elements.map(function(x, i) { 170 | var [r,c] = this._reverseIndex(i); 171 | return fn(x, r, c, this); 172 | }, this); 173 | return new Matrix(this.rows, this.cols, elems); 174 | } 175 | 176 | fmap(fn) { 177 | /* 178 | Full map calls fn on every element whereas 179 | map() ignores undefined indexes 180 | */ 181 | var elems = new Array(this.size); 182 | for(var i = this.size - 1; i >= 0; i--) { 183 | var [r,c] = this._reverseIndex(i); 184 | elems[i] = fn(this.elements[i], r, c, this); 185 | } 186 | return new Matrix(this.rows, this.cols, elems); 187 | } 188 | 189 | forEach(fn) { 190 | var elems = this.elements.forEach(function(x, i) { 191 | var [r,c] = this._reverseIndex(i); 192 | fn(x, r, c, this); 193 | }, this); 194 | } 195 | 196 | reduce(fn, memo) { 197 | this.forEach(function(val, row, col, matrix) { 198 | if(row + col === 0 && memo === void 0) { 199 | return memo = val; 200 | } 201 | memo = fn(memo, val, row, col, matrix); 202 | }); 203 | return memo; 204 | } 205 | 206 | scale(scalar) { 207 | return new Matrix( 208 | this.rows, 209 | this.cols, 210 | this.elements.map(x => x * scalar) 211 | ); 212 | } 213 | 214 | transpose() { 215 | var m = new Matrix(this.cols, this.rows, new Array(this.size)); 216 | this.forEach(function(val, row, col) { 217 | m.set(col, row, val); 218 | }); 219 | return m; 220 | } 221 | 222 | identity() { 223 | if(!this.isSquare()) { 224 | throw new Error('Cannot invert a non-square matrix', this); 225 | } 226 | return this.map((v, r, c) => r - c ? 1 : 0); 227 | } 228 | 229 | submatrix(tlr, tlc, brr, brc) { 230 | var rows = brr - tlr; 231 | var cols = brc - tlc; 232 | var elems = []; 233 | this.forEach(function(val, row, col) { 234 | if( 235 | (tlr <= row && row < brr) || 236 | (tlc <= col && col < brc) 237 | ) { 238 | elems.push(val); 239 | } 240 | }); 241 | return new Matrix(rows, cols, elems); 242 | } 243 | 244 | minor(row, col) { 245 | var elems = []; 246 | this.forEach(function(val, vrow, vcol) { 247 | if(vrow !== row && vcol !== col) { 248 | elems.push(val); 249 | } 250 | }); 251 | return new Matrix(this.rows-1, this.cols-1, elems); 252 | } 253 | 254 | cofactor(row, col) { 255 | var n = this.minor(row, col).determinant(); 256 | return (row+col) % 2 ? n : -n; 257 | } 258 | 259 | cofactorMatrix() { 260 | return this.map((_, row, col, mat) => mat.cofactor(row, col)); 261 | } 262 | 263 | invert() { 264 | if(!this.isSquare()) { 265 | throw new Error('Cannot invert a non-square matrix', this); 266 | } 267 | var det = this.determinant(); 268 | if(det === 0) { 269 | throw new Error('Cannot invert a square matrix whose det = 0', this); 270 | } 271 | return inv(this, det); 272 | } 273 | 274 | determinant() { 275 | if(!this.isSquare()) { 276 | throw new Error('Cannot get the determinant of a non-square matrix', this); 277 | } 278 | return det(this); 279 | } 280 | 281 | isSquare() { 282 | return this.rows === this.cols; 283 | } 284 | 285 | equals(matrix) { 286 | return ( 287 | matrix && 288 | this.size === matrix.size && 289 | this.rows === matrix.rows && 290 | this.cols === matrix.cols && 291 | matrix.elements && 292 | this.elements.valueOf === matrix.elements.valueOf 293 | ); 294 | } 295 | 296 | toArray() { 297 | return this.elements; 298 | } 299 | 300 | toTable() { 301 | var table = new Array(this.rows); 302 | for(var i = this.rows - 1; i >= 0; i--) { 303 | var lead = i * this.cols; 304 | table[i] = this.elements.slice(lead, lead + this.cols); 305 | } 306 | return table; 307 | } 308 | 309 | toString() { 310 | if(this.rows === 1) { 311 | return '['+this.elements.join(' ')+']'; 312 | } 313 | 314 | var nums = this.elements.map(x => x.toString()); 315 | var maxl = nums.reduce( 316 | ((s,c) => s > c.length ? s : c.length), []); 317 | 318 | nums = nums.map(n => 319 | (new Array(maxl - n.length).join(" "))+(n || '-')+" " 320 | ); 321 | 322 | var str = ""; 323 | for(var i = 0; i < this.rows; i++) { 324 | var lead = this.cols * i; 325 | var inner = nums 326 | .slice(lead, lead + this.cols) 327 | .join(''); 328 | if(i === 0) { 329 | str += '⎡ '+inner+'⎤\n'; 330 | } else if(i === this.rows - 1) { 331 | str += '⎣ '+inner+'⎦'; 332 | } else { 333 | str += '⎜ '+inner+'⎟\n'; 334 | } 335 | } 336 | return str; 337 | } 338 | 339 | } 340 | 341 | function fromTable(table) { 342 | var rows = table.length; 343 | var cols = table[0].length; 344 | var elems = new Array(rows * cols); 345 | for(var r = rows - 1; r >= 0; r--) { 346 | for(var c = cols - 1; c >= 0; c--) { 347 | elems[(r * cols) + c] = table[r][c]; 348 | } 349 | } 350 | return new Matrix(rows, cols, elems); 351 | } 352 | 353 | -------------------------------------------------------------------------------- /src/determinant.js: -------------------------------------------------------------------------------- 1 | 2 | export default function det(matrix) { 3 | switch(matrix.rows) { 4 | case 1: return det1(matrix); 5 | case 2: return det2(matrix); 6 | case 3: return det3(matrix); 7 | default: return detN(matrix); 8 | } 9 | } 10 | 11 | // http://en.wikipedia.org/wiki/Determinant#Definition 12 | 13 | function det1(matrix) { 14 | return matrix.get(0,0); 15 | } 16 | 17 | function det2(m) { 18 | /* 19 | | a b | 20 | | c d | = ad - bc 21 | */ 22 | var [a,b,c,d] = m.elements; 23 | return (a * d) - (b * c); 24 | } 25 | 26 | function det3(m) { 27 | /* 28 | | a b c | 29 | | d e f | = aei + bfg + cdh - ceg - bdi - afh 30 | | g h i | 31 | */ 32 | var [a,b,c,d,e,f,g,h,i] = m.elements; 33 | return ( 34 | (a * e * i) + 35 | (b * f * g) + 36 | (c * d * h) - 37 | (c * e * g) - 38 | (b * d * i) - 39 | (a * f * h) 40 | ); 41 | } 42 | 43 | /* 44 | Speed boosts could be made by deriving more 45 | literal solving functions. 46 | */ 47 | 48 | function detN(m) { 49 | /* 50 | | a b c d | 51 | | e f g h | 52 | | i j k l | = idk lol 53 | | m n o p | 54 | */ 55 | return m.getRow(0) 56 | .map((v, i) => v * det(m.minor(0, i)) * Math.pow(-1, i)) 57 | .reduce((x,y) => x + y); 58 | // ^ need to put this one on my resume 59 | } 60 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 2 | import Matrix from './Matrix'; 3 | 4 | // export default var Matt = {Matrix}; 5 | 6 | var Matt = {Matrix}; 7 | module.exports = Matt; 8 | -------------------------------------------------------------------------------- /src/invert.js: -------------------------------------------------------------------------------- 1 | 2 | // import Matrix from './Matrix'; 3 | // ^ bug with dependencies *shock* 4 | 5 | function MatrixClass(m) { 6 | return m.constructor; 7 | } 8 | 9 | // http://en.wikipedia.org/wiki/Invertible_matrix#Methods_of_matrix_inversion 10 | export default function inv(matrix, det) { 11 | switch(matrix.rows) { 12 | case 1: return inv1(matrix, det); 13 | case 2: return inv2(matrix, det); 14 | case 3: return inv3(matrix, det); 15 | default: return invN(matrix, det); 16 | } 17 | } 18 | 19 | 20 | 21 | function inv1(m, det) { 22 | var Matrix = MatrixClass(m); 23 | return new Matrix(1, 1, [1/det]); 24 | } 25 | 26 | function inv2(m, det) { 27 | var Matrix = MatrixClass(m); 28 | /* 29 | ( a b )^-1 ___1__ ( d -b ) 30 | ( c d ) = det(A) ( -c a ) 31 | */ 32 | var [a,b,c,d] = m.elements; 33 | var B = new Matrix(2, 2, [d, -b, -c, a]); 34 | return B.scale(1/det); 35 | } 36 | 37 | function inv3(m, det) { 38 | var Matrix = MatrixClass(m); 39 | /* 40 | lol, I ain't deriving this 41 | */ 42 | var [a,b,c,d,e,f,g,h,i] = m.elements; 43 | return new Matrix(3, 3, [ 44 | (e*i)-(f*h), -((b*i)-(c*h)), (b*f)-(c*e), 45 | -((d*i)-(f*g)), (a*i)-(c*g), -((a*f)-(c*d)), 46 | (d*h)-(e*g), -((a*h)-(b*g)), (a*e)-(b*d) 47 | ]).scale(1/det); 48 | } 49 | 50 | function invN(m, det) { 51 | return m 52 | .cofactorMatrix() 53 | .transpose() 54 | .scale(1/det); 55 | } 56 | -------------------------------------------------------------------------------- /test/Matrix.js: -------------------------------------------------------------------------------- 1 | 2 | var assert = require('assert'); 3 | var Matrix = require('..').Matrix; 4 | 5 | describe('Matrix', function() { 6 | beforeEach(function() { 7 | this.m1 = new Matrix(3, 3, [1,2,3,1,2,3,1,2,3]); 8 | this.m2 = new Matrix(2, 2, [1,2,3,4]); 9 | }); 10 | 11 | describe('constructor(rows Number, cols Number, elements [Any]) Matrix', function() { 12 | it('should accept a multi-dimensional array', function() { 13 | var m3 = new Matrix([ 14 | [1,2,3], 15 | [1,2,3], 16 | [1,2,3] 17 | ]); 18 | assert(this.m1.equals(m3)); 19 | 20 | var m4 = new Matrix([ 21 | [1,2], 22 | [3,4] 23 | ]); 24 | assert(this.m2.equals(m4)); 25 | 26 | assert(this.m1.equals(new Matrix(this.m1.toTable()))); 27 | assert(this.m2.equals(new Matrix(this.m2.toTable()))); 28 | }); 29 | it('should initialize with a rows Number property', function() { 30 | assert.equal(this.m1.rows, 3); 31 | assert.equal(this.m2.rows, 2); 32 | }); 33 | 34 | it('should initialize with a cols Number property', function() { 35 | assert.equal(this.m1.cols, 3); 36 | assert.equal(this.m2.cols, 2); 37 | }); 38 | 39 | it('should initialize with a size Number property', function() { 40 | assert.equal(this.m1.size, 9); 41 | assert.equal(this.m2.size, 4); 42 | }); 43 | 44 | it('should initialize with an elements [Any] property', function() { 45 | assert.equal(this.m1.elements.length, 9); 46 | assert.equal(this.m2.elements.length, 4); 47 | 48 | assert.deepEqual( 49 | [1,2,3,1,2,3,1,2,3], 50 | this.m1.elements 51 | ); 52 | 53 | assert.deepEqual( 54 | [1,2,3,4], this.m2.elements 55 | ); 56 | }); 57 | }); 58 | 59 | describe('get(row Number, col Number) Any', function() { 60 | it('should return the element at the specified position', function() { 61 | assert(this.m1.get(0,0) === 1); 62 | assert(this.m2.get(0,0) === 1); 63 | 64 | assert(this.m1.get(2,2) === 3); 65 | assert(this.m2.get(1,1) === 4); 66 | 67 | assert(this.m1.get(1,0) === 1); 68 | assert(this.m2.get(1,0) === 3); 69 | }); 70 | }); 71 | 72 | describe('set(row Number, col Number, value Any) Matrix', function() { 73 | it('should return the element at the specified position', function() { 74 | assert(this.m1.set(0,0,1).get(0,0) === 1); 75 | assert(this.m2.set(0,0,1).get(0,0) === 1); 76 | 77 | assert(this.m1.set(2,2,3).get(2,2) === 3); 78 | assert(this.m2.set(1,1,4).get(1,1) === 4); 79 | 80 | assert(this.m1.set(1,0,1).get(1,0) === 1); 81 | assert(this.m2.set(1,0,3).get(1,0) === 3); 82 | }); 83 | 84 | it('should return a new matrix', function() { 85 | assert(this.m1 !== this.m1.set(0,0,5)); 86 | assert(this.m2 !== this.m2.set(2,2,5)); 87 | }); 88 | }); 89 | 90 | describe('getRow(row Number) [Any]', function() { 91 | it('should return the matrix elements of a given row', function() { 92 | assert.deepEqual([1,2,3], this.m1.getRow(0)); 93 | assert.deepEqual([1,2,3], this.m1.getRow(1)); 94 | assert.deepEqual([1,2,3], this.m1.getRow(2)); 95 | 96 | assert.deepEqual([1,2], this.m2.getRow(0)); 97 | assert.deepEqual([3,4], this.m2.getRow(1)); 98 | }); 99 | }); 100 | 101 | describe('setRow(row Number, elements [Any]) [Matrix]', function() { 102 | it('should return a new matrix with updated row', function() { 103 | var m3 = this.m1.setRow(0, [4,5,6]); 104 | assert.deepEqual([1,2,3], this.m1.getRow(0)); 105 | assert.deepEqual([4,5,6], m3.getRow(0)); 106 | }); 107 | it('should throw if not enough elements are provided', function() { 108 | var m1 = this.m1; 109 | 110 | assert.throws(function() { 111 | m1.setRow(0, [4, 5]); 112 | }) 113 | }); 114 | it('should throw if more elements are provided', function() { 115 | var m1 = this.m1; 116 | 117 | assert.throws(function() { 118 | m1.setRow(0, [4, 5, 6, 7]); 119 | }) 120 | }); 121 | }); 122 | 123 | describe('getColumn(col Number) [Any]', function() { 124 | it('should return the matrix elements of a given column', function() { 125 | assert.deepEqual([1,1,1], this.m1.getColumn(0)); 126 | assert.deepEqual([2,2,2], this.m1.getColumn(1)); 127 | assert.deepEqual([3,3,3], this.m1.getColumn(2)); 128 | 129 | assert.deepEqual([1,3], this.m2.getColumn(0)); 130 | assert.deepEqual([2,4], this.m2.getColumn(1)); 131 | }); 132 | }); 133 | 134 | describe('setColumn(col Number, elements [Any]) [Matrix]', function() { 135 | it('should return a new matrix with updated column', function() { 136 | var m3 = this.m1.setColumn(0, [4,5,6]); 137 | assert.deepEqual([1,1,1], this.m1.getColumn(0)); 138 | assert.deepEqual([4,5,6], m3.getColumn(0)); 139 | }); 140 | 141 | it('should throw if not enough elements are provided', function() { 142 | var m1 = this.m1; 143 | 144 | assert.throws(function() { 145 | m1.setColumn(0, [4, 5]); 146 | }) 147 | }); 148 | it('should throw if more elements are provided', function() { 149 | var m1 = this.m1; 150 | 151 | assert.throws(function() { 152 | m1.setColumn(0, [4, 5, 6, 7]); 153 | }) 154 | }); 155 | }); 156 | 157 | describe('getDiagonal() [Any]', function() { 158 | it('should return the left diagonal elements of the matrix', function() { 159 | assert.deepEqual([1,2,3], this.m1.getDiagonal()); 160 | assert.deepEqual([1,4], this.m2.getDiagonal()); 161 | }); 162 | it('should return the left diagonal elements right of given offset', function() { 163 | assert.deepEqual([1,2,3], this.m1.getDiagonal(0)); 164 | assert.deepEqual([1,4], this.m2.getDiagonal(0)); 165 | 166 | assert.deepEqual([2,3], this.m1.getDiagonal(1)); 167 | assert.deepEqual([2], this.m2.getDiagonal(1)); 168 | 169 | assert.deepEqual([3], this.m1.getDiagonal(2)); 170 | assert.deepEqual([], this.m2.getDiagonal(2)); 171 | }); 172 | }); 173 | 174 | describe('getRightDiagonal() [Any]', function() { 175 | it('should return the right diagonal elements of the matrix', function() { 176 | assert.deepEqual([3,2,1], this.m1.getRightDiagonal()); 177 | assert.deepEqual([2,3], this.m2.getRightDiagonal()); 178 | }); 179 | it('should return the right diagonal elements left of given offset', function() { 180 | assert.deepEqual([3,2,1], this.m1.getRightDiagonal(0)); 181 | assert.deepEqual([2,3], this.m2.getRightDiagonal(0)); 182 | 183 | assert.deepEqual([2,1], this.m1.getRightDiagonal(1)); 184 | assert.deepEqual([1], this.m2.getRightDiagonal(1)); 185 | 186 | assert.deepEqual([1], this.m1.getRightDiagonal(2)); 187 | assert.deepEqual([], this.m2.getRightDiagonal(2)); 188 | }); 189 | }); 190 | 191 | describe('trace() Number', function() { 192 | it('should return the sum of left diagonal elements of the matrix', function() { 193 | assert.equal(6, this.m1.trace()); 194 | assert.equal(5, this.m2.trace()); 195 | }); 196 | }); 197 | 198 | describe('rightTrace() Number', function() { 199 | it('should return the sum of right diagonal elements of the matrix', function() { 200 | assert.equal(6, this.m1.rightTrace()); 201 | assert.equal(5, this.m2.rightTrace()); 202 | }); 203 | }); 204 | 205 | describe('add(matrix Matrix, ) Matrix', function() { 206 | it('should return this matrix added to the given matrix', function() { 207 | var m1 = this.m1; 208 | assert(m1.add(m1).equals(m1.scale(2))); 209 | 210 | var m2 = this.m2; 211 | assert(m2.add(m2).equals(m2.scale(2))); 212 | }); 213 | it('should add with the reduce function if provided', function() { 214 | function id(x,y) { 215 | return x; 216 | } 217 | 218 | var m1 = this.m1; 219 | var m2 = this.m2; 220 | assert(m1.equals(m1.add(m1, id))); 221 | assert(m2.equals(m2.add(m2, id))); 222 | }); 223 | it('should throw if the matrices cannot be added', function() { 224 | var m1 = this.m1; 225 | var m2 = this.m2; 226 | 227 | assert.doesNotThrow(function() { 228 | m1.add(m1); 229 | m2.add(m2); 230 | }); 231 | assert.throws(function() { 232 | m1.add(m2); 233 | }); 234 | assert.throws(function() { 235 | m2.add(m1); 236 | }); 237 | }); 238 | }); 239 | 240 | describe('subtract(matrix Matrix, ) Matrix', function() { 241 | it('should return this matrix subtracted by the given matrix', function() { 242 | var m1 = this.m1; 243 | assert(m1.subtract(m1).equals(m1.scale(0))); 244 | 245 | var m2 = this.m2; 246 | assert(m2.subtract(m2).equals(m2.scale(0))); 247 | }); 248 | it('should subtract with the reduce function if provided', function() { 249 | function id(x,y) { 250 | return x; 251 | } 252 | 253 | var m1 = this.m1; 254 | var m2 = this.m2; 255 | assert(m1.equals(m1.subtract(m1, id))); 256 | assert(m2.equals(m2.subtract(m2, id))); 257 | }); 258 | it('should throw if the matrices cannot be subtracted', function() { 259 | var m1 = this.m1; 260 | var m2 = this.m2; 261 | 262 | assert.doesNotThrow(function() { 263 | m1.subtract(m1); 264 | m2.subtract(m2); 265 | }); 266 | assert.throws(function() { 267 | m1.subtract(m2); 268 | }); 269 | assert.throws(function() { 270 | m2.subtract(m1); 271 | }); 272 | }); 273 | }); 274 | 275 | describe('multiply(matrix Matrix, ) Matrix', function() { 276 | it('should return this matrix multipled by the given matrix', function() { 277 | var m1 = this.m1; 278 | var m2 = this.m2; 279 | var m3 = this.m1.multiply(m1); 280 | 281 | assert(m1.rows === m3.rows); 282 | assert(m1.cols === m3.cols); 283 | assert(m1.size === m3.size); 284 | assert(m3.equals(new Matrix(3,3, [ 285 | 30, 36, 42, 286 | 66, 81, 96, 287 | 102, 126, 150 288 | ]))); 289 | 290 | var m4 = this.m2.multiply(m2); 291 | 292 | assert(m2.rows === m4.rows); 293 | assert(m2.cols === m4.cols); 294 | assert(m2.size === m4.size); 295 | assert(m4.equals(new Matrix(2,2, [ 296 | 7, 10, 297 | 15, 22 298 | ]))); 299 | 300 | }); 301 | it('should add with the reduce function if provided', function() { 302 | function id(x,y) { 303 | return x; 304 | } 305 | 306 | var m1 = this.m1; 307 | var m2 = this.m2; 308 | assert(m1.equals(m1.multiply(m1, id))); 309 | assert(m2.equals(m2.multiply(m2, id))); 310 | }); 311 | it('should throw if the matrices cannot be multiply', function() { 312 | var m1 = this.m1; 313 | var m2 = this.m2; 314 | 315 | assert.doesNotThrow(function() { 316 | m1.multiply(m1); 317 | m2.multiply(m2); 318 | }); 319 | assert.throws(function() { 320 | m1.multiply(m2); 321 | }); 322 | assert.throws(function() { 323 | m2.multiply(m1); 324 | }); 325 | }); 326 | }); 327 | 328 | describe('joinHorizontal(matrix Matrix) Matrix', function() { 329 | it('should return this matrix joined horizontally by the given matrix', function() { 330 | var m3 = this.m1.joinHorizontal(this.m1); 331 | var m4 = new Matrix(3, 6, [ 332 | 1, 2, 3, 1, 2, 3, 333 | 1, 2, 3, 1, 2, 3, 334 | 1, 2, 3, 1, 2, 3 335 | ]); 336 | assert(m3.equals(m4)); 337 | 338 | var m5 = this.m2.joinHorizontal(this.m2); 339 | var m6 = new Matrix(2, 4, [ 340 | 1, 2, 1, 2, 341 | 3, 4, 3, 4 342 | ]); 343 | assert(m5.equals(m6)); 344 | }); 345 | it('should throw if the matrices cannot be joined horizontally', function() { 346 | var m1 = this.m1; 347 | var m2 = this.m2; 348 | 349 | assert.doesNotThrow(function() { 350 | m1.joinHorizontal(m1); 351 | m2.joinHorizontal(m2); 352 | }); 353 | assert.throws(function() { 354 | m1.joinHorizontal(m2); 355 | }); 356 | assert.throws(function() { 357 | m2.joinHorizontal(m1); 358 | }); 359 | }); 360 | }); 361 | 362 | describe('joinVertical(matrix Matrix) Matrix', function() { 363 | it('should return this matrix joined vertically by the given matrix', function() { 364 | var m3 = this.m1.joinVertical(this.m1); 365 | var m4 = new Matrix(6, 3, [ 366 | 1, 2, 3, 367 | 1, 2, 3, 368 | 1, 2, 3, 369 | 1, 2, 3, 370 | 1, 2, 3, 371 | 1, 2, 3 372 | ]); 373 | assert(m3.equals(m4)); 374 | 375 | var m5 = this.m2.joinVertical(this.m2); 376 | var m6 = new Matrix(4, 2, [ 377 | 1, 2, 378 | 3, 4, 379 | 1, 2, 380 | 3, 4 381 | ]); 382 | assert(m5.equals(m6)); 383 | }); 384 | it('should throw if the matrices cannot be joined vertically', function() { 385 | var m1 = this.m1; 386 | var m2 = this.m2; 387 | 388 | assert.doesNotThrow(function() { 389 | m1.joinVertical(m1); 390 | m2.joinVertical(m2); 391 | }); 392 | assert.throws(function() { 393 | m1.joinVertical(m2); 394 | }); 395 | assert.throws(function() { 396 | m2.joinVertical(m1); 397 | }); 398 | }); 399 | }); 400 | 401 | describe('clone() Matrix', function() { 402 | it('should return a copy of this matrix', function() { 403 | assert(this.m1 !== this.m1.clone()); 404 | assert(this.m1.equals(this.m1.clone())); 405 | }); 406 | }); 407 | 408 | describe('map(fn Function(element Any, row Number, col Number, matrix Matrix)) Matrix', function() { 409 | it('should return a transformed matrix', function() { 410 | var m3 = this.m2.map(function(x) { 411 | return x * 2; 412 | }); 413 | var m4 = this.m2.scale(2); 414 | 415 | assert(m3 instanceof Matrix); 416 | assert(m3.equals(m4)); 417 | }); 418 | }); 419 | 420 | describe('fmap(fn Function(element Any, row Number, col Number, matrix Matrix)) Matrix', function() { 421 | it('should return a transformed matrix', function() { 422 | var m3 = this.m2.fmap(function(x) { 423 | return x * 2; 424 | }); 425 | var m4 = this.m2.scale(2); 426 | 427 | assert(m3 instanceof Matrix); 428 | assert(m3.equals(m4)); 429 | }); 430 | it('should map all (even undefined) indexes', function() { 431 | function f0() {return 0;} 432 | var z1 = this.m1.fmap(f0); 433 | 434 | var m3 = new Matrix(3, 3, []); 435 | var z3 = m3.fmap(f0); 436 | 437 | var z0 = new Matrix(3, 3, [ 438 | 0, 0, 0, 439 | 0, 0, 0, 440 | 0, 0, 0 441 | ]); 442 | assert(z1.equals(z0)); 443 | assert(z3.equals(z0)); 444 | assert(z1.equals(z3)); 445 | }); 446 | }); 447 | 448 | describe('forEach(fn Function(element Any, row Number, col Number, matrix Matrix)) void', function() { 449 | it('should call the given function with each element', function() { 450 | var m1 = this.m1; 451 | var m3 = new Matrix(3,3, new Array(9)); 452 | var calls = 0; 453 | m1.forEach(function(val, row, col, mat) { 454 | calls++; 455 | m3.set(row, col, val); 456 | }); 457 | 458 | assert.equal(calls, 9); 459 | assert(m3.equals(m1)); 460 | }); 461 | }); 462 | 463 | describe('reduce(fn Function(acc Any, value Any, row Number, col Number, matrix Matrix), ) Matrix', function() { 464 | it('should return the accumulated value of fn on the matrix', function() { 465 | function add(x,y) {return x + y;} 466 | 467 | assert.equal(18, this.m1.reduce(add)); 468 | assert.equal(10, this.m2.reduce(add)); 469 | }); 470 | it('should accept an initial accumulator value', function() { 471 | function add(x,y) {return x + y;} 472 | 473 | assert.equal(38, this.m1.reduce(add, 20)); 474 | assert.equal(30, this.m2.reduce(add, 20)); 475 | }); 476 | }); 477 | 478 | describe('scale(num Number) Matrix', function() { 479 | it('should return a new scaled matrix', function() { 480 | var m3 = new Matrix(2, 2, [2,4,6,8]); 481 | var m4 = this.m2.scale(2); 482 | 483 | assert(m4 instanceof Matrix); 484 | assert(m3.equals(m4)); 485 | }); 486 | }); 487 | 488 | describe('transpose() Matrix', function() { 489 | it('should return the transposed form of this matrix', function() { 490 | var m1 = this.m1; 491 | var m2 = this.m2; 492 | var m3 = this.m1.transpose(); 493 | 494 | assert(m1.size === m3.size); 495 | assert(m1.rows === m3.cols); 496 | assert(m1.cols === m3.rows); 497 | 498 | var m4 = this.m2.transpose(); 499 | 500 | assert(m2.size === m4.size); 501 | assert(m2.rows === m4.cols); 502 | assert(m2.cols === m4.rows); 503 | 504 | // http://en.wikipedia.org/wiki/Transpose#Properties 505 | var A = new Matrix(2, 2, [4,5,6,7]); 506 | var B = new Matrix(2, 2, [8,9,10,11]); 507 | 508 | // (A`)` = A 509 | assert(A.transpose().transpose().equals(A)); 510 | 511 | // (A+B)` = A` + B` 512 | assert( 513 | A.add(B).transpose().equals( 514 | A.transpose().add(B.transpose()) 515 | ) 516 | ); 517 | 518 | // (AB)` = B`A` 519 | assert( 520 | A.multiply(B).equals( 521 | B.transpose().multiply(A.transpose()) 522 | ) 523 | ); 524 | 525 | }); 526 | }); 527 | 528 | describe('identity() Matrix', function() { 529 | it('should return the identity matrix of the matrix', function() { 530 | var m3 = new Matrix(3,3, [ 531 | 1, 0, 0, 532 | 0, 1, 0, 533 | 0, 0, 1 534 | ]); 535 | assert(this.m1.identity().equals(m3)); 536 | 537 | var m4 = new Matrix(2,2, [1,0,0,1]); 538 | assert(this.m2.identity().equals(m4)); 539 | }); 540 | it('should throw if the matrix is not square', function() { 541 | var m1 = this.m1; 542 | var m2 = this.m2; 543 | var m3 = new Matrix(1,3, [1,2,3]); 544 | var m4 = new Matrix(3,4, [ 545 | 1, 2, 3, 4, 546 | 5, 6, 7, 8, 547 | 9,10,11,12 548 | ]); 549 | 550 | assert.throws(function() { 551 | m3.invert(); 552 | }); 553 | assert.throws(function() { 554 | m4.invert(); 555 | }); 556 | }); 557 | }); 558 | 559 | describe('submatrix(topLeftRow Number, topLeftCol Number, bottomRightRow Number, bottomRightCol Number) Matrix', function() { 560 | it('should return the specified submatrix', function() { 561 | var m1s = this.m1.submatrix(0,0,2,2); 562 | assert(m1s.equals(new Matrix(2,2, [ 563 | 1, 2, 564 | 1, 2 565 | ]))); 566 | 567 | var m2s = this.m2.submatrix(0,0,1,1); 568 | assert(m2s.equals(new Matrix(1,1,[1]))); 569 | }); 570 | }); 571 | 572 | describe('minor(row Number, col Number) Matrix', function() { 573 | it('should return the specified minor of the matrix', function() { 574 | var m1 = this.m1; 575 | 576 | // top-left 577 | var min1 = m1.minor(0,0); 578 | assert(min1.equals(new Matrix(2,2, [ 579 | 2, 3, 580 | 2, 3 581 | ]))); 582 | 583 | // bottom-left 584 | var min2 = m1.minor(2,0); 585 | assert(min2.equals(new Matrix(2,2, [ 586 | 2, 3, 587 | 2, 3 588 | ]))); 589 | 590 | // top-right 591 | var min3 = m1.minor(0,2); 592 | assert(min3.equals(new Matrix(2,2, [ 593 | 1, 2, 594 | 1, 2, 595 | ]))); 596 | 597 | // bottom-right 598 | var min4 = m1.minor(2,2); 599 | assert(min4.equals(new Matrix(2,2, [ 600 | 1, 2, 601 | 1, 2 602 | ]))); 603 | 604 | // center 605 | var min5 = m1.minor(1,1); 606 | assert(min4.equals(new Matrix(2,2, [ 607 | 1, 3, 608 | 1, 3 609 | ]))); 610 | }); 611 | }); 612 | 613 | describe('cofactor(row Number, col Number) Number', function() { 614 | it('should return the cofactor of the specified matrix element', function() { 615 | var m1 = this.m1; 616 | var c1 = new Matrix(3,3, [ 617 | 0, 0, 0, 618 | 0, 0, 0, 619 | 0, 0, 0 620 | ]); 621 | m1.forEach(function(val, row, col) { 622 | assert.equal( 623 | m1.cofactor(row, col), 624 | c1.get(row, col) 625 | ); 626 | }); 627 | }); 628 | }); 629 | 630 | describe('cofactorMatrix() Matrix', function() { 631 | it('should return the matrix cofactors in a matrix', function() { 632 | var m1 = this.m1.cofactorMatrix(); 633 | var c1 = new Matrix(3,3, [ 634 | 0, 0, 0, 635 | 0, 0, 0, 636 | 0, 0, 0 637 | ]); 638 | assert(m1.equals(c1)); 639 | }); 640 | }); 641 | 642 | describe('invert() Matrix', function() { 643 | it('should return the inversion of the matrix', function() { 644 | // 1x1 645 | assert(new Matrix(1,1,[1]).equals( 646 | new Matrix(1,1,[1]).invert() 647 | )); 648 | assert(new Matrix(1,1,[0.5]).equals( 649 | new Matrix(1,1,[2]).invert() 650 | )); 651 | // 2x2 652 | assert(new Matrix(2,2,[-2, 1, 3/2, (-1/2)]).equals( 653 | this.m2.invert() 654 | )); 655 | // 3x3 656 | var n3 = new Matrix(3,3, [ 657 | 4, 2, 0, 658 | 5, 6, 7, 659 | 1, 3, 9 660 | ]); 661 | var n3i = new Matrix(3,3, [ 662 | 33/56, -9/28, 1/4, 663 | -19/28, 9/14, -1/2, 664 | 9/56, -5/28, 1/4 665 | ]); 666 | assert(n3.invert().equals(n3i)); 667 | // NxN 668 | var n4 = new Matrix(4, 4, [ 669 | 1, 3, 5, 7, 670 | 2, 4, 6, 8, 671 | 4, 6, 2, 8, 672 | 7, 1, 3, 5 673 | ]); 674 | var n4i = new Matrix(4, 4, [ 675 | -3/8, 1/4, 0, 1/8, 676 | -43/40, 19/20, 1/10, -7/40, 677 | -29/40, 17/20, -1/5, -1/40, 678 | 47/40, -21/20, 1/10, 3/40 679 | ]); 680 | assert(n4.invert().equals(n4i)); 681 | }); 682 | it('should throw if the matrix is not square', function() { 683 | var m1 = this.m1; 684 | var m2 = this.m2; 685 | var m3 = new Matrix(1,3, [1,2,3]); 686 | var m4 = new Matrix(3,4, [ 687 | 1, 2, 3, 4, 688 | 5, 6, 7, 8, 689 | 9,10,11,12 690 | ]); 691 | 692 | assert.throws(function() { 693 | m3.invert(); 694 | }); 695 | assert.throws(function() { 696 | m4.invert(); 697 | }); 698 | }); 699 | it('should throw if the determinant is zero', function() { 700 | var m1 = this.m1; 701 | var m2 = this.m2; 702 | 703 | assert.throws(function() { 704 | m1.invert(); 705 | }); 706 | }); 707 | }); 708 | 709 | describe('determinant() Number', function() { 710 | it('should return the determinant of the matrix', function() { 711 | // 1x1 712 | assert.equal(1, new Matrix(1,1,[1]).determinant()); 713 | // 2x2 714 | assert.equal(-2, this.m2.determinant()); 715 | // 3x3 716 | assert.equal(0, this.m1.determinant()); 717 | // NxN 718 | var n4 = new Matrix(4, 4, [ 719 | 1, 3, 5, 7, 720 | 2, 4, 6, 8, 721 | 4, 6, 2, 8, 722 | 7, 1, 3, 5 723 | ]); 724 | assert.equal(160, n4.determinant()); 725 | 726 | var n5 = new Matrix(5,5, [ 727 | 1, 3, 5, 7, 2, 728 | 1, 6, 4, 2, 6, 729 | 5, 7, 2, 1, 3, 730 | 4, 2, 6, 1, 6, 731 | 1, 1, 2, 5, 7 732 | ]); 733 | assert.equal(7410, n5.determinant()); 734 | 735 | var n6 = new Matrix(6,6,[ 736 | 1, 2, 3, 6, 5, 4, 737 | 1, 2, 3, 6, 5, 4, 738 | 1, 2, 3, 6, 5, 4, 739 | 1, 2, 3, 6, 5, 4, 740 | 1, 2, 3, 6, 5, 4, 741 | 1, 2, 3, 6, 5, 4 742 | ]); 743 | assert.equal(0, n6.determinant()); 744 | 745 | var n8 = new Matrix(8,8, [ 746 | 1, 2, 3, 6, 5, 4, 8, 7, 747 | 1, 2, 3, 6, 5, 4, 8, 7, 748 | 1, 2, 3, 6, 5, 4, 8, 7, 749 | 1, 2, 3, 6, 5, 4, 8, 7, 750 | 1, 2, 3, 6, 5, 4, 8, 7, 751 | 1, 2, 3, 6, 5, 4, 8, 7, 752 | 1, 2, 3, 6, 5, 4, 8, 7, 753 | 1, 2, 3, 6, 5, 4, 8, 7 754 | ]); 755 | assert.equal(0, n8.determinant()); 756 | }); 757 | it('should throw if the matrix is not square', function() { 758 | var m1 = this.m1; 759 | var m2 = this.m2; 760 | var m3 = new Matrix(1,3, [1,2,3]); 761 | var m4 = new Matrix(3,4, [ 762 | 1, 2, 3, 4, 763 | 5, 6, 7, 8, 764 | 9,10,11,12 765 | ]); 766 | 767 | assert.doesNotThrow(function() { 768 | m1.determinant(); 769 | m2.determinant(); 770 | }); 771 | assert.throws(function() { 772 | m3.determinant(); 773 | }); 774 | assert.throws(function() { 775 | m4.determinant(); 776 | }); 777 | }); 778 | }); 779 | 780 | describe('isSquare() boolean', function() { 781 | it('should return whether the matrix\'s rows = cols', function() { 782 | assert(this.m1.isSquare()); 783 | assert(this.m2.isSquare()); 784 | 785 | var m3 = new Matrix(1, 3, [2, 4, 5]); 786 | assert(!m3.isSquare()); 787 | }); 788 | }); 789 | 790 | describe('equals(matrix Matrix) boolean', function() { 791 | it('should return whether this equals the given matrix', function() { 792 | assert(this.m1.equals(this.m1)); 793 | assert(this.m2.equals(this.m2)); 794 | 795 | var m1 = new Matrix(3, 3, [1,2,3,1,2,3,1,2,3]); 796 | var m2 = new Matrix(2, 2, [1,2,3,4]); 797 | 798 | assert(m1.equals(this.m1)); 799 | assert(m2.equals(this.m2)); 800 | 801 | assert(!m1.equals(false)); 802 | assert(!m1.equals({ 803 | rows: 3, 804 | cols: 3, 805 | size: 9 806 | })); 807 | assert(!m1.equals(m2)); 808 | }); 809 | }); 810 | 811 | describe('toArray() [Any]', function() { 812 | it('should return an array of matrix elements', function() { 813 | assert(typeof this.m1.toArray() === 'object'); 814 | assert(Array.isArray(this.m1.toArray())); 815 | }); 816 | }); 817 | 818 | describe('toTable() [[Any]]', function() { 819 | it('should return a multi-dimensional array representation of the matrix', function() { 820 | assert.deepEqual(this.m1.toTable(), [ 821 | [1,2,3], 822 | [1,2,3], 823 | [1,2,3] 824 | ]); 825 | }); 826 | }); 827 | 828 | describe('toString() String', function() { 829 | it('should return a string representation of a matrix', function() { 830 | assert(typeof this.m1.toString() === 'string'); 831 | }); 832 | }); 833 | 834 | }); 835 | --------------------------------------------------------------------------------