packages feed

BCMtools-0.1.0: src/BCM/DiskMatrix.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ScopedTypeVariables #-}
module BCM.DiskMatrix
    ( Offset
    , DiskData(..)
    , DiskMatrix(..)
    , read
    , write
    , DMatrix(..)
    , DSMatrix(..)
    ) where

import Prelude hiding (replicate)
import Control.Monad (replicateM_)
import Control.Monad.IO.Class (MonadIO(..))
import Control.Applicative ((<$>))
import qualified Data.ByteString.Lazy as L
import Data.Bits (shiftR)
import Data.Binary.IEEE754 (getFloat64le, putFloat64le)
import Data.Binary.Put (putWord32le, putWord64le, runPut)
import Data.Binary.Get (getWord32le, getWord64le, runGet)
import qualified Data.Vector.Generic as G
import System.IO
import Numeric (showHex)

import BCM.Matrix.Instances (d_matrix_magic, ds_matrix_magic)

type Offset = Integer

class DiskData a where
    -- | The default value
    zero :: a

    sizeOf :: a -> Int

    fromByteString :: L.ByteString -> a
    
    toByteString :: a -> L.ByteString

    hRead1 :: MonadIO m => Handle -> m a
    hRead1 h = liftIO $ fmap fromByteString $ L.hGet h $ sizeOf (undefined :: a)
    {-# INLINE hRead1 #-}

    hWrite1 :: MonadIO m => Handle -> a -> m ()
    hWrite1 h = liftIO . L.hPut h . toByteString
    {-# INLINE hWrite1 #-}

    {-# MINIMAL zero, sizeOf, fromByteString, toByteString #-}

instance DiskData Double where
    zero = 0.0

    sizeOf _ = 8
    {-# INLINE sizeOf #-}

    fromByteString = runGet getFloat64le
    {-# INLINE fromByteString #-}

    toByteString = runPut . putFloat64le
    {-# INLINE toByteString #-}

instance DiskData Int where
    zero = 0

    sizeOf _ = 8
    {-# INLINE sizeOf #-}

    fromByteString = fromIntegral . runGet getWord64le
    {-# INLINE fromByteString #-}

    toByteString = runPut . putWord64le . fromIntegral
    {-# INLINE toByteString #-}

-- | Matrix stored in binary file
class DiskData a => DiskMatrix m a where
    elemType :: m a -> a
    elemType _ = undefined
    {-# INLINE elemType #-}

    hReadMatrixEither :: MonadIO io => Handle -> io (Either String (m a))

    dim :: m a -> (Int, Int)

    replicate :: MonadIO io => Handle -> (Int, Int) -> a -> io (m a)

    unsafeRead :: MonadIO io => m a -> (Int, Int) -> io a

    unsafeWrite :: MonadIO io => m a -> (Int, Int) -> a -> io ()

    unsafeReadRow :: (G.Vector v a, MonadIO io) => m a -> Int -> io (v a)
    unsafeReadRow mat i = G.generateM c $ \j -> unsafeRead mat (i,j)
      where
        (_,c) = dim mat
    {-# INLINE unsafeReadRow #-}

    close :: MonadIO io => m a -> io ()

-- Derived methods

write :: (MonadIO io, DiskMatrix m a) => m a -> (Int, Int) -> a -> io ()
write mat (i,j) x | i >= r || j >= c = error "Index out of bounds"
                  | otherwise = unsafeWrite mat (i,j) x
  where
    (r,c) = dim mat
{-# INLINE write #-}

data DMatrix a = DMatrix !Int  -- ^ rows
                         !Int  -- ^ cols
                         !Offset -- offset
                         !Handle  -- ^ file handle

instance DiskData a => DiskMatrix DMatrix a where
    hReadMatrixEither h = liftIO $ do
        p <- hTell h
        magic <- runGet getWord32le <$> L.hGet h 4
        if magic == d_matrix_magic
           then do
               r <- hRead1 h
               c <- hRead1 h
               return $ Right $ DMatrix r c (p+20) h
           else return $ Left $ "Read fail: wrong signature: 0x" ++ showHex magic ""
    {-# INLINE hReadMatrixEither #-}

    dim (DMatrix r c _ _) = (r,c)
    {-# INLINE dim #-}

    replicate h (r,c) x = liftIO $ do
        p <- hTell h
        L.hPut h $ runPut $ putWord32le d_matrix_magic
        hWrite1 h r
        hWrite1 h c
        replicateM_ (r*c) $ hWrite1 h x
        return $ DMatrix r c (p+20) h
    {-# INLINE replicate #-}

    unsafeRead mat@(DMatrix _ c offset h) (i,j) = liftIO $ do
        hSeek h AbsoluteSeek $ offset + fromIntegral (sizeOf (elemType mat) * idx c i j)
        hRead1 h
    {-# INLINE unsafeRead #-}

    unsafeWrite mat@(DMatrix _ c offset h) (i,j) x = liftIO $ do
        hSeek h AbsoluteSeek $ offset + fromIntegral (sizeOf (elemType mat) * idx c i j)
        hWrite1 h x
    {-# INLINE unsafeWrite #-}

    unsafeReadRow mat@(DMatrix _ c offset h) i = liftIO $ do
        hSeek h AbsoluteSeek $ offset + fromIntegral (size * c * i)
        G.replicateM c $ hRead1 h
      where
        size = sizeOf $ elemType mat
    {-# INLINE unsafeReadRow #-}

    close (DMatrix _ _ _ h) = liftIO $ hClose h

-- | Symmetric matrix
data DSMatrix a = DSMatrix !Int  -- ^ size
                           !Offset  -- ^ offset
                           !Handle  -- ^ file handle

instance DiskData a => DiskMatrix DSMatrix a where
    hReadMatrixEither h = liftIO $ do
        p <- hTell h
        magic <- runGet getWord32le <$> L.hGet h 4
        if magic == ds_matrix_magic
           then do
               n <- hRead1 h
               return $ Right $ DSMatrix n (p+12) h
           else return $ Left $ "Read fail: wrong signature: 0x" ++ showHex magic ""
    {-# INLINE hReadMatrixEither #-}

    dim (DSMatrix n _ _) = (n,n)
    {-# INLINE dim #-}

    replicate h (r,c) x
        | r /= c = error "Not a sqaure matrix"
        | otherwise = liftIO $ do
            p <- hTell h
            L.hPut h $ runPut $ putWord32le ds_matrix_magic
            hWrite1 h r
            replicateM_ (((r+1)*r) `shiftR` 1) $ hWrite1 h x
            return $ DSMatrix r (p+12) h
    {-# INLINE replicate #-}

    unsafeRead mat@(DSMatrix n offset h) (i,j) = liftIO $ do
        hSeek h AbsoluteSeek $ offset + fromIntegral (sizeOf (elemType mat) * idx' n i j)
        hRead1 h
    {-# INLINE unsafeRead #-}

    unsafeWrite mat@(DSMatrix n offset h) (i,j) x = liftIO $ do
        hSeek h AbsoluteSeek $ offset + fromIntegral (sizeOf (elemType mat) * idx' n i j)
        hWrite1 h x
    {-# INLINE unsafeWrite #-}

    close (DSMatrix _ _ h) = liftIO $ hClose h

------------------------------------------------------------------------------
-- helper functions
------------------------------------------------------------------------------

-- normal matrix indexing
idx :: Int -> Int -> Int -> Int
idx c i j = i * c + j
{-# INLINE idx #-}

-- upper triangular matrix indexing
idx' :: Int -> Int -> Int -> Int
idx' n i j | i <= j = (i * (2 * n - i - 1)) `shiftR` 1 + j
           | otherwise = (j * (2 * n - j - 1)) `shiftR` 1 + i
{-# INLINE idx' #-}