Class: Matrix

Inherits:
Object
  • Object
show all
Includes:
ExceptionForMatrix
Defined in:
lib/matrix.rb

Overview

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.

Method Catalogue

To create a matrix:

  • Matrix[*rows]

  • Matrix.[](*rows)

  • Matrix.rows(rows, copy = true)

  • Matrix.columns(columns)

  • Matrix.diagonal(*values)

  • Matrix.scalar(n, value)

  • Matrix.scalar(n, value)

  • Matrix.identity(n)

  • Matrix.unit(n)

  • Matrix.I(n)

  • Matrix.zero(n)

  • Matrix.row_vector(row)

  • Matrix.column_vector(column)

To access Matrix elements/columns/rows/submatrices/properties:

  • [](i, j)

  • #row_size

  • #column_size

  • #row(i)

  • #column(j)

  • #collect

  • #map

  • #minor(*param)

Properties of a matrix:

  • #regular?

  • #singular?

  • #square?

Matrix arithmetic:

  • *(m)

  • +(m)

  • -(m)

  • #/(m)

  • #inverse

  • #inv

  • **

Matrix functions:

  • #determinant

  • #det

  • #rank

  • #trace

  • #tr

  • #transpose

  • #t

Conversion to other data types:

  • #coerce(other)

  • #row_vectors

  • #column_vectors

  • #to_a

String representations:

  • #to_s

  • #inspect

Defined Under Namespace

Classes: Scalar

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(init_method, *argv) ⇒ Matrix

This method is used by the other methods that create matrices, and is of no use to general users.



248
249
250
# File 'lib/matrix.rb', line 248

def initialize(init_method, *argv)
  self.send(init_method, *argv)
end

Class Method Details

.[](*rows) ⇒ Object

Creates a matrix where each argument is a row.

Matrix[ [25, 93], [-1, 66] ]
   =>  25 93
       -1 66


122
123
124
# File 'lib/matrix.rb', line 122

def Matrix.[](*rows)
  new(:init_rows, rows, false)
end

.column_vector(column) ⇒ Object

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


233
234
235
236
237
238
239
240
241
242
# File 'lib/matrix.rb', line 233

def Matrix.column_vector(column)
  case column
  when Vector
    Matrix.columns([column.to_a])
  when Array
    Matrix.columns([column])
  else
    Matrix.columns([[column]])
  end
end

.columns(columns) ⇒ Object

Creates a matrix using columns as an array of column vectors.

Matrix.columns([[25, 93], [-1, 66]])
   =>  25 -1
       93 66


144
145
146
147
148
149
150
151
152
153
# File 'lib/matrix.rb', line 144

def Matrix.columns(columns)
  rows = (0 .. columns[0].size - 1).collect {
    |i|
    (0 .. columns.size - 1).collect {
      |j|
      columns[j][i]
    }
  }
  Matrix.rows(rows, false)
end

.diagonal(*values) ⇒ Object

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


162
163
164
165
166
167
168
169
170
171
# File 'lib/matrix.rb', line 162

def Matrix.diagonal(*values)
  size = values.size
  rows = (0 .. size  - 1).collect {
    |j|
    row = Array.new(size).fill(0, 0, size)
    row[j] = values[j]
    row
  }
  rows(rows, false)
end

.identity(n) ⇒ Object Also known as: unit, I

Creates an n by n identity matrix.

Matrix.identity(2)
  => 1 0
     0 1


190
191
192
# File 'lib/matrix.rb', line 190

def Matrix.identity(n)
  Matrix.scalar(n, 1)
end

.row_vector(row) ⇒ Object

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


214
215
216
217
218
219
220
221
222
223
# File 'lib/matrix.rb', line 214

def Matrix.row_vector(row)
  case row
  when Vector
    Matrix.rows([row.to_a], false)
  when Array
    Matrix.rows([row.dup], false)
  else
    Matrix.rows([[row]], false)
  end
end

.rows(rows, copy = true) ⇒ Object

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


133
134
135
# File 'lib/matrix.rb', line 133

def Matrix.rows(rows, copy = true)
  new(:init_rows, rows, copy)
end

.scalar(n, value) ⇒ Object

Creates an n by n diagonal matrix where each diagonal element is value.

Matrix.scalar(2, 5)
  => 5 0
     0 5


180
181
182
# File 'lib/matrix.rb', line 180

def Matrix.scalar(n, value)
  Matrix.diagonal(*Array.new(n).fill(value, 0, n))
end

.zero(n) ⇒ Object

Creates an n by n zero matrix.

Matrix.zero(2)
  => 0 0
     0 0


204
205
206
# File 'lib/matrix.rb', line 204

def Matrix.zero(n)
  Matrix.scalar(n, 0)
end

Instance Method Details

#*(m) ⇒ Object

Matrix multiplication.

Matrix[[2,4], [6,8]] * Matrix.identity(2)
  => 2 4
     6 8


451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
# File 'lib/matrix.rb', line 451

def *(m) # m is matrix or vector or number
  case(m)
  when Numeric
    rows = @rows.collect {
      |row|
      row.collect {
        |e|
        e * m
      }
    }
    return Matrix.rows(rows, false)
  when Vector
    m = Matrix.column_vector(m)
    r = self * m
    return r.column(0)
  when Matrix
    Matrix.Raise ErrDimensionMismatch if column_size != m.row_size
  
    rows = (0 .. row_size - 1).collect {
      |i|
      (0 .. m.column_size - 1).collect {
        |j|
        vij = 0
        0.upto(column_size - 1) do
          |k|
          vij += self[i, k] * m[k, j]
        end
        vij
      }
    }
    return Matrix.rows(rows, false)
  else
    x, y = m.coerce(self)
    return x * y
  end
end

#**(other) ⇒ Object

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


638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
# File 'lib/matrix.rb', line 638

def ** (other)
  if other.kind_of?(Integer)
    x = self
    if other <= 0
      x = self.inverse
      return Matrix.identity(self.column_size) if other == 0
      other = -other
    end
    z = x
    n = other  - 1
    while n != 0
      while (div, mod = n.divmod(2)
             mod == 0)
        x = x * x
        n = div
      end
      z *= x
      n -= 1
    end
    z
  elsif other.kind_of?(Float) || defined?(Rational) && other.kind_of?(Rational)
    Matrix.Raise ErrOperationNotDefined, "**"
  else
    Matrix.Raise ErrOperationNotDefined, "**"
  end
end

#+(m) ⇒ Object

Matrix addition.

Matrix.scalar(2,5) + Matrix[[1,0], [-4,7]]
  =>  6  0
     -4 12


494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
# File 'lib/matrix.rb', line 494

def +(m)
  case m
  when Numeric
    Matrix.Raise ErrOperationNotDefined, "+"
  when Vector
    m = Matrix.column_vector(m)
  when Matrix
  else
    x, y = m.coerce(self)
    return x + y
  end
  
  Matrix.Raise ErrDimensionMismatch unless row_size == m.row_size and column_size == m.column_size
  
  rows = (0 .. row_size - 1).collect {
    |i|
    (0 .. column_size - 1).collect {
      |j|
      self[i, j] + m[i, j]
    }
  }
  Matrix.rows(rows, false)
end

#-(m) ⇒ Object

Matrix subtraction.

Matrix[[1,5], [4,2]] - Matrix[[9,3], [-4,1]]
  => -8  2
      8  1


524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
# File 'lib/matrix.rb', line 524

def -(m)
  case m
  when Numeric
    Matrix.Raise ErrOperationNotDefined, "-"
  when Vector
    m = Matrix.column_vector(m)
  when Matrix
  else
    x, y = m.coerce(self)
    return x - y
  end
  
  Matrix.Raise ErrDimensionMismatch unless row_size == m.row_size and column_size == m.column_size
  
  rows = (0 .. row_size - 1).collect {
    |i|
    (0 .. column_size - 1).collect {
      |j|
      self[i, j] - m[i, j]
    }
  }
  Matrix.rows(rows, false)
end

#/(other) ⇒ Object

Matrix division (multiplication by the inverse).

Matrix[[7,6], [3,9]] / Matrix[[2,9], [3,1]]
  => -7  1
     -3 -6


554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
# File 'lib/matrix.rb', line 554

def /(other)
  case other
  when Numeric
    rows = @rows.collect {
      |row|
      row.collect {
        |e|
        e / other
      }
    }
    return Matrix.rows(rows, false)
  when Matrix
    return self * other.inverse
  else
    x, y = other.coerce(self)
    rerurn x / y
  end
end

#==(other) ⇒ Object Also known as: eql?

Returns true if and only if the two matrices contain equal elements.



400
401
402
403
404
# File 'lib/matrix.rb', line 400

def ==(other)
  return false unless Matrix === other
  
  other.compare_by_row_vectors(@rows)
end

#[](i, j) ⇒ Object

Returns element (i,j) of the matrix. That is: row i, column j.



265
266
267
# File 'lib/matrix.rb', line 265

def [](i, j)
  @rows[i][j]
end

#cloneObject

Returns a clone of the matrix, so that the contents of each do not reference identical objects.



424
425
426
# File 'lib/matrix.rb', line 424

def clone
  Matrix.rows(@rows)
end

#coerce(other) ⇒ Object

FIXME: describe #coerce.



809
810
811
812
813
814
815
816
# File 'lib/matrix.rb', line 809

def coerce(other)
  case other
  when Numeric
    return Scalar.new(other), self
  else
    raise TypeError, "#{self.class} can't be coerced into #{other.class}"
  end
end

#collectObject Also known as: map

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


327
328
329
330
# File 'lib/matrix.rb', line 327

def collect # :yield: e
  rows = @rows.collect{|row| row.collect{|e| yield e}}
  Matrix.rows(rows, false)
end

#column(j) ⇒ Object

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.



305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/matrix.rb', line 305

def column(j) # :yield: e
  if block_given?
    0.upto(row_size - 1) do
      |i|
      yield @rows[i][j]
    end
  else
    col = (0 .. row_size - 1).collect {
      |i|
      @rows[i][j]
    }
    Vector.elements(col, false)
  end
end

#column_sizeObject

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.



282
283
284
# File 'lib/matrix.rb', line 282

def column_size
  @rows[0].size
end

#column_vectorsObject

Returns an array of the column vectors of the matrix. See Vector.



832
833
834
835
836
837
838
# File 'lib/matrix.rb', line 832

def column_vectors
  columns = (0 .. column_size - 1).collect {
    |i|
    column(i)
  }
  columns
end

#compare_by_row_vectors(rows) ⇒ Object

Not really intended for general consumption.



410
411
412
413
414
415
416
417
418
# File 'lib/matrix.rb', line 410

def compare_by_row_vectors(rows)
  return false unless @rows.size == rows.size
  
  0.upto(@rows.size - 1) do
    |i|
    return false unless @rows[i] == rows[i]
  end
  true
end

#determinantObject Also known as: det

Returns the determinant of the matrix. If the matrix is not square, the result is 0.

Matrix[[7,6], [3,9]].determinant
  => 63


675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
# File 'lib/matrix.rb', line 675

def determinant
  return 0 unless square?
  
  size = row_size - 1
  a = to_a
  
  det = 1
  k = 0
  begin 
    if (akk = a[k][k]) == 0
      i = k
      begin
        return 0 if (i += 1) > size
      end while a[i][k] == 0
      a[i], a[k] = a[k], a[i]
      akk = a[k][k]
      det *= -1
    end
    (k + 1).upto(size) do
      |i|
      q = a[i][k] / akk
      (k + 1).upto(size) do
        |j|
        a[i][j] -= a[k][j] * q
      end
    end
    det *= akk
  end while (k += 1) <= size
  det
end

#hashObject

Returns a hash-code for the matrix.



431
432
433
434
435
436
437
438
439
# File 'lib/matrix.rb', line 431

def hash
  value = 0
  for row in @rows
    for e in row
      value ^= e.hash
    end
  end
  return value
end

#inspectObject

Overrides Object#inspect



864
865
866
# File 'lib/matrix.rb', line 864

def inspect
  "Matrix"+@rows.inspect
end

#inverseObject Also known as: inv

Returns the inverse of the matrix.

Matrix[[1, 2], [2, 1]].inverse
  => -1  1
      0 -1


579
580
581
582
# File 'lib/matrix.rb', line 579

def inverse
  Matrix.Raise ErrDimensionMismatch unless square?
  Matrix.I(row_size).inverse_from(self)
end

#inverse_from(src) ⇒ Object

Not for public consumption?



588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
# File 'lib/matrix.rb', line 588

def inverse_from(src)
  size = row_size - 1
  a = src.to_a
  
  for k in 0..size
    if (akk = a[k][k]) == 0
      i = k
      begin
        Matrix.Raise ErrNotRegular if (i += 1) > size
      end while a[i][k] == 0
      a[i], a[k] = a[k], a[i]
      @rows[i], @rows[k] = @rows[k], @rows[i]
      akk = a[k][k]
    end
    
    for i in 0 .. size
      next if i == k
      q = a[i][k] / akk
      a[i][k] = 0
      
      (k + 1).upto(size) do   
        |j|
        a[i][j] -= a[k][j] * q
      end
      0.upto(size) do
        |j|
        @rows[i][j] -= @rows[k][j] * q
      end
    end
    
    (k + 1).upto(size) do
      |j|
      a[k][j] /= akk
    end
    0.upto(size) do
      |j|
      @rows[k][j] /= akk
    end
  end
  self
end

#minor(*param) ⇒ Object

Returns a section of the matrix. The parameters are either:

  • start_row, nrows, start_col, ncols; OR

  • col_range, row_range

Matrix.diagonal(9, 5, -3).minor(0..1, 0..2)
  => 9 0 0
     0 5 0


342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/matrix.rb', line 342

def minor(*param)
  case param.size
  when 2
    from_row = param[0].first
    size_row = param[0].end - from_row
    size_row += 1 unless param[0].exclude_end?
    from_col = param[1].first
    size_col = param[1].end - from_col
    size_col += 1 unless param[1].exclude_end?
  when 4
    from_row = param[0]
    size_row = param[1]
    from_col = param[2]
    size_col = param[3]
  else
    Matrix.Raise ArgumentError, param.inspect
  end
  
  rows = @rows[from_row, size_row].collect{
    |row|
    row[from_col, size_col]
  }
  Matrix.rows(rows, false)
end

#rankObject

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


714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
# File 'lib/matrix.rb', line 714

def rank
  if column_size > row_size
    a = transpose.to_a
    a_column_size = row_size
    a_row_size = column_size
  else
    a = to_a
    a_column_size = column_size
    a_row_size = row_size
  end
  rank = 0
  k = 0
  begin
    if (akk = a[k][k]) == 0
      i = k
      exists = true
      begin
        if (i += 1) > a_column_size - 1
          exists = false
          break
        end
      end while a[i][k] == 0
      if exists
        a[i], a[k] = a[k], a[i]
        akk = a[k][k]
      else
        i = k
        exists = true
        begin
          if (i += 1) > a_row_size - 1
            exists = false
            break
          end
        end while a[k][i] == 0
        if exists
          k.upto(a_column_size - 1) do
            |j|
            a[j][k], a[j][i] = a[j][i], a[j][k]
          end
          akk = a[k][k]
        else
          next
        end
      end
    end
    (k + 1).upto(a_row_size - 1) do
      |i|
      q = a[i][k] / akk
      (k + 1).upto(a_column_size - 1) do
        |j|
        a[i][j] -= a[k][j] * q
      end
    end
    rank += 1
  end while (k += 1) <= a_column_size - 1
  return rank
end

#regular?Boolean

Returns true if this is a regular matrix.

Returns:

  • (Boolean)


374
375
376
# File 'lib/matrix.rb', line 374

def regular?
  square? and rank == column_size
end

#row(i) ⇒ Object

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.



290
291
292
293
294
295
296
297
298
# File 'lib/matrix.rb', line 290

def row(i) # :yield: e
  if block_given?
    for e in @rows[i]
      yield e
    end
  else
    Vector.elements(@rows[i])
  end
end

#row_sizeObject

Returns the number of rows.



272
273
274
# File 'lib/matrix.rb', line 272

def row_size
  @rows.size
end

#row_vectorsObject

Returns an array of the row vectors of the matrix. See Vector.



821
822
823
824
825
826
827
# File 'lib/matrix.rb', line 821

def row_vectors
  rows = (0 .. row_size - 1).collect {
    |i|
    row(i)
  }
  rows
end

#singular?Boolean

Returns true is this is a singular (i.e. non-regular) matrix.

Returns:

  • (Boolean)


381
382
383
# File 'lib/matrix.rb', line 381

def singular?
  not regular?
end

#square?Boolean

Returns true is this is a square matrix. See note in column_size about this being unreliable, though.

Returns:

  • (Boolean)


389
390
391
# File 'lib/matrix.rb', line 389

def square?
  column_size == row_size
end

#to_aObject

Returns an array of arrays that describe the rows of the matrix.



843
844
845
# File 'lib/matrix.rb', line 843

def to_a
  @rows.collect{|row| row.collect{|e| e}}
end

#to_sObject

Overrides Object#to_s



854
855
856
857
858
859
# File 'lib/matrix.rb', line 854

def to_s
  "Matrix[" + @rows.collect{
    |row|
    "[" + row.collect{|e| e.to_s}.join(", ") + "]"
  }.join(", ")+"]"
end

#traceObject Also known as: tr

Returns the trace (sum of diagonal elements) of the matrix.

Matrix[[7,6], [3,9]].trace
  => 16


777
778
779
780
781
782
783
784
# File 'lib/matrix.rb', line 777

def trace
  tr = 0
  0.upto(column_size - 1) do
    |i|
    tr += @rows[i][i]
  end
  tr
end

#transposeObject Also known as: t

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


797
798
799
# File 'lib/matrix.rb', line 797

def transpose
  Matrix.columns(@rows)
end