scijs / scijs-ndarray-for-matlab-users

Common scijs/ndarray operations for people familar with MATLAB (or at least not familiar with scijs)

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

scijs/ndarray for MATLAB users

Common scijs/ndarray operations for people familar with MATLAB (or at least not familiar with scijs)

Introduction

This document is a work in progress! Inspired by Numpy for Matlab users, it aspires to be NumPy for MATLAB users. The intent is both to communicate what is possible in JavaScript using scijs and to illuminate which parts still need work. I'll be mostly offline until the new year so I may not respond immediately, but comments, question and pull requests are more than welcome.

Memory Management

First things first, ndarrays are similar to but different to work with than MATLAB arrays. To get a sense for how managing ndarrays differs from managing MATLAB arrays, consider the diagonal of a 5×5 matrix:

var pool = require('ndarray-scratch')
var diag = require('ndarray-diagonal')

// A 5x5 matrix of ones:
var a = pool.ones([5,5])

// A view of the diagonal of a:
var x = diag(a)

Even though they have different dimensionality and shape, the internal data contained in ndarray x is identical by reference to that in a. The difference is that the strides and offsets of x are set to select only the diagonal elements of a. To see this directly, note the data of x still has 25 numbers even though it's shape is a vector of length 5:

console.log(x)
// => View1dfloat64 {
//   data: 
//    Float64Array {'0': 1, '1': 1, '2': 1, ..., '22': 1, '23': 1, '24': 1 },
//   shape: [ 5 ],
//   stride: [ 6 ],
//   offset: 0 }

Cloning simplifies the representation by allocating new storage and copying only the elements exposed by view x:

console.log(pool.clone(x))
// => View1dfloat64 {
//      data: Float64Array { '0': 1, '1': 1, '2': 1, '3': 1, '4': 1 },
//      shape: [ 5 ],
//      stride: [ 1 ],
//      offset: 0 }

As a result, a nice advantage of ndarrays the ability to manipulate representations without the need to iterate directly or allocate additional storage (beyond the lightweight ndarray wrapper). The example below uses in-place operations of ndarray-ops to assign the scalar 3 to the diagonal and double the first two columns of a:

var ops = require('ndarray-ops')
var show = require('ndarray-show')

// Set each element of the diagonal of a (5x5 matrix of ones) to 3:
ops.assigns(diag(a, 3))

// Double the first two columns:
ops.mulseq(a.hi(null,2))

console.log(show(a))
// => 6.000    2.000    1.000    1.000    1.000
//    2.000    6.000    1.000    1.000    1.000
//    2.000    2.000    3.000    1.000    1.000
//    2.000    2.000    1.000    3.000    1.000
//    2.000    2.000    1.000    1.000    3.000

Operations

The table below collects common matlab operations as well as their ndarray analogs. Not all operations have a counterpart, some because of features and shortcomings of the JavaScript language, some because of differences in memory management, and some because they're simply not yet implemented.

MATLAB JavaScript Notes
ndims(a) a.dimension get the number of dimensions of a
numel(a) a.size get the number of elements of an arary
size(a) a.shape get the size of the array
size(a,n) a.shape[n-1] get the number of elements of the n-th dimension of array a
[1 2 3; 4 5 6 ] ndarray([1,2,3,4,5,6],[2,3]) 2×3 matrix literal (using Array type)
  ndarray(new Float64Array([1,2,3,4,5,6]),[2,3]) 2×3 matrix literal (using 64-bit typed array)
  pack([[1,2,3],[4,5,6]]) 2×3 matrix literal from nested array
