Repository: josdejong/mathjs
Pull request #362 (2015-05-08 17:23:20)

Multinomial
Adding support for [multinomial theorem](https://en.wikipedia.org/wiki/Multinomial_theorem).


Patch:
diff --git a/lib/function/combinatorics/bellNumbers.js b/lib/function/combinatorics/bellNumbers.js
index 58cbc6d91..b4bb1e4bd 100644
--- a/lib/function/combinatorics/bellNumbers.js
+++ b/lib/function/combinatorics/bellNumbers.js
@@ -7,8 +7,7 @@ module.exports = function (math) {
   collection = math.collection,
 
   isNumber = util.number.isNumber,
-  isInteger = util.number.isInteger;
-
+  isPositiveInteger = util.number.isPositiveInteger;
   /**
   * The Bell Numbers count the number of partitions of a set. A partition is a pairwise disjoint subset of S whose union is S.
 
@@ -37,7 +36,7 @@ module.exports = function (math) {
 
     if (isNumber(n) || n instanceof BigNumber) {
 
-      if (!isInteger(n) || n < 0) {
+      if (!isPositiveInteger(n)) {
         throw new TypeError('Positive integer value expected in function bellNumbers');
       }
 
@@ -49,15 +48,4 @@ module.exports = function (math) {
     }
     throw new math.error.UnsupportedTypeError('bellNumbers', math['typeof'](n));
   }
-
-
-
-  /**
-   * Test whether BigNumber n is a positive integer
-   * @param {BigNumber} n
-   * @returns {boolean} isPositiveInteger
-   */
-   var isPositiveInteger = function(n) {
-     return n.isInteger() && n.gte(0);
-   };
- };
+ };
\ No newline at end of file
diff --git a/lib/function/combinatorics/stirlingS2.js b/lib/function/combinatorics/stirlingS2.js
index ae456f3ce..5fe12bf43 100644
--- a/lib/function/combinatorics/stirlingS2.js
+++ b/lib/function/combinatorics/stirlingS2.js
@@ -7,8 +7,7 @@ module.exports = function (math) {
   collection = math.collection,
 
   isNumber = util.number.isNumber,
-  isInteger = util.number.isInteger;
-
+  isPositiveInteger = util.number.isPositiveInteger;
   /**
   * The Stirling numbers of the second kind, counts the number of ways to partition a set of n labelled objects into k nonempty unlabelled subsets.
 
@@ -39,7 +38,7 @@ module.exports = function (math) {
 
     if ((isNumber(n) && isNumber(k)) || n instanceof BigNumber) {
 
-      if (!isInteger(n) || n < 0 || !isInteger(k) || k < 0) {
+      if (!isPositiveInteger(n) || !isPositiveInteger(k)) {
         throw new TypeError('Positive integer value expected in function stirlingS2');
       }
       else if (k > n) {
@@ -69,15 +68,4 @@ module.exports = function (math) {
       throw new TypeError('Integer values are expected in stirlingS2')
     }
   };
-
-
-
-  /**
-   * Test whether BigNumber n is a positive integer
-   * @param {BigNumber} n
-   * @returns {boolean} isPositiveInteger
-   */
-   var isPositiveInteger = function(n) {
-     return n.isInteger() && n.gte(0);
-   };
- };
+};
diff --git a/lib/function/probability/multinomial.js b/lib/function/probability/multinomial.js
new file mode 100644
index 000000000..4196d4087
--- /dev/null
+++ b/lib/function/probability/multinomial.js
@@ -0,0 +1,56 @@
+'use strict';
+var _ = require('underscore');
+
+module.exports = function (math) {
+  var util = require('../../util/index'),
+
+  BigNumber = math.type.BigNumber,
+  isArray = Array.isArray,
+  collection = math.collection,
+  isInteger = util.number.isInteger,
+  isPositiveInteger = util.number.isPositiveInteger;
+
+  /**
+   * Multinomial Coefficients compute the number of ways of picking a1, a2, ..., ai unordered outcomes from `n` possibilities.
+   *
+   * multinomial takes one array of integers as an argument.
+   * The following condition must be enforced: every ai <= 0
+   *
+   * Syntax:
+   *
+   *     math.multinomial(a) // a is an array type
+   *
+   * Examples:
+   *
+   *    math.multinomial([1,2,1]); // returns 12
+   *
+   * See also:
+   *
+   *    combinations, factorial
+   *
+   * @param {Integer[] | BigNumber[]} a    Number of objects in the subset
+   * @return {Number | BigNumber}     Multinomial coefficient.
+   */
+   math.multinomial = function multinomial (a) {
+
+    var arity = arguments.length;
+    if (arity != 1) {
+      throw new math.error.ArgumentsError('multinomial', arguments.length, 1);
+    }
+
+    if (!_.isArray(a)) {
+      throw new math.error.UnsupportedTypeError('multinomial', math['typeof'](a));
+    }
+    var n = 0;
+    var denom = 1;
+    collection.deepForEach(a, function(ai) {
+      if(!isPositiveInteger(ai)) {
+        throw new TypeError('Positive integer value expected in function multinomial');
+      }
+      n = math.add(n, ai);
+      denom = math.multiply(denom, math.factorial(ai));
+    });
+    return math.divide(math.factorial(n), denom);
+  };
+
+};
diff --git a/lib/math.js b/lib/math.js
index b630ba1f3..ec30b9f79 100644
--- a/lib/math.js
+++ b/lib/math.js
@@ -314,6 +314,7 @@ function create (config) {
   require('./function/probability/pickRandom')(math, _config);
   require('./function/probability/permutations')(math, _config);
   require('./function/probability/combinations')(math, _config);
+  require('./function/probability/multinomial')(math, _config);
 
   // functions - relational
   require('./function/relational/compare')(math, _config);
diff --git a/lib/util/number.js b/lib/util/number.js
index af6572ce4..82279955e 100644
--- a/lib/util/number.js
+++ b/lib/util/number.js
@@ -265,7 +265,7 @@ exports.nearlyEqual = function(x, y, epsilon) {
 */
 exports.isPositiveInteger = function(n) {
   var BigNumber = require('../type/BigNumber');
-  if (this.isNumber(n) && this.isInteger(n) && n >= 0) {
+  if (exports.isNumber(n) && exports.isInteger(n) && n >= 0) {
     return true;
   }
 


Test patch:
diff --git a/test/function/probability/multinomial.test.js b/test/function/probability/multinomial.test.js
new file mode 100644
index 000000000..330398ab3
--- /dev/null
+++ b/test/function/probability/multinomial.test.js
@@ -0,0 +1,32 @@
+var assert = require('assert'),
+error = require('../../../lib/error/index'),
+math = require('../../../index'),
+multinomial = math.multinomial,
+_ = require('underscore');
+
+describe('multinomial', function() {
+
+  it('should calculate the multinomial of an array of numbers', function() {
+    assert.equal(multinomial([1,2,1]), 12);
+    assert.equal(multinomial([4,2,1]), 105);
+    assert.equal(multinomial([4,4]), 70);
+  });
+
+  it('should calculate the multinomial of n items taken k at a time with BigNumbers', function() {
+    assert.equal(_.isEqual(multinomial([math.bignumber(3), math.bignumber(4), math.bignumber(5)]), math.bignumber(27720)),true);
+    assert.deepEqual(multinomial([math.bignumber(10), math.bignumber(1), math.bignumber(2)]), math.bignumber(858));
+  });
+
+  it('should not work with non-integer and negative input', function() {
+    assert.throws(function() {multinomial([0.5,3])}, TypeError);
+      assert.throws(function() {multinomial([math.bignumber(3), math.bignumber(0.5)])}, TypeError);
+      assert.throws(function() {multinomial([math.bignumber(3.5), math.bignumber(-3)])}, TypeError);
+      assert.throws(function() {multinomial([math.bignumber(3.5), 1/3])}, TypeError);
+    });
+
+  it('should not work with the wrong number or type of arguments', function() {
+      assert.throws(function() {multinomial(5, 3, 2)});
+      assert.throws(function() {multinomial(true, "hello world")});
+  });
+
+});