Function builder backend

Distribute

op(x, y)

order-preserving function composition
fn('a', 'b').should.equal('a*b')

distributeSingle(fn, Y)

scalar
fn(A.lone, A.S).should.equal('*a')
vector
fn(A.lone, A.V).should.deep.equal(['*1', '*2', '*3'])
matrix
fn(A.lone, A.M).should.deep.equal([
  ['*1', '*2'],
  ['*3', '*4'],
  ['*5', '*6']
])

distributeLeft(fn, X, y)

vector * scalar
fn(A.pair, A.V, A.S).should.deep.equal(['1*a', '2*a', '3*a'])
matrix * scalar
fn(A.pair, A.M, A.S).should.deep.equal([
  ['1*a', '2*a'],
  ['3*a', '4*a'],
  ['5*a', '6*a']
])

distributeRight(fn, x, Y)

scalar * vector
fn(A.pair, A.S, A.V).should.deep.equal(['a*1', 'a*2', 'a*3'])
scalar * matrix
fn(A.pair, A.S, A.M).should.deep.equal([
  ['a*1', 'a*2'],
  ['a*3', 'a*4'],
  ['a*5', 'a*6']
])

distributeBoth(X, Y)

vector * vector
fn(A.pair, A.U, A.R).should.deep.equal(['a*3', 'b*2', 'c*1'])
vector * matrix
fn(A.pair, A.U, A.M).should.deep.equal([
  ['a*1', 'a*2'],
  ['b*3', 'b*4'],
  ['c*5', 'c*6']
])
matrix * matrix
fn(A.pair, A.L, A.M).should.deep.equal([
  ['a*1', 'b*2'],
  ['c*3', 'd*4'],
  ['e*5', 'f*6']
])
non-commutative: order-preserving
fn(A.pair, A.U, A.R).should.not.deep.equal(fn(A.pair, A.R, A.U))
vector * longer vector
fn(A.pair, A.U, A.VV).should.deep.equal(['a*1', 'b*2', 'c*3', 'a*4', 'b*5', 'c*6'])
mismatch vector length
(function() {
  return fn(A.pair, A.V, [1, 2])
}).should.throw(/different dimensions/)
longer vector * matrix
fn(A.pair, A.UU, A.M).should.deep.equal([
  ['a*1', 'a*2'],
  ['b*3', 'b*4'],
  ['c*5', 'c*6'],
  ['d*1', 'd*2'],
  ['e*3', 'e*4'],
  ['f*5', 'f*6']
])

distribute(X, Y)

scalar * scalar
fn(A.pair, A.S, A.T).should.deep.equal('a*0')
scalar * vector
fn(A.pair, A.S, A.V).should.deep.equal(['a*1', 'a*2', 'a*3'])
scalar * matrix
fn(A.pair, A.S, A.M).should.deep.equal([
  ['a*1', 'a*2'],
  ['a*3', 'a*4'],
  ['a*5', 'a*6']
])
vector * vector
fn(A.pair, A.U, A.R).should.deep.equal(['a*3', 'b*2', 'c*1'])
vector * matrix
fn(A.pair, A.U, A.M).should.deep.equal([
  ['a*1', 'a*2'],
  ['b*3', 'b*4'],
  ['c*5', 'c*6']
])
matrix * matrix
fn(A.pair, A.L, A.M).should.deep.equal([
  ['a*1', 'b*2'],
  ['c*3', 'd*4'],
  ['e*5', 'f*6']
])
non-commutative: order-preserving
fn(A.pair, A.U, A.R).should.not.deep.equal(fn(A.pair, A.R, A.U))
vector * longer vector
fn(A.pair, A.U, A.VV).should.deep.equal(['a*1', 'b*2', 'c*3', 'a*4', 'b*5', 'c*6'])
longer vector * matrix
fn(A.pair, A.UU, A.M).should.deep.equal([
  ['a*1', 'a*2'],
  ['b*3', 'b*4'],
  ['c*5', 'c*6'],
  ['d*1', 'd*2'],
  ['e*3', 'e*4'],
  ['f*5', 'f*6']
])

Associate

asso(fn, argObj)

apply in order
wrapfn('a', 'b', 'c').should.equal('a*b*c');
applicable to single array
fn(A.pair, ['a', 'b', 'c']).should.equal('a*b*c');

assodist(fn, argObj)

apply in order, distribute while can
wrapfn('a', A.V, 'b').should.deep.equal(['a*1*b', 'a*2*b', 'a*3*b']);

Simple functions generalized with assodist for tensor

c()

