var _ = require('lodash').runInContext();
////////// lomath // //////////
Prepare lodash for extension and export
var _ = require('lodash').runInContext();
the module: lodash extended with math mixins
var lomath = _.mixin({
AUTHOR: "kengz",
VERSION: "0.2.9",
//////////////////////////// Function builder backend // ////////////////////////////
We employ clearer terminologies to distinguish the “depth” or the “dimension” of the objects. In general, we call generic array of depth-N a “N-tensor” or “rank-N tensor”. A scalar is “0-tensor”; a simple array/vector is “1-tensor”, matrix (array of arrays) is “2-tensor”, and so on. A generic function that operates over tensor is built from an atomic function fn taking two scalar arguments. Applying a function into depths of tensor is done via distribution, and evaluating a multi-argument function is done via associativity. sample operation to demonstrate function composition
op: function(x, y) {
return x + '*' + y;
},
distribute a unary function over every scalar in tensor Y;
distributeSingle: function(fn, Y) {
if (!(Y instanceof Array)) return fn(Y);
var len = Y.length,
res = Array(len);
while (len--) res[len] = Y[len] instanceof Array ?
lomath.distributeSingle(fn, Y[len]) : fn(Y[len])
return res;
},
Distribute fn with left tensor X over right scalar y.
distributeLeft: function(fn, X, y) {
var len = X.length,
res = Array(len);
while (len--) res[len] = X[len] instanceof Array ?
lomath.distributeLeft(fn, X[len], y) : fn(X[len], y)
return res;
},
Distribute fn with left scalar x over right tensor Y.
distributeRight: function(fn, x, Y) {
var len = Y.length,
res = Array(len);
while (len--) res[len] = Y[len] instanceof Array ?
lomath.distributeRight(fn, x, Y[len]) : fn(x, Y[len])
return res;
},
Distribute fn between non-scalar tensors X, Y: pair them up term-wise and calling distribute recursively. If at any depth X and Y have different lengths, recycle if the mod of lengths is 0.
distributeBoth: function(fn, X, Y) {
var Xlen = X.length,
Ylen = Y.length;
if (Xlen % Ylen == 0 || Ylen % Xlen == 0) {
var res;
if (Xlen > Ylen) {
res = Array(Xlen);
while (Xlen--) res[Xlen] = lomath.distribute(fn, X[Xlen], Y[Xlen % Ylen]);
} else {
res = Array(Ylen);
while (Ylen--) res[Ylen] = lomath.distribute(fn, X[Ylen % Xlen], Y[Ylen]);
}
return res;
} else throw "Cannot distribute arrays of different dimensions.";
},
Generic Distribute: Distribute fn between left tensor X and right tensor Y, while preserving the argument-ordering (vital for non-commutative functions). lomath pairs up the tensors term-wise while descending down the depths recursively, until finding a scalar to distributeLeft/Right. Method is at its fastest, and assuming the data depth isn’t too deep (otherwise JS will have troubles with it)
distribute: function(fn, X, Y) {
if (X instanceof Array)
return Y instanceof Array ?
lomath.distributeBoth(fn, X, Y) : lomath.distributeLeft(fn, X, Y);
else
return Y instanceof Array ?
lomath.distributeRight(fn, X, Y) : fn(X, Y);
},
Generic associate: take the arguments object or array and apply atomic fn (non-tensor) from left to right
asso: function(fn, argObj) {
var len = argObj.length,
i = 0;
optimize arg form based on length or argObj
var args = len < 3 ? argObj : _.toArray(argObj),
res = fn(args[i++], args[i++]);
while (i < len) res = fn(res, args[i++]);
return res;
},
Associate with distributivity: Similar to asso but is for tensor functions; apply atomic fn distributively from left to right. Usage: for applying fn on tensors element-wise if they have matching dimensions.
assodist: function(fn, argObj) {
var len = argObj.length,
i = 0;
optimize arg form based on length or argObj
var args = len < 3 ? argObj : _.toArray(argObj),
res = lomath.distribute(fn, args[i++], args[i++]);
while (i < len) res = lomath.distribute(fn, res, args[i++]);
return res;
},
Future: Future: Future: cross and wedge, need index summation too, matrix mult.
/////////////////// Basic functions // /////////////////// Concat all arguments into single vector by _.flattenDeep
c: function() {
return _.flattenDeep(_.toArray(arguments));
},
atomic sum: takes in a tensor (any rank) and sum all values
a_sum: function(T) {
actual function call; recurse if need to
var total = 0,
len = T.length;
while (len--) total += (T[len] instanceof Array ?
lomath.a_sum(T[len], 0) : T[len])
return total;
},
sum all values in all arguments
sum: function() {
var res = 0;
var len = arguments.length;
while (len--) res += (arguments[len] instanceof Array ?
lomath.a_sum(arguments[len]) : arguments[len])
return res;
},
fsum: function(T, fn) {
var sum = 0;
for (var i = 0; i < T.length; i++)
sum += fn(T, i);
return sum;
},
atomic prod, analogue to a_sum. Multiply all values in a tensor
a_prod: function(T) {
actual function call; recurse if need to
var total = 1,
len = T.length;
while (len--) total *= (T[len] instanceof Array ?
lomath.a_prod(T[len], 1) : T[len])
return total;
},
product of all values in all arguments
prod: function() {
var res = 1,
len = arguments.length;
while (len--) res *= (arguments[len] instanceof Array ?
lomath.a_prod(arguments[len]) : arguments[len])
return res;
},
atomic add: add two scalars x, y.
a_add: function(x, y) {
return x + y;
},
add all tensor arguments element-wise/distributively and associatively
add: function() {
sample call pattern: pass whole args
return lomath.assodist(lomath.a_add, arguments);
},
atomic subtract
a_subtract: function(x, y) {
return x - y;
},
subtract all tensor arguments element-wise/distributively and associatively
subtract: function() {
return lomath.assodist(lomath.a_subtract, arguments);
},
atomic multiply
a_multiply: function(x, y) {
return x * y;
},
multiply all tensor arguments element-wise/distributively and associatively Note: lomath is generic; is different from matrix multiplication
multiply: function() {
return lomath.assodist(lomath.a_multiply, arguments);
},
atomic divide
a_divide: function(x, y) {
return x / y;
},
divide all tensor arguments element-wise/distributively and associatively
divide: function() {
return lomath.assodist(lomath.a_divide, arguments);
},
atomic log. Use base e by default
a_log: function(x, base) {
return base == undefined ? Math.log(x) : Math.log(x) / Math.log(base);
},
take the log of tensor T to the n element-wise
log: function(T, base) {
return lomath.distribute(lomath.a_log, T, base);
},
atomic square
a_square: function(x) {
return x * x;
},
square: function(T) {
return lomath.distributeSingle(lomath.a_square, T);
},
atomic root
a_root: function(x, base) {
var n = base == undefined ? 2 : base;
return n % 2 ?
if odd power
Math.sign(x) * Math.pow(Math.abs(x), 1 / n) :
Math.pow(x, 1 / n);
},
take the n-th root of tensor T element-wise
root: function(T, n) {
return lomath.distribute(lomath.a_root, T, n);
},
atomic logistic
a_logistic: function(z) {
return 1 / (1 + Math.exp(-z))
},
logistic: function(T) {
return lomath.distributeSingle(lomath.a_logistic, T);
},
////////////////// Basic checkers // ////////////////// check if x is in range set by left, right
inRange: function(left, right, x) {
return left - 1 < x && x < right + 1;
},
check if x is an integer
isInteger: function(x) {
return x == Math.floor(x);
},
check if x is a double
isDouble: function(x) {
return x != Math.floor(x);
},
check if x is positive
isPositive: function(x) {
return x > 0;
},
check if x less than or eq to 0
nonPositive: function(x) {
return !(x > 0);
},
check if x is negative
isNegative: function(x) {
return x < 0;
},
check if x greater than or eq to 0
nonNegative: function(x) {
return !(x < 0);
},
isZero: function(x) {
return x == 0;
},
nonZero: function(x) {
return x != 0;
},
parity: function(x) {
return x % 2 ? -1 : 1;
},
check if all tensor entries are of the same sign, with the specified sign function
sameSig: function(T, sigFn) {
return Boolean(lomath.prod(lomath.distributeSingle(sigFn, T)));
},
check if all tensor entries are of the same sign, with the specified sign function
deepEqual: function(T, S) {
if (T.length != S.length) return false;
var Left = T,
Right = S;
if (!lomath.isFlat(T)) {
Left = _.flattenDeep(T);
Right = _.flattenDeep(S);
};
var Llen = Left.length,
Rlen = Right.length;
while (Llen--) {
if (Left[Llen] != Right[Llen]) return false;
}
return true;
},
//////////////////////////////////////// Unary functions from JS Math object, // //////////////////////////////////////// wrapped to function with generic tensor
abs: function(T) {
return lomath.distributeSingle(Math.abs, T);
},
acos: function(T) {
return lomath.distributeSingle(Math.acos, T);
},
acosh: function(T) {
return lomath.distributeSingle(Math.acosh, T);
},
asin: function(T) {
return lomath.distributeSingle(Math.asin, T);
},
asinh: function(T) {
return lomath.distributeSingle(Math.asinh, T);
},
atan: function(T) {
return lomath.distributeSingle(Math.atan, T);
},
atanh: function(T) {
return lomath.distributeSingle(Math.atanh, T);
},
ceil: function(T) {
return lomath.distributeSingle(Math.ceil, T);
},
cos: function(T) {
return lomath.distributeSingle(Math.cos, T);
},
cosh: function(T) {
return lomath.distributeSingle(Math.cosh, T);
},
exp: function(T) {
return lomath.distributeSingle(Math.exp, T);
},
floor: function(T) {
return lomath.distributeSingle(Math.floor, T);
},
log10: function(T) {
return lomath.distributeSingle(Math.log10, T);
},
log1p: function(T) {
return lomath.distributeSingle(Math.log1p, T);
},
log2: function(T) {
return lomath.distributeSingle(Math.log2, T);
},
round: function(T) {
return lomath.distributeSingle(Math.round, T);
},
pow: function(T, n) {
return lomath.distribute(Math.pow, T, n);
},
sign: function(T) {
return lomath.distributeSingle(Math.sign, T);
},
sin: function(T) {
return lomath.distributeSingle(Math.sin, T);
},
sinh: function(T) {
return lomath.distributeSingle(Math.sinh, T);
},
sqrt: function(T) {
return lomath.distributeSingle(Math.sqrt, T);
},
tan: function(T) {
return lomath.distributeSingle(Math.tan, T);
},
tanh: function(T) {
return lomath.distributeSingle(Math.tanh, T);
},
trunc: function(T) {
return lomath.distributeSingle(Math.trunc, T);
},
/////////////////// Regex functions // /////////////////// return a function that matches regex, e.g. matchRegex(/red/)(‘red Apple’) returns true
reMatch: function(regex) {
return function(str) {
if (str != undefined)
return str.search(regex) != -1;
}
},
negation of reMatch
reNotMatch: function(regex) {
return function(str) {
if (str != undefined)
return str.search(regex) == -1;
}
},
return the string matched by regex
reGet: function(regex) {
return function(str) {
if (str != undefined) {
var matched = str.match(regex);
return matched == null ? null : matched[0];
}
}
},
wrap a regex into string for regex set operation
reWrap: function(reg) {
return '(?:' + String(reg).replace(/\//g, '') + ')'
},
return a single regex as the “AND” of all arg regex’s
reAnd: function() {
return new RegExp(_.map(_.toArray(arguments), lomath.reWrap).join(''));
},
return a function that matches all(AND) of the regexs
reAndMatch: function() {
return lomath.reMatch(lomath.reAnd.apply(null, arguments));
},
return a single regex as the “OR” of all arg regex’s
reOr: function() {
return new RegExp(_.map(_.toArray(arguments), lomath.reWrap).join('|'));
},
return a function that matches at least one(OR) of the regexs
reOrMatch: function() {
return lomath.reMatch(lomath.reOr.apply(null, arguments));
},
reString: function(regexp) {
return regexp.toString().replace(/^\/|\/.*$/g, '')
},
////////////////// Array creation // //////////////////
union, intersection, difference, xor seq from R: like _.range, but starts with 1 by default
seq: function(start, stop, step) {
if (stop == null) {
stop = start || 1;
start = 1;
}
step = step || 1;
var length = Math.max(Math.ceil((stop - start) / step), 0) + 1;
var range = Array(length);
for (var idx = 0; idx < length; idx++, start += step) {
range[idx] = start;
}
return range;
},
return an array of length N initialized to val (default to 0)
numeric: function(N, val) {
return val == undefined ? _.fill(Array(N), 0) : _.fill(Array(N), val);
},
///////////////////// Tensor properties // /////////////////////
Note that a tensor has homogenous depth, that is, there cannot tensors of different ranks in the same vector, e.g. [1, [2,3], 4] is prohibited. return the depth (rank) of tensor, assuming homogeneity
depth: function(T) {
var t = T,
d = 0;
while (t.length) {
d++;
t = t[0];
}
return d;
},
return the size of a tensor (total number of scalar entries) return 0 for scalar
volume: function(T) {
return _.flattenDeep(T).length;
},
[[1,1,1,1],[2,2,2,2],[3,3,3,3]],
[[4,4,4,4],[5,5,5,5],[6,6,6,6]]
])
Get the dimension of a (non-scalar) tensor by _.flattenDeep, assume rectangular
dim: function(T) {
var dim = [],
ptr = T;
while (ptr.length) {
dim.push(ptr.length);
ptr = ptr[0];
}
return dim;
},
check if a tensor is rank-1
isFlat: function(T) {
var flat = true,
len = T.length;
while (len--) {
flat *= !(T[len] instanceof Array);
if (!flat) break;
}
return Boolean(flat);
},
get the maximum length of the deepest array in (non-scalar) tensor T.
maxDeepestLength: function(T) {
if (!(T instanceof Array)) return 0;
var stack = [],
sizes = [];
stack.push(T);
while (stack.length) {
var curr = stack.pop(),
len = curr.length;
if (lomath.isFlat(curr))
sizes.push(len);
else
while (len--)
stack.push(curr[len]);
}
return _.max(sizes);
},
///////////////////////// Tensor transformation // /////////////////////////
lodash methods .chunk .flatten, _.flattenDeep swap at index i, j Mutates the array
swap: function(arr, i, j) {
arr[i] = arr.splice(j, 1, arr[i])[0];
return arr;
},
return a copy of reversed arr from index i to j inclusive
reverse: function(arr, i, j) {
var vec = arr.slice(0);
var k = i == undefined ? 0 : i;
var l = j == undefined ? arr.length - 1 : j;
var mid = Math.ceil((k + l) / 2);
while (k < mid)
lomath.swap(vec, k++, l--);
return vec;
},
return a copy: extend an array till toLen, filled with val defaulted to 0. Mutates the array
extend: function(arr, toLen, val) {
var lendiff = toLen - arr.length,
rePal = (val == undefined ? 0 : val);
if (lendiff < 0)
throw new Error("Array longer than the length to extend to")
while (lendiff--)
arr.push(rePal);
return arr;
},
applying _.indexOf in batch; returns -1 for field if not found
batchIndexOf: function(arr, fieldArr) {
return _.map(fieldArr, function(t) {
return _.indexOf(arr, t)
});
},
return valid indices from indArr, i.e. in range 0 to maxLen
validInds: function(indArr, maxLen) {
return _.filter(indArr, lomath.inRange.bind(null, 0, maxLen));
},
return a copy with sub rows from matrix M
rbind: function(M, indArr) {
indArr = lomath.validInds(indArr, M.length);
if (indArr.length == 0) return [];
return _.map(indArr, function(i) {
return _.cloneDeep(M[i]);
});
},
return a copy with sub rows from matrix M
cbind: function(M, indArr) {
indArr = lomath.validInds(indArr, M[0].length)
if (indArr.length == 0) return [];
return _.map(M, function(row) {
return _.map(indArr, function(i) {
return row[i];
});
});
},
Assuming matrix has header, rbind by header fields instead of indices
cbindByField: function(M, fieldArr) {
assuming header is first row of matrix
var header = M[0],
fieldInds = lomath.batchIndexOf(header, fieldArr);
return lomath.cbind(M, fieldInds);
},
[1, 2, 3],
[4]
])
[1, 2, 3],
[4]
], 'a')
make a tensor rectangular by filling with val, defaulted to 0. mutates the tensor
rectangularize: function(T, val) {
var toLen = lomath.maxDeepestLength(T),
stack = [];
stack.push(T);
while (stack.length) {
var curr = stack.pop();
if (lomath.isFlat(curr))
lomath.extend(curr, toLen, val);
else
_.each(curr, function(c) {
stack.push(c);
})
}
return T;
},
use chunk from inside to outside:
reshape: function(arr, dimArr) {
var tensor = arr;
var len = dimArr.length;
while (--len)
tensor = _.chunk(tensor, dimArr[len]);
return tensor;
},
flattenJSON: function(obj, delimiter) {
var delim = delimiter || '.'
var nobj = {}
_.each(obj, function(val, key) {
if (_.isPlainObject(val) && !_.isEmpty(val)) {
var strip = lomath.flattenJSON(val, delim)
_.each(strip, function(v, k) {
nobj[key + delim + k] = v
})
} else if (_.isArray(val) && !_.isEmpty(val)) {
_.each(val, function(v, index) {
nobj[key + delim + index] = v
if (_.isObject(v)) {
nobj = lomath.flattenJSON(nobj, delim)
};
})
} else {
nobj[key] = val
}
})
return nobj
},
unflattenJSON: function(obj, delimiter) {
return lomath.unobjectifyArray(lomath.unflattenJSON_objectifyArray(obj, delimiter))
},
unflattenJSON_objectifyArray: function(obj, delimiter) {
var delim = delimiter || '.';
cache deep keys for deletion later
var deepKeys = [];
iterate over each key-val pair breath first
_.each(obj, function(val, key) {
if can be delimiter-splitted
if (_.includes(key, delim)) {
var ind = key.lastIndexOf(delim)
new key
var v0 = key.slice(0, ind);
the tail as new key now
var v1 = key.slice(ind + delim.length);
guard, if sibling didn’t already create it
if (!obj[v0]) {
obj[v0] = {}
};
obj[v0][v1] = val;
push for deletion
deepKeys.push(key)
}
});
delete the old deepkeys
_.map(deepKeys, function(dkey) {
delete obj[dkey]
});
check if should go down further if still has delim
var goDown = false;
_.each(_.keys(obj), function(k) {
if (k.lastIndexOf(delim) != -1) {
goDown = true;
return false
};
})
if (goDown) {
return lomath.unflattenJSON_objectifyArray(obj, delim)
} else {
return obj
}
},
unobjectifyArray: function(obj) {
var index = 0;
var arr = [];
var isIndex = true;
if (_.isEmpty(obj)) {
isIndex = false;
};
iterate through and see if all keys indeed form a numeric sequence sort keys. Sort since iteration order is not guaranteed.
var sortedKeys = _.keys(obj).sort()
_.each(sortedKeys, function(k) {
add the value while keys still behaving like numeric
if (parseInt(k) == index++) {
arr.push(obj[k])
} else {
isIndex = false
return false
}
})
var res = isIndex ? arr : obj
recurse down the val if it’s not primitive type
_.each(res, function(v, k) {
if (_.isObject(v)) {
res[k] = lomath.unobjectifyArray(v)
}
})
return res
},
//////////// Matrices // //////////// transpose a matrix
transpose: function(M) {
return _.zip.apply(null, M);
},
trace a matrix
trace: function(M) {
var len = M.length,
sum = 0;
while (len--)
sum += M[len][len]
return sum;
},
multiply two matrices
matMultiply: function(A, B) {
var T = lomath.transpose(B);
return _.map(A, function(a) {
return _.map(T, lomath.dot.bind(null, a))
})
},
coSubMatrix: function(M, r, c) {
r = r || 0;
c = c || 0;
var coSubMat = [];
for (var i = 0; i < M.length; i++) {
if (i != r) {
var row = M[i],
coRow = [];
for (var j = 0; j < M.length; j++) {
if (j != c) coRow.push(row[j]);
}
coSubMat.push(coRow);
}
}
return coSubMat;
},
coMatrix: function(M) {
var coMat = [];
for (var i = 0; i < M.length; i++) {
var coRow = [];
for (var j = 0; j < M.length; j++) {
var subMat = lomath.coSubMatrix(M, i, j);
coRow.push(lomath.parity(i + j) * lomath.det(subMat));
}
coMat.push(coRow);
}
return coMat;
},
adj: function(M) {
var adjMat = [];
for (var i = 0; i < M.length; i++) {
var adjRow = [];
for (var j = 0; j < M.length; j++) {
var subMat = lomath.coSubMatrix(M, j, i);
adjRow.push(lomath.parity(i + j) * lomath.det(subMat));
}
adjMat.push(adjRow);
}
return adjMat;
},
detSum: function(M, i) {
var sign = lomath.parity(i),
cofactor = M[0][i],
coSubMat = lomath.coSubMatrix(M, 0, i);
return sign * cofactor * lomath.det(coSubMat);
},
det: function(M) {
var n = M.length;
var det = 0;
if (!n) {
det = M
} else if (n == 1) {
det = M[0][0] || M[0]
} else if (n == 2) {
det = M[0][0] * M[1][1] - M[1][0] * M[0][1]
} else {
var head = M[0];
det += lomath.fsum(M, lomath.detSum)
}
return det;
},
inv: function(M) {
var det = lomath.det(M);
if (det == 0) return null;
else
return lomath.multiply(1 / det, lomath.adj(M))
},
///////////////////////////// Subsets and combinatorics // ///////////////////////////// generate n-nary number of length
genAry: function(length, n) {
var range = _.map(_.range(n), String);
var tmp = range,
it = length;
while (--it) {
tmp = _.flattenDeep(_.map(range, function(x) {
return lomath.distributeRight(lomath.a_add, x, tmp)
}));
}
return tmp;
},
convert array of strings to array of array of numbers
toNumArr: function(sarr) {
return _.map(sarr, function(str) {
return _.map(str.split(''), function(x) {
return parseInt(x);
})
})
},
generate all permutation subset indices of n items
pSubset: function(n) {
var range = _.map(_.range(n), String),
res = [],
count = n;
res.push(range); //init
if (count == 0) return res;
while (--count) {
the last batch to expand on
var last = _.last(res);
var batch = [];
_.each(last, function(k) {
for (var i = 0; i < n; i++)
if (!_.includes(k.split(''), String(i)))
batch.push(k + i);
})
res.push(batch);
}
return res;
},
generate all subset indices of n items
subset: function(n) {
var range = _.map(_.range(n), String),
res = [],
count = n;
res.push(range); //init
if (count == 0) return res;
while (--count) {
the last batch to expand on
var last = _.last(res);
var batch = [];
_.each(last, function(k) {
for (var i = Number(_.last(k)) + 1; i < n; i++)
batch.push(k + i);
})
res.push(batch);
}
return res;
},
generate the indices of n-perm-r
permList: function(n, r) {
return lomath.toNumArr(lomath.pSubset(n)[r - 1]);
},
generate the indices of n-choose-r
combList: function(n, r) {
return lomath.toNumArr(lomath.subset(n)[r - 1]);
},
generate all permutations of n items
permute: function(n) {
var range = _.range(n),
res = [],
diffs, k = 0;
while (k != -1) {
res.push(range.slice(0));
diffs = lomath.stairs(range),
k = _.findLastIndex(diffs, lomath.isPositive);
var l = _.findLastIndex(range, function(t) {
return t > range[k];
});
lomath.swap(range, k, l);
range = lomath.reverse(range, k + 1, null);
}
return res;
},
return factorial(n) alias: fact
factorial: function(n) {
if (n == 0) return 1;
if (n < 0) throw "Negative factorial not defined"
var count = n,
res = n;
while (--count)
res *= count;
return res;
},
return n-permute-r alias: perm
permutation: function(n, r) {
if (r == 0) return 1;
if (n < 0 || r < 0) throw "Negative permutation not defined"
var count = r,
term = n;
res = n;
while (--count)
res *= --term;
return res;
},
return n-choose-r alias: comb
combination: function(n, r) {
var l = (r > n / 2) ? n - r : r;
if (n < 0 || l < 0) throw "Negative combination not defined"
return lomath.permutation(n, l) / lomath.factorial(l);
},
/////////////////// Handy vectorial // /////////////////// return the dot product of two vectors recyle if lengths mismatch
dot: function(X, Y) {
return _.sum(lomath.multiply(X, Y));
},
return the sum of n-powers of a tensor, default to n = 2
powSum: function(T, n) {
var L = n == undefined ? 2 : n;
return _.sum(lomath.pow(T, L));
},
return the L-n norm of a vector, default to L-2
norm: function(v, n) {
var L = n == undefined ? 2 : n;
return lomath.a_root(lomath.powSum(v, L), L);
},
normalize a vector(tensor) by L-n norm, default to n=2
normalize: function(v, n) {
return lomath.divide(v, lomath.norm(v, n));
},
rescale a vector to unit length
rescale: function(v) {
return lomath.normalize(v, 1);
},
//////////////// handy Matrix // ////////////////
Matrix ops Matrix ops Matrix ops
/////////////// Handy trend // /////////////// return the stairs: adjacent difference in a vector
stairs: function(v) {
var dlen = v.length - 1,
st = Array(dlen);
while (dlen--)
st[dlen] = v[dlen + 1] - v[dlen];
return st;
},
check the trend of vector v using sign-function
stairsTrend: function(v, sigFn) {
return lomath.sameSig(lomath.stairs(v), sigFn);
},
check if vector v is increasing
increasing: function(v) {
return lomath.stairsTrend(v, lomath.isPositive);
},
check is vector v is non-decreasing
nonDecreasing: function(v) {
return lomath.stairsTrend(v, lomath.nonNegative);
},
check is vector v is decreasing
decreasing: function(v) {
return lomath.stairsTrend(v, lomath.isNegative);
},
check is vector v is non-increasing
nonIncreasing: function(v) {
return lomath.stairsTrend(v, lomath.nonPositive);
},
///////////////////// Handy statistical // ///////////////////// return the average of a vector
mean: function(v) {
return _.sum(v) / v.length;
},
return the expectation value E(fn(x)), given probability and value vectors, and an optional atomic fn, defaulted to identity E(x). Note: fn must be atomic alias E
expVal: function(X, P, fn) {
var val, prob, func;
if only X is specified
if (P == undefined && fn == undefined) {
var hist = lomath.histogram(X);
val = hist.value,
prob = hist.prob;
}
if X, P specified (maybe fn too)
else if (typeof P === 'object') {
val = X;
prob = P;
func = fn;
}
if X, fn specified
else if (typeof P === 'function') {
var hist = lomath.histogram(X);
val = hist.value,
prob = hist.prob;
func = P;
}
if (func != undefined)
return lomath.dot(lomath.distributeSingle(func, val), prob);
return lomath.dot(val, prob);
},
return the variance, given probability and value vectors alias Var
variance: function(X, P, fn) {
if only X is specified
if (P == undefined && fn == undefined) {
return lomath.expVal(X, lomath.a_square) - lomath.a_square(lomath.expVal(X))
}
if X, P specified (maybe fn too)
else if (typeof P === 'object') {
return fn == undefined ?
lomath.expVal(X, P, lomath.a_square) - lomath.a_square(lomath.expVal(X, P)) :
lomath.expVal(X, P, _.flow(fn, lomath.a_square)) - lomath.a_square(lomath.expVal(X, P, fn));
}
if X, fn specified
else if (typeof P === 'function') {
return lomath.expVal(X, _.flow(P, lomath.a_square)) - lomath.a_square(lomath.expVal(X, P));
}
},
return the variance, given probability and value vectors
stdev: function(X, P, fn) {
return Math.sqrt(lomath.variance(X, P, fn));
},
histogram: function(data, fn, pair) {
if (fn == true) // called with data, pair
return _.toPairs(_.countBy(data));
var bin = _.countBy(data, fn);
if (pair == true) // called with data, fn, pair
return _.toPairs(bin);
var freq = _.values(bin);
return { //called with data, [fn]
value: _.keys(bin),
freq: freq,
prob: lomath.rescale(freq)
}
},
Calculate the rate of return r in % of an exp growth, given final value m_f, initial value m_i, and time interval t
expGRate: function(m_f, m_i, t) {
return 100 * (Math.exp(Math.log(m_f / m_i) / t) - 1);
},
Calculate the trailing exp rate of return in the last t years given a vector v
trailExpGRate: function(v, t) {
var len = v.length;
return lomath.expGRate(v[len - 1], v[len - 1 - t], t);
},
//////////////////////////////////////// Plotting modules: normal and dynamic // ////////////////////////////////////////
[{
name: "linear",
data: v
}, {
name: "square",
data: vv
}],
"Title 1"
)
[{
name: "log",
data: _.log(v)
}],
"Title 2"
)
hc: require(__dirname+’/chart/plot.js’).hc
hc: function() {
var p = require(__dirname + '/chart/plot.js').p;
return new p();
},
[{
name: "linear",
data: [1, 2, 3, 4, 5, 6]
}, {
name: "square",
data: [1, 4, 9, 16, 25, 36]
}],
"Title 1"
)
[{
name: "square",
data: [[3, 9], [4, 16], [5, 25], [6, 36]]
}],
"Title 2"
)
plot: function(seriesArr, title, yLabel, xLabel) {
console.log("Please call this method by _.hc.plot");
return 0;
},
chart: {
type: 'column'
},
title: {
text: 'Monthly Average Rainfall'
},
subtitle: {
text: 'Source: WorldClimate.com'
},
xAxis: {
categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
crosshair: true
},
yAxis: {
min: 0,
title: {
text: 'Rainfall (mm)'
}
},
plotOptions: {
column: {
pointPadding: 0.2,
borderWidth: 0
}
},
series: [{
name: 'Tokyo',
data: [49.9, 71.5, 106.4, 129.2, 144.0, 176.0]
}, {
name: 'New York',
data: [83.6, 78.8, 98.5, 93.4, 106.0, 84.5]
}, {
name: 'London',
data: [48.9, 38.8, 39.3, 41.4, 47.0, 48.3]
}, {
name: 'Berlin',
data: [42.4, 33.2, 34.5, 39.7, 52.6, 75.5]
}]
})
advPlot: function(options) {
console.log("Please call this method by _.hc.advPlot");
return 0;
},
render: function(autosave) {
console.log("Please call this method by _.hc.advPlot");
return 0;
},
The time variables for tick tock
t_start: 0,
t_end: 0,
tick: function() {
lomath.t_start = _.now();
return lomath.t_start;
},
tock: function() {
lomath.t_end = _.now();
var diff = lomath.t_end - lomath.t_start;
console.log('Elapsed ms:', diff);
return diff;
},
p: function() {
console.log.apply(null, arguments);
}
})
Export lomath as _
module.exports = lomath;