multilinear-io-0.2.1: src/Multilinear/Generic/Serialize.hs
{-|
Module : Multilinear.Generic.Serialize
Description : Generic array tensor serialization: binary, JSON, CSV.
Copyright : (c) Artur M. Brodzki, 2018
License : BSD3
Maintainer : artur@brodzki.org
Stability : experimental
Portability : Windows/POSIX
-}
module Multilinear.Generic.Serialize (
toBinary, toBinaryFile,
fromBinary, fromBinaryFile,
Multilinear.Generic.Serialize.toJSON, toJSONFile,
Multilinear.Generic.Serialize.fromJSON, fromJSONFile,
fromCSV, toCSV
) where
import Codec.Compression.GZip
import Control.Exception
import Control.Monad.Trans.Class
import Control.Monad.Trans.Either
import Control.Monad.Trans.Maybe
import Data.Aeson
import qualified Data.ByteString.Lazy as ByteString
import Data.CSV.Enumerator
import Data.Either
import Data.Serialize
import qualified Data.Vector as Boxed
import Data.Vector.Serialize ()
import Multilinear.Class
import Multilinear.Generic
import qualified Multilinear.Index.Finite as Finite
import Multilinear.Index.Finite.Serialize ()
import Multilinear.Index.Infinite.Serialize ()
-- Binary serialization instance
instance Serialize a => Serialize (Tensor a)
-- JSON serialization instance
instance ToJSON a => ToJSON (Tensor a)
-- JSON deserialization instance
instance FromJSON a => FromJSON (Tensor a)
invalidIndices :: String -- ^ CSV error message
invalidIndices = "Indices and its sizes not compatible with structure of matrix!"
deserializationError :: String -- ^ CSV error message
deserializationError = "Components deserialization error!"
{-| Serialize tensor to binary string -}
toBinary :: (
Serialize a
) => Tensor a -- ^ Tensor to serialize
-> ByteString.ByteString -- ^ Tensor serialized to lazy ByteString
toBinary = Data.Serialize.encodeLazy
{-| Write tensor to binary file. Uses compression with gzip -}
toBinaryFile :: (
Serialize a
) => String -- ^ File name
-> Tensor a -- ^ Tensor to serialize
-> IO ()
toBinaryFile name = ByteString.writeFile name . compress . toBinary
{-| Deserialize tensor from binary string -}
fromBinary :: (
Serialize a
) => ByteString.ByteString -- ^ ByteString to deserialize
-> Either String (Tensor a) -- ^ Deserialized tensor or an error message.
fromBinary = Data.Serialize.decodeLazy
{-| Read tensor from binary file -}
fromBinaryFile :: (
Serialize a
) => String -- ^ File path.
-> EitherT String IO (Tensor a) -- ^ Deserialized tensor or an error message
fromBinaryFile name = do
contents <- lift $ ByteString.readFile name
EitherT $ return $ fromBinary $ decompress contents
{-| Serialize tensor to JSON string -}
toJSON :: (
ToJSON a
) => Tensor a -- ^ Tensor to serialize.
-> ByteString.ByteString -- ^ Tensor serialized to lazy ByteString.
toJSON = Data.Aeson.encode
{-| Write tensor to JSON file -}
toJSONFile :: (
ToJSON a
) => String -- ^ File path.
-> Tensor a -- ^ Tensor to serialize
-> IO ()
toJSONFile name = ByteString.writeFile name . Multilinear.Generic.Serialize.toJSON
{-| Deserialize tensor from JSON string -}
fromJSON :: (
FromJSON a
) => ByteString.ByteString -- ^ ByteString to deserialize
-> Maybe (Tensor a) -- ^ Deserialized tensor or Nothing, if deserialization error occured.
fromJSON = Data.Aeson.decode
{-| Read tensor from JSON file -}
fromJSONFile :: (
FromJSON a
) => String -- ^ File path.
-> MaybeT IO (Tensor a) -- ^ Deserialized tensor or Nothing, if error occured.
fromJSONFile name = do
contents <- lift $ ByteString.readFile name
MaybeT $ return $ Multilinear.Generic.Serialize.fromJSON contents
{-| Read tensor (matrix) components from CSV file. -}
{-# INLINE fromCSV #-}
fromCSV :: (
Num a, Serialize a
) => String -- ^ Indices names (one character per index, first character: rows index, second character: columns index)
-> String -- ^ CSV file name
-> Char -- ^ Separator expected to be used in this CSV file
-> EitherT SomeException IO (Tensor a) -- ^ Generated matrix or error message
fromCSV x = case x of
[u,d] -> \fileName separator -> do
csv <- EitherT $ readCSVFile (CSVS separator (Just '"') (Just '"') separator) fileName
let components = (Data.Serialize.decode <$> ) <$> csv
let rows = length components
let columns = if rows > 0 then length $ rights (head components) else 0
if rows > 0 && columns > 0
then return $
FiniteTensor (Finite.Contravariant rows [u]) $ (
SimpleFinite (Finite.Covariant columns [d]) . Boxed.fromList . rights
) <$> Boxed.fromList components
else EitherT $ return $ Left $ SomeException $ TypeError deserializationError
_ -> \_ _ -> return $ Err invalidIndices
{-| Write matrix to CSV file. -}
{-# INLINE toCSV #-}
toCSV :: (
Num a, Serialize a
) => Tensor a -- ^ Matrix to serialize. If given tensor os not a matrix, an error occurs and no data (0 rows) are saved to file.
-> String -- ^ CSV file name
-> Char -- ^ Separator expected to be used in this CSV file
-> IO Int -- ^ Number of rows written
toCSV t = case order t of
(1,1) -> \fileName separator ->
let t' = _standardize t
elems = Boxed.toList $ Boxed.toList . tensorScalars <$> tensorsFinite t'
encodedElems = (Data.Serialize.encode <$>) <$> elems
in writeCSVFile (CSVS separator (Just '"') (Just '"') separator) fileName encodedElems
_ -> \_ _ -> return 0