concat scalars
fn('a', 'b', 'c').should.deep.equal(['a', 'b', 'c'])
concat vector
fn(A.V).should.deep.equal(A.V)
concat matrix
fn(A.M).should.deep.equal([1, 2, 3, 4, 5, 6])
concat tensors
fn(A.S, A.V, A.U, A.M).should.deep.equal(['a', 1, 2, 3, 'a', 'b', 'c', 1, 2, 3, 4, 5, 6])

a_sum(T)

atomic sum single tensor
fn(A.M).should.equal(1 + 2 + 3 + 4 + 5 + 6)

sum()

sum tensors
fn(A.T, A.V, A.M).should.equal(0 + 1 + 2 + 3 + 1 + 2 + 3 + 4 + 5 + 6)

fsum()

sum mult with index
fn([1, 1, 1], function(T, i) {
  return T[i] * i
}).should.equal(3)

a_prod(T)

atomic product single tensor
fn(A.M).should.equal(1 * 2 * 3 * 4 * 5 * 6)

prod()

prod tensors
fn(A.T, A.V, A.M).should.equal(0 * 1 * 2 * 3 * 1 * 2 * 3 * 4 * 5 * 6)

a_add(x,y)

atomic add scalars
fn(1, 2).should.equal(1 + 2)

add()

scalars
fn(1, 2, 3).should.equal(1 + 2 + 3)
scalar and vector
fn(A.T, A.V).should.deep.equal(A.V)
scalar and matrix
fn(A.T, A.M).should.deep.equal(A.M)
vector and vector
fn(A.V, A.N).should.deep.equal([0, 0, 0])
vector and matrix
fn(A.V, A.M).should.deep.equal([
  [1 + 1, 1 + 2],
  [2 + 3, 2 + 4],
  [3 + 5, 3 + 6]
])
matrix and matrix
fn(A.K, A.M).should.deep.equal([
  [0, 0],
  [0, 0],
  [0, 0]
])
vectors of unequal lengths
fn(A.V, A.VV).should.deep.equal([1 + 1, 2 + 2, 3 + 3, 1 + 4, 2 + 5, 3 + 6])
tensors of unequal lengths
fn(A.VV, A.M).should.deep.equal([
  [1 + 1, 1 + 2],
  [2 + 3, 2 + 4],
  [3 + 5, 3 + 6],
  [4 + 1, 4 + 2],
  [5 + 3, 5 + 4],
  [6 + 5, 6 + 6]
])
multiple tensors
fn(A.V, A.M, A.R).should.deep.equal([
  [1 + 1 + 3, 1 + 2 + 3],
  [2 + 3 + 2, 2 + 4 + 2],
  [3 + 5 + 1, 3 + 6 + 1]
])

a_subtract(x,y)

atomic subtract scalars
fn(1, 2).should.equal(1 - 2)

subtract()

scalars
fn(1, 2, 3).should.equal(1 - 2 - 3)
scalar and vector
fn(A.T, A.V).should.deep.equal(A.N)
scalar and matrix
fn(A.T, A.M).should.deep.equal(A.K)
vector and vector
fn(A.V, A.V).should.deep.equal([0, 0, 0])
vector and matrix
fn(A.V, A.M).should.deep.equal([
  [1 - 1, 1 - 2],
  [2 - 3, 2 - 4],
  [3 - 5, 3 - 6]
])
matrix and matrix
fn(A.M, A.M).should.deep.equal([
  [0, 0],
  [0, 0],
  [0, 0]
])
vectors of unequal lengths
fn(A.V, A.VV).should.deep.equal([1 - 1, 2 - 2, 3 - 3, 1 - 4, 2 - 5, 3 - 6])
tensors of unequal lengths
fn(A.VV, A.M).should.deep.equal([
  [1 - 1, 1 - 2],
  [2 - 3, 2 - 4],
  [3 - 5, 3 - 6],
  [4 - 1, 4 - 2],
  [5 - 3, 5 - 4],
  [6 - 5, 6 - 6]
])
multiple tensors
fn(A.V, A.M, A.R).should.deep.equal([
  [1 - 1 - 3, 1 - 2 - 3],
  [2 - 3 - 2, 2 - 4 - 2],
  [3 - 5 - 1, 3 - 6 - 1]
])

a_multiply(x,y)

atomic multiply scalars
fn(1, 2).should.equal(1 * 2)

multiply()

