matrix 0.0.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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