Repository: josdejong/mathjs
Pull request #773 (2017-01-17 22:34:12)

Add Kronecker Product (Direct / Tensor Product)
Adds Kronecker Product as math.kron(x, y);

From issue #704 



Patch:
diff --git a/.travis.yml b/.travis.yml
index a93f6fc0c..272aee154 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,4 +4,3 @@ node_js:
   - 0.12
   - 4
   - 6
-  - 7
diff --git a/HISTORY.md b/HISTORY.md
index c8f534950..460150ea3 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,12 +1,6 @@
 # History
 
 
-## not yet released, version 3.8.2
-
-- Fixed #765: `FunctionAssignmentNode.toString()` returning a string
-  incompatible with the function assignment syntax.
-
-
 ## 2016-12-15, version 3.8.1
 
 - Implemented function `mad` (median absolute deviation). Thanks @ruhleder.
diff --git a/lib/expression/node/FunctionAssignmentNode.js b/lib/expression/node/FunctionAssignmentNode.js
index aa68b9e36..2fe3f20ed 100644
--- a/lib/expression/node/FunctionAssignmentNode.js
+++ b/lib/expression/node/FunctionAssignmentNode.js
@@ -139,7 +139,8 @@ function factory (type, config, load, typed) {
     if (needParenthesis(this, parenthesis)) {
       expr = '(' + expr + ')';
     }
-    return this.name + '(' + this.params.join(', ') + ') = ' + expr;
+    return 'function ' + this.name +
+        '(' + this.params.join(', ') + ') = ' + expr;
   };
 
   /**
diff --git a/lib/function/matrix/index.js b/lib/function/matrix/index.js
index f942b55ef..3c5681db4 100644
--- a/lib/function/matrix/index.js
+++ b/lib/function/matrix/index.js
@@ -9,6 +9,7 @@ module.exports = [
   require('./flatten'),
   require('./forEach'),
   require('./inv'),
+  require('./kron'),
   require('./map'),
   require('./ones'),
   require('./partitionSelect'),
diff --git a/lib/function/matrix/kron.js b/lib/function/matrix/kron.js
new file mode 100644
index 000000000..608d0269e
--- /dev/null
+++ b/lib/function/matrix/kron.js
@@ -0,0 +1,85 @@
+'use strict';
+
+var size = require('../../utils/array').size;
+
+function factory(type, config, load, typed) {
+  var matrix = load(require('../../type/matrix/function/matrix'));
+    /**
+     * Calculates the kronecker product of 2 matrices / vectors.
+     *
+     * NOTE: If a one dimensional vector / matrix is given, it will be
+     * wrapped so its two dimensions.
+     * See the examples.
+     *
+     * Syntax:
+     *
+     *    math.kron(x, y)
+     *
+     * Examples:
+     *
+     *    math.kron([[1, 0], [0, 1]], [[1, 2], [3, 4]]);
+     *    // returns [ [ 1, 2, 0, 0 ], [ 3, 4, 0, 0 ], [ 0, 0, 1, 2 ], [ 0, 0, 3, 4 ] ]
+     *
+     *    math.kron([1,1], [2,3,4]);
+     *    // returns [ [ 2, 3, 4, 2, 3, 4 ] ]
+     *
+     * @param  {Array | Matrix} x     First vector
+     * @param  {Array | Matrix} y     Second vector
+     * @return {Array | Matrix}       Returns the kronecker product of `x` and `y`
+     */
+    var kron = typed('kron', {
+        'Matrix, Matrix': function(x, y) {
+            return matrix(_kron(x.toArray(), y.toArray()));
+        },
+
+        'Matrix, Array': function(x, y) {
+            return matrix(_kron(x.toArray(), y));
+        },
+
+        'Array, Matrix': function(x, y) {
+            return matrix(_kron(x, y.toArray()));
+        },
+
+        'Array, Array': _kron
+    });
+
+    return kron;
+
+    /**
+     * Calculate the kronecker product of two matrices / vectors
+     * @param {Array} a  First vector
+     * @param {Array} b  Second vector
+     * @returns {Array} Returns the kronecker product of x and y
+     * @private
+     */
+    function _kron(a, b) {
+        // Deal with the dimensions of the matricies.
+        if (size(a).length === 1) {
+          // Wrap it in a 2D Matrix
+          a = [a];
+        }
+        if (size(b).length === 1) {
+          // Wrap it in a 2D Matrix
+          b = [b]
+        }
+        if (size(a).length > 2 || size(b).length > 2) {
+            throw new RangeError('Vectors with dimensions greater then 2 are not supported expected ' +
+            '(Size x = ' + JSON.stringify(a.length) + ', y = ' + JSON.stringify(b.length) + ')');
+        }
+        var t = [];
+        var r = [];
+
+        return a.map(function(a) {
+            return b.map(function(b) {
+                return a.map(function(y) {
+                    return b.map(function(x) {
+                        return r.push(y * x);
+                    });
+                }, t.push(r = []));
+            });
+        }, t = []) && t;
+    }
+}
+
+exports.name = 'kron';
+exports.factory = factory;