scalars
fn(1, 2, 3).should.equal(1 * 2 * 3)
scalar and vector
fn(A.T, A.V).should.deep.equal([0, 0, 0])
scalar and matrix
fn(A.T, A.M).should.deep.equal([
  [0, 0],
  [0, 0],
  [0, 0]
])
vector and vector
fn(A.V, A.V).should.deep.equal([1 * 1, 2 * 2, 3 * 3])
vector and matrix
fn(A.V, A.M).should.deep.equal([
  [1 * 1, 1 * 2],
  [2 * 3, 2 * 4],
  [3 * 5, 3 * 6]
])
matrix and matrix
fn(A.M, A.M).should.deep.equal([
  [1 * 1, 2 * 2],
  [3 * 3, 4 * 4],
  [5 * 5, 6 * 6]
])
vectors of unequal lengths
fn(A.V, A.VV).should.deep.equal([1 * 1, 2 * 2, 3 * 3, 1 * 4, 2 * 5, 3 * 6])
tensors of unequal lengths
fn(A.VV, A.M).should.deep.equal([
  [1 * 1, 1 * 2],
  [2 * 3, 2 * 4],
  [3 * 5, 3 * 6],
  [4 * 1, 4 * 2],
  [5 * 3, 5 * 4],
  [6 * 5, 6 * 6]
])
multiple tensors
fn(A.V, A.M, A.R).should.deep.equal([
  [1 * 1 * 3, 1 * 2 * 3],
  [2 * 3 * 2, 2 * 4 * 2],
  [3 * 5 * 1, 3 * 6 * 1]
])

a_divide(x,y)

atomic divide scalars
fn(1, 2).should.equal(1 / 2)

divide()

scalars
fn(1, 2, 3).should.equal(1 / 2 / 3)
scalar and vector
fn(A.T, A.V).should.deep.equal([0, 0, 0])
scalar and matrix
fn(A.T, A.M).should.deep.equal([
  [0, 0],
  [0, 0],
  [0, 0]
])
vector and vector
fn(A.V, A.V).should.deep.equal([1 / 1, 2 / 2, 3 / 3])
vector and matrix
fn(A.V, A.M).should.deep.equal([
  [1 / 1, 1 / 2],
  [2 / 3, 2 / 4],
  [3 / 5, 3 / 6]
])
matrix and matrix
fn(A.M, A.M).should.deep.equal([
  [1 / 1, 2 / 2],
  [3 / 3, 4 / 4],
  [5 / 5, 6 / 6]
])
vectors of unequal lengths
fn(A.V, A.VV).should.deep.equal([1 / 1, 2 / 2, 3 / 3, 1 / 4, 2 / 5, 3 / 6])
tensors of unequal lengths
fn(A.VV, A.M).should.deep.equal([
  [1 / 1, 1 / 2],
  [2 / 3, 2 / 4],
  [3 / 5, 3 / 6],
  [4 / 1, 4 / 2],
  [5 / 3, 5 / 4],
  [6 / 5, 6 / 6]
])
multiple tensors
fn(A.V, A.M, A.R).should.deep.equal([
  [1 / 1 / 3, 1 / 2 / 3],
  [2 / 3 / 2, 2 / 4 / 2],
  [3 / 5 / 1, 3 / 6 / 1]
])

a_log(x, base)

atomic log, base e default
fn(Math.E).should.equal(1);
base specified
fn(8, 2).should.equal(3);
log(0)
fn(0).should.equal(-Infinity);
log(Infinity)
fn(-1).should.deep.equal(NaN);

log(T, base)

tensor
fn(A.V).should.deep.equal([_.a_log(1), _.a_log(2), _.a_log(3)])

a_square(x)

atomic square
fn(2).should.equal(4)

square(T)

tensor
fn(A.V).should.deep.equal([_.a_square(1), _.a_square(2), _.a_square(3)])

a_root(x)

atomic root, base 2 default
fn(4).should.equal(2);
base specified
fn(8, 3).should.equal(2);
negative with even base
fn(-4, 2).should.deep.equal(NaN);
negative with odd base
fn(-8, 3).should.equal(-2);
base == 0
fn(4, 0).should.deep.equal(Infinity);

root(T)

tensor
fn(A.V).should.deep.equal([_.a_root(1), _.a_root(2), _.a_root(3)])

a_logistic(x)

atomic logistic
fn(0).should.equal(0.5);

logistic(x)

atomic logistic
fn([0, 0]).should.deep.equal([0.5, 0.5]);

Simple signature checkers for tensors

inRange(left, right, x)

is in range
fn(0, 3, 2).should.be.true
out of range
fn(0, 3, -2).should.be.false
left boundary
fn(0, 3, 0).should.be.true
right boundary
fn(0, 3, 3).should.be.true

sameSig(T, sigFn), sigFn:

