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