Test patch:
diff --git a/test/expression/node/FunctionAssignmentNode.test.js b/test/expression/node/FunctionAssignmentNode.test.js
index dac19fa23..be0860b8f 100644
--- a/test/expression/node/FunctionAssignmentNode.test.js
+++ b/test/expression/node/FunctionAssignmentNode.test.js
@@ -284,7 +284,7 @@ describe('FunctionAssignmentNode', function() {
 
   it ('should respect the \'all\' parenthesis option', function () {
     var expr = math.parse('f(x)=x+1');
-    assert.equal(expr.toString({parenthesis: 'all'}), 'f(x) = (x + 1)');
+    assert.equal(expr.toString({parenthesis: 'all'}), 'function f(x) = (x + 1)');
     assert.equal(expr.toTex({parenthesis: 'all'}), '\\mathrm{f}\\left(x\\right):=\\left( x+1\\right)');
   });
 
@@ -294,7 +294,7 @@ describe('FunctionAssignmentNode', function() {
     var o = new OperatorNode('+', 'add', [a, x]);
     var n = new FunctionAssignmentNode('f', ['x'], o);
 
-    assert.equal(n.toString(), 'f(x) = 2 + x');
+    assert.equal(n.toString(), 'function f(x) = 2 + x');
   });
 
   it ('should stringify a FunctionAssignmentNode conataining an AssignmentNode', function () {
@@ -303,7 +303,7 @@ describe('FunctionAssignmentNode', function() {
     var n1 = new AssignmentNode(new SymbolNode('a'), a);
     var n = new FunctionAssignmentNode('f', ['x'], n1);
 
-    assert.equal(n.toString(), 'f(x) = (a = 2)');
+    assert.equal(n.toString(), 'function f(x) = (a = 2)');
   });
 
   it ('should stringify a FunctionAssignmentNode with custom toString', function () {
diff --git a/test/expression/parse.test.js b/test/expression/parse.test.js
index 5995ad5de..c13691a4a 100644
--- a/test/expression/parse.test.js
+++ b/test/expression/parse.test.js
@@ -786,17 +786,6 @@ describe('parse', function() {
       assert.deepEqual(parseAndEval('partialAdd(2)(3)', scope), 5);
     });
 
-    it('should invoke a function which is a property of a function', function () {
-      function f () {
-        return '42'
-      }
-      f.foo = function () {
-        return 'bar'
-      }
-
-      assert.deepEqual(parseAndEval('f.foo()', {f: f}), 'bar');
-    });
-
     it('should invoke a function on an object with the right context', function () {
       approx.equal(parseAndEval('(2.54 cm).toNumeric("inch")'), 1);
       assert.deepEqual(parseAndEval('bignumber(2).plus(3)'), math.bignumber(5));
diff --git a/test/function/matrix/kron.test.js b/test/function/matrix/kron.test.js
new file mode 100644
index 000000000..ac9ca021f
--- /dev/null
+++ b/test/function/matrix/kron.test.js
@@ -0,0 +1,86 @@
+// test kronecker product
+var assert = require('assert'),
+    math = require('../../../index');
+
+describe('kron', function() {
+
+  it('should calculate the kronecker product of two arrays', function() {
+      assert.deepEqual(math.kron([
+          [1, -2, 1],
+          [1, 1, 0]
+      ], [
+          [1, 0],
+          [0, 1]
+      ]), [
+          [1, 0, -2, -0, 1, 0],
+          [0, 1, -0, -2, 0, 1],
+          [1, 0, 1, 0, 0, 0],
+          [0, 1, 0, 1, 0, 0]
+      ]);
+      assert.deepEqual(math.kron([
+          [53, 12, -9, 0],
+          [2, 2, 5, 2]
+      ], [
+          [99, 56, 22, 7]
+      ]), [
+          [5247, 2968, 1166, 371, 1188, 672, 264, 84, -891, -504, -198, -63, 0, 0, 0, 0],
+          [198, 112, 44, 14, 198, 112, 44, 14, 495, 280, 110, 35, 198, 112, 44, 14]
+      ]);
+  });
+
+  it('should calculate product for empty 2D Arrays', function() {
+    assert.deepEqual(math.kron([[]], [[]]), [[]]);
+  });
+
+  it('should calculate product for 1D Arrays', function() {
+    assert.deepEqual(math.kron([1,1], [[1,0],[0,1]]), [[1, 0, 1, 0],[0, 1, 0, 1]]);
+    assert.deepEqual(math.kron([[1,0],[0,1]], [1,1]), [[1, 1, 0, 0],[0, 0, 1, 1]]);
+    assert.deepEqual(math.kron([1,2,6,8], [12,1,2,3]), [[12,1,2,3,24,2,4,6,72,6,12,18,96,8,16,24]]);
+  });
+
+  it('should throw an error for greater then 2 dimensions', function() {
+    assert.throws(function () {
+      math.kron([[[1, 1], [1, 1]], [[1, 1], [1, 1]]], [[[1, 2, 3], [4, 5, 6]], [[6, 7, 8], [9, 10, 11]]]);
+    });
+  });
+
+  it('should throw an error if called with an invalid number of arguments', function() {
+    assert.throws(function () {math.kron();}, TypeError);
+    assert.throws(function () {math.kron([[1,2]]);}, TypeError);
+  });
+
+  describe('DenseMatrix', function () {
+
+    it('should calculate the kronecker product of a 2d matrix', function() {
+      var y = math.matrix([[1, 1], [1, 1]]);
+      var x = math.matrix([[1, 0], [0, 1]]);
+      var product = math.kron(x, y);
+      assert.deepEqual(product.valueOf(), [[1,1,0,0],[1,1,0,0],[0,0,1,1],[0,0,1,1]]);
+
+      var y = math.matrix([[1, 2], [55, -1]]);
+      var x = math.matrix([[13, 0], [0, -1]]);
+      var product = math.kron(x, y);
+      assert.deepEqual(product.toArray(), [[13,26,0,0],[715,-13,0,-0],[0,0,-1,-2],[0,0,-55,1]]);
+    });
+
+    it('should throw an error for invalid kronecker product of matrix', function() {
+      y = math.matrix([[[]]]);
+      x = math.matrix([[[1,1], [1,1]], [[1,1], [1,1]]]);
+      assert.throws(function () { math.kron(y, x) });
+    });
+  });
+
+  describe('SparseMatrix', function () {
+    it('should calculate the kronecker product of a 2d matrix', function() {
+      var y = math.sparse([[1, 1], [1, 1]]);
+      var x = math.sparse([[1, 0], [0, 1]]);
+      var product = math.kron(x, y);
+      assert.deepEqual(product.valueOf(), [[1,1,0,0],[1,1,0,0],[0,0,1,1],[0,0,1,1]]);
+
+      var y = math.matrix([[1, 2], [55, -1]], 'sparse');
+      var x = math.matrix([[13, 0], [0, -1]], 'sparse');
+      var product = math.kron(x, y);
+      assert.deepEqual(product.toArray(), [[13,26,0,0],[715,-13,0,-0],[0,0,-1,-2],[0,0,-55,1]]);
+    });
+  });
+});