isInteger
fn(A.V, _.isInteger).should.be.true
isDouble
fn([0.1, 0.2], _.isDouble).should.be.true
isPositive
fn(A.V, _.isPositive).should.be.true
nonPositive
fn([0, -1, -2], _.nonPositive).should.be.true
isNegative
fn(A.N, _.isNegative).should.be.true
nonNegative
fn([0, 1, 2], _.nonNegative).should.be.true
isZero
fn([0, 0, 0], _.isZero).should.be.true
nonZero
fn(A.V, _.nonZero).should.be.true
parity
_.parity(1).should.equal(-1)
_.parity(2).should.equal(1)

deepEqual(T, S)

matching vectors
fn(A.V, A.V).should.be.true
matching matrices and higher
fn(A.M, A.M).should.be.true
unmatching length vectors
fn(A.U, A.UU).should.be.false
unmatching length matrices
fn(A.U, A.M).should.be.false

Simple JS Math functions for tensors

tests implied from Function builder backend
fn(A.lone, A.S).should.equal('*a')
_.abs(-1).should.equal(Math.abs(-1))
_.acos(1).should.equal(Math.acos(1))
_.acosh(1).should.equal(Math.acosh(1))
_.asin(1).should.equal(Math.asin(1))
_.asinh(1).should.equal(Math.asinh(1))
_.atan(1).should.equal(Math.atan(1))
_.atanh(1).should.equal(Math.atanh(1))
_.ceil(1).should.equal(Math.ceil(1))
_.cos(1).should.equal(Math.cos(1))
_.cosh(1).should.equal(Math.cosh(1))
_.exp(1).should.equal(Math.exp(1))
_.floor(1).should.equal(Math.floor(1))
_.log10(2).should.equal(Math.log10(2))
_.log1p(2).should.equal(Math.log1p(2))
_.log2(2).should.equal(Math.log2(2))
_.round(1).should.equal(Math.round(1))
_.pow(1, 1).should.equal(Math.pow(1, 1))
_.sign(1).should.equal(Math.sign(1))
_.sin(1).should.equal(Math.sin(1))
_.sinh(1).should.equal(Math.sinh(1))
_.sqrt(1).should.equal(Math.sqrt(1))
_.tan(1).should.equal(Math.tan(1))
_.tanh(1).should.equal(Math.tanh(1))
_.trunc(1).should.equal(Math.trunc(1))

Regex functions

reMatch(regex)
_.reMatch(A.reWord)(A.strWord).should.be.true
_.reMatch(A.reWord)(A.strNum).should.be.false
_.reMatch(A.reNum)(A.strNum).should.be.true
_.reMatch(A.reNum)(A.strWord).should.be.false
reNotMatch(regex)
_.reNotMatch(A.reWord)(A.strWord).should.be.false
_.reNotMatch(A.reWord)(A.strNum).should.be.true
_.reNotMatch(A.reNum)(A.strNum).should.be.false
_.reNotMatch(A.reNum)(A.strWord).should.be.true
reGet(regex)
_.reGet(A.reWord)(A.str).should.equal(A.strWord)
_.reGet(A.reNum)(A.str).should.equal(A.strNum)
expect(_.reGet(A.reWord)(A.strNum)).to.be.null
reWrap(reg)
_.reWrap(A.reWord).should.be.a('string')
_.reWrap(A.reWord).should.equal('(?:[a-zA-Z]+)')
reAnd()
_.reAnd(A.reWord, A.reNum).should.be.an.instanceof(RegExp)
_.reAnd(A.reWord, A.reNum).should.deep.equal(/(?:[a-zA-Z]+)(?:[0-9]+)/)
reAndMatch()
_.reAndMatch(A.reWord, A.reNum)(A.str).should.be.true
_.reAndMatch(A.reWord, A.reNum)(A.strWord).should.be.false
_.reAndMatch(A.reWord, A.reNum)(A.strNum).should.be.false
reOr()
_.reOr(A.reWord, A.reNum).should.be.an.instanceof(RegExp)
_.reOr(A.reWord, A.reNum).should.deep.equal(/(?:[a-zA-Z]+)|(?:[0-9]+)/)
reAndMatch()
_.reOrMatch(A.reWord, A.reNum)(A.str).should.be.true
_.reOrMatch(A.reWord, A.reNum)(A.strWord).should.be.true
_.reOrMatch(A.reWord, A.reNum)(A.strNum).should.be.true
reString()
_.reString(A.reWord).should.equal(A.reWord.toString().replace(/^\/|\/.*$/g, ''))

Array creation

seq([start], stop, [step])

