packages feed

lapack-0.1: src/Numeric/LAPACK/Eigen/General.hs

{-# LANGUAGE TypeFamilies #-}
module Numeric.LAPACK.Eigen.General (
   values,
   schur,
   decompose,
   ComplexOf,
   ) where

import Numeric.LAPACK.Matrix.Square (Square)

import qualified Numeric.LAPACK.Matrix.Shape.Private as MatrixShape
import Numeric.LAPACK.Matrix.Shape.Private (Order(RowMajor,ColumnMajor))
import Numeric.LAPACK.Vector (Vector)
import Numeric.LAPACK.Private
         (ComplexOf, RealOf, zero, withAutoWorkspaceInfo,
          copyToTemp, copyToColumnMajor, allocArray)

import qualified Numeric.LAPACK.FFI.Complex as LapackComplex
import qualified Numeric.LAPACK.FFI.Real as LapackReal
import qualified Numeric.BLAS.FFI.Complex as BlasComplex
import qualified Numeric.BLAS.FFI.Real as BlasReal
import qualified Numeric.Netlib.Utility as Call
import qualified Numeric.Netlib.Class as Class

import qualified Data.Array.Comfort.Storable.Internal as Array
import qualified Data.Array.Comfort.Shape as Shape
import Data.Array.Comfort.Storable.Internal (Array(Array))

import System.IO.Unsafe (unsafePerformIO)

import Foreign.Marshal.Array (advancePtr, peekArray)
import Foreign.C.Types (CInt, CChar)
import Foreign.ForeignPtr (withForeignPtr)
import Foreign.Ptr (Ptr, nullPtr, nullFunPtr, castPtr)
import Foreign.Storable (Storable)

import Control.Monad.Trans.Cont (ContT(ContT), evalContT)
import Control.Monad.IO.Class (liftIO)

import Data.Complex (Complex)


values ::
   (Shape.C sh, Class.Floating a) =>
   Square sh a -> Vector sh (ComplexOf a)
values =
   getValues $
   Class.switchFloating
      (Values valuesAux) (Values valuesAux)
      (Values valuesAux) (Values valuesAux)

type Values_ sh a = Square sh a -> Vector sh (ComplexOf a)

newtype Values sh a = Values {getValues :: Values_ sh a}

valuesAux ::
   (Shape.C sh, Class.Floating a, RealOf a ~ ar, Storable ar) =>
   Values_ sh a
valuesAux (Array (MatrixShape.Square _order size) a) =
      Array.unsafeCreateWithSize size $ \n wPtr -> do
   let lda = n
   evalContT $ do
      jobvsPtr <- Call.char 'N'
      sortPtr <- Call.char 'N'
      aPtr <- copyToTemp (n*n) a
      ldaPtr <- Call.cint lda
      sdimPtr <- Call.alloca
      let vsPtr = nullPtr
      ldvsPtr <- Call.cint n
      let bworkPtr = nullPtr
      liftIO $ withAutoWorkspaceInfo "gees" $ \workPtr lworkPtr infoPtr ->
         gees
            jobvsPtr sortPtr n aPtr ldaPtr sdimPtr
            wPtr vsPtr ldvsPtr workPtr lworkPtr bworkPtr infoPtr


{- |
If @(q,r) = schur a@, then @a = q \<#\> r \<#\> adjoint q@,
where @q@ is unitary (orthogonal)
and @r@ is a right-upper triangular matrix for complex @a@
and a 1x1-or-2x2-block upper triangular matrix for real @a@.
With @getDiagonal r@ you get all eigenvalues of @a@ if @a@ is complex
and the real parts of the eigenvalues if @a@ is real.
Complex conjugated eigenvalues of a real matrix @a@
are encoded as 2x2 blocks along the diagonal.
-}
schur ::
   (Shape.C sh, Class.Floating a) =>
   Square sh a -> (Square sh a, Square sh a)
schur =
   getSchur $
   Class.switchFloating
      (Schur schurAux) (Schur schurAux)
      (Schur schurAux) (Schur schurAux)

type Schur_ sh a = Square sh a -> (Square sh a, Square sh a)

newtype Schur sh a = Schur {getSchur :: Schur_ sh a}

schurAux ::
   (Shape.C sh, Class.Floating a, RealOf a ~ ar, Storable ar) =>
   Schur_ sh a
schurAux (Array (MatrixShape.Square order size) a) = unsafePerformIO $ do
   let n = Shape.size size
   let lda = n
   let sh = MatrixShape.Square ColumnMajor size
   evalContT $ do
      jobvsPtr <- Call.char 'V'
      sortPtr <- Call.char 'N'
      aPtr <- ContT $ withForeignPtr a
      (s,sPtr) <- allocArray sh
      liftIO $ copyToColumnMajor order n n aPtr sPtr
      ldaPtr <- Call.cint lda
      sdimPtr <- Call.alloca
      wPtr <- Call.allocaArray n
      (vs,vsPtr) <- allocArray sh
      ldvsPtr <- Call.cint n
      let bworkPtr = nullPtr
      liftIO $ withAutoWorkspaceInfo "gees" $ \workPtr lworkPtr infoPtr ->
         gees
            jobvsPtr sortPtr n sPtr ldaPtr sdimPtr
            wPtr vsPtr ldvsPtr workPtr lworkPtr bworkPtr infoPtr
      return (vs, s)



type GEES_ ar a =
   Ptr CChar -> Ptr CChar -> Int -> Ptr a -> Ptr CInt ->
   Ptr CInt -> Ptr (Complex ar) -> Ptr a -> Ptr CInt ->
   Ptr a -> Ptr CInt -> Ptr Bool -> Ptr CInt -> IO ()

newtype GEES a = GEES {getGEES :: GEES_ (RealOf a) a}

gees :: Class.Floating a => GEES_ (RealOf a) a
gees =
   getGEES $
   Class.switchFloating
      (GEES geesReal) (GEES geesReal) (GEES geesComplex) (GEES geesComplex)

geesReal :: Class.Real a => GEES_ a a
geesReal
      jobvsPtr sortPtr n aPtr ldaPtr sdimPtr
      wPtr vsPtr ldvsPtr workPtr lworkPtr bworkPtr infoPtr =
   evalContT $ do
      let selectPtr = nullFunPtr
      nPtr <- Call.cint n
      wrPtr <- Call.allocaArray n
      wiPtr <- Call.allocaArray n
      liftIO $
         LapackReal.gees
            jobvsPtr sortPtr selectPtr nPtr aPtr ldaPtr sdimPtr
            wrPtr wiPtr vsPtr ldvsPtr workPtr lworkPtr bworkPtr infoPtr
      liftIO $ zipComplex n wrPtr wiPtr wPtr

geesComplex :: Class.Real a => GEES_ a (Complex a)
geesComplex
      jobvsPtr sortPtr n aPtr ldaPtr sdimPtr
      wPtr vsPtr ldvsPtr workPtr lworkPtr bworkPtr infoPtr =
   evalContT $ do
      let selectPtr = nullFunPtr
      nPtr <- Call.cint n
      rworkPtr <- Call.allocaArray n
      liftIO $
         LapackComplex.gees
            jobvsPtr sortPtr selectPtr nPtr aPtr ldaPtr sdimPtr
            wPtr vsPtr ldvsPtr workPtr lworkPtr rworkPtr bworkPtr infoPtr



{- |
@(vr,d,vl) = Eigen.decompose a@

Counterintuitively, @vr@ contains the right eigenvectors
and @vl@ contains the left eigenvectors as columns.
The idea is to provide a decomposition of @a@.
If @a@ is diagonalizable, then @vr@ and @vl@ are almost inverse to each other.
More precisely, @adjoint vl \<#\> vr@ is a diagonal matrix.
This is because all eigenvectors are normalized to Euclidean norm 1.
With the following scaling, the decomposition becomes perfect:

> let scal = Array.map recip $ getDiagonal $ adjoint vl <#> vr
> a == vr <#> diagonal d <#> diagonal scal <#> adjoint vl

If @a@ is non-diagonalizable then some columns of @vr@ and @vl@ are left zero
and the above property does not hold.
-}
decompose ::
   (Shape.C sh, Class.Floating a) =>
   Square sh a ->
   (Square sh (ComplexOf a),
    Vector sh (ComplexOf a),
    Square sh (ComplexOf a))
decompose =
   getDecompose $
   Class.switchFloating
      (Decompose decomposeReal)
      (Decompose decomposeReal)
      (Decompose decomposeComplex)
      (Decompose decomposeComplex)

newtype Decompose sh a =
   Decompose {
      getDecompose ::
         Square sh a ->
         (Square sh (ComplexOf a),
          Vector sh (ComplexOf a),
          Square sh (ComplexOf a))
   }

decomposeReal ::
   (Shape.C sh, Class.Real a) =>
   Square sh a ->
   (Square sh (Complex a), Vector sh (Complex a), Square sh (Complex a))
decomposeReal (Array (MatrixShape.Square order size) a) =
      unsafePerformIO $ do
   let n = Shape.size size
   let lda = n
   evalContT $ do
      jobvlPtr <- Call.char 'V'
      jobvrPtr <- Call.char 'V'
      nPtr <- Call.cint n
      aPtr <- copyToTemp (n*n) a
      ldaPtr <- Call.cint lda
      wrPtr <- Call.allocaArray n
      wiPtr <- Call.allocaArray n
      vlPtr <- Call.allocaArray (n*n)
      ldvlPtr <- Call.cint n
      vrPtr <- Call.allocaArray (n*n)
      ldvrPtr <- Call.cint n
      liftIO $ withAutoWorkspaceInfo "geev" $
         LapackReal.geev
            jobvlPtr jobvrPtr nPtr aPtr ldaPtr
            wrPtr wiPtr vlPtr ldvlPtr vrPtr ldvrPtr
      (w,wPtr) <- allocArray size
      liftIO $ zipComplex n wrPtr wiPtr wPtr
      let sh = MatrixShape.Square ColumnMajor size
      (vlc,vlcPtr) <- allocArray sh
      (vrc,vrcPtr) <- allocArray sh
      liftIO $ eigenvectorsToComplex n wiPtr vlPtr vlcPtr
      liftIO $ eigenvectorsToComplex n wiPtr vrPtr vrcPtr
      return $
         case order of
            RowMajor -> (vlc, w, vrc)
            ColumnMajor -> (vrc, w, vlc)

eigenvectorsToComplex ::
   (Eq a, Class.Real a) =>
   Int -> Ptr a -> Ptr a -> Ptr (Complex a) -> IO ()
eigenvectorsToComplex n wiPtr vPtr vcPtr = evalContT $ do
   nPtr <- Call.cint n
   zeroPtr <- Call.real zero
   inc0Ptr <- Call.cint 0
   inc1Ptr <- Call.cint 1
   inc2Ptr <- Call.cint 2
   liftIO $ do
      let go _ _ [] = return ()
          go xPtr yPtr (False:wi) = do
            let yrPtr = castPtr yPtr
            let yiPtr = advancePtr yrPtr 1
            BlasReal.copy nPtr xPtr    inc1Ptr yrPtr inc2Ptr
            BlasReal.copy nPtr zeroPtr inc0Ptr yiPtr inc2Ptr
            go (advancePtr xPtr n) (advancePtr yPtr n) wi
          go xPtr yPtr (True:True:wi) = do
            let xrPtr = xPtr
            let xiPtr = advancePtr xPtr n
            let yrPtr = castPtr yPtr
            let yiPtr = advancePtr yrPtr 1
            let y1Ptr = advancePtr yPtr n
            BlasReal.copy nPtr xrPtr inc1Ptr yrPtr inc2Ptr
            BlasReal.copy nPtr xiPtr inc1Ptr yiPtr inc2Ptr
            BlasComplex.copy nPtr yPtr inc1Ptr y1Ptr inc1Ptr
            LapackComplex.lacgv nPtr y1Ptr inc1Ptr
            go (advancePtr xPtr (2*n)) (advancePtr yPtr (2*n)) wi
          go _xPtr _yPtr wi =
            error $ "eigenvectorToComplex: invalid non-real pattern " ++ show wi
      go vPtr vcPtr . map (zero/=) =<< peekArray n wiPtr

decomposeComplex ::
   (Shape.C sh, Class.Real a) =>
   Square sh (Complex a) ->
   (Square sh (Complex a), Vector sh (Complex a), Square sh (Complex a))
decomposeComplex (Array (MatrixShape.Square order size) a) =
      unsafePerformIO $ do
   let n = Shape.size size
   let lda = n
   evalContT $ do
      jobvlPtr <- Call.char 'V'
      jobvrPtr <- Call.char 'V'
      nPtr <- Call.cint n
      aPtr <- copyToTemp (n*n) a
      ldaPtr <- Call.cint lda
      (w,wPtr) <- allocArray size
      let sh = MatrixShape.Square ColumnMajor size
      (vl,vlPtr) <- allocArray sh
      ldvlPtr <- Call.cint n
      (vr,vrPtr) <- allocArray sh
      ldvrPtr <- Call.cint n
      rworkPtr <- Call.allocaArray (2*n)
      liftIO $ withAutoWorkspaceInfo "geev" $ \workPtr lworkPtr infoPtr ->
         LapackComplex.geev
            jobvlPtr jobvrPtr nPtr aPtr ldaPtr
            wPtr vlPtr ldvlPtr vrPtr ldvrPtr
            workPtr lworkPtr rworkPtr infoPtr
      return $
         case order of
            RowMajor -> (vl, w, vr)
            ColumnMajor -> (vr, w, vl)


zipComplex ::
   (Class.Real a) => Int -> Ptr a -> Ptr a -> Ptr (Complex a) -> IO ()
zipComplex n vr vi vc =
   evalContT $ do
      nPtr <- Call.cint n
      incxPtr <- Call.cint 1
      incyPtr <- Call.cint 2
      let yPtr = castPtr vc
      liftIO $ BlasReal.copy nPtr vr incxPtr yPtr incyPtr
      liftIO $ BlasReal.copy nPtr vi incxPtr (advancePtr yPtr 1) incyPtr