extendmatrix 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data.tar.gz.sig ADDED
@@ -0,0 +1,2 @@
1
+ �R&ש��pV�%0���T�ljਾ�p��! }r)� ?�V=|��G�שv�͜+[��ȴ{��Zz�=*+� ��ڕ)�d������P��vgr H��8>�� ]�>x܆Ƕ��w�zp����-�����(
2
+ ۺן�>����Jm|���
data/History.txt ADDED
@@ -0,0 +1,3 @@
1
+ === 0.0.60 / 2010-05-04
2
+
3
+ * First gem version
data/Manifest.txt ADDED
@@ -0,0 +1,7 @@
1
+ History.txt
2
+ Manifest.txt
3
+ ORIGINAL_README.txt
4
+ README.txt
5
+ Rakefile
6
+ lib/extendmatrix.rb
7
+ spec/extendmatrix_spec.rb
@@ -0,0 +1,22 @@
1
+ Extensions to the Ruby Matrix module
2
+ ====================================
3
+
4
+ This README is a small description of the work done by Cosmin Bonchis as a
5
+ Google Summer of Code 2007 project for Ruby Central Inc.
6
+
7
+ The project consists of some enhancements to the Ruby "Matrix" module and includes: LU and QR (Householder, Givens, Gram Schmidt, Hessenberg) decompositions, bidiagonalization, eigenvalue and eigenvector calculations.
8
+
9
+ This archive contains in extendmatrix.rb file the source code of the project, an implementation of mapcar used in extending matrix, and all the tests files in the "tests" directory.
10
+
11
+ The code can also be found on the RubyForge repository at http://matrix.rubyforge.org/svn/trunk/ or the project's SVN repository can be checked out through anonymous access with the following command(s).
12
+
13
+ svn checkout svn://rubyforge.org/var/svn/matrix
14
+ svn checkout http://matrix.rubyforge.org/svn/trunk/
15
+
16
+
17
+ Relevant URLs:
18
+ ==============
19
+
20
+ Project sources:
21
+ http://matrix.rubyforge.org/svn/trunk/
22
+
data/README.txt ADDED
@@ -0,0 +1,32 @@
1
+ = extendmatrix
2
+
3
+ * http://github.com/clbustos/extendmatrix
4
+
5
+ == DESCRIPTION:
6
+
7
+ The project consists of some enhancements to the Ruby "Matrix" module and includes: LU and QR (Householder, Givens, Gram Schmidt, Hessenberg) decompositions, bidiagonalization, eigenvalue and eigenvector calculations.
8
+ Include some aditional code to obtains marginal for rows and columns.
9
+
10
+ The original code can be found on the RubyForge repository at http://matrix.rubyforge.org/svn/trunk/ or the project's SVN repository can be checked out through anonymous access with the following command(s).
11
+
12
+ Work done by Cosmin Bonchis as a Google Summer of Code 2007 project for Ruby Central Inc.
13
+
14
+ == SYNOPSIS:
15
+
16
+ require 'matrix_extensions'
17
+ m = Matrix.new(4, 3){|i, j| i * 3 + j}
18
+ m[1, 2].should == 5
19
+ m[3, 1..2].should == Vector[10, 11]
20
+ m[0..1, 0..2].should == Matrix[[0, 1, 2], [3, 4, 5]]
21
+
22
+ == REQUIREMENTS:
23
+
24
+ * Only Ruby
25
+
26
+ == INSTALL:
27
+
28
+ * sudo gem install matrix-extensions
29
+
30
+ == LICENSE:
31
+
32
+ One of http://www.opensource.org/licenses/
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ # -*- ruby -*-
2
+ $:.unshift(File.dirname(__FILE__)+"/lib")
3
+ require 'rubygems'
4
+ require 'hoe'
5
+ require 'extendmatrix.rb'
6
+ Hoe.plugin :git
7
+ Hoe.spec 'extendmatrix' do
8
+ self.rubyforge_name = 'ruby-statsample'
9
+ self.version = Matrix::EXTENSION_VERSION
10
+ self.developer('Cosmin Bonchis', 'cbonchis_info.uvt.ro')
11
+ end
12
+
13
+ # vim: syntax=ruby
@@ -0,0 +1,1048 @@
1
+ require 'rational'
2
+ require 'matrix'
3
+
4
+ class Vector
5
+ include Enumerable
6
+ # fix for Vector#coerce on Ruby 1.8.x
7
+ if RUBY_VERSION<="1.9.0"
8
+ alias_method :old_coerce, :coerce
9
+ def coerce(other)
10
+ case other
11
+ when Numeric
12
+ return Matrix::Scalar.new(other), self
13
+ else
14
+ raise TypeError, "#{self.class} can't be coerced into #{other.class}"
15
+ end
16
+ end
17
+
18
+ end
19
+
20
+ module Norm
21
+ def Norm.sqnorm(obj, p)
22
+ sum = 0
23
+ obj.each{|x| sum += x ** p}
24
+ sum
25
+ end
26
+ end
27
+
28
+ alias :length :size
29
+ alias :index :[]
30
+ #
31
+ # Returns the value of an index vector or
32
+ # a Vector with the values of a range
33
+ # v = Vector[1, 2, 3, 4]
34
+ # v[0] => 1
35
+ # v[0..2] => Vector[1, 2, 3]
36
+ #
37
+ def [](i)
38
+ case i
39
+ when Range
40
+ Vector[*to_a.slice(i)]
41
+ else
42
+ index(i)
43
+ end
44
+ end
45
+
46
+ #
47
+ # Sets a vector value/(range of values) with a new value/(values from a vector)
48
+ # v = Vector[1, 2, 3]
49
+ # v[2] = 9 => Vector[1, 2, 9]
50
+ # v[1..2] = Vector[9, 9, 9, 9, 9] => v: Vector[1, 9, 9]
51
+ #
52
+ def []=(i, v)
53
+ case i
54
+ when Range
55
+ (self.size..i.begin - 1).each{|e| self[e] = 0} # self.size must be in the first place because the size of self can be modified
56
+ [v.size, i.entries.size].min.times {|e| self[e + i.begin] = v[e]}
57
+ (v.size + i.begin .. i.end).each {|e| self[e] = 0}
58
+ else
59
+ @elements[i]=v
60
+ end
61
+ end
62
+
63
+ class << self
64
+ #
65
+ # Returns a concatenated Vector
66
+ #
67
+ def concat(*args)
68
+ v = []
69
+ args.each{|x| v += x.to_a}
70
+ Vector[*v]
71
+ end
72
+ end
73
+
74
+ #
75
+ # Changes the elements of vector and returns a Vector
76
+ #
77
+ def collect!
78
+ els = @elements.collect! {|v| yield(v)}
79
+ Vector.elements(els, false)
80
+ end
81
+
82
+ #
83
+ # Iterates the elements of a vector
84
+ #
85
+ def each
86
+ (0...size).each {|i| yield(self[i])}
87
+ nil
88
+ end
89
+
90
+ #
91
+ # Returns the maximum element of a vector
92
+ #
93
+ def max
94
+ to_a.max
95
+ end
96
+
97
+ #
98
+ # Returns the minimum element of a vector
99
+ #
100
+ def min
101
+ to_a.min
102
+ end
103
+
104
+ #
105
+ # Returns the p-norm of a vector
106
+ #
107
+ def norm(p = 2)
108
+ Norm.sqnorm(self, p) ** (Float(1)/p)
109
+ end
110
+
111
+ #
112
+ # Returns the infinite-norm
113
+ #
114
+ def norm_inf
115
+ [min.abs, max.abs].max
116
+ end
117
+
118
+ #
119
+ # Returns a slice of vector
120
+ #
121
+ def slice(*args)
122
+ Vector[*to_a.slice(*args)]
123
+ end
124
+
125
+ def slice_set(v, b, e)
126
+ for i in b..e
127
+ self[i] = v[i-b]
128
+ end
129
+ end
130
+
131
+ #
132
+ # Sets a slice of vector
133
+ #
134
+ def slice=(args)
135
+ case args[1]
136
+ when Range
137
+ slice_set(args[0], args[1].begin, args[1].last)
138
+ else
139
+ slice_set(args[0], args[1], args[2])
140
+ end
141
+ end
142
+
143
+ #
144
+ # Return the vector divided by a scalar
145
+ #
146
+ def /(c)
147
+ map {|e| e.quo(c)}
148
+ end
149
+
150
+ #
151
+ # Return the matrix column coresponding to the vector transpose
152
+ #
153
+ def transpose
154
+ Matrix[self.to_a]
155
+ end
156
+
157
+ alias :t :transpose
158
+
159
+ #
160
+ # Computes the Householder vector (MC, Golub, p. 210, algorithm 5.1.1)
161
+ #
162
+ def house
163
+ s = self[1..length-1]
164
+ sigma = s.inner_product(s)
165
+ v = clone; v[0] = 1
166
+ if sigma == 0
167
+ beta = 0
168
+ else
169
+ mu = Math.sqrt(self[0] ** 2 + sigma)
170
+ if self[0] <= 0
171
+ v[0] = self[0] - mu
172
+ else
173
+ v[0] = - sigma.quo(self[0] + mu)
174
+ end
175
+ v2 = v[0] ** 2
176
+ beta = 2 * v2.quo(sigma + v2)
177
+ v /= v[0]
178
+ end
179
+ return v, beta
180
+ end
181
+
182
+ #
183
+ #Projection operator
184
+ #(http://en.wikipedia.org/wiki/Gram-Schmidt_process#The_Gram.E2.80.93Schmidt_process)
185
+ #
186
+ def proj(v)
187
+ vp = v.inner_product(self)
188
+ vp = Float vp if vp.is_a?(Integer)
189
+ self * (vp / inner_product(self))
190
+ end
191
+
192
+ #
193
+ # Return the vector normalized
194
+ #
195
+ def normalize
196
+ self / self.norm
197
+ end
198
+
199
+ #
200
+ # Stabilized Gram-Schmidt process
201
+ # (http://en.wikipedia.org/wiki/Gram-Schmidt_process#Algorithm)
202
+ #
203
+ def self.gram_schmidt(*vectors)
204
+ v = vectors.clone
205
+ for j in 0...v.size
206
+ for i in 0..j-1
207
+ v[j] -= v[i] * v[j].inner_product(v[i])
208
+ end
209
+ v[j] /= v[j].norm
210
+ end
211
+ v
212
+ end
213
+ end
214
+
215
+ class Matrix
216
+
217
+ EXTENSION_VERSION="0.1.0"
218
+ include Enumerable
219
+ public_class_method :new
220
+
221
+ attr_reader :rows, :wrap
222
+ @wrap = nil
223
+
224
+ def initialize(*argv)
225
+ return initialize_old(*argv) if argv[0].is_a?(Symbol)
226
+ n, m, val = argv; val = 0 if not val
227
+ f = (block_given?)? lambda {|i,j| yield(i, j)} : lambda {|i,j| val}
228
+ init_rows((0...n).collect {|i| (0...m).collect {|j| f.call(i,j)}}, true)
229
+ end
230
+
231
+ #
232
+ # For invoking a method
233
+ #
234
+ def initialize_old(init_method, *argv)
235
+ self.send(init_method, *argv)
236
+ end
237
+
238
+ alias :ids :[]
239
+ #
240
+ # Return a value or a vector/matrix of values depending
241
+ # if the indexes are ranges or not
242
+ # m = Matrix.new(4, 3){|i, j| i * 3 + j}
243
+ # m: 0 1 2
244
+ # 3 4 5
245
+ # 6 7 8
246
+ # 9 10 11
247
+ # m[1, 2] => 5
248
+ # m[3,1..2] => Vector[10, 11]
249
+ # m[0..1, 0..2] => Matrix[[0, 1, 2], [3, 4, 5]]
250
+ #
251
+ def [](i, j)
252
+ case i
253
+ when Range
254
+ case j
255
+ when Range
256
+ Matrix[*i.collect{|l| self.row(l)[j].to_a}]
257
+ else
258
+ column(j)[i]
259
+ end
260
+ else
261
+ case j
262
+ when Range
263
+ row(i)[j]
264
+ else
265
+ ids(i, j)
266
+ end
267
+ end
268
+ end
269
+
270
+
271
+
272
+
273
+ #
274
+ # Set the values of a matrix
275
+ # m = Matrix.new(3, 3){|i, j| i * 3 + j}
276
+ # m: 0 1 2
277
+ # 3 4 5
278
+ # 6 7 8
279
+ # m[1, 2] = 9 => Matrix[[0, 1, 2], [3, 4, 9], [6, 7, 8]]
280
+ # m[2,1..2] = Vector[8, 8] => Matrix[[0, 1, 2], [3, 8, 8], [6, 7, 8]]
281
+ # m[0..1, 0..1] = Matrix[[0, 0, 0],[0, 0, 0]]
282
+ # => Matrix[[0, 0, 2], [0, 0, 8], [6, 7, 8]]
283
+ #
284
+ def []=(i, j, v)
285
+ case i
286
+ when Range
287
+ if i.entries.size == 1
288
+ self[i.begin, j] = (v.is_a?(Matrix) ? v.row(0) : v)
289
+ else
290
+ case j
291
+ when Range
292
+ if j.entries.size == 1
293
+ self[i, j.begin] = (v.is_a?(Matrix) ? v.column(0) : v)
294
+ else
295
+ i.each{|l| self.row= l, v.row(l - i.begin), j}
296
+ end
297
+ else
298
+ self.column= j, v, i
299
+ end
300
+ end
301
+ else
302
+ case j
303
+ when Range
304
+ if j.entries.size == 1
305
+ self[i, j.begin] = (v.is_a?(Vector) ? v[0] : v)
306
+ else
307
+ self.row= i, v, j
308
+ end
309
+ else
310
+ @rows[i][j] = (v.is_a?(Vector) ? v[0] : v)
311
+
312
+ end
313
+ end
314
+ end
315
+
316
+ #
317
+ # Return a clone matrix
318
+ #
319
+ def clone
320
+ super
321
+ end
322
+
323
+ def initialize_copy(orig)
324
+ init_rows(orig.rows, true)
325
+ self.wrap=(orig.wrap)
326
+ end
327
+
328
+
329
+ class << self
330
+ #
331
+ # Creates a matrix with the given matrices as diagonal blocks
332
+ #
333
+ def diag(*args)
334
+ dsize = 0
335
+ sizes = args.collect{|e| x = (e.is_a?(Matrix)) ? e.row_size : 1; dsize += x; x}
336
+ m = Matrix.zero(dsize)
337
+ count = 0
338
+
339
+ sizes.size.times{|i|
340
+ range = count..(count+sizes[i]-1)
341
+ m[range, range] = args[i]
342
+ count += sizes[i]
343
+ }
344
+ m
345
+ end
346
+
347
+ #
348
+ # Tests if all the elements of two matrix are equal in delta
349
+ #
350
+ def equal_in_delta?(m0, m1, delta = 1.0e-10)
351
+ delta = delta.abs
352
+ m0.row_size.times {|i|
353
+ m0.column_size.times {|j|
354
+ x=m0[i,j]; y=m1[i,j]
355
+ return false if (x < y - delta or x > y + delta)
356
+ }
357
+ }
358
+ true
359
+ end
360
+
361
+ #
362
+ # Tests if all the diagonal elements of two matrix are equal in delta
363
+ #
364
+ def diag_in_delta?(m1, m0, eps = 1.0e-10)
365
+ n = m1.row_size
366
+ return false if n != m0.row_size or m1.column_size != m0.column_size
367
+ n.times{|i|
368
+ return false if (m1[i,i]-m0[i,i]).abs > eps
369
+ }
370
+ true
371
+ end
372
+ end
373
+
374
+ #
375
+ # Returns the matrix divided by a scalar
376
+ #
377
+ def quo(v)
378
+ map {|e| e.quo(v)}
379
+ end
380
+
381
+ #
382
+ # quo seems always desirable
383
+ #
384
+ alias :/ :quo
385
+
386
+ #
387
+ # Set de values of a matrix and the value of wrap property
388
+ #
389
+ def set(m)
390
+ 0.upto(m.row_size - 1) do |i|
391
+ 0.upto(m.column_size - 1) do |j|
392
+ self[i, j] = m[i, j]
393
+ end
394
+ end
395
+ self.wrap = m.wrap
396
+ end
397
+
398
+ def wraplate(ijwrap = "")
399
+ "class << self
400
+ def [](i, j)
401
+ #{ijwrap}; @rows[i][j]
402
+ end
403
+
404
+ def []=(i, j, v)
405
+ #{ijwrap}; @rows[i][j] = v
406
+ end
407
+ end"
408
+ end
409
+
410
+ #
411
+ # Set wrap feature of a matrix
412
+ #
413
+ def wrap=(mode = :torus)
414
+ case mode
415
+ when :torus then eval(wraplate("i %= row_size; j %= column_size"))
416
+ when :h_cylinder then eval(wraplate("i %= row_size"))
417
+ when :v_cylinder then eval(wraplate("j %= column_size"))
418
+ when :nil then eval(wraplate)
419
+ end
420
+ @wrap = mode
421
+ end
422
+
423
+ #
424
+ # Returns the maximum length of column elements
425
+ #
426
+ def max_len_column(j)
427
+ column_collect(j) {|x| x.to_s.length}.max
428
+ end
429
+
430
+ #
431
+ # Returns a list with the maximum lengths
432
+ #
433
+ def cols_len
434
+ (0...column_size).collect {|j| max_len_column(j)}
435
+ end
436
+
437
+ #
438
+ # Returns a string for nice printing matrix
439
+ #
440
+ def to_s(mode = :pretty, len_col = 3)
441
+ return super if empty?
442
+ if mode == :pretty
443
+ clen = cols_len
444
+ to_a.collect {|r|
445
+ i=0
446
+ r.map {|x|
447
+ l=clen[i]
448
+ i+=1
449
+ format("%#{l}s ", x.to_s)
450
+ } << "\n"
451
+ }.join("")
452
+ else
453
+ i = 0; s = ""; cs = column_size
454
+ each do |e|
455
+ i = (i + 1) % cs
456
+ s += format("%#{len_col}s ", e.to_s)
457
+ s += "\n" if i == 0
458
+ end
459
+ s
460
+ end
461
+ end
462
+
463
+ #
464
+ # Iterate the elements of a matrix
465
+ #
466
+ def each
467
+ @rows.each {|x| x.each {|e| yield(e)}}
468
+ nil
469
+ end
470
+
471
+ #
472
+ # a hided module of Matrix
473
+ module MMatrix
474
+ def self.default_block(block)
475
+ block ? lambda { |i| block.call(i) } : lambda {|i| i }
476
+ end
477
+
478
+ #
479
+ # Returns:
480
+ # 1) the index of row/column and
481
+ # 2) the values Vector for changing the row/column and
482
+ # 3) the range of changes
483
+ #
484
+ def self.id_vect_range(args, l)
485
+ i = args[0] # the column(/the row) to be change
486
+ vect = args[1] # the values vector
487
+
488
+ case args.size
489
+ when 3 then range = args[2] # the range of the elements to be change
490
+ when 4 then range = args[2]..args[3] #the range by borders
491
+ else range = 0...l
492
+ end
493
+ return i, vect, range
494
+ end
495
+
496
+ end
497
+
498
+ #
499
+ # Returns an array with the elements collected from the row "i".
500
+ # When a block is given, the elements of that vector are iterated.
501
+ #
502
+ def row_collect(i, &block)
503
+ f = MMatrix.default_block(block)
504
+ @rows[i].collect {|e| f.call(e)}
505
+ end
506
+
507
+ #
508
+ # Returns row vector number "i" like Matrix.row as a Vector.
509
+ # When the block is given, the elements of row "i" are modified
510
+ #
511
+ def row!(i)
512
+ if block_given?
513
+ @rows[i].collect! {|e| yield e }
514
+ else
515
+ Vector.elements(@rows[i], false)
516
+ end
517
+ end
518
+ alias :row_collect! :row!
519
+
520
+ #
521
+ # Returns an array with the elements collected from the column "j".
522
+ # When a block is given, the elements of that vector are iterated.
523
+ #
524
+ def column_collect(j, &block)
525
+ f = MMatrix.default_block(block)
526
+ (0...row_size).collect {|r| f.call(self[r, j])}
527
+ end
528
+
529
+ #
530
+ # Returns column vector number "j" as a Vector.
531
+ # When the block is given, the elements of column "j" are mmodified
532
+ #
533
+ def column!(j)
534
+ if block_given?
535
+ (0...row_size).collect { |i| @rows[i][j] = yield @rows[i][j] }
536
+ else
537
+ column(j)
538
+ end
539
+ end
540
+ alias :column_collect! :column!
541
+
542
+ #
543
+ # Set a certain column with the values of a Vector
544
+ # m = Matrix.new(3, 3){|i, j| i * 3 + j + 1}
545
+ # m.column= 1, Vector[1, 1, 1], 1..2
546
+ # m => 1 2 3
547
+ # 4 1 6
548
+ # 7 1 9
549
+ #
550
+ def column=(args)
551
+ m = row_size
552
+ c, v, r = MMatrix.id_vect_range(args, m)
553
+ (m..r.begin - 1).each{|i| self[i, c] = 0}
554
+ [v.size, r.entries.size].min.times{|i| self[i + r.begin, c] = v[i]}
555
+ ((v.size + r.begin)..r.entries.last).each {|i| self[i, c] = 0}
556
+ end
557
+
558
+ #
559
+ # Set a certain row with the values of a Vector
560
+ # m = Matrix.new(3, 3){|i, j| i * 3 + j + 1}
561
+ # m.row= 0, Vector[0, 0], 1..2
562
+ # m => 1 0 0
563
+ # 4 5 6
564
+ # 7 8 9
565
+ #
566
+ def row=(args)
567
+ i, val, range = MMatrix.id_vect_range(args, column_size)
568
+ row!(i)[range] = val
569
+ end
570
+
571
+ def norm(p = 2)
572
+ Vector::Norm.sqnorm(self, p) ** (Float(1)/p)
573
+ end
574
+
575
+ def norm_frobenius
576
+ norm
577
+ end
578
+ alias :normF :norm_frobenius
579
+
580
+ #
581
+ # Tests if the matrix is empty or not
582
+ #
583
+ def empty?
584
+ @rows.empty? if @rows
585
+ end
586
+
587
+ #
588
+ # Returns the row/s of matrix as a Matrix
589
+ #
590
+ def row2matrix(r)
591
+ a = self.send(:row, r).to_a
592
+ if r.is_a?(Range) and r.entries.size > 1
593
+ return Matrix[*a]
594
+ else
595
+ return Matrix[a]
596
+ end
597
+ end
598
+
599
+ #
600
+ # Returns the colomn/s of matrix as a Matrix
601
+ #
602
+ def column2matrix(c)
603
+ a = self.send(:column, c).to_a
604
+ if c.is_a?(Range) and c.entries.size > 1
605
+ return Matrix[*a]
606
+ else
607
+ return Matrix[*a.collect{|x| [x]}]
608
+ end
609
+ end
610
+
611
+ # Calculate marginal of rows
612
+ def row_sum
613
+ (0...row_size).collect {|i|
614
+ row(i).to_a.inject(0) {|a,v| a+v}
615
+ }
616
+ end
617
+ # Calculate marginal of columns
618
+ def column_sum
619
+ (0...column_size).collect {|i|
620
+ column(i).to_a.inject(0) {|a,v| a+v}
621
+ }
622
+ end
623
+ # Calculate sum of cells
624
+ def total_sum
625
+ row_sum.inject(0){|a,v| a+v}
626
+ end
627
+
628
+ module LU
629
+ #
630
+ # Return the Gauss vector, MC, Golub, 3.2.1 Gauss Transformation, p94
631
+ #
632
+ def self.gauss_vector(mat, k)
633
+ t = mat.column2matrix(k)
634
+ tk = t[k, 0]
635
+ (0..k).each{|i| t[i, 0] = 0}
636
+ return t if tk == 0
637
+ (k+1...mat.row_size).each{|i| t[i, 0] = t[i, 0].to_f / tk}
638
+ t
639
+ end
640
+
641
+ #
642
+ # Return the Gauss transformation matrix: M_k = I - tau * e_k^T
643
+ #
644
+ def self.gauss(mat, k)
645
+ i = Matrix.I(mat.column_size)
646
+ tau = gauss_vector(mat, k)
647
+ e = i.row2matrix(k)
648
+ i - tau * e
649
+ end
650
+
651
+ #
652
+ # LU factorization: A = LU
653
+ # where L is lower triangular and U is upper triangular
654
+ #
655
+ def self.factorization(mat)
656
+ u = mat.clone
657
+ n = u.column_size
658
+ i = Matrix.I(n)
659
+ l = i.clone
660
+ (n-1).times {|k|
661
+ mk = gauss(u, k)
662
+ u = mk * u # M_{n-1} * ... * M_1 * A = U
663
+ l += i - mk # L = M_1^{-1} * ... * M_{n-1}^{-1} = I + sum_{k=1}^{n-1} tau * e
664
+ }
665
+ return l, u
666
+ end
667
+ end
668
+
669
+ #
670
+ # Return the upper triangular matrix of LU factorization
671
+ # M_{n-1} * ... * M_1 * A = U
672
+ #
673
+ def U
674
+ LU.factorization(self)[1]
675
+ end
676
+
677
+ #
678
+ # Return the lower triangular matrix of LU factorization
679
+ # L = M_1^{-1} * ... * M_{n-1}^{-1} = I + sum_{k=1}^{n-1} tau * e
680
+ #
681
+ def L
682
+ LU.factorization(self)[0]
683
+ end
684
+
685
+ module Householder
686
+ #
687
+ # a QR factorization that uses Householder transformation
688
+ # Q^T * A = R
689
+ # MC, Golub & van Loan, pg 224, 5.2.1 Householder QR
690
+ #
691
+ def self.QR(mat)
692
+ h = []
693
+ a = mat.clone
694
+ m = a.row_size
695
+ n = a.column_size
696
+ n.times{|j|
697
+ v, beta = a[j..m - 1, j].house
698
+
699
+ h[j] = Matrix.diag(Matrix.I(j), Matrix.I(m-j)- beta * (v * v.t))
700
+
701
+ a[j..m-1, j..n-1] = (Matrix.I(m-j) - beta * (v * v.t)) * a[j..m-1, j..n-1]
702
+ a[(j+1)..m-1,j] = v[2..(m-j)] if j < m - 1 }
703
+ h
704
+ end
705
+
706
+ #
707
+ # From the essential part of Householder vector
708
+ # it returns the coresponding upper(U_j)/lower(V_j) matrix
709
+ #
710
+ def self.bidiagUV(essential, dim, beta)
711
+ v = Vector.concat(Vector[1], essential)
712
+ dimv = v.size
713
+ Matrix.diag(Matrix.I(dim - dimv), Matrix.I(dimv) - beta * (v * v.t) )
714
+ end
715
+
716
+ #
717
+ # Householder Bidiagonalization algorithm. MC, Golub, pg 252, Algorithm 5.4.2
718
+ # Returns the matrices U_B and V_B such that: U_B^T * A * V_B = B,
719
+ # where B is upper bidiagonal.
720
+ #
721
+ def self.bidiag(mat)
722
+ a = mat.clone
723
+ m = a.row_size
724
+ n = a.column_size
725
+ ub = Matrix.I(m)
726
+ vb = Matrix.I(n)
727
+ n.times{|j|
728
+ v, beta = a[j..m-1,j].house
729
+ a[j..m-1, j..n-1] = (Matrix.I(m-j) - beta * (v * v.t)) * a[j..m-1, j..n-1]
730
+ a[j+1..m-1, j] = v[1..(m-j-1)]
731
+ ub *= bidiagUV(a[j+1..m-1,j], m, beta) #Ub = U_1 * U_2 * ... * U_n
732
+ if j < n - 2
733
+ v, beta = (a[j, j+1..n-1]).house
734
+ a[j..m-1, j+1..n-1] = a[j..m-1, j+1..n-1] * (Matrix.I(n-j-1) - beta * (v * v.t))
735
+ a[j, j+2..n-1] = v[1..n-j-2]
736
+ vb *= bidiagUV(a[j, j+2..n-1], n, beta) #Vb = V_1 * U_2 * ... * V_n-2
737
+ end }
738
+ return ub, vb
739
+ end
740
+
741
+ #
742
+ #Householder Reduction to Hessenberg Form
743
+ #
744
+ def self.toHessenberg(mat)
745
+ h = mat.clone
746
+ n = h.row_size
747
+ u0 = Matrix.I(n)
748
+ for k in (0...n - 2)
749
+ v, beta = h[k+1..n-1, k].house #the householder matrice part
750
+ houseV = Matrix.I(n-k-1) - beta * (v * v.t)
751
+ u0 *= Matrix.diag(Matrix.I(k+1), houseV)
752
+ h[k+1..n-1, k..n-1] = houseV * h[k+1..n-1, k..n-1]
753
+ h[0..n-1, k+1..n-1] = h[0..n-1, k+1..n-1] * houseV
754
+ end
755
+ return h, u0
756
+ end
757
+
758
+
759
+ end #end of Householder module
760
+
761
+ #
762
+ # Returns the upper bidiagonal matrix obtained with Householder Bidiagonalization algorithm
763
+ #
764
+ def bidiagonal
765
+ ub, vb = Householder.bidiag(self)
766
+ ub.t * self * vb
767
+ end
768
+
769
+ #
770
+ # Returns the orthogonal matrix Q of Householder QR factorization
771
+ # where Q = H_1 * H_2 * H_3 * ... * H_n,
772
+ #
773
+ def houseQ
774
+ h = Householder.QR(self)
775
+ q = h[0]
776
+ (1...h.size).each{|i| q *= h[i]}
777
+ q
778
+ end
779
+
780
+ #
781
+ # Returns the matrix R of Householder QR factorization
782
+ # R = H_n * H_n-1 * ... * H_1 * A is an upper triangular matrix
783
+ #
784
+ def houseR
785
+ h = Householder.QR(self)
786
+ r = self.clone
787
+ h.size.times{|i| r = h[i] * r}
788
+ r
789
+ end
790
+
791
+ #
792
+ # Modified Gram Schmidt QR factorization (MC, Golub, p. 232)
793
+ # A = Q_1 * R_1
794
+ #
795
+ def gram_schmidt
796
+ a = clone
797
+ n = column_size
798
+ m = row_size
799
+ q = Matrix.new(m, n){0}
800
+ r = Matrix.zero(n)
801
+ for k in 0...n
802
+ r[k,k] = a[0...m, k].norm
803
+ q[0...m, k] = a[0...m, k] / r[k, k]
804
+ for j in (k+1)...n
805
+ r[k, j] = q[0...m, k].t * a[0...m, j]
806
+ a[0...m, j] -= q[0...m, k] * r[k, j]
807
+ end
808
+ end
809
+ return q, r
810
+ end
811
+
812
+ #
813
+ # Returns the Q_1 matrix of Modified Gram Schmidt algorithm
814
+ # Q_1 has orthonormal columns
815
+ #
816
+ def gram_schmidtQ
817
+ gram_schmidt[0]
818
+ end
819
+
820
+ #
821
+ # Returns the R_1 upper triangular matrix of Modified Gram Schmidt algorithm
822
+ #
823
+ def gram_schmidtR
824
+ gram_schmidt[1]
825
+ end
826
+
827
+
828
+ module Givens
829
+ #
830
+ # Returns the values "c and s" of a Given rotation
831
+ # MC, Golub, pg 216, Alghorithm 5.1.3
832
+ #
833
+ def self.givens(a, b)
834
+ if b == 0
835
+ c = 0; s = 0
836
+ else
837
+ if b.abs > a.abs
838
+ tau = Float(-a)/b; s = 1/Math.sqrt(1+tau**2); c = s * tau
839
+ else
840
+ tau = Float(-b)/a; c = 1/Math.sqrt(1+tau**2); s = c * tau
841
+ end
842
+ end
843
+ return c, s
844
+ end
845
+
846
+ #
847
+ # a QR factorization using Givens rotation
848
+ # Computes the upper triangular matrix R and the orthogonal matrix Q
849
+ # where Q^t A = R (MC, Golub, p227 algorithm 5.2.2)
850
+ #
851
+ def self.QR(mat)
852
+ r = mat.clone
853
+ m = r.row_size
854
+ n = r.column_size
855
+ q = Matrix.I(m)
856
+ n.times{|j|
857
+ m-1.downto(j+1){|i|
858
+ c, s = givens(r[i - 1, j], r[i, j])
859
+ qt = Matrix.I(m); qt[i-1..i, i-1..i] = Matrix[[c, s],[-s, c]]
860
+ q *= qt
861
+ r[i-1..i, j..n-1] = Matrix[[c, -s],[s, c]] * r[i-1..i, j..n-1]}}
862
+ return r, q
863
+ end
864
+
865
+ end
866
+
867
+ #
868
+ # Returns the upper triunghiular matrix R of a Givens QR factorization
869
+ #
870
+ def givensR
871
+ Givens.QR(self)[0]
872
+ end
873
+
874
+ #
875
+ # Returns the orthogonal matrix Q of Givens QR factorization.
876
+ # Q = G_1 * ... * G_t where G_j is the j'th Givens rotation
877
+ # and 't' is the total number of rotations
878
+ #
879
+ def givensQ
880
+ Givens.QR(self)[1]
881
+ end
882
+
883
+ module Hessenberg
884
+ #
885
+ # the matrix must be an upper R^(n x n) Hessenberg matrix
886
+ #
887
+ def self.QR(mat)
888
+ r = mat.clone
889
+ n = r.row_size
890
+ q = Matrix.I(n)
891
+ for j in (0...n-1)
892
+ c, s = Givens.givens(r[j,j], r[j+1, j])
893
+ cs = Matrix[[c, s], [-s, c]]
894
+ q *= Matrix.diag(Matrix.I(j), cs, Matrix.I(n - j - 2))
895
+ r[j..j+1, j..n-1] = cs.t * r[j..j+1, j..n-1]
896
+ end
897
+ return q, r
898
+ end
899
+ end
900
+
901
+ #
902
+ # Returns the orthogonal matrix Q of Hessenberg QR factorization
903
+ # Q = G_1 *...* G_(n-1) where G_j is the Givens rotation G_j = G(j, j+1, omega_j)
904
+ #
905
+ def hessenbergQ
906
+ Hessenberg.QR(self)[0]
907
+ end
908
+
909
+ #
910
+ # Returns the upper triunghiular matrix R of a Hessenberg QR factorization
911
+ #
912
+ def hessenbergR
913
+ Hessenberg.QR(self)[1]
914
+ end
915
+
916
+ #
917
+ # Return an upper Hessenberg matrix obtained with Householder reduction to Hessenberg Form algorithm
918
+ #
919
+ def hessenberg_form_H
920
+ Householder.toHessenberg(self)[0]
921
+ end
922
+
923
+ #
924
+ # The real Schur decomposition.
925
+ # The eigenvalues are aproximated in diagonal elements of the real Schur decomposition matrix
926
+ #
927
+ def realSchur(eps = 1.0e-10, steps = 100)
928
+ h = self.hessenberg_form_H
929
+ h1 = Matrix[]
930
+ i = 0
931
+ loop do
932
+ h1 = h.hessenbergR * h.hessenbergQ
933
+ break if Matrix.diag_in_delta?(h1, h, eps) or steps <= 0
934
+ h = h1.clone
935
+ steps -= 1
936
+ i += 1
937
+ end
938
+ h1
939
+ end
940
+
941
+
942
+ module Jacobi
943
+ #
944
+ # Returns the nurm of the off-diagonal element
945
+ #
946
+ def self.off(a)
947
+ n = a.row_size
948
+ sum = 0
949
+ n.times{|i| n.times{|j| sum += a[i, j]**2 if j != i}}
950
+ Math.sqrt(sum)
951
+ end
952
+
953
+ #
954
+ # Returns the index pair (p, q) with 1<= p < q <= n and A[p, q] is the maximum in absolute value
955
+ #
956
+ def self.max(a)
957
+ n = a.row_size
958
+ max = 0
959
+ p = 0
960
+ q = 0
961
+ n.times{|i|
962
+ ((i+1)...n).each{|j|
963
+ val = a[i, j].abs
964
+ if val > max
965
+ max = val
966
+ p = i
967
+ q = j
968
+ end }}
969
+ return p, q
970
+ end
971
+
972
+ #
973
+ # Compute the cosine-sine pair (c, s) for the element A[p, q]
974
+ #
975
+ def self.sym_schur2(a, p, q)
976
+ if a[p, q] != 0
977
+ tau = Float(a[q, q] - a[p, p])/(2 * a[p, q])
978
+ if tau >= 0
979
+ t = 1./(tau + Math.sqrt(1 + tau ** 2))
980
+ else
981
+ t = -1./(-tau + Math.sqrt(1 + tau ** 2))
982
+ end
983
+ c = 1./Math.sqrt(1 + t ** 2)
984
+ s = t * c
985
+ else
986
+ c = 1
987
+ s = 0
988
+ end
989
+ return c, s
990
+ end
991
+
992
+ #
993
+ # Returns the Jacobi rotation matrix
994
+ #
995
+ def self.J(p, q, c, s, n)
996
+ j = Matrix.I(n)
997
+ j[p,p] = c; j[p, q] = s
998
+ j[q,p] = -s; j[q, q] = c
999
+ j
1000
+ end
1001
+ end
1002
+
1003
+ #
1004
+ # Classical Jacobi 8.4.3 Golub & van Loan
1005
+ #
1006
+ def cJacobi(tol = 1.0e-10)
1007
+ a = self.clone
1008
+ n = row_size
1009
+ v = Matrix.I(n)
1010
+ eps = tol * a.normF
1011
+ while Jacobi.off(a) > eps
1012
+ p, q = Jacobi.max(a)
1013
+ c, s = Jacobi.sym_schur2(a, p, q)
1014
+ #print "\np:#{p} q:#{q} c:#{c} s:#{s}\n"
1015
+ j = Jacobi.J(p, q, c, s, n)
1016
+ a = j.t * a * j
1017
+ v = v * j
1018
+ end
1019
+ return a, v
1020
+ end
1021
+
1022
+ #
1023
+ # Returns the aproximation matrix computed with Classical Jacobi algorithm.
1024
+ # The aproximate eigenvalues values are in the diagonal of the matrix A.
1025
+ #
1026
+ def cJacobiA(tol = 1.0e-10)
1027
+ cJacobi(tol)[0]
1028
+ end
1029
+
1030
+ #
1031
+ # Returns a Vector with the eigenvalues aproximated values.
1032
+ # The eigenvalues are computed with the Classic Jacobi Algorithm.
1033
+ #
1034
+ def eigenvaluesJacobi
1035
+ a = cJacobiA
1036
+ Vector[*(0...row_size).collect{|i| a[i, i]}]
1037
+ end
1038
+
1039
+ #
1040
+ # Returns the orthogonal matrix obtained with the Jacobi eigenvalue algorithm.
1041
+ # The columns of V are the eigenvector.
1042
+ #
1043
+ def cJacobiV(tol = 1.0e-10)
1044
+ cJacobi(tol)[1]
1045
+ end
1046
+ end
1047
+
1048
+