{-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GADTs #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -Wno-unticked-promoted-constructors #-} -- | -- Module : Data.Massiv.Core.Index.Internal -- Copyright : (c) Alexey Kuleshevich 2018-2022 -- License : BSD3 -- Maintainer : Alexey Kuleshevich -- Stability : experimental -- Portability : non-portable module Data.Massiv.Core.Index.Internal ( Sz (SafeSz), pattern Sz, pattern Sz1, unSz, zeroSz, oneSz, liftSz, liftSz2, consSz, unconsSz, snocSz, unsnocSz, setSzM, insertSzM, pullOutSzM, mkSzM, Dim (..), Dimension (DimN), pattern Dim1, pattern Dim2, pattern Dim3, pattern Dim4, pattern Dim5, IsIndexDimension, IsDimValid, ReportInvalidDim, Lower, Index (..), iterA_, iterM_, Ix0 (..), type Ix1, pattern Ix1, IndexException (..), SizeException (..), ShapeException (..), showsPrecWrapped, ) where import Control.DeepSeq import Control.Exception (Exception (..), throw) import Control.Monad (void, when) import Control.Monad.Catch (MonadThrow (..)) import Control.Monad.ST import Control.Scheduler import Data.Coerce import Data.Kind import Data.Massiv.Core.Loop import Data.Typeable import GHC.TypeLits import System.Random.Stateful -- | `Sz` is the size of the array. It describes total number of elements along -- each dimension in the array. It is a wrapper around an index of the same -- dimension, however it provides type safety preventing mixup with -- index. Moreover the @Sz@ constructor and others such as -- `Data.Massiv.Core.Index.Sz1`, `Data.Massiv.Core.Index.Sz2`, ... that -- are specialized to specific dimensions, prevent creation of invalid sizes with -- negative values by clamping them to zero. -- -- ====__Examples__ -- -- >>> import Data.Massiv.Array -- >>> Sz (1 :> 2 :. 3) -- Sz (1 :> 2 :. 3) -- -- `Sz` has a `Num` instance, which is very convenient: -- -- >>> Sz (1 :> 2 :. 3) + 5 -- Sz (6 :> 7 :. 8) -- -- However subtraction can sometimes lead to surprising behavior, because size is not -- allowed to take negative values it will be clamped at 0. -- -- >>> Sz (1 :> 2 :. 3) - 2 -- Sz (0 :> 0 :. 1) -- -- __Warning__: It is always wrong to `negate` a size, thus it will result in an -- error. For that reason also watch out for partially applied @(`Prelude.-` sz)@, which is -- deugared into @`negate` sz@. See more info about it in -- [#114](https://github.com/lehins/massiv/issues/114). -- -- @since 0.3.0 newtype Sz ix = -- | Safe size constructor. It is unsafe to use it without making sure that it does not contain -- negative components. Use `Data.Massiv.Core.Index.Sz` pattern instead. -- -- @since 0.3.0 SafeSz ix deriving (Eq, Ord, NFData) -- | A safe bidirectional pattern synonym for `Sz` construction that will make sure that none of -- the size elements are negative. -- -- @since 0.3.0 pattern Sz :: Index ix => ix -> Sz ix pattern Sz ix <- SafeSz ix where Sz ix = SafeSz (liftIndex (max 0) ix) {-# COMPLETE Sz #-} -- | 1-dimensional size constructor. Especially useful with literals: @(Sz1 5) == Sz (5 :: Int)@. -- -- @since 0.3.0 pattern Sz1 :: Ix1 -> Sz Ix1 pattern Sz1 ix <- SafeSz ix where Sz1 ix = SafeSz (max 0 ix) {-# COMPLETE Sz1 #-} instance (UniformRange ix, Index ix) => Uniform (Sz ix) where uniformM g = SafeSz <$> uniformRM (pureIndex 0, pureIndex maxBound) g {-# INLINE uniformM #-} instance UniformRange ix => UniformRange (Sz ix) where uniformRM (SafeSz l, SafeSz u) g = SafeSz <$> uniformRM (l, u) g {-# INLINE uniformRM #-} instance (UniformRange ix, Index ix) => Random (Sz ix) instance Index ix => Show (Sz ix) where showsPrec n sz@(SafeSz usz) = showsPrecWrapped n (str ++) where str = "Sz" ++ case unDim (dimensions sz) of 1 -> "1 " ++ show usz _ -> " (" ++ shows usz ")" -- | Calling `negate` is an error. instance (Num ix, Index ix) => Num (Sz ix) where (+) x y = Sz (coerce x + coerce y) {-# INLINE (+) #-} (-) x y = Sz (coerce x - coerce y) {-# INLINE (-) #-} (*) x y = Sz (coerce x * coerce y) {-# INLINE (*) #-} abs !x = x {-# INLINE abs #-} negate x | x == zeroSz = x | otherwise = error $ "Attempted to negate: " ++ show x ++ ", this can lead to unexpected behavior. See https://github.com/lehins/massiv/issues/114" {-# INLINE negate #-} signum x = SafeSz (signum (coerce x)) {-# INLINE signum #-} fromInteger = Sz . fromInteger {-# INLINE fromInteger #-} -- | Construct size from index while checking its correctness. Throws -- `SizeNegativeException` and `SizeOverflowException`. -- -- @since 0.6.0 mkSzM :: (Index ix, MonadThrow m) => ix -> m (Sz ix) mkSzM ix = do let guardNegativeOverflow i !acc = do when (i < 0) $ throwM $ SizeNegativeException (SafeSz ix) let acc' = i * acc when (acc' /= 0 && acc' < acc) $ throwM $ SizeOverflowException (SafeSz ix) pure acc' Sz ix <$ foldlIndex (\acc i -> acc >>= guardNegativeOverflow i) (pure 1) ix {-# INLINE mkSzM #-} -- | Function for unwrapping `Sz`. -- -- ==== __Example__ -- -- >>> import Data.Massiv.Core.Index -- >>> unSz $ Sz3 1 2 3 -- 1 :> 2 :. 3 -- -- @since 0.3.0 unSz :: Sz ix -> ix unSz (SafeSz ix) = ix {-# INLINE unSz #-} -- | An empty size with all elements in size equal to @0@. -- -- ==== __Example__ -- -- >>> import Data.Massiv.Core.Index -- >>> zeroSz :: Sz5 -- Sz (0 :> 0 :> 0 :> 0 :. 0) -- -- @since 0.3.0 zeroSz :: Index ix => Sz ix zeroSz = SafeSz (pureIndex 0) {-# INLINE zeroSz #-} -- | A singleton size with all elements in size equal to @1@. -- -- ==== __Example__ -- -- >>> import Data.Massiv.Core.Index -- >>> oneSz :: Sz3 -- Sz (1 :> 1 :. 1) -- -- @since 0.3.0 oneSz :: Index ix => Sz ix oneSz = SafeSz (pureIndex 1) {-# INLINE oneSz #-} -- | Same as `liftIndex`, but for `Sz` -- -- ==== __Example__ -- -- >>> import Data.Massiv.Core.Index -- >>> liftSz succ (Sz2 2 3) -- Sz (3 :. 4) -- -- @since 0.4.0 liftSz :: Index ix => (Int -> Int) -> Sz ix -> Sz ix liftSz f (SafeSz ix) = Sz (liftIndex f ix) {-# INLINE liftSz #-} -- | Same as `liftIndex2`, but for `Sz` -- -- ==== __Example__ -- -- >>> import Data.Massiv.Core.Index -- >>> liftSz2 (-) (Sz2 2 3) (Sz2 3 1) -- Sz (0 :. 2) -- -- @since 0.4.3 liftSz2 :: Index ix => (Int -> Int -> Int) -> Sz ix -> Sz ix -> Sz ix liftSz2 f sz1 sz2 = Sz (liftIndex2 f (coerce sz1) (coerce sz2)) {-# INLINE liftSz2 #-} -- | Same as `consDim`, but for `Sz` -- -- ==== __Example__ -- -- >>> import Data.Massiv.Core.Index -- >>> consSz (Sz1 1) (Sz2 2 3) :: Sz3 -- Sz (1 :> 2 :. 3) -- -- @since 0.3.0 consSz :: Index ix => Sz Ix1 -> Sz (Lower ix) -> Sz ix consSz (SafeSz i) (SafeSz ix) = SafeSz (consDim i ix) {-# INLINE consSz #-} -- | Same as `snocDim`, but for `Sz` -- -- ==== __Example__ -- -- >>> import Data.Massiv.Core.Index -- >>> snocSz (Sz2 2 3) (Sz1 1) :: Sz3 -- Sz (2 :> 3 :. 1) -- -- @since 0.3.0 snocSz :: Index ix => Sz (Lower ix) -> Sz Ix1 -> Sz ix snocSz (SafeSz i) (SafeSz ix) = SafeSz (snocDim i ix) {-# INLINE snocSz #-} -- | Same as `setDimM`, but for `Sz` -- -- ==== __Example__ -- -- >>> import Data.Massiv.Core.Index -- >>> setSzM (Sz2 2 3) 2 (Sz1 1) :: IO Sz2 -- Sz (1 :. 3) -- >>> setSzM (Sz2 2 3) 3 (Sz1 1) :: IO Sz2 -- *** Exception: IndexDimensionException: (Dim 3) for (2 :. 3) -- -- @since 0.3.0 setSzM :: (MonadThrow m, Index ix) => Sz ix -> Dim -> Sz Int -> m (Sz ix) setSzM (SafeSz sz) dim (SafeSz sz1) = SafeSz <$> setDimM sz dim sz1 {-# INLINE setSzM #-} -- | Same as `insertDimM`, but for `Sz` -- -- ==== __Example__ -- -- >>> import Data.Massiv.Core.Index -- >>> insertSzM (Sz2 2 3) 3 (Sz1 1) :: IO Sz3 -- Sz (1 :> 2 :. 3) -- >>> insertSzM (Sz2 2 3) 4 (Sz1 1) :: IO Sz3 -- *** Exception: IndexDimensionException: (Dim 4) for (2 :. 3) -- -- @since 0.3.0 insertSzM :: (MonadThrow m, Index ix) => Sz (Lower ix) -> Dim -> Sz Int -> m (Sz ix) insertSzM (SafeSz sz) dim (SafeSz sz1) = SafeSz <$> insertDimM sz dim sz1 {-# INLINE insertSzM #-} -- | Same as `unconsDim`, but for `Sz` -- -- ==== __Example__ -- -- >>> import Data.Massiv.Core.Index -- >>> unconsSz $ Sz3 1 2 3 -- (Sz1 1,Sz (2 :. 3)) -- -- @since 0.3.0 unconsSz :: Index ix => Sz ix -> (Sz Ix1, Sz (Lower ix)) unconsSz (SafeSz sz) = coerce (unconsDim sz) {-# INLINE unconsSz #-} -- | Same as `unsnocDim`, but for `Sz` -- -- ==== __Example__ -- -- >>> import Data.Massiv.Core.Index -- >>> unsnocSz $ Sz3 1 2 3 -- (Sz (1 :. 2),Sz1 3) -- -- @since 0.3.0 unsnocSz :: Index ix => Sz ix -> (Sz (Lower ix), Sz Ix1) unsnocSz (SafeSz sz) = coerce (unsnocDim sz) {-# INLINE unsnocSz #-} -- | Same as `pullOutDim`, but for `Sz` -- -- >>> import Data.Massiv.Core.Index -- >>> pullOutSzM (Sz3 1 2 3) 3 -- (Sz1 1,Sz (2 :. 3)) -- >>> pullOutSzM (Sz3 1 2 3) 0 -- *** Exception: IndexDimensionException: (Dim 0) for (1 :> 2 :. 3) -- -- @since 0.3.0 pullOutSzM :: (MonadThrow m, Index ix) => Sz ix -> Dim -> m (Sz Ix1, Sz (Lower ix)) pullOutSzM (SafeSz sz) = fmap coerce . pullOutDimM sz {-# INLINE pullOutSzM #-} -- | A way to select Array dimension at a value level. -- -- @since 0.1.0 newtype Dim = Dim {unDim :: Int} deriving (Eq, Ord, Num, Real, Integral, Enum, NFData) instance Show Dim where show (Dim d) = "(Dim " ++ show d ++ ")" instance Uniform Dim where uniformM g = Dim <$> uniformRM (1, maxBound) g instance UniformRange Dim where uniformRM r g = Dim <$> uniformRM (coerce r) g instance Random Dim -- | A way to select Array dimension at a type level. -- -- @since 0.2.4 data Dimension (n :: Nat) where DimN :: (1 <= n, KnownNat n) => Dimension n -- | Construct 1st dimension -- -- @since 0.2.4 pattern Dim1 :: Dimension 1 pattern Dim1 = DimN -- | Construct 2nd dimension -- -- @since 0.2.4 pattern Dim2 :: Dimension 2 pattern Dim2 = DimN -- | Construct 3rd dimension -- -- @since 0.2.4 pattern Dim3 :: Dimension 3 pattern Dim3 = DimN -- | Construct 4th dimension -- -- @since 0.2.4 pattern Dim4 :: Dimension 4 pattern Dim4 = DimN -- | Construct 5th dimension -- -- @since 0.2.4 pattern Dim5 :: Dimension 5 pattern Dim5 = DimN -- | A type level constraint that ensures index is indeed valid and that supplied dimension can be -- safely used with it. -- -- @since 0.2.4 type IsIndexDimension ix n = (1 <= n, n <= Dimensions ix, Index ix, KnownNat n) -- | This type family will always point to a type for a dimension that is one lower than the type -- argument. -- -- @since 0.1.0 type family Lower ix :: Type type family ReportInvalidDim (dims :: Nat) (n :: Nat) isNotZero isLess :: Bool where ReportInvalidDim dims n True True = True ReportInvalidDim dims n True False = TypeError ( Text "Dimension " :<>: ShowType n :<>: Text " is higher than " :<>: Text "the maximum expected " :<>: ShowType dims ) ReportInvalidDim dims n False isLess = TypeError (Text "Zero dimensional indices are not supported") type family IsDimValid ix n :: Bool where IsDimValid ix n = ReportInvalidDim (Dimensions ix) n (1 <=? n) (n <=? Dimensions ix) -- | This is bread and butter of multi-dimensional array indexing. It is unlikely that any of the -- functions in this class will be useful to a regular user, unless general algorithms are being -- implemented that do span multiple dimensions. class ( Eq ix , Ord ix , Show ix , NFData ix , Typeable ix , Eq (Lower ix) , Ord (Lower ix) , Show (Lower ix) , NFData (Lower ix) , KnownNat (Dimensions ix) ) => Index ix where -- | Type level information on how many dimensions this index has. -- -- @since 0.2.0 type Dimensions ix :: Nat -- | What is the dimensionality of this index. -- -- @since 0.2.0 dimensions :: proxy ix -> Dim -- | Total number of elements in an array of this size. -- -- @since 0.1.0 totalElem :: Sz ix -> Int -- | Prepend a dimension to the index -- -- @since 0.1.0 consDim :: Int -> Lower ix -> ix -- | Take a dimension from the index from the outside -- -- @since 0.1.0 unconsDim :: ix -> (Int, Lower ix) -- | Apppend a dimension to the index -- -- @since 0.1.0 snocDim :: Lower ix -> Int -> ix -- | Take a dimension from the index from the inside -- -- @since 0.1.0 unsnocDim :: ix -> (Lower ix, Int) -- | Pull out value at specified dimension from the index, thus also lowering it dimensionality. -- -- @since 0.2.5 pullOutDimM :: MonadThrow m => ix -> Dim -> m (Int, Lower ix) -- | Insert a dimension into the index insertDimM :: MonadThrow m => Lower ix -> Dim -> Int -> m ix -- | Extract the value index has at specified dimension. -- -- @since 0.3.0 getDimM :: MonadThrow m => ix -> Dim -> m Int getDimM ix dim = fst <$> modifyDimM ix dim id {-# INLINE [1] getDimM #-} -- | Set the value for an index at specified dimension. -- -- @since 0.3.0 setDimM :: MonadThrow m => ix -> Dim -> Int -> m ix setDimM ix dim i = snd <$> modifyDimM ix dim (const i) {-# INLINE [1] setDimM #-} -- | Update the value for an index at specified dimension and return the old value as -- well as the updated index. -- -- @since 0.4.1 modifyDimM :: MonadThrow m => ix -> Dim -> (Int -> Int) -> m (Int, ix) modifyDimM ix dim f = do i <- getDimM ix dim ix' <- setDimM ix dim (f i) pure (i, ix') {-# INLINE [1] modifyDimM #-} -- | Lift an `Int` to any index by replicating the value as many times as there are dimensions. -- -- @since 0.1.0 pureIndex :: Int -> ix -- | Zip together two indices with a function -- -- @since 0.1.0 liftIndex2 :: (Int -> Int -> Int) -> ix -> ix -> ix -- | Map a function over an index -- -- @since 0.1.0 liftIndex :: (Int -> Int) -> ix -> ix liftIndex f = liftIndex2 (\_ i -> f i) (pureIndex 0) {-# INLINE [1] liftIndex #-} -- | Perform a left fold over the index foldlIndex :: (a -> Int -> a) -> a -> ix -> a default foldlIndex :: Index (Lower ix) => (a -> Int -> a) -> a -> ix -> a foldlIndex f !acc !ix = foldlIndex f (f acc i0) ixL where !(i0, ixL) = unconsDim ix {-# INLINE [1] foldlIndex #-} -- TODO: implement in terms of foldlIndex and pull out of the class -- | Check whether index is positive and is within the size. -- -- @since 0.1.0 isSafeIndex :: Sz ix -- ^ Size -> ix -- ^ Index -> Bool default isSafeIndex :: Index (Lower ix) => Sz ix -> ix -> Bool isSafeIndex sz !ix = isSafeIndex n0 i0 && isSafeIndex szL ixL where !(n0, szL) = unconsSz sz !(i0, ixL) = unconsDim ix {-# INLINE [1] isSafeIndex #-} -- | Convert linear index from size and index -- -- @since 0.1.0 toLinearIndex :: Sz ix -- ^ Size -> ix -- ^ Index -> Ix1 default toLinearIndex :: Index (Lower ix) => Sz ix -> ix -> Ix1 toLinearIndex (SafeSz sz) !ix = toLinearIndex (SafeSz szL) ixL * n + i where !(szL, n) = unsnocDim sz !(ixL, i) = unsnocDim ix {-# INLINE [1] toLinearIndex #-} -- | Convert linear index from size and index with an accumulator. Currently is useless and will -- likely be removed in future versions. -- -- @since 0.1.0 toLinearIndexAcc :: Ix1 -> ix -> ix -> Ix1 default toLinearIndexAcc :: Index (Lower ix) => Ix1 -> ix -> ix -> Ix1 toLinearIndexAcc !acc !sz !ix = toLinearIndexAcc (acc * n + i) szL ixL where !(n, szL) = unconsDim sz !(i, ixL) = unconsDim ix {-# INLINE [1] toLinearIndexAcc #-} -- | Compute an index from size and linear index -- -- @since 0.1.0 fromLinearIndex :: Sz ix -> Ix1 -> ix default fromLinearIndex :: Index (Lower ix) => Sz ix -> Ix1 -> ix fromLinearIndex (SafeSz sz) !k = consDim q ixL where !(!q, !ixL) = fromLinearIndexAcc (snd (unconsDim sz)) k {-# INLINE [1] fromLinearIndex #-} -- | Compute an index from size and linear index using an accumulator, thus trying to optimize for -- tail recursion while getting the index computed. -- -- @since 0.1.0 fromLinearIndexAcc :: ix -> Ix1 -> (Int, ix) default fromLinearIndexAcc :: Index (Lower ix) => ix -> Ix1 -> (Ix1, ix) fromLinearIndexAcc !ix' !k = (q, consDim r ixL) where !(!m, !ix) = unconsDim ix' !(!kL, !ixL) = fromLinearIndexAcc ix k !(!q, !r) = quotRem kL m {-# INLINE [1] fromLinearIndexAcc #-} -- | A way to make sure index is withing the bounds for the supplied size. Takes two functions -- that will be invoked whenever index (2nd arg) is outsize the supplied size (1st arg) -- -- @since 0.1.0 repairIndex :: Sz ix -- ^ Size -> ix -- ^ Index -> (Sz Int -> Int -> Int) -- ^ Repair when below zero -> (Sz Int -> Int -> Int) -- ^ Repair when higher than size -> ix default repairIndex :: Index (Lower ix) => Sz ix -> ix -> (Sz Int -> Int -> Int) -> (Sz Int -> Int -> Int) -> ix repairIndex sz !ix rBelow rOver = consDim (repairIndex n i rBelow rOver) (repairIndex szL ixL rBelow rOver) where !(n, szL) = unconsSz sz !(i, ixL) = unconsDim ix {-# INLINE [1] repairIndex #-} -- | This function is what makes it possible to iterate over an array of any dimension. -- -- @since 0.1.0 iterM :: Monad m => ix -- ^ Start index -> ix -- ^ End index -> ix -- ^ Increment -> (Int -> Int -> Bool) -- ^ Continue iterating while predicate is True (eg. until end of row) -> a -- ^ Initial value for an accumulator -> (ix -> a -> m a) -- ^ Accumulator function -> m a default iterM :: (Index (Lower ix), Monad m) => ix -> ix -> ix -> (Int -> Int -> Bool) -> a -> (ix -> a -> m a) -> m a iterM !sIx eIx !incIx cond !acc f = loopM s (`cond` e) (+ inc) acc $ \ !i !acc0 -> iterM sIxL eIxL incIxL cond acc0 $ \ !ix -> f (consDim i ix) where !(s, sIxL) = unconsDim sIx !(e, eIxL) = unconsDim eIx !(inc, incIxL) = unconsDim incIx {-# INLINE iterM #-} iterRowMajorST :: Int -- ^ Scheduler multiplying factor. Must be positive -> Scheduler s a -- ^ Scheduler to use -> ix -- ^ Start index -> ix -- ^ Stride -> Sz ix -- ^ Size -> a -- ^ Initial accumulator -> (a -> ST s (a, a)) -- ^ Function that splits accumulator for each scheduled job. -> (ix -> a -> ST s a) -- ^ Action -> ST s a default iterRowMajorST :: Index (Lower ix) => Int -> Scheduler s a -> ix -> ix -> Sz ix -> a -> (a -> ST s (a, a)) -> (ix -> a -> ST s a) -> ST s a iterRowMajorST !fact scheduler ixStart ixStride sz initAcc splitAcc f = do let !(SafeSz n, szL@(SafeSz nL)) = unconsSz sz if n > 0 then do let !(!start, !ixL) = unconsDim ixStart !(!stride, !sL) = unconsDim ixStride if numWorkers scheduler > 1 && fact > 1 && n < numWorkers scheduler * fact then do let !newFact = 1 + (fact `quot` n) loopM start (< start + n * stride) (+ stride) initAcc $ \j acc -> iterRowMajorST newFact scheduler ixL sL szL acc splitAcc (f . consDim j) else splitWorkWithFactorST fact scheduler start stride n initAcc splitAcc $ \_ _ chunkStartAdj chunkStopAdj acc -> loopM chunkStartAdj (< chunkStopAdj) (+ stride) acc $ \j a -> iterM ixL nL sL (<) a (f . consDim j) else pure initAcc {-# INLINE iterRowMajorST #-} -- | Similar to `iterM`, but no restriction on a Monad. -- -- @since 1.0.2 iterF :: ix -> ix -> ix -> (Int -> Int -> Bool) -> f a -> (ix -> f a -> f a) -> f a default iterF :: (Index (Lower ix)) => ix -> ix -> ix -> (Int -> Int -> Bool) -> f a -> (ix -> f a -> f a) -> f a iterF !sIx !eIx !incIx cond initAct f = loopF s (`cond` e) (+ inc) initAct $ \ !i g -> iterF sIxL eIxL incIxL cond g (\ !ix -> f (consDim i ix)) where !(s, sIxL) = unconsDim sIx !(e, eIxL) = unconsDim eIx !(inc, incIxL) = unconsDim incIx {-# INLINE iterF #-} -- | A single step in iteration -- -- @since 0.1.0 stepNextMF :: ix -> ix -> ix -> (Int -> Int -> Bool) -> (Maybe ix -> f a) -> f a default stepNextMF :: (Index (Lower ix)) => ix -> ix -> ix -> (Int -> Int -> Bool) -> (Maybe ix -> f a) -> f a stepNextMF !sIx !eIx !incIx cond f = nextMaybeF s (`cond` e) (+ inc) $ \ !mni -> stepNextMF sIxL eIxL incIxL cond $ \mIxN -> f $! case mIxN of Just ixN -> Just $! consDim s ixN Nothing -> case mni of Just ni -> Just $! consDim ni (pureIndex 0) Nothing -> Nothing where !(s, sIxL) = unconsDim sIx !(e, eIxL) = unconsDim eIx !(inc, incIxL) = unconsDim incIx {-# INLINE stepNextMF #-} iterTargetRowMajorA_ :: Applicative f => Int -- ^ Target linear index accumulator -> Int -- ^ Target linear index start -> Sz ix -- ^ Target size -> ix -- ^ Source start index -> ix -- ^ Source stride -> (Ix1 -> ix -> f a) -- ^ Action that accepts a linear index of the target, -- multi-dimensional index of the source and accumulator -> f () default iterTargetRowMajorA_ :: (Applicative f, Index (Lower ix)) => Int -> Int -> Sz ix -> ix -> ix -> (Ix1 -> ix -> f a) -> f () iterTargetRowMajorA_ !iAcc !iStart szRes ixStart ixStride f = do let !(SafeSz nRes, !szL) = unconsSz szRes !(!start, !ixL) = unconsDim ixStart !(!stride, !sL) = unconsDim ixStride iloopA_ (iAcc * nRes) start (< start + nRes * stride) (+ stride) $ \k j -> iterTargetRowMajorA_ k iStart szL ixL sL $ \i jl -> f i (consDim j jl) {-# INLINE iterTargetRowMajorA_ #-} iterTargetRowMajorAccM :: Monad m => Int -- ^ Target linear index accumulator -> Int -- ^ Target linear index start -> Sz ix -- ^ Target size -> ix -- ^ Source start index -> ix -- ^ Source stride -> a -- ^ Accumulator -> (Ix1 -> ix -> a -> m a) -- ^ Action that accepts a linear index of the target, -- multi-dimensional index of the source and accumulator -> m a default iterTargetRowMajorAccM :: (Monad m, Index (Lower ix)) => Int -> Int -> Sz ix -> ix -> ix -> a -> (Ix1 -> ix -> a -> m a) -> m a iterTargetRowMajorAccM !iAcc !iStart szRes ixStart ixStride initAcc f = do let !(SafeSz nRes, !szL) = unconsSz szRes !(!start, !ixL) = unconsDim ixStart !(!stride, !sL) = unconsDim ixStride iloopM (iAcc * nRes) start (< start + nRes * stride) (+ stride) initAcc $ \k j acc -> iterTargetRowMajorAccM k iStart szL ixL sL acc $ \i jl -> f i (consDim j jl) {-# INLINE iterTargetRowMajorAccM #-} iterTargetRowMajorAccST :: Int -- ^ Linear index accumulator -> Int -- ^ Scheduler multiplying factor. Must be positive -> Scheduler s a -- ^ Scheduler to use -> Int -- ^ Target linear index start -> Sz ix -- ^ Target size -> ix -- ^ Source start index -> ix -- ^ Source stride -> a -- ^ Initial accumulator -> (a -> ST s (a, a)) -- ^ Function that splits accumulator for each scheduled job. -> (Ix1 -> ix -> a -> ST s a) -- ^ Action -> ST s a default iterTargetRowMajorAccST :: Index (Lower ix) => Int -> Int -> Scheduler s a -> Int -> Sz ix -> ix -> ix -> a -> (a -> ST s (a, a)) -> (Ix1 -> ix -> a -> ST s a) -> ST s a iterTargetRowMajorAccST !iAcc !fact scheduler iStart sz ixStart ixStride initAcc splitAcc f = do let !(SafeSz n, nL) = unconsSz sz if n > 0 then do let !(!start, !ixL) = unconsDim ixStart !(!stride, !sL) = unconsDim ixStride !iAccL = iAcc * n if numWorkers scheduler > 1 && fact > 1 && n < numWorkers scheduler * fact then do let newFact = 1 + (fact `quot` n) iloopM iAccL start (< start + n * stride) (+ stride) initAcc $ \k j acc -> do iterTargetRowMajorAccST k newFact scheduler iStart nL ixL sL acc splitAcc $ \i -> f i . consDim j else splitWorkWithFactorST fact scheduler start stride n initAcc splitAcc $ \chunkStart _ chunkStartAdj chunkStopAdj acc -> iloopM (iAccL + chunkStart) chunkStartAdj (< chunkStopAdj) (+ stride) acc $ \k j a -> iterTargetRowMajorAccM k iStart nL ixL sL a $ \i -> f i . consDim j else pure initAcc {-# INLINE iterTargetRowMajorAccST #-} iterTargetRowMajorAccST_ :: Int -- ^ Index accumulator -> Int -- ^ Scheduler multiplying factor. Must be positive -> Scheduler s () -- ^ Scheduler to use -> Int -- ^ Target linear start index -> Sz ix -- ^ Target size -> ix -- ^ Source start index -> ix -- ^ Source stride -> a -- ^ Initial accumulator -> (a -> ST s (a, a)) -- ^ Function that splits accumulator for each scheduled job. -> (Ix1 -> ix -> a -> ST s a) -- ^ Action -> ST s () default iterTargetRowMajorAccST_ :: Index (Lower ix) => Int -> Int -> Scheduler s () -> Int -> Sz ix -> ix -> ix -> a -> (a -> ST s (a, a)) -> (Ix1 -> ix -> a -> ST s a) -> ST s () iterTargetRowMajorAccST_ !iAcc !fact scheduler iStart sz ixStart ixStride initAcc splitAcc f = do let !(SafeSz n, szL) = unconsSz sz when (n > 0) $ do let !(!start, !ixL) = unconsDim ixStart !(!stride, !sL) = unconsDim ixStride !iAccL = iAcc * n if numWorkers scheduler > 1 && fact > 1 && n < numWorkers scheduler * fact then do let !newFact = 1 + (fact `quot` n) void $ iloopM iAccL start (< n * stride) (+ stride) initAcc $ \k j acc -> do (accCur, accNext) <- splitAcc acc scheduleWork_ scheduler $ iterTargetRowMajorAccST_ k newFact scheduler iStart szL ixL sL accCur splitAcc $ \i -> f i . consDim j pure accNext else void $ splitWorkWithFactorST fact scheduler start stride n initAcc splitAcc $ \chunkStart _ chunkStartAdj chunkStopAdj acc -> void $ iloopM (iAccL + chunkStart) chunkStartAdj (< chunkStopAdj) (+ stride) acc $ \k j a -> iterTargetRowMajorAccM k iStart szL ixL sL a $ \i -> f i . consDim j {-# INLINE iterTargetRowMajorAccST_ #-} -- | Zero-dimension, i.e. a scalar. Can't really be used directly as there is no instance of -- `Index` for it, and is included for completeness. data Ix0 = Ix0 deriving (Eq, Ord, Show) instance NFData Ix0 where rnf Ix0 = () -- | A type synonym for 1-dimensional index, i.e. `Int`. -- -- >>> 5 :: Ix1 -- 5 -- -- @since 0.1.0 type Ix1 = Int -- | This is a very handy pattern synonym to indicate that any arbitrary `Integral` literal is an -- `Int`, e.g. a 1-dimensional index: @(Ix1 5) == (5 :: Int)@ -- -- >>> Ix1 5 -- 5 -- >>> :t Ix1 5 -- Ix1 5 :: Ix1 -- -- @since 0.1.0 pattern Ix1 :: Int -> Ix1 pattern Ix1 i = i {-# COMPLETE Ix1 #-} type instance Lower Int = Ix0 instance Index Ix1 where type Dimensions Ix1 = 1 dimensions _ = 1 {-# INLINE [1] dimensions #-} totalElem = unSz {-# INLINE [1] totalElem #-} isSafeIndex (SafeSz k) !i = 0 <= i && i < k {-# INLINE [1] isSafeIndex #-} toLinearIndex _ = id {-# INLINE [1] toLinearIndex #-} toLinearIndexAcc !acc m i = acc * m + i {-# INLINE [1] toLinearIndexAcc #-} fromLinearIndex _ = id {-# INLINE [1] fromLinearIndex #-} fromLinearIndexAcc n k = k `quotRem` n {-# INLINE [1] fromLinearIndexAcc #-} repairIndex k@(SafeSz ksz) !i rBelow rOver | ksz <= 0 = throw $ IndexZeroException ksz | i < 0 = rBelow k i | i >= ksz = rOver k i | otherwise = i {-# INLINE [1] repairIndex #-} consDim i _ = i {-# INLINE [1] consDim #-} unconsDim i = (i, Ix0) {-# INLINE [1] unconsDim #-} snocDim _ i = i {-# INLINE [1] snocDim #-} unsnocDim i = (Ix0, i) {-# INLINE [1] unsnocDim #-} getDimM ix 1 = pure ix getDimM ix d = throwM $ IndexDimensionException ix d {-# INLINE [1] getDimM #-} setDimM _ 1 ix = pure ix setDimM ix d _ = throwM $ IndexDimensionException ix d {-# INLINE [1] setDimM #-} modifyDimM ix 1 f = pure (ix, f ix) modifyDimM ix d _ = throwM $ IndexDimensionException ix d {-# INLINE [1] modifyDimM #-} pullOutDimM ix 1 = pure (ix, Ix0) pullOutDimM ix d = throwM $ IndexDimensionException ix d {-# INLINE [1] pullOutDimM #-} insertDimM Ix0 1 i = pure i insertDimM ix d _ = throwM $ IndexDimensionException ix d {-# INLINE [1] insertDimM #-} pureIndex i = i {-# INLINE [1] pureIndex #-} liftIndex f = f {-# INLINE [1] liftIndex #-} liftIndex2 f = f {-# INLINE [1] liftIndex2 #-} foldlIndex f = f {-# INLINE [1] foldlIndex #-} iterM k0 k1 inc cond = loopM k0 (`cond` k1) (+ inc) {-# INLINE iterM #-} iterF k0 k1 inc cond = loopF k0 (`cond` k1) (+ inc) {-# INLINE iterF #-} stepNextMF k0 k1 inc cond = nextMaybeF k0 (`cond` k1) (+ inc) {-# INLINE stepNextMF #-} iterRowMajorST fact scheduler start step n = iterLinearAccST fact scheduler start step (unSz n) {-# INLINE iterRowMajorST #-} iterTargetRowMajorA_ iAcc iStart (SafeSz nRes) start stride = iloopA_ (iAcc * nRes + iStart) start (< start + nRes * stride) (+ stride) {-# INLINE iterTargetRowMajorA_ #-} iterTargetRowMajorAccM iAcc iStart (SafeSz nRes) start stride = iloopM (iAcc * nRes + iStart) start (< start + nRes * stride) (+ stride) {-# INLINE iterTargetRowMajorAccM #-} iterTargetRowMajorAccST iAcc fact scheduler iStart sz start stride initAcc splitAcc action = do let !n = unSz sz !iAccL = iStart + iAcc * n splitWorkWithFactorST fact scheduler start stride n initAcc splitAcc $ \chunkStart _ chunkStartAdj chunkStopAdj acc -> iloopM (iAccL + chunkStart) chunkStartAdj (< chunkStopAdj) (+ stride) acc action {-# INLINE iterTargetRowMajorAccST #-} iterTargetRowMajorAccST_ iAcc fact scheduler iStart sz start stride initAcc splitAcc action = do let !n = unSz sz !iAccL = iStart + iAcc * n void $ splitWorkWithFactorST fact scheduler start stride n initAcc splitAcc $ \chunkStart _ chunkStartAdj chunkStopAdj acc -> void $ iloopM (iAccL + chunkStart) chunkStartAdj (< chunkStopAdj) (+ stride) acc action {-# INLINE iterTargetRowMajorAccST_ #-} -- | Same as `iterM`, but don't bother with accumulator and return value. -- -- @since 0.1.0 iterM_ :: (Index ix, Monad m) => ix -> ix -> ix -> (Int -> Int -> Bool) -> (ix -> m a) -> m () iterM_ sIx eIx incIx cond f = iterM sIx eIx incIx cond () $ \ !ix !a -> f ix >> pure a {-# INLINE iterM_ #-} {-# DEPRECATED iterM_ "In favor of more lax `iterA_`" #-} -- | Same as `iterM`, Iterate over a region with specific step, but using -- `Applicative` instead of a `Monad` and don't bother with accumulator or return value. -- -- @since 1.0.2 iterA_ :: forall ix f a . (Index ix, Applicative f) => ix -- ^ Starting index -> ix -- ^ Ending index (not included) -> ix -- ^ Stepping index -> (Int -> Int -> Bool) -- ^ Continuation function. Loop will stop on `False` -> (ix -> f a) -- ^ Action applied to an index. Result is ignored. -> f () iterA_ sIx eIx incIx cond f = iterF sIx eIx incIx cond (pure ()) $ \ix go -> f ix *> go {-# INLINE iterA_ #-} -- | Exceptions that get thrown when there is a problem with an index, size or dimension. -- -- @since 0.3.0 data IndexException where -- | Index contains a zero value along one of the dimensions. IndexZeroException :: Index ix => !ix -> IndexException -- | Dimension is out of reach. IndexDimensionException :: (NFData ix, Eq ix, Show ix, Typeable ix) => !ix -> !Dim -> IndexException -- | Index is out of bounds. IndexOutOfBoundsException :: Index ix => !(Sz ix) -> !ix -> IndexException instance Show IndexException where show (IndexZeroException ix) = "IndexZeroException: " ++ showsPrec 1 ix "" show (IndexDimensionException ix dim) = "IndexDimensionException: " ++ showsPrec 1 dim " for " ++ showsPrec 1 ix "" show (IndexOutOfBoundsException sz ix) = "IndexOutOfBoundsException: " ++ showsPrec 1 ix " is not safe for " ++ showsPrec 1 sz "" showsPrec n exc = showsPrecWrapped n (show exc ++) instance Eq IndexException where e1 == e2 = case (e1, e2) of (IndexZeroException i1, IndexZeroException i2t) | Just i2 <- cast i2t -> i1 == i2 (IndexDimensionException i1 d1, IndexDimensionException i2t d2) | Just i2 <- cast i2t -> i1 == i2 && d1 == d2 (IndexOutOfBoundsException sz1 i1, IndexOutOfBoundsException sz2t i2t) | Just i2 <- cast i2t , Just sz2 <- cast sz2t -> sz1 == sz2 && i1 == i2 _ -> False instance NFData IndexException where rnf = \case IndexZeroException i -> rnf i IndexDimensionException i d -> i `deepseq` rnf d IndexOutOfBoundsException sz i -> sz `deepseq` rnf i instance Exception IndexException -- | Exception that indicates an issue with an array size. -- -- @since 0.3.0 data SizeException where -- | Two sizes are expected to be equal along some or all dimensions, but they are not. SizeMismatchException :: Index ix => !(Sz ix) -> !(Sz ix) -> SizeException -- | Total number of elements does not match between the two sizes. SizeElementsMismatchException :: (Index ix, Index ix') => !(Sz ix) -> !(Sz ix') -> SizeException -- | Described subregion is too big for the specified size. SizeSubregionException :: Index ix => !(Sz ix) -> !ix -> !(Sz ix) -> SizeException -- | An array with the size cannot contain any elements. SizeEmptyException :: Index ix => !(Sz ix) -> SizeException -- | Total number of elements is too large resulting in overflow. -- -- @since 0.6.0 SizeOverflowException :: Index ix => !(Sz ix) -> SizeException -- | At least one dimensions contain a negative value. -- -- @since 0.6.0 SizeNegativeException :: Index ix => !(Sz ix) -> SizeException instance Eq SizeException where e1 == e2 = case (e1, e2) of (SizeMismatchException sz1 sz1', SizeMismatchException sz2t sz2t') | Just sz2 <- cast sz2t , Just sz2' <- cast sz2t' -> sz1 == sz2 && sz1' == sz2' (SizeElementsMismatchException sz1 sz1', SizeElementsMismatchException sz2t sz2t') | Just sz2 <- cast sz2t , Just sz2' <- cast sz2t' -> sz1 == sz2 && sz1' == sz2' (SizeSubregionException sz1 i1 sz1', SizeSubregionException sz2t i2t sz2t') | Just sz2 <- cast sz2t , Just i2 <- cast i2t , Just sz2' <- cast sz2t' -> sz1 == sz2 && i1 == i2 && sz1' == sz2' (SizeEmptyException sz1, SizeEmptyException sz2t) | Just sz2 <- cast sz2t -> sz1 == sz2 (SizeOverflowException sz1, SizeOverflowException sz2t) | Just sz2 <- cast sz2t -> sz1 == sz2 (SizeNegativeException sz1, SizeNegativeException sz2t) | Just sz2 <- cast sz2t -> sz1 == sz2 _ -> False instance NFData SizeException where rnf = \case SizeMismatchException sz sz' -> sz `deepseq` rnf sz' SizeElementsMismatchException sz sz' -> sz `deepseq` rnf sz' SizeSubregionException sz i sz' -> sz `deepseq` i `deepseq` rnf sz' SizeEmptyException sz -> rnf sz SizeOverflowException sz -> rnf sz SizeNegativeException sz -> rnf sz instance Exception SizeException instance Show SizeException where show (SizeMismatchException sz sz') = "SizeMismatchException: (" ++ show sz ++ ") vs (" ++ show sz' ++ ")" show (SizeElementsMismatchException sz sz') = "SizeElementsMismatchException: (" ++ show sz ++ ") vs (" ++ show sz' ++ ")" show (SizeSubregionException sz' ix sz) = "SizeSubregionException: (" ++ show sz' ++ ") is to small for " ++ show ix ++ " (" ++ show sz ++ ")" show (SizeEmptyException sz) = "SizeEmptyException: (" ++ show sz ++ ") corresponds to an empty array" show (SizeOverflowException sz) = "SizeOverflowException: (" ++ show sz ++ ") is too big" show (SizeNegativeException sz) = "SizeNegativeException: (" ++ show sz ++ ") contains negative value" showsPrec n exc = showsPrecWrapped n (show exc ++) -- | Exception that can happen upon conversion of a ragged type array into the rectangular kind. Which -- means conversion from lists is susceptible to this exception. -- -- @since 0.3.0 data ShapeException = -- | Across a specific dimension there was not enough elements for the supplied size DimTooShortException !Dim !(Sz Ix1) !(Sz Ix1) | -- | Across a specific dimension there was too many elements for the supplied size DimTooLongException !Dim !(Sz Ix1) !(Sz Ix1) | -- | Expected an empty size, but the shape was not empty. ShapeNonEmpty deriving (Eq) instance Show ShapeException where showsPrec n = \case DimTooShortException d sz sz' -> showsShapeExc "DimTooShortException" d sz sz' DimTooLongException d sz sz' -> showsShapeExc "DimTooLongException" d sz sz' ShapeNonEmpty -> ("ShapeNonEmpty" ++) where showsShapeExc tyName d sz sz' = showsPrecWrapped n ( (tyName ++) . (" for " ++) . shows d . (": expected (" ++) . shows sz . ("), got (" ++) . shows sz' . (")" ++) ) instance Exception ShapeException showsPrecWrapped :: Int -> ShowS -> ShowS showsPrecWrapped n inner | n < 1 = inner | otherwise = ('(' :) . inner . (")" ++)