horde-ad-0.3.0.0: src/HordeAd/Core/CarriersConcrete.hs
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -Wno-orphans #-}
-- | Tensor operations implementation using the ox-arrays package.
-- These definitions, mostly class instances, are needed to make concrete
-- arrays a valid carrier for a tensor class algebra (instance) defined in
-- "HordeAd.Core.OpsConcrete".
module HordeAd.Core.CarriersConcrete
( -- * RepConcrete and its operations
RepConcrete, tftkG, eltDictRep, showDictRep
, replTargetRep, defTargetRep
-- * Concrete and its instances
, Concrete(..), ConcreteFun(..), rtoVector, stoVector, xtoVector
, DummyDualTarget(..)
) where
import Prelude
import Control.DeepSeq (NFData (..))
import Data.Default
import Data.Proxy (Proxy (Proxy))
import Data.Type.Equality ((:~:) (Refl))
import Data.Vector.Storable qualified as VS
import Data.Array.Nested qualified as Nested
import Data.Array.Nested.Lemmas
import Data.Array.Nested.Mixed qualified as Mixed
import Data.Array.Nested.Mixed.Shape
import Data.Array.Nested.Ranked qualified as Ranked
import Data.Array.Nested.Ranked.Shape
import Data.Array.Nested.Shaped qualified as Shaped
import Data.Array.Nested.Shaped.Shape
import Data.Array.Nested.Types (fromSNat')
import Data.Array.Strided.Orthotope (liftVEltwise1)
import HordeAd.Core.TensorKind
import HordeAd.Core.Types
-- * Orphan ox-arrays instances
instance (Nested.IntElt r, Nested.PrimElt r, Eq r, Num r)
=> IntegralH (Nested.Ranked n r) where
-- These can't be partial, because our conditionals are not lazy
-- and so the counterfactual branches, with zeros, may get executed
-- even though they are subsequently ignored.
quotH a b = Nested.rquotArray a (Ranked.liftRanked1 mmakeNonZero b)
remH a b = Nested.rremArray a (Ranked.liftRanked1 mmakeNonZero b)
instance (Nested.IntElt r, Nested.PrimElt r, Eq r, Num r)
=> IntegralH (Nested.Shaped sh r) where
quotH a b = Nested.squotArray a (Shaped.liftShaped1 mmakeNonZero b)
remH a b = Nested.sremArray a (Shaped.liftShaped1 mmakeNonZero b)
instance (Nested.IntElt r, Nested.PrimElt r, Eq r, Num r)
=> IntegralH (Nested.Mixed sh r) where
quotH a b = Nested.mquotArray a (mmakeNonZero b)
remH a b = Nested.mremArray a (mmakeNonZero b)
instance (NumScalar r, Ord (Nested.Ranked n r))
=> Real (Nested.Ranked n r) where
toRational = error "toRational is not defined for tensors"
instance (NumScalar r, Ord (Nested.Shaped sh r))
=> Real (Nested.Shaped sh r) where
toRational = error "toRational is not defined for tensors"
instance (NumScalar r, Ord (Nested.Mixed sh r))
=> Real (Nested.Mixed sh r) where
toRational = error "toRational is not defined for tensors"
instance (NumScalar r, Ord (Nested.Ranked n r), Nested.FloatElt r)
=> RealFrac (Nested.Ranked n r) where
properFraction = error "properFraction is not defined for tensors"
instance (NumScalar r, Ord (Nested.Shaped sh r), Nested.FloatElt r)
=> RealFrac (Nested.Shaped sh r) where
properFraction = error "properFraction is not defined for tensors"
instance (NumScalar r, Ord (Nested.Mixed sh r), Nested.FloatElt r)
=> RealFrac (Nested.Mixed sh r) where
properFraction = error "properFraction is not defined for tensors"
instance (Nested.PrimElt r, Nested.FloatElt r)
=> RealFloatH (Nested.Ranked n r) where
atan2H = Nested.ratan2Array
instance (Nested.PrimElt r, Nested.FloatElt r)
=> RealFloatH (Nested.Shaped sh r) where
atan2H = Nested.satan2Array
instance (Nested.PrimElt r, Nested.FloatElt r)
=> RealFloatH (Nested.Mixed sh r) where
atan2H = Nested.matan2Array
instance ( NumScalar r, Ord (Nested.Ranked n r), Nested.PrimElt r
, Nested.FloatElt r )
=> RealFloat (Nested.Ranked n r) where
atan2 = Nested.ratan2Array
floatRadix = error "operation not defined for tensors"
floatDigits = error "operation not defined for tensors"
floatRange = error "operation not defined for tensors"
decodeFloat = error "operation not defined for tensors"
encodeFloat = error "operation not defined for tensors"
isNaN = error "operation not defined for tensors"
isInfinite = error "operation not defined for tensors"
isDenormalized = error "operation not defined for tensors"
isNegativeZero = error "operation not defined for tensors"
isIEEE = error "operation not defined for tensors"
instance ( NumScalar r, Ord (Nested.Shaped sh r), Nested.PrimElt r
, Nested.FloatElt r )
=> RealFloat (Nested.Shaped sh r) where
atan2 = Nested.satan2Array
floatRadix = error "operation not defined for tensors"
floatDigits = error "operation not defined for tensors"
floatRange = error "operation not defined for tensors"
decodeFloat = error "operation not defined for tensors"
encodeFloat = error "operation not defined for tensors"
isNaN = error "operation not defined for tensors"
isInfinite = error "operation not defined for tensors"
isDenormalized = error "operation not defined for tensors"
isNegativeZero = error "operation not defined for tensors"
isIEEE = error "operation not defined for tensors"
instance ( NumScalar r, Ord (Nested.Mixed sh r), Nested.PrimElt r
, Nested.FloatElt r )
=> RealFloat (Nested.Mixed sh r) where
atan2 = Nested.matan2Array
floatRadix = error "operation not defined for tensors"
floatDigits = error "operation not defined for tensors"
floatRange = error "operation not defined for tensors"
decodeFloat = error "operation not defined for tensors"
encodeFloat = error "operation not defined for tensors"
isNaN = error "operation not defined for tensors"
isInfinite = error "operation not defined for tensors"
isDenormalized = error "operation not defined for tensors"
isNegativeZero = error "operation not defined for tensors"
isIEEE = error "operation not defined for tensors"
-- TODO: make more efficient somehow?
mmakeNonZero :: (Nested.PrimElt r, Eq r, Num r)
=> Nested.Mixed sh r -> Nested.Mixed sh r
mmakeNonZero =
Mixed.mliftNumElt1
(`liftVEltwise1` VS.map (\x -> if x == 0 then 1 else x))
-- * RepConcrete and its operations
-- | The type family that represents tensor kinds in concrete arrays.
type family RepConcrete (y :: TK) where
RepConcrete (TKScalar r) = r
RepConcrete (TKR2 n x) = Nested.Ranked n (RepConcrete x)
RepConcrete (TKS2 sh x) = Nested.Shaped sh (RepConcrete x)
RepConcrete (TKX2 sh x) = Nested.Mixed sh (RepConcrete x)
RepConcrete (TKProduct x z) = (RepConcrete x, RepConcrete z)
-- TODO: check if we can get errors due to the made-up shapes
-- and if the errors are early and clear. Maybe for nested fully shaped tensors
-- no errors are possible? Statically known dimensions are preserved fine
-- also in mixed arrays.
-- | Compute the full shape tensor kind for a concrete array.
-- If the array is empty and not shaped, shapes can be made up
-- (defaulting to zero dimensions).
tftkG :: SingletonTK y -> RepConcrete y -> FullShapeTK y
tftkG stk t =
let repackShapeTree :: SingletonTK y
-> Mixed.ShapeTree (RepConcrete y)
-> FullShapeTK y
{-# NOINLINE repackShapeTree #-}
repackShapeTree stk0 tree = case stk0 of
STKScalar -> FTKScalar
STKR _ stk1 -> let (sh, rest) = tree
in FTKR sh $ if shrSize sh == 0 -- rest crashes
then zeroShapes stk1
else repackShapeTree stk1 rest
STKS _ stk1 -> let (sh, rest) = tree
in FTKS sh $ if shsSize sh == 0 -- rest crashes
then zeroShapes stk1
else repackShapeTree stk1 rest
STKX _ stk1 -> let (sh, rest) = tree
in FTKX sh $ if shxSize sh == 0 -- rest crashes
then zeroShapes stk1
else repackShapeTree stk1 rest
STKProduct stk1 stk2 ->
let (tree1, tree2) = tree
in FTKProduct (repackShapeTree stk1 tree1)
(repackShapeTree stk2 tree2)
zeroShapes :: SingletonTK y -> FullShapeTK y
{-# NOINLINE zeroShapes #-}
zeroShapes stk0 = case stk0 of
STKScalar -> FTKScalar
STKR n stk1 -> FTKR (shrFromList n (replicate (fromSNat' n) 0))
(zeroShapes stk1)
STKS sh stk1 -> FTKS sh $ zeroShapes stk1 -- not made up in this case
STKX ssx stk1 -> FTKX (shxCompleteZeros ssx) $ zeroShapes stk1
-- statically known shapes not made up
STKProduct stk1 stk2 -> FTKProduct (zeroShapes stk1) (zeroShapes stk2)
in case stk of -- this starts with non-recursive shorthands
STKScalar -> FTKScalar
STKR _ STKScalar -> FTKR (Nested.rshape t) FTKScalar
STKS sh STKScalar -> FTKS sh FTKScalar
STKX _ STKScalar -> FTKX (Nested.mshape t) FTKScalar
_ | Dict <- eltDictRec stk -> repackShapeTree stk (Mixed.mshapeTree t)
eltDictRep :: SingletonTK y -> Dict Nested.KnownElt (RepConcrete y)
eltDictRep STKScalar = Dict -- the prevalent case
eltDictRep stk = eltDictRec stk
eltDictRec :: SingletonTK y -> Dict Nested.KnownElt (RepConcrete y)
{-# NOINLINE eltDictRec #-}
eltDictRec = \case
STKScalar -> Dict
STKR SNat x | Dict <- eltDictRec x -> Dict
STKS sh x | Dict <- eltDictRec x -> withKnownShS sh Dict
STKX sh x | Dict <- eltDictRec x -> withKnownShX sh Dict
STKProduct stk1 stk2 | Dict <- eltDictRec stk1
, Dict <- eltDictRec stk2 -> Dict
showDictRep :: SingletonTK y -> Dict Show (RepConcrete y)
showDictRep = \case
STKScalar -> Dict
STKR _ x | Dict <- showDictRep x
, Dict <- eltDictRec x -> Dict
STKS _ x | Dict <- showDictRep x
, Dict <- eltDictRec x -> Dict
STKX _ x | Dict <- showDictRep x
, Dict <- eltDictRec x -> Dict
STKProduct stk1 stk2 | Dict <- showDictRep stk1
, Dict <- showDictRep stk2 -> Dict
nfdataDictRep :: SingletonTK y -> Dict NFData (RepConcrete y)
nfdataDictRep = \case
STKScalar -> Dict
STKR _ x | Dict <- nfdataDictRep x
, Dict <- eltDictRec x -> Dict
STKS _ x | Dict <- nfdataDictRep x
, Dict <- eltDictRec x -> Dict
STKX _ x | Dict <- nfdataDictRep x
, Dict <- eltDictRec x -> Dict
STKProduct stk1 stk2 | Dict <- nfdataDictRep stk1
, Dict <- nfdataDictRep stk2 -> Dict
replTargetRep :: TKAllNum y
=> (forall r. NumScalar r => r) -> FullShapeTK y -> RepConcrete y
replTargetRep r = \case
FTKScalar @r | Dict0 <- numFromTKAllNum (Proxy @r) -> r
FTKR sh (FTKScalar @r) | Dict0 <- numFromTKAllNum (Proxy @r) ->
Nested.rreplicatePrim sh r
FTKS @sh sh (FTKScalar @r) | Dict0 <- numFromTKAllNum (Proxy @r)
, Refl <- lemAppNil @sh ->
Nested.sreplicatePrim sh r
FTKX @sh sh (FTKScalar @r) | Dict0 <- numFromTKAllNum (Proxy @r)
, Refl <- lemAppNil @sh ->
Nested.mreplicatePrim sh r
FTKR sh x | Dict <- eltDictRec (ftkToSTK x) ->
Nested.rreplicate sh $ Nested.rscalar $ replTargetRep r x
FTKS @sh sh x | Dict <- eltDictRec (ftkToSTK x)
, Refl <- lemAppNil @sh ->
Nested.sreplicate sh $ Nested.sscalar $ replTargetRep r x
FTKX @sh sh x | Dict <- eltDictRec (ftkToSTK x)
, Refl <- lemAppNil @sh ->
Nested.mreplicate sh $ Nested.mscalar $ replTargetRep r x
FTKProduct ftk1 ftk2 -> (replTargetRep r ftk1, replTargetRep r ftk2)
defTargetRep :: FullShapeTK y -> RepConcrete y
defTargetRep = \case
FTKScalar -> def
FTKR sh FTKScalar ->
Nested.rreplicatePrim sh def
FTKS @sh sh FTKScalar | Refl <- lemAppNil @sh ->
Nested.sreplicatePrim sh def
FTKX @sh sh FTKScalar | Refl <- lemAppNil @sh ->
Nested.mreplicatePrim sh def
FTKR sh x | Dict <- eltDictRec (ftkToSTK x) ->
Nested.rreplicate sh $ Nested.rscalar $ defTargetRep x
FTKS @sh sh x | Dict <- eltDictRec (ftkToSTK x)
, Refl <- lemAppNil @sh ->
Nested.sreplicate sh $ Nested.sscalar $ defTargetRep x
FTKX @sh sh x | Dict <- eltDictRec (ftkToSTK x)
, Refl <- lemAppNil @sh ->
Nested.mreplicate sh $ Nested.mscalar $ defTargetRep x
FTKProduct ftk1 ftk2 -> (defTargetRep ftk1, defTargetRep ftk2)
-- * Concrete and its instances
-- | A newtype wrapper over 'RepConcrete'.
-- It's needed because @RepConcrete@ can't be partially applied.
-- This type also lets us work around the woes with defining 'Show'
-- for the @RepConcrete@ type family. It gives us a concrete thing
-- to attach a @Show@ instance to.
type role Concrete nominal
newtype Concrete y = Concrete {unConcrete :: RepConcrete y}
instance KnownSTK y => Show (Concrete y) where
showsPrec d (Concrete t) | Dict <- showDictRep (knownSTK @y) = showsPrec d t
instance KnownSTK y => NFData (Concrete y) where
rnf (Concrete t) | Dict <- nfdataDictRep (knownSTK @y) = rnf t
type role ConcreteFun nominal nominal
newtype ConcreteFun x z = ConcreteFun (RepConcrete x -> RepConcrete z)
type instance HFunOf Concrete = ConcreteFun
type instance PrimalOf Concrete = Concrete
type instance DualOf Concrete = DummyDualTarget
type instance PlainOf Concrete = Concrete
type instance ShareOf Concrete = Concrete
instance GoodScalar r => EqH Concrete (TKScalar r) where
Concrete u ==. Concrete v = Concrete $ u == v
instance GoodScalar r => OrdH Concrete (TKScalar r) where
Concrete u <=. Concrete v = Concrete $ u <= v
instance GoodScalar r => EqH Concrete (TKR n r) where
Concrete u ==. Concrete v =
Concrete $ Ranked.rtoPrimitive u == Ranked.rtoPrimitive v
instance GoodScalar r => OrdH Concrete (TKR n r) where
Concrete u <=. Concrete v =
Concrete $ Ranked.rtoPrimitive u <= Ranked.rtoPrimitive v
instance GoodScalar r => EqH Concrete (TKS sh r) where
Concrete u ==. Concrete v =
Concrete $ Shaped.stoPrimitive u == Shaped.stoPrimitive v
instance GoodScalar r => OrdH Concrete (TKS sh r) where
Concrete u <=. Concrete v =
Concrete $ Shaped.stoPrimitive u <= Shaped.stoPrimitive v
instance GoodScalar r => EqH Concrete (TKX sh r) where
Concrete u ==. Concrete v =
Concrete $ Mixed.toPrimitive u == Mixed.toPrimitive v
instance GoodScalar r => OrdH Concrete (TKX sh r) where
Concrete u <=. Concrete v =
Concrete $ Mixed.toPrimitive u <= Mixed.toPrimitive v
deriving instance Boolean (Concrete (TKScalar Bool))
deriving instance Eq (RepConcrete y) => Eq (Concrete y)
deriving instance Ord (RepConcrete y) => Ord (Concrete y)
deriving instance Num (RepConcrete y) => Num (Concrete y)
deriving instance IntegralH (RepConcrete y) => IntegralH (Concrete y)
deriving instance Real (RepConcrete y) => Real (Concrete y)
deriving instance Fractional (RepConcrete y) => Fractional (Concrete y)
deriving instance Floating (RepConcrete y) => Floating (Concrete y)
deriving instance RealFrac (RepConcrete y) => RealFrac (Concrete y)
deriving instance RealFloatH (RepConcrete y) => RealFloatH (Concrete y)
deriving instance RealFloat (RepConcrete y) => RealFloat (Concrete y)
rtoVector :: GoodScalar r => Concrete (TKR n r) -> VS.Vector r
rtoVector = Nested.rtoVector . unConcrete
stoVector :: GoodScalar r => Concrete (TKS sh r) -> VS.Vector r
stoVector = Nested.stoVector . unConcrete
xtoVector :: GoodScalar r => Concrete (TKX sh r) -> VS.Vector r
xtoVector = Nested.mtoVector . unConcrete
type role DummyDualTarget nominal
type DummyDualTarget :: Target
newtype DummyDualTarget y = DummyDualTarget (FullShapeTK y)
deriving (Eq, Show)