| 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 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
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
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 644
644: def ** (other)
645: if other.kind_of?(Integer)
646: x = self
647: if other <= 0
648: x = self.inverse
649: return Matrix.identity(self.column_size) if other == 0
650: other = -other
651: end
652: z = x
653: n = other - 1
654: while n != 0
655: while (div, mod = n.divmod(2)
656: mod == 0)
657: x = x * x
658: n = div
659: end
660: z *= x
661: n -= 1
662: end
663: z
664: elsif other.kind_of?(Float) || defined?(Rational) && other.kind_of?(Rational)
665: Matrix.Raise ErrOperationNotDefined, "**"
666: else
667: Matrix.Raise ErrOperationNotDefined, "**"
668: end
669: 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 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 681
681: def determinant
682: return 0 unless square?
683:
684: size = row_size - 1
685: a = to_a
686:
687: det = 1
688: k = 0
689: begin
690: if (akk = a[k][k]) == 0
691: i = k
692: begin
693: return 0 if (i += 1) > size
694: end while a[i][k] == 0
695: a[i], a[k] = a[k], a[i]
696: akk = a[k][k]
697: det *= -1
698: end
699: (k + 1).upto(size) do
700: |i|
701: q = a[i][k] / akk
702: (k + 1).upto(size) do
703: |j|
704: a[i][j] -= a[k][j] * q
705: end
706: end
707: det *= akk
708: end while (k += 1) <= size
709: det
710: end
Overrides Object#inspect
# File lib/matrix.rb, line 870
870: def inspect
871: "Matrix"+@rows.inspect
872: 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: i = k
594: akk = a[k][k].abs
595: for j in (k+1)..size
596: v = a[j][k].abs
597: if v > akk
598: i = j
599: akk = v
600: end
601: end
602: Matrix.Raise ErrNotRegular if akk == 0
603: if i != k
604: a[i], a[k] = a[k], a[i]
605: @rows[i], @rows[k] = @rows[k], @rows[i]
606: end
607: akk = a[k][k]
608:
609: for i in 0 .. size
610: next if i == k
611: q = a[i][k] / akk
612: a[i][k] = 0
613:
614: (k + 1).upto(size) do
615: |j|
616: a[i][j] -= a[k][j] * q
617: end
618: 0.upto(size) do
619: |j|
620: @rows[i][j] -= @rows[k][j] * q
621: end
622: end
623:
624: (k + 1).upto(size) do
625: |j|
626: a[k][j] /= akk
627: end
628: 0.upto(size) do
629: |j|
630: @rows[k][j] /= akk
631: end
632: end
633: self
634: 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 720
720: def rank
721: if column_size > row_size
722: a = transpose.to_a
723: a_column_size = row_size
724: a_row_size = column_size
725: else
726: a = to_a
727: a_column_size = column_size
728: a_row_size = row_size
729: end
730: rank = 0
731: k = 0
732: begin
733: if (akk = a[k][k]) == 0
734: i = k
735: exists = true
736: begin
737: if (i += 1) > a_column_size - 1
738: exists = false
739: break
740: end
741: end while a[i][k] == 0
742: if exists
743: a[i], a[k] = a[k], a[i]
744: akk = a[k][k]
745: else
746: i = k
747: exists = true
748: begin
749: if (i += 1) > a_row_size - 1
750: exists = false
751: break
752: end
753: end while a[k][i] == 0
754: if exists
755: k.upto(a_column_size - 1) do
756: |j|
757: a[j][k], a[j][i] = a[j][i], a[j][k]
758: end
759: akk = a[k][k]
760: else
761: next
762: end
763: end
764: end
765: (k + 1).upto(a_row_size - 1) do
766: |i|
767: q = a[i][k] / akk
768: (k + 1).upto(a_column_size - 1) do
769: |j|
770: a[i][j] -= a[k][j] * q
771: end
772: end
773: rank += 1
774: end while (k += 1) <= a_column_size - 1
775: return rank
776: 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 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
Overrides Object#to_s
# File lib/matrix.rb, line 860
860: def to_s
861: "Matrix[" + @rows.collect{
862: |row|
863: "[" + row.collect{|e| e.to_s}.join(", ") + "]"
864: }.join(", ")+"]"
865: end