I use LAPACK now, but long ago I wrote code for singular value 
decomposition, which includes some code for eigenvalues/vectors of a 
symmetric matrix.  I have appended it here in case you would like to use it.

Henry Rich


display =: (i.0 0)"_ ((1!:2) 2:)
dispwithts =: 3 : 'display(": 3 }. 6!:0 ''),'' '',y'

NB. y is a symmetric matrix
NB. Result is the Householder tridiagonalization:
NB. diagonal elements ; subdiagonal elements ; xform =. |: orthonormal xform
NB. (Transpose of the usual form, for computational ease)
NB. (|: xform) mp diagonal mp xform  reconstructs the original matrix
NB. Equations are from Numerical Recipes in C, 1st edition, pp. 368 ff.
NB. Henry H. Rich, 2001
trisymm =: 3 : 0
dispwithts 'starting trisymm'
diag =. offdiag =. acchu =. $0
m =. y

while. 2 < #m do.
   hh =. }. {. m  NB. 1st col of m, dropping first element
   subm =. (< ,~ <<0) { m  NB. m, dropping 1st row & column
   mag =. %: ssq =. +/ *: hh  NB. mag, magsq of hh
   smag =. mag * (0 > {. hh) { 1 _1  NB. +- mag of hh.
     NB. This produces the Householder matrix; we choose the sign
     NB. to be the same as the sign of the first element to reduce
     NB. roundoff error.
   u =. (smag + {. hh) 0} hh  NB. I - */~ normalized u will be the
                              NB. Householder matrix: first column of m,
                              NB. with the first element +- magnitude of 
column
   h =. % ssq + smag * {. hh NB. reciprocal of h: magsq of modified 
column is the magsq of
                    NB. the unmodified column +- magnitude * 1st element
   offdiag =. offdiag , - smag  NB. The resulting offdiagonal value will
                                 NB. be -+ mag of unmodified column
   p =. h * subm +/@:*"1 u   NB. Eqn. 11.2.10
   k =. -: h * +/ p * u   NB. Eqn 11.2.11
   q =. p - k * u  NB. Eqn 11.2.12
   diag =. diag , (< ,~ <0) { m   NB. top-left element stays on the diagonal
   m =. subm - (q */ u) + (u */ q)  NB. Eqn. 11.2.13
   acchu =. acchu , < h , u   NB. Accumulate h and u for rollup at end
end.
diag =. diag , (<0 1) |: m
offdiag =. offdiag , (< 0 ; <<0) { m
NB. x is h,u  y is matrix  #m = #u
NB. Result is M * (Householder matrix derived from h,u),  with identity 
column & row appended
rollup =. 13 : '(1 , (#y)$0) , 0 ,. y - (y (+/@:*)"1 (}.x)) */ ((}. * 
{.) x)'
diag ; offdiag ; rollup&.>/ acchu , < =/~ i. 2
)


NB. y is diagonal elements ; subdiagonal elements ; |: orthonormal xform
NB. Result is eigenvalues ; |: eigenvectors (i. e. 1-cells are eigenvectors)
NB. This is the QL algorithm with implicit shifts, taken from
NB. Numerical Recipes in C pp. 380-381
eigentrisymm =: 3 : 0
dispwithts 'starting eigentrisymm'

NB. We extend the offdiagonal elements with one 0 to make them
NB. match the diagonal and to provide a stopper for the search below.
d =. 0{::y
e =. (1{::y),0

v =. 2{::y  NB. Init eigenvectors to xform to date

NB. Loop to produce each eigenvalue.  We consider all elements starting at
NB. 'start' - when we compute one eigenvalue, we know that we have 
rotated the offdiagonal
NB. start{e to zero, so we put the eigenvalue over start{d, advance 
start, and continue
NB. processing the (now smaller) matrix.  This is the 'deflation' of d&e.
start =. 0
while. start < #d do.
   NB. Find a small off-diagonal element (i. e. zero to machine 
precision when compared
   NB. with the diagonal elements); the iteration can start there, since 
a rotation
   NB. of the row containing that offdiagonal will leave it zero.
   NB. If the small off-diagonal element is the first, stop iterating 
and out
   NB. the eigenvalue
   while. start < i =. split =. start + ( e ((] =!.0 +) (+ 
1&(|.!.0))@:|)&(start&}.) d ) i. 1 do.
     NB. Calculate the shift for the implicit-shift method.  di1 is used 
in the loop as
     NB. (i+1){d
     g =. ( (di1 =. split{d) - (start{d) ) + (-/ (1 0+start){d) ( ] % (+ 
  ((* 0&<:)~ >:&.*:))@(% +:) ) start{e
     s =. c =. 1  NB. Init the rotation
     p =. 0
     while. (i =. <: i) >: start do.
       NB. We are now performing Givens rotations on rows i and i+1, by 
the book
       f =. s * i{e
       b =. c * i{e
       if. (f >:&| g) do.
         e =. (f*r =. %: >: *: c =. g % f) (>:i)} e
         c =. c * s =. %r
       else.
         e =. (g*r =. %: >: *: s =. f % g) (>:i)} e
         s =. s * c =. %r
       end.
       d =. (g + p =. s * r =. ( ( (di1 =. i{d) - g =. di1 - p ) * s ) + 
+: c * b) (>:i)} d
       g =. (c*r) - b

       NB. The rotation was given by c and s.  Rotate the eigenvectors 
correspondingly
       v =. (((c,-s),:s,c) +/ . * (0 1+i) { v) (0 1+i)} v
     end.
     d =. ( (start{d) - p ) start} d   NB. Install the calculated eigenvalue
     e =. (g,0) (start,split)} e  NB. Install the first offdiagonal, and 
clear the last, which was trashed
   end.
   start =. >: start
end.
d ; v
)

NB. Return eigenvalues and eigenvectors of symmetric matrix
NB. y is symmetric matrix
NB. result is eigenvalues ; (|: eigenvectors)
symmeigeninfo =: eigentrisymm @: trisymm

NB. y is n,m$ matrix
NB. Result is y +/ . * |:y  (the input matrix is transposed already,
NB. you see).  We do this without an explicit transpose, and we take 
advantage of the
NB. symmetricity of the result: compute upper half of result, then 
reflect to bottom
NB. half
NB. calcata =: 13 : '> (([ , }...@[ ,. ])&.>)/ (<@((+/@:*)"1 {.))\. y' 
afterdisplaying 'starting calcata'
NB. This version doesn't use symmetry, but it seems to use less memory
calcata =: +/@:*"1/~

NB. svd in transpose space
NB. y is |: matrix
NB. returns:  (|: left eigenvectors),.(|: right eigenvectors),.singular 
values
NB. so if the return is x;y;z, (|:x) +/ . * y * z  is the original matrix
NB. rows of x are left eigenvectors
NB. rows of z are right eigenvectors
svd =: 3 : 0
NB. For best results y should have more columns than rows
dispwithts 'starting calcata'
'r s'=. symmeigeninfo calcata y
dispwithts 'Inverting r'
t=. %:r
dispwithts 'Calculating u and stitching results'
NB. y =. y;s;t
y =. (y +/ . * ~ s % t),.s,.t
if. 0: dispwithts 'Returning boxed results' do. end.
)

NB. =================================================
NB. test for random matrices of size y (default 3 3), or given matrix
NB. e.g. testsvd 4 5
testsvd=: 3 : 0
diagmat=. * =...@i.@#
if. 2=#$y do. mat=. y
else. mat=. ? (2{.y,3 3) $ 10 end.
x =. svd |: mat
z =. {:"1 x
y =. }:" 1 (c =. - >: {:$mat) {."1 x
x =. c }."1 x
NB. mat should be:
NB. t=. (|:x) +/ . * (diagmat z) +/ . * (y)
t=. (|:x) +/ . * y * (z)
ok1 =. *./ 0.001 > | , mat-t
NB. Furthermore, x +/ . * |:x  and y +/ . * |: y should be identity
ok2 =. *./ 0.001 > | , (=/~ i. # x) - calcata x
ok3 =. *./ 0.001 > | , (=/~ i. # y) - calcata y
ok1 *. ok2 *. ok3
)

Igor Zhuravlov wrote:
> В сообщении от Четверг 10 июня 2010 13:34:57 вы написали:
>> Although it would be a lot of work, have you considered implementing any
>> of the eigenvalue routines, like those in dgeev?  Even if it were just for
>> symmetric matrices it would be a big step forward.
> 
> You are right, there is a lot of work here. But this task is not impossible. 
> xGEEx (xGEEV and almost identical to it xGEES) code consists of approx. 7400 
> Fortran lines (including comments and excluding similar routines). Approx. 
> 20% 
> of xGEEx is already implemented [1]. I think xGEEx will be implemented some 
> day, by me or somebody else.
> 
> As for me, I'm inclining to import the generalized EV routines xGGEx (xGGEV 
> and similar xGGES) first (see TODO file in mt addon). xGGEx code consists of 
> approx. 9200 Fortran lines (including comments and excluding duplicated 
> code). 
> But:
> - approx. 40% of xGGEx is already implemented [1];
> - xGGEV will allow me to advance in yet another addon (still in development).
> 
> ---
> [1] Callgraphs for LAPACK eigenvalue-related routines are referenced in
> http://www.jsoftware.com/jwiki/Addons/math/mt/MATRIX
> 
----------------------------------------------------------------------
For information about J forums see http://www.jsoftware.com/forums.htm

Reply via email to