| Class | Matrix |
| In: |
lib/matrix.rb
|
| Parent: | Object |
The Matrix class represents a mathematical matrix, and provides methods for creating special-case matrices (zero, identity, diagonal, singular, vector), operating on them arithmetically and algebraically, and determining their mathematical properties (trace, rank, inverse, determinant).
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’. This may be fixed in the future.
To create a matrix:
To access Matrix elements/columns/rows/submatrices/properties:
Properties of a matrix:
Matrix arithmetic:
Matrix functions:
Conversion to other data types:
String representations:
| identity | -> | unit |
| identity | -> | I |
Creates a matrix where each argument is a row.
Matrix[ [25, 93], [-1, 66] ]
=> 25 93
-1 66
# File lib/matrix.rb, line 122
122: def Matrix.[](*rows)
123: new(:init_rows, rows, false)
124: end
Creates a single-column matrix where the values of that column are as given in column.
Matrix.column_vector([4,5,6])
=> 4
5
6
# File lib/matrix.rb, line 233
233: def Matrix.column_vector(column)
234: case column
235: when Vector
236: Matrix.columns([column.to_a])
237: when Array
238: Matrix.columns([column])
239: else
240: Matrix.columns([[column]])
241: end
242: end
Creates a matrix using columns as an array of column vectors.
Matrix.columns([[25, 93], [-1, 66]])
=> 25 -1
93 66
# File lib/matrix.rb, line 144
144: def Matrix.columns(columns)
145: rows = (0 .. columns[0].size - 1).collect {
146: |i|
147: (0 .. columns.size - 1).collect {
148: |j|
149: columns[j][i]
150: }
151: }
152: Matrix.rows(rows, false)
153: end
Creates a matrix where the diagonal elements are composed of values.
Matrix.diagonal(9, 5, -3)
=> 9 0 0
0 5 0
0 0 -3
# File lib/matrix.rb, line 162
162: def Matrix.diagonal(*values)
163: size = values.size
164: rows = (0 .. size - 1).collect {
165: |j|
166: row = Array.new(size).fill(0, 0, size)
167: row[j] = values[j]
168: row
169: }
170: rows(rows, false)
171: end
Creates an n by n identity matrix.
Matrix.identity(2)
=> 1 0
0 1
# File lib/matrix.rb, line 190
190: def Matrix.identity(n)
191: Matrix.scalar(n, 1)
192: end
This method is used by the other methods that create matrices, and is of no use to general users.
# File lib/matrix.rb, line 248
248: def initialize(init_method, *argv)
249: self.send(init_method, *argv)
250: end
Creates a single-row matrix where the values of that row are as given in row.
Matrix.row_vector([4,5,6])
=> 4 5 6
# File lib/matrix.rb, line 214
214: def Matrix.row_vector(row)
215: case row
216: when Vector
217: Matrix.rows([row.to_a], false)
218: when Array
219: Matrix.rows([row.dup], false)
220: else
221: Matrix.rows([[row]], false)
222: end
223: end
Creates a matrix where rows is an array of arrays, each of which is a row to the matrix. If the optional argument copy is false, use the given arrays as the internal structure of the matrix without copying.
Matrix.rows([[25, 93], [-1, 66]])
=> 25 93
-1 66
# File lib/matrix.rb, line 133
133: def Matrix.rows(rows, copy = true)
134: new(:init_rows, rows, copy)
135: end
Creates an n by n diagonal matrix where each diagonal element is value.
Matrix.scalar(2, 5)
=> 5 0
0 5
# File lib/matrix.rb, line 180
180: def Matrix.scalar(n, value)
181: Matrix.diagonal(*Array.new(n).fill(value, 0, n))
182: end
Creates an n by n zero matrix.
Matrix.zero(2)
=> 0 0
0 0
# File lib/matrix.rb, line 204
204: def Matrix.zero(n)
205: Matrix.scalar(n, 0)
206: end
Matrix multiplication.
Matrix[[2,4], [6,8]] * Matrix.identity(2)
=> 2 4
6 8
# File lib/matrix.rb, line 451
451: def *(m) # m is matrix or vector or number
452: case(m)
453: when Numeric
454: rows = @rows.collect {
455: |row|
456: row.collect {
457: |e|
458: e * m
459: }
460: }
461: return Matrix.rows(rows, false)
462: when Vector
463: m = Matrix.column_vector(m)
464: r = self * m
465: return r.column(0)
466: when Matrix
467: Matrix.Raise ErrDimensionMismatch if column_size != m.row_size
468:
469: rows = (0 .. row_size - 1).collect {
470: |i|
471: (0 .. m.column_size - 1).collect {
472: |j|
473: vij = 0
474: 0.upto(column_size - 1) do
475: |k|
476: vij += self[i, k] * m[k, j]
477: end
478: vij
479: }
480: }
481: return Matrix.rows(rows, false)
482: else
483: x, y = m.coerce(self)
484: return x * y
485: end
486: end
Matrix exponentiation. Defined for integer powers only. Equivalent to multiplying the matrix by itself N times.
Matrix[[7,6], [3,9]] ** 2
=> 67 96
48 99
# File lib/matrix.rb, line 638
638: def ** (other)
639: if other.kind_of?(Integer)
640: x = self
641: if other <= 0
642: x = self.inverse
643: return Matrix.identity(self.column_size) if other == 0
644: other = -other
645: end
646: z = x
647: n = other - 1
648: while n != 0
649: while (div, mod = n.divmod(2)
650: mod == 0)
651: x = x * x
652: n = div
653: end
654: z *= x
655: n -= 1
656: end
657: z
658: elsif other.kind_of?(Float) || defined?(Rational) && other.kind_of?(Rational)
659: Matrix.Raise ErrOperationNotDefined, "**"
660: else
661: Matrix.Raise ErrOperationNotDefined, "**"
662: end
663: end
Matrix addition.
Matrix.scalar(2,5) + Matrix[[1,0], [-4,7]]
=> 6 0
-4 12
# File lib/matrix.rb, line 494
494: def +(m)
495: case m
496: when Numeric
497: Matrix.Raise ErrOperationNotDefined, "+"
498: when Vector
499: m = Matrix.column_vector(m)
500: when Matrix
501: else
502: x, y = m.coerce(self)
503: return x + y
504: end
505:
506: Matrix.Raise ErrDimensionMismatch unless row_size == m.row_size and column_size == m.column_size
507:
508: rows = (0 .. row_size - 1).collect {
509: |i|
510: (0 .. column_size - 1).collect {
511: |j|
512: self[i, j] + m[i, j]
513: }
514: }
515: Matrix.rows(rows, false)
516: end
Matrix subtraction.
Matrix[[1,5], [4,2]] - Matrix[[9,3], [-4,1]]
=> -8 2
8 1
# File lib/matrix.rb, line 524
524: def -(m)
525: case m
526: when Numeric
527: Matrix.Raise ErrOperationNotDefined, "-"
528: when Vector
529: m = Matrix.column_vector(m)
530: when Matrix
531: else
532: x, y = m.coerce(self)
533: return x - y
534: end
535:
536: Matrix.Raise ErrDimensionMismatch unless row_size == m.row_size and column_size == m.column_size
537:
538: rows = (0 .. row_size - 1).collect {
539: |i|
540: (0 .. column_size - 1).collect {
541: |j|
542: self[i, j] - m[i, j]
543: }
544: }
545: Matrix.rows(rows, false)
546: end
Matrix division (multiplication by the inverse).
Matrix[[7,6], [3,9]] / Matrix[[2,9], [3,1]]
=> -7 1
-3 -6
# File lib/matrix.rb, line 554
554: def /(other)
555: case other
556: when Numeric
557: rows = @rows.collect {
558: |row|
559: row.collect {
560: |e|
561: e / other
562: }
563: }
564: return Matrix.rows(rows, false)
565: when Matrix
566: return self * other.inverse
567: else
568: x, y = other.coerce(self)
569: rerurn x / y
570: end
571: end
Returns true if and only if the two matrices contain equal elements.
# File lib/matrix.rb, line 400
400: def ==(other)
401: return false unless Matrix === other
402:
403: other.compare_by_row_vectors(@rows)
404: end
Returns element (i,j) of the matrix. That is: row i, column j.
# File lib/matrix.rb, line 265
265: def [](i, j)
266: @rows[i][j]
267: end
Returns a clone of the matrix, so that the contents of each do not reference identical objects.
# File lib/matrix.rb, line 424
424: def clone
425: Matrix.rows(@rows)
426: end
Returns a matrix that is the result of iteration of the given block over all elements of the matrix.
Matrix[ [1,2], [3,4] ].collect { |i| i**2 }
=> 1 4
9 16
# File lib/matrix.rb, line 327
327: def collect # :yield: e
328: rows = @rows.collect{|row| row.collect{|e| yield e}}
329: Matrix.rows(rows, false)
330: end
Returns column vector number j of the matrix as a Vector (starting at 0 like an array). When a block is given, the elements of that vector are iterated.
# File lib/matrix.rb, line 305
305: def column(j) # :yield: e
306: if block_given?
307: 0.upto(row_size - 1) do
308: |i|
309: yield @rows[i][j]
310: end
311: else
312: col = (0 .. row_size - 1).collect {
313: |i|
314: @rows[i][j]
315: }
316: Vector.elements(col, false)
317: end
318: end
Returns the number of columns. Note that it is possible to construct a matrix with uneven columns (e.g. Matrix[ [1,2,3], [4,5] ]), but this is mathematically unsound. This method uses the first row to determine the result.
# File lib/matrix.rb, line 282
282: def column_size
283: @rows[0].size
284: end
Not really intended for general consumption.
# File lib/matrix.rb, line 410
410: def compare_by_row_vectors(rows)
411: return false unless @rows.size == rows.size
412:
413: 0.upto(@rows.size - 1) do
414: |i|
415: return false unless @rows[i] == rows[i]
416: end
417: true
418: end
Returns the determinant of the matrix. If the matrix is not square, the result is 0.
Matrix[[7,6], [3,9]].determinant
=> 63
# File lib/matrix.rb, line 675
675: def determinant
676: return 0 unless square?
677:
678: size = row_size - 1
679: a = to_a
680:
681: det = 1
682: k = 0
683: begin
684: if (akk = a[k][k]) == 0
685: i = k
686: begin
687: return 0 if (i += 1) > size
688: end while a[i][k] == 0
689: a[i], a[k] = a[k], a[i]
690: akk = a[k][k]
691: det *= -1
692: end
693: (k + 1).upto(size) do
694: |i|
695: q = a[i][k] / akk
696: (k + 1).upto(size) do
697: |j|
698: a[i][j] -= a[k][j] * q
699: end
700: end
701: det *= akk
702: end while (k += 1) <= size
703: det
704: end
Returns a hash-code for the matrix.
# File lib/matrix.rb, line 431
431: def hash
432: value = 0
433: for row in @rows
434: for e in row
435: value ^= e.hash
436: end
437: end
438: return value
439: end
Overrides Object#inspect
# File lib/matrix.rb, line 864
864: def inspect
865: "Matrix"+@rows.inspect
866: end
Returns the inverse of the matrix.
Matrix[[1, 2], [2, 1]].inverse
=> -1 1
0 -1
# File lib/matrix.rb, line 579
579: def inverse
580: Matrix.Raise ErrDimensionMismatch unless square?
581: Matrix.I(row_size).inverse_from(self)
582: end
Not for public consumption?
# File lib/matrix.rb, line 588
588: def inverse_from(src)
589: size = row_size - 1
590: a = src.to_a
591:
592: for k in 0..size
593: if (akk = a[k][k]) == 0
594: i = k
595: begin
596: Matrix.Raise ErrNotRegular if (i += 1) > size
597: end while a[i][k] == 0
598: a[i], a[k] = a[k], a[i]
599: @rows[i], @rows[k] = @rows[k], @rows[i]
600: akk = a[k][k]
601: end
602:
603: for i in 0 .. size
604: next if i == k
605: q = a[i][k] / akk
606: a[i][k] = 0
607:
608: (k + 1).upto(size) do
609: |j|
610: a[i][j] -= a[k][j] * q
611: end
612: 0.upto(size) do
613: |j|
614: @rows[i][j] -= @rows[k][j] * q
615: end
616: end
617:
618: (k + 1).upto(size) do
619: |j|
620: a[k][j] /= akk
621: end
622: 0.upto(size) do
623: |j|
624: @rows[k][j] /= akk
625: end
626: end
627: self
628: end
Returns a section of the matrix. The parameters are either:
Matrix.diagonal(9, 5, -3).minor(0..1, 0..2)
=> 9 0 0
0 5 0
# File lib/matrix.rb, line 342
342: def minor(*param)
343: case param.size
344: when 2
345: from_row = param[0].first
346: size_row = param[0].end - from_row
347: size_row += 1 unless param[0].exclude_end?
348: from_col = param[1].first
349: size_col = param[1].end - from_col
350: size_col += 1 unless param[1].exclude_end?
351: when 4
352: from_row = param[0]
353: size_row = param[1]
354: from_col = param[2]
355: size_col = param[3]
356: else
357: Matrix.Raise ArgumentError, param.inspect
358: end
359:
360: rows = @rows[from_row, size_row].collect{
361: |row|
362: row[from_col, size_col]
363: }
364: Matrix.rows(rows, false)
365: end
Returns the rank of the matrix. Beware that using Float values, with their usual lack of precision, can affect the value returned by this method. Use Rational values instead if this is important to you.
Matrix[[7,6], [3,9]].rank
=> 2
# File lib/matrix.rb, line 714
714: def rank
715: if column_size > row_size
716: a = transpose.to_a
717: a_column_size = row_size
718: a_row_size = column_size
719: else
720: a = to_a
721: a_column_size = column_size
722: a_row_size = row_size
723: end
724: rank = 0
725: k = 0
726: begin
727: if (akk = a[k][k]) == 0
728: i = k
729: exists = true
730: begin
731: if (i += 1) > a_column_size - 1
732: exists = false
733: break
734: end
735: end while a[i][k] == 0
736: if exists
737: a[i], a[k] = a[k], a[i]
738: akk = a[k][k]
739: else
740: i = k
741: exists = true
742: begin
743: if (i += 1) > a_row_size - 1
744: exists = false
745: break
746: end
747: end while a[k][i] == 0
748: if exists
749: k.upto(a_column_size - 1) do
750: |j|
751: a[j][k], a[j][i] = a[j][i], a[j][k]
752: end
753: akk = a[k][k]
754: else
755: next
756: end
757: end
758: end
759: (k + 1).upto(a_row_size - 1) do
760: |i|
761: q = a[i][k] / akk
762: (k + 1).upto(a_column_size - 1) do
763: |j|
764: a[i][j] -= a[k][j] * q
765: end
766: end
767: rank += 1
768: end while (k += 1) <= a_column_size - 1
769: return rank
770: end
Returns true if this is a regular matrix.
# File lib/matrix.rb, line 374
374: def regular?
375: square? and rank == column_size
376: end
Returns row vector number i of the matrix as a Vector (starting at 0 like an array). When a block is given, the elements of that vector are iterated.
# File lib/matrix.rb, line 290
290: def row(i) # :yield: e
291: if block_given?
292: for e in @rows[i]
293: yield e
294: end
295: else
296: Vector.elements(@rows[i])
297: end
298: end
Returns the number of rows.
# File lib/matrix.rb, line 272
272: def row_size
273: @rows.size
274: end
Returns true is this is a singular (i.e. non-regular) matrix.
# File lib/matrix.rb, line 381
381: def singular?
382: not regular?
383: end
Returns true is this is a square matrix. See note in column_size about this being unreliable, though.
# File lib/matrix.rb, line 389
389: def square?
390: column_size == row_size
391: end
Returns an array of arrays that describe the rows of the matrix.
# File lib/matrix.rb, line 843
843: def to_a
844: @rows.collect{|row| row.collect{|e| e}}
845: end
Overrides Object#to_s
# File lib/matrix.rb, line 854
854: def to_s
855: "Matrix[" + @rows.collect{
856: |row|
857: "[" + row.collect{|e| e.to_s}.join(", ") + "]"
858: }.join(", ")+"]"
859: end
Returns the trace (sum of diagonal elements) of the matrix.
Matrix[[7,6], [3,9]].trace
=> 16
# File lib/matrix.rb, line 777
777: def trace
778: tr = 0
779: 0.upto(column_size - 1) do
780: |i|
781: tr += @rows[i][i]
782: end
783: tr
784: end
Returns the transpose of the matrix.
Matrix[[1,2], [3,4], [5,6]]
=> 1 2
3 4
5 6
Matrix[[1,2], [3,4], [5,6]].transpose
=> 1 3 5
2 4 6
# File lib/matrix.rb, line 797
797: def transpose
798: Matrix.columns(@rows)
799: end