dvda-0.2.2: Dvda/CallNative.hs
{-# OPTIONS_GHC -Wall #-}
{-# Language TypeOperators #-}
{-# Language TypeFamilies #-}
{-# Language FlexibleContexts #-}
{-# Language FlexibleInstances #-}
{-# Language GADTs #-}
module Dvda.CallNative ( toNative
, nativeCall
, nativeDiff
, nativeGrad
, nativeJacob
, nativeRun
) where
import Data.Hashable ( Hashable )
import qualified Data.IntMap as IM
import Data.List ( mapAccumL )
import Data.Maybe ( fromJust, catMaybes )
import Numeric.LinearAlgebra ( Element, Container )
import Dvda
import Dvda.BinUn ( BinOp(Mul), applyBinary, applyUnary )
import Dvda.Expr ( Expr(..), Const(..), dim )
import Dvda.Graph ( FunGraph(..), DvdaDim(..), DynamicExpr, fgLookup, fgExprFromKey )
import Dvda.HashMap ( HashMap )
import qualified Dvda.HashMap as HM
import Dvda.SymMonad ( rad )
class (Hashable (INumT b), Eq (INumT b), Element (INumT b)) => NativeInputs b where
type INumT b
toReplacements :: FunGraph (INumT b) b c -> b -> HashMap (DynamicExpr (INumT b)) (DynamicExpr (INumT b))
insToSyms :: DvdaDim sh => FunGraph a b c -> Expr sh a -> Expr sh a -> Maybe (DynamicExpr a, DynamicExpr a)
insToSyms fg e@(ERef _ _ k) out = fmap (\x -> (makeDynamic x, makeDynamic out)) $ fgExprFromKey (dim e) k fg
insToSyms _ _ _ = Nothing
instance (DvdaDim sh, Hashable a, Element a, Eq a) => NativeInputs (Expr sh a) where
type INumT (Expr sh a) = a
toReplacements fg@(FunGraph _ _ ins _) xs = HM.fromList $ catMaybes [insToSyms fg ins xs]
instance (DvdaDim sh, Hashable a, Element a, Eq a) => NativeInputs [Expr sh a] where
type INumT [Expr sh a] = a
toReplacements fg@(FunGraph _ _ ins _) xs = HM.fromList $ catMaybes $ zipWith (insToSyms fg) ins xs
instance (DvdaDim sh, Hashable a, Element a, Eq a) => NativeInputs [[Expr sh a]] where
type INumT [[Expr sh a]] = a
toReplacements fg@(FunGraph _ _ ins _) xs =
HM.fromList $ catMaybes $ zipWith (insToSyms fg) (concat ins) (concat xs)
instance (NativeInputs a, NativeInputs b, INumT a ~ INumT b) => NativeInputs (a :* b) where
type INumT (a :* b) = INumT a
toReplacements (FunGraph hm im (in0 :* in1) outs) (x0 :* x1) = HM.union r0 r1
where
r0 = toReplacements (FunGraph hm im in0 outs) x0
r1 = toReplacements (FunGraph hm im in1 outs) x1
---------------------------------------------------------------------------
class NativeOutput c where
type ONumT c
traverseOutputs :: (NativeInputs b)
=> HashMap (DynamicExpr (ONumT c)) (DynamicExpr (ONumT c))
-> FunGraph (ONumT c) b c
-> c
-> (FunGraph (ONumT c) b c, c)
instance (DvdaDim sh, Floating a, Num (Vector a), Container Vector a, Hashable a, Eq a)
=> NativeOutput (Expr sh a) where
type ONumT (Expr sh a) = a
traverseOutputs = eval
instance (DvdaDim sh, Floating a, Num (Vector a), Container Vector a, Hashable a, Eq a)
=> NativeOutput [Expr sh a] where
type ONumT [Expr sh a] = a
traverseOutputs = mapAccumL . eval
instance (DvdaDim sh, Floating a, Num (Vector a), Container Vector a, Hashable a, Eq a)
=> NativeOutput [[Expr sh a]] where
type ONumT [[Expr sh a]] = a
traverseOutputs = mapAccumL . mapAccumL . eval
instance (NativeOutput a, NativeOutput b, ONumT a ~ ONumT b) => NativeOutput (a :* b) where
type ONumT (a :* b) = ONumT a
traverseOutputs replacementMap (FunGraph hm0 im0 ins outs) (x' :* y') = (FunGraph hm2 im2 ins outs, x :* y)
where
err = error "DON'T LOOK AT THESE OUTPUTS YA GOON"
(FunGraph hm1 im1 _ _, x) = traverseOutputs replacementMap (FunGraph hm0 im0 ins err) x'
(FunGraph hm2 im2 _ _, y) = traverseOutputs replacementMap (FunGraph hm1 im1 ins err) y'
replace :: (Hashable a, Eq a, Element a, DvdaDim sh) => FunGraph a b c -> Expr sh a -> Expr sh a -> FunGraph a b c
replace fg0@(FunGraph hm0 im0 ins outs) old new = FunGraph hm im ins outs
where
(k, _) = fromJust $ fgLookup old fg0
hm = HM.insert (makeDynamic new) (k, error "after callNative has happened you can't look at symSets") hm0
im = IM.insert k (makeDynamic new) im0
eval :: (Hashable a, Eq a, Floating a, Num (Vector a), Container Vector a, DvdaDim sh)
=> HashMap (DynamicExpr a) (DynamicExpr a) -> FunGraph a b c -> Expr sh a -> (FunGraph a b c, Expr sh a)
eval _ _ (EDimensionless _) = error "WHO PUT AN EDimensionless IN THIS GRAPH"
eval _ _ (EDeriv _ _) = error "WHO PUT AN EDeriv IN THIS GRAPH"
eval _ _ (EGrad _ _) = error "WHO PUT AN EDeriv IN THIS GRAPH"
eval _ _ (EJacob _ _) = error "WHO PUT AN EJacob IN THIS GRAPH"
eval replacementMap fg expr@(ERef _ _ k) = eval replacementMap fg (fromJust $ fgExprFromKey (dim expr) k fg)
eval _ fg expr@(EConst _) = (fg, expr)
eval replacementMap fg0 expr@(ESym _ _) = case HM.lookup (makeDynamic expr) replacementMap of
Nothing -> (fg0, expr)
Just replacementExpr' -> (fg1, replacementExpr)
where
replacementExpr = fromDynamic (dim expr) replacementExpr'
fg1 = replace fg0 expr replacementExpr
eval replacementMap fg0 expr@(EUnary op x') = (fg2, newExpr)
where
(fg1, x) = eval replacementMap fg0 x'
newExpr = applyUnary op x
fg2 = replace fg1 expr newExpr
eval replacementMap fg0 expr@(EBinary op x' y') = (fg3, newExpr)
where
(fg1, x) = eval replacementMap fg0 x'
(fg2, y) = eval replacementMap fg1 y'
newExpr = applyBinary op x y
fg3 = replace fg2 expr newExpr
eval replacementMap fg (EScale (EConst (CSingleton _ x)) y) = eval replacementMap fg z
where
z = applyBinary Mul (EConst (CSingleton (dim y) x)) y
eval replacementMap fg0 expr@(EScale x' y') = (fg3, newExpr)
where
(fg1, x) = eval replacementMap fg0 x'
(fg2, y) = eval replacementMap fg1 y'
newExpr = case x of EConst (CSingleton _ c) -> applyBinary Mul (EConst (CSingleton (dim y) c)) y
_ -> EScale x y
fg3 = replace fg2 expr newExpr
toNative :: (Show a, NativeInputs b, NativeOutput c, a ~ INumT b, a ~ ONumT c) => FunGraph a b c -> b -> c
toNative fg@(FunGraph _ _ _ outs) xs = snd $ traverseOutputs replacementMap fg outs
where
replacementMap = toReplacements fg xs
-- | Convenience function for natively computing function This is
-- expected to be very slow. Using code generation instead is
-- recommended
nativeCall :: (Hashable a, Eq a, Show a, Element a, Floating a, Num (Vector a), Container Vector a)
=> (Expr Z a -> [Expr Z a]) -> Expr Z a -> [Expr Z a]
nativeCall f = toNative $ runFunGraph $ do
let x = sym "x"
inputs_ x
outputs_ (f x)
-- | Lift a unary function over @Floating a => a@ to a function over
-- @Floating a => Expr Z a@
liftNative :: (Hashable a, Eq a, Show a, Element a, Floating a,
Num (Vector a), Container Vector a,
Floating b, b ~ Expr Z a) => (b -> b) -> Expr Z a -> Expr Z a
liftNative f x = case nativeCall (return . f) x of
[] -> error "Function didn't return."
(v:_) -> v
-- | Evaluate a unary function over @Floating a => a@ using Dvda's
-- internal machinery. The typeclass constraints should make sure the
-- error doesn't happen, but it could anyway.
nativeRun :: (Hashable a, Eq a, Show a, Element a, Floating a,
Num (Vector a), Container Vector a,
Floating b, b ~ Expr Z a) => (b -> b) -> a -> a
nativeRun f x = case liftNative f (EConst (CSingleton Z x)) of
(EConst (CSingleton Z v)) -> v
_ -> error "Function must be unary over class Floating."
-- | Convenience function for natively computing jacobian, requires
-- you to pass the number of inputs. This is expected to be very
-- slow. Using code generation instead is recommended
nativeJacob :: (Hashable a, Eq a, Show a, Element a, Floating a, Num (Vector a), Container Vector a)
=> Int -> ([Expr Z a] -> [Expr Z a]) -> [Expr Z a] -> [[Expr Z a]]
nativeJacob n f = toNative $ runFunGraph $ do
let xs = map (\k -> sym ("x_"++show k)) [0..(n-1::Int)]
inputs_ xs
ys <- mapM (flip rad xs) (f xs)
outputs_ ys
-- | Convenience function for natively computing gradient, requires
-- you to pass the number of inputs. This is expected to be very
-- slow. Using code generation instead is recommended
nativeGrad :: (Hashable a, Eq a, Show a, Element a, Floating a, Num (Vector a), Container Vector a)
=> Int -> ([Expr Z a] -> Expr Z a) -> [Expr Z a] -> [Expr Z a]
nativeGrad n f = toNative $ runFunGraph $ do
let xs = map (\k -> sym ("x_"++show k)) [0..(n-1::Int)]
inputs_ xs
ys <- rad (f xs) xs
outputs_ ys
-- | Convenience function for natively computing a derivative. This
-- is expected to be very slow. Using code generation instead is
-- recommended
nativeDiff :: (Hashable a, Eq a, Show a, Element a, Floating a, Num (Vector a), Container Vector a)
=> (Expr Z a -> Expr Z a) -> Expr Z a -> Expr Z a
nativeDiff f = toNative $ runFunGraph $ do
let x = sym "x"
inputs_ x
[y] <- rad (f x) [x]
outputs_ y