As of this writing, NMatrix v0.0.9 is available on RubyGems. It is likely that the next version will be a beta release
candidate, as there’s only one critical feature still missing (
== between matrices of different storage types).
An enormous amount has changed since my last entry.
New, friendlier constructor
First and foremost, NMatrix sports a new constructor, based on helpful comments from folks on the listserv. Here are some examples of construction:
NMatrix.new([4,4], 0) # 4x4 dense matrix of :int32, all 0 NMatrix.new([4,4], 0.0) # 4x4 dense matrix of :float64, all 0.0 NMatrix.new([4,4], 0.0, dtype: :complex64) # 4x4 dense matrix of :complex64 NMatrix.new([1,4], stype: :yale) # size 4 sparse (Yale) row vector of 0s NMatrix.new([4,3], [0,1,2]) # 4 rows, 3 columns: gradient across each row from 0 to 2 (int32) NMatrix.new([4,3], [0,1,2], stype: :yale, default: 0) # same as above, but Yale storage (int32) NMatrix.new([4,1], stype: :list, dtype: :rational128) # size 4 sparse (list) column vector of rational 0s NMatrix.new([4,4]) # 4x4 dense matrix containing nils NMatrix.new([4,4], dtype: :int64) # 4x4 uninitialized dense matrix containing 64-bit integers NMatrix.new(4) # same as above
The different storage types (stypes) have slightly different behaviors when no initialization value is provided. This may change in the future, but addresses the somewhat different use-cases of these storage types.
I show a variety of examples above, several of which are not particularly wise uses — for example, the
gradient, which uses 32-bit integers and must store 11 column indices and pointers (which are most likely unsigned
long integers) in addition to the 11 entries (4 for the always-stored diagonal, 1 for the default, and 6 for the
The key to understanding the differing construction is the default value. All sparse matrices need a default. Usually we think of sparse matrices as being zero-based (only non-zero values are stored), but NMatrix now allows other defaults. However, dense matrices don’t need defaults, and in fact sometimes it’s more efficient to allocate them without initializing them — such as if they’re about to store the results of some LAPACK function call. But if they contain Ruby objects, they have to be initialized, which is why they become nil.
We’re still working out a few bugs in construction. If you find any, please report them in our issue tracker.
Elimination of NVectors
We removed the NVector class. Frankly, it didn’t make sense. A vector can’t have an orientation unless it has multiple dimensions — even if some of those dimensions have length 1. Now, vectors and matrices are treated the same in the code.
The good news is that element access (
) has been rewritten so that the programmer only needs provide the coordinates
whose dimensions are not 1. For example:
n = NMatrix.new([4,1,3], 0) # 3-dimensional matrix of 0s n[2,1] == n[2,0,1] => true
The same rule applies to slicing.
Aleksey Timin contributed the framework for matrix slicing. Matrices can be sliced by copying or by reference. Modifying a copy-slice does not modify the original matrix; but modifying a reference slice does.
A copy of some portion of the matrix may be made using
new_matrix = n.slice(0..1,0..2)
And what appears to be a copy, but is actually a reference, may be made using brackets:
ref = n[0..1,0..2]
Reference slices may also be modified in a variety of ways:
n[0..4,2..8] = 0 # change all entries to 0 n[0..4,2..8] = [0,1,2] # change the selected entries to [0,1,2,0,1,2,0,1,2...] n[0..4,2..8] = NMatrix.new([3,3], [0,1,2]) # same as above
For the sake of speed, each of these
= returns the right-hand value, whether that value is an array, matrix, or
single value. So, you can do this:
x = m[0..4,2..8] = n[0..1,0..2] = [1,2,3]
x will be equal to
[1,2,3] after the evaluation.
Matrices are now enumerable. There are a variety of enumerators — namely
each_ordered_stored_with_indices. The first,
each, is guaranteed to produce the same iteration regardless of the
storage type. The other two iterate only across the stored entries.
A regular matrix comparison, returning a single boolean, can be accomplished using
!=. In earlier versions of
NMatrix, the results of the element-wise versions,
<=, were matrices of 0s and 1s,
stored using the
Now, these comparisons return matrices of Ruby objects:
n < m => [ true, false, false, true, false, false, false, true, true, true, true, true ]
Try experimenting with sparse matrices to see how the default value (
#default_value) is initialized on the result during an
I’m really excited about all of the chunky bacon in our latest release. I feel like things are really coming together for our library. I’m also glad to see that people are using it.
If you’re thinking of using NMatrix yourself, I strongly encourage it. Although I’m writing my dissertation, I plan to prioritize troubleshooting ahead of just about everything else. I want using NMatrix to be an easy choice for every Rubyist.
Thanks for reading!