matrix 0.0.1 → 0.1.0

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