Repository: josdejong/mathjs
Pull request #270 (2015-01-27 20:32:52)

Added trace matrix operation
Added the trace matrix operation, abbreviated "tr". See tests for usage.

http://en.wikipedia.org/wiki/Trace_(linear_algebra)


Patch:
diff --git a/lib/function/matrix/trace.js b/lib/function/matrix/trace.js
new file mode 100644
index 000000000..f75f8baa7
--- /dev/null
+++ b/lib/function/matrix/trace.js
@@ -0,0 +1,97 @@
+'use strict';
+
+module.exports = function (math) {
+  var util = require('../../util/index'),
+
+      Matrix = require('../../type/Matrix'),
+
+      object = util.object,
+      string = util.string;
+
+  /**
+   * Calculate the trace of a matrix.
+   *
+   * Syntax:
+   *
+   *    math.trace(x)
+   *
+   * Examples:
+   *
+   *    math.trace([[1, 2], [3, 4]]); // returns 5
+   *
+   *    var A = [
+   *      [1, 2, 3],
+   *      [-1, 2, 3],
+   *      [2, 0, 3]
+   *    ]
+   *    math.trace(A); // returns 6
+   *
+   * @param {Array | Matrix} x  A matrix
+   * @return {Number} The trace of `x`
+   */
+  math.trace = function trace (x) {
+    if (arguments.length != 1) {
+      throw new math.error.ArgumentsError('trace', arguments.length, 1);
+    }
+
+    var size;
+    if (x instanceof Matrix) {
+      size = x.size();
+    }
+    else if (x instanceof Array) {
+      x = new Matrix(x);
+      size = x.size();
+    }
+    else {
+      // a scalar
+      size = [];
+    }
+
+    switch (size.length) {
+      case 0:
+        // scalar
+        return object.clone(x);
+
+      case 1:
+        // vector
+        if (size[0] == 1) {
+          return object.clone(x.valueOf()[0]);
+        }
+        else {
+          throw new RangeError('Matrix must be square ' +
+              '(size: ' + string.format(size) + ')');
+        }
+
+      case 2:
+        // two dimensional array
+        var rows = size[0];
+        var cols = size[1];
+        if (rows == cols) {
+          return _trace(x.clone().valueOf());
+        }
+        else {
+          throw new RangeError('Matrix must be square ' +
+              '(size: ' + string.format(size) + ')');
+        }
+
+      default:
+        // multi dimensional array
+        throw new RangeError('Matrix must be two dimensional ' +
+            '(size: ' + string.format(size) + ')');
+    }
+  };
+
+  /**
+   * Calculate the trace of a matrix
+   * @param {Array[]} matrix  A square, two dimensional matrix
+   * @returns {Number} trace
+   * @private
+   */
+  function _trace (matrix) {
+    var sum = 0;
+    for (var i = 0; i < matrix.length; i++) {
+        sum = math.add(sum, matrix[i][i]);
+    }
+    return sum;
+  }
+};
diff --git a/lib/math.js b/lib/math.js
index 902ed8435..919198164 100644
--- a/lib/math.js
+++ b/lib/math.js
@@ -256,6 +256,7 @@ function create (config) {
   require('./function/matrix/size')(math, _config);
   require('./function/matrix/squeeze')(math, _config);
   require('./function/matrix/subset')(math, _config);
+  require('./function/matrix/trace')(math, _config);
   require('./function/matrix/transpose')(math, _config);
   require('./function/matrix/zeros')(math, _config);
 


Test patch:
diff --git a/test/function/matrix/trace.test.js b/test/function/matrix/trace.test.js
new file mode 100644
index 000000000..23c28a9ca
--- /dev/null
+++ b/test/function/matrix/trace.test.js
@@ -0,0 +1,114 @@
+var assert = require('assert'),
+    error = require('../../../lib/error/index'),
+    approx = require('../../../tools/approx'),
+    math = require('../../../index');
+
+describe('trace', function() {
+
+  it('should calculate correctly the trace of a NxN matrix', function() {
+    assert.equal(math.trace([5]), 5);
+    assert.equal(math.trace([[1,2],[3,4]]), 5);
+    assert.equal(math.trace(math.matrix([[1,2],[3,4]])), 5);
+    approx.equal(math.trace([
+      [-2, 2,  3],
+      [-1, 1,  3],
+      [ 2, 0, -1]
+    ]), -2);
+    approx.equal(math.trace([
+      [ 1, 4,  7],
+      [ 3, 0,  5],
+      [-1, 9, 11]
+    ]), 12);
+    approx.equal(math.trace([
+      [1,7,4,3,7], 
+      [0,7,0,3,7], 
+      [0,7,4,3,0], 
+      [1,7,5,9,7], 
+      [2,7,4,3,7]
+    ]), 28);
+    approx.equal(math.trace(math.diag([4,-5,6])), 5);
+  });
+
+  it('should return N for the identity matrix',function() {
+    assert.equal(math.trace(math.eye(7)), 7);
+    assert.equal(math.trace(math.eye(2)), 2);
+    assert.equal(math.trace(math.eye(1)), 1);
+  });
+
+  it('should calculate the trace for a scalar',function() {
+    assert.equal(math.trace(7), 7);
+
+    var c1 = math.complex(2, 3);
+    var c2 = math.trace(c1);
+    assert.deepEqual(c1, c2);
+
+    // c2 should be a clone
+    c1.re = 0;
+    assert.equal(c1.re, 0);
+    assert.equal(c2.re, 2);
+  });
+
+  it('should calculate the trace for a 1x1 matrix',function() {
+    var c1 = math.complex(2, 3);
+    var c2 = math.trace([[c1]]);
+    assert.deepEqual(c1, c2);
+
+    // c2 should be a clone
+    c1.re = 0;
+    assert.equal(c1.re, 0);
+    assert.equal(c2.re, 2);
+  });
+
+  it('should calculate correctly the trace of a matrix with bignumbers', function() {
+    var bignumber = math.bignumber;
+
+    // 1x1
+    assert.deepEqual(math.trace([bignumber(5)]), bignumber(5));
+
+    // 2x2
+    assert.deepEqual(math.trace([
+      [bignumber(1), bignumber(2)],
+      [bignumber(3), bignumber(4)]
+    ]), bignumber(5));
+
+    // 3x3
+    assert.deepEqual(math.trace([
+      [bignumber(-2), bignumber(2), bignumber( 3)],
+      [bignumber(-1), bignumber(1), bignumber( 3)],
+      [bignumber( 2), bignumber(0), bignumber(-1)]
+    ]), bignumber(-2));
+
+    // the following would fail with regular Numbers due to a precision overflow
+    assert.deepEqual(math.trace([
+      [bignumber(1e10+1), bignumber(1e10)],
+      [bignumber(1e10), bignumber(-1e10)]
+    ]), bignumber(1));
+  });
+
+  it('should calculate the trace of a matrix with mixed numbers and bignumbers', function() {
+    var bignumber = math.bignumber;
+    assert.deepEqual(math.trace([
+      [bignumber(2), 1],
+      [bignumber(3), 4]
+    ]), bignumber(6));
+  });
+
+  it('should not change the value of the initial matrix', function() {
+    var m_test = [[1,2,3],[4,5,6],[7,8,9]];
+    math.trace(m_test);
+    assert.deepEqual(m_test, [[1,2,3],[4,5,6],[7,8,9]]);
+  });
+
+  it('should not accept a non-square matrix', function() {
+    assert.throws(function() { math.trace([1,2]); });
+    assert.throws(function() { math.trace([[1,2,3],[1,2,3]]); });
+    assert.throws(function() { math.trace([0,1],[0,1],[0,1]); });
+  });
+
+  it('should not accept arrays with dimensions higher than 2', function() {
+    assert.throws(function() { math.trace([[[1]]]); }, RangeError);
+    assert.throws(function() { math.trace(math.matrix([[[1]]])); }, RangeError);
+  });
+
+
+});
\ No newline at end of file