(to)
fn(3).should.deep.equal([1, 2, 3])
(from,to)
fn(0, 2).should.deep.equal([0, 1, 2])
fn(-2, 2).should.deep.equal([-2, -1, 0, 1, 2])
(from,to,diff)
fn(-4, 4, 2).should.deep.equal([-4, -2, 0, 2, 4])

numeric(N, [val])

(len)
fn(3).should.deep.equal([0, 0, 0])
(len,val)
fn(3, 'a').should.deep.equal(['a', 'a', 'a'])

Tensor properties

depth(T)

(T)
fn(A.T).should.equal(0)
fn(A.B).should.equal(3)

volume(T)

(T)
fn(A.T).should.equal(0)
fn(A.B).should.equal(4 * 3 * 2)

dim(T)

(T)
fn(A.T).should.deep.equal([])
fn(A.B).should.deep.equal([2, 3, 4])

isFlat(T)

(T)
fn(A.S).should.be.true
fn(A.V).should.be.true
fn(A.M).should.be.false

maxDeepestLength(T)

(T)
fn(A.T).should.equal(0)
fn(A.V).should.equal(3)
fn(A.M).should.equal(2)

Tensor transformation

swap(arr, i, j); mutates

(V,i,j); mutates
fn(v, 0, 2).should.deep.equal(A.R)
v.should.deep.equal(A.R)

reverse(arr, [i, [j]])

reverse all (V)
fn(A.VZ).should.deep.equal([5, 4, 3, 2, 1, 0])
reverse from (V,i)
fn(A.VZ, 2).should.deep.equal([0, 1, 5, 4, 3, 2])
reverse till (V,null,j)
fn(A.VZ, null, 2).should.deep.equal([2, 1, 0, 3, 4, 5])
reverse from to (V,i,j)
fn(A.VZ, 2, 4).should.deep.equal([0, 1, 4, 3, 2, 5])

extend(arr, toLen, [val]); mutates

(V, toLen) default val == 0
fn(v, 6).should.deep.equal([1, 2, 3, 0, 0, 0])
(V, toLen, val)
fn(v, 6, 'a').should.deep.equal([1, 2, 3, 'a', 'a', 'a'])
shorter (V,0)
(function() {
  return fn(v, 0)
}).should.throw(/Array longer/)

batchIndexOf(arr, fieldArr)

none valid
fn(A.UU, [1, 2, 3]).should.deep.equal([-1, -1, -1])
some valid
fn(A.UU, [1, 'a', 3]).should.deep.equal([-1, 0, -1])
all valid
fn(A.UU, A.U).should.deep.equal([0, 1, 2])
shuffled
fn(A.UU, ['c', 'b', 'a']).should.deep.equal([2, 1, 0])
repeated
fn(A.UU, ['a', 'a', 'a']).should.deep.equal([0, 0, 0])

validInds(indArr, maxLen)

none valid
fn([-2, -1], 2).should.deep.equal([])
some valid
fn([-2, 0, 2, 4], 2).should.deep.equal([0, 2])
shuffled
fn([-2, 4, 2, 0], 2).should.deep.equal([2, 0])
repeated
fn([-2, 4, 0, 2, 0], 2).should.deep.equal([0, 2, 0])

rbind(M, indArr)

none valid
fn(A.C, [-1, -2, 100]).should.deep.equal([])
some valid
fn(A.C, [-1, 0, 1]).should.deep.equal([
  [1, 2, 3],
  [4, 5, 6]
])
all valid
fn(A.C, [0, 1]).should.deep.equal([
  [1, 2, 3],
  [4, 5, 6]
])
shuffled
fn(A.C, [1, 0]).should.deep.equal([
  [4, 5, 6],
  [1, 2, 3]
])
repeated
fn(A.C, [1, 1]).should.deep.equal([
  [4, 5, 6],
  [4, 5, 6]
])

cbind(M, indArr)

none valid
fn(A.D, [-1, -2, 100]).should.deep.equal([])
some valid
fn(A.D, [-1, 0, 100]).should.deep.equal([
  ['a'],
  [1],
  [-1]
])
all valid
fn(A.D, [0, 2]).should.deep.equal([
  ['a', 'c'],
  [1, 3],
  [-1, -3]
])
shuffled
fn(A.D, [2, 1, 0]).should.deep.equal([
  ['c', 'b', 'a'],
  [3, 2, 1],
  [-3, -2, -1]
])
repeated
fn(A.D, [0, 0]).should.deep.equal([
  ['a', 'a'],
  [1, 1],
  [-1, -1]
])

cbindByField(M, fieldArr)