a(end) a.get(a.shape[0]-1) access last element in the 1×n matrix a
a(2, 5) a.get(1, 4) access element in second row, fifth column
a(2, :) a.pick(1, null) entire second row of a
a(1:5, :) a.hi(5, null) the first five rows of a
a(end-4:end, :) a.lo(a.shape[0]-5, null) the last five rows of a
a(1:3, 5:9) a.hi(3, 9).lo(0, 4) rows one to three and columns five to nine of a
a([2, 4, 5], [1, 3]) rows 2, 4, and 5 and columns 1 and 3.
a(3:2:21, :) a.hi(21, null).lo(2, null).step(2, 1) every other row of a, starting with the third and going to the twenty-first
a(1:2:end, :) a.step(2, 1) every other row of a, starting with the first
a(end:-1:1, :) or flipup(a) a.step(-1, 1) a with rows in reverse order
a([1:end 1], :) a with copy of the first rows appended to the end
a.' a.transpose(1, 0) transpose of a
a' conjugate transpose of a
c = a * b gemm(c, a, b) matrix multiply
c = a + b ops.add(c, a, b) matrix addition
c = a + 2 ops.adds(c, a, 2) matrix + scalar addition
a += b (not available in MATLAB) ops.addeq(a, b) in-place matrix addition
c = a .* b ops.mul(c, a, b) element-wise multiply
a = a .* b ops.muleq(a, b) element-wise multiply (in-place)
c = a ./ b ops.div(c, a, b) element-wise division
a = a ./ b ops.diveq(a, b) element-wise division (in-place)
a.^3 ops.pows(a, 3) element-wise scalar exponentiation
(a>0.5) matrix whose i,jth element is (a_ij > 0.5)
find(a>0.5) find the indices where (a > 0.5)
a(:, find(v>0.5)) extract the columns of a where vector v > 0.5
a(a<0.5)=0 a with elements less than 0.5 zeroed out
a .* (a>0.5) a with elements less than 0.5 zeroed out
a(:) = 3 ops.assigns(a, 3) set all values to the same scalar value
y = x y =pool.clone(x) clone by value
y = x(2, :) y = x.pick(1, null) slices are by reference
1:10 create an increasing vector
0:9 create an increasing vector
zeros(3, 4) pool.zeros([3, 4], 'float64') 3×4 rand-2 array full of 64-bit floating point zeros
zeros(3, 4, 5) pool.zeros([3, 4, 5], 'float64') 3×4×5 rank-3 array full of 64-bit floating point zeros
ones(3, 4) pool.ones([3, 4], 'float64') 3×4 rank-2 array full of 64-bit floating point ones
eye(3) pool.eye([3, 3], 'float64') 3×3 identity matrix with 64-bit floating point precision
diag(a) diag(a) vector of diagonal elements of a (returns diagonal by reference)
diag(a, 0) b = pool.zeros(a.shape)
ops.assign(diag(b), diag(a))
square diagonal matrix whose nonzero values are the elements of a
rand(3, 4) fill(pool.zeros([3, 4]), Math.random) random 3×4 matrix
linspace(1, 3, 4) linspace(1, 3, 4) 4 equally spaced samples between 1 and 3, inclusive
[x, y] = meshgrid(0:8, 0:5) two 2D arrays: one of x values, one of y values
[x, y] = meshgrid([1, 2, 4], [2, 4, 5])
repmat(a, m, n) tile(a, [m, n]) create m×n copies of a
[a b] concatCols([a, b]) concatenate columns of a and b
[a; b] concatRows([a, b]) concatenate rows of a and b
max(max(a)) maximum element of a
max(a) ops.max(a) maximum element in a
norm(v) ops.norm2(v) L2 norm of vector v
c = a & b ops.band(c, a, b) element-by-element AND operator
c = a | b ops.bor(c, a, b) element-by-element OR operator
inv(a) inverse of square matrix a
pinv(a) pseudo-inverse of matrix a
rank(a) rank of matrix a
a\b lup(a, a, P)
solve(a, a, P, b)
solution of a x = b for x
b/a solution of x a = b for x
chol(a) chol(a, L) cholesky factorization of matrix
[V, D] = eig(a) eigenvalues and eigenvectors of a
[V, D] = eig(a, b) eigenvalues and eigenvectors of a, b
[Q, R, P] = qr(a, 0) qr.factor(A, d)
qr.constructQ(A, Q)
QR decomposition. (Depending on the use, you can likely use Q without constructing explicitly. See documentation.)
[L, U, P] = lu(a) lup(A, L, P) LU decomposition
fft(a) fft(1, ar, ai) Fourier transform of a. Javascript does not have a complex type so real and imaginary parts must be passed separately.
ifft(a) fft(-1, ar, ai) inverse Fourier transform of a
[b, I] = sortrows(a, i) sort(a) sort the rows of the matrix
  sort(a.transpose(1, 0)) sort the column of the matrix
regress(y, X) qr.factor( A, d );
qr.solve( A, d, y );
multilinear regression
decimate(x, q) resample(output, input) downsample with low-pass filtering (resample downsamples by a factor of two)
unique
squeeze(a) squeeze(a) Remove singleton dimensions of a

License

© 2015 Ricky Reusser. MIT License.

About

Common scijs/ndarray operations for people familar with MATLAB (or at least not familiar with scijs)