biohazard-0.6.9: src/Bio/Util/Jacobi.hs
-- | Jacobi algorithm for Eigen decomposition of symmetric matrices.
module Bio.Util.Jacobi ( eigenS ) where
import Bio.Prelude
import qualified Data.Vector.Unboxed as U ( Vector, thaw, unsafeFreeze, fromListN, slice )
import qualified Data.Vector.Unboxed.Mutable as U ( read, write, MVector, length, replicate )
type Matrix = U.Vector Double
type Vector = U.Vector Double
type MMatrix s = U.MVector s Double
-- | Decomposes a symmetric matrix into a vector of eigenvalues and a
-- vector of eigenvectors.
eigenS :: Matrix -> ( Vector, [Vector] )
eigenS mat = runST (do
m <- U.thaw mat
let n = round (sqrt (fromIntegral (U.length m) :: Double))
v <- U.replicate (n*n) 0
forM_ [0..n-1] $ \i -> U.write v (n*i+i) 1
let j_iter !rd = do
sm <- sum . map abs <$> sequence
[ U.read m (p*n+q) | p <- [1..n-2], q <- [p+1..n-1] ]
case () of
_ | sm == 0 -> return () -- normal exit: convergence to machine precision
| rd == 50 -> return () -- 50 iters, weird, but IDGAF.
| otherwise -> do
let thresh = if rd < 3 then 0.2 * sm / fromIntegral (n*n) else 0
forM_ [1..n-1] $ \k ->
forM_ [0..k-1] $ \l ->
jacobi_rot n thresh (l,k) m v
j_iter (rd+1)
j_iter (0::Int)
d <- U.fromListN n <$> forM [0..n-1] (\i -> U.read m (n*i+i))
v' <- U.unsafeFreeze v
return (d, [ U.slice i n v' | i <- [0,n..n*n-1] ]))
-- | Performs one Jacobi rotation at @(p,q)@. We operate on the upper
-- triangle, so at all times @p<q@. Algorithm inspired by "Numerical
-- Recipes in C: The Art of Scientific Computing" (ISBN 0-521-43108-6)
jacobi_rot :: Int -> Double -> (Int,Int) -> MMatrix s -> MMatrix s -> ST s ()
jacobi_rot n thresh (p,q) m v = do
a_pp <- U.read m (p*n+p)
a_pq <- U.read m (p*n+q)
a_qq <- U.read m (q*n+q)
let g = 100 * abs a_pq
case () of
_ | abs a_pq <= thresh -> return ()
| abs a_pp + g == abs a_pp &&
abs a_qq + g == abs a_qq -> U.write v (p*n+q) 0
| otherwise -> do
let theta = 0.5 * (a_qq-a_pp) / a_pq
t = if theta*theta + 1 == theta*theta
then recip $ 2 * theta -- approx for overflow case
else signum theta / ( abs theta + sqrt ( theta*theta +1 ) )
c = recip $ sqrt $ t*t + 1
s = c*t
tau = s / (1+c)
forM_ [0..n-1] $ \r -> do
when (r/=p && r/=q) $ do
a_rp <- U.read m $ min (r*n+p) (p*n+r)
a_rq <- U.read m $ min (r*n+q) (q*n+r)
let a_rp' = a_rp - s * (a_rq + tau * a_rp)
a_rq' = a_rq + s * (a_rp - tau * a_rq)
U.write m (min (r*n+p) (p*n+r)) a_rp'
U.write m (min (r*n+q) (q*n+r)) a_rq'
v_rp <- U.read v (p*n+r)
v_rq <- U.read v (q*n+r)
U.write v (p*n+r) $ v_rp - s * (v_rq + tau * v_rp)
U.write v (q*n+r) $ v_rq + s * (v_rp - tau * v_rq)
U.write m (p*n+q) 0
U.write m (p*n+p) $ a_pp - t * a_pq
U.write m (q*n+q) $ a_qq + t * a_pq