Ruby Matrix, the Forgotten Library

by on April 4, 2013

Ruby Matrix, the Forgotten Library

This guest post is contributed by Matthew Kirk, who is a partner at Modulus 7, specializing in software development and strategy. The basis of his career has been around utilizing science to improve businesses. He has spoken at technology conferences around the world and in his spare time, he enjoys traveling and adding to his 2000+ vinyl record collection.

Matthew Kirk Remember matrices from math class? No not the movie, but the rectangular array of numbers. While you might not see it often, Ruby has a matrix implementation that is well tested and allows you to accomplish tough calculations quickly.

While I won’t be able to teach you everything there is to be known about matrices, we will cover how to use matrices within Ruby as well as some quirks and their major selling points. By the end of this I hope that you delve deeper into learning about matrices and use them in your next project.

What are matrices?

A matrix according to Wikipedia is a rectangular array of numbers. Used heavily in math, matrices are all over languages like R and Matlab. They can be a great way to store numerical data and simplify many difficult and tedious problems. Instead of solving systems of equations matrices can simplify these into one equation.

In terms of how Ruby implements matrices, Ruby stores all matrix rows into one big array. The only requirement is that the arrays are of the same dimension. So for each row that is added to a matrix each one must be of the same size.

Just like arrays, matrices are zero indexed meaning that the first row is index 0 and the first column is index 0. Unlike arrays though you have to have two indexes to get to an element:

Making matrices:

Some quirks:

The Matrix library has some quirks. The Matrix class allows non-numerical data to go into itself. This could be useful for storing things like symbols in a more x, y format but render most of the matrix functions useless.

For instance:

Another quirk to be aware of: Matrix[*rows] does not copy the rows objects but instead points to it. To avoid this use Matrix.rows(rows) or Matrix.columns(columns). Implementation wise Matrix[*rows] calls the function Matrix.rows(rows, copy = false).

Iterating over matrices:

How do you iterate over a matrix? Most likely you would think that matrices read left to right top to bottom. And that’s true. But there are other cases as well.

In total there are 7 ways to iterate over a Matrix in ruby:

  • :all This reads left to right top to bottom. This is the default case when you type matrix.each
  • :diagonal: This only reads the diagonal elements or row index == column index
  • :off_diagonal: This will read everything not on the diagonal or row index != column index
  • :lower: This reads the lower triangle of the matrix or row index <= column index
  • :strict_lower: this is more strict and reads only row index < column index
  • :strict_upper: this is a strict upper triangle and is row index > column index
  • :upper: row index >= column index

An example:

Example: Parabola with matrix:

Imagine you want to fit a curve through three points. If you remember math class you might remember that you can do this by fitting a quadratic. For instance lets say we want a line that goes through (1,2) (3, 5.5) and (6, 6). To solve this we would write the equations:

Math equation

The way to solve this would usually involve lots of algebra and substitutions. While it’s easy to solve this in the case where we already know the numbers it is difficult to come up with a general solution (try it I dare you).

Instead of worrying about solving this using non-matrix algebra we can solve it using matrix algebra. The first step is to rewrite the above system into this form:

Math equation

To make it even easier to solve we would rewrite this as Ax = b. To solve this we would take the inverse of A and then multiply both sides by that. Which would yield:

Math equation

Now that we know this, we can easily solve this using Ruby with the following formula.

While it’s close it won’t be correct unless you use Rational. Ruby’s matrix library graciously utilizes functions that preserve precision. You would expect most libraries to convert to floats but ruby does not.

For instance you can change the above function call to:

Whenever possible try to preserve the precision!

The general case, fitting an n-power polynomial to n points:

Up above we only fit this curve to 3 points. But what about 4 or 15 points? This would be quite simple to do and would only require a little bit of modification:

Conclusion:

While you might not use matrices every day, they can be useful to solve problems involving systems of equations. Ruby has a robust matrix library that can be useful in finding solutions to these types of problems. Next time you want to fit a curve keep in mind matrices might be the best way to go!

For more information about matrices I recommend reading Wikipedia articles. There are lots of math professors who spend hours updating them tediously. If they are too confusing, think about picking up a book on matrix algebra like Matrix Computations.

Feel free to ask questions and give feedback in the comments section of this post. Thanks!

Technorati Tags: , , ,

Posted by Matthew Kirk

Follow me on Twitter to communicate and stay connected

{ 15 comments… read them below or add one }

David Chapman April 4, 2013 at 10:40 am

Can it handle complex number? I’m sure with a little help from ‘mathn’ it could. Is this already a thing?

Reply

Matthew Kirk April 4, 2013 at 8:29 pm

