packages feed

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

{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fplugin GHC.TypeLits.KnownNat.Solver #-}
{-# OPTIONS_GHC -fplugin GHC.TypeLits.Normalise #-}
-- | The grammar of delta expressions.
--
-- A delta expression can be viewed as a concise representation
-- of a linear map (which is the derivative of the objective function)
-- and its evaluation on a given argument (in module "HordeAd.Core.DeltaEval")
-- as an adjoint (in the algebraic sense) of the linear map
-- applied to that argument. Since linear maps can be represented
-- as matrices, this operation corresponds to a transposition
-- of the matrix. However, the matrix is not constructed,
-- but is represented and transposed preserving the sparsity
-- of the representation.
--
-- The \'sparsity\' is less obvious when a delta expression
-- contains big concrete tensors, e.g., via the `DeltaScale` constructor.
-- However, via 'DeltaReplicateR' and other constructors, the tensors
-- can be enlarged much beyond what's embedded in the delta term.
-- Also, if the expression refers to unknown inputs ('DeltaInput')
-- it may denote, after evaluation, a still larger tensor.
--
-- The algebraic structure here is an extension of vector space
-- with some additional constructors. The crucial extra constructor
-- 'DeltaInput' replaces the usual one-hot access to parameters
-- with something cheaper and more uniform.
-- A lot of the remaining additional constructors is for introducing
-- and reducing dimensions of tensors and it mimics many of the operations
-- available for the primal value arrays.
module HordeAd.Core.Delta
  ( -- * Delta identifiers
    NodeId, mkNodeId, nodeIdToFTK
  , InputId, mkInputId, inputIdToFTK
    -- * The grammar of delta expressions
  , Delta(..), NestedTarget(..)
    -- * Full tensor kind derivation for delta expressions
  , ftkDelta
  ) where

import Prelude

import Control.Exception.Assert.Sugar
import Data.Dependent.EnumMap.Strict qualified as DMap
import Data.Kind (Type)
import Data.Type.Equality (TestEquality (..), gcastWith, testEquality, (:~:))
import Data.Vector.Generic qualified as V
import Data.Vector.Strict qualified as Data.Vector
import GHC.TypeLits (type (+), type (<=))
import Text.Show.Functions ()

import Data.Array.Nested (type (++))
import Data.Array.Nested.Mixed.Shape
import Data.Array.Nested.Permutation qualified as Permutation
import Data.Array.Nested.Ranked.Shape
import Data.Array.Nested.Shaped.Shape
import Data.Array.Nested.Types (snatPlus, unsafeCoerceRefl)

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

-- * Delta identifiers

-- | The identifiers for nodes of delta expression trees.
type role NodeId nominal nominal
data NodeId :: Target -> TK -> Type where
  NodeId :: forall target y. FullShapeTK y -> Int -> NodeId target y

-- No Eq instance to limit hacks outside this module.

instance Show (NodeId target y) where
  showsPrec d (NodeId _ n) =
    showsPrec d n  -- less verbose, more readable

instance DMap.Enum1 (NodeId target) where
  type Enum1Info (NodeId target) = FullShapeTK
  fromEnum1 (NodeId ftk n) = (n, ftk)
  toEnum1 n ftk = NodeId ftk n

instance TestEquality (NodeId target) where
  testEquality (NodeId ftk1 _) (NodeId ftk2 _) = matchingFTK ftk1 ftk2

-- | Wrap non-negative (only!) integers in the t'NodeId' newtype.
mkNodeId :: FullShapeTK y -> Int -> NodeId f y
mkNodeId ftk i = assert (i >= 0) $ NodeId ftk i

nodeIdToFTK :: NodeId f y -> FullShapeTK y
nodeIdToFTK (NodeId ftk _) = ftk

-- | The identifiers for input leaves of delta expressions.
type role InputId nominal nominal
data InputId :: Target -> TK -> Type where
  InputId :: forall target y. FullShapeTK y -> Int -> InputId target y

-- No Eq instance to limit hacks outside this module.

instance Show (InputId target y) where  -- backward compatibility
  showsPrec _ (InputId _ n) =
    showParen True
    $ showString "InputId "
      . shows n

instance DMap.Enum1 (InputId target) where
  type Enum1Info (InputId target) = FullShapeTK
  fromEnum1 (InputId ftk n) = (n, ftk)
  toEnum1 n ftk = InputId ftk n

instance TestEquality (InputId target) where
  testEquality (InputId ftk1 _) (InputId ftk2 _) = matchingFTK ftk1 ftk2

-- | Wrap non-negative (only!) integers in the t'InputId' newtype.
mkInputId :: FullShapeTK y -> Int -> InputId f y
mkInputId ftk i = assert (i >= 0) $ InputId ftk i

inputIdToFTK :: InputId f y -> FullShapeTK y
inputIdToFTK (InputId ftk _) = ftk


-- * The grammar of delta expressions

-- | The grammar of delta expressions.
--
-- The t`NodeId` identifier that appears in a @DeltaShare n d@ expression
-- is the unique identity stamp of subterm @d@, that is, there is
-- no different term @e@ such that @DeltaShare n e@ appears in any delta
-- expression term in memory during the same run of an executable.
-- The subterm identity is used to avoid evaluating shared
-- subterms repeatedly in gradient and derivative computations.
-- The identifiers also represent data dependencies among terms
-- for the purpose of gradient and derivative computation. Computation for
-- a term may depend only on data obtained from terms with lower value
-- of their node identifiers. Such data dependency determination
-- agrees with the subterm relation, but is faster than traversing
-- the term tree in order to determine the relation of terms.
--
-- When computing gradients, node identifiers are also used to index,
-- directly or indirectly, the data accumulated for each node,
-- in the form of cotangents, that is partial derivatives
-- of the objective function with respect to the position(s)
-- of the node in the whole objective function dual number term
-- (or, more precisely, with respect to the single node in the term DAG,
-- in which subterms with the same node identifier are collapsed).
-- Only the @DeltaInput@ nodes have a separate data storage.
-- The t`InputId` identifiers in the @DeltaInput@ term constructors
-- are indexes into a contiguous vector of cotangents of @DeltaInput@
-- subterms of the whole term. The value at that index is the partial
-- derivative of the objective function (represented by the whole term,
-- or more precisely by (the data flow graph of) its particular
-- evaluation from which the delta expression originates)
-- with respect to the input parameter component at that index
-- in the objective function domain.
type role Delta nominal nominal
data Delta :: Target -> Target where
  -- Sharing-related operations
  DeltaShare :: NodeId target y -> Delta target y -> Delta target y
  DeltaInput :: InputId target y -> Delta target y

  -- General operations
  DeltaPair :: forall y z target.
               Delta target y -> Delta target z
            -> Delta target (TKProduct y z)
  DeltaProject1 :: forall y z target.
                   Delta target (TKProduct y z) -> Delta target y
  DeltaProject2 :: forall y z target.
                   Delta target (TKProduct y z) -> Delta target z
  DeltaMapAccumL
    :: forall target k accy by ey.
       ( Show (target (BuildTensorKind k accy))
       , Show (target (BuildTensorKind k ey)) )
    => SNat k
    -> FullShapeTK by
    -> FullShapeTK ey
    -> target (BuildTensorKind k accy)
    -> target (BuildTensorKind k ey)
    -> HFun (TKProduct (ADTensorKind (TKProduct accy ey))
                       (TKProduct accy ey))
            (ADTensorKind (TKProduct accy by))
    -> HFun (TKProduct (ADTensorKind (TKProduct accy by))
                       (TKProduct accy ey))
            (ADTensorKind (TKProduct accy ey))
    -> Delta target accy
    -> Delta target (BuildTensorKind k ey)
    -> Delta target (TKProduct accy (BuildTensorKind k by))

  -- Vector space operations
  DeltaZero :: FullShapeTK y -> Delta target y
  DeltaScale :: Num (target y)
             => NestedTarget target y -> Delta target y -> Delta target y
  DeltaAdd :: Num (target y)
           => Delta target y -> Delta target y -> Delta target y

  -- Scalar arithmetic
  DeltaCastK :: ( NumScalar r1, Differentiable r1
                , NumScalar r2, Differentiable r2 )
             => Delta target (TKScalar r1) -> Delta target (TKScalar r2)

  -- Ranked tensor operations
  DeltaCastR :: ( NumScalar r1, Differentiable r1
                , NumScalar r2, Differentiable r2 )
             => Delta target (TKR n r1) -> Delta target (TKR n r2)
  DeltaDot0R :: (NumScalar r, Show (target (TKR n r)))
             => target (TKR n r) -> Delta target (TKR n r)
             -> Delta target (TKScalar r)
  DeltaIndex0R :: forall m r target. GoodScalar r
               => Delta target (TKR m r) -> IxROf target m
               -> Delta target (TKScalar r)
  DeltaIndexR :: forall m n r target.
                 SNat n
              -> Delta target (TKR2 (m + n) r) -> IxROf target m
              -> Delta target (TKR2 n r)
  DeltaFromVectorLinearR :: forall m r target. GoodScalar r
                         => IShR m
                         -> Data.Vector.Vector (Delta target (TKScalar r))
                         -> Delta target (TKR m r)
  DeltaFromVectorR :: forall m n x target.
                      IShR m
                   -> Data.Vector.Vector (Delta target (TKR2 n x))
                   -> Delta target (TKR2 (m + n) x)
  DeltaSum0R :: forall m r target. NumScalar r
             => Delta target (TKR m r)
             -> Delta target (TKScalar r)
  DeltaSumR :: forall m n x target.
               SNat m -> Delta target (TKR2 (m + n) x)
            -> Delta target (TKR2 n x)
  DeltaScatterR :: forall m n p r target.
                   SNat m -> SNat n -> SNat p
                -> IShR p -> Delta target (TKR2 (m + n) r)
                -> (IxROf target m -> IxROf target p)
                -> Delta target (TKR2 (p + n) r)
  DeltaReplicate0NR :: forall m r target. GoodScalar r
                    => IShR m -> Delta target (TKScalar r)
                    -> Delta target (TKR m r)
  DeltaReplicateR :: forall m n x target.
                     IShR m -> Delta target (TKR2 n x)
                  -> Delta target (TKR2 (m + n) x)
  DeltaGatherR :: forall m n p r target.
                  SNat m -> SNat n -> SNat p
               -> IShR m -> Delta target (TKR2 (p + n) r)
               -> (IxROf target m -> IxROf target p)
               -> Delta target (TKR2 (m + n) r)
  DeltaAppendR :: Delta target (TKR2 (1 + n) r)
               -> Delta target (TKR2 (1 + n) r)
               -> Delta target (TKR2 (1 + n) r)
  DeltaSliceR :: Int -> Int -> Delta target (TKR2 (1 + n) r)
              -> Delta target (TKR2 (1 + n) r)
  DeltaReverseR :: Delta target (TKR2 (1 + n) r)
                -> Delta target (TKR2 (1 + n) r)
  DeltaTransposeR :: Permutation.PermR -> Delta target (TKR2 n r)
                  -> Delta target (TKR2 n r)
  DeltaReshapeR :: IShR m -> Delta target (TKR2 n r)
                -> Delta target (TKR2 m r)

  -- Shaped tensor operations
  DeltaCastS :: ( NumScalar r1, Differentiable r1
                , NumScalar r2, Differentiable r2 )
             => Delta target (TKS sh r1) -> Delta target (TKS sh r2)
  DeltaDot0S :: (NumScalar r, Show (target (TKS sh r)))
             => target (TKS sh r) -> Delta target (TKS sh r)
             -> Delta target (TKScalar r)
  DeltaIndex0S :: forall shm r target. GoodScalar r
               => Delta target (TKS shm r) -> IxSOf target shm
               -> Delta target (TKScalar r)
  DeltaIndexS :: forall shm shn r target.
                 ShS shn
              -> Delta target (TKS2 (shm ++ shn) r) -> IxSOf target shm
              -> Delta target (TKS2 shn r)
  DeltaFromVectorLinearS :: forall shm r target. GoodScalar r
                         => ShS shm
                         -> Data.Vector.Vector (Delta target (TKScalar r))
                         -> Delta target (TKS shm r)
  DeltaFromVectorS :: forall shm shn x target.
                      ShS shm
                   -> Data.Vector.Vector (Delta target (TKS2 shn x))
                   -> Delta target (TKS2 (shm ++ shn) x)
  DeltaSum0S :: forall shm r target. NumScalar r
             => Delta target (TKS shm r)
             -> Delta target (TKScalar r)
  DeltaSumS :: forall shm shn x target.
               ShS shm -> Delta target (TKS2 (shm ++ shn) x)
            -> Delta target (TKS2 shn x)
  DeltaScatterS :: forall shm shn shp r target.
                   ShS shm -> ShS shn -> ShS shp
                -> Delta target (TKS2 (shm ++ shn) r)
                -> (IxSOf target shm -> IxSOf target shp)
                -> Delta target (TKS2 (shp ++ shn) r)
  DeltaReplicate0NS :: forall shm r target. GoodScalar r
                    => ShS shm -> Delta target (TKScalar r)
                    -> Delta target (TKS shm r)
  DeltaReplicateS :: forall shm shn x target.
                     ShS shm -> Delta target (TKS2 shn x)
                  -> Delta target (TKS2 (shm ++ shn) x)
  DeltaGatherS :: forall shm shn shp r target.
                  ShS shm -> ShS shn -> ShS shp
               -> Delta target (TKS2 (shp ++ shn) r)
               -> (IxSOf target shm -> IxSOf target shp)
               -> Delta target (TKS2 (shm ++ shn) r)
  DeltaAppendS :: forall target r m n sh.
                  Delta target (TKS2 (m ': sh) r)
               -> Delta target (TKS2 (n ': sh) r)
               -> Delta target (TKS2 ((m + n) ': sh) r)
  DeltaSliceS :: SNat i -> SNat n -> SNat k
              -> Delta target (TKS2 (i + n + k ': sh) r)
              -> Delta target (TKS2 (n ': sh) r)
  DeltaReverseS :: Delta target (TKS2 (n ': sh) r)
                -> Delta target (TKS2 (n ': sh) r)
  DeltaTransposeS :: forall perm sh r target.
                     (Permutation.IsPermutation perm, Rank perm <= Rank sh)
                  => Permutation.Perm perm
                  -> Delta target (TKS2 sh r)
                  -> Delta target (TKS2 (Permutation.PermutePrefix perm sh) r)
  DeltaReshapeS :: Product sh ~ Product sh2
                => ShS sh2
                -> Delta target (TKS2 sh r)
                -> Delta target (TKS2 sh2 r)

  -- Mixed tensor operations
  DeltaCastX :: ( NumScalar r1, Differentiable r1
                , NumScalar r2, Differentiable r2 )
             => Delta target (TKX sh r1) -> Delta target (TKX sh r2)
  DeltaDot0X :: (NumScalar r, Show (target (TKX sh r)))
             => target (TKX sh r) -> Delta target (TKX sh r)
             -> Delta target (TKScalar r)
  DeltaIndex0X :: forall shm r target. GoodScalar r
               => Delta target (TKX shm r) -> IxXOf target shm
               -> Delta target (TKScalar r)
  DeltaIndexX :: forall shm shn r target.
                 StaticShX shn
              -> Delta target (TKX2 (shm ++ shn) r) -> IxXOf target shm
              -> Delta target (TKX2 shn r)
  DeltaFromVectorLinearX :: forall shm r target. GoodScalar r
                         => IShX shm
                         -> Data.Vector.Vector (Delta target (TKScalar r))
                         -> Delta target (TKX shm r)
  DeltaFromVectorX :: forall shm shn x target.
                      IShX shm
                   -> Data.Vector.Vector (Delta target (TKX2 shn x))
                   -> Delta target (TKX2 (shm ++ shn) x)
  DeltaSum0X :: forall shm r target. NumScalar r
             => Delta target (TKX shm r)
             -> Delta target (TKScalar r)
  DeltaSumX :: forall shm shn x target.
               StaticShX shm -> Delta target (TKX2 (shm ++ shn) x)
            -> Delta target (TKX2 shn x)
  DeltaScatterX :: StaticShX shm -> StaticShX shn -> StaticShX shp
                -> IShX shp -> Delta target (TKX2 (shm ++ shn) r)
                -> (IxXOf target shm -> IxXOf target shp)
                -> Delta target (TKX2 (shp ++ shn) r)
  DeltaReplicate0NX :: forall shm r target. GoodScalar r
                    => IShX shm -> Delta target (TKScalar r)
                    -> Delta target (TKX shm r)
  DeltaReplicateX :: forall shm shn x target.
                     IShX shm -> Delta target (TKX2 shn x)
                  -> Delta target (TKX2 (shm ++ shn) x)
  DeltaGatherX :: StaticShX shm -> StaticShX shn -> StaticShX shp
               -> IShX shm -> Delta target (TKX2 (shp ++ shn) r)
               -> (IxXOf target shm -> IxXOf target shp)
               -> Delta target (TKX2 (shm ++ shn) r)
  DeltaAppendX :: forall m n sh r target.
                  Delta target (TKX2 (m ': sh) r)
               -> Delta target (TKX2 (n ': sh) r)
               -> Delta target (TKX2 (AddMaybe m n ': sh) r)
  DeltaSliceX :: SMayNat Int i -> SMayNat Int n -> SMayNat Int k
              -> Delta target (TKX2 (AddMaybe (AddMaybe i n) k ': sh) r)
              -> Delta target (TKX2 (n ': sh) r)
  DeltaReverseX :: Delta target (TKX2 (mn ': sh) r)
                -> Delta target (TKX2 (mn ': sh) r)
  DeltaTransposeX :: forall perm sh r target.
                     (Permutation.IsPermutation perm, Rank perm <= Rank sh)
                  => Permutation.Perm perm
                  -> Delta target (TKX2 sh r)
                  -> Delta target (TKX2 (Permutation.PermutePrefix perm sh) r)
  DeltaReshapeX :: IShX sh2 -> Delta target (TKX2 sh r)
                -> Delta target (TKX2 sh2 r)

  -- Conversions
  DeltaConvert :: TKConversion a b -> Delta target a -> Delta target b

deriving instance Show (IntOf target) => Show (Delta target y)

-- | A newtype defined only to cut the knot of 'Show' instances in 'DeltaScale'
-- that are problematic to pass around as dictionaries without
-- bloating each constructor. The @DeltaScale@ constructor appears
-- in delta expressions a lot and so the primal
-- subterm would bloat the pretty-printed output (though OTOH the primal
-- terms are often important).
--
-- Possibly, @Has Show (Delta target)@ is a better solution.
type NestedTarget :: Target -> Target
type role NestedTarget nominal nominal
newtype NestedTarget target y = NestedTarget (target y)

instance Show (NestedTarget target y) where
  showsPrec _ _ = showString "<primal>"


-- * Full tensor kind derivation for delta expressions

-- | Full tensor kind derivation for delta expressions.
ftkDelta :: forall target y.
            Delta target y -> FullShapeTK y
ftkDelta = \case
  DeltaShare i _ -> nodeIdToFTK i
  DeltaInput i -> inputIdToFTK i

  DeltaPair t1 t2 -> FTKProduct (ftkDelta t1) (ftkDelta t2)
  DeltaProject1 v -> case ftkDelta v of
    FTKProduct ftk1 _ -> ftk1
  DeltaProject2 v -> case ftkDelta v of
    FTKProduct _ ftk2 -> ftk2
  DeltaMapAccumL k bftk _eftk _q _es _df _rf acc0' _es' ->
    FTKProduct (ftkDelta acc0') (buildFTK k bftk)

  DeltaZero ftk -> ftk
  DeltaScale _ d -> ftkDelta d
  DeltaAdd (DeltaShare i _) _ -> nodeIdToFTK i
  DeltaAdd _ e -> ftkDelta e

  DeltaCastK{} -> FTKScalar

  DeltaCastR d -> case ftkDelta d of
    FTKR sh _ -> FTKR sh FTKScalar
  DeltaDot0R{} -> FTKScalar
  DeltaIndex0R _ _ -> FTKScalar
  DeltaIndexR _ d ix | SNat <- ixrRank ix -> case ftkDelta d of
    FTKR sh x -> FTKR (shrDrop sh) x
  DeltaFromVectorLinearR shm _ -> FTKR shm FTKScalar
  DeltaFromVectorR shm l -> case V.uncons l of
    Nothing -> error "ftkDelta: empty vector"
    Just (d, _) | FTKR shn x <- ftkDelta d -> FTKR (shm `shrAppend` shn) x
  DeltaSum0R{} -> FTKScalar
  DeltaSumR @m SNat d -> case ftkDelta d of
    FTKR shmshn x -> FTKR (shrDrop @m shmshn) x
  DeltaScatterR (SNat @m) _ _ shp d _ -> case ftkDelta d of
    FTKR sh x -> FTKR (shp `shrAppend` shrDrop @m sh) x
  DeltaReplicate0NR shm _ -> FTKR shm FTKScalar
  DeltaReplicateR shm d -> case ftkDelta d of
    FTKR shn x -> FTKR (shm `shrAppend` shn) x
  DeltaGatherR _ _ (SNat @p) shm d _ -> case ftkDelta d of
    FTKR sh x -> FTKR (shm `shrAppend` shrDrop @p sh) x
  -- Depite the warning, the pattern match is exhaustive and if a dummy
  -- pattern is added, GHC 9.14.1 complains about that, in turn.
  DeltaAppendR a b -> case ftkDelta a of
    FTKR (ai :$: ash) x -> case ftkDelta b of
      FTKR (bi :$: _) _ -> FTKR (ai + bi :$: ash) x
  DeltaSliceR _ n d -> case ftkDelta d of
    FTKR sh x -> FTKR (n :$: shrTail sh) x
  DeltaReverseR d -> ftkDelta d
  DeltaTransposeR perm d -> case ftkDelta d of
    FTKR sh x -> FTKR (shrPermutePrefix perm sh) x
  DeltaReshapeR sh d -> case ftkDelta d of
    FTKR _ x -> FTKR sh x

  DeltaCastS d -> case ftkDelta d of
    FTKS sh FTKScalar -> FTKS sh FTKScalar
  DeltaDot0S{} -> FTKScalar
  DeltaIndex0S _ _ -> FTKScalar
  DeltaIndexS shn d _ix -> case ftkDelta d of
    FTKS _ x -> FTKS shn x
  DeltaFromVectorLinearS shm _ -> FTKS shm FTKScalar
  DeltaFromVectorS shm l -> case V.uncons l of
    Nothing -> error "ftkDelta: empty vector"
    Just (d, _) | FTKS shn x <- ftkDelta d -> FTKS (shm `shsAppend` shn) x
  DeltaSum0S{} -> FTKScalar
  DeltaSumS @shm @shn shm d | SNat <- shsRank shm -> case ftkDelta d of
    FTKS shmshn x ->
      gcastWith (unsafeCoerceRefl :: Drop (Rank shm) (shm ++ shn) :~: shn) $
      FTKS (shsDrop @(Rank shm) shmshn) x
  DeltaScatterS _shm shn shp d _ -> case ftkDelta d of
    FTKS _ x -> FTKS (shp `shsAppend` shn) x
  DeltaReplicate0NS shm _ -> FTKS shm FTKScalar
  DeltaReplicateS shm d -> case ftkDelta d of
    FTKS shn x -> FTKS (shm `shsAppend` shn) x
  DeltaGatherS shm shn _shp d _ -> case ftkDelta d of
    FTKS _ x -> FTKS (shm `shsAppend` shn) x
  DeltaAppendS a b -> case (ftkDelta a, ftkDelta b) of
    (FTKS (m :$$ sh) x, FTKS (n :$$ _) _) -> FTKS (snatPlus m n :$$ sh) x
  DeltaSliceS _ n@SNat _ d -> case ftkDelta d of
    FTKS (_ :$$ sh) x -> FTKS (n :$$ sh) x
  DeltaReverseS d -> ftkDelta d
  DeltaTransposeS perm d -> case ftkDelta d of
    FTKS sh x -> FTKS (shsPermutePrefix perm sh) x
  DeltaReshapeS sh2 d -> case ftkDelta d of
    FTKS _ x -> FTKS sh2 x

  DeltaCastX d -> case ftkDelta d of
    FTKX sh FTKScalar -> FTKX sh FTKScalar
  DeltaDot0X{} -> FTKScalar
  DeltaIndex0X _ _ -> FTKScalar
  DeltaIndexX @shm @shn _shn d ix -> case ftkDelta d of
    FTKX sh x | SNat @len <- ixxRank ix ->
      gcastWith (unsafeCoerceRefl :: Drop (Rank shm) (shm ++ shn) :~: shn) $
      FTKX (shxDrop @len sh) x
  DeltaFromVectorLinearX shm _ -> FTKX shm FTKScalar
  DeltaFromVectorX shm l -> case V.uncons l of
    Nothing -> error "ftkDelta: empty vector"
    Just (d, _) | FTKX shn x <- ftkDelta d -> FTKX (shm `shxAppend` shn) x
  DeltaSum0X{} -> FTKScalar
  DeltaSumX @shm @shn shm d | SNat <- ssxRank shm -> case ftkDelta d of
    FTKX shmshn x ->
      gcastWith (unsafeCoerceRefl :: Drop (Rank shm) (shm ++ shn) :~: shn) $
      FTKX (shxDrop @(Rank shm) shmshn) x
  DeltaScatterX @_ @shn ssm _ _ shp d _ -> case ftkDelta d of
    FTKX sh x -> FTKX (shp `shxAppend` shxDropSSX @_ @shn ssm sh) x
  DeltaReplicate0NX shm _ -> FTKX shm FTKScalar
  DeltaReplicateX shm d -> case ftkDelta d of
    FTKX shn x -> FTKX (shm `shxAppend` shn) x
  DeltaGatherX @_ @shn _ _ ssp shm d _ -> case ftkDelta d of
    FTKX sh x -> FTKX (shm `shxAppend` shxDropSSX @_ @shn ssp sh) x
  DeltaAppendX a b -> case (ftkDelta a, ftkDelta b) of
    (FTKX (m :$% sh) x, FTKX (n :$% _) _) ->
      FTKX (smnAddMaybe m n :$% sh) x
  DeltaSliceX _ n _ d -> case ftkDelta d of
    FTKX (_ :$% sh) x -> FTKX (n :$% sh) x
  DeltaReverseX d -> ftkDelta d
  DeltaTransposeX perm d -> case ftkDelta d of
    FTKX sh x -> FTKX (shxPermutePrefix perm sh) x
  DeltaReshapeX sh2 d -> case ftkDelta d of
    FTKX _ x -> FTKX sh2 x

  DeltaConvert c d -> convertFTK c $ ftkDelta d