Warning: Code in this blog post is very old and likely will not work with the current version of NMatrix. Please check the NMatrix wiki for the most recent information.
Two months ago, I mentioned the existence of a prototype Ruby linear algebra library, written in C.
I am pleased to announce that yesterday we released our first alpha of said library, NMatrix v0.0.1.
Creating Matrices
There are lots of different ways to create matrices. The first and easiest is to supply dimensions and initial data:
>> n = NMatrix.new(4, 0) # a square 4x4 dense zero matrix
=> #<NMatrix:0x9a57e14shape:[4,4] dtype:int32 stype:dense>
>> n.pretty_print
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
=> nil
Data Types
You may notice that this first matrix defaulted to dtype=:int32. NMatrix will try to guess the dtype based on the first initial value you provide (e.g., NMatrix.new(4, [0.0, 1]) will be :float32), but you can choose to provide a dtype in addition to or in lieu of initial values:
>> n = NMatrix.new(4, [0,1], :rational128)
=> #<NMatrix:0x9959e04shape:[4,4] dtype:rational128 stype:dense>
>> n.pretty_print
0/1 1/1 0/1 1/1
0/1 1/1 0/1 1/1
0/1 1/1 0/1 1/1
0/1 1/1 0/1 1/1
>> m = NMatrix.new(4, :int64) # no initialization of values
=> #<NMatrix:0x99fad68shape:[4,4] dtype:int64 stype:dense>
>> m.pretty_print
-1217641248 161386160 161386100 161385680
161385640 161385520 161385420 161384180
161384120 161381060 161381020 161412140
161411940 161411880 161411840 160879240
=> nil
Storage Formats
The storage type (stype) can also be specified, prior to the dimension argument. However, with sparse storage formats, initial values don’t make sense, and these matrices will contain zeros by default.
# empty list-of-lists-of-lists 4x3x4 matrix
n = NMatrix.new(:list, [4,3,4], :int64)
# Ruby objects in a 'Yale' sparse matrix
m = NMatrix.new(:yale, [5,4], :object)
# A byte matrix containing a gradient
o = NMatrix.new(:dense, 5, [0,1,2,3,4], :byte)
The matrix m created above is a Yale-format sparse matrix, or more specifically, “new Yale,” which differs from “old Yale” in that the diagonal is stored separately from the non-diagonal elements. Thus, diagonals can be accessed and set in constant time.
Currently, all storage is row-based.
Conversion
You can also convert between any of these three stypes using cast, e.g.,
n = NMatrix.new(:list, 4, :int64)
n[0,0] = 5
n[0,3] = -2
dense = n.cast(:dense, :int64)
Vectors
Currently, only dense vectors are implemented as a child class of NMatrix, and creation is similar:
>> nv = NVector.new(5, :int64)
=> #<NVector:0x9a62328shape:[5,1] dtype:int64 stype:dense orientation:column>
Math Operations
Most element-wise mathematical operations are supported for Yale and dense types. These use the basic operators (e.g., +, -, /, *, ==).
For non-element-wise matrix multiplication, use the dot instance method of NMatrix. Whole-matrix comparison (returning a single boolean value) is the equal? or eql? method.
Road Map
Much more remains to be written than has been completed. Here are some of our key priorities:
- determinants
- matrix-vector multiplication for Yale
- adaptation of SciRuby Matlab file reader to support NMatrix
- in-place transposition
If you want to get involved, I suggest visiting the NMatrix issue tracker. It will contain not only bugs, but also features that need to be implemented.
Conclusion
We’re all pretty excited about NMatrix. But we couldn’t have gotten this far without Masahiro Tanaka’s NArray, which has served as a model for our library.
And we can’t do it without your help. A numerical library in Ruby is no small endeavour, and probably requires at least one full-time programmer (which we do not have). Please consider contributing, even if it’s just by letting us know what you think.
Happy coding!