packages feed

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

{-# LANGUAGE UndecidableInstances #-}
-- | The environment datatype and operations for creating and accessing it.
module HordeAd.Core.AstEnv
  ( AstEnv, SpanTargetFam, SpanTarget(..)
  , lemPlainOfSpan, dictSpanFam, toFromFullSpan, toFullSpan, fromFullSpan
  , emptyEnv  -- TODO: showsPrecAstEnv
  , extendEnv, extendEnvI, extendEnvVarsS
  ) where

import Prelude

import Data.Dependent.EnumMap.Strict (DEnumMap)
import Data.Dependent.EnumMap.Strict qualified as DMap
import Data.Kind (Type)
import Data.Proxy (Proxy)
import Data.Type.Equality ((:~:) (Refl))

import Data.Array.Nested.Shaped.Shape
import Data.Array.Nested.Types (unsafeCoerceRefl)

import HordeAd.Core.Ast
import HordeAd.Core.Ops
import HordeAd.Core.TensorKind
import HordeAd.Core.Types

-- | The environment that keeps values assigned to variables
-- during interpretation.
type AstEnv :: Target -> Type
type AstEnv target = DEnumMap AstVarName (SpanTarget target)

type family SpanTargetFam target s :: Target where
  -- This variant causes typing problems;
--SpanTargetFam target (PrimalStepSpan s2) = SpanTargetFam (PrimalOf target) s2
  SpanTargetFam target (PrimalStepSpan s2) = PrimalOf (SpanTargetFam target s2)
  SpanTargetFam target DualSpan = target  -- !!! not: DualOf target, see below
  SpanTargetFam target FullSpan = target
  SpanTargetFam target PlainSpan = PlainOf target
  -- The equation for DualSpan is hacky to prevent interpreter crashing fast
  -- due to no tensor operations defined for Delta expressions. Instead of
  -- producing the Delta expressions (in non-symbolic instances), we produce
  -- full dual numbers for which tensor operations are always defined.
  -- To keep the artificial representation sound, we try to manually ensure
  -- in the interpreter code that the primal part of these dual numbers
  -- is always zero, so only the dual part (the Delta expression) matters.

-- This is needed, because type families can't yet be partially applied.
type role SpanTarget nominal nominal
newtype SpanTarget target s_y = SpanTarget (SpanTargetFamUncurried target s_y)
type family SpanTargetFamUncurried target s_y :: Type where
  SpanTargetFamUncurried target '(s, y) = SpanTargetFam target s y

lemPlainOfSpan :: ADReady target
               => Proxy target -> SAstSpan s
               -> PlainOf (SpanTargetFam target s) :~: PlainOf target
{-# INLINE lemPlainOfSpan #-}
lemPlainOfSpan _ = \case
  SFullSpan -> Refl
  SPrimalStepSpan SFullSpan -> Refl
  -- This is true morally and in all instances, even though it's
  -- not derivable.
  SPrimalStepSpan _ -> unsafeCoerceRefl
  SDualSpan -> Refl  -- due to the dual hack
  SPlainSpan -> Refl

dictSpanFam :: ADReady target
            => Proxy target -> SAstSpan s
            -> Dict0 (ADReadyClasses (SpanTargetFam target s))
{-# INLINE dictSpanFam #-}
dictSpanFam _ = \case
  SFullSpan -> Dict0
  SPrimalStepSpan SFullSpan -> Dict0
  SPrimalStepSpan _ ->
    error "dictSpanFam: these operations on nested primal terms are illegal"
  SDualSpan -> Dict0  -- due to the dual hack
  SPlainSpan -> Dict0

toFromFullSpan
  :: BaseTensor target
  => SingletonTK y -> SAstSpan s
  -> ( SpanTargetFam target s y -> SpanTargetFam target FullSpan y
     , SpanTargetFam target FullSpan y -> SpanTargetFam target s y )
{-# INLINE toFromFullSpan #-}
toFromFullSpan stk = \case
  SFullSpan -> (id, id)
  SPrimalStepSpan SFullSpan -> (tfromPrimal stk, tprimalPart)
  SPrimalStepSpan _ ->
    error "toFromFullSpan: nested primal numbers are not converted to full dual numbers"
  {- This would require an arbitrarily large number of dictionaries
     for PrimalOf (PrimalOf (... (PrimalOf target)))
     or equating the type of primal numbers and dual numbers with
     a zero dual part, after a finite number of steps, which is troublesome,
     or at step zero, which is crude and gives too few typing hints.
  SPrimalStepSpan s4 ->
    let (toFull, fromFull) = toFromFull stk s4
    in (toFull . tfromPrimal stk, tprimalPart . fromFull)
  -}
  SDualSpan -> (id, id)  -- due to the dual hack
  SPlainSpan -> (tfromPlain stk, tplainPart)

toFullSpan :: BaseTensor target
           => SingletonTK y -> SAstSpan s
           -> SpanTargetFam target s y -> SpanTargetFam target FullSpan y
{-# INLINE toFullSpan #-}
toFullSpan stk = \case
  SFullSpan -> id
  SPrimalStepSpan SFullSpan -> tfromPrimal stk
  SPrimalStepSpan _ ->
    error "toFullSpan: nested primal numbers are not converted to full dual numbers"
  SDualSpan -> id  -- due to the dual hack
  SPlainSpan -> tfromPlain stk

fromFullSpan :: BaseTensor target
             => SAstSpan s
             -> SpanTargetFam target FullSpan y -> SpanTargetFam target s y
{-# INLINE fromFullSpan #-}
fromFullSpan = \case
  SFullSpan -> id
  SPrimalStepSpan SFullSpan -> tprimalPart
  SPrimalStepSpan _ ->
    error "fromFullSpan: nested primal numbers are not converted to full dual numbers"
  SDualSpan -> id  -- due to the dual hack
  SPlainSpan -> tplainPart

emptyEnv :: AstEnv target
emptyEnv = DMap.empty

{- TODO:
showsPrecAstEnv
  :: AllTargetShow target
  => Int -> AstEnv target -> ShowS
showsPrecAstEnv d demap =
  showParen (d > 10) $
    showString "fromList "
    . showListWith
        (\(k :=> SpanTarget target) ->
           withKnownSTK (ftkToSTK $ varNameToFTK k) $
           showsPrec 2 k . showString " :=> " . showsPrec 1 target)
        (DMap.toList demap)
-}

extendEnv :: forall target s y.
             AstVarName '(s, y) -> SpanTargetFam target s y -> AstEnv target
          -> AstEnv target
extendEnv !var !t !env =
  if DMap.member var env
  then error $ "extendEnv: duplicate " ++ show var
  else DMap.insert var (SpanTarget t) env

extendEnvI :: IntVarName -> IntOf target -> AstEnv target
           -> AstEnv target
extendEnvI !var !i !env = extendEnv var i env

extendEnvVarsS :: forall target sh.
                  AstVarListS sh -> IxSOf target sh -> AstEnv target
               -> AstEnv target
extendEnvVarsS ZS ZIS !env = env
extendEnvVarsS (var ::$ vars) (i :.$ ix) env =
  extendEnvVarsS vars ix (extendEnvI var i env)