horde-ad-0.3.0.0: src/HordeAd/Core/AstInterpret.hs
{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
{-# OPTIONS_GHC -fplugin GHC.TypeLits.Normalise #-}
-- | Interpretation of AST terms in an arbitrary tensor operations
-- class instance. With the exception of the the interpretation
-- of the sharing mechanisms and any other performance tweaks,
-- the interpretation is the unique homorphism determined by the instance.
-- The sharing mechanisms are translated so as to preserve sharing in case
-- the instance is a term algebra as well.
module HordeAd.Core.AstInterpret
( interpretAstFull, interpretAstPrimal, interpretAstDual, interpretAstPlain
) where
import Prelude
import Data.Dependent.EnumMap.Strict qualified as DMap
import Data.List.NonEmpty (NonEmpty (..))
import Data.List.NonEmpty qualified as NonEmpty
import Data.Proxy (Proxy (Proxy))
import Data.Type.Equality ((:~:) (Refl))
import Data.Vector.Generic qualified as V
import Data.Array.Nested.Lemmas
import Data.Array.Nested.Mixed.Shape
import Data.Array.Nested.Shaped.Shape
import HordeAd.Core.Ast
import HordeAd.Core.AstEnv
import HordeAd.Core.AstTools
import HordeAd.Core.Conversion
import HordeAd.Core.ConvertTensor
import HordeAd.Core.Ops
import HordeAd.Core.TensorKind
import HordeAd.Core.Types
#ifdef WITH_EXPENSIVE_ASSERTIONS
import Control.Exception.Assert.Sugar
#endif
interpretAstFull
:: forall target y. ADReady target
=> AstEnv target -> AstTensor AstMethodLet FullSpan y
-> target y
{-# INLINE interpretAstFull #-}
interpretAstFull = interpretAst
interpretAstPrimal
:: forall target y. ADReady target
=> AstEnv target -> AstTensor AstMethodLet PrimalSpan y
-> PrimalOf target y
{-# INLINE interpretAstPrimal #-}
interpretAstPrimal = interpretAst
interpretAstDual
:: forall target y. ADReady target
=> AstEnv target -> AstTensor AstMethodLet DualSpan y
-> DualOf target y
{-# INLINE interpretAstDual #-}
interpretAstDual env a = tdualPart (ftkToSTK (ftkAst a)) $ interpretAst env a
interpretAstPlain
:: forall target y. ADReady target
=> AstEnv target -> AstTensor AstMethodLet PlainSpan y
-> PlainOf target y
{-# INLINE interpretAstPlain #-}
interpretAstPlain = interpretAst
-- | Interpret a term in an environment.
interpretAst
:: forall target s y. (ADReady target, KnownSpan s)
=> AstEnv target -> AstTensor AstMethodLet s y
-> SpanTargetFam target s y
{-# INLINEABLE interpretAst #-}
interpretAst !env | Refl <- lemPlainOfSpan (Proxy @target) (knownSpan @s)
, Dict0 <- dictSpanFam (Proxy @target) (knownSpan @s) = \case
AstPair t1 t2 -> tpair (interpretAst env t1) (interpretAst env t2)
AstProject1 t -> tproject1 (interpretAst env t)
AstProject2 t -> tproject2 (interpretAst env t)
AstMapAccumLDer k bftk eftk f0 df0 rf0 acc0 es ->
let f = interpretAstHFun env f0
df = interpretAstHFun env df0
rf = interpretAstHFun env rf0
acc02 = interpretAst env acc0
es2 = interpretAst env es
in tmapAccumLDer (Proxy @(SpanTargetFam target s))
k (ftkAst acc0) bftk eftk f df rf acc02 es2
AstApply f t ->
let f2 = interpretAstHFun env f
t2 = interpretAst env t
in tapply f2 t2
AstVar var ->
case DMap.lookup var env of
Just (SpanTarget t) ->
#ifdef WITH_EXPENSIVE_ASSERTIONS
withKnownSTK (ftkToSTK $ varNameToFTK var) $
-- We can't assert anything about bounds, because values can be
-- symbolic and so not directly comparable to bounds.
assert (tftk (ftkToSTK $ varNameToFTK var) t == varNameToFTK var
`blame` ( tftk (ftkToSTK $ varNameToFTK var) t
, varNameToFTK var, var, t ))
#endif
t
_ -> error $ "interpretAst: unknown AstVar " ++ show var
-- ++ " in environment " ++ showsPrecAstEnv 0 env ""
-- TODO: recognize nested builds and, for Concrete, call tbuild instead;
-- also recognize map and zipWith in nested builds and call these
AstBuild1 snat stk (var, v) ->
let f i = interpretAst (extendEnvI var i env) v
in tbuild1 snat stk f
-- We assume there are no nested lets with the same variable.
AstLet @_ @_ @_ @s2 var u v ->
let (toFull, fromFull) =
toFromFullSpan @target (ftkToSTK (ftkAst v)) (knownSpan @s2)
t = withKnownSpan (varNameToSpan var) $ interpretAst env u
env2 w = extendEnv var w env
in case varNameToSpan var of
SFullSpan ->
fromFull $ ttlet t (\w -> toFull $ interpretAst (env2 w) v)
SPrimalStepSpan SFullSpan ->
fromFull $ ttletPrimal t (\w -> toFull $ interpretAst (env2 w) v)
SPrimalStepSpan _ ->
error "interpretAst: can't store a nested primal value"
-- actually, we could, by substituting into v0, etc.
SDualSpan ->
fromFull $ ttlet t (\w -> toFull $ interpretAst (env2 w) v)
-- due to the dual hack
SPlainSpan | SPlainSpan <- knownSpan @s2 -> -- a speedup
ttlet t (\w -> interpretAst (env2 w) v)
SPlainSpan ->
fromFull $ ttletPlain t (\w -> toFull $ interpretAst (env2 w) v)
AstPrimalPart @_ @s2 a -> case knownSpan @s2 of
SFullSpan -> tprimalPart $ interpretAst env a
SPrimalStepSpan SFullSpan -> tprimalPart $ interpretAst env a
SPrimalStepSpan _ ->
error "interpretAst: can't convert a nested primal value" -- (... easily)
SDualSpan -> tdefTarget (ftkAst a) -- primal zero
SPlainSpan -> tprimalPart $ interpretAst env a
AstDualPart a ->
-- We zero the primal part, but keep it a dual number, not its second
-- component, that is a Delta expression (in non-symbolic instances).
tfromDual $ tdualPart (ftkToSTK (ftkAst a)) $ interpretAst env a
AstPlainPart @_ @s2 a -> case knownSpan @s2 of
SFullSpan -> tplainPart $ interpretAst env a
SPrimalStepSpan SFullSpan -> tplainPart $ interpretAst env a
SPrimalStepSpan _ ->
error "interpretAst: can't convert a nested primal value" -- (... easily)
SDualSpan -> tdefTarget (ftkAst a) -- plain zero
SPlainSpan -> interpretAst env a
AstFromPrimal a -> tfromPrimal (ftkToSTK (ftkAst a)) $ interpretAst env a
AstFromDual a ->
-- Not @tfromDual $ interpretAst env a@, because dual parts are represented
-- as dual numbers with zero primal parts, so nothing needs to be done here,
-- because the part is already zeroed by inductive assumption, so this
-- also works as a representation of a dual number with zero primal part,
-- which is the semantics of `AstFromDual`.
interpretAst env a
AstFromPlain a -> tfromPlain (ftkToSTK (ftkAst a)) $ interpretAst env a
AstPlusK u v -> interpretAst env u + interpretAst env v
AstTimesK u v -> interpretAst env u * interpretAst env v
AstN1K opCode u ->
let u2 = interpretAst env u
in interpretAstN1 opCode u2
AstR1K opCode u ->
let u2 = interpretAst env u
in interpretAstR1 opCode u2
AstR2K opCode u v ->
let u2 = interpretAst env u
v2 = interpretAst env v
in interpretAstR2 opCode u2 v2
AstI2K opCode u v ->
let u2 = interpretAst env u
v2 = interpretAst env v
in interpretAstI2 opCode u2 v2
AstConcreteK k -> tkconcrete k
AstFloorK v -> tkfloor $ interpretAst env v
AstFromIntegralK v -> tkfromIntegral $ interpretAst env v
AstCastK v -> tkcast $ interpretAst env v
AstArgMinK v -> tkargMin $ interpretAst env v
AstArgMaxK v -> tkargMax $ interpretAst env v
AstIndexK v ix ->
let v2 = interpretAst env v
ix3 = interpretAst env <$> ix
in tsindex0 v2 ix3
AstPlusS u v -> interpretAst env u + interpretAst env v
AstTimesS u v -> interpretAst env u * interpretAst env v
AstN1S opCode u -> interpretAstN1 opCode (interpretAst env u)
AstR1S opCode u -> interpretAstR1 opCode (interpretAst env u)
AstR2S opCode u v ->
interpretAstR2 opCode (interpretAst env u) (interpretAst env v)
AstI2S opCode u v ->
interpretAstI2 opCode (interpretAst env u) (interpretAst env v)
AstConcreteS a -> tsconcrete a
AstFloorS v -> tsfloor $ interpretAst env v
AstFromIntegralS v -> tsfromIntegral $ interpretAst env v
AstCastS v -> tscast $ interpretAst env v
AstArgMinS v -> tsargMin $ interpretAst env v
AstArgMaxS v -> tsargMax $ interpretAst env v
AstIndexS @shm shn v ix ->
withKnownShS shn $
withKnownSTK (stkAstX v) $
let v2 = interpretAst env v
ix3 = interpretAst env <$> ix
in tsindex @_ @shm v2 ix3
AstCondK b a1 a2 ->
let c = interpretAst env b
in kcond c (interpretAst env a1) (interpretAst env a2)
AstCondS b a1 a2 | FTKS sh x <- ftkAst a1 ->
withKnownShS sh $
withKnownSTK (ftkToSTK x) $
let c = interpretAst env b
in scond c (interpretAst env a1) (interpretAst env a2)
AstFromVectorK shm l ->
let l2 = V.map (interpretAst env) l
in tsfromVectorLinear shm l2
AstFromVectorS shm l -> case V.uncons l of
Just (v, _) | FTKS shn x <- ftkAst v ->
withKnownShS shn $
withKnownSTK (ftkToSTK x) $
let l2 = V.map (interpretAst env) l
in tsfromVectorN shm l2
Nothing -> error "interpretAst: empty vector in AstFromVectorS"
AstSumK v -> case ftkAst v of
FTKS shm _ ->
withKnownShS shm $
tssum0 (interpretAst env v)
t@(AstSumS @shm shm v) -> case ftkAst t of
FTKS shn x ->
withKnownShS shm $
withKnownShS shn $
withKnownSTK (ftkToSTK x) $
tssumN @_ @shm (interpretAst env v)
-- TODO: this breaks specialization:
AstScatterS _ shn shp v (ZS, ix) ->
withKnownShS shn $
withKnownShS shp $
withKnownSTK (stkAstX v) $
tsoneHot (interpretAst env v) (interpretAst env <$> ix)
AstScatterS @_ @_ @shp (SNat :$$ _) shn shp v (var ::$ ZS, ix) ->
withKnownShS shn $
withKnownShS shp $
withKnownSTK (stkAstX v) $
let t1 = interpretAst env v
f2 :: IntOf target -> IxSOf target shp
f2 !i2 = interpretAst (extendEnvI var i2 env) <$> ix
in tsscatter1 t1 f2
AstScatterS @shm @shn @shp shm shn shp v (vars, ix) ->
withKnownShS shm $
withKnownShS shn $
withKnownShS shp $
withKnownSTK (stkAstX v) $
let t1 = interpretAst env v
f2 :: IxSOf target shm -> IxSOf target shp
f2 !ix2 = interpretAst (extendEnvVarsS vars ix2 env) <$> ix
in tsscatter @_ @_ @shn t1 f2
AstReplicateK shm v ->
tsreplicate0N shm (interpretAst env v)
AstReplicateS shm v -> case ftkAst v of
FTKS shn x ->
withKnownShS shn $
withKnownSTK (ftkToSTK x) $
tsreplicateN shm (interpretAst env v)
AstGatherS _ shn _ v (ZS, ix) -> interpretAst env (AstIndexS shn v ix)
AstGatherS @_ @_ @shp (SNat :$$ _) shn shp v (var ::$ ZS, ix) ->
withKnownShS shn $
withKnownShS shp $
withKnownSTK (stkAstX v) $
let t1 = interpretAst env v
f2 :: IntOf target -> IxSOf target shp
f2 !i2 = interpretAst (extendEnvI var i2 env) <$> ix
in tsgather1 t1 f2
AstGatherS @shm @shn @shp shm shn shp v (vars, ix) ->
withKnownShS shm $
withKnownShS shn $
withKnownShS shp $
withKnownSTK (stkAstX v) $
let t1 = interpretAst env v
f2 :: IxSOf target shm -> IxSOf target shp
f2 !ix2 = interpretAst (extendEnvVarsS vars ix2 env) <$> ix
in tsgather @_ @_ @shn t1 f2
AstIotaS SNat -> tsiota
t@(AstAppendS @_ @_ @shRest @x a b) -> case ftkAst t of
FTKS (SNat :$$ shRest) x ->
withKnownShS shRest $
withKnownSTK (ftkToSTK x) $
let flattenAppend
:: AstTensor AstMethodLet s (TKS2 (m ': shRest) x)
-> NonEmpty (SpanTargetFam target s (TKR2 (Rank (m ': shRest)) x))
flattenAppend (AstAppendS c d) = flattenAppend c <> flattenAppend d
flattenAppend u | (FTKS (SNat :$$ _) _) <- ftkAst u =
NonEmpty.singleton (rfromS $ interpretAst env u)
in case flattenAppend t of
-- At least 3 elements and a concrete instance:
ne@(_ :| _ : _ : _) | isConcreteInstance @target ->
sfromR $ trconcat ne
_ ->
let t1 = interpretAst env a
t2 = interpretAst env b
in tsappend t1 t2
AstSliceS i n k v ->
withKnownSTK (stkAstX v) $
tsslice i n k $ interpretAst env v
AstReverseS v ->
withKnownSTK (stkAstX v) $
tsreverse (interpretAst env v)
AstTransposeS perm v ->
withKnownSTK (stkAstX v) $
tstranspose perm $ interpretAst env v
AstReshapeS sh2 v ->
withKnownSTK (stkAstX v) $
tsreshape sh2 (interpretAst env v)
AstConvert c a ->
tconvert c (ftkToSTK (ftkAst a)) (interpretAst env a)
AstDot0 u v -> case ftkAst u of
FTKS sh _ ->
withKnownShS sh $
tsdot0 (interpretAst env u) (interpretAst env v)
AstDot1InS @sh @n sh SNat u v ->
withKnownShS sh $
tsdot1In @_ @sh (SNat @n) (interpretAst env u) (interpretAst env v)
AstMatmul2S SNat SNat SNat u v ->
tsmatmul2 (interpretAst env u) (interpretAst env v)
AstBoolNotK arg ->
tfromPlain STKScalar $ notB $ interpretAst env arg
AstBoolNotS arg | FTKS sh _ <- ftkAst arg ->
withKnownShS sh $
tfromPlain (ftkToSTK $ ftkAst arg)
$ tsmap0N notB $ interpretAst env arg
AstBoolAndK arg1 arg2 ->
let b1 = interpretAst env arg1
b2 = interpretAst env arg2
in tfromPlain STKScalar $ b1 &&* b2
AstBoolAndS arg1 arg2 | FTKS sh _ <- ftkAst arg1 ->
withKnownShS sh $
let b1 = interpretAst env arg1
b2 = interpretAst env arg2
in tfromPlain (ftkToSTK $ ftkAst arg1)
$ tszipWith0N (&&*) b1 b2
AstLeqK arg1 arg2 ->
let r1 = interpretAst env arg1
r2 = interpretAst env arg2
in tfromPlain STKScalar $ r1 <=. r2
AstLeq arg1 arg2 ->
let r1 = interpretAst env arg1
r2 = interpretAst env arg2
in tfromPlain STKScalar $ r1 <=. r2
AstLeqS @shb @sh shb sh arg1 arg2 | Refl <- lemAppNil @shb ->
withKnownShS shb $
withKnownShS sh $
let r1 = interpretAst env arg1
r2 = interpretAst env arg2
in tfromPlain (STKS shb STKScalar)
$ sunNest
$ szipWithNested
(\a1 a2 ->
let c = convCmp ConvXS
(convCmp (Conv0X (STKS ZSS STKScalar))
(convCmp ConvXS (Conv0X STKScalar)))
in tconvert c STKScalar $ sunNest a1 <=. sunNest a2)
(snest @_ @_ @sh shb r1) (snest shb r2)
interpretAstHFun
:: forall target x y s. (KnownSpan s, BaseTensor (SpanTargetFam target s))
=> AstEnv target -> AstHFun s x y
-> HFunOf (SpanTargetFam target s) x y
{-# INLINE interpretAstHFun #-}
interpretAstHFun _env (AstLambda var t) =
tlambda @(SpanTargetFam target s) (varNameToFTK var)
$ HFun $ \ (ws :: f x) ->
toFullSpan @f (ftkToSTK (ftkAst t)) (knownSpan @s)
$ interpretAst @f
(extendEnv var (fromFullSpan (knownSpan @s) ws) emptyEnv) t
-- Interpretation in empty environment makes sense here, because
-- there are no free variables except for the one declared.
-- This version accepts nested arrays, because they are needed here.
szipWithNested :: ( KnownShS sh, KnownSTK x, KnownSTK x1, KnownSTK x2
, BaseTensor target )
=> (target (TKS2 '[] x1) -> target (TKS2 '[] x2)
-> target (TKS2 '[] x))
-> target (TKS2 sh x1) -> target (TKS2 sh x2)
-> target (TKS2 sh x)
{-# INLINE szipWithNested #-}
szipWithNested @sh f u v | Refl <- lemAppNil @sh =
tsbuild @_ @sh (\ix -> f (tsindex u ix) (tsindex v ix))
-- * Interpretation of arithmetic, boolean and relation operations
interpretAstN1 :: Num a
=> OpCodeNum1 -> a -> a
{-# INLINE interpretAstN1 #-}
interpretAstN1 NegateOp u = negate u
interpretAstN1 AbsOp u = abs u
interpretAstN1 SignumOp u = signum u
interpretAstR1 :: Floating a
=> OpCode1 -> a -> a
{-# INLINE interpretAstR1 #-}
interpretAstR1 RecipOp u = recip u
interpretAstR1 ExpOp u = exp u
interpretAstR1 LogOp u = log u
interpretAstR1 SqrtOp u = sqrt u
interpretAstR1 SinOp u = sin u
interpretAstR1 CosOp u = cos u
interpretAstR1 TanOp u = tan u
interpretAstR1 AsinOp u = asin u
interpretAstR1 AcosOp u = acos u
interpretAstR1 AtanOp u = atan u
interpretAstR1 SinhOp u = sinh u
interpretAstR1 CoshOp u = cosh u
interpretAstR1 TanhOp u = tanh u
interpretAstR1 AsinhOp u = asinh u
interpretAstR1 AcoshOp u = acosh u
interpretAstR1 AtanhOp u = atanh u
interpretAstR2 :: RealFloatH a
=> OpCode2 -> a -> a -> a
{-# INLINE interpretAstR2 #-}
interpretAstR2 DivideOp u v = u / v
interpretAstR2 PowerOp u v = u ** v
interpretAstR2 LogBaseOp u v = logBase u v
interpretAstR2 Atan2Op u v = atan2H u v
interpretAstI2 :: IntegralH a
=> OpCodeIntegral2 -> a -> a -> a
{-# INLINE interpretAstI2 #-}
interpretAstI2 QuotOp u v = quotH u v
interpretAstI2 RemOp u v = remH u v