repa-1.0.0.0: Data/Array/Repa.hs
{-# LANGUAGE PatternGuards, PackageImports #-}
{-# LANGUAGE ScopedTypeVariables, RankNTypes #-}
{-# LANGUAGE TypeOperators, FlexibleContexts #-}
-- | See the repa-examples package for examples.
--
-- More information is also at http://code.haskell.org/trac/repa
--
-- NOTE: To get decent performance you must use GHC head branch > 6.13.20100309.
--
-- WARNING: Most of the functions that operate on indices don't perform bounds checks.
-- Doing these checks would interfere with code optimisation and reduce performance.
-- Indexing outside arrays, or failing to meet the stated obligations will
-- likely cause heap corruption.
--
--
module Data.Array.Repa
( module Data.Array.Repa.Shape
, module Data.Array.Repa.Index
, module Data.Array.Repa.Slice
, Array (..)
-- * Constructors
, fromUArray
, fromFunction
, unit
-- * Projections
, extent
, delay
, toUArray
, index, (!:)
, toScalar
-- * Basic Operations
, force
, isManifest
, deepSeqArray
-- * Conversion
, fromList
, toList
-- * Index space transformations
, reshape
, append, (+:+)
, transpose
, replicate
, slice
, backpermute
, backpermuteDft
-- * Structure preserving operations
, map
, zipWith
-- * Reductions
, fold
, sum
, sumAll
-- * Generic traversal
, traverse
, traverse2
-- * Testing
, arbitrarySmallArray
, props_DataArrayRepa)
where
import Data.Array.Repa.Index
import Data.Array.Repa.Slice
import Data.Array.Repa.Shape
import Data.Array.Repa.QuickCheck
import qualified Data.Array.Repa.Shape as S
import "dph-prim-par" Data.Array.Parallel.Unlifted (Elt)
import qualified "dph-prim-par" Data.Array.Parallel.Unlifted as U
import qualified "dph-prim-seq" Data.Array.Parallel.Unlifted.Sequential as USeq
import Test.QuickCheck
import Prelude hiding (sum, map, zipWith, replicate)
import qualified Prelude as P
stage = "Data.Array.Repa"
-- | Possibly delayed arrays.
data Array sh a
= -- | An array represented as some concrete unboxed data.
Manifest sh (U.Array a)
-- | An array represented as a function that computes each element.
| Delayed sh (sh -> a)
-- Constructors ----------------------------------------------------------------------------------
-- | Create a `Manifest` array from an unboxed `U.Array`.
-- The elements are in row-major order.
fromUArray
:: Shape sh
=> sh
-> U.Array a
-> Array sh a
{-# INLINE fromUArray #-}
fromUArray sh uarr
= sh `S.deepSeq`
uarr `seq`
Manifest sh uarr
-- | Create a `Delayed` array from a function.
fromFunction
:: Shape sh
=> sh
-> (sh -> a)
-> Array sh a
{-# INLINE fromFunction #-}
fromFunction sh fnElems
= sh `S.deepSeq` Delayed sh fnElems
-- | Wrap a scalar into a singleton array.
unit :: Elt a => a -> Array Z a
{-# INLINE unit #-}
unit = Delayed Z . const
-- Projections ------------------------------------------------------------------------------------
-- | Take the extent of an array.
extent :: Array sh a -> sh
{-# INLINE extent #-}
extent arr
= case arr of
Manifest sh _ -> sh
Delayed sh _ -> sh
-- | Unpack an array into delayed form.
delay :: (Shape sh, Elt a)
=> Array sh a
-> (sh, sh -> a)
{-# INLINE delay #-}
delay arr
= case arr of
Manifest sh uarr -> (sh, \i -> uarr U.!: S.toIndex sh i)
Delayed sh fn -> (sh, fn)
-- | Convert an array to an unboxed `U.Array`, forcing it if required.
-- The elements come out in row-major order.
toUArray
:: (Shape sh, Elt a)
=> Array sh a
-> U.Array a
{-# INLINE toUArray #-}
toUArray arr
= case force arr of
Manifest _ uarr -> uarr
_ -> error $ stage ++ ".toList: force failed"
-- | Get an indexed element from an array.
--
-- OBLIGATION: The index must be within the array.
--
-- @inRange zeroDim (shape arr) ix == True@
--
index, (!:)
:: forall sh a
. (Shape sh, Elt a)
=> Array sh a
-> sh
-> a
{-# INLINE index #-}
index arr ix
= case arr of
Delayed _ fn -> fn ix
Manifest sh uarr -> uarr U.!: (S.toIndex sh ix)
{-# INLINE (!:) #-}
(!:) arr ix = index arr ix
-- | Take the scalar value from a singleton array.
toScalar :: Elt a => Array Z a -> a
{-# INLINE toScalar #-}
toScalar arr
= case arr of
Delayed _ fn -> fn Z
Manifest _ uarr -> uarr U.!: 0
-- Basic Operations -------------------------------------------------------------------------------
-- | Force an array, so that it becomes `Manifest`.
force :: (Shape sh, Elt a)
=> Array sh a -> Array sh a
{-# INLINE force #-}
force arr
= case arr of
Manifest sh uarr
-> sh `S.deepSeq` uarr `seq`
Manifest sh uarr
Delayed sh fn
-> let uarr = U.map (fn . S.fromIndex sh)
$! U.enumFromTo (0 :: Int) (S.size sh - 1)
in sh `S.deepSeq` uarr `seq`
Manifest sh uarr
isManifest :: Array sh a -> Array sh a
{-# INLINE isManifest #-}
isManifest arr
= case arr of
Manifest{} -> arr
_ -> error "not manifest"
-- | Ensure an array's structure is fully evaluated.
-- This evaluates the extent and outer constructor, but does not `force` the elements.
infixr 0 `deepSeqArray`
deepSeqArray
:: Shape sh
=> Array sh a
-> b -> b
{-# INLINE deepSeqArray #-}
deepSeqArray arr x
= case arr of
Delayed sh _ -> sh `S.deepSeq` x
Manifest sh uarr -> sh `S.deepSeq` uarr `seq` x
-- Conversion -------------------------------------------------------------------------------------
-- | Convert a list to an array.
-- The length of the list must be exactly the `size` of the extent given, else `error`.
--
fromList
:: (Shape sh, Elt a)
=> sh
-> [a]
-> Array sh a
{-# INLINE fromList #-}
fromList sh xx
| U.length uarr /= S.size sh
= error $ unlines
[ stage ++ ".fromList: size of array shape does not match size of list"
, " size of shape = " ++ (show $ S.size sh) ++ "\n"
, " size of list = " ++ (show $ U.length uarr) ++ "\n" ]
| otherwise
= Manifest sh uarr
where uarr = U.fromList xx
-- | Convert an array to a list.
toList :: (Shape sh, Elt a)
=> Array sh a
-> [a]
{-# INLINE toList #-}
toList arr
= case force arr of
Manifest _ uarr -> U.toList uarr
_ -> error $ stage ++ ".toList: force failed"
-- Instances --------------------------------------------------------------------------------------
-- Show
instance (Shape sh, Elt a, Show a) => Show (Array sh a) where
show arr = show $ toList arr
-- Eq
instance (Shape sh, Elt a, Eq a) => Eq (Array sh a) where
{-# INLINE (==) #-}
(==) arr1 arr2
= toScalar
$ fold (&&) True
$ (flip reshape) (Z :. (S.size $ extent arr1))
$ zipWith (==) arr1 arr2
{-# INLINE (/=) #-}
(/=) a1 a2
= not $ (==) a1 a2
-- Num
-- All operators apply elementwise.
instance (Shape sh, Elt a, Num a) => Num (Array sh a) where
(+) = zipWith (+)
(-) = zipWith (-)
(*) = zipWith (*)
negate = map negate
abs = map abs
signum = map signum
fromInteger n = Delayed failShape (\_ -> fromInteger n)
where failShape = error $ stage ++ ".fromInteger: Constructed array has no shape."
-- Index space transformations --------------------------------------------------------------------
-- | Impose a new shape on the elements of an array.
-- The new extent must be the same size as the original, else `error`.
--
reshape :: (Shape sh, Shape sh', Elt a)
=> Array sh a
-> sh'
-> Array sh' a
{-# INLINE reshape #-}
reshape arr newExtent
| not $ S.size newExtent == S.size (extent arr)
= error $ stage ++ ".reshape: reshaped array will not match size of the original"
| otherwise
= Delayed newExtent
$ ((arr !:) . (S.fromIndex (extent arr)) . (S.toIndex newExtent))
-- | Append two arrays.
--
-- OBLIGATION: The higher dimensions of both arrays must have the same extent.
--
-- @tail (listOfShape (shape arr1)) == tail (listOfShape (shape arr2))@
--
append, (+:+)
:: (Shape sh, Elt a)
=> Array (sh :. Int) a
-> Array (sh :. Int) a
-> Array (sh :. Int) a
{-# INLINE append #-}
append arr1 arr2
= traverse2 arr1 arr2 fnExtent fnElem
where
(_ :. n) = extent arr1
fnExtent (sh :. i) (_ :. j)
= sh :. (i + j)
fnElem f1 f2 (sh :. i)
| i < n = f1 (sh :. i)
| otherwise = f2 (sh :. (i - n))
{-# INLINE (+:+) #-}
(+:+) arr1 arr2 = append arr1 arr2
-- | Transpose the lowest two dimensions of an array.
-- Transposing an array twice yields the original.
transpose
:: (Shape sh, Elt a)
=> Array (sh :. Int :. Int) a
-> Array (sh :. Int :. Int) a
{-# INLINE transpose #-}
transpose arr
= traverse arr
(\(sh :. m :. n) -> (sh :. n :.m))
(\f -> \(sh :. i :. j) -> f (sh :. j :. i))
-- | Replicate an array, according to a given slice specification.
replicate
:: ( Slice sl
, Shape (FullShape sl)
, Shape (SliceShape sl)
, Elt e)
=> sl
-> Array (SliceShape sl) e
-> Array (FullShape sl) e
{-# INLINE replicate #-}
replicate sl arr
= backpermute
(fullOfSlice sl (extent arr))
(sliceOfFull sl)
arr
-- | Take a slice from an array, according to a given specification.
slice :: ( Slice sl
, Shape (FullShape sl)
, Shape (SliceShape sl)
, Elt e)
=> Array (FullShape sl) e
-> sl
-> Array (SliceShape sl) e
{-# INLINE slice #-}
slice arr sl
= backpermute
(sliceOfFull sl (extent arr))
(fullOfSlice sl)
arr
-- | Backwards permutation of an array's elements.
-- The result array has the same extent as the original.
backpermute
:: forall sh sh' a
. (Shape sh, Shape sh', Elt a)
=> sh' -- ^ Extent of result array.
-> (sh' -> sh) -- ^ Function mapping each index in the result array
-- to an index of the source array.
-> Array sh a -- ^ Source array.
-> Array sh' a
{-# INLINE backpermute #-}
backpermute newExtent perm arr
= traverse arr (const newExtent) (. perm)
-- | Default backwards permutation of an array's elements.
-- If the function returns `Nothing` then the value at that index is taken
-- from the default array (@arrDft@)
backpermuteDft
:: forall sh sh' a
. (Shape sh, Shape sh', Elt a)
=> Array sh' a -- ^ Default values (@arrDft@)
-> (sh' -> Maybe sh) -- ^ Function mapping each index in the result array
-- to an index in the source array.
-> Array sh a -- ^ Source array.
-> Array sh' a
{-# INLINE backpermuteDft #-}
backpermuteDft arrDft fnIndex arrSrc
= Delayed (extent arrDft) fnElem
where fnElem ix
= case fnIndex ix of
Just ix' -> arrSrc !: ix'
Nothing -> arrDft !: ix
-- Structure Preserving Operations ----------------------------------------------------------------
-- | Apply a worker function to each element of an array,
-- yielding a new array with the same extent.
map :: (Shape sh, Elt a, Elt b)
=> (a -> b)
-> Array sh a
-> Array sh b
{-# INLINE map #-}
map f arr
= Delayed (extent arr) (f . (arr !:))
-- | Combine two arrays, element-wise, with a binary operator.
-- If the extent of the two array arguments differ,
-- then the resulting array's extent is their intersection.
zipWith :: (Shape sh, Elt a, Elt b, Elt c)
=> (a -> b -> c)
-> Array sh a
-> Array sh b
-> Array sh c
{-# INLINE zipWith #-}
zipWith f arr1 arr2
= arr1 `deepSeqArray`
arr2 `deepSeqArray`
Delayed (S.intersectDim (extent arr1) (extent arr2))
(\ix -> f (arr1 !: ix) (arr2 !: ix))
-- Reductions -------------------------------------------------------------------------------------
-- IMPORTANT:
-- These reductions use the sequential version of foldU, mapU and enumFromToU.
-- If we use parallel versions then we'll end up with nested parallelism
-- and the gang will abort at runtime.
-- | Fold the innermost dimension of an array.
-- Combine this with `transpose` to fold any other dimension.
fold :: (Shape sh, Elt a)
=> (a -> a -> a)
-> a
-> Array (sh :. Int) a
-> Array sh a
{-# INLINE fold #-}
fold f x arr
= x `seq` arr `deepSeqArray`
let sh' :. n = extent arr
elemFn i = USeq.foldU f x
$ USeq.mapU
(\ix -> arr !: (i :. ix))
(USeq.enumFromToU 0 (n - 1))
in Delayed sh' elemFn
-- | Sum the innermost dimension of an array.
sum :: (Shape sh, Elt a, Num a)
=> Array (sh :. Int) a
-> Array sh a
{-# INLINE sum #-}
sum arr = fold (+) 0 arr
-- | Sum all the elements of an array.
sumAll :: (Shape sh, Elt a, Num a)
=> Array sh a
-> a
{-# INLINE sumAll #-}
sumAll arr
= USeq.foldU (+) 0
$ USeq.mapU ((arr !:) . (S.fromIndex (extent arr)))
$ USeq.enumFromToU
0
((S.size $ extent arr) - 1)
-- Generic Traversal -----------------------------------------------------------------------------
-- | Unstructured traversal.
traverse
:: forall sh sh' a b
. (Shape sh, Shape sh', Elt a)
=> Array sh a -- ^ Source array.
-> (sh -> sh') -- ^ Function to produce the extent of the result.
-> ((sh -> a) -> sh' -> b) -- ^ Function to produce elements of the result.
-- It is passed a lookup function to get elements of the source.
-> Array sh' b
{-# INLINE traverse #-}
traverse arr transExtent newElem
= arr `deepSeqArray`
Delayed
(transExtent (extent arr))
(newElem (arr !:))
-- | Unstructured traversal over two arrays at once.
traverse2
:: forall sh sh' sh'' a b c
. ( Shape sh, Shape sh', Shape sh''
, Elt a, Elt b, Elt c)
=> Array sh a -- ^ First source array.
-> Array sh' b -- ^ Second source array.
-> (sh -> sh' -> sh'') -- ^ Function to produce the extent of the result.
-> ((sh -> a) -> (sh' -> b)
-> (sh'' -> c)) -- ^ Function to produce elements of the result.
-- It is passed lookup functions to get elements of the
-- source arrays.
-> Array sh'' c
{-# INLINE traverse2 #-}
traverse2 arrA arrB transExtent newElem
= arrA `deepSeqArray` arrB `deepSeqArray`
Delayed
(transExtent (extent arrA) (extent arrB))
(newElem ((!:) arrA) ((!:) arrB))
-- Arbitrary --------------------------------------------------------------------------------------
-- | Create an arbitrary small array, restricting the size of each of the dimensions to some value.
arbitrarySmallArray
:: (Shape sh, Elt a, Arbitrary sh, Arbitrary a)
=> Int
-> Gen (Array (sh :. Int) a)
arbitrarySmallArray maxDim
= do sh <- arbitrarySmallShape maxDim
xx <- arbitraryListOfLength (S.size sh)
return $ fromList sh xx
-- Properties -------------------------------------------------------------------------------------
-- | QuickCheck properties for this module and its children.
props_DataArrayRepa :: [(String, Property)]
props_DataArrayRepa
= props_DataArrayRepaIndex
++ [(stage ++ "." ++ name, test) | (name, test)
<- [ ("id_force/DIM5", property prop_id_force_DIM5)
, ("id_toScalarUnit", property prop_id_toScalarUnit)
, ("id_toListFromList/DIM3", property prop_id_toListFromList_DIM3)
, ("id_transpose/DIM4", property prop_id_transpose_DIM4)
, ("reshapeTransposeSize/DIM3", property prop_reshapeTranspose_DIM3)
, ("appendIsAppend/DIM3", property prop_appendIsAppend_DIM3)
, ("sumAllIsSum/DIM3", property prop_sumAllIsSum_DIM3) ]]
-- The Eq instance uses fold and zipWith.
prop_id_force_DIM5
= forAll (arbitrarySmallArray 10) $ \(arr :: Array DIM5 Int) ->
arr == force arr
prop_id_toScalarUnit (x :: Int)
= toScalar (unit x) == x
-- Conversions ------------------------
prop_id_toListFromList_DIM3
= forAll (arbitrarySmallShape 10) $ \(sh :: DIM3) ->
forAll (arbitraryListOfLength (S.size sh)) $ \(xx :: [Int]) ->
toList (fromList sh xx) == xx
-- Index Space Transforms -------------
prop_id_transpose_DIM4
= forAll (arbitrarySmallArray 20) $ \(arr :: Array DIM3 Int) ->
transpose (transpose arr) == arr
-- A reshaped array has the same size and sum as the original
prop_reshapeTranspose_DIM3
= forAll (arbitrarySmallArray 20) $ \(arr :: Array DIM3 Int) ->
let arr' = transpose arr
sh' = extent arr'
in (S.size $ extent arr) == S.size (extent (reshape arr sh'))
&& (sumAll arr == sumAll arr')
prop_appendIsAppend_DIM3
= forAll (arbitrarySmallArray 20) $ \(arr1 :: Array DIM3 Int) ->
sumAll (append arr1 arr1) == (2 * sumAll arr1)
-- Reductions --------------------------
prop_sumAllIsSum_DIM3
= forAll (arbitrarySmallShape 100) $ \(sh :: DIM2) ->
forAll (arbitraryListOfLength (S.size sh)) $ \(xx :: [Int]) ->
sumAll (fromList sh xx) == P.sum xx