none valid
fn(A.D, [1, 2, 3]).should.deep.equal([])
some valid
fn(A.D, [1, 'a', 3]).should.deep.equal([
  ['a'],
  [1],
  [-1]
])
all valid
fn(A.D, ['a', 'c']).should.deep.equal([
  ['a', 'c'],
  [1, 3],
  [-1, -3]
])
shuffled
fn(A.D, ['c', 'b', 'a']).should.deep.equal([
  ['c', 'b', 'a'],
  [3, 2, 1],
  [-3, -2, -1]
])
repeated
fn(A.D, ['a', 'a']).should.deep.equal([
  ['a', 'a'],
  [1, 1],
  [-1, -1]
])

rectangularize(T, val); mutates

non-rectangular, default
fn(Q).should.deep.equal([
  [1, 2, 3],
  [4, 0, 0]
])
non-rectangular, val
fn(Q, -1).should.deep.equal([
  [1, 2, 3],
  [4, -1, -1]
])
rectangular, no change
fn(R).should.deep.equal([
  [1, 2, 3],
  [4, 5, 6]
])

reshape(arr, dimArr)

used with _.c and _.dim
fn(_.c(A.B), _.dim(A.B)).should.deep.equal(A.B)
rectangular
fn(R, [2, 3, 4]).should.deep.equal(A.B)
non-rectangular
fn(Q, [2, 3, 4]).should.deep.equal([
  [
    [1, 1, 1, 1],
    [2, 2, 2, 2],
    [3, 3, 3, 3]
  ],
  [
    [4, 4, 4, 4],
    [5, 5, 5, 5],
    [6, 6]
  ]
])

flattenJSON(obj)

default delimiter
fn(R).should.deep.equal({
  "level1.level2.level3": 0,
  "level1.level2.level3b": {},
  "level1.level2b.level3.0": 2,
  "level1.level2b.level3.1": 3,
  "level1.level2b.level3.2": 4,
  "level1.level2b.level3b.0.level4": 5,
  "level1.level2b.level3b.0.level4b": 6,
  "level1.level2b.level3b.0.level4c": 7
})
custom delimiter
fn(R, '_').should.deep.equal({
  "level1_level2_level3": 0,
  "level1_level2_level3b": {},
  "level1_level2b_level3_0": 2,
  "level1_level2b_level3_1": 3,
  "level1_level2b_level3_2": 4,
  "level1_level2b_level3b_0_level4": 5,
  "level1_level2b_level3b_0_level4b": 6,
  "level1_level2b_level3b_0_level4c": 7
})

unflattenJSON(obj)

default delimiter
fn(_.flattenJSON(R)).should.deep.equal(R)
custom delimiter
fn(_.flattenJSON(R, '_'), '_').should.deep.equal(R)

Matrices

transpose(M)

square
fn(A.C).should.deep.equal([
  [1, 4, 7],
  [2, 5, 8],
  [3, 6, 9]
])
non-square
fn(A.M).should.deep.equal([
  [1, 3, 5],
  [2, 4, 6]
])

trace(M)

square
fn(A.C).should.equal(15)

matMultiply(A,B)

square
fn([
  [1, 2],
  [3, 4]
], [
  [1, 2],
  [3, 4]
]).should.deep.equal([
  [7, 10],
  [15, 22]
])

coSubMatrix(M,r,c)

default
fn(A.C).should.deep.equal([
  [5, 6],
  [8, 9]
])
specified r,c
fn(A.C, 0, 1).should.deep.equal([
  [4, 6],
  [7, 9]
])

coMatrix(M)

unambiguous case
fn(mat).should.deep.equal([
  [7, -2, -3],
  [5, -16, 9],
  [-3, 6, -3]
])

adj(M)

unambiguous case
fn(mat).should.deep.equal([
  [7, 5, -3],
  [-2, -16, 6],
  [-3, 9, -3]
])

detSum(M, i)

sub determinant at index
fn(mat, 0).should.equal(7)

det(M)

scalar, just return
fn(2).should.equal(2)
1x1 matrix, just return
fn([2]).should.equal(2)
fn([
  [2]
]).should.equal(2)
2x2 matrix, basecase
fn([
  [1, 2],
  [3, 4]
]).should.equal(-2)
3x3 matrix and above
fn(mat).should.equal(-6)

inv(M)

invertible
fn(mat).should.deep.equal([
  [5.625, -2.375, -2.5],
  [4.75, -2.25, -2],
  [-3.375, 1.625, 1.5]
])
non-invertible
expect(fn([
  [1, 1],
  [1, 1]
])).to.be.null

Subsets and combinatorics

genAry(length, n)

