matrix 0.0.1 → 0.4.1

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