Commit 7df0fde6 authored by Oleg Dzhimiev's avatar Oleg Dzhimiev

Gauss-Newton algorithm as extension to numbers.js

parent 5ae63a0d
/**
* @file numbers.calculus.extra.js
* @brief Gauss-Newton
* @copyright Copyright (C) 2017 Elphel Inc.
* @authors Oleg Dzhimiev <oleg@elphel.com>
*
* @license: GPL-3.0
*
* @licstart The following is the entire license notice for the
* JavaScript code in this page.
*
* The JavaScript code in this page is free software: you can
* redistribute it and/or modify it under the terms of the GNU
* General Public License (GNU GPL) as published by the Free Software
* Foundation, either version 3 of the License, or (at your option)
* any later version. The code is distributed WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
*
* As additional permission under GNU GPL version 3 section 7, you
* may distribute non-source (e.g., minimized or compacted) forms of
* that code without the copy of the GNU GPL normally required by
* section 4, provided you include this license notice and a URL
* through which recipients can access the Corresponding Source.
*
* @licend The above is the entire license notice
* for the JavaScript code in this page.
*/
/**
* Gauss-Newton algorithm for minimizing error function which
* is a sum of squared errors for each measurement
*
* @v {Array} vector, initial approximation
* @n {Number} number of measuments
* @r {Function} residual function
* @dr {Array} array of derivative functions
* @eps {Number} precision
*
*/
numbers.calculus.GaussNewton = function(v,n,r,dr,eps){
var epsilon = eps || 1e-8
var limit = 1000
var stop = false
var counter = 0
var v0 = v
while(!stop){
counter++
var v1 = iterate(v0,n,r,dr)
var s0 = sigma(v0,n,r)
var s1 = sigma(v1,n,r)
if((Math.abs(s1-s0)<epsilon)||(counter==limit)){
stop = true
}
v0 = v1
}
return {
count: counter,
error: s1,
v: v0
}
//functions
function iterate(v,n,r,dr){
var J = jacobian(v,n,dr)
var Jt = numbers.matrix.transpose(J)
// JtJ
J = numbers.matrix.multiply(Jt,J)
// (Jt x J)^-1
J = numbers.matrix.inverse(J)
// (Jt x J)^-1 x Jt
J = numbers.matrix.multiply(J,Jt)
var V = []
for(var i=0;i<n;i++){
V.push([r(i,v)])
}
var delta = numbers.matrix.multiply(J,V)
var res = []
for(var i=0;i<v.length;i++){
res[i] = v[i]-delta[i][0]
}
return res
}
function sigma(v,n,r){
var sum = 0;
for(var i=0;i<n;i++){
sum += r(i,v)*r(i,v)
}
sum = Math.sqrt(sum/n)
return sum
}
function jacobian(v,n,dr){
var J = []
for(var i=0;i<n;i++){
var row = []
for(var j=0;j<dr.length;j++){
row.push(dr[j](i,v))
}
J[i] = row
}
return J
}
}
!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.numbers=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
module.exports = require('./lib/numbers.js');
},{"./lib/numbers.js":2}],2:[function(require,module,exports){
/**
* numbers.js
* http://github.com/sjkaliski/numbers.js
*
* Copyright 2012 Stephen Kaliski
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var numbers = exports;
// Expose methods
numbers.basic = require('./numbers/basic');
numbers.calculus = require('./numbers/calculus');
numbers.complex = require('./numbers/complex');
numbers.dsp = require('./numbers/dsp');
numbers.matrix = require('./numbers/matrix');
numbers.prime = require('./numbers/prime');
numbers.statistic = require('./numbers/statistic');
numbers.generate = require('./numbers/generators');
numbers.random = require('./numbers/random');
/**
* @property {Number} EPSILON Epsilon (error bound) to be used
* in calculations. Can be set and retrieved freely.
*
* Given the float-point handling by JavaScript, as well as
* the numbersal proficiency of some methods, it is common
* practice to include a bound by which discrepency between
* the "true" answer and the returned value is acceptable.
*
* If no value is provided, 0.001 is default.
*/
numbers.EPSILON = 0.001;
},{"./numbers/basic":3,"./numbers/calculus":4,"./numbers/complex":5,"./numbers/dsp":6,"./numbers/generators":7,"./numbers/matrix":8,"./numbers/prime":9,"./numbers/random":10,"./numbers/statistic":11}],3:[function(require,module,exports){
/**
* basic.js
* http://github.com/sjkaliski/numbers.js
*
* Copyright 2012 Stephen Kaliski
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var basic = exports;
/**
* Determine the summation of numbers in a given array.
*
* @param {Array} collection of numbers.
* @return {Number} sum of numbers in array.
*/
basic.sum = function (arr) {
if (Object.prototype.toString.call(arr) === '[object Array]') {
var total = 0;
for (var i = 0; i < arr.length; i++) {
if (typeof (arr[i]) === 'number') {
total = total + arr[i];
} else {
throw new Error('All elements in array must be numbers');
}
}
return total;
} else {
throw new Error('Input must be of type Array');
}
};
/**
* Subtracts elements from one another in array.
*
* e.g [5,3,1,-1] -> 5 - 3 - 1 - (-1) = 2
*
* @param {Array} collection of numbers.
* @return {Number} difference.
*/
basic.subtraction = function (arr) {
if (Object.prototype.toString.call(arr) === '[object Array]') {
var total = arr[0];
if (typeof (total) !== 'number') {
throw new Error('All elements in array must be numbers');
}
for (var i = 1, length = arr.length; i < length; i++) {
if (typeof (arr[i]) === 'number') {
total -= arr[i];
} else {
throw new Error('All elements in array must be numbers');
}
}
return total;
} else {
throw new Error('Input must be of type Array');
}
};
/**
* Product of all elements in an array.
*
* @param {Array} collection of numbers.
* @return {Number} product.
*/
basic.product = function (arr) {
if (Object.prototype.toString.call(arr) === '[object Array]') {
var total = arr[0];
if (typeof (total) !== 'number') {
throw new Error('All elements in array must be numbers');
}
for (var i = 1, length = arr.length; i < length; i++) {
if (typeof (arr[i]) === 'number') {
total = total * arr[i];
} else {
throw new Error('All elements in array must be numbers');
}
}
return total;
} else {
throw new Error('Input must be of type Array');
}
};
/**
* Return the square of any value.
*
* @param {Number} number
* @return {Number} square of number
*/
basic.square = function (num) {
if (typeof (num) !== 'number') {
throw new Error('Input must be a number.');
} else {
return num * num;
}
};
/**
* Calculate the binomial coefficient (n choose k)
*
* @param {Number} available choices
* @param {Number} number chosen
* @return {Number} number of possible choices
*/
basic.binomial = function (n, k) {
var arr = [];
function _binomial(n, k) {
if (typeof (n) !== 'number' && typeof (k) !== 'number') {
throw new Error('Input must be a number.');
}
if (n >= 0 && k === 0) return 1;
if (n === 0 && k > 0) return 0;
if (arr[n] && arr[n][k] > 0) return arr[n][k];
if (!arr[n]) arr[n] = [];
arr[n][k] = _binomial(n - 1, k - 1) + _binomial(n - 1, k);
return arr[n][k];
}
return _binomial(n, k);
};
/**
* Factorial for some integer.
*
* @param {Number} integer.
* @return {Number} result.
*/
basic.factorial = function (num) {
if (typeof (num) !== 'number') throw new Error("Input must be a number.");
if (num < 0) throw new Error("Input must not be negative.");
var i = 2,
o = 1;
while (i <= num) {
o *= i++;
}
return o;
};
/**
* Calculate the greastest common divisor amongst two integers.
*
* @param {Number} number A.
* @param {Number} number B.
* @return {Number} greatest common divisor for integers A, B.
*/
basic.gcd = function (a, b) {
var c;
a = +a;
b = +b;
// Same as isNaN() but faster
if (a !== a || b !== b) {
return NaN;
}
//Same as !isFinite() but faster
if (a === Infinity || a === -Infinity || b === Infinity || b === -Infinity) {
return Infinity;
}
// Checks if a or b are decimals
if ((a % 1 !== 0) || (b % 1 !== 0)) {
throw new Error("Can only operate on integers");
}
while (b) {
c = a % b;
a = b;
b = c;
}
return (0 < a) ? a : -a;
};
/**
* Calculate the least common multiple amongst two integers.
*
* @param {Number} number A.
* @param {Number} number B.
* @return {Number} least common multiple for integers A, B.
*/
basic.lcm = function (num1, num2) {
return Math.abs(num1 * num2) / basic.gcd(num1, num2);
};
/**
* Retrieve a specified quantity of elements from an array, at random.
*
* @param {Array} set of values to select from.
* @param {Number} quantity of elements to retrieve.
* @param {Boolean} allow the same number to be returned twice.
* @return {Array} random elements.
*/
basic.random = function (arr, quant, allowDuplicates) {
if (arr.length === 0) {
throw new Error('Empty array');
} else if (quant > arr.length && !allowDuplicates) {
throw new Error('Quantity requested exceeds size of array');
}
if (allowDuplicates === true) {
var result = [],
i;
for (i = 0; i < quant; i++) {
result[i] = arr[Math.floor(Math.random() * arr.length)];
}
return result;
} else {
return basic.shuffle(arr).slice(0, quant);
}
};
/**
* Shuffle an array, in place.
*
* @param {Array} array to be shuffled.
* @return {Array} shuffled array.
*/
basic.shuffle = function (array) {
var m = array.length,
t, i;
while (m) {
i = Math.floor(Math.random() * m--);
t = array[m];
array[m] = array[i];
array[i] = t;
}
return array;
};
/**
* Find maximum value in an array.
*
* @param {Array} array to be traversed.
* @return {Number} maximum value in the array.
*/
basic.max = function (arr) {
if (!Array.isArray(arr)) {
throw new Error("Input must be of type Array");
}
var max = -Infinity,
val;
for (var i = 0, len = arr.length; i < len; i++) {
val = +arr[i];
if (max < val) {
max = val;
}
// Math.max() returns NaN if one of the elements is not a number.
if (val !== val) {
return NaN;
}
}
return max;
};
/**
* Find minimum value in an array.
*
* @param {Array} array to be traversed.
* @return {Number} minimum value in the array.
*/
basic.min = function (arr) {
if (!Array.isArray(arr)) {
throw new Error("Input must be of type Array");
}
var min = +Infinity,
val;
for (var i = 0, len = arr.length; i < len; i++) {
val = +arr[i];
if (val < min) {
min = val;
}
// Math.min() returns NaN if one of the elements is not a number.
if (val !== val) {
return NaN;
}
}
return min;
};
/**
* Create a range of numbers.
*
* @param {Number} The start of the range.
* @param {Number} The end of the range.
* @return {Array} An array containing numbers within the range.
*/
basic.range = function (start, stop, step) {
var array, i = 0,
len;
if (arguments.length <= 1) {
stop = start || 0;
start = 0;
}
step = step || 1;
if (stop < start) {
step = 0 - Math.abs(step);
}
len = Math.max(Math.ceil((stop - start) / step) + 1, 0);
array = new Array(len);
while (i < len) {
array[i++] = start;
start += step;
}
return array;
};
/**
* Determine if the number is an integer.
*
* @param {Number} the number
* @return {Boolean} true for int, false for not int.
*/
basic.isInt = function (n) {
return n % 1 === 0;
};
/**
* Calculate the divisor and modulus of two integers.
*
* @param {Number} int a.
* @param {Number} int b.
* @return {Array} [div, mod].
*/
basic.divMod = function (a, b) {
if (b <= 0) throw new Error("b cannot be zero. Undefined.");
if (!basic.isInt(a) || !basic.isInt(b)) throw new Error("A or B are not integers.");
return [Math.floor(a / b), a % b];
};
/**
* Calculate:
* if b >= 1: a^b mod m.
* if b = -1: modInverse(a, m).
* if b < 1: finds a modular rth root of a such that b = 1/r.
*
* @param {Number} Number a.
* @param {Number} Number b.
* @param {Number} Modulo m.
* @return {Number} see the above documentation for return values.
*/
basic.powerMod = function (a, b, m) {
if (typeof (a) !== 'number' || typeof (b) !== 'number' || typeof (m) !== 'number') throw new Error("Inputs must be numbers.");
// If b < -1 should be a small number, this method should work for now.
if (b < -1) return Math.pow(a, b) % m;
if (b === 0) return 1 % m;
if (b >= 1) {
var result = 1;
while (b > 0) {
if ((b % 2) === 1) {
result = (result * a) % m;
}
a = (a * a) % m;
b = b >> 1;
}
return result;
}
if (b === -1) return basic.modInverse(a, m);
if (b < 1) {
return basic.powerMod(a, Math.pow(b, -1), m);
}
};
/**
* Calculate the extended Euclid Algorithm or extended GCD.
*
* @param {Number} int a.
* @param {Number} int b.
* @return {Array} [a, x, y] a is the GCD. x and y are the values such that ax + by = gcd(a, b) .
*/
basic.egcd = function (a, b) {
a = +a;
b = +b;
// Same as isNaN() but faster
if (a !== a || b !== b) {
return [NaN, NaN, NaN];
}
//Same as !isFinite() but faster
if (a === Infinity || a === -Infinity || b === Infinity || b === -Infinity) {
return [Infinity, Infinity, Infinity];
}
// Checks if a or b are decimals
if ((a % 1 !== 0) || (b % 1 !== 0)) {
throw new Error("Can only operate on integers");
}
var signX = (a < 0) ? -1 : 1,
signY = (b < 0) ? -1 : 1,
x = 0,
y = 1,
oldX = 1,
oldY = 0,
q, r, m, n;
a = Math.abs(a);
b = Math.abs(b);
while (a !== 0) {
q = Math.floor(b / a);
r = b % a;
m = x - oldX * q;
n = y - oldY * q;
b = a;
a = r;
x = oldX;
y = oldY;
oldX = m;
oldY = n;
}
return [b, signX * x, signY * y];
};
/**
* Calculate the modular inverse of a number.
*
* @param {Number} Number a.
* @param {Number} Modulo m.
* @return {Number} if true, return number, else throw error.
*/
basic.modInverse = function (a, m) {
var r = basic.egcd(a, m);
if (r[0] !== 1) throw new Error('No modular inverse exists');
return r[1] % m;
};
/**
* Determine is two numbers are equal within a given margin of precision.
*
* @param {Number} first number.
* @param {Number} second number.
* @param {Number} epsilon.
*/
basic.numbersEqual = function (first, second, epsilon) {
if (typeof (first) !== 'number' || typeof (second) !== 'number' || typeof (epsilon) !== 'number') throw new Error("First and Second must be numbers.");
return (first - second) < epsilon && (first - second) > -epsilon;
};
/**
* Calculate the falling factorial of a number
*
* {@see http://mathworld.wolfram.com/FallingFactorial.html}
*
* @param {Number} Base
* @param {Number} Steps to fall
* @returns {Number} Result
*/
basic.fallingFactorial = function (n, k) {
var i = (n - k + 1),
r = 1;
if (n < 0) {
throw new Error("n cannot be negative.");
}
if (k > n) {
throw new Error("k cannot be greater than n.");
}
while (i <= n) {
r *= i++;
}
return r;
};
/**
* Calculate the permutation (n choose k)
*
* @param {Number} available choices
* @param {Number} number chosen
* @return {Number} number of ordered variations
*/
basic.permutation = function (n, k) {
if (n <= 0) {
throw new Error("n cannot be less than or equal to 0.");
}
if (n < k) {
throw new Error("k cannot be greater than k.");
}
var binomial = basic.binomial(n, k);
var permutation = binomial * basic.factorial(k);
return permutation;
};
},{}],4:[function(require,module,exports){
/**
* calculus.js
* http://github.com/sjkaliski/numbers.js
*
* Copyright 2012 Stephen Kaliski
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var numbers = require('../numbers');
var calculus = exports;
/**
* Calculate point differential for a specified function at a
* specified point. For functions of one variable.
*
* @param {Function} math function to be evaluated.
* @param {Number} point to be evaluated.
* @return {Number} result.
*/
calculus.pointDiff = function (func, point) {
var a = func(point - 0.001);
var b = func(point + 0.001);
return (b - a) / (0.002);
};
/**
* Calculate riemann sum for a specified, one variable, function
* from a starting point, to a finishing point, with n divisions.
*
* @param {Function} math function to be evaluated.
* @param {Number} point to initiate evaluation.
* @param {Number} point to complete evaluation.
* @param {Number} quantity of divisions.
* @param {Function} (Optional) Function that returns which value
* to sample on each interval; if none is provided, left endpoints
* will be used.
* @return {Number} result.
*/
calculus.Riemann = function (func, start, finish, n, sampler) {
var inc = (finish - start) / n;
var totalHeight = 0;
var i;
if (typeof sampler === 'function') {
for (i = start; i < finish; i += inc) {
totalHeight += func(sampler(i, i + inc));
}
} else {
for (i = start; i < finish; i += inc) {
totalHeight += func(i);
}
}
return totalHeight * inc;
};
/**
* Helper function in calculating integral of a function
* from a to b using simpson quadrature.
*
* @param {Function} math function to be evaluated.
* @param {Number} point to initiate evaluation.
* @param {Number} point to complete evaluation.
* @return {Number} evaluation.
*/
function SimpsonDef(func, a, b) {
var c = (a + b) / 2;
var d = Math.abs(b - a) / 6;
return d * (func(a) + 4 * func(c) + func(b));
}
/**
* Helper function in calculating integral of a function
* from a to b using simpson quadrature. Manages recursive
* investigation, handling evaluations within an error bound.
*
* @param {Function} math function to be evaluated.
* @param {Number} point to initiate evaluation.
* @param {Number} point to complete evaluation.
* @param {Number} total value.
* @param {Number} Error bound (epsilon).
* @return {Number} recursive evaluation of left and right side.
*/
function SimpsonRecursive(func, a, b, whole, eps) {
var c = a + b;
var left = SimpsonDef(func, a, c);
var right = SimpsonDef(func, c, b);
if (Math.abs(left + right - whole) <= 15 * eps) {
return left + right + (left + right - whole) / 15;
} else {
return SimpsonRecursive(func, a, c, eps / 2, left) + SimpsonRecursive(func, c, b, eps / 2, right);
}
}
/**
* Evaluate area under a curve using adaptive simpson quadrature.
*
* @param {Function} math function to be evaluated.
* @param {Number} point to initiate evaluation.
* @param {Number} point to complete evaluation.
* @param {Number} Optional error bound (epsilon);
* global error bound will be used as a fallback.
* @return {Number} area underneath curve.
*/
calculus.adaptiveSimpson = function (func, a, b, eps) {
eps = (typeof eps === 'undefined') ? numbers.EPSILON : eps;
return SimpsonRecursive(func, a, b, SimpsonDef(func, a, b), eps);
};
/**
* Calculate limit of a function at a given point. Can approach from
* left, middle, or right.
*
* @param {Function} math function to be evaluated.
* @param {Number} point to evaluate.
* @param {String} approach to limit.
* @return {Number} limit.
*/
calculus.limit = function (func, point, approach) {
if (approach === 'left') {
return func(point - 1e-15);
} else if (approach === 'right') {
return func(point + 1e-15);
} else if (approach === 'middle') {
return (calculus.limit(func, point, 'left') + calculus.limit(func, point, 'right')) / 2;
} else {
throw new Error('Approach not provided');
}
};
/**
* Calculate Stirling approximation gamma.
*
* @param {Number} number to calculate.
* @return {Number} gamma.
*/
calculus.StirlingGamma = function (num) {
return Math.sqrt(2 * Math.PI / num) * Math.pow((num / Math.E), num);
};
/**
* Calculate Lanczos approximation gamma.
*
* @param {Number} number to calculate.
* @return {Number} gamma.
*/
calculus.LanczosGamma = function (num) {
var p = [
0.99999999999980993, 676.5203681218851, -1259.1392167224028,
771.32342877765313, -176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6, 1.5056327351493116e-7
];
var i;
var g = 7;
if (num < 0.5) return Math.PI / (Math.sin(Math.PI * num) * calculus.LanczosGamma(1 - num));
num -= 1;
var a = p[0];
var t = num + g + 0.5;
for (i = 1; i < p.length; i++) {
a += p[i] / (num + i);
}
return Math.sqrt(2 * Math.PI) * Math.pow(t, num + 0.5) * Math.exp(-t) * a;
};
/**
* Calculate the integral of f(x1,x2,...) over intervals
* [a1,b1], [a2,b2], ..., using the montecarlo method:
*
* integral of f(x,y) = (1/N)*(b2-a2)*(b1-a1)*sum(f)
*
* where N = number of points for f to be evaluated at.
* The error for this method is about 1/root(N) and will
* always converge.
*
* @param {Function} math function.
* @param {Number} number of points
* @param {Array(s)} intervals
* @return {Number} approximation to integral
*/
calculus.MonteCarlo = function (func, N) {
//takes an arbitrary number of arguments after N
//all of the arguments must be arrays which are intervals
if (arguments.length < 2) {
throw new Error('Please enter at least one interval.');
} else if (N <= 0) {
throw new Error('Please use a positive integer for N.');
}
var L = [];
N = Math.ceil(N);
for (var i = 2; i < arguments.length; i++) {
L.push(arguments[i]);
}
var coeff = L.map(function (l) { //subtract the endpoints
return l[1] - l[0];
}).reduce(function (a, b) { //multiply each endpoint difference
return a * b;
}) / N;
var fvals = numbers.matrix.transpose(L.map(function (l) {
//generate an array of arrays, each nested array being N
//random values in each interval - N-by-3 array, and then
//transpose it to get a 3-by-N array
return numbers.statistic.randomSample(l[0], l[1], N);
})).map(function (l) {
//evaluate func at each set of points
return func.apply(null, [l[0], l[1], l[2]]);
});
return coeff * fvals.reduce(function (a, b) {
return a + b;
});
};
},{"../numbers":2}],5:[function(require,module,exports){
/**
* complex.js
* http://github.com/sjkaliski/numbers.js
*
* Copyright 2012 Stephen Kaliski
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var numbers = require('../numbers');
var basic = numbers.basic;
var Complex = function (re, im) {
this.re = re;
this.im = im;
this.r = this.magnitude();
this.t = this.phase(); // theta = t = arg(z)
};
/**
* Add a complex number to this one.
*
* @param {Complex} Number to add.
* @return {Complex} New complex number (sum).
*/
Complex.prototype.add = function (addend) {
return new Complex(this.re + addend.re, this.im + addend.im);
};
/**
* Subtract a complex number from this one.
*
* @param {Complex} Number to subtract.
* @return {Complex} New complex number (difference).
*/
Complex.prototype.subtract = function (subtrahend) {
return new Complex(this.re - subtrahend.re, this.im - subtrahend.im);
};
/**
* Multiply a complex number with this one.
*
* @param {Complex} Number to multiply by.
* @return {Complex} New complex number (product).
*/
Complex.prototype.multiply = function (multiplier) {
var re = this.re * multiplier.re - this.im * multiplier.im;
var im = this.im * multiplier.re + this.re * multiplier.im;
return new Complex(re, im);
};
/**
* Divide this number with another complex number.
*
* @param {Complex} Divisor.
* @return {Complex} New complex number (quotient).
*/
Complex.prototype.divide = function (divisor) {
var denominator = divisor.re * divisor.re + divisor.im * divisor.im;
var re = (this.re * divisor.re + this.im * divisor.im) / denominator;
var im = (this.im * divisor.re - this.re * divisor.im) / denominator;
return new Complex(re, im);
};
/**
* Get the magnitude of this number.
*
* @return {Number} Magnitude.
*/
Complex.prototype.magnitude = function () {
return Math.sqrt(this.re * this.re + this.im * this.im);
};
/**
* Get the phase of this number.
*
* @return {Number} Phase.
*/
Complex.prototype.phase = function () {
return Math.atan2(this.im, this.re);
};
/**
* Conjugate the imaginary part
*
* @return {Complex} Conjugated number
*/
Complex.prototype.conjugate = function () {
return new Complex(this.re, -1 * this.im);
};
/**
* Raises this complex number to the nth power.
*
* @param {number} power to raise this complex number to.
* @return {Complex} the nth power of this complex number.
*/
Complex.prototype.pow = function (n) {
var constant = Math.pow(this.magnitude(), n);
return new Complex(constant * Math.cos(n * this.phase()), constant * Math.sin(n * this.phase()));
};
/**
* Raises this complex number to given complex power.
*
* @param complexN the complex number to raise this complex number to.
* @return {Complex} this complex number raised to the given complex number.
*/
Complex.prototype.complexPow = function (complexN) {
var realSqPlusImSq = Math.pow(this.re, 2) + Math.pow(this.im, 2);
var multiplier = Math.pow(realSqPlusImSq, complexN.re / 2) * Math.pow(Math.E, -complexN.im * this.phase());
var theta = complexN.re * this.phase() + 0.5 * complexN.im * Math.log(realSqPlusImSq);
return new Complex(multiplier * Math.cos(theta), multiplier * Math.sin(theta));
};
/**
* Find all the nth roots of this complex number.
*
* @param {Number} root of this complex number to take.
* @return {Array} an array of size n with the roots of this complex number.
*/
Complex.prototype.roots = function (n) {
var result = new Array(n);
for (var i = 0; i < n; i++) {
var theta = (this.phase() + 2 * Math.PI * i) / n;
var radiusConstant = Math.pow(this.magnitude(), 1 / n);
result[i] = (new Complex(radiusConstant * Math.cos(theta), radiusConstant * Math.sin(theta)));
}
return result;
};
/**
* Returns the sine of this complex number.
*
* @return {Complex} the sine of this complex number.
*/
Complex.prototype.sin = function () {
var E = new Complex(Math.E, 0);
var i = new Complex(0, 1);
var negativeI = new Complex(0, -1);
var numerator = E.complexPow(i.multiply(this)).subtract(E.complexPow(negativeI.multiply(this)));
return numerator.divide(new Complex(0, 2));
};
/**
* Returns the cosine of this complex number.
*
* @return {Complex} the cosine of this complex number.
*/
Complex.prototype.cos = function () {
var E = new Complex(Math.E, 0);
var i = new Complex(0, 1);
var negativeI = new Complex(0, -1);
var numerator = E.complexPow(i.multiply(this)).add(E.complexPow(negativeI.multiply(this)));
return numerator.divide(new Complex(2, 0));
};
/**
* Returns the tangent of this complex number.
*
* @return {Complex} the tangent of this complex number.
*/
Complex.prototype.tan = function () {
return this.sin().divide(this.cos());
};
/**
* Checks for equality between this complex number and another
* within a given range defined by epsilon.
*
* @param {Complex} complex number to check this number against.
* @param {Number} epsilon
* @return {boolean} true if equal within epsilon, false otherwise
*/
Complex.prototype.equals = function (complex, epsilon) {
return basic.numbersEqual(this.re, complex.re, epsilon) &&
basic.numbersEqual(this.im, complex.im, epsilon);
};
module.exports = Complex;
},{"../numbers":2}],6:[function(require,module,exports){
/**
* dsp.js
* http://github.com/sjkaliski/numbers.js
*
* Copyright 2012 Stephen Kaliski
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var numbers = require('../numbers');
var Complex = numbers.complex;
var dsp = exports;
/**
* Returns an array composed of elements from arr, starting at index start
* and counting by step.
*
* @param {Array} Input array.
* @param {Number} Starting array index.
* @param {Number} Step size.
* @return {Array} Resulting sub-array.
*/
dsp.segment = function (arr, start, step) {
var result = [];
for (var i = start; i < arr.length; i += step) {
result.push(arr[i]);
}
return result;
};
/**
* Returns an array of complex numbers representing the frequency spectrum
* of real valued time domain sequence x. (x.length must be integer power of 2)
* Inspired by http://rosettacode.org/wiki/Fast_Fourier_transform#Python
*
* @param {Array} Real-valued series input, eg. time-series.
* @return {Array} Array of complex numbers representing input signal in Fourier domain.
*/
dsp.fft = function (x) {
var N = x.length;
if (N <= 1) {
return [new Complex(x[0], 0)];
}
if (Math.log(N) / Math.LN2 % 1 !== 0) {
throw new Error('Array length must be integer power of 2');
}
var even = dsp.fft(dsp.segment(x, 0, 2));
var odd = dsp.fft(dsp.segment(x, 1, 2));
var res = [],
Nby2 = N / 2;
for (var k = 0; k < N; k++) {
var tmpPhase = -2 * Math.PI * k / N;
var phasor = new Complex(Math.cos(tmpPhase), Math.sin(tmpPhase));
if (k < Nby2) {
res[k] = even[k].add(phasor.multiply(odd[k]));
} else {
res[k] = even[k - Nby2].subtract(phasor.multiply(odd[k - Nby2]));
}
}
return res;
};
},{"../numbers":2}],7:[function(require,module,exports){
/**
* generators.js
* http://github.com/sjkaliski/numbers.js
*
* Copyright 2012 Stephen Kaliski, Kartik Talwar
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var generate = exports;
/**
* Fast Fibonacci Implementation
*
* @param {Number} number to calculate
* @return {Number} nth fibonacci number
*/
generate.fibonacci = function (n) {
// Adapted from
// http://bosker.wordpress.com/2011/04/29/the-worst-algorithm-in-the-world/
var bitSystem = function (n) {
var bit, bits = [];
while (n > 0) {
bit = (n < 2) ? n : n % 2;
n = Math.floor(n / 2);
bits.push(bit);
}
return bits.reverse();
};
var a = 1;
var b = 0;
var c = 1;
var system = bitSystem(n);
var temp;
for (var i = 0; i < system.length; i++) {
var bit = system[i];
if (bit) {
temp = [(a + c) * b, (b * b) + (c * c)];
a = temp[0];
b = temp[1];
} else {
temp = [(a * a) + (b * b), (a + c) * b];
a = temp[0];
b = temp[1];
}
c = a + b;
}
return b;
};
/**
* Populate the given array with a Collatz Sequence.
*
* @param {Number} first number.
* @param {Array} arrary to be populated.
* @return {Array} array populated with Collatz sequence
*/
generate.collatz = function (n, result) {
result.push(n);
if (n === 1) {
return;
} else if (n % 2 === 0) {
generate.collatz(n / 2, result);
} else {
generate.collatz(3 * n + 1, result);
}
};
},{}],8:[function(require,module,exports){
/**
* matrix.js
* http://github.com/sjkaliski/numbers.js
*
* Copyright 2012 Stephen Kaliski
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var matrix = exports;
var ERROR_MATRIX_NOT_SQUARE = 'Matrix must be square.',
ERROR_VECTOR_NOT_2D = 'Only two dimensional operations are supported at this time.';
/**
* Check to see if a point is 2D. Used in all 2D vector functions.
* Throw error if it's not.
*
* @param {Array} point in question.
* @return {undefined} nothing is returned.
*/
matrix._check2DVector = function (point) {
if (point.length !== 2) {
throw new Error(ERROR_VECTOR_NOT_2D);
}
};
/**
* Return a deep copy of the input matrix.
*
* @param {Array} matrix to copy.
* @return {Array} copied matrix.
*/
matrix.deepCopy = function (arr) {
if (!Array.isArray(arr)) {
throw new Error('Input must be a matrix.');
} else if (arr[0][0] === undefined) {
throw new Error('Input cannot be a vector.');
}
var result = new Array(arr.length);
for (var i = 0; i < arr.length; i++) {
result[i] = arr[i].slice();
}
return result;
};
/**
* Return true if matrix is square, false otherwise.
*
* @param {Array} arr
* @return {Boolean}
*/
matrix.isSquare = function (arr) {
if (!Array.isArray(arr)) {
throw new Error('Input must be a matrix.');
} else if (arr[0][0] === undefined) {
throw new Error('Input cannot be a vector.');
}
var rows = arr.length;
for (var i = 0; i < rows; i++) {
if (arr[i].length !== rows) return false;
}
return true;
};
/**
* Add two matrices together. Matrices must be of same dimension.
*
* @param {Array} matrix A.
* @param {Array} matrix B.
* @return {Array} summed matrix.
*/
matrix.addition = function (arrA, arrB) {
if (arrA.length !== arrB.length || arrA[0].length !== arrB[0].length) {
throw new Error('Matrix mismatch');
}
var result = new Array(arrA.length),
i;
if (!arrA[0].length) {
// The arrays are vectors.
for (i = 0; i < arrA.length; i++) {
result[i] = arrA[i] + arrB[i];
}
} else {
for (i = 0; i < arrA.length; i++) {
result[i] = new Array(arrA[i].length);
for (var j = 0; j < arrA[i].length; j++) {
result[i][j] = arrA[i][j] + arrB[i][j];
}
}
}
return result;
};
/**
* Subtract one matrix from another (A - B). Matrices must be of same dimension.
*
* @param {Array} matrix A.
* @param {Array} matrix B.
* @return {Array} subtracted matrix.
*/
matrix.subtraction = function (arrA, arrB) {
if (arrA.length !== arrB.length || arrA[0].length !== arrB[0].length) {
throw new Error("Matrix mismatch");
}
var result = new Array(arrA.length),
i;
if (!arrA[0].length) {
// The arrays are vectors.
for (i = 0; i < arrA.length; i++) {
result[i] = arrA[i] - arrB[i];
}
} else {
for (i = 0; i < arrA.length; i++) {
result[i] = new Array(arrA[i].length);
for (var j = 0; j < arrA[i].length; j++) {
result[i][j] = arrA[i][j] - arrB[i][j];
}
}
}
return result;
};
/**
* Scalar multiplication on an matrix.
*
* @param {Array} matrix.
* @param {Number} scalar.
* @return {Array} updated matrix.
*/
matrix.scalar = function (arr, val) {
for (var i = 0; i < arr.length; i++) {
for (var j = 0; j < arr[i].length; j++) {
arr[i][j] = val * arr[i][j];
}
}
return arr;
};
/**
* Transpose a matrix.
*
* @param {Array} matrix.
* @return {Array} transposed matrix.
*/
matrix.transpose = function (arr) {
var result = new Array(arr[0].length);
for (var i = 0; i < arr[0].length; i++) {
result[i] = new Array(arr.length);
for (var j = 0; j < arr.length; j++) {
result[i][j] = arr[j][i];
}
}
return result;
};
/**
* Create an identity matrix of dimension n x n.
*
* @param {Number} dimension of the identity array to be returned.
* @return {Array} n x n identity matrix.
*/
matrix.identity = function (n) {
var result = new Array(n);
for (var i = 0; i < n; i++) {
result[i] = new Array(n);
for (var j = 0; j < n; j++) {
result[i][j] = (i === j) ? 1 : 0;
}
}
return result;
};
/**
* Evaluate dot product of two vectors. Vectors must be of same length.
*
* @param {Array} vector.
* @param {Array} vector.
* @return {Array} dot product.
*/
matrix.dotproduct = function (vectorA, vectorB) {
if (vectorA.length !== vectorB.length) {
throw new Error("Vector mismatch");
}
var result = 0;
for (var i = 0; i < vectorA.length; i++) {
result += vectorA[i] * vectorB[i];
}
return result;
};
/**
* Multiply two matrices. They must abide by standard matching.
*
* e.g. A x B = (m x n) x (n x m), where n, m are integers who define
* the dimensions of matrices A, B.
*
* @param {Array} matrix.
* @param {Array} matrix.
* @return {Array} result of multiplied matrices.
*/
matrix.multiply = function (arrA, arrB) {
if (arrA[0].length !== arrB.length) {
throw new Error("Matrix mismatch");
}
var result = new Array(arrA.length);
for (var x = 0; x < arrA.length; x++) {
result[x] = new Array(arrB[0].length);
}
var arrB_T = matrix.transpose(arrB);
for (var i = 0; i < result.length; i++) {
for (var j = 0; j < result[i].length; j++) {
result[i][j] = matrix.dotproduct(arrA[i], arrB_T[j]);
}
}
return result;
};
/**
* Evaluate determinate of matrix. Expect speed
* degradation for matrices over 4x4.
*
* @param {Array} matrix.
* @return {Number} determinant.
*/
matrix.determinant = function (m) {
var numRow = m.length;
var numCol = m[0].length;
var det = 0;
var row, col;
var diagLeft, diagRight;
if (!matrix.isSquare(m)) {
throw new Error(ERROR_MATRIX_NOT_SQUARE);
}
if (numRow === 1) {
return m[0][0];
} else if (numRow === 2) {
return m[0][0] * m[1][1] - m[0][1] * m[1][0];
}
for (col = 0; col < numCol; col++) {
diagLeft = m[0][col];
diagRight = m[0][col];
for (row = 1; row < numRow; row++) {
diagRight *= m[row][(((col + row) % numCol) + numCol) % numCol];
diagLeft *= m[row][(((col - row) % numCol) + numCol) % numCol];
}
det += diagRight - diagLeft;
}
return det;
};
/**
* Returns a LUP decomposition of the given matrix such that:
*
* A*P = L*U
*
* Where
* A is the input matrix
* P is a pivot matrix
* L is a lower triangular matrix
* U is a upper triangular matrix
*
* This method returns an array of three matrices such that:
*
* matrix.luDecomposition(array) = [L, U, P]
*
* @param {Array} arr
* @return {Array} array of matrices [L, U, P]
*/
matrix.lupDecomposition = function (arr) {
if (!matrix.isSquare(arr)) {
throw new Error(ERROR_MATRIX_NOT_SQUARE);
}
var size = arr.length;
var LU = matrix.deepCopy(arr);
var P = matrix.transpose(matrix.identity(size));
var currentRow;
var currentColumn = new Array(size);
this.getL = function (a) {
var m = a[0].length;
var L = matrix.identity(m);
for (var i = 0; i < m; i++) {
for (var j = 0; j < m; j++) {
if (i > j) {
L[i][j] = a[i][j];
}
}
}
return L;
};
this.getU = function (a) {
var m = a[0].length;
var U = matrix.identity(m);
for (var i = 0; i < m; i++) {
for (var j = 0; j < m; j++) {
if (i <= j) {
U[i][j] = a[i][j];
}
}
}
return U;
};
for (var j = 0; j < size; j++) {
var i;
for (i = 0; i < size; i++) {
currentColumn[i] = LU[i][j];
}
for (i = 0; i < size; i++) {
currentRow = LU[i];
var minIndex = Math.min(i, j);
var s = 0;
for (var k = 0; k < minIndex; k++) {
s += currentRow[k] * currentColumn[k];
}
currentRow[j] = currentColumn[i] -= s;
}
//Find pivot
var pivot = j;
for (i = j + 1; i < size; i++) {
if (Math.abs(currentColumn[i]) > Math.abs(currentColumn[pivot])) {
pivot = i;
}
}
if (pivot !== j) {
LU = matrix.rowSwitch(LU, pivot, j);
P = matrix.rowSwitch(P, pivot, j);
}
if (j < size && LU[j][j] !== 0) {
for (i = j + 1; i < size; i++) {
LU[i][j] /= LU[j][j];
}
}
}
return [this.getL(LU), this.getU(LU), P];
};
/**
* Rotate a two dimensional vector by degree.
*
* @param {Array} point.
* @param {Number} degree.
* @param {String} direction - clockwise or counterclockwise.
* @return {Array} vector.
*/
matrix.rotate = function (point, degree, direction) {
matrix._check2DVector(point);
var negate = direction === 'clockwise' ? -1 : 1;
var radians = degree * (Math.PI / 180);
var transformation = [
[Math.cos(radians), -1 * negate * Math.sin(radians)],
[negate * Math.sin(radians), Math.cos(radians)]
];
return matrix.multiply(transformation, point);
};
/**
* Scale a two dimensional vector by scale factor x and scale factor y.
*
* @param {Array} point.
* @param {Number} sx.
* @param {Number} sy.
* @return {Array} vector.
*/
matrix.scale = function (point, sx, sy) {
matrix._check2DVector(point);
var transformation = [
[sx, 0],
[0, sy]
];
return matrix.multiply(transformation, point);
};
/**
* Shear a two dimensional vector by shear factor k.
*
* @param {Array} point.
* @param {Number} k.
* @param {String} direction - xaxis or yaxis.
* @return {Array} vector.
*/
matrix.shear = function (point, k, direction) {
matrix._check2DVector(point);
var xplaceholder = direction === 'xaxis' ? k : 0;
var yplaceholder = direction === 'yaxis' ? k : 0;
var transformation = [
[1, xplaceholder],
[yplaceholder, 1]
];
return matrix.multiply(transformation, point);
};
/**
* Perform an affine transformation on the given vector.
*
* @param {Array} point.
* @param {Number} tx.
* @param {Number} ty.
* @return {Array} vector.
*/
matrix.affine = function (point, tx, ty) {
matrix._check2DVector(point);
var transformation = [
[1, 0, tx],
[0, 1, ty],
[0, 0, 1]
];
var newpoint = [
[point[0][0]],
[point[1][0]],
[1]
];
var transformed = matrix.multiply(transformation, newpoint);
return [
[transformed[0][0]],
[transformed[1][0]]
];
};
/**
* Scales a row of a matrix by a factor and returns the updated matrix.
* Used in row reduction functions.
*
* @param {Array} matrix.
* @param {Number} row.
* @param {Number} scale.
*/
matrix.rowScale = function (m, row, scale) {
var result = new Array(m.length);
for (var i = 0; i < m.length; i++) {
result[i] = new Array(m[i].length);
for (var j = 0; j < m[i].length; j++) {
if (i === row) {
result[i][j] = scale * m[i][j];
} else {
result[i][j] = m[i][j];
}
}
}
return result;
};
/**
* Swaps two rows of a matrix and returns the updated matrix.
* Used in row reduction functions.
*
* @param {Array} matrix.
* @param {Number} row1.
* @param {Number} row2.
*/
matrix.rowSwitch = function (m, row1, row2) {
var result = new Array(m.length);
for (var i = 0; i < m.length; i++) {
result[i] = new Array(m[i].length);
for (var j = 0; j < m[i].length; j++) {
if (i === row1) {
result[i][j] = m[row2][j];
} else if (i === row2) {
result[i][j] = m[row1][j];
} else {
result[i][j] = m[i][j];
}
}
}
return result;
};
/**
* Adds a multiple of one row to another row
* in a matrix and returns the updated matrix.
* Used in row reduction functions.
*
* @param {Array} matrix.
* @param {Number} row1.
* @param {Number} row2.
*/
matrix.rowAddMultiple = function (m, from, to, scale) {
var result = new Array(m.length);
for (var i = 0; i < m.length; i++) {
result[i] = new Array(m[i].length);
for (var j = 0; j < m[i].length; j++) {
if (i === to) {
result[to][j] = m[to][j] + scale * m[from][j];
} else {
result[i][j] = m[i][j];
}
}
}
return result;
};
/**
* Gauss-Jordan Elimination
*
* @param {Array} matrix.
* @param {Number} epsilon.
* @return {Array} RREF matrix.
*/
matrix.GaussJordanEliminate = function (m, epsilon) {
// Translated from:
// http://elonen.iki.fi/code/misc-notes/python-gaussj/index.html
var eps = (typeof epsilon === 'undefined') ? 1e-10 : epsilon;
var h = m.length;
var w = m[0].length;
var y = -1;
var y2, x, c;
while (++y < h) {
// Pivot.
var maxrow = y;
y2 = y;
while (++y2 < h) {
if (Math.abs(m[y2][y]) > Math.abs(m[maxrow][y]))
maxrow = y2;
}
var tmp = m[y];
m[y] = m[maxrow];
m[maxrow] = tmp;
// Singular
if (Math.abs(m[y][y]) <= eps) {
return m;
}
// Eliminate column
y2 = y;
while (++y2 < h) {
c = m[y2][y] / m[y][y];
x = y - 1;
while (++x < w) {
m[y2][x] -= m[y][x] * c;
}
}
}
// Backsubstitute.
y = h;
while (--y >= 0) {
c = m[y][y];
y2 = -1;
while (++y2 < y) {
x = w;
while (--x >= y) {
m[y2][x] -= m[y][x] * m[y2][y] / c;
}
}
m[y][y] /= c;
// Normalize row
x = h - 1;
while (++x < w) {
m[y][x] /= c;
}
}
return m;
};
/**
* Alias to Gauss-Jordan Elimination
*
* @param {Array} matrix.
* @param {Number} epsilon.
* @return {Array} RREF matrix.
*/
matrix.rowReduce = function (m, epsilon) {
return matrix.GaussJordanEliminate(m, epsilon);
};
/**
* nxn matrix inversion
*
* @param {Array} matrix.
* @return {Array} inverted matrix.
*/
matrix.inverse = function (m) {
if (!matrix.isSquare(m)) {
throw new Error(ERROR_MATRIX_NOT_SQUARE);
}
var n = m.length,
identity = matrix.identity(n),
i;
// AI
for (i = 0; i < n; i++) {
m[i] = m[i].concat(identity[i]);
}
// inv(IA)
m = matrix.GaussJordanEliminate(m);
// inv(A)
for (i = 0; i < n; i++) {
m[i] = m[i].slice(n);
}
return m;
};
/**
* Get a column of a matrix as a vector.
*
* @param {Array} matrix
* @param {Int} column number
* @return {Array} column
*/
matrix.getCol = function (M, n) {
var result = new Array(M.length);
if (n < 0) {
throw new Error('The specified column must be a positive integer.');
} else if (n >= M[0].length) {
throw new Error('The specified column must be between 0 and the number of columns - 1.');
}
for (var i = 0; i < M.length; i++) {
result[i] = M[i][n];
}
return result;
};
/**
* Reorder the rows of a matrix based off an array of numbers.
*
* @param {Array} matrix
* @param {Array} desired re-ordering
* @return {Array} reordered matrix
*/
matrix.reorderRows = function (M, L) {
var result = [];
if (L === undefined) {
throw new Error('A reordering array must be entered.');
} else if (L.length !== M.length) {
throw new Error('The reordered matrix must have the same number of rows as the original matrix.');
}
for (var i = 0; i < L.length; i++) {
if (L[i] < 0) {
throw new Error('The desired order of the rows must be positive integers.');
} else if (L[i] >= L.length) {
throw new Error('The desired order of the rows must start at 0 and end at the number of rows - 1.');
} else {
result.push(M[L[i]]);
}
}
return result;
};
/**
* Reorder the columns of a matrix based off an array of numbers.
*
* @param {Array} matrix
* @param {Array} desired re-ordering
* @return {Array} reordered matrix
*/
matrix.reorderCols = function (M, L) {
var result = [];
if (L === undefined) {
throw new Error('Please enter a desired reordering array.');
} else if (L.length !== M[0].length) {
throw new Error('The reordered matrix must have the same number of columns as the original matrix.');
}
for (var i = 0; i < L.length; i++) {
if (L[i] < 0) {
throw new Error('The desired order of the columns must be positive integers.');
} else if (L[i] >= L.length) {
throw new Error('The desired order of the columns must start at 0 and end at the number of columns - 1.');
} else {
result.push(matrix.getCol(M, L[i]));
}
}
return matrix.transpose(result);
};
/**
* Reverse the rows of a matrix.
*
* @param {Array} matrix
* @return {Array} reversed matrix
*/
matrix.reverseRows = function (M) {
var L = [];
for (var i = M.length - 1; i > -1; i--) {
L.push(i);
}
return matrix.reorderRows(M, L);
};
/**
* Reverse the columns of a matrix.
*
* @param {Array} matrix
* @return {Array} reversed matrix
*/
matrix.reverseCols = function (M) {
var L = [];
for (var i = M.length - 1; i > -1; i--) {
L.push(i);
}
return matrix.reorderCols(M, L);
};
/**
* Create a n x m matrix of zeros.
*
* @param {Int} number of rows
* @param {Int} number of columns
* @return {Array} matrix
*/
matrix.zeros = function (n, m) {
var M = new Array(n);
if (n < 1 || m < 1) {
throw new Error('The matrix dimensions must be positive integers.');
}
n = Math.ceil(n);
m = Math.ceil(m);
for (var i = 0; i < n; i++) {
var empty = new Array(m);
for (var j = 0; j < m; j++) {
empty[j] = 0;
}
M[i] = empty;
}
return M;
};
/**
* Create a zigzag matrix. point represents the starting corner,
* dir represents which direction to begin moving in. There are
* 8 possible permutations for this. Rounds dimension upwards.
*
* @param {Int} size of (square) matrix
* @param {String} corner (TL,TR,BL,BR)
* @param {String} direction (V,H)
* @return {Array} zigzag matrix.
*/
matrix.zigzag = function (n, point, dir) {
if (n <= 1) {
throw new Error('Matrix size must be at least 2x2.');
}
n = Math.ceil(n);
var mat = matrix.zeros(n, n);
//create one kind of permutation - all other permutations can be
//created from this particular permutation through transformations
var BRH = function (M) { //starting at bottom right, moving horizontally
var jump = false,
tl = n * n,
br = 1,
inc = 1,
row, col, val, i, j;
M[0][0] = tl;
M[n - 1][n - 1] = br;
for (i = 1; i < n; i++) {
//generate top/bottom row
if (jump) {
tl -= 4 * inc;
br += 4 * inc;
inc++;
} else {
tl--;
br++;
}
M[0][i] = tl;
M[n - 1][n - 1 - i] = br;
jump = !jump;
}
var dec = true;
for (i = 1; i < n; i++) {
//iterate diagonally from top row
row = 0;
col = i;
val = M[row][col];
for (j = 1; j < i + 1; j++) {
if (dec) {
val -= 1;
} else {
val += 1;
}
row++;
col--;
M[row][col] = val;
}
dec = !dec;
}
if (n % 2 === 0) {
dec = true;
} else {
dec = false;
}
for (i = 1; i < n - 1; i++) {
//iterate diagonally from bottom row
row = n - 1;
col = i;
val = M[row][col];
for (j = 1; j < n - i; j++) {
if (dec) {
val--;
} else {
val++;
}
row--;
col++;
M[row][col] = val;
}
dec = !dec;
}
return M;
};
var BRV = function (M) { //starting at bottom right, moving vertically
return matrix.transpose(BRH(M));
};
var BLH = function (M) { //starting at bottom left, moving horizontally
return matrix.reverseCols(BRH(M));
};
var BLV = function (M) { //starting at bottom left, moving vertically
return matrix.reverseRows(TLV(BLH(M)));
};
var TRH = function (M) { //starting at top right, moving horizontally
return matrix.reverseRows(BRH(M));
};
var TRV = function (M) { //starting at top right, moving vertically
return matrix.reverseRows(BRV(M));
};
var TLH = function (M) { //starting at top left, moving horizontally
return matrix.reverseCols(matrix.reverseRows(BRH(M)));
};
var TLV = function (M) { //starting at top left, moving vertically
return matrix.transpose(TLH(M));
};
if ((point === 'BR') && (dir === 'H')) {
return (BRH(mat));
} else if ((point === 'BR') && (dir === 'V')) {
return (BRV(mat));
} else if ((point === 'BL') && (dir === 'H')) {
return (BLH(mat));
} else if ((point === 'BL') && (dir === 'V')) {
return (BLV(mat));
} else if ((point === 'TR') && (dir === 'H')) {
return (TRH(mat));
} else if ((point === 'TR') && (dir === 'V')) {
return (TRV(mat));
} else if ((point === 'TL') && (dir === 'H')) {
return (TLH(mat));
} else if ((point === 'TL') && (dir === 'V')) {
return (TLV(mat));
} else {
throw new Error('Enter the direction (V,H) and corner (BR,BL,TR,TL) correctly.');
}
};
/**
* Calculate the p-norm of a vector. Specific cases include:
* - Infinity (largest absolute entry)
* - -Infinity (smallest absolute entry)
*
* @param {Array} vector
* @param {Number} the value of p (norm order)
* @return {Number} the p-norm of v
*/
matrix.vectorNorm = function (v, p) {
// calculate the p'th norm of a vector v
if (!(Array.isArray(v)) || (v.length === 0)) {
throw new Error('Vector must be an array of at least length 1.');
} else if ((typeof p !== 'undefined') && (typeof p !== 'number')) {
throw new Error('Norm order must be a number.');
}
p = (typeof p === 'undefined') ? 2 : p;
var n = v.length,
ans = 0,
term, i;
switch (p) {
case Infinity:
for (i = 0; i < n; i++) {
term = Math.abs(v[i]);
if (term > ans) {
ans = term;
}
}
break;
case -Infinity:
ans = Infinity;
for (i = 0; i < n; i++) {
term = Math.abs(v[i]);
if (term < ans) {
ans = term;
}
}
break;
default:
for (i = 0; i < n; i++) {
ans += Math.pow(Math.abs(v[i]), p);
}
ans = Math.pow(ans, 1 / p);
break;
}
return ans;
};
/**
* Calculate the p-norm of a matrix. Specific cases include:
* - Infinity (largest absolute row)
* - -Infinity (smallest absolute row)
* - 1 (largest absolute column)
* - -1 (smallest absolute column)
* - 2 (largest singular value)
* - -2 (smallest singular value)
* - null (Frobenius norm)
*
* @param {Array} vector
* @param {Number} the value of p (norm order)
* @return {Number} the p-norm of M
*/
matrix.matrixNorm = function (M, p) {
if (!(Array.isArray(M)) || (M.length === 0) || !Array.isArray(M[0])) {
throw new Error('Matrix must be an array of at least length 1.');
} else if ((typeof p !== 'undefined') && (typeof p !== 'number') && (p !== null)) {
throw new Error('Norm order must be a number or null.');
}
p = (typeof p === 'undefined') ? null : p;
var m = M.length, //number of rows
n = M[0].length, //number of cols
ans = 0,
term, i, j;
switch (p) {
// the largest value when absolute-ing and summing each row
case Infinity:
for (i = 0; i < m; i++) {
term = 0;
for (j = 0; j < n; j++) {
term += Math.abs(M[i][j]);
}
if (term > ans) {
ans = term;
}
}
break;
// the smallest value when absolute-ing and summing each row
case -Infinity:
ans = Infinity;
for (i = 0; i < m; i++) {
term = 0;
for (j = 0; j < n; j++) {
term += Math.abs(M[i][j]);
}
if (term < ans) {
ans = term;
}
}
break;
// the largest value when absolute-ing and summing each column
case 1:
for (i = 0; i < n; i++) {
term = 0;
for (j = 0; j < m; j++) {
term += Math.abs(M[j][i]);
}
if (term > ans) {
ans = term;
}
}
break;
// the smallest value when absolute-ing and summing each column
case -1:
ans = Infinity;
for (i = 0; i < n; i++) {
term = 0;
for (j = 0; j < m; j++) {
term += Math.abs(M[j][i]);
}
if (term < ans) {
ans = term;
}
}
break;
// the Frobenius norm
case null:
for (i = 0; i < m; i++) {
for (j = 0; j < n; j++) {
ans += Math.pow(M[i][j], 2);
}
}
ans = Math.pow(ans, 0.5);
break;
// largest singular value
case 2:
throw new Error("Singular values are not yet supported in numbers.js.");
// smallest singular value
case -2:
throw new Error("Singular values are not yet supported in numbers.js.");
// entry-wise norm; analogous to that of the entry-wise vector norm.
default:
for (i = 0; i < m; i++) {
for (j = 0; j < n; j++) {
ans += Math.pow(Math.abs(M[i][j]), p);
}
}
ans = Math.pow(ans, 1 / p);
}
return ans;
};
/**
* Determines if a matrix has an upper bandwidth of q.
*
* @param {Array} matrix
* @param {Number} upper bandwidth
* @return {Boolean} true if upper bandwidth is q; false otherwise
*/
matrix.isUpperBand = function (M, q) {
if (!Array.isArray(M) || !Array.isArray(M[0]) || M.length < 2) {
throw new Error('Matrix must be an array of at least dimension 2.');
} else if (typeof q !== 'number' || q < 0 || (q % 1) !== 0) {
throw new Error('Upper bandwidth must be a nonzero integer.');
}
var result = true,
n = M[0].length,
cnt = 0;
for (var i = q + 1; i < n; i++) {
if (M[cnt][i] !== 0) {
result = false;
break;
}
cnt++;
}
return result;
};
/**
* Determines if a matrix has an lower bandwidth of p.
*
* @param {Array} matrix
* @param {Number} lower bandwidth
* @return {Boolean} true if lower bandwidth is p; false otherwise
*/
matrix.isLowerBand = function (M, p) {
if (!Array.isArray(M) || !Array.isArray(M[0]) || M.length < 2) {
throw new Error('Matrix must be an array of at least dimension 2.');
} else if (typeof p !== 'number' || p < 0 || (p % 1) !== 0) {
throw new Error('Lower bandwidth must be a nonzero integer.');
}
var result = true,
m = M.length,
cnt = 0;
for (var i = p + 1; i < m; i++) {
if (M[i][cnt] !== 0) {
result = false;
break;
}
cnt++;
}
return result;
};
/**
* Add all of the elements in an array together except for the i'th one.
* This is a helper function for determining diagonal dominance, and it
* should be noted that each element is passed to Math.abs() beforehand.
*
* @param {Array} array
* @param {Int} index of element to ignore.
* @return {Number} sum.
*/
var sumNondiagonalElements = function (arr, i) {
var sum = 0,
j;
for (j = 0; j < i; j++) {
sum += Math.abs(arr[j]);
}
for (j = i + 1; j < arr.length; j++) {
sum += Math.abs(arr[j]);
}
return sum;
};
/**
* Determines if a matrix is (weak) row diagonally-dominant.
*
* @param {Array} matrix
* @return {Boolean} true if so, false otherwise.
*/
matrix.isRowDD = function (M) {
var n = M.length;
if (!matrix.isSquare(M)) {
throw new Error(ERROR_MATRIX_NOT_SQUARE);
}
for (var i = 0; i < n; i++) {
var row = M[i],
diag = row[i],
sum = sumNondiagonalElements(row, i);
if (Math.abs(diag) < sum) {
return false;
}
}
return true;
};
/**
* Determines if a matrix is strictly row diagonally-dominant.
*
* @param {Array} matrix
* @return {Boolean} true if so, false otherwise.
*/
matrix.isStrictlyRowDD = function (M) {
if (!matrix.isSquare(M)) {
throw new Error(ERROR_MATRIX_NOT_SQUARE);
}
var n = M.length;
for (var i = 0; i < n; i++) {
var row = M[i],
diag = row[i],
sum = sumNondiagonalElements(row, i);
if (Math.abs(diag) <= sum) {
return false;
}
}
return true;
};
/**
* Determines if a matrix is (weak) column diagonally-dominant.
*
* @param {Array} matrix
* @return {Boolean} true if so, false otherwise.
*/
matrix.isColumnDD = function (M) {
if (!matrix.isSquare) {
throw new Error(ERROR_MATRIX_NOT_SQUARE);
}
var n = M.length;
for (var i = 0; i < n; i++) {
var col = matrix.getCol(M, i),
diag = col[i],
sum = sumNondiagonalElements(col, i);
if (Math.abs(diag) < sum) {
return false;
}
}
return true;
};
/**
* Determines if a matrix is strictly column diagonally-dominant.
*
* @param {Array} matrix
* @return {Boolean} true if so, false otherwise.
*/
matrix.isStrictlyColumnDD = function (M) {
if (!matrix.isSquare(M)) {
throw new Error(ERROR_MATRIX_NOT_SQUARE);
}
var n = M.length;
for (var i = 0; i < n; i++) {
var col = matrix.getCol(M, i),
diag = col[i],
sum = sumNondiagonalElements(col, i);
if (Math.abs(diag) <= sum) {
return false;
}
}
return true;
};
},{}],9:[function(require,module,exports){
/**
* prime.js
* http://github.com/sjkaliski/numbers.js
*
* Copyright 2012 Stephen Kaliski
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var basic = require('./basic');
var prime = exports;
/**
* Determine if number is prime.
* Adopted from http://www.javascripter.net/faq/numberisprime.htm
*
* @param {Number} number to evaluate.
* @return {Boolean} return true if value is prime. false otherwise.
*/
prime.simple = function (n) {
if (isNaN(n) || !isFinite(n) || n % 1 || n < 2) {
return false;
}
if (n % 2 === 0) {
return (n === 2);
}
if (n % 3 === 0) {
return (n === 3);
}
for (var i = 5, m = Math.sqrt(n); i <= m; i += 6) {
if ((n % i === 0) || (n % (i + 2) === 0)) {
return false;
}
}
return true;
};
/**
* Returns the prime factors of a number.
* More info (http://bateru.com/news/2012/05/code-of-the-day-javascript-prime-factors-of-a-number/)
* Taken from Ratio.js
*
* @param {Number} num
* @return {Array} an array of numbers
* @example prime.factorization(20).join(',') === "2,2,5"
**/
prime.factorization = function (num) {
num = Math.floor(num);
var root;
var factors = [];
var x;
var sqrt = Math.sqrt;
var doLoop = 1 < num && isFinite(num);
while (doLoop) {
root = sqrt(num);
x = 2;
if (num % x) {
x = 3;
while ((num % x) && ((x += 2) < root)) {}
}
x = (root < x) ? num : x;
factors.push(x);
doLoop = (x !== num);
num /= x;
}
return factors;
};
/**
* Determine if a number is prime in Polynomial time, using a randomized algorithm.
* http://en.wikipedia.org/wiki/Miller-Rabin_primality_test
*
* @param {Number} number to Evaluate.
* @param {Number} number to Determine accuracy rate (number of trials) default value = 20.
* @return {Boolean} return true if value is prime. false otherwise.
*/
prime.millerRabin = function (n, k) {
if (arguments.length === 1) k = 20;
if (n === 2) return true;
if (!basic.isInt(n) || n <= 1 || n % 2 === 0) return false;
var s = 0;
var d = n - 1;
while (true) {
var dm = basic.divMod(d, 2);
var quotient = dm[0];
var remainder = dm[1];
if (remainder === 1) break;
s += 1;
d = quotient;
}
var tryComposite = function (a) {
if (basic.powerMod(a, d, n) === 1) return false;
for (var i = 0; i < s; i++) {
if (basic.powerMod(a, Math.pow(2, i) * d, n) === n - 1) return false;
}
return true;
};
for (var i = 0; i < k; i++) {
var a = 2 + Math.floor(Math.random() * (n - 2 - 2));
if (tryComposite(a)) return false;
}
return true;
};
/**
* Return a list of prime numbers from 1...n, inclusive.
*
* @param {Number} upper limit of test n.
* @return {Array} list of values that are prime up to n.
*/
prime.sieve = function (n) {
if (n < 2) return [];
var result = [2];
for (var i = 3; i <= n; i++) {
var notMultiple = false;
for (var j in result) {
notMultiple = notMultiple || (0 === i % result[j]);
}
if (!notMultiple) {
result.push(i);
}
}
return result;
};
/**
* Determine if two numbers are coprime.
*
* @param {Number} number.
* @param {Number} number.
* @return {Boolean} whether the values are coprime or not.
*/
prime.coprime = function (a, b) {
return basic.gcd(a, b) === 1;
};
/**
* Determine if a number is a perfect power.
* Please note that this method does not find the minimal value of k where
* m^k = n
* http://en.wikipedia.org/wiki/Perfect_power
*
* @param {Number} value in question
* @return {Array|Boolean} [m, k] if it is a perfect power, false otherwise
*/
prime.getPerfectPower = function (n) {
var test = prime.getPrimePower(n);
if (test && test[1] > 1) return test;
return false;
};
/**
* Determine if a number is a prime power and return the prime and the power.
* http://en.wikipedia.org/wiki/Prime_power
*
* @param {Number} value in question
* @return {Array|Boolean} if it is a prime power, return [prime, power].
*/
prime.getPrimePower = function (n) {
if (n < 2) return false;
if (prime.millerRabin(n)) return [n, 1];
if (n % 2 === 0) return [2, n.toString(2).length - 1];
var factors = prime.factorization(n);
if (!factors) return false;
var len = factors.length;
for (var i = 0; i < len; i++) {
var t = 0,
p = 0;
while (t <= n) {
t = Math.pow(factors[i], p);
if (t / n === 1) return [factors[i], p];
p++;
}
}
return false;
};
},{"./basic":3}],10:[function(require,module,exports){
var random = exports;
// random number generator.
var rGen = Math.random;
/**
* Set the pseudo random number generator used by the random module.
*
* @param {Function} Random number generator
*/
random.setGenerator = function (fn) {
if (typeof fn !== "function") {
throw new Error("Must pass a function");
}
rGen = fn;
};
/**
* Return a random sample of values over a set of bounds with
* a specified quantity.
*
* @param {Number} lower bound.
* @param {Number} upper bound.
* @param {Number} quantity of elements in random sample.
* @return {Array} random sample.
*/
random.sample = function (lower, upper, n) {
var sample = [];
sample.length = n;
for (var i = 0; i < n; i++) {
sample[i] = lower + (upper - lower) * rGen();
}
return sample;
};
/**
* A pseudo-random number sampling method for generating pairs of independent,
* standard, normally distributed (zero expectation, unit variance) random
* numbers, given a source of uniformly distributed random numbers.
* http://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform
*
* @param {Number} mu or mean
* @param {Number} sigma or standard deviation
* @return {Number} a value that is part of a normal distribution.
*/
random.boxMullerTransform = function (mu, sigma) {
if (arguments.length <= 1) sigma = 1;
if (arguments.length === 0) mu = 0;
var u = 0,
v = 0,
s;
do {
u = rGen() * 2 - 1;
v = rGen() * 2 - 1;
s = u * u + v * v;
} while (s === 0 || s > 1);
var c = Math.sqrt(-2 * Math.log(s) / s),
x = u * c,
y = v * c;
x = mu + x * sigma;
y = mu + y * sigma;
return [x, y];
};
/**
* A Random number that is along an irwin hall distribution.
* http://en.wikipedia.org/wiki/Irwin-Hall_distribution
*
* @param {Number} max possible sum
* @param {Number} number to subtract
* @return {Number} random number along an irwin hall distribution.
*/
random.irwinHall = function (n, sub) {
if (arguments.length === 1) sub = 0;
var sum = 0;
for (var i = 0; i < n; i++) sum += rGen();
return sum - sub;
};
/**
* Returns a random value along a bates distribution from [a, b] or [0, 1].
* http://en.wikipedia.org/wiki/Bates_distribution
*
* @param {Number} number of times summing
* @param {Number} random maximum value (default is 1)
* @param {Number} random minimum value (default is 0)
* @return {Number} random number along an bates distribution.
*/
random.bates = function (n, b, a) {
if (arguments.length <= 2) a = 0;
if (arguments.length === 1) b = 1;
var sum = 0;
for (var i = 0; i < n; i++) sum += (b - a) * rGen() + a;
return sum / n;
};
random.distribution = {};
/**
* Returns an array of size n that is an approximate normal distribution
*
* @param {Number} n size of returned array
* @param {Number} mu or mean
* @param {Number} sigma or standard deviation
* @return {Array} array of size n of a normal distribution
*/
random.distribution.normal = function (n, mu, sigma) {
if (arguments.length <= 2) sigma = 1;
if (arguments.length === 1) mu = 0;
return random.distribution.boxMuller(n, mu, sigma);
};
/**
* Returns an array of size n that is an approximate log normal distribution
*
* @param {Number} n size of returned array
* @param {Number} mu or mean
* @param {Number} sigma or standard deviation
* @return {Array} array of size n of a log normal distribution
*/
random.distribution.logNormal = function (n, mu, sigma) {
if (arguments.length <= 2) sigma = 1;
if (arguments.length === 1) mu = 0;
var exponential = function (x) {
return Math.exp(x);
};
return random.distribution.boxMuller(n, mu, sigma).map(exponential);
};
/**
* Returns an array of size n that is a normal distribution
* leveraging the Box Muller Transform
* http://en.wikipedia.org/wiki/Box%E2%80%93Muller_transform
*
* @param {Number} n size of returned array
* @param {Number} mu or mean
* @param {Number} sigma or standard deviation
* @param {Number} determine if the distribution will be polar coordinates.
* @return {Array} array of size n of a normal distribution
*/
random.distribution.boxMuller = function (n, mu, sigma, rc) {
if (arguments.length <= 3) rc = false;
if (arguments.length <= 2) sigma = 1;
if (arguments.length === 1) mu = 0;
var results = [];
for (var i = 0; i < n; i++) {
var randomBMT = random.boxMullerTransform(mu, sigma);
results.push((rc) ? randomBMT : randomBMT[0]);
}
return results;
};
/**
* Returns an array of n that is an irwin hall distribution.
* http://en.wikipedia.org/wiki/Irwin-Hall_distribution
*
* @param {Number} length of array
* @param {Number} irwinHall max sum value (default is n)
* @param {Number} irwinHall subtraction value (default is 0)
* @return {Array} irwin hall distribution from [a, b]
*/
random.distribution.irwinHall = function (n, m, sub) {
if (arguments.length <= 2) sub = 0;
if (arguments.length === 1) m = n;
var results = new Array(n);
for (var i = 0; i < n; i++) {
results[i] = random.irwinHall(m, sub);
}
return results;
};
/**
* An approach to create a normal distribution,
* that relies on the central limit theorem,
* resulting in an approximately standard normal distribution
* with bounds of (-6, 6)
*
* @param {Number} length of array
* @return {Array} an array of an approximate normal distribution from [-6, 6] of length n.
*/
random.distribution.irwinHallNormal = function (n) {
return random.distribution.irwinHall(n, 12, 6);
};
/**
* Returns an array of n that is a bates distribution from
* http://en.wikipedia.org/wiki/Bates_distribution
*
* @param {Number} length of array
* @param {Number} max bates value (default is n)
* @param {Number} minimum bound a (default is 0)
* @return {Array} bates distribution from [a, b]
*/
random.distribution.bates = function (n, b, a) {
if (arguments.length <= 2) a = 0;
if (arguments.length === 1) b = n;
var results = new Array(n);
for (var i = 0; i < n; i++) {
results[i] = random.bates(n, b, a);
}
return results;
};
},{}],11:[function(require,module,exports){
/**
* statistic.js
* http://github.com/sjkaliski/numbers.js
*
* Copyright 2012 Stephen Kaliski
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var basic = require('./basic');
var statistic = exports;
/**
* Calculate the mean value of a set of numbers in array.
*
* @param {Array} set of values.
* @return {Number} mean value.
*/
statistic.mean = function (arr) {
var count = arr.length;
var sum = basic.sum(arr);
return sum / count;
};
/**
* Calculate the median value of a set of numbers in array.
*
* @param {Array} set of values.
* @return {Number} median value.
*/
statistic.median = function (arr) {
return statistic.quantile(arr, 1, 2);
};
/**
* Calculate the mode value of a set of numbers in array.
*
* @param {Array} set of values.
* @return {Number} mode value.
*/
statistic.mode = function (arr) {
var counts = {};
for (var i = 0, n = arr.length; i < n; i++) {
if (counts[arr[i]] === undefined) {
counts[arr[i]] = 0;
} else {
counts[arr[i]]++;
}
}
var highest;
for (var number in counts) {
if (counts.hasOwnProperty(number)) {
if (highest === undefined || counts[number] > counts[highest]) {
highest = number;
}
}
}
return Number(highest);
};
/**
* Calculate the kth q-quantile of a set of numbers in an array.
* As per http://en.wikipedia.org/wiki/Quantile#Quantiles_of_a_population
* Ex: Median is 1st 2-quantile
* Ex: Upper quartile is 3rd 4-quantile
*
* @param {Array} set of values.
* @param {Number} index of quantile.
* @param {Number} number of quantiles.
* @return {Number} kth q-quantile of values.
*/
statistic.quantile = function (arr, k, q) {
var sorted, count, index;
if (k === 0) return Math.min.apply(null, arr);
if (k === q) return Math.max.apply(null, arr);
sorted = arr.slice(0);
sorted.sort(function (a, b) {
return a - b;
});
count = sorted.length;
index = count * k / q;
if (index % 1 === 0) return 0.5 * sorted[index - 1] + 0.5 * sorted[index];
return sorted[Math.floor(index)];
};
/**
* Return a set of summary statistics provided an array.
*
* @return {Object} summary statistics.
*/
statistic.report = function (array) {
return {
mean: statistic.mean(array),
firstQuartile: statistic.quantile(array, 1, 4),
median: statistic.median(array),
thirdQuartile: statistic.quantile(array, 3, 4),
standardDev: statistic.standardDev(array)
};
};
/**
* Evaluate the standard deviation for a set of values.
*
* @param {Array} set of values.
* @return {Number} standard deviation.
*/
statistic.standardDev = function (arr) {
var count = arr.length;
var mean = statistic.mean(arr);
var squaredArr = [];
for (var i = 0; i < arr.length; i++) {
squaredArr[i] = Math.pow((arr[i] - mean), 2);
}
return Math.sqrt((1 / count) * basic.sum(squaredArr));
};
/**
* Evaluate the correlation amongst a set of values.
*
* @param {Array} set of values.
* @return {Number} correlation.
*/
statistic.correlation = function (arrX, arrY) {
if (arrX.length === arrY.length) {
var covarXY = statistic.covariance(arrX, arrY);
var stdDevX = statistic.standardDev(arrX);
var stdDevY = statistic.standardDev(arrY);
return covarXY / (stdDevX * stdDevY);
} else {
throw new Error('Array mismatch');
}
};
/**
* Calculate the Coefficient of Determination of a dataset and regression line.
*
* @param {Array} Source data.
* @param {Array} Regression data.
* @return {Number} A number between 0 and 1.0 that represents how well the regression line fits the data.
*/
statistic.rSquared = function (source, regression) {
var residualSumOfSquares = basic.sum(source.map(function (d, i) {
return basic.square(d - regression[i]);
}));
var totalSumOfSquares = basic.sum(source.map(function (d) {
return basic.square(d - statistic.mean(source));
}));
return 1 - (residualSumOfSquares / totalSumOfSquares);
};
/**
* Create a function to calculate the exponential regression of a dataset.
*
* @param {Array} set of values.
* @return {Function} function to accept X values and return corresponding regression Y values.
*/
statistic.exponentialRegression = function (arrY) {
var n = arrY.length;
var arrX = basic.range(1, n);
var xSum = basic.sum(arrX);
var yLog = arrY.map(function (d) {
return Math.log(d);
});
var xSquared = arrX.map(function (d) {
return d * d;
});
var xSquaredSum = basic.sum(xSquared);
var yLogSum = basic.sum(yLog);
var xyLog = arrX.map(function (d, i) {
return d * yLog[i];
});
var xyLogSum = basic.sum(xyLog);
var a = (yLogSum * xSquaredSum - xSum * xyLogSum) / (n * xSquaredSum - (xSum * xSum));
var b = (n * xyLogSum - xSum * yLogSum) / (n * xSquaredSum - (xSum * xSum));
var fn = function (x) {
if (typeof x === 'number') {
return Math.exp(a) * Math.exp(b * x);
} else {
return x.map(function (d) {
return Math.exp(a) * Math.exp(b * d);
});
}
};
fn.rSquared = statistic.rSquared(arrY, arrX.map(fn));
return fn;
};
/**
* Create a function to calculate the linear regression of a dataset.
*
* @param {Array} X array.
* @param {Array} Y array.
* @return {Function} A function which given X or array of X values will return Y.
*/
statistic.linearRegression = function (arrX, arrY) {
var n = arrX.length;
var xSum = basic.sum(arrX);
var ySum = basic.sum(arrY);
var xySum = basic.sum(arrX.map(function (d, i) {
return d * arrY[i];
}));
var xSquaredSum = basic.sum(arrX.map(function (d) {
return d * d;
}));
var xMean = statistic.mean(arrX);
var yMean = statistic.mean(arrY);
var b = (xySum - 1 / n * xSum * ySum) / (xSquaredSum - 1 / n * (xSum * xSum));
var a = yMean - b * xMean;
return function (x) {
if (typeof x === 'number') {
return a + b * x;
} else {
return x.map(function (d) {
return a + b * d;
});
}
};
};
/**
* Evaluate the covariance amongst 2 sets.
*
* @param {Array} set 1 of values.
* @param {Array} set 2 of values.
* @return {Number} covariance.
*/
statistic.covariance = function (set1, set2) {
if (set1.length === set2.length) {
var n = set1.length;
var total = 0;
var sum1 = basic.sum(set1);
var sum2 = basic.sum(set2);
for (var i = 0; i < n; i++) {
total += set1[i] * set2[i];
}
return (total - sum1 * sum2 / n) / n;
} else {
throw new Error('Array mismatch');
}
};
},{"./basic":3}]},{},[1])(1)
});
\ No newline at end of file
numbers.matrix.toString = function(a){
var res = ""
for(var i=0;i<a.length;i++){
res += a[i].join(" ")+"\n"
}
return res
}
...@@ -43,8 +43,8 @@ function align_init(){ ...@@ -43,8 +43,8 @@ function align_init(){
//align_heading(); //align_heading();
//test_markers_set1(); //test_markers_set1();
//if (DEBUG_ALIGN) test_markers_set1(); //if (DEBUG_ALIGN) test_markers_set1();
//if (DEBUG_ALIGN) test_markers_set2(); if (DEBUG_ALIGN) test_markers_set2();
if (DEBUG_ALIGN) test_markers_set3(); //if (DEBUG_ALIGN) test_markers_set3();
x3dom_align_GN(); x3dom_align_GN();
}); });
...@@ -123,61 +123,15 @@ function x3dom_align_GN(){ ...@@ -123,61 +123,15 @@ function x3dom_align_GN(){
var x0 = Data.camera.kml.latitude; var x0 = Data.camera.kml.latitude;
var y0 = Data.camera.kml.longitude; var y0 = Data.camera.kml.longitude;
var h0 = Data.camera.kml.heading; var h0 = Data.camera.kml.heading;
var epsilon = 1e-8;
//if (h0>180) h0 = h0 - 360;
//tests
//test_AxB();
//test_At();
//test_AxV();
//test_Ainv();
var ε = 0.000000001;
var iterate = true;
var counter = 0;
var result = 0;
var xyh = [x0,y0,(h0>180)?h0-360:h0]; var xyh = [x0,y0,(h0>180)?h0-360:h0];
while(iterate){ var result = numbers.calculus.GaussNewton(xyh,Data.markers.length,r_i,[dr_dx_i,dr_dy_i,dr_dh_i],epsilon);
if (DEBUG_ALIGN){
// functions values
for(var i=0;i<Data.markers.length;i++){
console.log(f1_3d_i(i,xyh[0],xyh[1],xyh[2])+" - "+f2_map_i(i,xyh[0],xyh[1],xyh[2])+" = "+r_i(i,xyh[0],xyh[1],xyh[2]));
}
}
counter++;
//console.log("Interation: "+counter+" for "+xyh[0]+" "+xyh[1]+" "+xyh[2]);
xyh_new = GaussNewtonAlgorithm(xyh[0],xyh[1],xyh[2]);
//if (xyh_new[2]<-180) xyh_new[2] += 360;
//if (xyh_new[2]> 180) xyh_new[2] -= 360;
var s0 = sigma(xyh[0],xyh[1],xyh[2]); xyh = result.v;
var s1 = sigma(xyh_new[0],xyh_new[1],xyh_new[2]); var s1 = result.error;
var counter = result.count;
//if ((s1>s0)||((s0-s1)<ε)){
if (Math.abs(s0-s1)<ε){
iterate = false;
}
if(DEBUG_ALIGN){
console.log("Errors: "+(xyh_new[0]-xyh[0])+" "+(xyh_new[1]-xyh[1])+" "+(xyh_new[2]-xyh[2]));
console.log("Iteration "+counter+" result: "+xyh_new[0]+" "+xyh_new[1]+" "+xyh_new[2]);
console.log("Error function value: "+sigma(xyh_new[0],xyh_new[1],xyh_new[2]));
}
if (counter==1000){
iterate = false;
}
xyh[0] = xyh_new[0];
xyh[1] = xyh_new[1];
xyh[2] = xyh_new[2];
}
//calc distance error //calc distance error
de = distance_error(x0,y0,(h0>180)?h0-360:h0); de = distance_error(x0,y0,(h0>180)?h0-360:h0);
...@@ -188,51 +142,14 @@ function x3dom_align_GN(){ ...@@ -188,51 +142,14 @@ function x3dom_align_GN(){
} }
/*
* calc the next iteration values
*/
function GaussNewtonAlgorithm(x,y,h){
var J = Jacobian(x,y,h);
var Jt = At(J);
var JtJ = AxB(Jt,J);
var JtJi = Ainv(JtJ);
var JtJixJt = AxB(JtJi,Jt);
var Vr = [];
for(var i=0;i<Data.markers.length;i++){
Vr[i] = r_i(i,x,y,h);
}
var d = AxV(JtJixJt,Vr);
var k = 1;
return [x-k*d[0], y-k*d[1], h-k*d[2]];
}
/*
* sum of squared residuals - criterion for stopping
*/
function sigma(x,y,h){
var sum = 0
for(var i=0;i<Data.markers.length;i++){
sum += r_i(i,x,y,h)*r_i(i,x,y,h);
}
sum = Math.sqrt(sum/Data.markers.length);
return sum;
}
/* /*
* heading in degrees from 3D model * heading in degrees from 3D model
*/ */
function f1_3d_i(i,x,y,h){ function f1_3d_i(i,v){
var base = Data.camera; var base = Data.camera;
var mark = Data.markers[i]; var mark = Data.markers[i];
var v = new x3dom.fields.SFVec3f(mark.align.x-base.x,0,mark.align.z-base.z); var vec = new x3dom.fields.SFVec3f(mark.align.x-base.x,0,mark.align.z-base.z);
var res = Math.atan2(v.x,-v.z)*180/Math.PI + h; var res = Math.atan2(vec.x,-vec.z)*180/Math.PI + v[2];
if (res> 180) res = res - 360; if (res> 180) res = res - 360;
if (res<-180) res = res + 360; if (res<-180) res = res + 360;
...@@ -243,10 +160,10 @@ function f1_3d_i(i,x,y,h){ ...@@ -243,10 +160,10 @@ function f1_3d_i(i,x,y,h){
/* /*
* heading in degrees from map * heading in degrees from map
*/ */
function f2_map_i(i,x,y,h){ function f2_map_i(i,v){
var mark = Data.markers[i]; var mark = Data.markers[i];
var p1_ll = new L.LatLng(x,y); var p1_ll = new L.LatLng(v[0],v[1]);
var p2_ll = new L.LatLng(mark.align.latitude,mark.align.longitude); var p2_ll = new L.LatLng(mark.align.latitude,mark.align.longitude);
//console.log(p1_ll); //console.log(p1_ll);
...@@ -275,9 +192,9 @@ function f2_map_i(i,x,y,h){ ...@@ -275,9 +192,9 @@ function f2_map_i(i,x,y,h){
/* /*
* residuals function * residuals function
*/ */
function r_i(i,x,y,h){ function r_i(i,v){
var f1 = f1_3d_i(i,x,y,h); var f1 = f1_3d_i(i,v);
var f2 = f2_map_i(i,x,y,h); var f2 = f2_map_i(i,v);
//return (f1-f2+360)%360; //return (f1-f2+360)%360;
return (f1-f2); return (f1-f2);
} }
...@@ -285,10 +202,10 @@ function r_i(i,x,y,h){ ...@@ -285,10 +202,10 @@ function r_i(i,x,y,h){
/* /*
* dr/dx(i) * dr/dx(i)
*/ */
function dr_dx_i(i,x,y,h){ function dr_dx_i(i,v){
var mark = Data.markers[i]; var mark = Data.markers[i];
var p1_ll = new L.LatLng(x,y); var p1_ll = new L.LatLng(v[0],v[1]);
var p2_ll = new L.LatLng(mark.align.latitude,mark.align.longitude); var p2_ll = new L.LatLng(mark.align.latitude,mark.align.longitude);
p1_ll.lat = p1_ll.lat*Math.PI/180; p1_ll.lat = p1_ll.lat*Math.PI/180;
...@@ -316,10 +233,10 @@ function dr_dx_i(i,x,y,h){ ...@@ -316,10 +233,10 @@ function dr_dx_i(i,x,y,h){
/* /*
* dr/dy(i) * dr/dy(i)
*/ */
function dr_dy_i(i,x,y,h){ function dr_dy_i(i,v){
var mark = Data.markers[i]; var mark = Data.markers[i];
var p1_ll = new L.LatLng(x,y); var p1_ll = new L.LatLng(v[0],v[1]);
var p2_ll = new L.LatLng(mark.align.latitude,mark.align.longitude); var p2_ll = new L.LatLng(mark.align.latitude,mark.align.longitude);
p1_ll.lat = p1_ll.lat*Math.PI/180; p1_ll.lat = p1_ll.lat*Math.PI/180;
...@@ -347,175 +264,10 @@ function dr_dy_i(i,x,y,h){ ...@@ -347,175 +264,10 @@ function dr_dy_i(i,x,y,h){
/* /*
* dr/dh(i) * dr/dh(i)
*/ */
function dr_dh_i(i,x,y,h){ function dr_dh_i(i,v){
return 1; return 1;
} }
/*
* Jacobi matrix
*/
function Jacobian(x,y,h){
var J = [];
var base = Data.camera;
for(var i=0;i<Data.markers.length;i++){
var mark = Data.markers[i];
J[i]=[ dr_dx_i(i,x,y,h), dr_dy_i(i,x,y,h), dr_dh_i(i,x,y,h)];
/*
e0 = 0.000000001;
e1 = 0.000000001;
e2 = 0.00001;
dri_dx_cal = dr_dx_i(i,x,y,h);
dri_dy_cal = dr_dy_i(i,x,y,h);
dri_dh_cal = dr_dh_i(i,x,y,h);
dri_dx_num = (r_i(i,x+e0,y ,h )-r_i(i,x-e0,y ,h ))/e0/2;
dri_dy_num = (r_i(i,x ,y+e1,h )-r_i(i,x ,y-e1,h ))/e1/2;
dri_dh_num = (r_i(i,x ,y ,h+e2)-r_i(i,x ,y ,h-e2))/e2/2;
console.log("CALC: "+dri_dx_cal.toFixed(10)+" "+dri_dy_cal.toFixed(10)+" "+dri_dh_cal.toFixed(4));
console.log("NUME: "+dri_dx_num.toFixed(10)+" "+dri_dy_num.toFixed(10)+" "+dri_dh_num.toFixed(4));
*/
}
return J;
}
/*
* Utility functions
*/
/*
* AxB, any dimensions
*/
function AxB(A,B){
var m1 = A.length;
var n1 = A[0].length;
var m2 = B.length;
var n2 = B[0].length;
if(n1!=m2){
console.log("M=AxB: cannot multiply matrices A_"+m1+"_"+n1+" x B_"+m2+"_"+n2);
return [];
}
var R = [];
for(var i=0;i<m1;i++){
R[i] = [];
for(var j=0;j<n2;j++){
R[i][j] = 0;
for(var k=0;k<n1;k++){
R[i][j] += A[i][k]*B[k][j];
}
}
}
return R;
}
/*
* Determinant for 3x3 only
*/
function Adet(A){
var m = A.length;
var n = A[0].length;
if ((m!=3)||(n!=3)){
console.log("Matrix inverting works only for 3x3 dimension");
}
var M = new x3dom.fields.SFMatrix4f(
A[0][0],A[0][1],A[0][2],0,
A[1][0],A[1][1],A[1][2],0,
A[2][0],A[2][1],A[2][2],0,
0,0,0,1
);
return M.det();
}
/*
* Inverted matrix for 3x3 only
*/
function Ainv(A){
var m = A.length;
var n = A[0].length;
if ((m!=3)||(n!=3)){
console.log("Matrix inverting works only for 3x3 dimension");
}
var M = new x3dom.fields.SFMatrix4f(
A[0][0],A[0][1],A[0][2],0,
A[1][0],A[1][1],A[1][2],0,
A[2][0],A[2][1],A[2][2],0,
0,0,0,1
);
var R = M.inverse();
return [
[R._00,R._01,R._02],
[R._10,R._11,R._12],
[R._20,R._21,R._22]
];
}
/*
* Transposed matrix, any dimensions
*/
function At(A){
var R = [];
for(var i=0;i<A[0].length;i++){
R[i] = [];
for(var j=0;j<A.length;j++){
R[i][j] = A[j][i];
}
}
return R;
}
/*
* Matrix x Vector - any dimensions
*/
function AxV(A,V){
var Vr = [];
var m1 = A.length;
var n1 = A[0].length;
var m2 = V.length;
var n2 = 1;
if (n1!=m2){
console.log("Matrix or vector dimension errors, too bad");
return [];
}
for(var i=0;i<m1;i++){
Vr[i] = 0;
for(var j=0;j<m2;j++){
Vr[i] += A[i][j]*V[j];
}
}
return Vr;
}
/* /*
* ui dialog to apply or cancel results * ui dialog to apply or cancel results
...@@ -614,7 +366,7 @@ function distance_error(x,y,h){ ...@@ -614,7 +366,7 @@ function distance_error(x,y,h){
for(var i=0;i<Data.markers.length;i++){ for(var i=0;i<Data.markers.length;i++){
var angle0 = h; var angle0 = h;
var angle1 = f2_map_i(i,x,y,h); var angle1 = f2_map_i(i,[x,y,h]);
var z_map = Math.cos(Math.PI/180*(angle0-angle1))*Data.markers[i].d_map; var z_map = Math.cos(Math.PI/180*(angle0-angle1))*Data.markers[i].d_map;
var z_x3d = -Data.markers[i].align.z; var z_x3d = -Data.markers[i].align.z;
sum += 1/z_map-1/z_x3d; sum += 1/z_map-1/z_x3d;
...@@ -647,92 +399,6 @@ function align_tilt(){ ...@@ -647,92 +399,6 @@ function align_tilt(){
} }
function test_Ainv(){
var A = [
[0,0,1],
[1,0,0],
[0,1,0]
];
console.log(A);
console.log(Ainv(A));
}
function test_AxV(){
var A = [
[1,2,3,4],
[5,6,7,8],
[9,10,11,12]
];
var V1 = [
13,
14,
15,
16
];
var V2 = [
13,
14,
15
];
console.log(AxV(A,V1));
console.log(AxV(A,V2));
}
function test_At(){
console.log("testing At: begin");
var A = [
[1,2,3,4],
[5,6,7,8],
[9,10,11,12]
];
console.log(A);
console.log(At(A));
console.log("testing At: end");
}
function test_AxB(){
console.log("testing AxB: begin");
var A = [
[1,2,3,4],
[5,6,7,8],
[9,10,11,12]
];
var B = [
[13,14,15],
[16,17,18],
[19,20,21],
[22,23,24],
];
var C = [
[2,2],
[1,1]
];
//test1: 3x4 x 4x3 = 3x3
console.log(AxB(A,B));
//test2: fail test case
console.log(AxB(A,C));
console.log("testing AxB: end");
}
function test_markers_set1(){ function test_markers_set1(){
Data.camera.kml.latitude = 40.7233861; Data.camera.kml.latitude = 40.7233861;
...@@ -774,4 +440,4 @@ function test_markers_set3(){ ...@@ -774,4 +440,4 @@ function test_markers_set3(){
{d_map:0, d_x3d:0, align:{ latitude: 40.79441790113347, longitude: -111.90620452165605, x:34.451361546214116, y: 30.539873602241727, z: -133.39148171966684 }} {d_map:0, d_x3d:0, align:{ latitude: 40.79441790113347, longitude: -111.90620452165605, x:34.451361546214116, y: 30.539873602241727, z: -133.39148171966684 }}
]; ];
} }
\ No newline at end of file
...@@ -25,6 +25,10 @@ ...@@ -25,6 +25,10 @@
<script type='text/javascript' src='js/leaflet/leaflet.camera-view-marker.js'></script> <script type='text/javascript' src='js/leaflet/leaflet.camera-view-marker.js'></script>
<script type='text/javascript' src='js/leaflet/leaflet.camera-view-marker.measure.js'></script> <script type='text/javascript' src='js/leaflet/leaflet.camera-view-marker.measure.js'></script>
<script type='text/javascript' src='js/numbers/numbers.js'></script>
<script type='text/javascript' src='js/numbers/numbers.matrix.extra.js'></script>
<script type='text/javascript' src='js/numbers/numbers.calculus.extra.js'></script>
<!---script type='text/javascript' src='js/x3dom/x3dom-full.debug.js'></script--> <!---script type='text/javascript' src='js/x3dom/x3dom-full.debug.js'></script-->
<script type='text/javascript' src='js/x3dom_init.js'></script> <script type='text/javascript' src='js/x3dom_init.js'></script>
<script type='text/javascript' src='js/x3dom_functions.js'></script> <script type='text/javascript' src='js/x3dom_functions.js'></script>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment