fei-nn-1.0.0: src/MXNet/NN/Utils/Repa.hs
{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE Rank2Types #-}
module MXNet.NN.Utils.Repa where
import RIO
import RIO.List (splitAt)
import RIO.List.Partial (last)
import qualified RIO.Text as T (pack)
import qualified RIO.Vector.Boxed as V
import qualified RIO.Vector.Boxed.Partial as V (tail, foldl1')
import qualified RIO.Vector.Unboxed as VU
import qualified RIO.Vector.Unboxed.Partial as VU (maxIndex)
import Control.Exception (throw)
import Control.Lens
import Data.Array.Repa (Shape, Array, U, DIM1, DIM2, DIM3, DIM4, All(..), Z(..), (:.)(..), extent, toUnboxed)
import qualified Data.Array.Repa as Repa
import Text.PrettyPrint.Leijen.Text (Pretty(..), (<+>), textStrict)
newtype PrettyArray u s e = PrettyArray (Array u s e)
instance (Pretty e, VU.Unbox e, Shape d) => Pretty (PrettyArray U d e) where
pretty (PrettyArray arr) = textStrict (T.pack $ Repa.showShape $ extent arr) <+> pretty (VU.toList $ toUnboxed arr)
class IxedReadOnly m where
ixr :: Index m -> Fold m (IxValue m)
type instance Index (Array u sh a) = sh
type instance IxValue (Array u sh a) = a
instance (Repa.Source u a, Shape sh) => IxedReadOnly (Array u sh a) where
ixr i f a
| not (Repa.inShapeRange Repa.zeroDim (extent a) i) = pure a
| otherwise = f (Repa.unsafeIndex a i) *> pure a
newtype ArrayFlatten u sh a = ArrayFlatten {getArray :: Array u sh a}
type instance Index (ArrayFlatten u sh a) = Int
type instance IxValue (ArrayFlatten u sh a) = a
(^#!) :: (Repa.Source u a, Shape sh, HasCallStack) => Array u sh a -> Int -> a
a ^#! i = ArrayFlatten a ^?! ixr i
instance (Repa.Source u a, Shape sh) => IxedReadOnly (ArrayFlatten u sh a) where
ixr i f aflt@(ArrayFlatten a)
| not (i >= 0 && i < Repa.size (extent a)) = pure aflt
| otherwise = f (Repa.unsafeLinearIndex a i) *> pure aflt
expandDim :: (Shape sh, VU.Unbox e) => Int -> Array U sh e -> Array U (sh :. Int) e
expandDim axis arr | axis >=0 && axis < rank = Repa.computeS $ Repa.reshape shape_new arr
| otherwise = error "Bad axis to expand."
where
shape = extent arr
rank = Repa.rank shape
(h, t) = splitAt (rank - axis) $ Repa.listOfShape shape
shape_new = Repa.shapeOfList $ h ++ [1] ++ t
vstack :: (Shape sh, VU.Unbox e) => V.Vector (Array U sh e) -> Array U sh e
-- alternative definition:
-- vstack = Repa.transpose . V.foldl1 (Repa.++) . V.map Repa.transpose
vstack arrs = Repa.fromUnboxed shape_new $ VU.concat $ V.toList $ V.map toUnboxed arrs
where
sumShape sh1 sh2 = let a1:r1 = reverse $ Repa.listOfShape sh1
a2:r2 = reverse $ Repa.listOfShape sh2
in if r1 == r2
then Repa.shapeOfList $ reverse $ (a1+a2):r1
else error "Cannot stack array because of incompatible shapes"
shape_new = V.foldl1' sumShape $ V.map extent arrs
vunstack :: (Unstackable sh, VU.Unbox e) => Array U sh e -> V.Vector (Array U (PredDIM sh) e)
vunstack arr = V.map (\i -> Repa.computeS $ Repa.slice arr (makeSliceAtAxis0 shape i)) range
where
shape = extent arr
dim0 = last $ Repa.listOfShape shape
range = V.enumFromN (0::Int) dim0
class (Shape sh,
Shape (PredDIM sh),
Repa.Slice (SliceAtAxis0 sh),
Repa.FullShape (SliceAtAxis0 sh) ~ sh,
Repa.SliceShape (SliceAtAxis0 sh) ~ PredDIM sh
) => Unstackable sh where
type PredDIM sh
type SliceAtAxis0 sh
makeSliceAtAxis0 :: sh -> Int -> SliceAtAxis0 sh
instance Unstackable DIM2 where
type PredDIM DIM2 = DIM1
type SliceAtAxis0 DIM2 = Z:.Int:.All
makeSliceAtAxis0 _ i = Z:.i:.All
instance Unstackable DIM3 where
type PredDIM DIM3 = DIM2
type SliceAtAxis0 DIM3 = Z:.Int:.All:.All
makeSliceAtAxis0 (sh:._) i = makeSliceAtAxis0 sh i :. All
instance Unstackable DIM4 where
type PredDIM DIM4 = DIM3
type SliceAtAxis0 DIM4 = Z:.Int:.All:.All:.All
makeSliceAtAxis0 (sh:._) i = makeSliceAtAxis0 sh i :. All
data ReshapeError = ReshapeMismatch (V.Vector Int) (V.Vector Int)
| ReshapeTooManyMinusOne (V.Vector Int)
deriving Show
instance Exception ReshapeError
reshapeEx :: (Shape sh1, Shape sh2, VU.Unbox e) => sh2 -> Array U sh1 e -> Array U sh2 e
reshapeEx shape arr = Repa.computeS $ Repa.reshape real_new_shape arr
where
old_shape = V.reverse $ V.fromList $ Repa.listOfShape $ extent arr
new_shape = V.reverse $ V.fromList $ Repa.listOfShape shape
shapeMismatch, tooManyN1 :: forall a. a
shapeMismatch = throw (ReshapeMismatch new_shape old_shape)
tooManyN1 = throw (ReshapeTooManyMinusOne new_shape)
sizeEqual sh = V.product old_shape == V.product sh
replaceZ i v | v == 0 = case old_shape V.!? i of
Just v' -> v'
Nothing -> shapeMismatch
| otherwise = v
new_shape_nz = V.imap replaceZ new_shape
minus_n1s = V.elemIndices (-1) new_shape_nz
filled_new_shape
| V.null minus_n1s = if sizeEqual new_shape_nz then new_shape_nz else shapeMismatch
| [s] <- V.toList minus_n1s = let (new_p1, new_p2) = V.splitAt s new_shape_nz
in matchN1 new_p1 (V.tail new_p2) old_shape
| otherwise = tooManyN1
matchN1 sh1a sh1b sh2 | r == 0 = sh1a V.++ V.fromList [q] V.++ sh1b
| otherwise = shapeMismatch
where size1 = V.product $ sh1a V.++ sh1b
size2 = V.product sh2
(q, r) = size2 `divMod` size1
real_new_shape = Repa.shapeOfList $ V.toList $ V.reverse filled_new_shape
argMax :: (VU.Unbox e, Ord e)
=> Array U DIM2 e -> V.Vector Int
--argMax overlaps =
-- let Z :. m :. n = extent overlaps
-- findMax row = VU.maxIndex $ toUnboxed $ Repa.computeS $ Repa.slice overlaps (Z :. row :. All)
-- in V.map findMax $ V.enumFromN (0 :: Int) m
argMax arr = V.map (VU.maxIndex . toUnboxed) (vunstack arr)