binary
fn(1, 2).should.deep.equal(['0', '1'])
fn(2, 2).should.deep.equal(['00', '01', '10', '11'])
fn(3, 2).should.deep.equal(
  ['000', '001', '010', '011', '100', '101', '110', '111']
)
ternary
fn(2, 3).should.deep.equal(['00', '01', '02', '10', '11', '12', '20', '21', '22'])

toNumArr(strArr)

binary
fn(['00', '01', '10', '11']).should.deep.equal([
  [0, 0],
  [0, 1],
  [1, 0],
  [1, 1]
])

pSubset(n)

0 elements
fn(0).should.deep.equal([
  []
])
3 elements
fn(3).should.deep.equal([
  ['0', '1', '2'],
  ['01', '02', '10', '12', '20', '21'],
  ['012', '021', '102', '120', '201', '210']
])

subset(n)

0 elements
fn(0).should.deep.equal([
  []
])
3 elements
fn(3).should.deep.equal([
  ['0', '1', '2'],
  ['01', '02', '12'],
  ['012']
])

permList(n)

3 elements, with _.toNumArr
fn(3, 1).should.deep.equal(_.toNumArr(['0', '1', '2']))
fn(3, 2).should.deep.equal(_.toNumArr(['01', '02', '10', '12', '20', '21']))
fn(3, 3).should.deep.equal(_.toNumArr(['012', '021', '102', '120', '201', '210']))

subset(n)

3 elements, with _.toNumArr
fn(3, 1).should.deep.equal(_.toNumArr(['0', '1', '2']))
fn(3, 2).should.deep.equal(_.toNumArr(['01', '02', '12']))
fn(3, 3).should.deep.equal(_.toNumArr(['012']))

permute(n)

n elements
fn(2).should.deep.equal(_.permList(2, 2))
fn(3).should.deep.equal(_.permList(3, 3))
fn(4).should.deep.equal(_.permList(4, 4))

factorial(n)

normal
fn(5).should.equal(120)
0
fn(0).should.equal(1)
-1 throw error
(function() {
  return fn(-1)
}).should.throw(/Negative factorial not defined/)
big, without stackoverflow
fn(1000).should.deep.equal(Infinity)

permutation(n,r)

normal
fn(5, 1).should.deep.equal(5)
fn(5, 5).should.deep.equal(120)
fn(1000, 1).should.deep.equal(1000)
0
fn(5, 0).should.deep.equal(1)
-1 throw error
(function() {
  return fn(5, -1)
}).should.throw(/Negative permutation not defined/)
big, without stackoverflow
fn(1000, 1000).should.deep.equal(Infinity)

combination(n,r)

normal
fn(5, 1).should.deep.equal(5)
fn(5, 5).should.deep.equal(1)
fn(1000, 1).should.deep.equal(1000)
fn(1000, 1000).should.deep.equal(1)
0
fn(5, 0).should.deep.equal(1)
-1 throw error
(function() {
  return fn(5, -1)
}).should.throw(/Negative combination not defined/)
big, without stackoverflow
fn(1000, 500).should.deep.equal(NaN)

vectorial

dot(X, Y)

normal
fn(A.V, A.V).should.deep.equal(1 + 4 + 9)
length mismatch, recycle
fn(A.V, A.VV).should.deep.equal(fn(_.c(A.V, A.V), A.VV))

powSum(T, [n])

(V) vector, default to 2
fn(A.V).should.deep.equal(1 + 4 + 9)
(V, n)
fn(A.V, 3).should.deep.equal(1 + 8 + 27)
(T, n) tensor
fn(A.M).should.deep.equal(1 + 4 + 9 + 16 + 25 + 36)

norm(v, [n])

default to L-2 norm
fn([3, 4]).should.deep.equal(5)
specify L-n norm
fn([3, 4], 1).should.deep.equal(7)

normalize(v, [n])

default to L-2 norm
fn([3, 4]).should.deep.equal([3 / 5, 4 / 5])
specify to L-n norm
fn([3, 4], 1).should.deep.equal([3 / 7, 4 / 7])

rescale(v)

simply L-1 norm
fn([3, 4]).should.deep.equal([3 / 7, 4 / 7])

trend

stairs(v)

length be 1 less
fn(A.V).length.should.equal(A.V.length - 1)
next - current index
fn([1, 2, 3, 5, 8]).should.deep.equal([1, 1, 2, 3])

stairsTrend(v, sigFn)

