matrix 0.1.0 → 0.4.3

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