Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

First commit

  • Loading branch information...
commit e309071c9233c8f7b1eb3ff68948f536ebd1fa94 0 parents
Angus Gibbs authored
1  .gitignore
@@ -0,0 +1 @@
+node_modules
2  .npmignore
@@ -0,0 +1,2 @@
+docs
+test
19 LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2012 by Angus Gibbs
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
24 README.md
@@ -0,0 +1,24 @@
+# Matrix.js
+
+## About
+
+[Matrix.js](http://angusgibbs.github.com/matrix/) is a JavaScript utility library
+for working with [matrices](http://wikipedia.org/wiki/Matrix_(Mathematics)). It
+currently supports:
+
+* Adding matrices
+* Subtracting matrices
+* Multiplying matrices
+* Finding the determinant of a *n* by *n* matrix
+* Creating a *n* by *n* identity matrix
+* Raising a *n* by *n* matrix to the *x<sup>th</sup>* power
+
+For more information, see the [docs](http://angusgibbs.github.com/matrix/docs/).
+
+## Contributing
+
+Patches welcome, just make sure there are matching unit tests. Tests use
+[mocha](http://visionmedi.github.com/mocha/) with [expect.js](https://github.com/LearnBoost/expect.js).
+One particular feature that still needs to be implemented is finding the inverse
+of a matrix. I have an implementation that can invert either a 2 by 2 or a 3 by 3 matrix,
+but I am still looking for how to invert a *n* by *n* matrix.
434 lib/matrix.js
@@ -0,0 +1,434 @@
+(function() {
+ // Public: Creates a new Matrix object.
+ //
+ // data - A two dimensional array of the matrix contents.
+ //
+ // Throws an error if `data` is not a valid two-dimensional numeric array.
+ // Returns a new Matrix object.
+ var Matrix = function(data) {
+ // Check if the matrix was just given a row and column value - if so,
+ // create an all zero matrix with the specified dimensions.
+ var args = arguments;
+ if (args.length === 2 && typeof args[0] === 'number' && typeof args[1] === 'number') {
+ // Store the number or rows and columns specified
+ var numRows = args[0];
+ var numCols = args[1];
+
+ // Make data an array
+ data = [];
+
+ // Construct an all zero two-dimensional matrix
+ for (var row = 0; row < numRows; row++) {
+ // Initialize an empty array at the current row
+ data[row] = [];
+
+ for (var col = 0; col < numCols; col++) {
+ data[row][col] = 0;
+ }
+ }
+ }
+
+ // Throw an error if `data` is not an array
+ if (!Array.isArray(data)) {
+ throwError('Array must be passed');
+ }
+
+ // Store a reference to this
+ var thiz = this;
+
+ // Store the matrix rows
+ thiz.rows = data.length;
+
+ // Store the data onto the matrix object
+ data.forEach(function(row, rowNum) {
+ // Throw an error if the row is not an array
+ if (!Array.isArray(row)) {
+ throwError('The array passed must be a two-dimensional array');
+ }
+
+ // Store the matrix cols if it hasn't been stored
+ if (!thiz.cols) {
+ thiz.cols = row.length;
+ }
+
+ // Initialize an array at the row number
+ thiz[rowNum] = [];
+
+ row.forEach(function(col, colNum) {
+ // Throw an error if the column is not a number
+ if (typeof col !== 'number') {
+ throwError('The matrix fields must be numeric');
+ }
+
+ // Store the column onto the matrix object
+ thiz[rowNum][colNum] = col;
+ });
+ });
+ };
+
+ // Internal: Creates a new matrix without the specified row or column.
+ //
+ // excludeRow - The row to exclude.
+ // excludeCol - The column to exclude.
+ //
+ // Returns the new matrix.
+ Matrix.prototype._submatrix = function(excludeRow, excludeCol) {
+ // Create an array that will be used to initialize the new matrix
+ var data = [];
+
+ // Go through each of the rows
+ for (var i = 0, row = 0; i < this.rows; i++) {
+ // Skip if it's the row to exclude
+ if (i === excludeRow) {
+ continue;
+ }
+
+ // Initialize an array for the current column
+ data[row] = [];
+
+ // Go through each of the columns
+ for (var j = 0, col = 0; j < this.cols; j++) {
+ // Skip if it was the column to exclude
+ if (j === excludeCol) {
+ continue;
+ }
+
+ // Save the old matrix's value at the current row/column to the
+ // new array
+ data[row][col] = this[i][j];
+
+ // Increment cols
+ col++;
+ }
+
+ // Increment rows
+ row++;
+ }
+
+ return new Matrix(data);
+ };
+
+ // Internal: Computes the determinant of a matrix.
+ //
+ // matrix - The matrix to get the determinant of.
+ //
+ // Returns the determinant.
+ function determinant(matrix) {
+ // Check if the matrix is a 2x2
+ if (matrix.rows === 2 && matrix.cols === 2) {
+ return (matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0]);
+ }
+ // Otherwise, reduce the size by 1 and recurse
+ else {
+ var det = 0;
+ for (var i = 0; i < matrix.cols; i++) {
+ det += matrix[0][i] * (i % 2 === 0 ? 1 : -1) * determinant(matrix._submatrix(0, i));
+ }
+
+ return det;
+ }
+ }
+
+ // Public: Computes the determinant of the matrix.
+ //
+ // Throws an error if the matrix does not have the same number of rows as
+ // columns.
+ // Returns the determinant.
+ Matrix.prototype.determinant = function() {
+ // Throw an error if the matrix does not have the same number or rows as
+ // columns
+ if (this.rows !== this.cols) {
+ throwError('Cannot compute the determinant of a non-square matrix');
+ }
+
+ return determinant(this);
+ };
+
+ // Public: Multiplies the current matrix by the matrix passed.
+ //
+ // matrix - The matrix to multiply. This can be a Matrix
+ // object or a two-dimensional array.
+ //
+ // Throws an error if there are invalid dimensions or if the matrix passed
+ // is not a valid Matrix object.
+ // Returns nothing.
+ Matrix.prototype.multiply = function(matrix) {
+ // If matrix is an array, convert it to a Matrix
+ if (Array.isArray(matrix)) {
+ matrix = new Matrix(matrix);
+ }
+
+ // Throw an error if the matrix is not a valid Matrix object
+ if (!matrix instanceof Matrix) {
+ throwError('Argument passed is not a valid Matrix object');
+ }
+
+ // Throw an error if there are invalid dimensions (matrix one columns
+ // must equal matrix two rows)
+ if (this.cols !== matrix.rows) {
+ throwError('Invalid dimensions');
+ }
+
+ // Create the product matrix
+ var product = new Matrix(this.rows, matrix.cols);
+
+ // Compute the product
+ for (var row = 0; row < this.rows; row++) {
+ for (var col = 0; col < matrix.cols; col++) {
+ var square = 0;
+
+ for (var i = 0; i < this.cols; i++) {
+ square += this[row][i] * matrix[i][col];
+ }
+
+ product[row][col] = square;
+ }
+ }
+
+ return this._setData(product);
+ };
+
+ // Public: Multiplies each element in the matrix by a scalar.
+ //
+ // c - The scalar to multiply by.
+ //
+ // Throws an error if the scalar is not numeric.
+ // Returns nothing.
+ Matrix.prototype.scalar = function(c) {
+ // Throw an error if the scalar is not numeric
+ if (typeof c !== 'number') {
+ throwError('The scalar must be numeric');
+ }
+
+ for (var row = 0; row < this.rows; row++) {
+ for (var col = 0; col < this.cols; col++) {
+ this[row][col] *= c;
+ }
+ }
+
+ return this;
+ };
+
+ // Public: Computes the sum of two or more matrices.
+ //
+ // Throws an error if the two matrices do not have valid dimensions.
+ // Returns nothing.
+ Matrix.prototype.add = function() {
+ // Get all the matrices to add to the current matrix
+ var matrices = Array.prototype.slice.call(arguments, 0);
+
+ // Save a reference to this
+ var thiz = this;
+
+ // Add each of the matrices to the current matrix
+ matrices.forEach(function(matrix) {
+ // Convert matrix to a Matrix object if it is an array
+ if (Array.isArray(matrix)) {
+ matrix = new Matrix(matrix);
+ }
+
+ // Throw an error if the dimensions do not match
+ if (thiz.rows !== matrix.rows || thiz.cols !== matrix.cols) {
+ throwError('Matrix dimensions do not match');
+ }
+
+ for (var row = 0; row < thiz.rows; row++) {
+ for (var col = 0; col < thiz.cols; col++) {
+ thiz[row][col] += matrix[row][col];
+ }
+ }
+ });
+
+ return this;
+ };
+
+ // Public: Computes the difference of two matrices.
+ //
+ // matrix - The matrix to add. This can be a Matrix object or a
+ // two-dimensional array.
+ //
+ // Throws an error if the two matrices do not have valid dimensions.
+ // Returns nothing.
+ Matrix.prototype.subtract = function(matrix) {
+ // Convert matrix to a Matrix object if it is an array
+ if (Array.isArray(matrix)) {
+ matrix = new Matrix(matrix);
+ }
+
+ // Throw an error if the dimensions do not match
+ if (this.rows !== matrix.rows || this.cols !== matrix.cols) {
+ throwError('Matrix dimensions do not match');
+ }
+
+ for (var row = 0; row < this.rows; row++) {
+ for (var col = 0; col < this.cols; col++) {
+ this[row][col] -= matrix[row][col];
+ }
+ }
+
+ return this;
+ };
+
+ // Public: Raises the matrix to the nth power.
+ //
+ // power - The power to raise the matrix to.
+ //
+ // Throws an error if the power is not an integer greater than one.
+ // Returns nothing.
+ Matrix.prototype.raise = function(power) {
+ // Throw an error if the power is not an integer
+ if (typeof power !== 'number' || Math.round(power, 0) !== power) {
+ throwError('The power must be an integer');
+ }
+
+ // Throw an error if the power is not >= 2
+ if (power < 2) {
+ throwError('The power must be greater than or equal to 1');
+ }
+
+ // Compute the power
+ var product = this.clone();
+ while (--power) {
+ product.multiply(this);
+ }
+
+ return this._setData(product);
+ };
+
+ // Public: Squares the matrix.
+ //
+ // Returns nothing.
+ Matrix.prototype.square = function() {
+ this.raise(2);
+
+ return this;
+ };
+
+ // Public: Cubes the matrix.
+ //
+ // Returns nothing.
+ Matrix.prototype.cube = function() {
+ this.raise(3);
+
+ return this;
+ };
+
+ // Public: Computes the inverse of the matrix.
+ //
+ // Returns nothing.
+ Matrix.prototype.inverse = function() {
+
+ };
+
+ // Public: Clones the matrix.
+ //
+ // Returns a new Matrix object.
+ Matrix.prototype.clone = function() {
+ return new Matrix(this.toArray());
+ };
+
+ // Public: Override Matrix#toJSON() and Matrix#toArray to return a
+ // two-dimensional array with the matrix data.
+ //
+ // Returns a two-dimensional array with the matrix data.
+ Matrix.prototype.toJSON = Matrix.prototype.toArray = function() {
+ var result = [];
+
+ for (var row = 0; row < this.rows; row++) {
+ result[row] = [];
+
+ for (var col = 0; col < this.cols; col++) {
+ result[row][col] = this[row][col];
+ }
+ }
+
+ return result;
+ };
+
+ // Internal: Sets the matrix's data to the object passed.
+ //
+ // data - A two-dimensional array or Matrix object with the data to set on
+ // the current object.
+ //
+ // Returns nothing.
+ Matrix.prototype._setData = function(data) {
+ var row, col;
+
+ // Remove the old data from the matrix
+ for (row = 0; row < this.rows; row++) {
+ delete this[row];
+ }
+
+ // Convert the data into a Matrix object if it is an array
+ if (Array.isArray(data)) {
+ data = new Matrix(data);
+ }
+
+ // Throw an error if data is not a valid Matrix object
+ if (!data instanceof Matrix) {
+ throwError('The data to set must be either a Matrix object or a two dimensional array');
+ }
+
+ // Set the new row and column counts
+ this.rows = data.rows;
+ this.cols = data.cols;
+
+ // Set the new data on the matrix
+ for (row = 0; row < this.rows; row++) {
+ this[row] = [];
+
+ for (col = 0; col < this.cols; col++) {
+ this[row][col] = data[row][col];
+ }
+ }
+
+ return this;
+ };
+
+ // Public: Creates an n by n identity Matrix object.
+ //
+ // n - The height and width of the matrix.
+ //
+ // Returns a new Matrix object.
+ Matrix.identity = function(n) {
+ // Create an array that will hold the matrix data
+ var data = [];
+
+ for (var row = 0; row < n; row++) {
+ // Intialize an empty array on the current row
+ data[row] = [];
+
+ for (var col = 0; col < n; col++) {
+ data[row][col] = row === col ? 1 : 0;
+ }
+ }
+
+ return new Matrix(data);
+ };
+
+ // Internal: Throws an error if silent is set set to false.
+ //
+ // msg - The error message.
+ //
+ // Returns nothing.
+ function throwError(msg) {
+ if (!Matrix.silent) {
+ throw new Error(msg);
+ }
+ }
+
+ // Define Matrix for AMD loaders
+ if (typeof define === 'function') {
+ define(function() {
+ return Matrix;
+ });
+ }
+ // Expose Matrix for node
+ else if (typeof module !== 'undefined' && module.exports) {
+ module.exports = Matrix;
+ }
+ // Otherwise write to window
+ else {
+ window.Matrix = Matrix;
+ }
+}());
28 package.json
@@ -0,0 +1,28 @@
+{
+ "name": "matrixjs",
+ "description": "A JavaScript utility library for working with mathematical matrices",
+ "version": "0.0.0",
+ "author": "Angus Gibbs (http://angusgibbs.com)",
+ "homepage": "http://angusgibbs.github.com/matrix/",
+ "respository": {
+ "type": "git",
+ "url": "git://github.com/angusgibbs/matrix.git"
+ },
+ "bugs": {
+ "url": "http://github.com/angusgibbs/matrix/issues"
+ },
+ "licenses": [
+ {
+ "type": "MIT",
+ "url": "http://github.com/angusgibbs/matrix/blob/master/LICENSE"
+ }
+ ],
+ "main": "lib/matrix",
+ "dependencies": {
+
+ },
+ "devDependencies": {
+ "mocha": "1.4.x",
+ "expect.js": "0.1.x"
+ }
+}
245 test/test.js
@@ -0,0 +1,245 @@
+var expect = require('expect.js');
+var Matrix = require('../lib/matrix');
+
+describe('Matrix', function() {
+ var m1, m2, m3, m4;
+
+ // Reset each of the matrices before each test and turn silent mode off
+ beforeEach(function() {
+ m1 = new Matrix([
+ [-2, 2, 3],
+ [-1, 1, 3],
+ [ 2, 0, -1]
+ ]);
+
+ m2 = new Matrix([
+ [2, 1],
+ [1, 1],
+ [3, 1]
+ ]);
+
+ m3 = new Matrix([
+ [1, 2, 3],
+ [4, 5, 6],
+ [7, 8, 9]
+ ]);
+
+ m4 = new Matrix([
+ [9, 8, 7],
+ [6, 5, 4],
+ [3, 2, 1]
+ ]);
+
+ Matrix.silent = false;
+ });
+
+ describe('constructor', function() {
+ it('should throw an error if the argument passed is not an array', function() {
+ expect(function() {
+ new Matrix('hi');
+ }).to.throwError();
+ });
+
+ it('should create a Matrix object from a two-dimensional array', function() {
+ expect(new Matrix([[1, 2], [3, 4]]).toArray()).to.eql([[1, 2], [3, 4]]);
+ });
+
+ it('should create an all zero array if a row and column number are passed', function() {
+ expect(new Matrix(2, 2).toArray()).to.eql([[0, 0], [0, 0]]);
+ });
+ });
+
+ describe('submatrices', function() {
+ it('should slice out the given row and column', function() {
+ expect(m1._submatrix(1, 1).toArray()).to.eql([
+ [-2, 3],
+ [ 2, -1]
+ ]);
+ });
+ });
+
+ describe('#determinant()', function() {
+ it('should throw an error if it is not a square matrix', function() {
+ expect(function() {
+ m2.determinant();
+ }).to.throwError();
+ });
+
+ it('should return the proper determinant', function() {
+ expect(m1.determinant()).to.equal(6);
+ });
+ });
+
+ describe('#multiply()', function() {
+ it('should throw an error if there are improper dimensions', function() {
+ expect(function() {
+ new Matrix(2, 2).multiply(new Matrix(3, 3));
+ }).to.throwError();
+ });
+
+ it('should throw an error if the matrix to multiply is not a valid Matrix object', function() {
+ expect(function() {
+ m1.multiply('hi');
+ }).to.throwError();
+ });
+
+ it('should multiply two matrices', function() {
+ expect(m1.clone().multiply(m2).toArray()).to.eql([
+ [7, 3],
+ [8, 3],
+ [1, 1]
+ ]);
+
+ expect(m1.clone().multiply(m3).toArray()).to.eql([
+ [27, 30, 33],
+ [24, 27, 30],
+ [-5, -4, -3]
+ ]);
+ });
+ });
+
+ describe('#add()', function() {
+ it('should throw an error if the dimensions do not match', function() {
+ expect(function() {
+ m1.add(m2);
+ }).to.throwError();
+ });
+
+ it('should throw an error if any of the dimensions do not match', function() {
+ expect(function() {
+ m1.add(m3).add(m2);
+ }).to.throwError();
+ });
+
+ it('should add two matrices with the same dimensions', function() {
+ expect(m1.add(m3).toArray()).to.eql([
+ [-1, 4, 6],
+ [3, 6, 9],
+ [9, 8, 8]
+ ]);
+ });
+
+ it('should add multiple matrices with the same dimensions', function() {
+ expect(m1.add(m3, m4).toArray()).to.eql([
+ [8, 12, 13],
+ [9, 11, 13],
+ [12, 10, 9]
+ ]);
+ });
+ });
+
+ describe('#subtract()', function() {
+ it('should throw an error if the dimensions do not match', function() {
+ expect(function() {
+ m1.subtract(m2);
+ }).to.throwError();
+ });
+
+ it('should subtract two matrices with the same dimensions', function() {
+ expect(m1.subtract(m3).toArray()).to.eql([
+ [-3, 0, 0],
+ [-5, -4, -3],
+ [-5, -8, -10]
+ ]);
+ });
+ });
+
+ describe('#scalar()', function() {
+ it('should throw an error if the scalar is not a number', function() {
+ expect(function() {
+ m1.scalar('hello');
+ }).to.throwError();
+ });
+
+ it('should multiply each element in the matrix by the scalar', function() {
+ expect(m1.scalar(2).toArray()).to.eql([
+ [-4, 4, 6],
+ [-2, 2, 6],
+ [ 4, 0, -2]
+ ]);
+ });
+ });
+
+ describe('#raise()', function() {
+ it('should throw an error if the power is not an integer', function() {
+ expect(function() {
+ m1.raise('hello');
+ }).to.throwError();
+
+ expect(function() {
+ m1.raise(1.5);
+ }).to.throwError();
+ });
+
+ it('should throw an error if the power is less than 2', function() {
+ expect(function() {
+ m1.raise(1);
+ }).to.throwError();
+ });
+
+ it('should throw an error if the matrix is not a square matrix', function() {
+ expect(function() {
+ m2.raise(2);
+ }).to.throwError();
+ });
+
+ it('should raise the matrix to the specified power', function() {
+ expect(m1.clone().raise(2).toArray()).to.eql([
+ [8, -2, -3],
+ [7, -1, -3],
+ [-6, 4, 7]
+ ]);
+
+ expect(m1.clone().raise(3).toArray()).to.eql([
+ [-20, 14, 21],
+ [-19, 13, 21],
+ [22, -8, -13]
+ ]);
+ });
+
+ describe('#square()', function() {
+ it('should alias #raise(2) to #square()', function() {
+ expect(m1.clone().raise(2).toArray()).to.eql(m1.clone().square().toArray());
+ });
+ });
+
+ describe('#cube()', function() {
+ it('should alias #raise(3) to #cube()', function() {
+ expect(m1.clone().raise(3).toArray()).to.eql(m1.clone().cube().toArray());
+ });
+ });
+ });
+
+ describe('Matrix.identity', function() {
+ it('should create an identity matrix of any size', function() {
+ expect(new Matrix.identity(2).toArray()).to.eql([
+ [1, 0],
+ [0, 1]
+ ]);
+
+ expect(new Matrix.identity(3).toArray()).to.eql([
+ [1, 0, 0],
+ [0, 1, 0],
+ [0, 0, 1]
+ ]);
+ });
+ });
+
+ describe('Matrix.silent', function() {
+ it('should throw an error when set to false', function() {
+ Matrix.silent = false;
+
+ expect(function() {
+ m2.raise(2);
+ }).to.throwError();
+ });
+
+ it('should not throw an error when set to false', function() {
+ Matrix.silent = true;
+
+ expect(function() {
+ m2.raise(2);
+ }).to.not.throwError();
+ });
+ });
+});
Please sign in to comment.
Something went wrong with that request. Please try again.