specify stairs sign, collapsed
fn(A.V, _.isPositive).should.be.true
increasing
_.increasing(A.V).should.be.true
nonDecreasing
_.nonDecreasing([1, 1, 2, 3]).should.be.true
decreasing
_.decreasing(A.R).should.be.true
nonIncreasing
_.nonIncreasing([3, 2, 1, 1]).should.be.true

statistical

mean(v)

0 mean
fn([-2, -1, 0, 1, 2]).should.equal(0)

expVal(X, P, [fn])

if only X is specified
fn(vv).should.equal((-1) * 0.1 + 0 + 1 * 0.3 + 2 * 0.4)
if X, P specified; no fn
fn(v, p).should.equal((-1) * 0.1 + 0 + 1 * 0.3 + 2 * 0.4)
if X, fn specified; no P
fn(vv, _.a_square).should.equal(1 * 0.1 + 0 + 1 * 0.3 + 4 * 0.4)
if X, P, fn specified: atomic function: square
fn(v, p, _.a_square).should.equal(1 * 0.1 + 0 + 1 * 0.3 + 4 * 0.4)

variance(P, X, [fn])

if only X is specified
fn(vv).should.equal(
  (1 * 0.1 + 0 + 1 * 0.3 + 4 * 0.4) -
  _.a_square((-1) * 0.1 + 0 + 1 * 0.3 + 2 * 0.4)
)
if X, P specified; no fn
fn(v, p).should.equal(
  (1 * 0.1 + 0 + 1 * 0.3 + 4 * 0.4) -
  _.a_square((-1) * 0.1 + 0 + 1 * 0.3 + 2 * 0.4)
)
if X, fn specified; no P
fn(vv, _.a_square).should.be.closeTo(
  (1 * 0.1 + 0 + 1 * 0.3 + 16 * 0.4) -
  _.a_square(1 * 0.1 + 0 + 1 * 0.3 + 4 * 0.4), 0.0001)
if X, P, fn specified: atomic function: square
fn(v, p, _.a_square).should.be.closeTo(
  (1 * 0.1 + 0 + 1 * 0.3 + 16 * 0.4) -
  _.a_square(1 * 0.1 + 0 + 1 * 0.3 + 4 * 0.4), 0.0001)

stdev(P, X, [fn])

trvial sqrt of variance
_.stdev(v, p).should.equal(
  Math.sqrt(
    (1 * 0.1 + 0 + 1 * 0.3 + 4 * 0.4) -
    _.a_square((-1) * 0.1 + 0 + 1 * 0.3 + 2 * 0.4)
  )
)

histogram(data, [fn], [pair])

called with data only
var hist = fn(['a', 'b', 'b', 'c', 'c', 'c', 'd', 'd', 'd', 'd'])
hist.value.should.deep.equal(['a', 'b', 'c', 'd'])
hist.freq.should.deep.equal([1, 2, 3, 4])
hist.prob.should.deep.equal([0.1, 0.2, 0.3, 0.4])
call with data, fn
var hist = fn(['a', 'b', 'b', 'c', 'c', 'c', 'd', 'd', 'd', 'd'], _.identity)
hist.value.should.deep.equal(['a', 'b', 'c', 'd'])
hist.freq.should.deep.equal([1, 2, 3, 4])
hist.prob.should.deep.equal([0.1, 0.2, 0.3, 0.4])
call with data, fn, pair
var hist = fn(['a', 'b', 'b', 'c', 'c', 'c', 'd', 'd', 'd', 'd'], _.identity, true)
hist.should.be.an.instanceof(Array)
hist.should.deep.equal([
  ['a', 1],
  ['b', 2],
  ['c', 3],
  ['d', 4]
])
call with data, pair
var hist = fn(['a', 'b', 'b', 'c', 'c', 'c', 'd', 'd', 'd', 'd'], true)
hist.should.be.an.instanceof(Array)
hist.should.deep.equal([
  ['a', 1],
  ['b', 2],
  ['c', 3],
  ['d', 4]
])

expGRate(m_f, m_i, t)

rate of expGrowth return
fn(8, 2, 2).should.equal(100)

trailExpGRate(v, t)

trailing expGRate
fn(v, 1).should.equal(100)
fn(v, 2).should.equal(100)
fn(v, 3).should.equal(100)

plotter

call constructor
_.hc().should.not.equal(0)
Please call this method by _.hc.plot
call plot
_.plot().should.equal(0)
Please call this method by _.hc.advPlot
call advPlot
_.advPlot().should.equal(0)
Please call this method by _.hc.advPlot
call render
_.render().should.equal(0)

timer

tick
_.tick().should.be.a('number')
Elapsed ms: 1
tock
_.tock().should.be.a('number')

printer

Testing printer
p
_.p("Testing printer")