easytensor-2.0.0.0: src/Numeric/DataFrame/Internal/PrimArray.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE UnboxedTuples #-}
module Numeric.DataFrame.Internal.PrimArray
( PrimArray (..), CumulDims (..)
, cumulDims, cdTotalDim, cdTotalDim#, cdIx
, ixOff, unsafeFromFlatList, getSteps, fromSteps
) where
import Data.Monoid as Mon (Monoid (..))
import Data.Semigroup as Sem (Semigroup (..))
import GHC.Base (ByteArray#, Int (..), Int#, Word (..), word2Int#)
import Numeric.Dimensions
import Numeric.PrimBytes
-- | Given @Dims ns@, @CumulativeDims@ is a list of length @Length ns + 1@;
-- which cumulative @totalDim@ accumulated on the right.
-- In particular, its first element is @totalDim ds@,
-- its last element is always is always @1@.
newtype CumulDims = CumulDims { unCumulDims :: [Word] }
instance Sem.Semigroup CumulDims where
CumulDims as <> CumulDims bs = CumulDims $ map (head bs *) (init as) ++ bs
instance Mon.Monoid CumulDims where
mempty = CumulDims [1]
#if !(MIN_VERSION_base(4,11,0))
mappend = (<>)
#endif
-- | Calculate cumulative dims
cumulDims :: Dims (ns :: [k]) -> CumulDims
cumulDims = CumulDims . uncurry (:)
. foldr (\d (c, cs) -> (c*d,c:cs)) (1, []) . listDims
-- | Get the total number of elements
cdTotalDim :: CumulDims -> Word
cdTotalDim ~(CumulDims ~(n:_)) = n
cdTotalDim# :: CumulDims -> Int#
cdTotalDim# ~(CumulDims ~(n:_)) = case n of W# w -> word2Int# w
-- | Calculate offset of an Idxs
--
-- Note, you can take offset of subspace with CumulDims of larger space
-- - very convenient!
cdIx :: CumulDims -> Idxs ns -> Int
cdIx ~(CumulDims ~(_:steps))
= fromIntegral . sum . zipWith (*) steps . listIdxs
-- | Try to get @CumulDims@ from an array,
-- and create it using @Dims@ if failed.
getSteps :: PrimArray t a => Dims (ns :: [k]) -> a -> CumulDims
getSteps dds df = case uniqueOrCumulDims df of
Left _ -> cumulDims dds
Right ds -> ds
{-# INLINE getSteps #-}
-- | Get @Dims@ by "de-accumulating" @CumulDims@.
fromSteps :: CumulDims -> SomeDims
fromSteps = someDimsVal . f . unCumulDims
where
-- ignore last value, which is always 1
f :: [Word] -> [Word]
f [] = []
f [_] = []
f [n,_] = [n]
f (a:b:cs) = a `quot` b : f (b:cs)
{-# INLINE fromSteps #-}
class PrimBytes t => PrimArray t a | a -> t where
-- | Broadcast element into array
broadcast :: t -> a
-- | Index an array given an offset
ix# :: Int# -> a -> t
-- | Generate an array using an accumulator funtion
gen# :: CumulDims
-- ^ Dimensionality of the result array;
-- Be careful! @ns@ depends on @a@, but this is not reflected in
-- types and is not checked at runtime.
-> (s -> (# s, t #))
-> s -> (# s, a #)
-- | update a single element in an array given an offset
upd# :: CumulDims
-- ^ Dimensionality of the result array;
-- Be careful! @ns@ depends on @a@, but this is not reflected in
-- types and is not checked at runtime.
-> Int# -> t -> a -> a
-- | If the array represented as a single broadcasted value,
-- return this value.
-- Otherwise, return full array content:
-- @CumulDims@, array offset (elements), byte array with the content.
arrayContent# :: a -> (# t | (# CumulDims, Int#, ByteArray# #) #)
-- | Offset of an array as a number of elements
offsetElems :: a -> Int#
offsetElems a = case arrayContent# a of
(# _ | #) -> 0#
(# | (# _, o, _ #) #) -> o
{-# INLINE offsetElems #-}
-- | Normally, this returns a cumulative @totalDim@s.
-- However, if a particular implementation does not have the dimensionality
-- information, it cannot return @CumulDims@;
-- In this case, it is a sign that all elements of an array are same.
-- Thus, it is possible to return the single element value instead.
--
-- Note, this function returns the only unique element only if it is
-- a such by construction (there is no equality checks involved).
uniqueOrCumulDims :: a -> Either t CumulDims
uniqueOrCumulDims a = case arrayContent# a of
(# x | #) -> Left x
(# | (# cd, _, _ #) #) -> Right cd
{-# INLINE uniqueOrCumulDims #-}
-- | Get array by its offset and cumulative dims in a ByteArray.
-- Both offset and dims are given in element number (not in bytes).
--
-- It is better to use this function instead of @fromBytes@ to avoid
-- recalculating @CumulDims@ for implementations that require it.
fromElems :: CumulDims -> Int# -> ByteArray# -> a
-- | Index array by an integer offset (starting from 0).
ixOff :: PrimArray t a => Int -> a -> t
ixOff (I# i) = ix# i
-- | Construct an array from a flat list and @Dims@;
-- Be careful! @ns@ depends on @a@, but this is not reflected in
-- types and is not checked at runtime.
unsafeFromFlatList :: PrimArray t a => Dims ns -> t -> [t] -> a
unsafeFromFlatList ds x0 vs = case gen# (cumulDims ds) f vs of (# _, r #) -> r
where
f [] = (# [], x0 #)
f (x:xs) = (# xs, x #)