SciRuby

Tools for Scientific Computing in Ruby

Tensors Using NumRuby

Tensor word can have slightly different meaning depending upon the nature of study, like it’s defined differently in Physics, slightly different in computer science. In computer terms, it is basically a n-dimensional array. A scalar (one value) is a tensor with 0 dimensions, a vector is a tensor with 1 dimension, a matrix is a tensor with 2 dimensions.

It’s possible to define a tensor in Ruby using the Array class of Ruby but it gets tedious when defining multi-dimensional tensors. Also, the Array object is designed to be heterogeneous which means that the elements of the array can be of different type or different classes which would seem as a plus point overall but it has a huge downside. Due to Array being heterogeneous, the memory allocation has to be in such a way that any element of any size can be added or removed from the array, which causes a lot of re-allocations. Also, the indexing and other array functions gets slower due to the heterogeneous nature.

What if there’s a scenario where there is only one type of tensor elements, which means that a homogeneous array would also do, and there are memory and speed constraints? NumRuby is the solution for such requirements.

Tensors in NumRuby

A tensor can be defined using the NMatrix object of NumRuby.

1
N = NMatrix.new [shape], [elements], :type

shape is the number of dimensions and size of each dimension of the tensor. For example, [2, 2, 2] shape tensor is a tensor with 3 dimensions and each dimension of size 2, hence number of elements is 8. A sample value of elements array for this could be [1, 2, 3, 4, 5, 6, 7, 8]. type is the data type of each of the tensor element, it could be any of :nm_bool, :nm_int, :nm_float32, :nm_float64, :nm_complex32 or :nm_complex64 depending on the requirements.

An example usage is shown below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[1] pry(main)> N = NMatrix.new [2, 2], [1, 2, 3, 4], :nm_int
=>
[
 [1, 2],
 [3, 4]
]
[2] pry(main)> N.elements
=> [1, 2, 3, 4]
[3] pry(main)> N.shape
=> [2, 2]
[4] pry(main)> N[0, 0]
=> 1
[5] pry(main)> N[1, 0]
=> 3
[6] pry(main)> N.dtype
=> :nm_int

Elementwise Operations

One can also perform elementwise operations using NumRuby. Elementwise operations are broadly of 2 types, Uni-operand and bi-operand.

Uni-Operand Operations

Uni-operand operators are those that apply to just one tensor. For example, sine, cos or tan of each of element of the tensor.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[1] pry(main)> N = NMatrix.new [2, 2], [1, 2, 3, 4], :nm_float64
=>
[
 [1.0, 2.0],
 [3.0, 4.0]
]
[2] pry(main)> N.sin
=>
[
 [0.8414709848078965,  0.9092974268256817],
 [0.1411200080598672, -0.7568024953079282]
]
[3] pry(main)> N.cos
=>
[
 [ 0.5403023058681398, -0.4161468365471424],
 [-0.9899924966004454, -0.6536436208636119]
]

Bi-Operand Operations

Bi-operand operators are those that apply to two tensor. For example, addition, subtraction or multiplication of each of the corresponding elements of the the 2 tensor.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[1] pry(main)> N = NMatrix.new [2, 2], [1, 2, 3, 4], :nm_float64
=>
[
 [1.0, 2.0],
 [3.0, 4.0]
]
[2] pry(main)> M = NMatrix.new [2, 2], [2, 2, 3, 3], :nm_float64
=>
[
 [2.0, 2.0],
 [3.0, 3.0]
]
[3] pry(main)> N+M
=>
[
 [3.0, 4.0],
 [6.0, 7.0]
]
[4] pry(main)> N*M
=>
[
 [2.0,  4.0],
 [9.0, 12.0]
]

Linear Algebra

NumRuby also supports linear algebra capabilities for 2-dimensional tensors. One can easily do operations such as matrix inverse, dot product, matrix decompositions.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
[1] pry(main)> N = NMatrix.new [2, 2], [1, 2, 3, 4], :nm_float64
=>
[
 [1.0, 2.0],
 [3.0, 4.0]
]
[2] pry(main)> M = NMatrix.new [2, 2], [2, 2, 3, 3], :nm_float64
=>
[
 [2.0, 2.0],
 [3.0, 3.0]
]
[3] pry(main)> N.dot(M)
=>
[
 [ 8.0,  8.0],
 [18.0, 18.0]
]
[4] pry(main)> N.invert
=>
[
 [-1.9999999999999996,  0.9999999999999998],
 [ 1.4999999999999998, -0.4999999999999999]
]
[5] pry(main)> N.det
=> -2.0

Comments