-
Notifications
You must be signed in to change notification settings - Fork 133
Tentative NMatrix Tutorial
- This tutorial is meant to mimic as closely as possible the Tentative NumPy Tutorial (as of August 2013) but for SciRuby's NMatrix. Because of this, examples and wording are used extensively from the Tentative Numpy Tutorial with little or no modification from the original. This is intentional, hereby disclosed, and hopefully justified given the nature of this project. The authors of this tutorial express deep appreciation to those responsible for the excellent Numpy and Tentative Numpy Tutorial. In addition, NMatrix was based heavily off of Masahiro Tanaka's excellent NArray work.
Before reading this tutorial you should know a bit of Ruby. If you would like to refresh your memory, take a look at the Ruby quickstart tutorial, the tutorials at rubymonk, or tryruby. If you wish to work the examples in this tutorial, you must also have some software installed on your computer. Minimally:
- Ruby >= 1.9.2 (Download Ruby)
- NMatrix (installation instructions)
You may find these useful for plotting:
Also, take a look at:
- pry for an enhanced interactive shell (better than irb)
- SciRuby(http://sciruby.com/) for additional scientific tools with growing NMatrix compatibility
- Follow the Tentative NumPy Tutorial as closely as possible.
- Aim to implement similar functionality, but in a ruby-ish way.
- Make a note on the wiki if something needs to be implemented to provide a particular functionality. Then, ideally, go and implement it and update the wiki.
An NMatrix is a homogeneous multidimensional array. It is a table of elements (usually numbers), all of the same type, indexed by a tuple of positive integers. In NMatrix dimensions are called axes. The number of axes is dim. For example, the coordinates of a point in 3D space [1, 2, 1] is an array of dim 1, because it has one axis. That axis has a length of 3. In example pictured below, the ruby Array has dim 2 (it is 2-dimensional). The first dimension (axis) has a length of 2, the second dimension has a length of 3.
# an array of dim 1 -- it has only one axis
[1, 2, 1] # plain ol' ruby Array
N[1, 2, 1] # an NMatrix
# an array of dim 2 -- has 3 columns and 2 rows.
[[1, 0, 0], # the plain ol' ruby Array of two arrays
[0, 1, 2]]
N[[1, 0, 0], [0, 1, 2]] # as an NMatrix
Key attributes of an NMatrix are:
NMatrix#dim
the number of axes (dimensions) of the array.
NMatrix#shape
A matrix with m number of rows and n number of columns will have a shape (m,n). The right-most number changes most rapidly while traversing the array (like Matlab, Numpy, and ruby Arrays of Arrays and opposite FORTRAN and PDL).
NMatrix#dtype
A symbol representing the type of array. (:byte, :int8, :int16, :int32, :int64, :float32, :float64, :complex64, :complex128, :rational64, :rational128, :object)
NMatrix#stype
The storage type of the array (:dense, :list, :yale). Dense is the default
and yale is for working with sparse arrays. List is less frequently used.
[NOTE: .itemsize and .size have no real equivalent right now. Also, access to underlying data (.data in numpy) is not well-defined]
You should be able to type in every code block in this document from here on and duplicate these results. Some output lines are omitted for brevity. The 'pp' method is short for 'pretty_print' and will often be used to display the NMatrix object.
% irb --simple-prompt
>> require 'nmatrix'
=> true
>> a = NMatrix.seq([3,5])
>> a.pp
[[0, 1, 2, 3, 4]
[5, 6, 7, 8, 9]
[10, 11, 12, 13, 14]]
>> a.shape
=> [3, 5]
>> a.dim
=> 2
>> a.class
=> NMatrix
>> a.dtype
=> :int32
>> a.stype
=> :dense
# [[no concept of itemsize or size right now]]
>> b = N[6, 7, 8]
>> b.pp
=> [6, 7, 8]
>> #<NMatrix:0x007fec8d777780 shape:[3] dtype:int32 stype:dense>
>> b.class
=> NMatrix
There are several ways to create NMatrix objects.
>> a = N[[2,3,4]]
>> a.dtype
=> :int32
>> b = N[[1.2, 3.5, 5.1]]
>> b.dtype
=> :float64
>> c = N[[2],[3],[4]] # same result as N[[2,3,4]].transpose
The simplest is using the N[ ] method. It understands arrays of arrays and will correctly deduce the correct type of the NMatrix (based on the element with highest typecode).
>> b = NMatrix[ [1.5, 2, 3], [4, 5, 6] ]
Note that even when creating vectors, the number of opening brackets controls the dimensionality. A vector created with N[1,2,3]
will have no orientation; but N[ [1,2,3] ]
will be a row vector, and N[ [1],[2],[3] ]
will be a column vector.
Once we have a matrix we can take a look at its attributes:
>> b.dim
=> 2
>> b.shape
=> [3, 2]
>> b.typecode
=> 5
>> b.element_size
=> 8
Arithmetic operations on NMatrix objects apply elementwise. A new NMatrix is created and filled with the result.
>> a = N[20.0,30.0,40.0,50.0]
=> [20.0, 30.0, 40.0, 50.0 shape:[4] dtype:float64 stype:dense>
>> b = N.seq [4]
=> [0, 1, 2, 3 shape:[4] dtype:int32 stype:dense>
>> c = a-b
=> [20.0, 29.0, 38.0, 47.0 shape:[4] dtype:float64 stype:dense>
>> b**2
=> [0, 1, 4, 9 shape:[4] dtype:int32 stype:dense>
>> a.map{|e| Math.sin(e)} * 10
=> [9.129452507276277, -9.880316240928618, 7.451131604793488, -2.6237485370392877 shape:[4] dtype:float64 stype:dense>
>> a < 35
=> [true, true, false, false shape:[4] dtype:object stype:dense>
Unlike in many matrix languages, the product operator * operates elementwise on NMatrix objects. The matrix product can be performed using the dot
function.
>> a = N[[1,1], [0,1]]
>> b = N[[2,0], [3,4]]
>> a*b
=> [2, 0, 0, 4 shape:[2,2] dtype:int32 stype:dense>
>> a.dot b
=> [5, 4, 3, 4 shape:[2,2] dtype:int32 stype:dense>
TODO: +=
-like operators
When operating with NMatrix objects of different types, the type of the resulting array corresponds to the more general or precise one (a behavior known as upcasting).
>> a = NVector.ones(3, :int32)
>> b = NVector.linspace(0, Math::PI, 3)
>> b.dtype
=> :float64
>> c = a+b
=> [1.0, 2.5707963267948966, 4.141592653589793 shape:[1,3] dtype:float64 stype:dense>
>> d = c*Complex(0,1)
=> [(0.0+1.0i), (0.0+2.5707963267948966i), (0.0+4.141592653589793i) shape:[1,3] dtype:complex128 stype:dense>
Many unary options, such as computing the sum of all the elements in the NMatrix, are implemented as methods of the NMatrix
class.
>> a = N.random([2,3])
=> [0.5441637193570046, 0.8916235693579796, 0.7244554237682101, 0.6617806999277982, 0.5792396150088834, 0.7118546436543527 shape:[2,3] dtype:float64 stype:dense
>> a.sum
=> [1.2059444192848028, 1.4708631843668631, 1.4363100674225628 shape:[1,3] dtype:float64 stype:dense>
>> a.min
=> [0.5441637193570046, 0.5792396150088834, 0.7118546436543527 shape:[1,3] dtype:float64 stype:dense>
>> a.max
=> [0.6617806999277982, 0.8916235693579796, 0.7244554237682101 shape:[1,3] dtype:float64 stype:dense>
By default, these operations are applied over the first dimension of the NMatrix. However, by specifying an optional parameter, you can apply an operation over the specified dimension of an NMatrix:
>> b = N.indgen([3,4])
=> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 shape:[3,4] dtype:int32 stype:dense>
>> b.sum(0)
=> [12, 15, 18, 21 shape:[1,4] dtype:int32 stype:dense>
>> b.sum(1)
=> [6, 22, 38 shape:[3,1] dtype:int32 stype:dense>