Path: blob/trunk/third_party/closure/goog/math/matrix.js
2868 views
// Copyright 2007 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Class for representing matrices and static helper functions.16*/1718goog.provide('goog.math.Matrix');1920goog.require('goog.array');21goog.require('goog.asserts');22goog.require('goog.math');23goog.require('goog.math.Size');24goog.require('goog.string');25262728/**29* Class for representing and manipulating matrices.30*31* The entry that lies in the i-th row and the j-th column of a matrix is32* typically referred to as the i,j entry of the matrix.33*34* The m-by-n matrix A would have its entries referred to as:35* [ a0,0 a0,1 a0,2 ... a0,j ... a0,n ]36* [ a1,0 a1,1 a1,2 ... a1,j ... a1,n ]37* [ a2,0 a2,1 a2,2 ... a2,j ... a2,n ]38* [ . . . . . ]39* [ . . . . . ]40* [ . . . . . ]41* [ ai,0 ai,1 ai,2 ... ai,j ... ai,n ]42* [ . . . . . ]43* [ . . . . . ]44* [ . . . . . ]45* [ am,0 am,1 am,2 ... am,j ... am,n ]46*47* @param {!goog.math.Matrix|!Array<!Array<number>>|!goog.math.Size|number} m48* A matrix to copy, a 2D-array to take as a template, a size object for49* dimensions, or the number of rows.50* @param {number=} opt_n Number of columns of the matrix (only applicable if51* the first argument is also numeric).52* @struct53* @constructor54* @final55*/56goog.math.Matrix = function(m, opt_n) {57if (m instanceof goog.math.Matrix) {58this.array_ = m.toArray();59} else if (60goog.isArrayLike(m) &&61goog.math.Matrix.isValidArray(62/** @type {!Array<!Array<number>>} */ (m))) {63this.array_ = goog.array.clone(/** @type {!Array<!Array<number>>} */ (m));64} else if (m instanceof goog.math.Size) {65this.array_ = goog.math.Matrix.createZeroPaddedArray_(m.height, m.width);66} else if (goog.isNumber(m) && goog.isNumber(opt_n) && m > 0 && opt_n > 0) {67this.array_ = goog.math.Matrix.createZeroPaddedArray_(68/** @type {number} */ (m), opt_n);69} else {70throw Error('Invalid argument(s) for Matrix contructor');71}7273this.size_ = new goog.math.Size(this.array_[0].length, this.array_.length);74};757677/**78* Creates a square identity matrix. i.e. for n = 3:79* <pre>80* [ 1 0 0 ]81* [ 0 1 0 ]82* [ 0 0 1 ]83* </pre>84* @param {number} n The size of the square identity matrix.85* @return {!goog.math.Matrix} Identity matrix of width and height {@code n}.86*/87goog.math.Matrix.createIdentityMatrix = function(n) {88var rv = [];89for (var i = 0; i < n; i++) {90rv[i] = [];91for (var j = 0; j < n; j++) {92rv[i][j] = i == j ? 1 : 0;93}94}95return new goog.math.Matrix(rv);96};979899/**100* Calls a function for each cell in a matrix.101* @param {goog.math.Matrix} matrix The matrix to iterate over.102* @param {function(this:T, number, number, number, !goog.math.Matrix)} fn103* The function to call for every element. This function104* takes 4 arguments (value, i, j, and the matrix)105* and the return value is irrelevant.106* @param {T=} opt_obj The object to be used as the value of 'this'107* within {@code fn}.108* @template T109*/110goog.math.Matrix.forEach = function(matrix, fn, opt_obj) {111for (var i = 0; i < matrix.getSize().height; i++) {112for (var j = 0; j < matrix.getSize().width; j++) {113fn.call(opt_obj, matrix.array_[i][j], i, j, matrix);114}115}116};117118119/**120* Tests whether an array is a valid matrix. A valid array is an array of121* arrays where all arrays are of the same length and all elements are numbers.122* @param {!Array<!Array<number>>} arr An array to test.123* @return {boolean} Whether the array is a valid matrix.124*/125goog.math.Matrix.isValidArray = function(arr) {126var len = 0;127for (var i = 0; i < arr.length; i++) {128if (!goog.isArrayLike(arr[i]) || len > 0 && arr[i].length != len) {129return false;130}131for (var j = 0; j < arr[i].length; j++) {132if (!goog.isNumber(arr[i][j])) {133return false;134}135}136if (len == 0) {137len = arr[i].length;138}139}140return len != 0;141};142143144/**145* Calls a function for every cell in a matrix and inserts the result into a146* new matrix of equal dimensions.147* @param {!goog.math.Matrix} matrix The matrix to iterate over.148* @param {function(this:T, number, number, number, !goog.math.Matrix): number}149* fn The function to call for every element. This function150* takes 4 arguments (value, i, j and the matrix)151* and should return a number, which will be inserted into a new matrix.152* @param {T=} opt_obj The object to be used as the value of 'this'153* within {@code fn}.154* @return {!goog.math.Matrix} A new matrix with the results from {@code fn}.155* @template T156*/157goog.math.Matrix.map = function(matrix, fn, opt_obj) {158var m = new goog.math.Matrix(matrix.getSize());159goog.math.Matrix.forEach(matrix, function(value, i, j) {160m.array_[i][j] = fn.call(opt_obj, value, i, j, matrix);161});162return m;163};164165166/**167* Creates a new zero padded matix.168* @param {number} m Height of matrix.169* @param {number} n Width of matrix.170* @return {!Array<!Array<number>>} The new zero padded matrix.171* @private172*/173goog.math.Matrix.createZeroPaddedArray_ = function(m, n) {174var rv = [];175for (var i = 0; i < m; i++) {176rv[i] = [];177for (var j = 0; j < n; j++) {178rv[i][j] = 0;179}180}181return rv;182};183184185/**186* Internal array representing the matrix.187* @type {!Array<!Array<number>>}188* @private189*/190goog.math.Matrix.prototype.array_;191192193/**194* After construction the Matrix's size is constant and stored in this object.195* @type {!goog.math.Size}196* @private197*/198goog.math.Matrix.prototype.size_;199200201/**202* Returns a new matrix that is the sum of this and the provided matrix.203* @param {goog.math.Matrix} m The matrix to add to this one.204* @return {!goog.math.Matrix} Resultant sum.205*/206goog.math.Matrix.prototype.add = function(m) {207if (!goog.math.Size.equals(this.size_, m.getSize())) {208throw Error('Matrix summation is only supported on arrays of equal size');209}210return goog.math.Matrix.map(211this, function(val, i, j) { return val + m.array_[i][j]; });212};213214215/**216* Appends the given matrix to the right side of this matrix.217* @param {goog.math.Matrix} m The matrix to augment this matrix with.218* @return {!goog.math.Matrix} A new matrix with additional columns on the219* right.220*/221goog.math.Matrix.prototype.appendColumns = function(m) {222if (this.size_.height != m.getSize().height) {223throw Error(224'The given matrix has height ' + m.size_.height + ', but ' +225' needs to have height ' + this.size_.height + '.');226}227var result =228new goog.math.Matrix(this.size_.height, this.size_.width + m.size_.width);229goog.math.Matrix.forEach(230this, function(value, i, j) { result.array_[i][j] = value; });231goog.math.Matrix.forEach(m, function(value, i, j) {232result.array_[i][this.size_.width + j] = value;233}, this);234return result;235};236237238/**239* Appends the given matrix to the bottom of this matrix.240* @param {goog.math.Matrix} m The matrix to augment this matrix with.241* @return {!goog.math.Matrix} A new matrix with added columns on the bottom.242*/243goog.math.Matrix.prototype.appendRows = function(m) {244if (this.size_.width != m.getSize().width) {245throw Error(246'The given matrix has width ' + m.size_.width + ', but ' +247' needs to have width ' + this.size_.width + '.');248}249var result = new goog.math.Matrix(250this.size_.height + m.size_.height, this.size_.width);251goog.math.Matrix.forEach(252this, function(value, i, j) { result.array_[i][j] = value; });253goog.math.Matrix.forEach(m, function(value, i, j) {254result.array_[this.size_.height + i][j] = value;255}, this);256return result;257};258259260/**261* Returns whether the given matrix equals this matrix.262* @param {goog.math.Matrix} m The matrix to compare to this one.263* @param {number=} opt_tolerance The tolerance when comparing array entries.264* @return {boolean} Whether the given matrix equals this matrix.265*/266goog.math.Matrix.prototype.equals = function(m, opt_tolerance) {267if (this.size_.width != m.size_.width) {268return false;269}270if (this.size_.height != m.size_.height) {271return false;272}273274var tolerance = opt_tolerance || 0;275for (var i = 0; i < this.size_.height; i++) {276for (var j = 0; j < this.size_.width; j++) {277if (!goog.math.nearlyEquals(278this.array_[i][j], m.array_[i][j], tolerance)) {279return false;280}281}282}283284return true;285};286287288/**289* Returns the determinant of this matrix. The determinant of a matrix A is290* often denoted as |A| and can only be applied to a square matrix.291* @return {number} The determinant of this matrix.292*/293goog.math.Matrix.prototype.getDeterminant = function() {294if (!this.isSquare()) {295throw Error('A determinant can only be take on a square matrix');296}297298return this.getDeterminant_();299};300301302/**303* Returns the inverse of this matrix if it exists or null if the matrix is304* not invertible.305* @return {goog.math.Matrix} A new matrix which is the inverse of this matrix.306*/307goog.math.Matrix.prototype.getInverse = function() {308if (!this.isSquare()) {309throw Error('An inverse can only be taken on a square matrix.');310}311if (this.getSize().width == 1) {312var a = this.getValueAt(0, 0);313return a == 0 ? null : new goog.math.Matrix([[1 / Number(a)]]);314}315var identity = goog.math.Matrix.createIdentityMatrix(this.size_.height);316var mi = this.appendColumns(identity).getReducedRowEchelonForm();317var i = mi.getSubmatrixByCoordinates_(3180, 0, identity.size_.width - 1, identity.size_.height - 1);319if (!i.equals(identity)) {320return null; // This matrix was not invertible321}322return mi.getSubmatrixByCoordinates_(0, identity.size_.width);323};324325326/**327* Transforms this matrix into reduced row echelon form.328* @return {!goog.math.Matrix} A new matrix reduced row echelon form.329*/330goog.math.Matrix.prototype.getReducedRowEchelonForm = function() {331var result = new goog.math.Matrix(this);332var col = 0;333// Each iteration puts one row in reduced row echelon form334for (var row = 0; row < result.size_.height; row++) {335if (col >= result.size_.width) {336return result;337}338339// Scan each column starting from this row on down for a non-zero value340var i = row;341while (result.array_[i][col] == 0) {342i++;343if (i == result.size_.height) {344i = row;345col++;346if (col == result.size_.width) {347return result;348}349}350}351352// Make the row we found the current row with a leading 1353this.swapRows_(i, row);354var divisor = result.array_[row][col];355for (var j = col; j < result.size_.width; j++) {356result.array_[row][j] = result.array_[row][j] / divisor;357}358359// Subtract a multiple of this row from each other row360// so that all the other entries in this column are 0361for (i = 0; i < result.size_.height; i++) {362if (i != row) {363var multiple = result.array_[i][col];364for (var j = col; j < result.size_.width; j++) {365result.array_[i][j] -= multiple * result.array_[row][j];366}367}368}369370// Move on to the next column371col++;372}373return result;374};375376377/**378* @return {!goog.math.Size} The dimensions of the matrix.379*/380goog.math.Matrix.prototype.getSize = function() {381return this.size_;382};383384385/**386* Return the transpose of this matrix. For an m-by-n matrix, the transpose387* is the n-by-m matrix which results from turning rows into columns and columns388* into rows389* @return {!goog.math.Matrix} A new matrix A^T.390*/391goog.math.Matrix.prototype.getTranspose = function() {392var m = new goog.math.Matrix(this.size_.width, this.size_.height);393goog.math.Matrix.forEach(394this, function(value, i, j) { m.array_[j][i] = value; });395return m;396};397398399/**400* Retrieves the value of a particular coordinate in the matrix or null if the401* requested coordinates are out of range.402* @param {number} i The i index of the coordinate.403* @param {number} j The j index of the coordinate.404* @return {?number} The value at the specified coordinate.405*/406goog.math.Matrix.prototype.getValueAt = function(i, j) {407if (!this.isInBounds_(i, j)) {408return null;409}410return this.array_[i][j];411};412413414/**415* @return {boolean} Whether the horizontal and vertical dimensions of this416* matrix are the same.417*/418goog.math.Matrix.prototype.isSquare = function() {419return this.size_.width == this.size_.height;420};421422423/**424* Sets the value at a particular coordinate (if the coordinate is within the425* bounds of the matrix).426* @param {number} i The i index of the coordinate.427* @param {number} j The j index of the coordinate.428* @param {number} value The new value for the coordinate.429*/430goog.math.Matrix.prototype.setValueAt = function(i, j, value) {431if (!this.isInBounds_(i, j)) {432throw Error(433'Index out of bounds when setting matrix value, (' + i + ',' + j +434') in size (' + this.size_.height + ',' + this.size_.width + ')');435}436this.array_[i][j] = value;437};438439440/**441* Performs matrix or scalar multiplication on a matrix and returns the442* resultant matrix.443*444* Matrix multiplication is defined between two matrices only if the number of445* columns of the first matrix is the same as the number of rows of the second446* matrix. If A is an m-by-n matrix and B is an n-by-p matrix, then their447* product AB is an m-by-p matrix448*449* Scalar multiplication returns a matrix of the same size as the original,450* each value multiplied by the given value.451*452* @param {goog.math.Matrix|number} m Matrix/number to multiply the matrix by.453* @return {!goog.math.Matrix} Resultant product.454*/455goog.math.Matrix.prototype.multiply = function(m) {456if (m instanceof goog.math.Matrix) {457if (this.size_.width != m.getSize().height) {458throw Error(459'Invalid matrices for multiplication. Second matrix ' +460'should have the same number of rows as the first has columns.');461}462return this.matrixMultiply_(/** @type {!goog.math.Matrix} */ (m));463} else if (goog.isNumber(m)) {464return this.scalarMultiply_(/** @type {number} */ (m));465} else {466throw Error(467'A matrix can only be multiplied by' +468' a number or another matrix.');469}470};471472473/**474* Returns a new matrix that is the difference of this and the provided matrix.475* @param {goog.math.Matrix} m The matrix to subtract from this one.476* @return {!goog.math.Matrix} Resultant difference.477*/478goog.math.Matrix.prototype.subtract = function(m) {479if (!goog.math.Size.equals(this.size_, m.getSize())) {480throw Error(481'Matrix subtraction is only supported on arrays of equal size.');482}483return goog.math.Matrix.map(484this, function(val, i, j) { return val - m.array_[i][j]; });485};486487488/**489* @return {!Array<!Array<number>>} A 2D internal array representing this490* matrix. Not a clone.491*/492goog.math.Matrix.prototype.toArray = function() {493return this.array_;494};495496497if (goog.DEBUG) {498/**499* Returns a string representation of the matrix. e.g.500* <pre>501* [ 12 5 9 1 ]502* [ 4 16 0 17 ]503* [ 12 5 1 23 ]504* </pre>505*506* @return {string} A string representation of this matrix.507* @override508*/509goog.math.Matrix.prototype.toString = function() {510// Calculate correct padding for optimum display of matrix511var maxLen = 0;512goog.math.Matrix.forEach(this, function(val) {513var len = String(val).length;514if (len > maxLen) {515maxLen = len;516}517});518519// Build the string520var sb = [];521goog.array.forEach(this.array_, function(row, x) {522sb.push('[ ');523goog.array.forEach(row, function(val, y) {524var strval = String(val);525sb.push(goog.string.repeat(' ', maxLen - strval.length) + strval + ' ');526});527sb.push(']\n');528});529530return sb.join('');531};532}533534535/**536* Returns the signed minor.537* @param {number} i The row index.538* @param {number} j The column index.539* @return {number} The cofactor C[i,j] of this matrix.540* @private541*/542goog.math.Matrix.prototype.getCofactor_ = function(i, j) {543return (i + j % 2 == 0 ? 1 : -1) * this.getMinor_(i, j);544};545546547/**548* Returns the determinant of this matrix. The determinant of a matrix A is549* often denoted as |A| and can only be applied to a square matrix. Same as550* public method but without validation. Implemented using Laplace's formula.551* @return {number} The determinant of this matrix.552* @private553*/554goog.math.Matrix.prototype.getDeterminant_ = function() {555if (this.getSize().area() == 1) {556return this.array_[0][0];557}558559// We might want to use matrix decomposition to improve running time560// For now we'll do a Laplace expansion along the first row561var determinant = 0;562for (var j = 0; j < this.size_.width; j++) {563determinant += (this.array_[0][j] * this.getCofactor_(0, j));564}565return determinant;566};567568569/**570* Returns the determinant of the submatrix resulting from the deletion of row i571* and column j.572* @param {number} i The row to delete.573* @param {number} j The column to delete.574* @return {number} The first minor M[i,j] of this matrix.575* @private576*/577goog.math.Matrix.prototype.getMinor_ = function(i, j) {578return this.getSubmatrixByDeletion_(i, j).getDeterminant_();579};580581582/**583* Returns a submatrix contained within this matrix.584* @param {number} i1 The upper row index.585* @param {number} j1 The left column index.586* @param {number=} opt_i2 The lower row index.587* @param {number=} opt_j2 The right column index.588* @return {!goog.math.Matrix} The submatrix contained within the given bounds.589* @private590*/591goog.math.Matrix.prototype.getSubmatrixByCoordinates_ = function(592i1, j1, opt_i2, opt_j2) {593var i2 = opt_i2 ? opt_i2 : this.size_.height - 1;594var j2 = opt_j2 ? opt_j2 : this.size_.width - 1;595var result = new goog.math.Matrix(i2 - i1 + 1, j2 - j1 + 1);596goog.math.Matrix.forEach(result, function(value, i, j) {597result.array_[i][j] = this.array_[i1 + i][j1 + j];598}, this);599return result;600};601602603/**604* Returns a new matrix equal to this one, but with row i and column j deleted.605* @param {number} i The row index of the coordinate.606* @param {number} j The column index of the coordinate.607* @return {!goog.math.Matrix} The value at the specified coordinate.608* @private609*/610goog.math.Matrix.prototype.getSubmatrixByDeletion_ = function(i, j) {611var m = new goog.math.Matrix(this.size_.width - 1, this.size_.height - 1);612goog.math.Matrix.forEach(m, function(value, x, y) {613m.setValueAt(x, y, this.array_[x >= i ? x + 1 : x][y >= j ? y + 1 : y]);614}, this);615return m;616};617618619/**620* Returns whether the given coordinates are contained within the bounds of the621* matrix.622* @param {number} i The i index of the coordinate.623* @param {number} j The j index of the coordinate.624* @return {boolean} The value at the specified coordinate.625* @private626*/627goog.math.Matrix.prototype.isInBounds_ = function(i, j) {628return i >= 0 && i < this.size_.height && j >= 0 && j < this.size_.width;629};630631632/**633* Matrix multiplication is defined between two matrices only if the number of634* columns of the first matrix is the same as the number of rows of the second635* matrix. If A is an m-by-n matrix and B is an n-by-p matrix, then their636* product AB is an m-by-p matrix637*638* @param {goog.math.Matrix} m Matrix to multiply the matrix by.639* @return {!goog.math.Matrix} Resultant product.640* @private641*/642goog.math.Matrix.prototype.matrixMultiply_ = function(m) {643var resultMatrix = new goog.math.Matrix(this.size_.height, m.getSize().width);644goog.math.Matrix.forEach(resultMatrix, function(val, x, y) {645var newVal = 0;646for (var i = 0; i < this.size_.width; i++) {647newVal += goog.asserts.assertNumber(this.getValueAt(x, i)) *648goog.asserts.assertNumber(m.getValueAt(i, y));649}650resultMatrix.setValueAt(x, y, newVal);651}, this);652return resultMatrix;653};654655656/**657* Scalar multiplication returns a matrix of the same size as the original,658* each value multiplied by the given value.659*660* @param {number} m number to multiply the matrix by.661* @return {!goog.math.Matrix} Resultant product.662* @private663*/664goog.math.Matrix.prototype.scalarMultiply_ = function(m) {665return goog.math.Matrix.map(this, function(val, x, y) { return val * m; });666};667668669/**670* Swaps two rows.671* @param {number} i1 The index of the first row to swap.672* @param {number} i2 The index of the second row to swap.673* @private674*/675goog.math.Matrix.prototype.swapRows_ = function(i1, i2) {676var tmp = this.array_[i1];677this.array_[i1] = this.array_[i2];678this.array_[i2] = tmp;679};680681682