Yes it can! The Matrix class is pretty robust. Do a search for “Complex Arithmetic” there’s quite a few functions in there for determining a real matrix, conjugate etc. :)

Reply

Pavel April 4, 2013 at 11:04 am

What about performance?

In my experience Vector and Matrix worked much slower then plain arrays

Reply

asaaki April 4, 2013 at 2:27 pm

If you would use Vector/Matrix just the same way you do with Array then there is no performance boost, because Vector and Matrix are implemented in pure Ruby while in MRI Array is implemented in C. In fact Matrix uses Array internally.

But if you use Matrix for matrix computations you should use this type unless you pretty sure that you can write better code with plain arrays leading to same results. I guess for complex matrix transformation people already implemented good Ruby code in Matrix. And other people like me rely on their work because I’m not a matrix crack.

In my experience I can say that Ruby isn’t used for heavy math usually, so there were never a real focus on performance in these cases. Improvements are welcome!

Reply

Matthew Kirk April 4, 2013 at 8:33 pm

I agree totally with what asaaki said. There will probably be a performance degradation due to the ruby overhead. But I would be curious how this works in Rubinius or JRuby, which seem to work better with plain ruby.

Reply

Marc-André Lafortune April 4, 2013 at 9:42 pm

To supplement Matthew’s and asaaki’s answers, the main performance penalty is when creating a Matrix: duplicating the arrays and checking of the arguments (i.e. that it’s rectangular). It’s a very light penalty and of course none of this is done when the library itself calculates new matrices.

Everything else should be pretty much as fast than using arrays. Let me know if you see potential improvements!

Reply

Philippe April 4, 2013 at 8:55 pm

Is it limited to 2 dimensions?

Reply

Matthew Kirk April 4, 2013 at 9:04 pm

Yes matrices are limited to 2 dimensions. There are other data structures out there like Cubes but they can be quite difficult to utilize and think about… Hope that helps.

Reply

Marc-André Lafortune April 4, 2013 at 9:26 pm

Great writeup!
The fact that `Matrix[*rows]` doesn’t copy the rows is a bug. I’ll fix it the next Ruby version.
You might want to mention that for other dimensions, or if performance is a must, one can look into the `NArray` lib.

Reply

Matthew Kirk April 4, 2013 at 9:35 pm

Really? When I read the source code I noticed that Matrix[*rows] just delegates to Matrix.rows(rows, false) I was somewhat confused when I saw that since it seems like an odd choice. Thanks for clarifying :).

NArray is great for speed! I usually turn to it after I notice slow speeds due to Matrix or Array. To be honest though that doesn’t happen all the time.

Reply

rkulla April 5, 2013 at 3:49 am

Interesting, so to build a 256×256 grid in matrix and containing all 0′s by default, you can just do:

grid = Matrix.build(256, 256) {|row, col| 0}
# or grid = Matrix.build(256) {|row, col| 0} since rows and cols are the same size

? instead of:

grid = Array.new(256) { Array.new(256, 0) }

Same amount of code but the Matrix one is clearer. Then to access row 5, col 20:

grid[4, 19]

Whereas with the Array only version, you’d do:

grid[4][19]

However, that at least lets you change the value with simply:

grid[4][19] = n

It’s worth noting that Matrix matrices are immutable!

Reply

Rodrigo DK April 8, 2013 at 1:25 am

Yeah, I remember having a hard time using matrixes years ago to make a homework from my graph theory class in ruby.

I’ve reverted to multiarrays in the end due to the immutable nature of ruby matrixes.

Copying the entire matrix to simulate mutability was slower and harder than using arrays.

So this is less powerful than my Casio calculator. :P

Reply

RailsCarma April 13, 2013 at 7:12 pm

Note that although matrices should theoretically be rectangular, this is not enforced by the class.

Also note that the determinant of integer matrices may be incorrectly calculated unless you also require ‘mathn’.

Reply

Matthew Kirk April 14, 2013 at 6:50 am

when I try m = Matrix[[1,2],[2]] it throws a ErrDimensionMismatch error so it does check rectangularity. Good point with mathn, since it defers all round offs it will calculate the determinant and everything else with much more accuracy.

Reply

Marc-André Lafortune April 14, 2013 at 12:09 pm

Both of these statements used to be the case, but I’ve fixed these more than 3 years ago. The determinant is always calculated correctly, even for integer matrices (see http://bugs.ruby-lang.org/issues/show/2772 ). As Matthew notes, dimensions of arguments are checked (i.e. matrices must be rectangular).

Reply

Leave a Comment

Previous post:

Next post: