1# encoding: utf-8 2# frozen_string_literal: false 3# 4# = matrix.rb 5# 6# An implementation of Matrix and Vector classes. 7# 8# See classes Matrix and Vector for documentation. 9# 10# Current Maintainer:: Marc-André Lafortune 11# Original Author:: Keiju ISHITSUKA 12# Original Documentation:: Gavin Sinclair (sourced from <i>Ruby in a Nutshell</i> (Matsumoto, O'Reilly)) 13## 14 15require "e2mmap" 16 17module ExceptionForMatrix # :nodoc: 18 extend Exception2MessageMapper 19 def_e2message(TypeError, "wrong argument type %s (expected %s)") 20 def_e2message(ArgumentError, "Wrong # of arguments(%d for %d)") 21 22 def_exception("ErrDimensionMismatch", "\#{self.name} dimension mismatch") 23 def_exception("ErrNotRegular", "Not Regular Matrix") 24 def_exception("ErrOperationNotDefined", "Operation(%s) can\\'t be defined: %s op %s") 25 def_exception("ErrOperationNotImplemented", "Sorry, Operation(%s) not implemented: %s op %s") 26end 27 28# 29# The +Matrix+ class represents a mathematical matrix. It provides methods for creating 30# matrices, operating on them arithmetically and algebraically, 31# and determining their mathematical properties such as trace, rank, inverse, determinant, 32# or eigensystem. 33# 34class Matrix 35 include Enumerable 36 include ExceptionForMatrix 37 autoload :EigenvalueDecomposition, "matrix/eigenvalue_decomposition" 38 autoload :LUPDecomposition, "matrix/lup_decomposition" 39 40 # instance creations 41 private_class_method :new 42 attr_reader :rows 43 protected :rows 44 45 # 46 # Creates a matrix where each argument is a row. 47 # Matrix[ [25, 93], [-1, 66] ] 48 # => 25 93 49 # -1 66 50 # 51 def Matrix.[](*rows) 52 rows(rows, false) 53 end 54 55 # 56 # Creates a matrix where +rows+ is an array of arrays, each of which is a row 57 # of the matrix. If the optional argument +copy+ is false, use the given 58 # arrays as the internal structure of the matrix without copying. 59 # Matrix.rows([[25, 93], [-1, 66]]) 60 # => 25 93 61 # -1 66 62 # 63 def Matrix.rows(rows, copy = true) 64 rows = convert_to_array(rows, copy) 65 rows.map! do |row| 66 convert_to_array(row, copy) 67 end 68 size = (rows[0] || []).size 69 rows.each do |row| 70 raise ErrDimensionMismatch, "row size differs (#{row.size} should be #{size})" unless row.size == size 71 end 72 new rows, size 73 end 74 75 # 76 # Creates a matrix using +columns+ as an array of column vectors. 77 # Matrix.columns([[25, 93], [-1, 66]]) 78 # => 25 -1 79 # 93 66 80 # 81 def Matrix.columns(columns) 82 rows(columns, false).transpose 83 end 84 85 # 86 # Creates a matrix of size +row_count+ x +column_count+. 87 # It fills the values by calling the given block, 88 # passing the current row and column. 89 # Returns an enumerator if no block is given. 90 # 91 # m = Matrix.build(2, 4) {|row, col| col - row } 92 # => Matrix[[0, 1, 2, 3], [-1, 0, 1, 2]] 93 # m = Matrix.build(3) { rand } 94 # => a 3x3 matrix with random elements 95 # 96 def Matrix.build(row_count, column_count = row_count) 97 row_count = CoercionHelper.coerce_to_int(row_count) 98 column_count = CoercionHelper.coerce_to_int(column_count) 99 raise ArgumentError if row_count < 0 || column_count < 0 100 return to_enum :build, row_count, column_count unless block_given? 101 rows = Array.new(row_count) do |i| 102 Array.new(column_count) do |j| 103 yield i, j 104 end 105 end 106 new rows, column_count 107 end 108 109 # 110 # Creates a matrix where the diagonal elements are composed of +values+. 111 # Matrix.diagonal(9, 5, -3) 112 # => 9 0 0 113 # 0 5 0 114 # 0 0 -3 115 # 116 def Matrix.diagonal(*values) 117 size = values.size 118 return Matrix.empty if size == 0 119 rows = Array.new(size) {|j| 120 row = Array.new(size, 0) 121 row[j] = values[j] 122 row 123 } 124 new rows 125 end 126 127 # 128 # Creates an +n+ by +n+ diagonal matrix where each diagonal element is 129 # +value+. 130 # Matrix.scalar(2, 5) 131 # => 5 0 132 # 0 5 133 # 134 def Matrix.scalar(n, value) 135 diagonal(*Array.new(n, value)) 136 end 137 138 # 139 # Creates an +n+ by +n+ identity matrix. 140 # Matrix.identity(2) 141 # => 1 0 142 # 0 1 143 # 144 def Matrix.identity(n) 145 scalar(n, 1) 146 end 147 class << Matrix 148 alias_method :unit, :identity 149 alias_method :I, :identity 150 end 151 152 # 153 # Creates a zero matrix. 154 # Matrix.zero(2) 155 # => 0 0 156 # 0 0 157 # 158 def Matrix.zero(row_count, column_count = row_count) 159 rows = Array.new(row_count){Array.new(column_count, 0)} 160 new rows, column_count 161 end 162 163 # 164 # Creates a single-row matrix where the values of that row are as given in 165 # +row+. 166 # Matrix.row_vector([4,5,6]) 167 # => 4 5 6 168 # 169 def Matrix.row_vector(row) 170 row = convert_to_array(row) 171 new [row] 172 end 173 174 # 175 # Creates a single-column matrix where the values of that column are as given 176 # in +column+. 177 # Matrix.column_vector([4,5,6]) 178 # => 4 179 # 5 180 # 6 181 # 182 def Matrix.column_vector(column) 183 column = convert_to_array(column) 184 new [column].transpose, 1 185 end 186 187 # 188 # Creates a empty matrix of +row_count+ x +column_count+. 189 # At least one of +row_count+ or +column_count+ must be 0. 190 # 191 # m = Matrix.empty(2, 0) 192 # m == Matrix[ [], [] ] 193 # => true 194 # n = Matrix.empty(0, 3) 195 # n == Matrix.columns([ [], [], [] ]) 196 # => true 197 # m * n 198 # => Matrix[[0, 0, 0], [0, 0, 0]] 199 # 200 def Matrix.empty(row_count = 0, column_count = 0) 201 raise ArgumentError, "One size must be 0" if column_count != 0 && row_count != 0 202 raise ArgumentError, "Negative size" if column_count < 0 || row_count < 0 203 204 new([[]]*row_count, column_count) 205 end 206 207 # 208 # Create a matrix by stacking matrices vertically 209 # 210 # x = Matrix[[1, 2], [3, 4]] 211 # y = Matrix[[5, 6], [7, 8]] 212 # Matrix.vstack(x, y) # => Matrix[[1, 2], [3, 4], [5, 6], [7, 8]] 213 # 214 def Matrix.vstack(x, *matrices) 215 x = CoercionHelper.coerce_to_matrix(x) 216 result = x.send(:rows).map(&:dup) 217 matrices.each do |m| 218 m = CoercionHelper.coerce_to_matrix(m) 219 if m.column_count != x.column_count 220 raise ErrDimensionMismatch, "The given matrices must have #{x.column_count} columns, but one has #{m.column_count}" 221 end 222 result.concat(m.send(:rows)) 223 end 224 new result, x.column_count 225 end 226 227 228 # 229 # Create a matrix by stacking matrices horizontally 230 # 231 # x = Matrix[[1, 2], [3, 4]] 232 # y = Matrix[[5, 6], [7, 8]] 233 # Matrix.hstack(x, y) # => Matrix[[1, 2, 5, 6], [3, 4, 7, 8]] 234 # 235 def Matrix.hstack(x, *matrices) 236 x = CoercionHelper.coerce_to_matrix(x) 237 result = x.send(:rows).map(&:dup) 238 total_column_count = x.column_count 239 matrices.each do |m| 240 m = CoercionHelper.coerce_to_matrix(m) 241 if m.row_count != x.row_count 242 raise ErrDimensionMismatch, "The given matrices must have #{x.row_count} rows, but one has #{m.row_count}" 243 end 244 result.each_with_index do |row, i| 245 row.concat m.send(:rows)[i] 246 end 247 total_column_count += m.column_count 248 end 249 new result, total_column_count 250 end 251 252 # 253 # Create a matrix by combining matrices entrywise, using the given block 254 # 255 # x = Matrix[[6, 6], [4, 4]] 256 # y = Matrix[[1, 2], [3, 4]] 257 # Matrix.combine(x, y) {|a, b| a - b} # => Matrix[[5, 4], [1, 0]] 258 # 259 def Matrix.combine(*matrices) 260 return to_enum(__method__, *matrices) unless block_given? 261 262 return Matrix.empty if matrices.empty? 263 matrices.map!(&CoercionHelper.method(:coerce_to_matrix)) 264 x = matrices.first 265 matrices.each do |m| 266 Matrix.Raise ErrDimensionMismatch unless x.row_count == m.row_count && x.column_count == m.column_count 267 end 268 269 rows = Array.new(x.row_count) do |i| 270 Array.new(x.column_count) do |j| 271 yield matrices.map{|m| m[i,j]} 272 end 273 end 274 new rows, x.column_count 275 end 276 277 def combine(*matrices, &block) 278 Matrix.combine(self, *matrices, &block) 279 end 280 281 # 282 # Matrix.new is private; use Matrix.rows, columns, [], etc... to create. 283 # 284 def initialize(rows, column_count = rows[0].size) 285 # No checking is done at this point. rows must be an Array of Arrays. 286 # column_count must be the size of the first row, if there is one, 287 # otherwise it *must* be specified and can be any integer >= 0 288 @rows = rows 289 @column_count = column_count 290 end 291 292 private def new_matrix(rows, column_count = rows[0].size) # :nodoc: 293 self.class.send(:new, rows, column_count) # bypass privacy of Matrix.new 294 end 295 296 # 297 # Returns element (+i+,+j+) of the matrix. That is: row +i+, column +j+. 298 # 299 def [](i, j) 300 @rows.fetch(i){return nil}[j] 301 end 302 alias element [] 303 alias component [] 304 305 # 306 # :call-seq: 307 # matrix[range, range] = matrix/element 308 # matrix[range, integer] = vector/column_matrix/element 309 # matrix[integer, range] = vector/row_matrix/element 310 # matrix[integer, integer] = element 311 # 312 # Set element or elements of matrix. 313 def []=(i, j, v) 314 raise FrozenError, "can't modify frozen Matrix" if frozen? 315 rows = check_range(i, :row) or row = check_int(i, :row) 316 columns = check_range(j, :column) or column = check_int(j, :column) 317 if rows && columns 318 set_row_and_col_range(rows, columns, v) 319 elsif rows 320 set_row_range(rows, column, v) 321 elsif columns 322 set_col_range(row, columns, v) 323 else 324 set_value(row, column, v) 325 end 326 end 327 alias set_element []= 328 alias set_component []= 329 private :set_element, :set_component 330 331 # Returns range or nil 332 private def check_range(val, direction) 333 return unless val.is_a?(Range) 334 count = direction == :row ? row_count : column_count 335 CoercionHelper.check_range(val, count, direction) 336 end 337 338 private def check_int(val, direction) 339 count = direction == :row ? row_count : column_count 340 CoercionHelper.check_int(val, count, direction) 341 end 342 343 private def set_value(row, col, value) 344 raise ErrDimensionMismatch, "Expected a a value, got a #{value.class}" if value.respond_to?(:to_matrix) 345 346 @rows[row][col] = value 347 end 348 349 private def set_row_and_col_range(row_range, col_range, value) 350 if value.is_a?(Matrix) 351 if row_range.size != value.row_count || col_range.size != value.column_count 352 raise ErrDimensionMismatch, [ 353 'Expected a Matrix of dimensions', 354 "#{row_range.size}x#{col_range.size}", 355 'got', 356 "#{value.row_count}x#{value.column_count}", 357 ].join(' ') 358 end 359 source = value.instance_variable_get :@rows 360 row_range.each_with_index do |row, i| 361 @rows[row][col_range] = source[i] 362 end 363 elsif value.is_a?(Vector) 364 raise ErrDimensionMismatch, 'Expected a Matrix or a value, got a Vector' 365 else 366 value_to_set = Array.new(col_range.size, value) 367 row_range.each do |i| 368 @rows[i][col_range] = value_to_set 369 end 370 end 371 end 372 373 private def set_row_range(row_range, col, value) 374 if value.is_a?(Vector) 375 Matrix.Raise ErrDimensionMismatch unless row_range.size == value.size 376 set_column_vector(row_range, col, value) 377 elsif value.is_a?(Matrix) 378 Matrix.Raise ErrDimensionMismatch unless value.column_count == 1 379 value = value.column(0) 380 Matrix.Raise ErrDimensionMismatch unless row_range.size == value.size 381 set_column_vector(row_range, col, value) 382 else 383 @rows[row_range].each{|e| e[col] = value } 384 end 385 end 386 387 private def set_column_vector(row_range, col, value) 388 value.each_with_index do |e, index| 389 r = row_range.begin + index 390 @rows[r][col] = e 391 end 392 end 393 394 private def set_col_range(row, col_range, value) 395 value = if value.is_a?(Vector) 396 value.to_a 397 elsif value.is_a?(Matrix) 398 Matrix.Raise ErrDimensionMismatch unless value.row_count == 1 399 value.row(0).to_a 400 else 401 Array.new(col_range.size, value) 402 end 403 Matrix.Raise ErrDimensionMismatch unless col_range.size == value.size 404 @rows[row][col_range] = value 405 end 406 407 # 408 # Returns the number of rows. 409 # 410 def row_count 411 @rows.size 412 end 413 414 alias_method :row_size, :row_count 415 # 416 # Returns the number of columns. 417 # 418 attr_reader :column_count 419 alias_method :column_size, :column_count 420 421 # 422 # Returns row vector number +i+ of the matrix as a Vector (starting at 0 like 423 # an array). When a block is given, the elements of that vector are iterated. 424 # 425 def row(i, &block) # :yield: e 426 if block_given? 427 @rows.fetch(i){return self}.each(&block) 428 self 429 else 430 Vector.elements(@rows.fetch(i){return nil}) 431 end 432 end 433 434 # 435 # Returns column vector number +j+ of the matrix as a Vector (starting at 0 436 # like an array). When a block is given, the elements of that vector are 437 # iterated. 438 # 439 def column(j) # :yield: e 440 if block_given? 441 return self if j >= column_count || j < -column_count 442 row_count.times do |i| 443 yield @rows[i][j] 444 end 445 self 446 else 447 return nil if j >= column_count || j < -column_count 448 col = Array.new(row_count) {|i| 449 @rows[i][j] 450 } 451 Vector.elements(col, false) 452 end 453 end 454 455 # 456 # Returns a matrix that is the result of iteration of the given block over all 457 # elements of the matrix. 458 # Elements can be restricted by passing an argument: 459 # * :all (default): yields all elements 460 # * :diagonal: yields only elements on the diagonal 461 # * :off_diagonal: yields all elements except on the diagonal 462 # * :lower: yields only elements on or below the diagonal 463 # * :strict_lower: yields only elements below the diagonal 464 # * :strict_upper: yields only elements above the diagonal 465 # * :upper: yields only elements on or above the diagonal 466 # Matrix[ [1,2], [3,4] ].collect { |e| e**2 } 467 # => 1 4 468 # 9 16 469 # 470 def collect(which = :all, &block) # :yield: e 471 return to_enum(:collect, which) unless block_given? 472 dup.collect!(which, &block) 473 end 474 alias_method :map, :collect 475 476 # 477 # Invokes the given block for each element of matrix, replacing the element with the value 478 # returned by the block. 479 # Elements can be restricted by passing an argument: 480 # * :all (default): yields all elements 481 # * :diagonal: yields only elements on the diagonal 482 # * :off_diagonal: yields all elements except on the diagonal 483 # * :lower: yields only elements on or below the diagonal 484 # * :strict_lower: yields only elements below the diagonal 485 # * :strict_upper: yields only elements above the diagonal 486 # * :upper: yields only elements on or above the diagonal 487 # 488 def collect!(which = :all) 489 return to_enum(:collect!, which) unless block_given? 490 raise FrozenError, "can't modify frozen Matrix" if frozen? 491 each_with_index(which){ |e, row_index, col_index| @rows[row_index][col_index] = yield e } 492 end 493 494 alias map! collect! 495 496 def freeze 497 @rows.freeze 498 super 499 end 500 501 # 502 # Yields all elements of the matrix, starting with those of the first row, 503 # or returns an Enumerator if no block given. 504 # Elements can be restricted by passing an argument: 505 # * :all (default): yields all elements 506 # * :diagonal: yields only elements on the diagonal 507 # * :off_diagonal: yields all elements except on the diagonal 508 # * :lower: yields only elements on or below the diagonal 509 # * :strict_lower: yields only elements below the diagonal 510 # * :strict_upper: yields only elements above the diagonal 511 # * :upper: yields only elements on or above the diagonal 512 # 513 # Matrix[ [1,2], [3,4] ].each { |e| puts e } 514 # # => prints the numbers 1 to 4 515 # Matrix[ [1,2], [3,4] ].each(:strict_lower).to_a # => [3] 516 # 517 def each(which = :all) # :yield: e 518 return to_enum :each, which unless block_given? 519 last = column_count - 1 520 case which 521 when :all 522 block = Proc.new 523 @rows.each do |row| 524 row.each(&block) 525 end 526 when :diagonal 527 @rows.each_with_index do |row, row_index| 528 yield row.fetch(row_index){return self} 529 end 530 when :off_diagonal 531 @rows.each_with_index do |row, row_index| 532 column_count.times do |col_index| 533 yield row[col_index] unless row_index == col_index 534 end 535 end 536 when :lower 537 @rows.each_with_index do |row, row_index| 538 0.upto([row_index, last].min) do |col_index| 539 yield row[col_index] 540 end 541 end 542 when :strict_lower 543 @rows.each_with_index do |row, row_index| 544 [row_index, column_count].min.times do |col_index| 545 yield row[col_index] 546 end 547 end 548 when :strict_upper 549 @rows.each_with_index do |row, row_index| 550 (row_index+1).upto(last) do |col_index| 551 yield row[col_index] 552 end 553 end 554 when :upper 555 @rows.each_with_index do |row, row_index| 556 row_index.upto(last) do |col_index| 557 yield row[col_index] 558 end 559 end 560 else 561 raise ArgumentError, "expected #{which.inspect} to be one of :all, :diagonal, :off_diagonal, :lower, :strict_lower, :strict_upper or :upper" 562 end 563 self 564 end 565 566 # 567 # Same as #each, but the row index and column index in addition to the element 568 # 569 # Matrix[ [1,2], [3,4] ].each_with_index do |e, row, col| 570 # puts "#{e} at #{row}, #{col}" 571 # end 572 # # => Prints: 573 # # 1 at 0, 0 574 # # 2 at 0, 1 575 # # 3 at 1, 0 576 # # 4 at 1, 1 577 # 578 def each_with_index(which = :all) # :yield: e, row, column 579 return to_enum :each_with_index, which unless block_given? 580 last = column_count - 1 581 case which 582 when :all 583 @rows.each_with_index do |row, row_index| 584 row.each_with_index do |e, col_index| 585 yield e, row_index, col_index 586 end 587 end 588 when :diagonal 589 @rows.each_with_index do |row, row_index| 590 yield row.fetch(row_index){return self}, row_index, row_index 591 end 592 when :off_diagonal 593 @rows.each_with_index do |row, row_index| 594 column_count.times do |col_index| 595 yield row[col_index], row_index, col_index unless row_index == col_index 596 end 597 end 598 when :lower 599 @rows.each_with_index do |row, row_index| 600 0.upto([row_index, last].min) do |col_index| 601 yield row[col_index], row_index, col_index 602 end 603 end 604 when :strict_lower 605 @rows.each_with_index do |row, row_index| 606 [row_index, column_count].min.times do |col_index| 607 yield row[col_index], row_index, col_index 608 end 609 end 610 when :strict_upper 611 @rows.each_with_index do |row, row_index| 612 (row_index+1).upto(last) do |col_index| 613 yield row[col_index], row_index, col_index 614 end 615 end 616 when :upper 617 @rows.each_with_index do |row, row_index| 618 row_index.upto(last) do |col_index| 619 yield row[col_index], row_index, col_index 620 end 621 end 622 else 623 raise ArgumentError, "expected #{which.inspect} to be one of :all, :diagonal, :off_diagonal, :lower, :strict_lower, :strict_upper or :upper" 624 end 625 self 626 end 627 628 SELECTORS = {all: true, diagonal: true, off_diagonal: true, lower: true, strict_lower: true, strict_upper: true, upper: true}.freeze 629 # 630 # :call-seq: 631 # index(value, selector = :all) -> [row, column] 632 # index(selector = :all){ block } -> [row, column] 633 # index(selector = :all) -> an_enumerator 634 # 635 # The index method is specialized to return the index as [row, column] 636 # It also accepts an optional +selector+ argument, see #each for details. 637 # 638 # Matrix[ [1,2], [3,4] ].index(&:even?) # => [0, 1] 639 # Matrix[ [1,1], [1,1] ].index(1, :strict_lower) # => [1, 0] 640 # 641 def index(*args) 642 raise ArgumentError, "wrong number of arguments(#{args.size} for 0-2)" if args.size > 2 643 which = (args.size == 2 || SELECTORS.include?(args.last)) ? args.pop : :all 644 return to_enum :find_index, which, *args unless block_given? || args.size == 1 645 if args.size == 1 646 value = args.first 647 each_with_index(which) do |e, row_index, col_index| 648 return row_index, col_index if e == value 649 end 650 else 651 each_with_index(which) do |e, row_index, col_index| 652 return row_index, col_index if yield e 653 end 654 end 655 nil 656 end 657 alias_method :find_index, :index 658 659 # 660 # Returns a section of the matrix. The parameters are either: 661 # * start_row, nrows, start_col, ncols; OR 662 # * row_range, col_range 663 # 664 # Matrix.diagonal(9, 5, -3).minor(0..1, 0..2) 665 # => 9 0 0 666 # 0 5 0 667 # 668 # Like Array#[], negative indices count backward from the end of the 669 # row or column (-1 is the last element). Returns nil if the starting 670 # row or column is greater than row_count or column_count respectively. 671 # 672 def minor(*param) 673 case param.size 674 when 2 675 row_range, col_range = param 676 from_row = row_range.first 677 from_row += row_count if from_row < 0 678 to_row = row_range.end 679 to_row += row_count if to_row < 0 680 to_row += 1 unless row_range.exclude_end? 681 size_row = to_row - from_row 682 683 from_col = col_range.first 684 from_col += column_count if from_col < 0 685 to_col = col_range.end 686 to_col += column_count if to_col < 0 687 to_col += 1 unless col_range.exclude_end? 688 size_col = to_col - from_col 689 when 4 690 from_row, size_row, from_col, size_col = param 691 return nil if size_row < 0 || size_col < 0 692 from_row += row_count if from_row < 0 693 from_col += column_count if from_col < 0 694 else 695 raise ArgumentError, param.inspect 696 end 697 698 return nil if from_row > row_count || from_col > column_count || from_row < 0 || from_col < 0 699 rows = @rows[from_row, size_row].collect{|row| 700 row[from_col, size_col] 701 } 702 new_matrix rows, [column_count - from_col, size_col].min 703 end 704 705 # 706 # Returns the submatrix obtained by deleting the specified row and column. 707 # 708 # Matrix.diagonal(9, 5, -3, 4).first_minor(1, 2) 709 # => 9 0 0 710 # 0 0 0 711 # 0 0 4 712 # 713 def first_minor(row, column) 714 raise RuntimeError, "first_minor of empty matrix is not defined" if empty? 715 716 unless 0 <= row && row < row_count 717 raise ArgumentError, "invalid row (#{row.inspect} for 0..#{row_count - 1})" 718 end 719 720 unless 0 <= column && column < column_count 721 raise ArgumentError, "invalid column (#{column.inspect} for 0..#{column_count - 1})" 722 end 723 724 arrays = to_a 725 arrays.delete_at(row) 726 arrays.each do |array| 727 array.delete_at(column) 728 end 729 730 new_matrix arrays, column_count - 1 731 end 732 733 # 734 # Returns the (row, column) cofactor which is obtained by multiplying 735 # the first minor by (-1)**(row + column). 736 # 737 # Matrix.diagonal(9, 5, -3, 4).cofactor(1, 1) 738 # => -108 739 # 740 def cofactor(row, column) 741 raise RuntimeError, "cofactor of empty matrix is not defined" if empty? 742 Matrix.Raise ErrDimensionMismatch unless square? 743 744 det_of_minor = first_minor(row, column).determinant 745 det_of_minor * (-1) ** (row + column) 746 end 747 748 # 749 # Returns the adjugate of the matrix. 750 # 751 # Matrix[ [7,6],[3,9] ].adjugate 752 # => 9 -6 753 # -3 7 754 # 755 def adjugate 756 Matrix.Raise ErrDimensionMismatch unless square? 757 Matrix.build(row_count, column_count) do |row, column| 758 cofactor(column, row) 759 end 760 end 761 762 # 763 # Returns the Laplace expansion along given row or column. 764 # 765 # Matrix[[7,6], [3,9]].laplace_expansion(column: 1) 766 # => 45 767 # 768 # Matrix[[Vector[1, 0], Vector[0, 1]], [2, 3]].laplace_expansion(row: 0) 769 # => Vector[3, -2] 770 # 771 # 772 def laplace_expansion(row: nil, column: nil) 773 num = row || column 774 775 if !num || (row && column) 776 raise ArgumentError, "exactly one the row or column arguments must be specified" 777 end 778 779 Matrix.Raise ErrDimensionMismatch unless square? 780 raise RuntimeError, "laplace_expansion of empty matrix is not defined" if empty? 781 782 unless 0 <= num && num < row_count 783 raise ArgumentError, "invalid num (#{num.inspect} for 0..#{row_count - 1})" 784 end 785 786 send(row ? :row : :column, num).map.with_index { |e, k| 787 e * cofactor(*(row ? [num, k] : [k,num])) 788 }.inject(:+) 789 end 790 alias_method :cofactor_expansion, :laplace_expansion 791 792 793 #-- 794 # TESTING -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 795 #++ 796 797 # 798 # Returns +true+ if this is a diagonal matrix. 799 # Raises an error if matrix is not square. 800 # 801 def diagonal? 802 Matrix.Raise ErrDimensionMismatch unless square? 803 each(:off_diagonal).all?(&:zero?) 804 end 805 806 # 807 # Returns +true+ if this is an empty matrix, i.e. if the number of rows 808 # or the number of columns is 0. 809 # 810 def empty? 811 column_count == 0 || row_count == 0 812 end 813 814 # 815 # Returns +true+ if this is an hermitian matrix. 816 # Raises an error if matrix is not square. 817 # 818 def hermitian? 819 Matrix.Raise ErrDimensionMismatch unless square? 820 each_with_index(:upper).all? do |e, row, col| 821 e == rows[col][row].conj 822 end 823 end 824 825 # 826 # Returns +true+ if this is a lower triangular matrix. 827 # 828 def lower_triangular? 829 each(:strict_upper).all?(&:zero?) 830 end 831 832 # 833 # Returns +true+ if this is a normal matrix. 834 # Raises an error if matrix is not square. 835 # 836 def normal? 837 Matrix.Raise ErrDimensionMismatch unless square? 838 rows.each_with_index do |row_i, i| 839 rows.each_with_index do |row_j, j| 840 s = 0 841 rows.each_with_index do |row_k, k| 842 s += row_i[k] * row_j[k].conj - row_k[i].conj * row_k[j] 843 end 844 return false unless s == 0 845 end 846 end 847 true 848 end 849 850 # 851 # Returns +true+ if this is an orthogonal matrix 852 # Raises an error if matrix is not square. 853 # 854 def orthogonal? 855 Matrix.Raise ErrDimensionMismatch unless square? 856 rows.each_with_index do |row, i| 857 column_count.times do |j| 858 s = 0 859 row_count.times do |k| 860 s += row[k] * rows[k][j] 861 end 862 return false unless s == (i == j ? 1 : 0) 863 end 864 end 865 true 866 end 867 868 # 869 # Returns +true+ if this is a permutation matrix 870 # Raises an error if matrix is not square. 871 # 872 def permutation? 873 Matrix.Raise ErrDimensionMismatch unless square? 874 cols = Array.new(column_count) 875 rows.each_with_index do |row, i| 876 found = false 877 row.each_with_index do |e, j| 878 if e == 1 879 return false if found || cols[j] 880 found = cols[j] = true 881 elsif e != 0 882 return false 883 end 884 end 885 return false unless found 886 end 887 true 888 end 889 890 # 891 # Returns +true+ if all entries of the matrix are real. 892 # 893 def real? 894 all?(&:real?) 895 end 896 897 # 898 # Returns +true+ if this is a regular (i.e. non-singular) matrix. 899 # 900 def regular? 901 not singular? 902 end 903 904 # 905 # Returns +true+ if this is a singular matrix. 906 # 907 def singular? 908 determinant == 0 909 end 910 911 # 912 # Returns +true+ if this is a square matrix. 913 # 914 def square? 915 column_count == row_count 916 end 917 918 # 919 # Returns +true+ if this is a symmetric matrix. 920 # Raises an error if matrix is not square. 921 # 922 def symmetric? 923 Matrix.Raise ErrDimensionMismatch unless square? 924 each_with_index(:strict_upper) do |e, row, col| 925 return false if e != rows[col][row] 926 end 927 true 928 end 929 930 # 931 # Returns +true+ if this is an antisymmetric matrix. 932 # Raises an error if matrix is not square. 933 # 934 def antisymmetric? 935 Matrix.Raise ErrDimensionMismatch unless square? 936 each_with_index(:upper) do |e, row, col| 937 return false unless e == -rows[col][row] 938 end 939 true 940 end 941 alias_method :skew_symmetric?, :antisymmetric? 942 943 # 944 # Returns +true+ if this is a unitary matrix 945 # Raises an error if matrix is not square. 946 # 947 def unitary? 948 Matrix.Raise ErrDimensionMismatch unless square? 949 rows.each_with_index do |row, i| 950 column_count.times do |j| 951 s = 0 952 row_count.times do |k| 953 s += row[k].conj * rows[k][j] 954 end 955 return false unless s == (i == j ? 1 : 0) 956 end 957 end 958 true 959 end 960 961 # 962 # Returns +true+ if this is an upper triangular matrix. 963 # 964 def upper_triangular? 965 each(:strict_lower).all?(&:zero?) 966 end 967 968 # 969 # Returns +true+ if this is a matrix with only zero elements 970 # 971 def zero? 972 all?(&:zero?) 973 end 974 975 #-- 976 # OBJECT METHODS -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 977 #++ 978 979 # 980 # Returns +true+ if and only if the two matrices contain equal elements. 981 # 982 def ==(other) 983 return false unless Matrix === other && 984 column_count == other.column_count # necessary for empty matrices 985 rows == other.rows 986 end 987 988 def eql?(other) 989 return false unless Matrix === other && 990 column_count == other.column_count # necessary for empty matrices 991 rows.eql? other.rows 992 end 993 994 # 995 # Called for dup & clone. 996 # 997 private def initialize_copy(m) 998 super 999 @rows = @rows.map(&:dup) unless frozen? 1000 end 1001 1002 # 1003 # Returns a hash-code for the matrix. 1004 # 1005 def hash 1006 @rows.hash 1007 end 1008 1009 #-- 1010 # ARITHMETIC -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 1011 #++ 1012 1013 # 1014 # Matrix multiplication. 1015 # Matrix[[2,4], [6,8]] * Matrix.identity(2) 1016 # => 2 4 1017 # 6 8 1018 # 1019 def *(m) # m is matrix or vector or number 1020 case(m) 1021 when Numeric 1022 rows = @rows.collect {|row| 1023 row.collect {|e| e * m } 1024 } 1025 return new_matrix rows, column_count 1026 when Vector 1027 m = self.class.column_vector(m) 1028 r = self * m 1029 return r.column(0) 1030 when Matrix 1031 Matrix.Raise ErrDimensionMismatch if column_count != m.row_count 1032 1033 rows = Array.new(row_count) {|i| 1034 Array.new(m.column_count) {|j| 1035 (0 ... column_count).inject(0) do |vij, k| 1036 vij + self[i, k] * m[k, j] 1037 end 1038 } 1039 } 1040 return new_matrix rows, m.column_count 1041 else 1042 return apply_through_coercion(m, __method__) 1043 end 1044 end 1045 1046 # 1047 # Matrix addition. 1048 # Matrix.scalar(2,5) + Matrix[[1,0], [-4,7]] 1049 # => 6 0 1050 # -4 12 1051 # 1052 def +(m) 1053 case m 1054 when Numeric 1055 Matrix.Raise ErrOperationNotDefined, "+", self.class, m.class 1056 when Vector 1057 m = self.class.column_vector(m) 1058 when Matrix 1059 else 1060 return apply_through_coercion(m, __method__) 1061 end 1062 1063 Matrix.Raise ErrDimensionMismatch unless row_count == m.row_count && column_count == m.column_count 1064 1065 rows = Array.new(row_count) {|i| 1066 Array.new(column_count) {|j| 1067 self[i, j] + m[i, j] 1068 } 1069 } 1070 new_matrix rows, column_count 1071 end 1072 1073 # 1074 # Matrix subtraction. 1075 # Matrix[[1,5], [4,2]] - Matrix[[9,3], [-4,1]] 1076 # => -8 2 1077 # 8 1 1078 # 1079 def -(m) 1080 case m 1081 when Numeric 1082 Matrix.Raise ErrOperationNotDefined, "-", self.class, m.class 1083 when Vector 1084 m = self.class.column_vector(m) 1085 when Matrix 1086 else 1087 return apply_through_coercion(m, __method__) 1088 end 1089 1090 Matrix.Raise ErrDimensionMismatch unless row_count == m.row_count && column_count == m.column_count 1091 1092 rows = Array.new(row_count) {|i| 1093 Array.new(column_count) {|j| 1094 self[i, j] - m[i, j] 1095 } 1096 } 1097 new_matrix rows, column_count 1098 end 1099 1100 # 1101 # Matrix division (multiplication by the inverse). 1102 # Matrix[[7,6], [3,9]] / Matrix[[2,9], [3,1]] 1103 # => -7 1 1104 # -3 -6 1105 # 1106 def /(other) 1107 case other 1108 when Numeric 1109 rows = @rows.collect {|row| 1110 row.collect {|e| e / other } 1111 } 1112 return new_matrix rows, column_count 1113 when Matrix 1114 return self * other.inverse 1115 else 1116 return apply_through_coercion(other, __method__) 1117 end 1118 end 1119 1120 # 1121 # Hadamard product 1122 # Matrix[[1,2], [3,4]].hadamard_product(Matrix[[1,2], [3,2]]) 1123 # => 1 4 1124 # 9 8 1125 # 1126 def hadamard_product(m) 1127 combine(m){|a, b| a * b} 1128 end 1129 alias_method :entrywise_product, :hadamard_product 1130 1131 # 1132 # Returns the inverse of the matrix. 1133 # Matrix[[-1, -1], [0, -1]].inverse 1134 # => -1 1 1135 # 0 -1 1136 # 1137 def inverse 1138 Matrix.Raise ErrDimensionMismatch unless square? 1139 self.class.I(row_count).send(:inverse_from, self) 1140 end 1141 alias_method :inv, :inverse 1142 1143 private def inverse_from(src) # :nodoc: 1144 last = row_count - 1 1145 a = src.to_a 1146 1147 0.upto(last) do |k| 1148 i = k 1149 akk = a[k][k].abs 1150 (k+1).upto(last) do |j| 1151 v = a[j][k].abs 1152 if v > akk 1153 i = j 1154 akk = v 1155 end 1156 end 1157 Matrix.Raise ErrNotRegular if akk == 0 1158 if i != k 1159 a[i], a[k] = a[k], a[i] 1160 @rows[i], @rows[k] = @rows[k], @rows[i] 1161 end 1162 akk = a[k][k] 1163 1164 0.upto(last) do |ii| 1165 next if ii == k 1166 q = a[ii][k].quo(akk) 1167 a[ii][k] = 0 1168 1169 (k + 1).upto(last) do |j| 1170 a[ii][j] -= a[k][j] * q 1171 end 1172 0.upto(last) do |j| 1173 @rows[ii][j] -= @rows[k][j] * q 1174 end 1175 end 1176 1177 (k+1).upto(last) do |j| 1178 a[k][j] = a[k][j].quo(akk) 1179 end 1180 0.upto(last) do |j| 1181 @rows[k][j] = @rows[k][j].quo(akk) 1182 end 1183 end 1184 self 1185 end 1186 1187 # 1188 # Matrix exponentiation. 1189 # Equivalent to multiplying the matrix by itself N times. 1190 # Non integer exponents will be handled by diagonalizing the matrix. 1191 # 1192 # Matrix[[7,6], [3,9]] ** 2 1193 # => 67 96 1194 # 48 99 1195 # 1196 def **(other) 1197 case other 1198 when Integer 1199 x = self 1200 if other <= 0 1201 x = self.inverse 1202 return self.class.identity(self.column_count) if other == 0 1203 other = -other 1204 end 1205 z = nil 1206 loop do 1207 z = z ? z * x : x if other[0] == 1 1208 return z if (other >>= 1).zero? 1209 x *= x 1210 end 1211 when Numeric 1212 v, d, v_inv = eigensystem 1213 v * self.class.diagonal(*d.each(:diagonal).map{|e| e ** other}) * v_inv 1214 else 1215 Matrix.Raise ErrOperationNotDefined, "**", self.class, other.class 1216 end 1217 end 1218 1219 def +@ 1220 self 1221 end 1222 1223 def -@ 1224 collect {|e| -e } 1225 end 1226 1227 #-- 1228 # MATRIX FUNCTIONS -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 1229 #++ 1230 1231 # 1232 # Returns the determinant of the matrix. 1233 # 1234 # Beware that using Float values can yield erroneous results 1235 # because of their lack of precision. 1236 # Consider using exact types like Rational or BigDecimal instead. 1237 # 1238 # Matrix[[7,6], [3,9]].determinant 1239 # => 45 1240 # 1241 def determinant 1242 Matrix.Raise ErrDimensionMismatch unless square? 1243 m = @rows 1244 case row_count 1245 # Up to 4x4, give result using Laplacian expansion by minors. 1246 # This will typically be faster, as well as giving good results 1247 # in case of Floats 1248 when 0 1249 +1 1250 when 1 1251 + m[0][0] 1252 when 2 1253 + m[0][0] * m[1][1] - m[0][1] * m[1][0] 1254 when 3 1255 m0, m1, m2 = m 1256 + m0[0] * m1[1] * m2[2] - m0[0] * m1[2] * m2[1] \ 1257 - m0[1] * m1[0] * m2[2] + m0[1] * m1[2] * m2[0] \ 1258 + m0[2] * m1[0] * m2[1] - m0[2] * m1[1] * m2[0] 1259 when 4 1260 m0, m1, m2, m3 = m 1261 + m0[0] * m1[1] * m2[2] * m3[3] - m0[0] * m1[1] * m2[3] * m3[2] \ 1262 - m0[0] * m1[2] * m2[1] * m3[3] + m0[0] * m1[2] * m2[3] * m3[1] \ 1263 + m0[0] * m1[3] * m2[1] * m3[2] - m0[0] * m1[3] * m2[2] * m3[1] \ 1264 - m0[1] * m1[0] * m2[2] * m3[3] + m0[1] * m1[0] * m2[3] * m3[2] \ 1265 + m0[1] * m1[2] * m2[0] * m3[3] - m0[1] * m1[2] * m2[3] * m3[0] \ 1266 - m0[1] * m1[3] * m2[0] * m3[2] + m0[1] * m1[3] * m2[2] * m3[0] \ 1267 + m0[2] * m1[0] * m2[1] * m3[3] - m0[2] * m1[0] * m2[3] * m3[1] \ 1268 - m0[2] * m1[1] * m2[0] * m3[3] + m0[2] * m1[1] * m2[3] * m3[0] \ 1269 + m0[2] * m1[3] * m2[0] * m3[1] - m0[2] * m1[3] * m2[1] * m3[0] \ 1270 - m0[3] * m1[0] * m2[1] * m3[2] + m0[3] * m1[0] * m2[2] * m3[1] \ 1271 + m0[3] * m1[1] * m2[0] * m3[2] - m0[3] * m1[1] * m2[2] * m3[0] \ 1272 - m0[3] * m1[2] * m2[0] * m3[1] + m0[3] * m1[2] * m2[1] * m3[0] 1273 else 1274 # For bigger matrices, use an efficient and general algorithm. 1275 # Currently, we use the Gauss-Bareiss algorithm 1276 determinant_bareiss 1277 end 1278 end 1279 alias_method :det, :determinant 1280 1281 # 1282 # Private. Use Matrix#determinant 1283 # 1284 # Returns the determinant of the matrix, using 1285 # Bareiss' multistep integer-preserving gaussian elimination. 1286 # It has the same computational cost order O(n^3) as standard Gaussian elimination. 1287 # Intermediate results are fraction free and of lower complexity. 1288 # A matrix of Integers will have thus intermediate results that are also Integers, 1289 # with smaller bignums (if any), while a matrix of Float will usually have 1290 # intermediate results with better precision. 1291 # 1292 private def determinant_bareiss 1293 size = row_count 1294 last = size - 1 1295 a = to_a 1296 no_pivot = Proc.new{ return 0 } 1297 sign = +1 1298 pivot = 1 1299 size.times do |k| 1300 previous_pivot = pivot 1301 if (pivot = a[k][k]) == 0 1302 switch = (k+1 ... size).find(no_pivot) {|row| 1303 a[row][k] != 0 1304 } 1305 a[switch], a[k] = a[k], a[switch] 1306 pivot = a[k][k] 1307 sign = -sign 1308 end 1309 (k+1).upto(last) do |i| 1310 ai = a[i] 1311 (k+1).upto(last) do |j| 1312 ai[j] = (pivot * ai[j] - ai[k] * a[k][j]) / previous_pivot 1313 end 1314 end 1315 end 1316 sign * pivot 1317 end 1318 1319 # 1320 # deprecated; use Matrix#determinant 1321 # 1322 def determinant_e 1323 warn "Matrix#determinant_e is deprecated; use #determinant", uplevel: 1 1324 determinant 1325 end 1326 alias_method :det_e, :determinant_e 1327 1328 # 1329 # Returns a new matrix resulting by stacking horizontally 1330 # the receiver with the given matrices 1331 # 1332 # x = Matrix[[1, 2], [3, 4]] 1333 # y = Matrix[[5, 6], [7, 8]] 1334 # x.hstack(y) # => Matrix[[1, 2, 5, 6], [3, 4, 7, 8]] 1335 # 1336 def hstack(*matrices) 1337 self.class.hstack(self, *matrices) 1338 end 1339 1340 # 1341 # Returns the rank of the matrix. 1342 # Beware that using Float values can yield erroneous results 1343 # because of their lack of precision. 1344 # Consider using exact types like Rational or BigDecimal instead. 1345 # 1346 # Matrix[[7,6], [3,9]].rank 1347 # => 2 1348 # 1349 def rank 1350 # We currently use Bareiss' multistep integer-preserving gaussian elimination 1351 # (see comments on determinant) 1352 a = to_a 1353 last_column = column_count - 1 1354 last_row = row_count - 1 1355 pivot_row = 0 1356 previous_pivot = 1 1357 0.upto(last_column) do |k| 1358 switch_row = (pivot_row .. last_row).find {|row| 1359 a[row][k] != 0 1360 } 1361 if switch_row 1362 a[switch_row], a[pivot_row] = a[pivot_row], a[switch_row] unless pivot_row == switch_row 1363 pivot = a[pivot_row][k] 1364 (pivot_row+1).upto(last_row) do |i| 1365 ai = a[i] 1366 (k+1).upto(last_column) do |j| 1367 ai[j] = (pivot * ai[j] - ai[k] * a[pivot_row][j]) / previous_pivot 1368 end 1369 end 1370 pivot_row += 1 1371 previous_pivot = pivot 1372 end 1373 end 1374 pivot_row 1375 end 1376 1377 # 1378 # deprecated; use Matrix#rank 1379 # 1380 def rank_e 1381 warn "Matrix#rank_e is deprecated; use #rank", uplevel: 1 1382 rank 1383 end 1384 1385 # Returns a matrix with entries rounded to the given precision 1386 # (see Float#round) 1387 # 1388 def round(ndigits=0) 1389 map{|e| e.round(ndigits)} 1390 end 1391 1392 # 1393 # Returns the trace (sum of diagonal elements) of the matrix. 1394 # Matrix[[7,6], [3,9]].trace 1395 # => 16 1396 # 1397 def trace 1398 Matrix.Raise ErrDimensionMismatch unless square? 1399 (0...column_count).inject(0) do |tr, i| 1400 tr + @rows[i][i] 1401 end 1402 end 1403 alias_method :tr, :trace 1404 1405 # 1406 # Returns the transpose of the matrix. 1407 # Matrix[[1,2], [3,4], [5,6]] 1408 # => 1 2 1409 # 3 4 1410 # 5 6 1411 # Matrix[[1,2], [3,4], [5,6]].transpose 1412 # => 1 3 5 1413 # 2 4 6 1414 # 1415 def transpose 1416 return self.class.empty(column_count, 0) if row_count.zero? 1417 new_matrix @rows.transpose, row_count 1418 end 1419 alias_method :t, :transpose 1420 1421 # 1422 # Returns a new matrix resulting by stacking vertically 1423 # the receiver with the given matrices 1424 # 1425 # x = Matrix[[1, 2], [3, 4]] 1426 # y = Matrix[[5, 6], [7, 8]] 1427 # x.vstack(y) # => Matrix[[1, 2], [3, 4], [5, 6], [7, 8]] 1428 # 1429 def vstack(*matrices) 1430 self.class.vstack(self, *matrices) 1431 end 1432 1433 #-- 1434 # DECOMPOSITIONS -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 1435 #++ 1436 1437 # 1438 # Returns the Eigensystem of the matrix; see +EigenvalueDecomposition+. 1439 # m = Matrix[[1, 2], [3, 4]] 1440 # v, d, v_inv = m.eigensystem 1441 # d.diagonal? # => true 1442 # v.inv == v_inv # => true 1443 # (v * d * v_inv).round(5) == m # => true 1444 # 1445 def eigensystem 1446 EigenvalueDecomposition.new(self) 1447 end 1448 alias_method :eigen, :eigensystem 1449 1450 # 1451 # Returns the LUP decomposition of the matrix; see +LUPDecomposition+. 1452 # a = Matrix[[1, 2], [3, 4]] 1453 # l, u, p = a.lup 1454 # l.lower_triangular? # => true 1455 # u.upper_triangular? # => true 1456 # p.permutation? # => true 1457 # l * u == p * a # => true 1458 # a.lup.solve([2, 5]) # => Vector[(1/1), (1/2)] 1459 # 1460 def lup 1461 LUPDecomposition.new(self) 1462 end 1463 alias_method :lup_decomposition, :lup 1464 1465 #-- 1466 # COMPLEX ARITHMETIC -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= 1467 #++ 1468 1469 # 1470 # Returns the conjugate of the matrix. 1471 # Matrix[[Complex(1,2), Complex(0,1), 0], [1, 2, 3]] 1472 # => 1+2i i 0 1473 # 1 2 3 1474 # Matrix[[Complex(1,2), Complex(0,1), 0], [1, 2, 3]].conjugate 1475 # => 1-2i -i 0 1476 # 1 2 3 1477 # 1478 def conjugate 1479 collect(&:conjugate) 1480 end 1481 alias_method :conj, :conjugate 1482 1483 # 1484 # Returns the imaginary part of the matrix. 1485 # Matrix[[Complex(1,2), Complex(0,1), 0], [1, 2, 3]] 1486 # => 1+2i i 0 1487 # 1 2 3 1488 # Matrix[[Complex(1,2), Complex(0,1), 0], [1, 2, 3]].imaginary 1489 # => 2i i 0 1490 # 0 0 0 1491 # 1492 def imaginary 1493 collect(&:imaginary) 1494 end 1495 alias_method :imag, :imaginary 1496 1497 # 1498 # Returns the real part of the matrix. 1499 # Matrix[[Complex(1,2), Complex(0,1), 0], [1, 2, 3]] 1500 # => 1+2i i 0 1501 # 1 2 3 1502 # Matrix[[Complex(1,2), Complex(0,1), 0], [1, 2, 3]].real 1503 # => 1 0 0 1504 # 1 2 3 1505 # 1506 def real 1507 collect(&:real) 1508 end 1509 1510 # 1511 # Returns an array containing matrices corresponding to the real and imaginary 1512 # parts of the matrix 1513 # 1514 # m.rect == [m.real, m.imag] # ==> true for all matrices m 1515 # 1516 def rect 1517 [real, imag] 1518 end 1519 alias_method :rectangular, :rect 1520 1521 #-- 1522 # CONVERTING -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 1523 #++ 1524 1525 # 1526 # The coerce method provides support for Ruby type coercion. 1527 # This coercion mechanism is used by Ruby to handle mixed-type 1528 # numeric operations: it is intended to find a compatible common 1529 # type between the two operands of the operator. 1530 # See also Numeric#coerce. 1531 # 1532 def coerce(other) 1533 case other 1534 when Numeric 1535 return Scalar.new(other), self 1536 else 1537 raise TypeError, "#{self.class} can't be coerced into #{other.class}" 1538 end 1539 end 1540 1541 # 1542 # Returns an array of the row vectors of the matrix. See Vector. 1543 # 1544 def row_vectors 1545 Array.new(row_count) {|i| 1546 row(i) 1547 } 1548 end 1549 1550 # 1551 # Returns an array of the column vectors of the matrix. See Vector. 1552 # 1553 def column_vectors 1554 Array.new(column_count) {|i| 1555 column(i) 1556 } 1557 end 1558 1559 # 1560 # Explicit conversion to a Matrix. Returns self 1561 # 1562 def to_matrix 1563 self 1564 end 1565 1566 # 1567 # Returns an array of arrays that describe the rows of the matrix. 1568 # 1569 def to_a 1570 @rows.collect(&:dup) 1571 end 1572 1573 # Deprecated. 1574 # 1575 # Use map(&:to_f) 1576 def elements_to_f 1577 warn "Matrix#elements_to_f is deprecated, use map(&:to_f)", uplevel: 1 1578 map(&:to_f) 1579 end 1580 1581 # Deprecated. 1582 # 1583 # Use map(&:to_i) 1584 def elements_to_i 1585 warn "Matrix#elements_to_i is deprecated, use map(&:to_i)", uplevel: 1 1586 map(&:to_i) 1587 end 1588 1589 # Deprecated. 1590 # 1591 # Use map(&:to_r) 1592 def elements_to_r 1593 warn "Matrix#elements_to_r is deprecated, use map(&:to_r)", uplevel: 1 1594 map(&:to_r) 1595 end 1596 1597 #-- 1598 # PRINTING -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 1599 #++ 1600 1601 # 1602 # Overrides Object#to_s 1603 # 1604 def to_s 1605 if empty? 1606 "#{self.class}.empty(#{row_count}, #{column_count})" 1607 else 1608 "#{self.class}[" + @rows.collect{|row| 1609 "[" + row.collect{|e| e.to_s}.join(", ") + "]" 1610 }.join(", ")+"]" 1611 end 1612 end 1613 1614 # 1615 # Overrides Object#inspect 1616 # 1617 def inspect 1618 if empty? 1619 "#{self.class}.empty(#{row_count}, #{column_count})" 1620 else 1621 "#{self.class}#{@rows.inspect}" 1622 end 1623 end 1624 1625 # Private helper modules 1626 1627 module ConversionHelper # :nodoc: 1628 # 1629 # Converts the obj to an Array. If copy is set to true 1630 # a copy of obj will be made if necessary. 1631 # 1632 private def convert_to_array(obj, copy = false) # :nodoc: 1633 case obj 1634 when Array 1635 copy ? obj.dup : obj 1636 when Vector 1637 obj.to_a 1638 else 1639 begin 1640 converted = obj.to_ary 1641 rescue Exception => e 1642 raise TypeError, "can't convert #{obj.class} into an Array (#{e.message})" 1643 end 1644 raise TypeError, "#{obj.class}#to_ary should return an Array" unless converted.is_a? Array 1645 converted 1646 end 1647 end 1648 end 1649 1650 extend ConversionHelper 1651 1652 module CoercionHelper # :nodoc: 1653 # 1654 # Applies the operator +oper+ with argument +obj+ 1655 # through coercion of +obj+ 1656 # 1657 private def apply_through_coercion(obj, oper) 1658 coercion = obj.coerce(self) 1659 raise TypeError unless coercion.is_a?(Array) && coercion.length == 2 1660 coercion[0].public_send(oper, coercion[1]) 1661 rescue 1662 raise TypeError, "#{obj.inspect} can't be coerced into #{self.class}" 1663 end 1664 1665 # 1666 # Helper method to coerce a value into a specific class. 1667 # Raises a TypeError if the coercion fails or the returned value 1668 # is not of the right class. 1669 # (from Rubinius) 1670 # 1671 def self.coerce_to(obj, cls, meth) # :nodoc: 1672 return obj if obj.kind_of?(cls) 1673 raise TypeError, "Expected a #{cls} but got a #{obj.class}" unless obj.respond_to? meth 1674 begin 1675 ret = obj.__send__(meth) 1676 rescue Exception => e 1677 raise TypeError, "Coercion error: #{obj.inspect}.#{meth} => #{cls} failed:\n" \ 1678 "(#{e.message})" 1679 end 1680 raise TypeError, "Coercion error: obj.#{meth} did NOT return a #{cls} (was #{ret.class})" unless ret.kind_of? cls 1681 ret 1682 end 1683 1684 def self.coerce_to_int(obj) 1685 coerce_to(obj, Integer, :to_int) 1686 end 1687 1688 def self.coerce_to_matrix(obj) 1689 coerce_to(obj, Matrix, :to_matrix) 1690 end 1691 1692 # Returns `nil` for non Ranges 1693 # Checks range validity, return canonical range with 0 <= begin <= end < count 1694 def self.check_range(val, count, kind) 1695 canonical = (val.begin + (val.begin < 0 ? count : 0)).. 1696 (val.end ? val.end + (val.end < 0 ? count : 0) - (val.exclude_end? ? 1 : 0) 1697 : count - 1) 1698 unless 0 <= canonical.begin && canonical.begin <= canonical.end && canonical.end < count 1699 raise IndexError, "given range #{val} is outside of #{kind} dimensions: 0...#{count}" 1700 end 1701 canonical 1702 end 1703 1704 def self.check_int(val, count, kind) 1705 val = CoercionHelper.coerce_to_int(val) 1706 if val >= count || val < -count 1707 raise IndexError, "given #{kind} #{val} is outside of #{-count}...#{count}" 1708 end 1709 val 1710 end 1711 end 1712 1713 include CoercionHelper 1714 1715 # Private CLASS 1716 1717 class Scalar < Numeric # :nodoc: 1718 include ExceptionForMatrix 1719 include CoercionHelper 1720 1721 def initialize(value) 1722 @value = value 1723 end 1724 1725 # ARITHMETIC 1726 def +(other) 1727 case other 1728 when Numeric 1729 Scalar.new(@value + other) 1730 when Vector, Matrix 1731 Scalar.Raise ErrOperationNotDefined, "+", @value.class, other.class 1732 else 1733 apply_through_coercion(other, __method__) 1734 end 1735 end 1736 1737 def -(other) 1738 case other 1739 when Numeric 1740 Scalar.new(@value - other) 1741 when Vector, Matrix 1742 Scalar.Raise ErrOperationNotDefined, "-", @value.class, other.class 1743 else 1744 apply_through_coercion(other, __method__) 1745 end 1746 end 1747 1748 def *(other) 1749 case other 1750 when Numeric 1751 Scalar.new(@value * other) 1752 when Vector, Matrix 1753 other.collect{|e| @value * e} 1754 else 1755 apply_through_coercion(other, __method__) 1756 end 1757 end 1758 1759 def /(other) 1760 case other 1761 when Numeric 1762 Scalar.new(@value / other) 1763 when Vector 1764 Scalar.Raise ErrOperationNotDefined, "/", @value.class, other.class 1765 when Matrix 1766 self * other.inverse 1767 else 1768 apply_through_coercion(other, __method__) 1769 end 1770 end 1771 1772 def **(other) 1773 case other 1774 when Numeric 1775 Scalar.new(@value ** other) 1776 when Vector 1777 Scalar.Raise ErrOperationNotDefined, "**", @value.class, other.class 1778 when Matrix 1779 #other.powered_by(self) 1780 Scalar.Raise ErrOperationNotImplemented, "**", @value.class, other.class 1781 else 1782 apply_through_coercion(other, __method__) 1783 end 1784 end 1785 end 1786 1787end 1788 1789 1790# 1791# The +Vector+ class represents a mathematical vector, which is useful in its own right, and 1792# also constitutes a row or column of a Matrix. 1793# 1794# == Method Catalogue 1795# 1796# To create a Vector: 1797# * Vector.[](*array) 1798# * Vector.elements(array, copy = true) 1799# * Vector.basis(size: n, index: k) 1800# * Vector.zero(n) 1801# 1802# To access elements: 1803# * #[](i) 1804# 1805# To set elements: 1806# * #[]=(i, v) 1807# 1808# To enumerate the elements: 1809# * #each2(v) 1810# * #collect2(v) 1811# 1812# Properties of vectors: 1813# * #angle_with(v) 1814# * Vector.independent?(*vs) 1815# * #independent?(*vs) 1816# * #zero? 1817# 1818# Vector arithmetic: 1819# * #*(x) "is matrix or number" 1820# * #+(v) 1821# * #-(v) 1822# * #/(v) 1823# * #+@ 1824# * #-@ 1825# 1826# Vector functions: 1827# * #inner_product(v), dot(v) 1828# * #cross_product(v), cross(v) 1829# * #collect 1830# * #collect! 1831# * #magnitude 1832# * #map 1833# * #map! 1834# * #map2(v) 1835# * #norm 1836# * #normalize 1837# * #r 1838# * #round 1839# * #size 1840# 1841# Conversion to other data types: 1842# * #covector 1843# * #to_a 1844# * #coerce(other) 1845# 1846# String representations: 1847# * #to_s 1848# * #inspect 1849# 1850class Vector 1851 include ExceptionForMatrix 1852 include Enumerable 1853 include Matrix::CoercionHelper 1854 extend Matrix::ConversionHelper 1855 #INSTANCE CREATION 1856 1857 private_class_method :new 1858 attr_reader :elements 1859 protected :elements 1860 1861 # 1862 # Creates a Vector from a list of elements. 1863 # Vector[7, 4, ...] 1864 # 1865 def Vector.[](*array) 1866 new convert_to_array(array, false) 1867 end 1868 1869 # 1870 # Creates a vector from an Array. The optional second argument specifies 1871 # whether the array itself or a copy is used internally. 1872 # 1873 def Vector.elements(array, copy = true) 1874 new convert_to_array(array, copy) 1875 end 1876 1877 # 1878 # Returns a standard basis +n+-vector, where k is the index. 1879 # 1880 # Vector.basis(size:, index:) # => Vector[0, 1, 0] 1881 # 1882 def Vector.basis(size:, index:) 1883 raise ArgumentError, "invalid size (#{size} for 1..)" if size < 1 1884 raise ArgumentError, "invalid index (#{index} for 0...#{size})" unless 0 <= index && index < size 1885 array = Array.new(size, 0) 1886 array[index] = 1 1887 new convert_to_array(array, false) 1888 end 1889 1890 # 1891 # Return a zero vector. 1892 # 1893 # Vector.zero(3) => Vector[0, 0, 0] 1894 # 1895 def Vector.zero(size) 1896 raise ArgumentError, "invalid size (#{size} for 0..)" if size < 0 1897 array = Array.new(size, 0) 1898 new convert_to_array(array, false) 1899 end 1900 1901 # 1902 # Vector.new is private; use Vector[] or Vector.elements to create. 1903 # 1904 def initialize(array) 1905 # No checking is done at this point. 1906 @elements = array 1907 end 1908 1909 # ACCESSING 1910 1911 # 1912 # :call-seq: 1913 # vector[range] 1914 # vector[integer] 1915 # 1916 # Returns element or elements of the vector. 1917 # 1918 def [](i) 1919 @elements[i] 1920 end 1921 alias element [] 1922 alias component [] 1923 1924 # 1925 # :call-seq: 1926 # vector[range] = new_vector 1927 # vector[range] = row_matrix 1928 # vector[range] = new_element 1929 # vector[integer] = new_element 1930 # 1931 # Set element or elements of vector. 1932 # 1933 def []=(i, v) 1934 raise FrozenError, "can't modify frozen Vector" if frozen? 1935 if i.is_a?(Range) 1936 range = Matrix::CoercionHelper.check_range(i, size, :vector) 1937 set_range(range, v) 1938 else 1939 index = Matrix::CoercionHelper.check_int(i, size, :index) 1940 set_value(index, v) 1941 end 1942 end 1943 alias set_element []= 1944 alias set_component []= 1945 private :set_element, :set_component 1946 1947 private def set_value(index, value) 1948 @elements[index] = value 1949 end 1950 1951 private def set_range(range, value) 1952 if value.is_a?(Vector) 1953 raise ArgumentError, "vector to be set has wrong size" unless range.size == value.size 1954 @elements[range] = value.elements 1955 elsif value.is_a?(Matrix) 1956 Matrix.Raise ErrDimensionMismatch unless value.row_count == 1 1957 @elements[range] = value.row(0).elements 1958 else 1959 @elements[range] = Array.new(range.size, value) 1960 end 1961 end 1962 1963 # Returns a vector with entries rounded to the given precision 1964 # (see Float#round) 1965 # 1966 def round(ndigits=0) 1967 map{|e| e.round(ndigits)} 1968 end 1969 1970 # 1971 # Returns the number of elements in the vector. 1972 # 1973 def size 1974 @elements.size 1975 end 1976 1977 #-- 1978 # ENUMERATIONS -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 1979 #++ 1980 1981 # 1982 # Iterate over the elements of this vector 1983 # 1984 def each(&block) 1985 return to_enum(:each) unless block_given? 1986 @elements.each(&block) 1987 self 1988 end 1989 1990 # 1991 # Iterate over the elements of this vector and +v+ in conjunction. 1992 # 1993 def each2(v) # :yield: e1, e2 1994 raise TypeError, "Integer is not like Vector" if v.kind_of?(Integer) 1995 Vector.Raise ErrDimensionMismatch if size != v.size 1996 return to_enum(:each2, v) unless block_given? 1997 size.times do |i| 1998 yield @elements[i], v[i] 1999 end 2000 self 2001 end 2002 2003 # 2004 # Collects (as in Enumerable#collect) over the elements of this vector and +v+ 2005 # in conjunction. 2006 # 2007 def collect2(v) # :yield: e1, e2 2008 raise TypeError, "Integer is not like Vector" if v.kind_of?(Integer) 2009 Vector.Raise ErrDimensionMismatch if size != v.size 2010 return to_enum(:collect2, v) unless block_given? 2011 Array.new(size) do |i| 2012 yield @elements[i], v[i] 2013 end 2014 end 2015 2016 #-- 2017 # PROPERTIES -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 2018 #++ 2019 2020 # 2021 # Returns +true+ iff all of vectors are linearly independent. 2022 # 2023 # Vector.independent?(Vector[1,0], Vector[0,1]) 2024 # => true 2025 # 2026 # Vector.independent?(Vector[1,2], Vector[2,4]) 2027 # => false 2028 # 2029 def Vector.independent?(*vs) 2030 vs.each do |v| 2031 raise TypeError, "expected Vector, got #{v.class}" unless v.is_a?(Vector) 2032 Vector.Raise ErrDimensionMismatch unless v.size == vs.first.size 2033 end 2034 return false if vs.count > vs.first.size 2035 Matrix[*vs].rank.eql?(vs.count) 2036 end 2037 2038 # 2039 # Returns +true+ iff all of vectors are linearly independent. 2040 # 2041 # Vector[1,0].independent?(Vector[0,1]) 2042 # => true 2043 # 2044 # Vector[1,2].independent?(Vector[2,4]) 2045 # => false 2046 # 2047 def independent?(*vs) 2048 self.class.independent?(self, *vs) 2049 end 2050 2051 # 2052 # Returns +true+ iff all elements are zero. 2053 # 2054 def zero? 2055 all?(&:zero?) 2056 end 2057 2058 def freeze 2059 @elements.freeze 2060 super 2061 end 2062 2063 # 2064 # Called for dup & clone. 2065 # 2066 private def initialize_copy(v) 2067 super 2068 @elements = @elements.dup unless frozen? 2069 end 2070 2071 2072 #-- 2073 # COMPARING -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 2074 #++ 2075 2076 # 2077 # Returns +true+ iff the two vectors have the same elements in the same order. 2078 # 2079 def ==(other) 2080 return false unless Vector === other 2081 @elements == other.elements 2082 end 2083 2084 def eql?(other) 2085 return false unless Vector === other 2086 @elements.eql? other.elements 2087 end 2088 2089 # 2090 # Returns a hash-code for the vector. 2091 # 2092 def hash 2093 @elements.hash 2094 end 2095 2096 #-- 2097 # ARITHMETIC -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 2098 #++ 2099 2100 # 2101 # Multiplies the vector by +x+, where +x+ is a number or a matrix. 2102 # 2103 def *(x) 2104 case x 2105 when Numeric 2106 els = @elements.collect{|e| e * x} 2107 self.class.elements(els, false) 2108 when Matrix 2109 Matrix.column_vector(self) * x 2110 when Vector 2111 Vector.Raise ErrOperationNotDefined, "*", self.class, x.class 2112 else 2113 apply_through_coercion(x, __method__) 2114 end 2115 end 2116 2117 # 2118 # Vector addition. 2119 # 2120 def +(v) 2121 case v 2122 when Vector 2123 Vector.Raise ErrDimensionMismatch if size != v.size 2124 els = collect2(v) {|v1, v2| 2125 v1 + v2 2126 } 2127 self.class.elements(els, false) 2128 when Matrix 2129 Matrix.column_vector(self) + v 2130 else 2131 apply_through_coercion(v, __method__) 2132 end 2133 end 2134 2135 # 2136 # Vector subtraction. 2137 # 2138 def -(v) 2139 case v 2140 when Vector 2141 Vector.Raise ErrDimensionMismatch if size != v.size 2142 els = collect2(v) {|v1, v2| 2143 v1 - v2 2144 } 2145 self.class.elements(els, false) 2146 when Matrix 2147 Matrix.column_vector(self) - v 2148 else 2149 apply_through_coercion(v, __method__) 2150 end 2151 end 2152 2153 # 2154 # Vector division. 2155 # 2156 def /(x) 2157 case x 2158 when Numeric 2159 els = @elements.collect{|e| e / x} 2160 self.class.elements(els, false) 2161 when Matrix, Vector 2162 Vector.Raise ErrOperationNotDefined, "/", self.class, x.class 2163 else 2164 apply_through_coercion(x, __method__) 2165 end 2166 end 2167 2168 def +@ 2169 self 2170 end 2171 2172 def -@ 2173 collect {|e| -e } 2174 end 2175 2176 #-- 2177 # VECTOR FUNCTIONS -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 2178 #++ 2179 2180 # 2181 # Returns the inner product of this vector with the other. 2182 # Vector[4,7].inner_product Vector[10,1] => 47 2183 # 2184 def inner_product(v) 2185 Vector.Raise ErrDimensionMismatch if size != v.size 2186 2187 p = 0 2188 each2(v) {|v1, v2| 2189 p += v1 * v2.conj 2190 } 2191 p 2192 end 2193 alias_method :dot, :inner_product 2194 2195 # 2196 # Returns the cross product of this vector with the others. 2197 # Vector[1, 0, 0].cross_product Vector[0, 1, 0] => Vector[0, 0, 1] 2198 # 2199 # It is generalized to other dimensions to return a vector perpendicular 2200 # to the arguments. 2201 # Vector[1, 2].cross_product # => Vector[-2, 1] 2202 # Vector[1, 0, 0, 0].cross_product( 2203 # Vector[0, 1, 0, 0], 2204 # Vector[0, 0, 1, 0] 2205 # ) #=> Vector[0, 0, 0, 1] 2206 # 2207 def cross_product(*vs) 2208 raise ErrOperationNotDefined, "cross product is not defined on vectors of dimension #{size}" unless size >= 2 2209 raise ArgumentError, "wrong number of arguments (#{vs.size} for #{size - 2})" unless vs.size == size - 2 2210 vs.each do |v| 2211 raise TypeError, "expected Vector, got #{v.class}" unless v.is_a? Vector 2212 Vector.Raise ErrDimensionMismatch unless v.size == size 2213 end 2214 case size 2215 when 2 2216 Vector[-@elements[1], @elements[0]] 2217 when 3 2218 v = vs[0] 2219 Vector[ v[2]*@elements[1] - v[1]*@elements[2], 2220 v[0]*@elements[2] - v[2]*@elements[0], 2221 v[1]*@elements[0] - v[0]*@elements[1] ] 2222 else 2223 rows = self, *vs, Array.new(size) {|i| Vector.basis(size: size, index: i) } 2224 Matrix.rows(rows).laplace_expansion(row: size - 1) 2225 end 2226 end 2227 alias_method :cross, :cross_product 2228 2229 # 2230 # Like Array#collect. 2231 # 2232 def collect(&block) # :yield: e 2233 return to_enum(:collect) unless block_given? 2234 els = @elements.collect(&block) 2235 self.class.elements(els, false) 2236 end 2237 alias_method :map, :collect 2238 2239 # 2240 # Like Array#collect! 2241 # 2242 def collect!(&block) 2243 return to_enum(:collect!) unless block_given? 2244 raise FrozenError, "can't modify frozen Vector" if frozen? 2245 @elements.collect!(&block) 2246 self 2247 end 2248 alias map! collect! 2249 2250 # 2251 # Returns the modulus (Pythagorean distance) of the vector. 2252 # Vector[5,8,2].r => 9.643650761 2253 # 2254 def magnitude 2255 Math.sqrt(@elements.inject(0) {|v, e| v + e.abs2}) 2256 end 2257 alias_method :r, :magnitude 2258 alias_method :norm, :magnitude 2259 2260 # 2261 # Like Vector#collect2, but returns a Vector instead of an Array. 2262 # 2263 def map2(v, &block) # :yield: e1, e2 2264 return to_enum(:map2, v) unless block_given? 2265 els = collect2(v, &block) 2266 self.class.elements(els, false) 2267 end 2268 2269 class ZeroVectorError < StandardError 2270 end 2271 # 2272 # Returns a new vector with the same direction but with norm 1. 2273 # v = Vector[5,8,2].normalize 2274 # # => Vector[0.5184758473652127, 0.8295613557843402, 0.20739033894608505] 2275 # v.norm => 1.0 2276 # 2277 def normalize 2278 n = magnitude 2279 raise ZeroVectorError, "Zero vectors can not be normalized" if n == 0 2280 self / n 2281 end 2282 2283 # 2284 # Returns an angle with another vector. Result is within the [0..Math::PI]. 2285 # Vector[1,0].angle_with(Vector[0,1]) 2286 # # => Math::PI / 2 2287 # 2288 def angle_with(v) 2289 raise TypeError, "Expected a Vector, got a #{v.class}" unless v.is_a?(Vector) 2290 Vector.Raise ErrDimensionMismatch if size != v.size 2291 prod = magnitude * v.magnitude 2292 raise ZeroVectorError, "Can't get angle of zero vector" if prod == 0 2293 dot = inner_product(v) 2294 if dot.abs >= prod 2295 dot.positive? ? 0 : Math::PI 2296 else 2297 Math.acos(dot / prod) 2298 end 2299 end 2300 2301 #-- 2302 # CONVERTING 2303 #++ 2304 2305 # 2306 # Creates a single-row matrix from this vector. 2307 # 2308 def covector 2309 Matrix.row_vector(self) 2310 end 2311 2312 # 2313 # Returns the elements of the vector in an array. 2314 # 2315 def to_a 2316 @elements.dup 2317 end 2318 2319 # 2320 # Return a single-column matrix from this vector 2321 # 2322 def to_matrix 2323 Matrix.column_vector(self) 2324 end 2325 2326 def elements_to_f 2327 warn "Vector#elements_to_f is deprecated", uplevel: 1 2328 map(&:to_f) 2329 end 2330 2331 def elements_to_i 2332 warn "Vector#elements_to_i is deprecated", uplevel: 1 2333 map(&:to_i) 2334 end 2335 2336 def elements_to_r 2337 warn "Vector#elements_to_r is deprecated", uplevel: 1 2338 map(&:to_r) 2339 end 2340 2341 # 2342 # The coerce method provides support for Ruby type coercion. 2343 # This coercion mechanism is used by Ruby to handle mixed-type 2344 # numeric operations: it is intended to find a compatible common 2345 # type between the two operands of the operator. 2346 # See also Numeric#coerce. 2347 # 2348 def coerce(other) 2349 case other 2350 when Numeric 2351 return Matrix::Scalar.new(other), self 2352 else 2353 raise TypeError, "#{self.class} can't be coerced into #{other.class}" 2354 end 2355 end 2356 2357 #-- 2358 # PRINTING -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 2359 #++ 2360 2361 # 2362 # Overrides Object#to_s 2363 # 2364 def to_s 2365 "Vector[" + @elements.join(", ") + "]" 2366 end 2367 2368 # 2369 # Overrides Object#inspect 2370 # 2371 def inspect 2372 "Vector" + @elements.inspect 2373 end 2374end 2375