matrix 0.1.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,882 @@
1
+ # frozen_string_literal: false
2
+ class Matrix
3
+ # Adapted from JAMA: http://math.nist.gov/javanumerics/jama/
4
+
5
+ # Eigenvalues and eigenvectors of a real matrix.
6
+ #
7
+ # Computes the eigenvalues and eigenvectors of a matrix A.
8
+ #
9
+ # If A is diagonalizable, this provides matrices V and D
10
+ # such that A = V*D*V.inv, where D is the diagonal matrix with entries
11
+ # equal to the eigenvalues and V is formed by the eigenvectors.
12
+ #
13
+ # If A is symmetric, then V is orthogonal and thus A = V*D*V.t
14
+
15
+ class EigenvalueDecomposition
16
+
17
+ # Constructs the eigenvalue decomposition for a square matrix +A+
18
+ #
19
+ def initialize(a)
20
+ # @d, @e: Arrays for internal storage of eigenvalues.
21
+ # @v: Array for internal storage of eigenvectors.
22
+ # @h: Array for internal storage of nonsymmetric Hessenberg form.
23
+ raise TypeError, "Expected Matrix but got #{a.class}" unless a.is_a?(Matrix)
24
+ @size = a.row_count
25
+ @d = Array.new(@size, 0)
26
+ @e = Array.new(@size, 0)
27
+
28
+ if (@symmetric = a.symmetric?)
29
+ @v = a.to_a
30
+ tridiagonalize
31
+ diagonalize
32
+ else
33
+ @v = Array.new(@size) { Array.new(@size, 0) }
34
+ @h = a.to_a
35
+ @ort = Array.new(@size, 0)
36
+ reduce_to_hessenberg
37
+ hessenberg_to_real_schur
38
+ end
39
+ end
40
+
41
+ # Returns the eigenvector matrix +V+
42
+ #
43
+ def eigenvector_matrix
44
+ Matrix.send(:new, build_eigenvectors.transpose)
45
+ end
46
+ alias_method :v, :eigenvector_matrix
47
+
48
+ # Returns the inverse of the eigenvector matrix +V+
49
+ #
50
+ def eigenvector_matrix_inv
51
+ r = Matrix.send(:new, build_eigenvectors)
52
+ r = r.transpose.inverse unless @symmetric
53
+ r
54
+ end
55
+ alias_method :v_inv, :eigenvector_matrix_inv
56
+
57
+ # Returns the eigenvalues in an array
58
+ #
59
+ def eigenvalues
60
+ values = @d.dup
61
+ @e.each_with_index{|imag, i| values[i] = Complex(values[i], imag) unless imag == 0}
62
+ values
63
+ end
64
+
65
+ # Returns an array of the eigenvectors
66
+ #
67
+ def eigenvectors
68
+ build_eigenvectors.map{|ev| Vector.send(:new, ev)}
69
+ end
70
+
71
+ # Returns the block diagonal eigenvalue matrix +D+
72
+ #
73
+ def eigenvalue_matrix
74
+ Matrix.diagonal(*eigenvalues)
75
+ end
76
+ alias_method :d, :eigenvalue_matrix
77
+
78
+ # Returns [eigenvector_matrix, eigenvalue_matrix, eigenvector_matrix_inv]
79
+ #
80
+ def to_ary
81
+ [v, d, v_inv]
82
+ end
83
+ alias_method :to_a, :to_ary
84
+
85
+
86
+ private def build_eigenvectors
87
+ # JAMA stores complex eigenvectors in a strange way
88
+ # See http://web.archive.org/web/20111016032731/http://cio.nist.gov/esd/emaildir/lists/jama/msg01021.html
89
+ @e.each_with_index.map do |imag, i|
90
+ if imag == 0
91
+ Array.new(@size){|j| @v[j][i]}
92
+ elsif imag > 0
93
+ Array.new(@size){|j| Complex(@v[j][i], @v[j][i+1])}
94
+ else
95
+ Array.new(@size){|j| Complex(@v[j][i-1], -@v[j][i])}
96
+ end
97
+ end
98
+ end
99
+
100
+ # Complex scalar division.
101
+
102
+ private def cdiv(xr, xi, yr, yi)
103
+ if (yr.abs > yi.abs)
104
+ r = yi/yr
105
+ d = yr + r*yi
106
+ [(xr + r*xi)/d, (xi - r*xr)/d]
107
+ else
108
+ r = yr/yi
109
+ d = yi + r*yr
110
+ [(r*xr + xi)/d, (r*xi - xr)/d]
111
+ end
112
+ end
113
+
114
+
115
+ # Symmetric Householder reduction to tridiagonal form.
116
+
117
+ private def tridiagonalize
118
+
119
+ # This is derived from the Algol procedures tred2 by
120
+ # Bowdler, Martin, Reinsch, and Wilkinson, Handbook for
121
+ # Auto. Comp., Vol.ii-Linear Algebra, and the corresponding
122
+ # Fortran subroutine in EISPACK.
123
+
124
+ @size.times do |j|
125
+ @d[j] = @v[@size-1][j]
126
+ end
127
+
128
+ # Householder reduction to tridiagonal form.
129
+
130
+ (@size-1).downto(0+1) do |i|
131
+
132
+ # Scale to avoid under/overflow.
133
+
134
+ scale = 0.0
135
+ h = 0.0
136
+ i.times do |k|
137
+ scale = scale + @d[k].abs
138
+ end
139
+ if (scale == 0.0)
140
+ @e[i] = @d[i-1]
141
+ i.times do |j|
142
+ @d[j] = @v[i-1][j]
143
+ @v[i][j] = 0.0
144
+ @v[j][i] = 0.0
145
+ end
146
+ else
147
+
148
+ # Generate Householder vector.
149
+
150
+ i.times do |k|
151
+ @d[k] /= scale
152
+ h += @d[k] * @d[k]
153
+ end
154
+ f = @d[i-1]
155
+ g = Math.sqrt(h)
156
+ if (f > 0)
157
+ g = -g
158
+ end
159
+ @e[i] = scale * g
160
+ h -= f * g
161
+ @d[i-1] = f - g
162
+ i.times do |j|
163
+ @e[j] = 0.0
164
+ end
165
+
166
+ # Apply similarity transformation to remaining columns.
167
+
168
+ i.times do |j|
169
+ f = @d[j]
170
+ @v[j][i] = f
171
+ g = @e[j] + @v[j][j] * f
172
+ (j+1).upto(i-1) do |k|
173
+ g += @v[k][j] * @d[k]
174
+ @e[k] += @v[k][j] * f
175
+ end
176
+ @e[j] = g
177
+ end
178
+ f = 0.0
179
+ i.times do |j|
180
+ @e[j] /= h
181
+ f += @e[j] * @d[j]
182
+ end
183
+ hh = f / (h + h)
184
+ i.times do |j|
185
+ @e[j] -= hh * @d[j]
186
+ end
187
+ i.times do |j|
188
+ f = @d[j]
189
+ g = @e[j]
190
+ j.upto(i-1) do |k|
191
+ @v[k][j] -= (f * @e[k] + g * @d[k])
192
+ end
193
+ @d[j] = @v[i-1][j]
194
+ @v[i][j] = 0.0
195
+ end
196
+ end
197
+ @d[i] = h
198
+ end
199
+
200
+ # Accumulate transformations.
201
+
202
+ 0.upto(@size-1-1) do |i|
203
+ @v[@size-1][i] = @v[i][i]
204
+ @v[i][i] = 1.0
205
+ h = @d[i+1]
206
+ if (h != 0.0)
207
+ 0.upto(i) do |k|
208
+ @d[k] = @v[k][i+1] / h
209
+ end
210
+ 0.upto(i) do |j|
211
+ g = 0.0
212
+ 0.upto(i) do |k|
213
+ g += @v[k][i+1] * @v[k][j]
214
+ end
215
+ 0.upto(i) do |k|
216
+ @v[k][j] -= g * @d[k]
217
+ end
218
+ end
219
+ end
220
+ 0.upto(i) do |k|
221
+ @v[k][i+1] = 0.0
222
+ end
223
+ end
224
+ @size.times do |j|
225
+ @d[j] = @v[@size-1][j]
226
+ @v[@size-1][j] = 0.0
227
+ end
228
+ @v[@size-1][@size-1] = 1.0
229
+ @e[0] = 0.0
230
+ end
231
+
232
+
233
+ # Symmetric tridiagonal QL algorithm.
234
+
235
+ private def diagonalize
236
+ # This is derived from the Algol procedures tql2, by
237
+ # Bowdler, Martin, Reinsch, and Wilkinson, Handbook for
238
+ # Auto. Comp., Vol.ii-Linear Algebra, and the corresponding
239
+ # Fortran subroutine in EISPACK.
240
+
241
+ 1.upto(@size-1) do |i|
242
+ @e[i-1] = @e[i]
243
+ end
244
+ @e[@size-1] = 0.0
245
+
246
+ f = 0.0
247
+ tst1 = 0.0
248
+ eps = Float::EPSILON
249
+ @size.times do |l|
250
+
251
+ # Find small subdiagonal element
252
+
253
+ tst1 = [tst1, @d[l].abs + @e[l].abs].max
254
+ m = l
255
+ while (m < @size) do
256
+ if (@e[m].abs <= eps*tst1)
257
+ break
258
+ end
259
+ m+=1
260
+ end
261
+
262
+ # If m == l, @d[l] is an eigenvalue,
263
+ # otherwise, iterate.
264
+
265
+ if (m > l)
266
+ iter = 0
267
+ begin
268
+ iter = iter + 1 # (Could check iteration count here.)
269
+
270
+ # Compute implicit shift
271
+
272
+ g = @d[l]
273
+ p = (@d[l+1] - g) / (2.0 * @e[l])
274
+ r = Math.hypot(p, 1.0)
275
+ if (p < 0)
276
+ r = -r
277
+ end
278
+ @d[l] = @e[l] / (p + r)
279
+ @d[l+1] = @e[l] * (p + r)
280
+ dl1 = @d[l+1]
281
+ h = g - @d[l]
282
+ (l+2).upto(@size-1) do |i|
283
+ @d[i] -= h
284
+ end
285
+ f += h
286
+
287
+ # Implicit QL transformation.
288
+
289
+ p = @d[m]
290
+ c = 1.0
291
+ c2 = c
292
+ c3 = c
293
+ el1 = @e[l+1]
294
+ s = 0.0
295
+ s2 = 0.0
296
+ (m-1).downto(l) do |i|
297
+ c3 = c2
298
+ c2 = c
299
+ s2 = s
300
+ g = c * @e[i]
301
+ h = c * p
302
+ r = Math.hypot(p, @e[i])
303
+ @e[i+1] = s * r
304
+ s = @e[i] / r
305
+ c = p / r
306
+ p = c * @d[i] - s * g
307
+ @d[i+1] = h + s * (c * g + s * @d[i])
308
+
309
+ # Accumulate transformation.
310
+
311
+ @size.times do |k|
312
+ h = @v[k][i+1]
313
+ @v[k][i+1] = s * @v[k][i] + c * h
314
+ @v[k][i] = c * @v[k][i] - s * h
315
+ end
316
+ end
317
+ p = -s * s2 * c3 * el1 * @e[l] / dl1
318
+ @e[l] = s * p
319
+ @d[l] = c * p
320
+
321
+ # Check for convergence.
322
+
323
+ end while (@e[l].abs > eps*tst1)
324
+ end
325
+ @d[l] = @d[l] + f
326
+ @e[l] = 0.0
327
+ end
328
+
329
+ # Sort eigenvalues and corresponding vectors.
330
+
331
+ 0.upto(@size-2) do |i|
332
+ k = i
333
+ p = @d[i]
334
+ (i+1).upto(@size-1) do |j|
335
+ if (@d[j] < p)
336
+ k = j
337
+ p = @d[j]
338
+ end
339
+ end
340
+ if (k != i)
341
+ @d[k] = @d[i]
342
+ @d[i] = p
343
+ @size.times do |j|
344
+ p = @v[j][i]
345
+ @v[j][i] = @v[j][k]
346
+ @v[j][k] = p
347
+ end
348
+ end
349
+ end
350
+ end
351
+
352
+ # Nonsymmetric reduction to Hessenberg form.
353
+
354
+ private def reduce_to_hessenberg
355
+ # This is derived from the Algol procedures orthes and ortran,
356
+ # by Martin and Wilkinson, Handbook for Auto. Comp.,
357
+ # Vol.ii-Linear Algebra, and the corresponding
358
+ # Fortran subroutines in EISPACK.
359
+
360
+ low = 0
361
+ high = @size-1
362
+
363
+ (low+1).upto(high-1) do |m|
364
+
365
+ # Scale column.
366
+
367
+ scale = 0.0
368
+ m.upto(high) do |i|
369
+ scale = scale + @h[i][m-1].abs
370
+ end
371
+ if (scale != 0.0)
372
+
373
+ # Compute Householder transformation.
374
+
375
+ h = 0.0
376
+ high.downto(m) do |i|
377
+ @ort[i] = @h[i][m-1]/scale
378
+ h += @ort[i] * @ort[i]
379
+ end
380
+ g = Math.sqrt(h)
381
+ if (@ort[m] > 0)
382
+ g = -g
383
+ end
384
+ h -= @ort[m] * g
385
+ @ort[m] = @ort[m] - g
386
+
387
+ # Apply Householder similarity transformation
388
+ # @h = (I-u*u'/h)*@h*(I-u*u')/h)
389
+
390
+ m.upto(@size-1) do |j|
391
+ f = 0.0
392
+ high.downto(m) do |i|
393
+ f += @ort[i]*@h[i][j]
394
+ end
395
+ f = f/h
396
+ m.upto(high) do |i|
397
+ @h[i][j] -= f*@ort[i]
398
+ end
399
+ end
400
+
401
+ 0.upto(high) do |i|
402
+ f = 0.0
403
+ high.downto(m) do |j|
404
+ f += @ort[j]*@h[i][j]
405
+ end
406
+ f = f/h
407
+ m.upto(high) do |j|
408
+ @h[i][j] -= f*@ort[j]
409
+ end
410
+ end
411
+ @ort[m] = scale*@ort[m]
412
+ @h[m][m-1] = scale*g
413
+ end
414
+ end
415
+
416
+ # Accumulate transformations (Algol's ortran).
417
+
418
+ @size.times do |i|
419
+ @size.times do |j|
420
+ @v[i][j] = (i == j ? 1.0 : 0.0)
421
+ end
422
+ end
423
+
424
+ (high-1).downto(low+1) do |m|
425
+ if (@h[m][m-1] != 0.0)
426
+ (m+1).upto(high) do |i|
427
+ @ort[i] = @h[i][m-1]
428
+ end
429
+ m.upto(high) do |j|
430
+ g = 0.0
431
+ m.upto(high) do |i|
432
+ g += @ort[i] * @v[i][j]
433
+ end
434
+ # Double division avoids possible underflow
435
+ g = (g / @ort[m]) / @h[m][m-1]
436
+ m.upto(high) do |i|
437
+ @v[i][j] += g * @ort[i]
438
+ end
439
+ end
440
+ end
441
+ end
442
+ end
443
+
444
+ # Nonsymmetric reduction from Hessenberg to real Schur form.
445
+
446
+ private def hessenberg_to_real_schur
447
+
448
+ # This is derived from the Algol procedure hqr2,
449
+ # by Martin and Wilkinson, Handbook for Auto. Comp.,
450
+ # Vol.ii-Linear Algebra, and the corresponding
451
+ # Fortran subroutine in EISPACK.
452
+
453
+ # Initialize
454
+
455
+ nn = @size
456
+ n = nn-1
457
+ low = 0
458
+ high = nn-1
459
+ eps = Float::EPSILON
460
+ exshift = 0.0
461
+ p = q = r = s = z = 0
462
+
463
+ # Store roots isolated by balanc and compute matrix norm
464
+
465
+ norm = 0.0
466
+ nn.times do |i|
467
+ if (i < low || i > high)
468
+ @d[i] = @h[i][i]
469
+ @e[i] = 0.0
470
+ end
471
+ ([i-1, 0].max).upto(nn-1) do |j|
472
+ norm = norm + @h[i][j].abs
473
+ end
474
+ end
475
+
476
+ # Outer loop over eigenvalue index
477
+
478
+ iter = 0
479
+ while (n >= low) do
480
+
481
+ # Look for single small sub-diagonal element
482
+
483
+ l = n
484
+ while (l > low) do
485
+ s = @h[l-1][l-1].abs + @h[l][l].abs
486
+ if (s == 0.0)
487
+ s = norm
488
+ end
489
+ if (@h[l][l-1].abs < eps * s)
490
+ break
491
+ end
492
+ l-=1
493
+ end
494
+
495
+ # Check for convergence
496
+ # One root found
497
+
498
+ if (l == n)
499
+ @h[n][n] = @h[n][n] + exshift
500
+ @d[n] = @h[n][n]
501
+ @e[n] = 0.0
502
+ n-=1
503
+ iter = 0
504
+
505
+ # Two roots found
506
+
507
+ elsif (l == n-1)
508
+ w = @h[n][n-1] * @h[n-1][n]
509
+ p = (@h[n-1][n-1] - @h[n][n]) / 2.0
510
+ q = p * p + w
511
+ z = Math.sqrt(q.abs)
512
+ @h[n][n] = @h[n][n] + exshift
513
+ @h[n-1][n-1] = @h[n-1][n-1] + exshift
514
+ x = @h[n][n]
515
+
516
+ # Real pair
517
+
518
+ if (q >= 0)
519
+ if (p >= 0)
520
+ z = p + z
521
+ else
522
+ z = p - z
523
+ end
524
+ @d[n-1] = x + z
525
+ @d[n] = @d[n-1]
526
+ if (z != 0.0)
527
+ @d[n] = x - w / z
528
+ end
529
+ @e[n-1] = 0.0
530
+ @e[n] = 0.0
531
+ x = @h[n][n-1]
532
+ s = x.abs + z.abs
533
+ p = x / s
534
+ q = z / s
535
+ r = Math.sqrt(p * p+q * q)
536
+ p /= r
537
+ q /= r
538
+
539
+ # Row modification
540
+
541
+ (n-1).upto(nn-1) do |j|
542
+ z = @h[n-1][j]
543
+ @h[n-1][j] = q * z + p * @h[n][j]
544
+ @h[n][j] = q * @h[n][j] - p * z
545
+ end
546
+
547
+ # Column modification
548
+
549
+ 0.upto(n) do |i|
550
+ z = @h[i][n-1]
551
+ @h[i][n-1] = q * z + p * @h[i][n]
552
+ @h[i][n] = q * @h[i][n] - p * z
553
+ end
554
+
555
+ # Accumulate transformations
556
+
557
+ low.upto(high) do |i|
558
+ z = @v[i][n-1]
559
+ @v[i][n-1] = q * z + p * @v[i][n]
560
+ @v[i][n] = q * @v[i][n] - p * z
561
+ end
562
+
563
+ # Complex pair
564
+
565
+ else
566
+ @d[n-1] = x + p
567
+ @d[n] = x + p
568
+ @e[n-1] = z
569
+ @e[n] = -z
570
+ end
571
+ n -= 2
572
+ iter = 0
573
+
574
+ # No convergence yet
575
+
576
+ else
577
+
578
+ # Form shift
579
+
580
+ x = @h[n][n]
581
+ y = 0.0
582
+ w = 0.0
583
+ if (l < n)
584
+ y = @h[n-1][n-1]
585
+ w = @h[n][n-1] * @h[n-1][n]
586
+ end
587
+
588
+ # Wilkinson's original ad hoc shift
589
+
590
+ if (iter == 10)
591
+ exshift += x
592
+ low.upto(n) do |i|
593
+ @h[i][i] -= x
594
+ end
595
+ s = @h[n][n-1].abs + @h[n-1][n-2].abs
596
+ x = y = 0.75 * s
597
+ w = -0.4375 * s * s
598
+ end
599
+
600
+ # MATLAB's new ad hoc shift
601
+
602
+ if (iter == 30)
603
+ s = (y - x) / 2.0
604
+ s *= s + w
605
+ if (s > 0)
606
+ s = Math.sqrt(s)
607
+ if (y < x)
608
+ s = -s
609
+ end
610
+ s = x - w / ((y - x) / 2.0 + s)
611
+ low.upto(n) do |i|
612
+ @h[i][i] -= s
613
+ end
614
+ exshift += s
615
+ x = y = w = 0.964
616
+ end
617
+ end
618
+
619
+ iter = iter + 1 # (Could check iteration count here.)
620
+
621
+ # Look for two consecutive small sub-diagonal elements
622
+
623
+ m = n-2
624
+ while (m >= l) do
625
+ z = @h[m][m]
626
+ r = x - z
627
+ s = y - z
628
+ p = (r * s - w) / @h[m+1][m] + @h[m][m+1]
629
+ q = @h[m+1][m+1] - z - r - s
630
+ r = @h[m+2][m+1]
631
+ s = p.abs + q.abs + r.abs
632
+ p /= s
633
+ q /= s
634
+ r /= s
635
+ if (m == l)
636
+ break
637
+ end
638
+ if (@h[m][m-1].abs * (q.abs + r.abs) <
639
+ eps * (p.abs * (@h[m-1][m-1].abs + z.abs +
640
+ @h[m+1][m+1].abs)))
641
+ break
642
+ end
643
+ m-=1
644
+ end
645
+
646
+ (m+2).upto(n) do |i|
647
+ @h[i][i-2] = 0.0
648
+ if (i > m+2)
649
+ @h[i][i-3] = 0.0
650
+ end
651
+ end
652
+
653
+ # Double QR step involving rows l:n and columns m:n
654
+
655
+ m.upto(n-1) do |k|
656
+ notlast = (k != n-1)
657
+ if (k != m)
658
+ p = @h[k][k-1]
659
+ q = @h[k+1][k-1]
660
+ r = (notlast ? @h[k+2][k-1] : 0.0)
661
+ x = p.abs + q.abs + r.abs
662
+ next if x == 0
663
+ p /= x
664
+ q /= x
665
+ r /= x
666
+ end
667
+ s = Math.sqrt(p * p + q * q + r * r)
668
+ if (p < 0)
669
+ s = -s
670
+ end
671
+ if (s != 0)
672
+ if (k != m)
673
+ @h[k][k-1] = -s * x
674
+ elsif (l != m)
675
+ @h[k][k-1] = -@h[k][k-1]
676
+ end
677
+ p += s
678
+ x = p / s
679
+ y = q / s
680
+ z = r / s
681
+ q /= p
682
+ r /= p
683
+
684
+ # Row modification
685
+
686
+ k.upto(nn-1) do |j|
687
+ p = @h[k][j] + q * @h[k+1][j]
688
+ if (notlast)
689
+ p += r * @h[k+2][j]
690
+ @h[k+2][j] = @h[k+2][j] - p * z
691
+ end
692
+ @h[k][j] = @h[k][j] - p * x
693
+ @h[k+1][j] = @h[k+1][j] - p * y
694
+ end
695
+
696
+ # Column modification
697
+
698
+ 0.upto([n, k+3].min) do |i|
699
+ p = x * @h[i][k] + y * @h[i][k+1]
700
+ if (notlast)
701
+ p += z * @h[i][k+2]
702
+ @h[i][k+2] = @h[i][k+2] - p * r
703
+ end
704
+ @h[i][k] = @h[i][k] - p
705
+ @h[i][k+1] = @h[i][k+1] - p * q
706
+ end
707
+
708
+ # Accumulate transformations
709
+
710
+ low.upto(high) do |i|
711
+ p = x * @v[i][k] + y * @v[i][k+1]
712
+ if (notlast)
713
+ p += z * @v[i][k+2]
714
+ @v[i][k+2] = @v[i][k+2] - p * r
715
+ end
716
+ @v[i][k] = @v[i][k] - p
717
+ @v[i][k+1] = @v[i][k+1] - p * q
718
+ end
719
+ end # (s != 0)
720
+ end # k loop
721
+ end # check convergence
722
+ end # while (n >= low)
723
+
724
+ # Backsubstitute to find vectors of upper triangular form
725
+
726
+ if (norm == 0.0)
727
+ return
728
+ end
729
+
730
+ (nn-1).downto(0) do |k|
731
+ p = @d[k]
732
+ q = @e[k]
733
+
734
+ # Real vector
735
+
736
+ if (q == 0)
737
+ l = k
738
+ @h[k][k] = 1.0
739
+ (k-1).downto(0) do |i|
740
+ w = @h[i][i] - p
741
+ r = 0.0
742
+ l.upto(k) do |j|
743
+ r += @h[i][j] * @h[j][k]
744
+ end
745
+ if (@e[i] < 0.0)
746
+ z = w
747
+ s = r
748
+ else
749
+ l = i
750
+ if (@e[i] == 0.0)
751
+ if (w != 0.0)
752
+ @h[i][k] = -r / w
753
+ else
754
+ @h[i][k] = -r / (eps * norm)
755
+ end
756
+
757
+ # Solve real equations
758
+
759
+ else
760
+ x = @h[i][i+1]
761
+ y = @h[i+1][i]
762
+ q = (@d[i] - p) * (@d[i] - p) + @e[i] * @e[i]
763
+ t = (x * s - z * r) / q
764
+ @h[i][k] = t
765
+ if (x.abs > z.abs)
766
+ @h[i+1][k] = (-r - w * t) / x
767
+ else
768
+ @h[i+1][k] = (-s - y * t) / z
769
+ end
770
+ end
771
+
772
+ # Overflow control
773
+
774
+ t = @h[i][k].abs
775
+ if ((eps * t) * t > 1)
776
+ i.upto(k) do |j|
777
+ @h[j][k] = @h[j][k] / t
778
+ end
779
+ end
780
+ end
781
+ end
782
+
783
+ # Complex vector
784
+
785
+ elsif (q < 0)
786
+ l = n-1
787
+
788
+ # Last vector component imaginary so matrix is triangular
789
+
790
+ if (@h[n][n-1].abs > @h[n-1][n].abs)
791
+ @h[n-1][n-1] = q / @h[n][n-1]
792
+ @h[n-1][n] = -(@h[n][n] - p) / @h[n][n-1]
793
+ else
794
+ cdivr, cdivi = cdiv(0.0, -@h[n-1][n], @h[n-1][n-1]-p, q)
795
+ @h[n-1][n-1] = cdivr
796
+ @h[n-1][n] = cdivi
797
+ end
798
+ @h[n][n-1] = 0.0
799
+ @h[n][n] = 1.0
800
+ (n-2).downto(0) do |i|
801
+ ra = 0.0
802
+ sa = 0.0
803
+ l.upto(n) do |j|
804
+ ra = ra + @h[i][j] * @h[j][n-1]
805
+ sa = sa + @h[i][j] * @h[j][n]
806
+ end
807
+ w = @h[i][i] - p
808
+
809
+ if (@e[i] < 0.0)
810
+ z = w
811
+ r = ra
812
+ s = sa
813
+ else
814
+ l = i
815
+ if (@e[i] == 0)
816
+ cdivr, cdivi = cdiv(-ra, -sa, w, q)
817
+ @h[i][n-1] = cdivr
818
+ @h[i][n] = cdivi
819
+ else
820
+
821
+ # Solve complex equations
822
+
823
+ x = @h[i][i+1]
824
+ y = @h[i+1][i]
825
+ vr = (@d[i] - p) * (@d[i] - p) + @e[i] * @e[i] - q * q
826
+ vi = (@d[i] - p) * 2.0 * q
827
+ if (vr == 0.0 && vi == 0.0)
828
+ vr = eps * norm * (w.abs + q.abs +
829
+ x.abs + y.abs + z.abs)
830
+ end
831
+ cdivr, cdivi = cdiv(x*r-z*ra+q*sa, x*s-z*sa-q*ra, vr, vi)
832
+ @h[i][n-1] = cdivr
833
+ @h[i][n] = cdivi
834
+ if (x.abs > (z.abs + q.abs))
835
+ @h[i+1][n-1] = (-ra - w * @h[i][n-1] + q * @h[i][n]) / x
836
+ @h[i+1][n] = (-sa - w * @h[i][n] - q * @h[i][n-1]) / x
837
+ else
838
+ cdivr, cdivi = cdiv(-r-y*@h[i][n-1], -s-y*@h[i][n], z, q)
839
+ @h[i+1][n-1] = cdivr
840
+ @h[i+1][n] = cdivi
841
+ end
842
+ end
843
+
844
+ # Overflow control
845
+
846
+ t = [@h[i][n-1].abs, @h[i][n].abs].max
847
+ if ((eps * t) * t > 1)
848
+ i.upto(n) do |j|
849
+ @h[j][n-1] = @h[j][n-1] / t
850
+ @h[j][n] = @h[j][n] / t
851
+ end
852
+ end
853
+ end
854
+ end
855
+ end
856
+ end
857
+
858
+ # Vectors of isolated roots
859
+
860
+ nn.times do |i|
861
+ if (i < low || i > high)
862
+ i.upto(nn-1) do |j|
863
+ @v[i][j] = @h[i][j]
864
+ end
865
+ end
866
+ end
867
+
868
+ # Back transformation to get eigenvectors of original matrix
869
+
870
+ (nn-1).downto(low) do |j|
871
+ low.upto(high) do |i|
872
+ z = 0.0
873
+ low.upto([j, high].min) do |k|
874
+ z += @v[i][k] * @h[k][j]
875
+ end
876
+ @v[i][j] = z
877
+ end
878
+ end
879
+ end
880
+
881
+ end
882
+ end