matrix 0.1.0 → 0.4.2

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.
@@ -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