

5.10. Matrix ManipulationIf you want to deal with numerical matrices, the standard library matrix is for you. This actually defines two separate classes, Matrix and Vector. You should also be aware of the excellent NArray library by Masahiro Tanaka (which can be found at www.rubyforge.org). This is not a standard library but is wellknown and very useful. If you have speed requirements, if you have specific data representation needs, or if you need capabilities such as Fast Fourier Transform, you should definitely look into this package. For most general purposes, however, the standard matrix library should suffice, and that is what we cover here. To create a matrix, we naturally use a classlevel method. There are multiple ways to do this. One way is simply to call Matrix.[] and list the rows as arrays. In the following example we do this in multiple lines, though of course that isn't necessary: m = Matrix[[1,2,3], [4,5,6], [7,8,9]] A similar method is to call rows, passing in an array of arrays (so that the "extra" brackets are necessary). The optional copy parameter, which defaults to true, determines whether the individual arrays are copied or simply stored. Therefore, let this parameter be true to protect the original arrays, or false if you want to save a little memory, and you are not concerned about this issue. row1 = [2,3] row2 = [4,5] m1 = Matrix.rows([row1,row2]) # copy=true m2 = Matrix.rows([row1,row2],false) # don't copy row1[1] = 99 # Now change row1 p m1 # Matrix[[2, 3], [4, 5]] p m2 # Matrix[[2, 99], [4, 5]] Matrices can similarly be specified in column order with the columns method. It does not accept the copy parameter because the arrays are split up anyway to be stored internally in rowmajor order. m1 = Matrix.rows([[1,2],[3,4]]) m2 = Matrix.columns([[1,3],[2,4]]) # m1 == m2 Matrices are assumed to be rectangular, but the code does not enforce this. If you assign a matrix with rows or columns that are shorter or longer than the others, you may naturally get errors or unusual results later. Certain special matrices, especially square ones, are easily constructed. The "identity" matrix can be constructed with the identity method (or its aliases I and unit): im1 = Matrix.identity(3) # Matrix[[1,0,0],[0,1,0],[0,0,1]] im2 = Matrix.I(3) # same im3 = Matrix.unit(3) # same A more general form is scalar, which assigns some value other than 1 to the diagonal: sm = Matrix.scalar(3,8) # Matrix[[8,0,0],[0,8,0],[0,0,8]] Still more general is the diagonal, which assigns an arbitrary sequence of values to the diagonal. (Obviously, it does not need the dimension parameter.) dm = Matrix.diagonal(2,3,7) # Matrix[[2,0,0],[0,3,0],[0,0,7]] The zero method creates a special matrix of the specified dimension, full of zero values: zm = Matrix.zero(3) # Matrix[[0,0,0],[0,0,0],[0,0,0]] Obviously, the identity, scalar, diagonal, and zero methods all construct square matrices. To create a 1xN or an Nx1 matrix, you can use the row_vector or column_vector shortcut methods, respectively: a = Matrix.row_vector(2,4,6,8) # Matrix[[2,4,6,8]] b = Matrix.column_vector(6,7,8,9) # Matrix[[6],[7],[8],[9]] Individual matrix elements can naturally be accessed with the bracket notation (with both indices specified in a single pair of brackets). Note that there is no []= method. This is for much the same reason that Fixnum lacks that method: Matrices are immutable objects (evidently a design decision by the library author). m = Matrix[[1,2,3],[4,5,6]] puts m[1,2] # 6 Be aware that indexing is from 0 as with Ruby arrays; this may contradict your mathematical expectation, but there is no option for 1based rows and columns unless you implement it yourself. # Naive approach... don't do this! class Matrix alias bracket [] def [](i,j) bracket(i1,j1) end end m = Matrix[[1,2,3],[4,5,6],[7,8,9]] p m[2,2] # 5 The preceding code does seem to work. Many or most matrix operations still behave as expected with the alternate indexing. Why might it fail? Because we don't know all the internal implementation details of the Matrix class. If it always uses its own [] method to access the matrix values, it should always be consistent. But if it ever accesses some internal array directly or uses some kind of shortcut, it might fail. Therefore if you use this kind of trick at all, it should be with caution and testing. In reality, you would have to change the row and vector methods as well. These methods use indices that number from zero without going through the [] method. I haven't checked to see what else might be required. Sometimes we need to discover the dimensions or shape of a matrix. There are various methods for this purpose, such as row_size and column_size. Let's look at these. The row_size method returns the number of rows in the matrix. The column_size method comes with a caveat, however: It checks the size of the first row only. If your matrix is for some reason not rectangular, this may not be meaningful. Furthermore, because the square? method calls these other two, it may not be reliable. m1 = Matrix[[1,2,3],[4,5,6],[7,8,9]] m2 = Matrix[[1,2,3],[4,5,6],[7,8]] m1.row_size # 3 m1.column_size # 3 m2.row_size # 3 m2.column_size # 3 (misleading) m1.square? # true m2.square? # true (incorrect) One answer to this minor problem would be to define a rectangular? method. class Matrix def rectangular? arr = to_a first = arr[0].size arr[1..1].all? {x x.size == first } end end You could, of course, modify square? to check first for a rectangular matrix. In that case, you might want to modify column_size to return nil for a nonrectangular matrix. To retrieve a section or piece of a matrix, several methods are available. The row_vectors method returns an array of Vector objects representing the rows of the matrix. (See the following discussion of the Vector class.) The column_vectors method works similarly. Finally, the minor method returns a smaller matrix from the larger one; its parameters are either four numbers (lower and upper bounds for the rows and columns) or two ranges. m = Matrix[[1,2,3,4],[5,6,7,8],[6,7,8,9]] rows = m.row_vectors # three Vector objects cols = m.column_vectors # four Vector objects m2 = m.minor(1,2,1,2) # Matrix[[6,7,],[7,8]] m3 = m.minor(0..1,1..3) # Matrix[[[2,3,4],[6,7,8]] The usual matrix operations can be applied: addition, subtraction, multiplication, and division. Some of these make certain assumptions about the dimensions of the matrices and may raise exceptions if the operands are incompatible (for example, trying to multiply a 3x3 matrix with a 4x4 matrix). Ordinary transformations such as inverse, transpose, and determinant are supported. For matrices of integers, the determinant will usually be better behaved if the mathn library is used (see the section 5.12 "Using mathn"). A Vector is in effect a special onedimensional matrix. It can be created with the [] or elements methods; the first takes an expanded array, and the latter takes an unexpanded array and an optional copy parameter (which defaults to true). arr = [2,3,4,5] v1 = Vector[*arr] # Vector[2,3,4,5] v2 = Vector.elements(arr) # Vector[2,3,4,5] v3 = Vector.elements(arr,false) # Vector[2,3,4,5] arr[2] = 7 # v3 is now Vector[2,3,7,5] The covector method converts a vector of length N to an Nx1 (effectively transposed) matrix. v = Vector[2,3,4] m = v.covector # Matrix[[2,3,4]] Addition and subtraction of similar vectors is supported. A vector may be multiplied by a matrix or by a scalar. All these operations are subject to normal mathematical rules. v1 = Vector[2,3,4] v2 = Vector[4,5,6] v3 = v1 + v2 # Vector[6,8,10] v4 = v1*v2.covector # Matrix[[8,10,12],[12,15,18],[16,20,24]] v5 = v1*5 # Vector[10,15,20] There is an inner_product method: v1 = Vector[2,3,4] v2 = Vector[4,5,6] x = v1.inner_product(v2) # 47 For additional information on the Matrix and Vector classes, go to any reference such as the ri commandline tool or the rubydoc.org website. 

