packages feed

horde-ad-0.3.0.0: src/HordeAd/Core/Ast.hs

{-# LANGUAGE ViewPatterns #-}
-- | AST of corresponding to the horde-ad operations specified
-- in the 'HordeAd.Core.Ops.BaseTensor' class and others.
-- The AST is essential for efficient handling of second order operations
-- such as build and map via BOT (bulk-operation transformation),
-- and fold and mapAccum via symbolic nested derivatives.
-- It also permits producing reusable reverse derivative terms,
-- which can be simplified, fused, inlined once and then
-- interpreted many times.
--
-- Note that @Ast*@ modules rarely depend on @Ops*@ and @Carriers*@ modules
-- (except for "HordeAd.Core.AstInterpret" and "HordeAd.Core.AstEnv"
-- that describe how to go from @Ast*@ to @Ops*@). Similarly, @Ops*@
-- and @Carriers*@ modules rarely depend on @Ast*@ modules
-- (except for "HordeAd.Core.OpsAst" and "HordeAd.Core.CarriersAst"
-- that describe how to define @Ops*@ in terms of @Ast*@).
-- Syntax is relatively separated from semantics and they meet
-- in the interpreter ("HordeAd.Core.AstInterpret")
-- and in the semantic model constructed from syntax ("HordeAd.Core.OpsAst").
--
-- (A copy of the text above is in "HordeAd.Core.Ops".)
module HordeAd.Core.Ast
  ( -- * The AstSpan tags, singletons and operations
    AstSpan(..), PrimalSpan, SAstSpan(..), KnownSpan(..), withKnownSpan
  , primalPart, dualPart, plainPart, fromPrimal, fromDual, fromPlain
    -- * Variables and related types
  , AstVarId, intToAstVarId
  , AstInt, IntVarName, pattern AstIntVar, AstBool
  , AstVarName(..), FtkAndBounds(..)
  , mkAstVarName, mkAstVarNameBounds
  , reshapeVarName, respanVarName, reboundsVarName
  , varNameToAstVarId, varNameToSpan, varNameToFTK, varNameToBounds
  , AstArtifactRev(..), AstArtifactFwd(..)
  , AstIxS, AstVarListS
  , ListS(..)  -- TODO: ListS should be hidden and its constructor
               -- exposed as AstVarListS constructor
  , pattern ZS, pattern (::$), pattern AstLeqInt
    -- * AST
  , AstMethodOfSharing(..), AstTensor(..), AstHFun(..)
  , OpCodeNum1(..), OpCode1(..), OpCode2(..), OpCodeIntegral2(..)
  ) where

import Prelude

import Data.Dependent.EnumMap.Strict qualified as DMap
import Data.Foldable qualified as Foldable
import Data.Kind (Type)
import Data.Type.Equality (TestEquality (..), (:~:) (Refl))
import Data.Vector.Strict qualified as Data.Vector
import GHC.Exts (withDict)
import GHC.IsList (IsList)
import GHC.IsList qualified as IsList
import GHC.TypeLits (type (+), type (<=))
import Type.Reflection (typeRep)

import Data.Array.Nested (type (++))
import Data.Array.Nested qualified as Nested
import Data.Array.Nested.Mixed.Shape
import Data.Array.Nested.Permutation qualified as Permutation
import Data.Array.Nested.Shaped.Shape
import Data.Array.Nested.Types (Init)

import HordeAd.Core.Conversion
import HordeAd.Core.TensorKind
import HordeAd.Core.Types

-- * The AstSpan tags, singletons and operations

-- | A type intended to be promoted that marks whether an AST term
-- is supposed to denote the (n-th iteration of taking the) primal part
-- of a dual number, the dual part, the whole dual number or a plain value.
-- It's mainly used to index the terms of the AstTensor type
-- and related GADTs.
type data AstSpan =
  FullSpan | PrimalStepSpan AstSpan | DualSpan | PlainSpan
type PrimalSpan = PrimalStepSpan FullSpan

-- | The singleton type for `AstSpan`.
type role SAstSpan nominal
data SAstSpan s where
  SFullSpan :: SAstSpan FullSpan
  SPrimalStepSpan :: SAstSpan s -> SAstSpan (PrimalStepSpan s)
  SDualSpan :: SAstSpan DualSpan
  SPlainSpan :: SAstSpan PlainSpan

instance TestEquality SAstSpan where
  testEquality SFullSpan SFullSpan = Just Refl
  testEquality (SPrimalStepSpan s1) (SPrimalStepSpan s2)
    | Just Refl <- testEquality s1 s2 = Just Refl
  testEquality SDualSpan SDualSpan = Just Refl
  testEquality SPlainSpan SPlainSpan = Just Refl
  testEquality _ _ = Nothing

-- These are weak definitions rewriting-wise and we can't move them
-- to AstSimplify to improve this, because it's too late
-- and also astPrimalPart only works on AstMethodLet.
primalPart :: forall s ms y. KnownSpan s
           => AstTensor ms s y -> AstTensor ms (PrimalStepSpan s) y
primalPart t = case knownSpan @s of
  SFullSpan -> cAstPrimalPart t
  SPrimalStepSpan{} -> cAstPrimalPart t
  SDualSpan -> fullSpanToStep knownSpan $ AstFromDual t  -- this is primal zero
  SPlainSpan -> cAstPrimalPart t

dualPart :: forall s ms y. KnownSpan s
         => AstTensor ms s y -> AstTensor ms DualSpan y
dualPart t = case knownSpan @s of
  SFullSpan -> cAstDualPart t
  SPrimalStepSpan s -> cAstDualPart $ stepToFullSpan s t  -- this is dual zero
  SDualSpan -> t
  SPlainSpan -> AstDualPart $ AstFromPlain t  -- this is dual zero

plainPart :: forall s ms y. KnownSpan s
          => AstTensor ms s y -> AstTensor ms PlainSpan y
plainPart t = case knownSpan @s of
  SFullSpan -> cAstPlainPart t
  SPrimalStepSpan{} -> cAstPlainPart t
  SDualSpan -> AstPlainPart $ AstFromDual t  -- this is plain zero
  SPlainSpan -> t

fromPrimal :: forall s ms y. KnownSpan s
           => AstTensor ms (PrimalStepSpan s) y -> AstTensor ms s y
fromPrimal t = case knownSpan @s of
  SFullSpan -> cAstFromPrimal t
  SPrimalStepSpan{} -> cAstFromPrimal t
  SDualSpan -> cAstDualPart $ stepToFullSpan knownSpan t  -- this is dual zero
  SPlainSpan -> cAstPlainPart t

fromDual :: forall s ms y. KnownSpan s
         => AstTensor ms DualSpan y -> AstTensor ms s y
fromDual t = case knownSpan @s of
  SFullSpan -> AstFromDual t
  SPrimalStepSpan s -> fullSpanToStep s $ AstFromDual t  -- this is primal zero
  SDualSpan -> t
  SPlainSpan -> AstPlainPart $ AstFromDual t  -- this is plain zero

fromPlain :: forall s ms y. KnownSpan s
          => AstTensor ms PlainSpan y -> AstTensor ms s y
fromPlain t = case knownSpan @s of
  SFullSpan -> AstFromPlain t
  SPrimalStepSpan{} -> AstFromPlain t
  SDualSpan -> AstDualPart $ AstFromPlain t  -- this is dual zero
  SPlainSpan -> t

class KnownSpan (s :: AstSpan) where
  knownSpan :: SAstSpan s

instance KnownSpan FullSpan where
  knownSpan = SFullSpan

instance KnownSpan s => KnownSpan (PrimalStepSpan s) where
  knownSpan = SPrimalStepSpan (knownSpan @s)

instance KnownSpan DualSpan where
  knownSpan = SDualSpan

instance KnownSpan PlainSpan where
  knownSpan = SPlainSpan

-- | Turn a singleton into a constraint via a continuation.
withKnownSpan :: forall s r. SAstSpan s -> (KnownSpan s => r) -> r
withKnownSpan = withDict @(KnownSpan s)

fullSpanToStep :: SAstSpan s
               -> AstTensor ms FullSpan y
               -> AstTensor ms (PrimalStepSpan s) y
fullSpanToStep = \case
  SFullSpan -> cAstPrimalPart
  SPrimalStepSpan sspan -> withKnownSpan sspan
                           $ cAstPrimalPart . fullSpanToStep sspan
  SDualSpan -> cAstPrimalPart . cAstDualPart
  SPlainSpan -> cAstPrimalPart . cAstPlainPart

stepToFullSpan :: SAstSpan s
               -> AstTensor ms (PrimalStepSpan s) y
               -> AstTensor ms FullSpan y
stepToFullSpan = \case
  SFullSpan -> cAstFromPrimal
  SPrimalStepSpan sspan -> withKnownSpan sspan
                           $ stepToFullSpan sspan . cAstFromPrimal
  SDualSpan -> AstFromDual . cAstFromPrimal
  SPlainSpan -> AstFromPlain . cAstFromPrimal

cAstPrimalPart :: forall y s ms. KnownSpan s
               => AstTensor ms s y -> AstTensor ms (PrimalStepSpan s) y
cAstPrimalPart (AstFromPrimal t) = t
cAstPrimalPart (AstFromPlain t) = AstFromPlain t
cAstPrimalPart t = AstPrimalPart t

cAstDualPart :: forall y ms.
                AstTensor ms FullSpan y -> AstTensor ms DualSpan y
cAstDualPart (AstFromDual t) = t
cAstDualPart t = AstDualPart t

cAstPlainPart :: forall y s ms. KnownSpan s
              => AstTensor ms s y -> AstTensor ms PlainSpan y
cAstPlainPart (AstFromPlain v) = v
cAstPlainPart (AstPrimalPart v) = cAstPlainPart v
cAstPlainPart (AstFromPrimal v) = cAstPlainPart v
cAstPlainPart t | SPlainSpan <- knownSpan @s = t
cAstPlainPart t = AstPlainPart t

cAstFromPrimal :: forall y s ms. KnownSpan s
               => AstTensor ms (PrimalStepSpan s) y -> AstTensor ms s y
cAstFromPrimal (AstFromPlain t) = cAstFromPlain t
cAstFromPrimal t = AstFromPrimal t

cAstFromPlain :: forall y s ms. KnownSpan s
              => AstTensor ms PlainSpan y -> AstTensor ms s y
cAstFromPlain t | SPlainSpan <- knownSpan @s = t
cAstFromPlain t = AstFromPlain t


-- * Variables and related types

newtype AstVarId = AstVarId Int
 deriving (Eq, Ord, Show, Enum)

intToAstVarId :: Int -> AstVarId
intToAstVarId = AstVarId

type role AstVarName nominal
data AstVarName :: (AstSpan, TK) -> Type where
  AstVarName :: AstVarId -> FtkAndBounds s_y -> AstVarName s_y

instance Eq (AstVarName '(s, y)) where
  AstVarName varId1 _ == AstVarName varId2 _ = varId1 == varId2

instance Show (AstVarName '(s, y)) where
  showsPrec d (AstVarName varId _) =
    showsPrec d varId  -- less verbose, more readable

instance TestEquality AstVarName where
  testEquality (AstVarName _ ftkBounds1) (AstVarName _ ftkBounds2)
    | Just Refl <- testEquality ftkBounds1 ftkBounds2 =
      Just Refl
  testEquality _ _ = Nothing

instance DMap.Enum1 AstVarName where
  type Enum1Info AstVarName = FtkAndBounds
  fromEnum1 (AstVarName varId ftkBounds) = (fromEnum varId, ftkBounds)
  toEnum1 varIdInt = AstVarName (toEnum varIdInt)

type role FtkAndBounds nominal
data FtkAndBounds :: (AstSpan, TK) -> Type where
  FtkAndBoundsFull :: FullShapeTK y
                   -> FtkAndBounds '(FullSpan, y)
  FtkAndBoundsPrimal :: FullShapeTK y -> SAstSpan s
                     -> FtkAndBounds '(PrimalStepSpan s, y)
  FtkAndBoundsDual :: FullShapeTK y
                   -> FtkAndBounds '(DualSpan, y)
  FtkAndBoundsPlain :: FullShapeTK y
                    -> FtkAndBounds '(PlainSpan, y)
  FtkAndBoundsBounds :: Int -> Int
                     -> FtkAndBounds '(PlainSpan, TKScalar Int)

instance TestEquality FtkAndBounds where
  testEquality ftkBounds1 ftkBounds2 = case (ftkBounds1, ftkBounds2) of
    (FtkAndBoundsFull ftk1, FtkAndBoundsFull ftk2)
      | Just Refl <- matchingFTK ftk1 ftk2 ->
        Just Refl
    (FtkAndBoundsPrimal ftk1 sspan1, FtkAndBoundsPrimal ftk2 sspan2)
      | Just Refl <- testEquality sspan1 sspan2
      , Just Refl <- matchingFTK ftk1 ftk2 ->
        Just Refl
    (FtkAndBoundsDual ftk1, FtkAndBoundsDual ftk2)
      | Just Refl <- matchingFTK ftk1 ftk2 ->
        Just Refl
    (FtkAndBoundsPlain ftk1, FtkAndBoundsPlain ftk2)
      | Just Refl <- matchingFTK ftk1 ftk2 ->
        Just Refl
    (FtkAndBoundsBounds _ _ , FtkAndBoundsBounds _ _) ->
      Just Refl
    _ -> Nothing

mkAstVarName :: forall s y. KnownSpan s
             => FullShapeTK y -> AstVarId -> AstVarName '(s, y)
mkAstVarName ftk varId =
  let ftkBounds = case knownSpan @s of
        SFullSpan -> FtkAndBoundsFull ftk
        SPrimalStepSpan sspan -> FtkAndBoundsPrimal ftk sspan
        SDualSpan -> FtkAndBoundsDual ftk
        SPlainSpan -> FtkAndBoundsPlain ftk
  in AstVarName varId ftkBounds

mkAstVarNameBounds :: (Int, Int) -> AstVarId
                   -> AstVarName '(PlainSpan, TKScalar Int)
{-# INLINE mkAstVarNameBounds #-}
mkAstVarNameBounds (lb, ub) varId = AstVarName varId $ FtkAndBoundsBounds lb ub

reshapeVarName :: FullShapeTK z -> AstVarName '(s, y) -> AstVarName '(s, z)
reshapeVarName ftk (AstVarName varId ftkBounds) =
  AstVarName varId $ case ftkBounds of
    FtkAndBoundsFull{} -> FtkAndBoundsFull ftk
    (FtkAndBoundsPrimal _ sspan) -> FtkAndBoundsPrimal ftk sspan
    FtkAndBoundsDual{} -> FtkAndBoundsDual ftk
    FtkAndBoundsPlain{} -> FtkAndBoundsPlain ftk
    FtkAndBoundsBounds{}
      | FTKScalar @r <- ftk
      , Just Refl <- testEquality (typeRep @r) (typeRep @Int) -> ftkBounds
    FtkAndBoundsBounds{} -> FtkAndBoundsPlain ftk

-- | This fails if the variable had bounds (that would be now lost,
-- unless the new span is the same as old, which is just as irregular).
respanVarName :: forall s s2 y. KnownSpan s2
              => AstVarName '(s, y) -> AstVarName '(s2, y)
respanVarName var@(AstVarName varId ftkBounds) = case ftkBounds of
  FtkAndBoundsBounds{} -> error "respanVarName: bounds lost"
  _ -> mkAstVarName (varNameToFTK var) varId

reboundsVarName :: (Int, Int) -> AstVarName '(PlainSpan, TKScalar Int)
                -> AstVarName '(PlainSpan, TKScalar Int)
reboundsVarName (lb, ub) (AstVarName varId _) =
  mkAstVarNameBounds (lb, ub) varId

varNameToAstVarId :: AstVarName s_y -> AstVarId
varNameToAstVarId (AstVarName varId _) = varId

varNameToSpan :: AstVarName '(s, y) -> SAstSpan s
varNameToSpan (AstVarName _ ftkBounds) = case ftkBounds of
  FtkAndBoundsFull{} -> SFullSpan
  (FtkAndBoundsPrimal _ sspan) -> SPrimalStepSpan sspan
  FtkAndBoundsDual{} -> SDualSpan
  FtkAndBoundsPlain{} -> SPlainSpan
  FtkAndBoundsBounds{} -> SPlainSpan

varNameToFTK :: AstVarName '(s, y) -> FullShapeTK y
varNameToFTK (AstVarName _ ftkBounds) = case ftkBounds of
  (FtkAndBoundsFull ftk) -> ftk
  (FtkAndBoundsPrimal ftk _) -> ftk
  (FtkAndBoundsDual ftk) -> ftk
  (FtkAndBoundsPlain ftk) -> ftk
  FtkAndBoundsBounds{} -> FTKScalar

varNameToBounds :: AstVarName '(s, y) -> Maybe (Int, Int)
varNameToBounds (AstVarName _ (FtkAndBoundsBounds lb ub)) = Just (lb, ub)
varNameToBounds _ = Nothing

-- | The reverse derivative artifact.
type role AstArtifactRev nominal nominal
data AstArtifactRev x z = AstArtifactRev
  { artVarDtRev      :: AstVarName '(FullSpan, ADTensorKind z)
  , artVarDomainRev  :: AstVarName '(FullSpan, x)
  , artDerivativeRev :: AstTensor AstMethodLet FullSpan (ADTensorKind x)
  , artPrimalRev     :: ~(AstTensor AstMethodLet FullSpan z)
      -- rarely used, so not forced
  }
 deriving Show

-- | The forward derivative artifact.
type role AstArtifactFwd nominal nominal
data AstArtifactFwd x z = AstArtifactFwd
  { artVarDsFwd      :: AstVarName '(FullSpan, ADTensorKind x)
  , artVarDomainFwd  :: AstVarName '(FullSpan, x)
  , artDerivativeFwd :: AstTensor AstMethodLet FullSpan (ADTensorKind z)
  , artPrimalFwd     :: ~(AstTensor AstMethodLet FullSpan z)
      -- rarely used, so not forced
  }
 deriving Show

-- | This is the (arbitrarily) chosen representation of terms denoting
-- integers in the indexes of tensor operations.
type AstInt ms = AstTensor ms PlainSpan (TKScalar Int)
-- ~ IntOf (AstTensor ms s)

type IntVarName = AstVarName '(PlainSpan, TKScalar Int)

pattern AstIntVar :: IntVarName -> AstInt ms
pattern AstIntVar var <- AstVar var

-- This is needed only for AstVarListS. Parameter i is needed only
-- to derive Foldable.
type role ListS nominal representational
newtype ListS sh i = AstVarListS {unAstVarListS :: IxS sh i}
  deriving (Foldable)

instance Show (AstVarListS sh) where
  showsPrec d (AstVarListS ix) = showsPrec d ix

instance KnownShS sh => IsList (ListS sh i) where
  type Item (ListS sh i) = i
  fromList = AstVarListS . IsList.fromList
  toList = Foldable.toList

pattern ZS :: forall sh i. () => sh ~ '[] => ListS sh i
pattern ZS = AstVarListS ZIS

pattern (::$)
  :: forall {sh1} {i}.
     forall n sh. (n : sh ~ sh1)
  => i -> ListS sh i -> ListS sh1 i
pattern i ::$ l <- AstVarListS (i :.$ (AstVarListS -> l))
  where i ::$ AstVarListS l = AstVarListS (i :.$ l)
infixr 3 ::$

{-# COMPLETE ZS, (::$) #-}

-- Data invariant: the var names have bounds of the form (0, k - 1),
-- where the corresponding dimension in sh is k. This is never checked.
type AstVarListS sh = ListS sh IntVarName

-- There's no data invariant here. The shape matches rather the argument
-- of indexing (or gather) than the indexes.
type AstIxS ms sh = IxS sh (AstInt ms)

pattern AstLeqInt :: AstInt ms -> AstInt ms -> AstBool ms
pattern AstLeqInt t u <- (matchAstLeqInt -> Just (t, u))
  where AstLeqInt t u = AstLeqK t u

matchAstLeqInt :: AstBool ms -> Maybe (AstInt ms, AstInt ms)
matchAstLeqInt (AstLeqK @r t u)
  | Just Refl <- testEquality (typeRep @r) (typeRep @Int) =
      Just (t, u)
matchAstLeqInt _ = Nothing

type AstBool ms = AstTensor ms PlainSpan (TKScalar Bool)
-- ~ BoolOf (AstTensor ms s)


-- * AST

type data AstMethodOfSharing = AstMethodShare | AstMethodLet

-- | AST for tensors that are meant to be differentiated.
--
-- Some terms have no semantics, e.g., currently terms with nested primal span.
type role AstTensor nominal nominal nominal
data AstTensor :: AstMethodOfSharing -> AstSpan -> Target where
  -- General operations, for scalar, ranked, shared and other tensors at once
  AstPair :: forall y z ms s.
             AstTensor ms s y -> AstTensor ms s z
          -> AstTensor ms s (TKProduct y z)
  AstProject1 :: forall y z ms s.
                 AstTensor ms s (TKProduct y z) -> AstTensor ms s y
  AstProject2 :: forall y z ms s.
                 AstTensor ms s (TKProduct y z) -> AstTensor ms s z
  AstMapAccumLDer
    :: forall accy by ey k ms s.
       SNat k
    -> FullShapeTK by
    -> FullShapeTK ey
    -> AstHFun s
               (TKProduct accy ey) (TKProduct accy by)
    -> AstHFun s
               (TKProduct (ADTensorKind (TKProduct accy ey))
                          (TKProduct accy ey))
               (ADTensorKind (TKProduct accy by))
    -> AstHFun s
               (TKProduct (ADTensorKind (TKProduct accy by))
                          (TKProduct accy ey))
               (ADTensorKind (TKProduct accy ey))
    -> AstTensor ms s accy
    -> AstTensor ms s (BuildTensorKind k ey)
    -> AstTensor ms s (TKProduct accy (BuildTensorKind k by))
  AstApply :: AstHFun s x z -> AstTensor ms s x -> AstTensor ms s z
  AstVar :: AstVarName '(s, y) -> AstTensor ms s y
  AstBuild1 :: forall y k ms s.
               SNat k -> SingletonTK y
            -> (IntVarName, AstTensor ms s y)
            -> AstTensor ms s (BuildTensorKind k y)

  -- Sharing-related operations, mutually exclusive via AstMethodOfSharing
  AstLet :: forall y z s s2.
            AstVarName '(s, y) -> AstTensor AstMethodLet s y
         -> AstTensor AstMethodLet s2 z
         -> AstTensor AstMethodLet s2 z
  AstShare :: AstVarName '(s, y) -> AstTensor AstMethodShare s y
           -> AstTensor AstMethodShare s y
  AstToShare :: AstTensor AstMethodLet s y
             -> AstTensor AstMethodShare s y

  -- Explicit dual numbers handling, eliminated in interpretation to ADVal
  AstPrimalPart :: forall y s ms. KnownSpan s
                => AstTensor ms s y -> AstTensor ms (PrimalStepSpan s) y
  AstDualPart :: forall y ms.
                 AstTensor ms FullSpan y -> AstTensor ms DualSpan y
  AstPlainPart :: forall y s ms. KnownSpan s
               => AstTensor ms s y -> AstTensor ms PlainSpan y
  AstFromPrimal :: forall y s ms.
                   AstTensor ms (PrimalStepSpan s) y -> AstTensor ms s y
  AstFromDual :: forall y ms.
                 AstTensor ms DualSpan y -> AstTensor ms FullSpan y
  AstFromPlain :: forall y s ms.
                  AstTensor ms PlainSpan y -> AstTensor ms s y

  -- Scalar arithmetic (to avoid the slowness of indexes as 1-element tensors)
  AstPlusK :: NumScalar r
           => AstTensor ms s (TKScalar r)
           -> AstTensor ms s (TKScalar r)
           -> AstTensor ms s (TKScalar r)
  AstTimesK :: NumScalar r
            => AstTensor ms s (TKScalar r)
            -> AstTensor ms s (TKScalar r)
            -> AstTensor ms s (TKScalar r)
  AstN1K :: NumScalar r
         => OpCodeNum1 -> AstTensor ms s (TKScalar r)
         -> AstTensor ms s (TKScalar r)
  AstR1K :: (NumScalar r, Differentiable r)
         => OpCode1 -> AstTensor ms s (TKScalar r)
         -> AstTensor ms s (TKScalar r)
  AstR2K :: (NumScalar r, Differentiable r)
         => OpCode2 -> AstTensor ms s (TKScalar r)
         -> AstTensor ms s (TKScalar r)
         -> AstTensor ms s (TKScalar r)
  AstI2K :: (NumScalar r, IntegralH r, Nested.IntElt r)
         => OpCodeIntegral2 -> AstTensor ms s (TKScalar r)
         -> AstTensor ms s (TKScalar r)
         -> AstTensor ms s (TKScalar r)
  AstConcreteK :: GoodScalar r
               => r -> AstTensor ms PlainSpan (TKScalar r)
  AstFloorK :: (NumScalar r1, Differentiable r1, NumScalar r2, Integral r2)
            => AstTensor ms PlainSpan (TKScalar r1)
            -> AstTensor ms PlainSpan (TKScalar r2)
  AstFromIntegralK :: (NumScalar r1, Integral r1, NumScalar r2)
                   => AstTensor ms PlainSpan (TKScalar r1)
                   -> AstTensor ms PlainSpan (TKScalar r2)
  AstCastK :: (NumScalar r1, Differentiable r1, NumScalar r2, Differentiable r2)
           => AstTensor ms s (TKScalar r1) -> AstTensor ms s (TKScalar r2)
  AstArgMinK :: forall n r ms. NumScalar r
             => AstTensor ms PlainSpan (TKS '[n] r)
             -> AstTensor ms PlainSpan (TKScalar Int)
  AstArgMaxK :: forall n r ms. NumScalar r
             => AstTensor ms PlainSpan (TKS '[n] r)
             -> AstTensor ms PlainSpan (TKScalar Int)
  AstIndexK :: forall shm r s ms. GoodScalar r
            => AstTensor ms s (TKS shm r) -> AstIxS ms shm
            -> AstTensor ms s (TKScalar r)
    -- out of bounds indexing is permitted and the results is def (==0)

  -- Shaped arithmetic
  AstPlusS :: NumScalar r
           => AstTensor ms s (TKS sh r)
           -> AstTensor ms s (TKS sh r)
           -> AstTensor ms s (TKS sh r)
  AstTimesS :: NumScalar r
            => AstTensor ms s (TKS sh r)
            -> AstTensor ms s (TKS sh r)
            -> AstTensor ms s (TKS sh r)
  AstN1S :: NumScalar r
         => OpCodeNum1 -> AstTensor ms s (TKS sh r)
         -> AstTensor ms s (TKS sh r)
  AstR1S :: (NumScalar r, Differentiable r)
         => OpCode1 -> AstTensor ms s (TKS sh r)
         -> AstTensor ms s (TKS sh r)
  AstR2S :: (NumScalar r, Differentiable r)
         => OpCode2 -> AstTensor ms s (TKS sh r)
         -> AstTensor ms s (TKS sh r)
         -> AstTensor ms s (TKS sh r)
  AstI2S :: (NumScalar r, IntegralH r, Nested.IntElt r)
         => OpCodeIntegral2 -> AstTensor ms s (TKS sh r)
         -> AstTensor ms s (TKS sh r)
         -> AstTensor ms s (TKS sh r)
  AstConcreteS :: GoodScalar r
               => Nested.Shaped sh r -> AstTensor ms PlainSpan (TKS sh r)
  AstFloorS :: (NumScalar r1, Differentiable r1, NumScalar r2, Integral r2)
            => AstTensor ms PlainSpan (TKS sh r1)
            -> AstTensor ms PlainSpan (TKS sh r2)
  AstFromIntegralS :: (NumScalar r1, Integral r1, NumScalar r2)
                   => AstTensor ms PlainSpan (TKS sh r1)
                   -> AstTensor ms PlainSpan (TKS sh r2)
  AstCastS :: (NumScalar r1, Differentiable r1, NumScalar r2, Differentiable r2)
           => AstTensor ms s (TKS sh r1)
           -> AstTensor ms s (TKS sh r2)
  AstArgMinS :: forall k sh r ms. NumScalar r
             => AstTensor ms PlainSpan (TKS (k ': sh) r)
             -> AstTensor ms PlainSpan (TKS (Init (k ': sh)) Int)
  AstArgMaxS :: forall k sh r ms. NumScalar r
             => AstTensor ms PlainSpan (TKS (k ': sh) r)
             -> AstTensor ms PlainSpan (TKS (Init (k ': sh)) Int)
  AstIndexS :: forall shm shn x s ms.
               ShS shn
            -> AstTensor ms s (TKS2 (shm ++ shn) x) -> AstIxS ms shm
            -> AstTensor ms s (TKS2 shn x)

  AstCondK :: forall r ms s. GoodScalar r
           => AstBool ms
           -> AstTensor ms s (TKScalar r)
           -> AstTensor ms s (TKScalar r)
           -> AstTensor ms s (TKScalar r)
  AstCondS :: forall sh x ms s.
              AstBool ms
           -> AstTensor ms s (TKS2 sh x)
           -> AstTensor ms s (TKS2 sh x)
           -> AstTensor ms s (TKS2 sh x)
  AstFromVectorK :: forall shm r s ms. GoodScalar r
                 => ShS shm -> Data.Vector.Vector (AstTensor ms s (TKScalar r))
                 -> AstTensor ms s (TKS shm r)
  AstFromVectorS :: forall shm shn x s ms.
                    ShS shm -> Data.Vector.Vector (AstTensor ms s (TKS2 shn x))
                 -> AstTensor ms s (TKS2 (shm ++ shn) x)
  AstSumK :: forall shm r s ms. NumScalar r
          => AstTensor ms s (TKS shm r)
          -> AstTensor ms s (TKScalar r)
  AstSumS :: forall shm shn x s ms. TKAllNum x
          => ShS shm -> AstTensor ms s (TKS2 (shm ++ shn) x)
          -> AstTensor ms s (TKS2 shn x)
  -- Shaped tensor operations
  AstScatterS :: forall shm shn shp x s ms. TKAllNum x
              => ShS shm -> ShS shn -> ShS shp
              -> AstTensor ms s (TKS2 (shm ++ shn) x)
              -> (AstVarListS shm, AstIxS ms shp)
              -> AstTensor ms s (TKS2 (shp ++ shn) x)
    -- out of bounds indexing is permitted and the results is def (==0)
  AstReplicateK :: forall shm r s ms. GoodScalar r
                => ShS shm -> AstTensor ms s (TKScalar r)
                -> AstTensor ms s (TKS shm r)
  AstReplicateS :: forall shm shn x s ms.
                   ShS shm -> AstTensor ms s (TKS2 shn x)
                -> AstTensor ms s (TKS2 (shm ++ shn) x)
  AstGatherS :: forall shm shn shp x s ms.
                ShS shm -> ShS shn -> ShS shp
             -> AstTensor ms s (TKS2 (shp ++ shn) x)
             -> (AstVarListS shm, AstIxS ms shp)
             -> AstTensor ms s (TKS2 (shm ++ shn) x)
    -- out of bounds indexing is permitted and the results is def (==0)
  AstIotaS :: forall n r ms. NumScalar r
           => SNat n -> AstTensor ms PlainSpan (TKS '[n] r)
  AstAppendS :: forall m n sh x ms s.
                AstTensor ms s (TKS2 (m ': sh) x)
             -> AstTensor ms s (TKS2 (n ': sh) x)
             -> AstTensor ms s (TKS2 ((m + n) ': sh) x)
  AstSliceS :: SNat i -> SNat n -> SNat k
            -> AstTensor ms s (TKS2 (i + n + k ': sh) x)
            -> AstTensor ms s (TKS2 (n ': sh) x)
  AstReverseS :: forall n sh x ms s.
                 AstTensor ms s (TKS2 (n ': sh) x)
              -> AstTensor ms s (TKS2 (n ': sh) x)
  AstTransposeS :: (Permutation.IsPermutation perm, Rank perm <= Rank sh)
                => Permutation.Perm perm -> AstTensor ms s (TKS2 sh x)
                -> AstTensor ms s (TKS2 (Permutation.PermutePrefix perm sh) x)
  AstReshapeS :: Product sh ~ Product sh2
              => ShS sh2
              -> AstTensor ms s (TKS2 sh x) -> AstTensor ms s (TKS2 sh2 x)

  -- Conversions
  AstConvert :: TKConversion a b -> AstTensor ms s a -> AstTensor ms s b

  -- Backend-specific primitives
  AstDot0 :: NumScalar r
          => AstTensor ms s (TKS sh r) -> AstTensor ms s (TKS sh r)
          -> AstTensor ms s (TKScalar r)
  AstDot1InS :: forall sh n r ms s. NumScalar r
             => ShS sh -> SNat n
             -> AstTensor ms s (TKS (sh ++ '[n]) r)
             -> AstTensor ms s (TKS (sh ++ '[n]) r)
             -> AstTensor ms s (TKS sh r)
  AstMatmul2S :: NumScalar r
              => SNat m -> SNat n -> SNat p
              -> AstTensor ms s (TKS '[m, n] r)
              -> AstTensor ms s (TKS '[n, p] r)
              -> AstTensor ms s (TKS '[m, p] r)

  -- Booleans
  AstBoolNotK :: AstBool ms -> AstBool ms
  AstBoolNotS :: AstTensor ms PlainSpan (TKS sh Bool)
              -> AstTensor ms PlainSpan (TKS sh Bool)
  AstBoolAndK :: AstBool ms -> AstBool ms -> AstBool ms
  AstBoolAndS :: AstTensor ms PlainSpan (TKS sh Bool)
              -> AstTensor ms PlainSpan (TKS sh Bool)
              -> AstTensor ms PlainSpan (TKS sh Bool)
  -- There are existential variables here.
  AstLeqK :: forall r ms. NumScalar r
          => AstTensor ms PlainSpan (TKScalar r)
          -> AstTensor ms PlainSpan (TKScalar r)
          -> AstBool ms
  AstLeq :: forall sh r ms. NumScalar r
         => AstTensor ms PlainSpan (TKS sh r)
         -> AstTensor ms PlainSpan (TKS sh r)
         -> AstBool ms
  AstLeqS :: forall shb sh r ms. NumScalar r
          => ShS shb -> ShS sh
          -> AstTensor ms PlainSpan (TKS (shb ++ sh) r)
          -> AstTensor ms PlainSpan (TKS (shb ++ sh) r)
          -> AstTensor ms PlainSpan (TKS shb Bool)

deriving instance Show (AstTensor ms s y)
  -- for this to work, AstConcreteS can't take a Concrete;
  -- an alternative might be @Has Show (AstTensor ms s)@, but then we'd need
  -- to write @has@ before we apply @show@ and we'd weaken @AllTargetShow@

type role AstHFun nominal nominal nominal
data AstHFun s x z where
  AstLambda :: ~(AstVarName '(s, x))
            -> ~(AstTensor AstMethodLet s z)
            -> AstHFun s x z
    -- ^ The function body can't have any free variables outside those
    -- listed in the first component of the pair; this reflects
    -- the quantification in 'HordeAd.Core.Ops.rrev'
    -- and prevents "perturbation confusion".
    --
    -- The constructor is non-strict in order not to pre-compute
    -- higher derivatives (e.g., inside folds) that are never going to be used.
    -- As a side effect, all lambdas (closed functions) are processed
    -- lazily, which causes no harm, since they have no outside free variables
    -- and so can't easiliy induce leaks by retaining outside values (e.g.,
    -- big environments from which values for the variables would be drawn).
    -- The cost of computing a reverse derivative of a fold nested inside
    -- the function argument n times is reduced by the laziness from 20^n
    -- to under 2^n (old experimental results). Note, however,
    -- that if the n-th forward and reverse derivative is taken,
    -- the laziness is defeated. To make the variable argument strict
    -- we'd need to modify some other code fragments, while the performance
    -- impact seems mixed.

deriving instance Show (AstHFun s x z)

data OpCodeNum1 =
    NegateOp | AbsOp | SignumOp
 deriving (Show, Eq)

data OpCode1 =
    RecipOp
  | ExpOp | LogOp | SqrtOp
  | SinOp | CosOp | TanOp | AsinOp | AcosOp | AtanOp
  | SinhOp | CoshOp | TanhOp | AsinhOp | AcoshOp | AtanhOp
 deriving (Show, Eq)

data OpCode2 =
    DivideOp
  | PowerOp | LogBaseOp
  | Atan2Op
 deriving (Show, Eq)

data OpCodeIntegral2 =
    QuotOp | RemOp
 deriving (Show, Eq)